自用B站首页增强脚本,提供推送卡片净化、倍速悬停预览、预览进度继承、无痕复制链接、搜索净化等功能
// ==UserScript==
// @name BIliBili首页增强
// @namespace https://www.ordosx.tech/
// @version 1.7
// @description 自用B站首页增强脚本,提供推送卡片净化、倍速悬停预览、预览进度继承、无痕复制链接、搜索净化等功能
// @author OrdosX
// @license MIT
// @match https://www.bilibili.com/
// @icon https://www.bilibili.com/favicon.ico
// @grant GM_setClipboard
// @note 1.7 修复:排除官方继承进度功能干扰
// @note 1.6 重构:完善预览增强逻辑
// @note 1.5 修复:搜索净化未能正常工作
// @note 1.4 更新:添加搜索净化功能,可删除搜索框默认内容与热搜
// ==/UserScript==
(function () {
'use strict';
// 悬停预览时的播放倍速
const PLAYBACK_RATE = 3;
// 通过正则表达式定义需要屏蔽的UP主关键词
const BLOCKED_AUTHORS_REGEX = [
/剪辑/,
/话题/,
/记录/,
/纪录/,
/兔警长/,
/混合体主脑/,
/BB姬/,
/档案/
];
/**
* 将视频节点的播放进度绑定到其父锚点元素
* @param {HTMLVideoElement} node - 视频元素节点
* @param {HTMLAnchorElement} parent_anchor - 链接节点
*/
function bind_progress_to_anchor(node, parent_anchor) {
parent_anchor.dataset.previewDuration = node.duration; // 记录视频总时长
node.addEventListener('timeupdate', () => {
// 仅在播放到新位置时更新预览时间,避免循环播放后重置
if (
'previewTime' in parent_anchor.dataset &&
parent_anchor.dataset.previewTime > node.currentTime
) return;
parent_anchor.dataset.previewTime = node.currentTime;
});
}
/**
* 根据用户的预览时间继承进度到视频链接
* @param {HTMLAnchorElement} node - 链接节点
* @param {URL} url - 链接的 URL 对象
*/
function inherit_preview_progress(node, url) {
if (
!('previewTime' in node.dataset) ||
!('previewDuration' in node.dataset)
) return;
const preview_time = Number(node.dataset.previewTime);
const preview_duration = Number(node.dataset.previewDuration);
if (
// 如果预览时间小于5秒
preview_time < 5 * PLAYBACK_RATE ||
// 或者视频时长小于5分钟并且已经看完
(preview_duration < 299 && preview_time + 3 > preview_duration)
) return;
// 修改 URL 以继承更合理的预览进度
url.searchParams.delete('t');
url.searchParams.append('t', Math.floor(preview_time));
node.href = url.toString();
}
/**
* 提升预览体验,实现预览加速、进度继承功能
* @param {HTMLVideoElement} node - 视频元素节点
*/
function setup_preview_enhancement(node) {
if (!(node instanceof HTMLVideoElement)) return;
// 等待视频元数据加载完毕再执行操作
node.addEventListener('loadedmetadata', () => {
// 包含该视频的父级链接节点
const parent_anchor = node.closest('a');
if (!parent_anchor) return;
node.playbackRate = PLAYBACK_RATE;
bind_progress_to_anchor(node, parent_anchor);
parent_anchor.addEventListener('click', () => {
const url_object = new URL(parent_anchor.href);
inherit_preview_progress(parent_anchor, url_object);
});
});
}
/**
* 删除不需要的推荐卡片
* @param {HTMLElement} node - 推荐卡片元素节点
*/
function remove_unwanted_card(node) {
if (!node.classList) return;
if (
// 移除直播卡片
node.classList.contains('bili-live-card') ||
// 移除分区卡片
node.classList.contains('floor-single-card') ||
// 移除广告卡片
(node.classList.contains('bili-video-card') && node.querySelector('a')?.href.startsWith('https://cm')) ||
// 移除没有链接的被屏蔽广告卡片
(node.classList.contains('bili-video-card') && !node.querySelector('a'))
) {
node.remove();
}
// 如果是屏蔽的UP主,移除相关视频卡片
const authorNode = node.querySelector('.bili-video-card__info--author');
if (authorNode && BLOCKED_AUTHORS_REGEX.some(regex => regex.test(authorNode.innerText))) {
node.remove();
}
}
/**
* 添加"复制链接"按钮到每个视频卡片
* @param {HTMLElement} node - 视频卡片节点
*/
function add_copy_link_button(node) {
// 确保节点是包含 bili-video-card__info--bottom 的视频卡片
if (!(node.classList && node.classList.contains('bili-video-card'))) return;
const bottomContainer = node.querySelector('.bili-video-card__info--bottom');
if (!bottomContainer) return;
// 创建并设置复制链接按钮的样式和事件
const copyButton = document.createElement('a');
copyButton.textContent = '复制链接';
copyButton.style.position = 'absolute';
copyButton.style.display = 'flex';
copyButton.style.right = '0';
copyButton.href = '#';
bottomContainer.appendChild(copyButton);
// 为复制按钮添加点击事件,点击时将链接复制到剪贴板
copyButton.addEventListener('click', (event) => {
event.preventDefault();
const videoLinkElement = bottomContainer.previousElementSibling.querySelector('a');
if (videoLinkElement) {
const videoUrl = videoLinkElement.href;
GM_setClipboard(videoUrl); // 使用 GM_setClipboard 将链接复制到剪贴板
copyButton.textContent = '已复制';
setTimeout(() => {
copyButton.textContent = '复制链接';
}, 1000);
}
});
}
/**
* 清理搜索框和热门搜索
*/
function purify_search() {
// 清空搜索框默认内容
const searchBox = document.querySelector('.nav-search-input');
if (searchBox) {
searchBox.attributes.removeNamedItem('placeholder');
searchBox.attributes.removeNamedItem('title');
}
// 清空热搜
(new MutationObserver((mutationsList) => {
mutationsList.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
// 查找新添加的节点中是否包含 'trending' 类的元素
const trendingElements = node.classList.contains('trending')
? [node]
: node.querySelectorAll('.trending');
// 删除所有找到的 'trending' 元素
trendingElements.forEach(trending => trending.remove());
}
});
});
})).observe(document.querySelector('.search-panel'), {
childList: true,
subtree: true
});
}
/**
* 减少页边距,隐藏右下按钮组和“换一换”按钮
*/
function remove_padding_and_buttons() {
// 减少主页主容器的左右页边距
const mainElement = document.querySelector('main.bili-feed4-layout');
if (mainElement) {
mainElement.style.padding = '0 20px';
}
// 隐藏右下角浮动按钮
const paletteElement = document.querySelector('.palette-feed4');
if (paletteElement) {
paletteElement.style.display = 'none';
}
// 隐藏“换一换”按钮
const switchElement = document.querySelector('.feed-roll-btn');
if (switchElement) {
switchElement.style.display = 'none';
}
}
// 页面加载完成后执行的逻辑
window.addEventListener('load', () => {
// 处理所有现有的视频卡片,添加"复制链接"按钮
const videoCards = document.querySelectorAll('.bili-video-card');
videoCards.forEach(add_copy_link_button);
purify_search();
remove_padding_and_buttons();
});
// 使用 MutationObserver 监听页面上的节点变化
(new MutationObserver((mutationsList) => {
mutationsList.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
// 对新增节点进行处理:设置视频倍速预览、移除不需要的卡片、添加复制链接按钮
setup_preview_enhancement(node);
remove_unwanted_card(node);
add_copy_link_button(node);
});
});
})).observe(document.querySelector('.recommended-container_floor-aside'), {
childList: true,
subtree: true
});
})();