- SignalDesk2 hr ago
Original Summary
集成了去广告和一些bug修复 已知问题 回滚后悬浮预览不跟随 :回滚/前进后封面、标题已恢复旧内容,但悬浮预览视频(B 站组件动态注入)无法改回旧视频,当前策略为回滚后屏蔽该卡预览,避免误显示最新内容。浏览器层面无法让回滚卡片播放旧预览,属固有限制。 有能力的老友可以试试修复一下,我反正是放弃了 回滚会短暂恢复广告 :快照保存时广告卡节点仍在 DOM,回滚到含广告的历史帧时广告会先重现、再由去广告逻辑重新隐藏(最终无广告,可能会闪一下)。 部分推广卡可能漏杀 :广告形态随账号/视口/刷新变化,如特殊推广卡未被现有检测命中会漏出,需按实际卡片结构补充规则。 个别布局错位 :占位策略已解决 4 列/5 列主要错位,但特殊推广卡在部分布局下仍可能出现少量网格重排。如图:布局错位 // ==UserScript== // @name Bilibili Feed Card Rollback & Forward (All-in-One) // @name:zh-CN Bilibili 推荐卡片回滚与前进(集成去广告) // @namespace http://tampermonkey.net/ // @version 1.1.7 // @description Save and restore Bilibili feed card information (rollback/forward), integrated with ad blocking. // @description:zh-CN 保存并恢复B站首页推荐卡片信息(回滚/前进),并集成B站去广告功能(屏蔽视频投票打分三连弹窗、小火箭推广、首页左滑推荐栏、直播自动播放等)。v1.1.7:仅对真正的单格广告卡片保留空白占位(JS 隐藏其内层);CSS 命中的整块/整列容器类改回彻底移除(display:none),避免整列留白。 // @author Gemini 2.5 Pro // @license MIT // @match ://.bilibili.com/ // @grant none // ==/UserScript== (function () { 'use strict'; / =================================================================================== 免责与授权说明 =================================================================================== - 模块一(回滚/前进)基于 GloryIsMine 的 https://greasyfork.org/zh-CN/scripts/526397-bilibili-feed-card-rollback - 模块二(去广告)集成自“去你妈的批站诈骗广告” v1.20 https://greasyfork.org/scripts/524050 =================================================================================== 模块总览 1. 回滚/前进时间线引擎:仅主页推荐流运行 2. 去广告引擎:全站(www / live / search / t)运行 =================================================================================== v1.1.1:卡片悬浮预览播放器(.v-inline-player)由 B 站 Vue 组件在 hover 时动态注入, 视频取源为 blob: 且 DOM 上无可回写的真实地址。纯 DOM 回滚改不动组件内部状态, 所以回滚/前进后会“截图标题对、预览却还是新视频”。修复:回滚/前进后对已替换卡片 屏蔽悬浮预览(清空播放器 + CSS 屏蔽注入点),避免误显示最新视频;推荐流真实刷新 (换一换/滚动加载)时自动恢复正常预览。 =================================================================================== */ // 屏蔽“已被回滚替换”卡片预览播放器时,注入的样式 id(便于后续移除恢复预览) const PREVIEW_MASK_STYLE_ID = 'bfrf-preview-mask'; // 定义历史记录的最大存储数量 const MAX_HISTORY = 20; // 定义在 sessionStorage 中存储时间线数据的键名 const TIMELINE_KEY = 'bilibili_feed_timeline'; // 定义在 sessionStorage 中存储指针位置的键名 const POINTER_KEY = 'bilibili_feed_pointer'; // 用于存储上一次卡片状态的快照,以检测内容是否发生变化 let lastStateSnapshot = null; // 标志位,用于确认控制按钮是否已经创建,防止重复创建 let controlsCreated = false; // 指向控制按钮的“位置同步”函数(由 createControlButtons 赋值, // 回滚引擎的 MutationObserver 每 tick 调用,确保跟随布局/重渲染而移动) let positionSyncControls = null; /** @description 获取当前页面上所有 feed-card 的详细信息。 @returns {Array|null} 包含所有卡片信息的数组,如果找不到卡片则返回 null。 */ function getFeedCardInfo() { const feedCards = document.querySelectorAll('.feed-card'); if (feedCards.length === 0) return null; return Array.from(feedCards).map(card => { // --- 封面图片处理 --- // B站使用 <picture> 标签来适配不同格式的图片以优化性能 const picture = card.querySelector('.bili-video-card__cover'); const avifSource = picture ? picture.querySelector('source[type="image/avif"]') : null; const webpSource = picture ? picture.querySelector('source[type="image/webp"]') : null; const imgSrc = picture ? picture.querySelector('img') : null; // --- 其他信息获取 --- const linkElement = card.querySelector('.bili-video-card__info--tit a'); const titleElement = card.querySelector('.bili-video-card__info--tit'); const statsTexts = card.querySelectorAll('.bili-video-card__stats--text'); const durationElement = card.querySelector('.bili-video-card__stats__duration'); const authorElement = card.querySelector('.bili-video-card__info--author'); // 返回一个结构化的对象,包含该卡片的所有关键信息 return { // 【关键】保存所有可能的图片源,以确保恢复时能完美显示 coverInfo: { avifSrcset: avifSource ? avifSource.srcset : '', webpSrcset: webpSource ? webpSource.srcset : '', imgSrc: imgSrc ? imgSrc.src : '' }, title: titleElement ? titleElement.textContent.trim() : '', viewCount: statsTexts[0] ? statsTexts[0].textContent.trim() : '0', commentCount: statsTexts[1] ? statsTexts[1].textContent.trim() : '0', duration: durationElement ? durationElement.textContent.trim() : '', author: authorElement ? authorElement.textContent.trim() : '', videoUrl: linkElement ? linkElement.href : '', }; }); } /** @description 将指定的卡片数据渲染到页面上。 @param {Array} cardData - 从历史记录中取出的卡片信息数组。 */ function renderFeedCards(cardData) { const feedCards = document.querySelectorAll('.feed-card'); // 安全检查:如果当前卡片数量和要恢复的数据数量不匹配,则中止操作 if (feedCards.length !== cardData.length) { console.error('BFRF: 卡片数量不匹配,无法渲染。'); return; } feedCards.forEach((card, index) => { const info = cardData[index]; if (!info) return; // 【关键】恢复所有图片源,确保封面图被正确替换 const picture = card.querySelector('.bili-video-card__cover'); if (picture && info.coverInfo) { const avifSource = picture.querySelector('source[type="image/avif"]'); const webpSource = picture.querySelector('source[type="image/webp"]'); const img = picture.querySelector('img'); if (avifSource && info.coverInfo.avifSrcset) avifSource.srcset = info.coverInfo.avifSrcset; if (webpSource && info.coverInfo.webpSrcset) webpSource.srcset = info.coverInfo.webpSrcset; if (img) { if (info.coverInfo.imgSrc) img.src = info.coverInfo.imgSrc; img.alt = info.title; // 同时更新图片的 alt 文本,有利于SEO和可访问性 } } // 恢复标题、链接、统计数据等其他信息 const titleLinks = card.querySelectorAll('.bili-video-card__info--tit a'); titleLinks.forEach(el => { el.textContent = info.title; el.href = info.videoUrl; el.title = info.title; }); const stats = card.querySelectorAll('.bili-video-card__stats--text'); if (stats[0]) stats[0].textContent = info.viewCount; if (stats[1]) stats[1].textContent = info.commentCount; const duration = card.querySelector('.bili-video-card__stats__duration'); if (duration) duration.textContent = info.duration; const author = card.querySelector('.bili-video-card__info--author'); if (author) author.textContent = info.author; const imageLink = card.querySelector('.bili-video-card__image--link'); if (imageLink) imageLink.href = info.videoUrl; }); } /** @description 将新的卡片状态保存到时间线中。 @param {Array} newInfo -
getFeedCardInfo函数返回的新的卡片信息数组。 */ function saveNewState(newInfo) { if (!newInfo || newInfo.length === 0) return- 情报分类:技术学习与提效
- 分类依据:内容涉及技术、AI、软件工具或工程实践
- 信息来源:服务器 / LINUX DO - 最新话题
- 发布时间:2026/10/13 08:39:57
- No replies yet