Bilibili Feed Card Rollback

Save and restore Bilibili feed card information

// ==UserScript==
// @name         Bilibili Feed Card Rollback
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Save and restore Bilibili feed card information
// @author       GloryIsMine
// @license      MIT
// @match        https://www.bilibili.com/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // 最大保存记录数
    const MAX_HISTORY = 10;
    const STORAGE_KEY = 'bilibili_feed_history';

    // 获取所有视频卡片信息的函数
    function getFeedCardInfo() {
        const feedCards = document.querySelectorAll('.feed-card');
        return Array.from(feedCards).map(card => {
            // 获取封面图片
            const coverImg = card.querySelector('.bili-video-card__cover img');
            const coverUrl = coverImg ? coverImg.src : '';

            // 获取视频标题
            const titleElement = card.querySelector('.bili-video-card__info--tit');
            const title = titleElement ? titleElement.textContent.trim() : '';

            // 获取播放次数和评论数
            const statsTexts = card.querySelectorAll('.bili-video-card__stats--text');
            const viewCount = statsTexts[0] ? statsTexts[0].textContent.trim() : '0';
            const commentCount = statsTexts[1] ? statsTexts[1].textContent.trim() : '0';

            // 获取视频时长
            const durationElement = card.querySelector('.bili-video-card__stats__duration');
            const duration = durationElement ? durationElement.textContent.trim() : '';

            // 获取UP主/频道信息
            const authorElement = card.querySelector('.bili-video-card__info--author');
            const author = authorElement ? authorElement.textContent.trim() : '';

            // 获取视频链接
            const linkElement = card.querySelector('.bili-video-card__info--tit a');
            const videoUrl = linkElement ? linkElement.href : '';

            // 获取inline-video元素
            const inlineVideoElement = card.querySelector('video');
            const inlineVideoUrl = inlineVideoElement ? inlineVideoElement.src : '';

            return {
                coverUrl,
                title,
                viewCount,
                commentCount,
                duration,
                author,
                videoUrl,
                inlineVideoUrl
            };
        });
    }

    // 保存feed-card信息到sessionStorage
    function saveFeedCards() {
        const currentInfo = getFeedCardInfo();
        let history = JSON.parse(sessionStorage.getItem(STORAGE_KEY) || '[]');
        
        // 添加新记录到开头
        history.unshift({
            timestamp: new Date().getTime(),
            cards: currentInfo
        });

        // 限制历史记录数量
        if (history.length > MAX_HISTORY) {
            history = history.slice(0, MAX_HISTORY);
        }

        sessionStorage.setItem(STORAGE_KEY, JSON.stringify(history));
    }

    // 恢复feed-card信息
    function restoreFeedCards() {
        const history = JSON.parse(sessionStorage.getItem(STORAGE_KEY) || '[]');
        if (history.length === 0) {
            alert('没有可恢复的历史记录');
            return;
        }

        const lastRecord = history[0];
        const feedCards = document.querySelectorAll('.feed-card');
        
        // 确保有足够的卡片可以恢复
        if (feedCards.length !== lastRecord.cards.length) {
            alert('当前页面卡片数量与历史记录不匹配,无法恢复');
            return;
        }

        // 更新每个卡片的内容
        feedCards.forEach((card, index) => {
            const cardInfo = lastRecord.cards[index];
            
            // 更新封面图片(包括所有图片源)
            const picture = card.querySelector('.bili-video-card__cover');
            if (picture) {
                // 更新所有source标签的srcset
                const sources = picture.querySelectorAll('source');
                sources.forEach(source => {
                    const currentSrcset = source.srcset;
                    // 从当前srcset中提取图片格式后缀(.avif或.webp)
                    const formatMatch = currentSrcset.match(/\.(avif|webp)/);
                    if (formatMatch) {
                        const format = formatMatch[0];
                        // 构建新的srcset,保持原有的尺寸和格式
                        const newSrcset = cardInfo.coverUrl + format;
                        source.srcset = newSrcset;
                    }
                });

                // 更新img标签
                const img = picture.querySelector('img');
                if (img) {
                    img.src = cardInfo.coverUrl;
                    img.alt = cardInfo.title;
                }
            }

            // 更新视频标题和链接
            const titleElements = card.querySelectorAll('.bili-video-card__info--tit a');
            titleElements.forEach(element => {
                element.textContent = cardInfo.title;
                element.href = cardInfo.videoUrl;
                element.title = cardInfo.title;
            });

            // 更新播放次数和评论数
            const statsTexts = card.querySelectorAll('.bili-video-card__stats--text');
            if (statsTexts[0]) statsTexts[0].textContent = cardInfo.viewCount;
            if (statsTexts[1]) statsTexts[1].textContent = cardInfo.commentCount;

            // 更新视频时长
            const durationElement = card.querySelector('.bili-video-card__stats__duration');
            if (durationElement) durationElement.textContent = cardInfo.duration;

            // 更新UP主/频道信息
            const authorElement = card.querySelector('.bili-video-card__info--author');
            if (authorElement) authorElement.textContent = cardInfo.author;

            // 更新所有相关的链接
            const imageLink = card.querySelector('.bili-video-card__image--link');
            if (imageLink) imageLink.href = cardInfo.videoUrl;

            const inlineVideoElement = card.querySelector('video');
            if (inlineVideoElement) inlineVideoElement.src = cardInfo.inlineVideoUrl;
        });

        // 移除已使用的记录
        history.shift();
        sessionStorage.setItem(STORAGE_KEY, JSON.stringify(history));
    }

    // 创建rollback按钮
    function createRollbackButton() {
        const feedRollBtn = document.querySelector('.feed-roll-btn');
        if (!feedRollBtn) return;

        const button = document.createElement('button');
        button.textContent = '回滚';
        button.className = 'feed-rollback-btn';
        button.style.cssText = `
            position: fixed;
            padding: 8px;
            background-color: #00a1d6;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 12px;
            z-index: ${getComputedStyle(feedRollBtn).zIndex};
            width: 54px;
        `;
        button.addEventListener('click', restoreFeedCards);
        
        // 将按钮添加到body中
        document.body.appendChild(button);

        // 更新按钮位置的函数
        function updateButtonPosition() {
            const feedRollBtnRect = feedRollBtn.getBoundingClientRect();
            button.style.left = `${feedRollBtnRect.left}px`;
            button.style.top = `${feedRollBtnRect.bottom + 8}px`;
        }

        // 初始化位置
        updateButtonPosition();

        // 监听可能影响位置的事件
        window.addEventListener('resize', updateButtonPosition);
        window.addEventListener('scroll', updateButtonPosition);
        
        // 监听页面缩放
        window.visualViewport?.addEventListener('resize', updateButtonPosition);
        window.visualViewport?.addEventListener('scroll', updateButtonPosition);

        // 创建MutationObserver来监听DOM变化
        const observer = new MutationObserver(updateButtonPosition);
        observer.observe(document.body, { 
            childList: true, 
            subtree: true,
            attributes: true 
        });
    }

    // 监听roll-btn点击事件
    function setupRollButtonListener() {
        const observer = new MutationObserver((mutations) => {
            const rollBtn = document.querySelector('.roll-btn');
            // 确保roll按钮存在且未初始化
            if (rollBtn && !rollBtn.dataset.rollbackInitialized) {
                rollBtn.dataset.rollbackInitialized = 'true';
                rollBtn.addEventListener('click', saveFeedCards);
                // 创建rollback按钮
                createRollbackButton();
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    }

    // 初始化
    function init() {
        setupRollButtonListener();
    }

    // 等待页面加载完成后初始化
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();

QingJ © 2025

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