// ==UserScript==
// @name Bilibili 按标签、标题、时长、UP主屏蔽视频
// @namespace https://github.com/tjxwork
// @version 1.1.5
// @note
// @note 新版本的视频介绍,来拯救一下我可怜的播放量吧 ●︿●
// @note 应该是目前B站最强的屏蔽视频插件?【tjxgame】
// @note https://www.bilibili.com/video/BV1WJ4m1u79n
// @note
// @note 作者的爱发电:https://afdian.com/a/tjxgame
// @note 欢迎订阅支持、提需求,您的赞助支持就是维护更新的最大动力!
// @note
// @note v1.1.5 修正导致缓存记录对象的 videoLink 记录出错的部分代码; 修改赞助按钮的跳出连接; (我真的是不知道什么鬼运气,去哪哪崩,刚开的爱发电也崩了。)
// @note v1.1.4 添加新功能:“屏蔽叠加层的提示只显示类型”,有部分用户可能连命中的屏蔽词都不想看到,但是又倾向使用叠加层模式,所以增加了这个开关。
// @note 感谢来自爱发电的赞助需求。
// @note v1.1.3 兼容脚本处理:[bv2av](https://greasyfork.org/zh-CN/scripts/398535)(此脚本会将视频链接替换为旧的 AV 号链接),感谢 @Henry-ZHR 的提出;
// @note 不完善功能修复:每次触发运行时,会将屏蔽叠加背景层与父元素尺寸进行同步,解决了页面布局变化时叠加层不跟随变化,感谢 @Henry-ZHR 的建议;
// @note “隐藏首页等页面的非视频元素” 功能生效范围增加:隐藏 搜索页——综合 下的 直播卡片
// @note v1.1.2 添加新功能:“按置顶评论屏蔽”;
// @note 注意:“按置顶评论屏蔽”、“屏蔽精选评论的视频” 这两个功能都用到了获取评论的API,
// @note 这个API对请求频率非常敏感,频繁刷新或者开启新页面会导致B站拒绝请求,正常浏览一般不会出现拒绝问题。
// @note v1.1.1 添加新功能:“屏蔽充电专属的视频”;
// @note v1.1.0 添加新功能:“屏蔽精选评论的视频”,骗子视频大概率会开启精选评论;
// @note “隐藏首页等页面的非视频元素” 功能生效范围增加:隐藏视频播放页右侧视频相关的游戏推荐;
// @note 控制台输出日志优化:现在只有发生变化的时候才会输出;
// @note v1.0.2 “隐藏首页等页面的非视频元素” 功能生效范围增加:隐藏视频播放页右侧最下方的“大家围观的直播”
// @note v1.0.1 修正了B站旧版首页的顶部推荐条失效的Bug;
// @note 如果用旧版首页只是想要更多的顶部推荐的话,建议使用 bilibili-app-recommend 来获取更多的推荐。
// @note 现在版本B站首页的推荐卡片有广告的问题,可以通过本脚本的 “隐藏首页等页面的非视频元素” 功能来解决。
// @note v1.0.0 菜单UI使用Vue3重构,现在不用担心缩放问题挡住UI了,界面更加现代化;
// @note 改进了判断逻辑,现在可以使用白名单来避免误杀关注的UP了;
// @note 新增功能:视频分区屏蔽、播放量屏蔽、点赞率屏蔽、竖屏视频屏蔽、UP主名称正则屏蔽、隐藏非视频元素、白名单避免屏蔽指定UP。
// @description 对Bilibili的视频卡片,以标签、标题、UP主、时长、竖屏、充电、评论等信息来屏蔽视频,附带去除视频卡片中的直播、广告、推广内容的功能。
// @author tjxwork
// @license CC-BY-NC-SA
// @icon https://www.bilibili.com/favicon.ico
// @match https://www.bilibili.com/*
// @match https://www.bilibili.com/v/popular/all/*
// @match https://www.bilibili.com/v/popular/weekly/*
// @match https://www.bilibili.com/v/popular/history/*
// @exclude https://www.bilibili.com/anime/*
// @exclude https://www.bilibili.com/movie/*
// @exclude https://www.bilibili.com/guochuang/*
// @exclude https://www.bilibili.com/variety/*
// @exclude https://www.bilibili.com/tv/*
// @exclude https://www.bilibili.com/documentary*
// @exclude https://www.bilibili.com/mooc/*
// @exclude https://www.bilibili.com/v/virtual/*
// @exclude https://www.bilibili.com/v/popular/music/*
// @exclude https://www.bilibili.com/v/popular/drama/*
// @match https://search.bilibili.com/*
// @exclude https://search.bilibili.com/live
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @require https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-w/vue/3.2.31/vue.global.min.js
// ==/UserScript==
"use strict";
// --------------------参数变量初始化--------------------
// 初始化屏蔽参数变量,从 油猴扩展存储 读取到 blockedParameter
let blockedParameter = GM_getValue("GM_blockedParameter", {
// 屏蔽标题
blockedTitle_Switch: true,
blockedTitle_UseRegular: true,
blockedTitle_Array: [],
// 屏蔽Up主和Uid
blockedNameOrUid_Switch: true,
blockedNameOrUid_UseRegular: false,
blockedNameOrUid_Array: [],
// 屏蔽视频分区
blockedVideoPartitions_Switch: true,
blockedVideoPartitions_UseRegular: false,
blockedVideoPartitions_Array: [],
// 屏蔽标签
blockedTag_Switch: true,
blockedTag_UseRegular: true,
blockedTag_Array: [],
// 屏蔽双重屏蔽标签
doubleBlockedTag_Switch: true,
doubleBlockedTag_UseRegular: true,
doubleBlockedTag_Array: [],
// 屏蔽短时长视频
blockedShortDuration_Switch: false,
blockedShortDuration: 0,
// 屏蔽低播放量视频
blockedBelowVideoViews_Switch: false,
blockedBelowVideoViews: 0,
// 屏蔽低于指定点赞率的视频
blockedBelowLikesRate_Switch: false,
blockedBelowLikesRate: 0,
// 屏蔽竖屏视频
blockedPortraitVideo_Switch: false,
// 屏蔽充电专属的视频
blockedChargingExclusive_Switch: false,
// 屏蔽精选评论的视频
blockedFilteredCommentsVideo_Switch: false,
// 屏蔽置顶评论
blockedTopComment_Switch: false,
blockedTopComment_UseRegular: true,
blockedTopComment_Array: [],
// 白名单Up主和Uid
whitelistNameOrUid_Switch: false,
whitelistNameOrUid_Array: [],
// 隐藏非视频元素
hideNonVideoElements_Switch: true,
// 屏蔽叠加层的提示只显示类型而不显示命中项
blockedOverlayOnlyDisplaysType_Switch: false,
// 隐藏视频而非叠加层模式
hideVideoMode_Switch: false,
// 控制台输出日志
consoleOutputLog_Switch: false,
});
// 旧参数适配
function oldParameterAdaptation(obj) {
//判断是否为旧参数,是的话就修改为新参数结构
if (Object.prototype.hasOwnProperty.call(obj, "blockedTitleArray")) {
// 屏蔽标题
obj["blockedTitle_Switch"] = true;
obj["blockedTitle_UseRegular"] = true;
obj["blockedTitle_Array"] = obj["blockedTitleArray"];
delete obj["blockedTitleArray"];
// 屏蔽Up主和Uid
obj["blockedNameOrUid_Switch"] = true;
obj["blockedNameOrUid_UseRegular"] = true;
obj["blockedNameOrUid_Array"] = obj["blockedNameOrUidArray"];
delete obj["blockedNameOrUidArray"];
// 屏蔽视频分区
obj["blockedVideoPartitions_Switch"] = false;
obj["blockedVideoPartitions_UseRegular"] = false;
obj["blockedVideoPartitions_Array"] = [];
// 屏蔽标签
obj["blockedTag_Switch"] = true;
obj["blockedTag_UseRegular"] = true;
obj["blockedTag_Array"] = obj["blockedTagArray"];
delete obj["blockedTagArray"];
// 屏蔽双重屏蔽标签
obj["doubleBlockedTag_Switch"] = true;
obj["doubleBlockedTag_UseRegular"] = true;
obj["doubleBlockedTag_Array"] = obj["doubleBlockedTagArray"];
delete obj["doubleBlockedTagArray"];
// 屏蔽短时长视频
obj["blockedShortDuration_Switch"] = true;
// 白名单Up主和Uid
obj["whitelistNameOrUid_Switch"] = false;
obj["whitelistNameOrUid_Array"] = [];
// 隐藏视频而非叠加层模式
obj["hideVideoMode_Switch"] = obj["hideVideoModeSwitch"];
delete obj["hideVideoModeSwitch"];
// 控制台输出日志
obj["consoleOutputLog_Switch"] = obj["consoleOutputLogSwitch"];
delete obj["consoleOutputLogSwitch"];
}
}
oldParameterAdaptation(blockedParameter);
// --------------------菜单UI部分--------------------
// 菜单UI的CSS,使用 GM_addStyle 注入 CSS
GM_addStyle(`
:root {
/* 主窗体背景色 */
--uiBackgroundColor: rgb(48, 48, 48);
/* 输入模块背景色 */
--uiInputContainerBackgroundColor: rgb(64, 64, 64);
/* 输入框背景色 */
--uiInputBoxBackgroundColor: rgb(89, 89, 89);
/* 滚动条背景色 */
--uiScrollbarBackgroundColor: rgb(141, 141, 141);
/* 文字颜色 */
--uiTextColor: rgb(250, 250, 250);
/* 按钮色 */
--uiButtonColor: rgb(0, 174, 236);
/* 边框色 */
--uiBorderColor: rgba(0, 0, 0, 0);
/* 提醒框背景色 */
--uiPromptBoxColor: rgb(42, 44, 53);
/* 屏蔽叠加层背景色 */
--blockedOverlayColor: rgba(60, 60, 60, 0.85);
/* 字体大小 */
--fontSize: 14px;
/* 行高 */
--lineHeight: 24px;
/* 圆角 */
--borderRadius: 4px;
}
/* 菜单UI */
#blockedMenuUi {
font-size: var(--fontSize);
position: fixed;
bottom: 4vh;
right: 2vw;
z-index: 1005;
width: 460px;
max-height: 90vh;
overflow-y: auto;
background-color: var(--uiBackgroundColor);
}
#blockedMenuUi,
#blockedMenuUi * {
color: var(--uiTextColor);
box-sizing: border-box;
border-style: solid;
border-width: 0px;
border-color: var(--uiBorderColor);
border-radius: var(--borderRadius);
line-height: var(--lineHeight);
vertical-align: middle;
font-family: "Cascadia Mono", Monaco, Consolas, "PingFang SC", "Helvetica Neue", "Microsoft YaHei", sans-serif;
}
/* 滚动条 */
#blockedMenuUi::-webkit-scrollbar,
#blockedMenuUi ul::-webkit-scrollbar {
width: 7px;
}
/* 滚动条 轨道*/
#blockedMenuUi::-webkit-scrollbar-track,
#blockedMenuUi ul::-webkit-scrollbar-track {
background: var(--uiScrollbarBackgroundColor);
border-radius: 7px;
}
/* 滚动条 滑块*/
#blockedMenuUi::-webkit-scrollbar-thumb,
#blockedMenuUi ul::-webkit-scrollbar-thumb {
background: var(--uiInputContainerBackgroundColor);
border-radius: 7px;
}
/* 滚动条 滑块 鼠标经过 */
#blockedMenuUi::-webkit-scrollbar-thumb:hover,
#blockedMenuUi ul::-webkit-scrollbar-thumb:hover {
background: var(--uiInputBoxBackgroundColor);
border-radius: 7px;
}
/* 滚动条 滑块 鼠标点击 */
#blockedMenuUi::-webkit-scrollbar-thumb:active,
#blockedMenuUi ul::-webkit-scrollbar-thumb:active {
background: var(--uiButtonColor);
border-radius: 7px;
}
#menuTitle {
font-size: 17px;
text-align: center;
margin: 10px;
}
.menuOptions {
background-color: var(--uiInputContainerBackgroundColor);
padding: 10px;
margin: 0 10px;
margin-bottom: 10px;
}
.titleLabelLeft {
display: inline-block;
width: 275px;
margin-bottom: 5px;
}
.titleLabelRight {
display: inline-block;
margin-bottom: 5px;
}
#blockedMenuUi label {
font-size: 16px;
vertical-align: middle;
}
#blockedMenuUi input {
background-color: var(--uiInputBoxBackgroundColor);
font-size: var(--fontSize);
line-height: var(--lineHeight);
border-radius: var(--borderRadius);
padding: 0 5px;
margin-bottom: 5px;
width: 360px;
vertical-align: middle;
}
#blockedMenuUi input[type="number"] {
width: 4em;
margin: 0 5px;
padding: 0 5px;
text-align: right;
appearance: none;
}
#blockedMenuUi input[type="number"]::-webkit-inner-spin-button,
#blockedMenuUi input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
#blockedMenuUi input[type="checkbox"] {
width: 16px;
height: 16px;
margin: 0;
margin-bottom: 2.5px;
margin-right: 5px;
appearance: none;
border: 1.5px solid var(--uiTextColor);
border-radius: 8px;
}
#blockedMenuUi input[type="checkbox"]:checked {
border: 3px solid;
background-color: var(--uiButtonColor);
}
#blockedMenuUi button {
line-height: var(--lineHeight);
border-radius: var(--borderRadius);
padding: 0;
margin-bottom: 5px;
margin-left: 5px;
width: 45px;
vertical-align: middle;
background-color: var(--uiButtonColor);
transition: background-color 0.1s ease;
}
#blockedMenuUi button:hover {
background-color: rgb(17, 154, 204);
}
#blockedMenuUi button:active {
background-color: rgb(62, 203, 255);
}
#blockedMenuUi ul {
background-color: var(--uiInputBoxBackgroundColor);
font-size: 14px;
padding: 5px 5px 0px 0px;
margin-inline: 0px;
margin: 0;
width: 100%;
min-height: 34px;
max-height: 92px;
overflow-y: auto;
}
#blockedMenuUi li {
line-height: var(--lineHeight);
border-radius: var(--borderRadius);
display: inline-block;
padding: 0 5px;
margin-bottom: 5px;
margin-left: 5px;
vertical-align: middle;
background-color: var(--uiButtonColor);
}
#blockedMenuUi li button {
width: 20px;
margin: 0px;
padding: 0 0 3px 0;
font-size: 24px;
line-height: 18px;
border: 0px;
}
#blockedMenuUi li button:hover {
background-color: var(--uiButtonColor);
color: rgb(221, 221, 221);
}
#blockedMenuUi li button:active {
background-color: var(--uiButtonColor);
color: var(--uiButtonColor);
}
#blockedMenuUi textarea {
background-color: var(--uiInputBoxBackgroundColor);
font-size: 14px;
padding: 0 5px;
width: 100%;
resize: none;
}
#menuButtonContainer {
position: sticky;
right: 0;
bottom: 0;
width: 100%;
background-color: var(--uiBackgroundColor);
margin-top: -10px;
}
#menuButtonContainer button {
line-height: var(--lineHeight);
border-radius: var(--borderRadius);
font-size: 16px;
border: 0;
padding: 0;
margin-top: 10px;
margin-bottom: 10px;
margin-left: 10px;
height: 45px;
width: 45px;
vertical-align: middle;
background-color: var(--uiButtonColor);
}
#menuButtonContainer label {
line-height: 45px;
border-radius: var(--borderRadius);
display: inline-block;
border: 0;
padding: 0;
margin: 10px 20px;
height: 45px;
width: 130px;
vertical-align: middle;
text-align: center;
background-color: var(--uiInputBoxBackgroundColor);
transition: opacity 1s;
}
/* 支付宝微信二维码 */
#alipayWeChatQrCode {
position: fixed;
top: 52%;
left: 16%;
transform: translate(0%, -50%);
box-shadow: 0 8px 8px rgb(85 85 85 / 85%);
}
`);
// 菜单UI的HTML
let menuUiHTML = `
<div id="blockedMenuUi">
<div id="menuTitle">Bilibili按标签、标题、时长、UP主屏蔽视频 v1.1.5</div>
<div id="menuOptionsList">
<div class="menuOptions">
<div class="titleLabelLeft">
<label><input type="checkbox" v-model="menuUiSettings.blockedTitle_Switch" />按标题屏蔽 </label>
</div>
<div class="titleLabelRight">
<label><input type="checkbox" v-model="menuUiSettings.blockedTitle_UseRegular" />启用正则</label>
</div>
<input type="text" placeholder="多项输入请用英文逗号间隔" spellcheck="false"
v-model="tempInputValue.blockedTitle_Array" /><button
@click="addArrayButton(tempInputValue, menuUiSettings, 'blockedTitle_Array')">添加</button>
<ul>
<li v-for="(value, index) in menuUiSettings.blockedTitle_Array">
{{value}}<button @click="delArrayButton(index, menuUiSettings.blockedTitle_Array)">×</button>
</li>
</ul>
</div>
<div class="menuOptions">
<div class="titleLabelLeft">
<label><input type="checkbox" v-model="menuUiSettings.blockedNameOrUid_Switch" />按UP名称或Uid屏蔽</label>
</div>
<div class="titleLabelRight">
<label><input type="checkbox" v-model="menuUiSettings.blockedNameOrUid_UseRegular" />启用正则</label>
</div>
<input type="text" placeholder="多项输入请用英文逗号间隔" spellcheck="false"
v-model="tempInputValue.blockedNameOrUid_Array" /><button
@click="addArrayButton(tempInputValue, menuUiSettings, 'blockedNameOrUid_Array')">添加</button>
<ul>
<li v-for="(value, index) in menuUiSettings.blockedNameOrUid_Array">
{{value}}<button @click="delArrayButton(index, menuUiSettings.blockedNameOrUid_Array)">×</button>
</li>
</ul>
</div>
<div class="menuOptions">
<div class="titleLabelLeft">
<label><input type="checkbox" v-model="menuUiSettings.blockedVideoPartitions_Switch" />按视频分区屏蔽</label>
</div>
<div class="titleLabelRight">
<label><input type="checkbox" v-model="menuUiSettings.blockedVideoPartitions_UseRegular" />启用正则</label>
</div>
<input type="text" placeholder="多项输入请用英文逗号间隔" spellcheck="false"
v-model="tempInputValue.blockedVideoPartitions_Array" /><button
@click="addArrayButton(tempInputValue, menuUiSettings, 'blockedVideoPartitions_Array')">添加</button>
<ul>
<li v-for="(value, index) in menuUiSettings.blockedVideoPartitions_Array">
{{value}}<button
@click="delArrayButton(index, menuUiSettings.blockedVideoPartitions_Array)">×</button>
</li>
</ul>
</div>
<div class="menuOptions">
<div class="titleLabelLeft">
<label><input type="checkbox" v-model="menuUiSettings.blockedTag_Switch" />按标签屏蔽</label>
</div>
<div class="titleLabelRight">
<label><input type="checkbox" v-model="menuUiSettings.blockedTag_UseRegular" />启用正则</label>
</div>
<input type="text" placeholder="多项输入请用英文逗号间隔" spellcheck="false"
v-model="tempInputValue.blockedTag_Array" /><button
@click="addArrayButton(tempInputValue, menuUiSettings, 'blockedTag_Array')">添加</button>
<ul>
<li v-for="(value, index) in menuUiSettings.blockedTag_Array">
{{value}}<button @click="delArrayButton(index, menuUiSettings.blockedTag_Array)">×</button>
</li>
</ul>
</div>
<div class="menuOptions">
<div class="titleLabelLeft">
<label><input type="checkbox" v-model="menuUiSettings.doubleBlockedTag_Switch" />按双重标签屏蔽</label>
</div>
<div class="titleLabelRight">
<label><input type="checkbox" v-model="menuUiSettings.doubleBlockedTag_UseRegular" />启用正则</label>
</div>
<input type="text" placeholder='多项输入请用英文逗号间隔(以"A标签|B标签"格式添加)' spellcheck="false"
v-model="tempInputValue.doubleBlockedTag_Array" /><button
@click="addArrayButton(tempInputValue, menuUiSettings, 'doubleBlockedTag_Array' )">添加</button>
<ul>
<li v-for="(value, index) in menuUiSettings.doubleBlockedTag_Array">
{{value}}<button @click="delArrayButton(index, menuUiSettings.doubleBlockedTag_Array)">×</button>
</li>
</ul>
</div>
<div class="menuOptions">
<div class="titleLabelLeft">
<label><input type="checkbox" v-model="menuUiSettings.blockedTopComment_Switch" />按置顶评论屏蔽 </label>
</div>
<div class="titleLabelRight">
<label><input type="checkbox" v-model="menuUiSettings.blockedTopComment_UseRegular" />启用正则</label>
</div>
<input type="text" placeholder="多项输入请用英文逗号间隔" spellcheck="false"
v-model="tempInputValue.blockedTopComment_Array" /><button
@click="addArrayButton(tempInputValue, menuUiSettings, 'blockedTopComment_Array')">添加</button>
<ul>
<li v-for="(value, index) in menuUiSettings.blockedTopComment_Array">
{{value}}<button @click="delArrayButton(index, menuUiSettings.blockedTopComment_Array)">×</button>
</li>
</ul>
</div>
<div class="menuOptions">
<div class="titleLabelLeft">
<label><input type="checkbox"
v-model="menuUiSettings.whitelistNameOrUid_Switch" />按UP名称或Uid避免屏蔽(白名单)</label>
</div>
<input type="text" placeholder='多项输入请用英文逗号间隔' spellcheck="false"
v-model="tempInputValue.whitelistNameOrUid_Array" /><button
@click="addArrayButton(tempInputValue, menuUiSettings, 'whitelistNameOrUid_Array' )">添加</button>
<ul>
<li v-for="(value, index) in menuUiSettings.whitelistNameOrUid_Array">
{{value}}<button @click="delArrayButton(index, menuUiSettings.whitelistNameOrUid_Array)">×</button>
</li>
</ul>
</div>
<div class="menuOptions">
<div class="titleLabelLeft">
<label><input type="checkbox" v-model="menuUiSettings.blockedShortDuration_Switch" />屏蔽低于指定时长的视频</label>
</div>
<input type="number" spellcheck="false" v-model="menuUiSettings.blockedShortDuration" />
<label>秒</label>
</div>
<div class="menuOptions">
<div class="titleLabelLeft">
<label><input type="checkbox"
v-model="menuUiSettings.blockedBelowVideoViews_Switch" />屏蔽低于指定播放量的视频</label>
</div>
<input type="number" spellcheck="false" v-model="menuUiSettings.blockedBelowVideoViews" />
<label>次</label>
</div>
<div class="menuOptions">
<div class="titleLabelLeft">
<label><input type="checkbox"
v-model="menuUiSettings.blockedBelowLikesRate_Switch" />屏蔽低于指定点赞率的视频</label>
</div>
<input type="number" spellcheck="false" v-model="menuUiSettings.blockedBelowLikesRate" />
<label>%</label>
</div>
<div class="menuOptions">
<label><input type="checkbox" v-model="menuUiSettings.blockedPortraitVideo_Switch" />屏蔽竖屏视频</label>
</div>
<div class="menuOptions">
<label><input type="checkbox" v-model="menuUiSettings.blockedChargingExclusive_Switch" />屏蔽充电专属的视频</label>
</div>
<div class="menuOptions">
<label><input type="checkbox"
v-model="menuUiSettings.blockedFilteredCommentsVideo_Switch" />屏蔽精选评论的视频</label>
</div>
<div class="menuOptions">
<label><input type="checkbox"
v-model="menuUiSettings.hideNonVideoElements_Switch" />隐藏首页等页面的非视频元素</label>
</div>
<div class="menuOptions">
<label><input type="checkbox" v-model="menuUiSettings.blockedOverlayOnlyDisplaysType_Switch" />屏蔽叠加层的提示只显示类型</label>
</div>
<div class="menuOptions">
<label><input type="checkbox" v-model="menuUiSettings.hideVideoMode_Switch" />隐藏视频而不是使用叠加层覆盖</label>
</div>
<div class="menuOptions">
<label><input type="checkbox" v-model="menuUiSettings.consoleOutputLog_Switch" />控制台输出日志开关</label>
</div>
</div>
<div id="menuButtonContainer">
<button @click="refreshButton()">读取</button>
<button @click="saveButton()">保存</button>
<button @click="closeButton()">关闭</button>
<button @click="authorButton()">作者</button>
<button @click="supportButton()">赞助</button>
<label :style="{ opacity: tempInputValue.promptText_Opacity }"
v-show="tempInputValue.promptText_Switch">{{tempInputValue.promptText}}</label>
</div>
<div id="alipayWeChatQrCode" v-show="tempInputValue.QrCode_Switch">
<img src="https://tc.dhmip.cn/imgs/2023/12/09/a8e5fff3320dc195.png" alt="感谢赞助">
</div>
</div>
`;
// 菜单UI
function blockedMenuUi() {
// 检查页面中是否已经存在这个元素
if (!document.getElementById("blockedMenuUi")) {
// 如果不存在,将菜单弹窗添加到页面
// 创建Div作为菜单容器
let menuUi = document.createElement("div");
menuUi.innerHTML = menuUiHTML;
document.body.appendChild(menuUi);
} else {
console.log("菜单 #blockedMenuUi 已存在");
return;
}
// 让油猴脚本的Vue代码能网页中正常工作。
unsafeWindow.Vue = Vue;
const { createApp, reactive, toRaw } = Vue;
createApp({
setup() {
// 设置选项数据
const menuUiSettings = reactive({});
// 临时存储的各数组对应的输入值
const tempInputValue = reactive({
blockedTitle_Array: "",
blockedNameOrUid_Array: "",
blockedVideoPartitions_Array: "",
blockedTag_Array: "",
doubleBlockedTag_Array: "",
blockedTopComment_Array: "",
whitelistNameOrUid_Array: "",
// 临时提示文本
promptText_Switch: true,
promptText_Opacity: 0,
promptText: "",
// 二维码显示开关
QrCode_Switch: false,
});
function showPromptText(text) {
// tempInputValue.promptText_Switch = true; // 显示 label 元素
tempInputValue.promptText_Opacity = 1;
tempInputValue.promptText = text;
// 1.5秒后隐藏 label 元素
setTimeout(() => {
// tempInputValue.promptText_Switch = false;
tempInputValue.promptText_Opacity = 0;
}, 1500);
}
// 添加数组项目
const addArrayButton = (tempInputValue, menuUiSettings, keyName) => {
// 确保 menuUiSettings[keyName] 是一个数组
if (!Array.isArray(menuUiSettings[keyName])) {
menuUiSettings[keyName] = [];
}
// 双重标签的特殊处理 判断是否为空
if (keyName == "doubleBlockedTag_Array" && tempInputValue[keyName].trim()) {
// 使用 split 按逗号分隔,然后映射去除每个标签的首尾空白
const items = tempInputValue[keyName]
.split(",")
.map((item) => item.split("|").map((str) => str.trim()))
.filter((subArray) => subArray.length === 2 && subArray.every((str) => str !== ""));
items.forEach((secondSplitItem) => {
// 将两个标签重新组合成一个字符串,并添加到设置数据中
const formattedItem = secondSplitItem.join("|");
menuUiSettings[keyName].push(formattedItem);
});
// 清空输入框内容
tempInputValue[keyName] = "";
return;
}
// 判断是否为空
if (tempInputValue[keyName].trim()) {
// 用逗号分隔值并去除每项的空格后添加到数组
const items = tempInputValue[keyName].split(",").map((item) => item.trim());
menuUiSettings[keyName].push(...items);
// 清空输入框内容
tempInputValue[keyName] = "";
}
};
//删除数组项目
const delArrayButton = (index, array) => {
//splice(要删除元素的索引位置, 要删除的元素数量)
array.splice(index, 1);
};
// 读取按钮 深拷贝函数,递归处理嵌套对象,普通对象 to 普通对象/响应式对象
function deepCopy(source, target) {
for (let key in source) {
if (typeof source[key] === "object" && source[key] !== null) {
target[key] = Array.isArray(source[key]) ? [] : {}; // 根据类型创建空对象或数组
deepCopy(source[key], target[key]); // 递归拷贝子对象
} else {
target[key] = source[key]; // 复制基本类型和函数等
}
}
}
// 读取按钮
const refreshButton = () => {
// 使用 deepCopy 函数进行深拷贝
deepCopy(blockedParameter, menuUiSettings);
showPromptText("读取数据");
};
// 保存按钮 深拷贝函数,递归处理响应式对象,响应式对象 to 普通对象
function deepCopyReactiveObject(reactiveObj, targetObj) {
for (let key in reactiveObj) {
const rawValue = toRaw(reactiveObj[key]); // 获取属性的原始值
if (typeof rawValue === "object" && rawValue !== null) {
targetObj[key] = Array.isArray(rawValue) ? [] : {}; // 根据类型创建空对象或数组
deepCopyReactiveObject(rawValue, targetObj[key]); // 递归处理嵌套的响应式子对象
} else {
targetObj[key] = rawValue; // 复制基本类型和函数等
}
}
}
// 保存按钮
const saveButton = () => {
// 将响应式对象深拷贝到普通对象 blockedParameter
deepCopyReactiveObject(menuUiSettings, blockedParameter);
// 将全局屏蔽参数对象变量 blockedParameter 保存到油猴扩展存储中
GM_setValue("GM_blockedParameter", blockedParameter);
showPromptText("保存数据");
// 触发一次主函数,以立刻生效
FuckYouBilibiliRecommendationSystem();
};
// 关闭按钮
const closeButton = () => {
// 获取需要删除的元素
let elementToRemove = document.getElementById("blockedMenuUi");
// 确保元素存在再进行删除操作
if (elementToRemove) {
// 先获取父元素
let parentElement = elementToRemove.parentNode;
// 在父元素删除指定的元素
parentElement.removeChild(elementToRemove);
}
};
// 作者主页
const authorButton = () => {
setTimeout(() => {
window.open("https://space.bilibili.com/351422438", "_blank");
}, 1000);
showPromptText("欢迎关注!");
};
// 赞助作者
const supportButton = () => {
if (!tempInputValue.QrCode_Switch) {
setTimeout(() => {
window.open("https://afdian.com/a/tjxgame", "_blank");
}, 1000);
tempInputValue.QrCode_Switch = true;
} else {
tempInputValue.QrCode_Switch = false;
}
showPromptText("感谢老板!");
};
// 打开菜单时,先加载一次数据
refreshButton();
return {
menuUiSettings,
tempInputValue,
addArrayButton,
delArrayButton,
refreshButton,
saveButton,
closeButton,
supportButton,
authorButton,
};
},
}).mount("#blockedMenuUi");
}
// 在油猴扩展中添加脚本菜单选项
GM_registerMenuCommand("屏蔽参数面板", blockedMenuUi);
// -----------------------逻辑处理部分--------------------------
// 视频的详细信息对象,以videoBv为键, 用于同窗口内的缓存查询
let videoInfoDict = {};
// 上次输出的视频详细信息对象,用于控制台判断是否输出日志
let lastConsoleVideoInfoDict = {};
// videoInfoDict 的参考内容结构
// videoInfoDict = {
// BV12i4y1e73B: {
// videoLink: "https://www.bilibili.com/video/BV12i4y1e73B/",
// videoTitle: "B站按 标签 标题 时长 UP主来屏蔽视频 油猴插件【tjxgame】",
// videoUpName: "tjxgame",
// videoUpUid: 351422438,
// videoPartitions: "软件应用",
// videoTags: [
// "科技2023年终总结",
// "视频",
// "教程",
// "tjxwork",
// "软件分享",
// "插件",
// "标签",
// "屏蔽",
// "油猴",
// "tjxgame",
// "2023热门年度盘点",
// ],
// topComment : "大更新,新视频!\nhttps://www.bilibili.com/video/BV1WJ4m1u79n/\n\nv1.0.0 菜单UI使用Vue3重构,现在不用担心缩放问题挡住UI了,界面更加现代化;\n改进了判断逻辑,现在可以使用白名单来避免误杀关注的UP了;\n新增功能:视频分区屏蔽、播放量屏蔽、点赞率屏蔽、竖屏视频屏蔽、UP主名称正则屏蔽、隐藏非视频元素、白名单避免屏蔽指定UP。"
// whiteListTargets: true,
// videoDuration: 259,
// videoView: 9067,
// videoLike: 507,
// videoLikesRate: "5.59",
// videoResolution: {
// width: 3840,
// height: 2160,
// },
// videoChargingExclusive : false
// filteredComments: false,
// blockedTarget: true,
// triggeredBlockedRules: [
// "屏蔽短时长视频: 259秒",
// "屏蔽低播放量: 9067次",
// "屏蔽低点赞率: 5.59%",
// "屏蔽标题: tjxgame",
// "屏蔽UP: tjxgame",
// "屏蔽分区: 软件应用",
// "屏蔽标签: 标签",
// "屏蔽双重标签: 油猴,插件",
// ],
// lastVideoInfoApiRequestTime: "2024-06-21T09:17:10.389Z",
// lastVideoTagApiRequestTime: "2024-06-21T09:17:10.389Z",
// lastVideoCommentsApiRequestTime: "2024-06-21T09:17:10.389Z",
// },
// };
// 日志输出,根据 consoleOutputLog_Switch 标志来决定是否输出日志
function consoleLogOutput(...args) {
// 启用控制台日志输出
if (blockedParameter.consoleOutputLog_Switch) {
// 获取当前时间的时分秒毫秒部分
let now = new Date();
let hours = now.getHours().toString().padStart(2, "0");
let minutes = now.getMinutes().toString().padStart(2, "0");
let seconds = now.getSeconds().toString().padStart(2, "0");
let milliseconds = now.getMilliseconds().toString().padStart(3, "0");
// 将时间信息添加到日志消息中
let logTime = `${hours}:${minutes}:${seconds}.${milliseconds}`;
// 合并时间信息和 args 成为一个数组
let logArray = [logTime, ...args];
console.log(...logArray);
}
}
// 简单对比对象是否不同
function objectDifferent(obj1, obj2) {
if (Object.keys(obj1).length !== Object.keys(obj2).length) {
return true;
}
for (const key in obj1) {
if (obj1[key] !== obj2[key]) {
return true;
}
}
return false;
}
// 获取视频元素
function getVideoElements() {
// // 获取所有有可能是视频元素的标签 (BewlyBewly插件的首页特殊处理)
// let bewlyBewly = document.getElementById("bewly");
// if (bewlyBewly) {
// // BewlyBewly插件使用shadowDOM,要在shadowDOM下面找元素
// let shadowRoot = bewlyBewly.shadowRoot;
// videoElements = shadowRoot.querySelectorAll("div.video-card.group");
// // 过滤掉没有包含a标签的元素
// videoElements = Array.from(videoElements).filter((element) => element.querySelector("a"));
// // 返回处理后的结果
// return videoElements;
// }
// BewlyBewly 更新后失效……
// 获取所有有可能是视频元素的标签
let videoElements = document.querySelectorAll(
// div.bili-video-card 首页(https://www.bilibili.com/)、分区首页(https://www.bilibili.com/v/*)、搜索页面(https://search.bilibili.com/*)
// div.video-page-card-small 播放页右侧推荐(https://www.bilibili.com/video/BV****)
// li.bili-rank-list-video__item 分区首页-子分区右侧热门(https://www.bilibili.com/v/*)
// div.video-card 综合热门(https://www.bilibili.com/v/popular/all) 、每周必看(https://www.bilibili.com/v/popular/weekly) 、入站必刷(https://www.bilibili.com/v/popular/history)
// li.rank-item 排行榜(https://www.bilibili.com/v/popular/rank/all)
// div.video-card-reco 旧版首页推送(https://www.bilibili.com/)
// div.video-card-common 旧版首页分区(https://www.bilibili.com/)
// div.rank-wrap 旧版首页分区右侧排行(https://www.bilibili.com/)
"div.bili-video-card, div.video-page-card-small, li.bili-rank-list-video__item, div.video-card, li.rank-item, div.video-card-reco, div.video-card-common, div.rank-wrap"
);
// 过滤掉没有包含a标签的元素
videoElements = Array.from(videoElements).filter((element) => element.querySelector("a"));
// 判断是否存在旧版首页的顶部推荐条,为空的情况下再进行剔除广告元素,因为旧版首页的顶部推荐条,和新版的广告元素的类值一样……
if (document.querySelector("div.recommend-container__2-line") == null) {
// 过滤掉 CSS类刚好为 'bili-video-card is-rcmd' 的元素,因为是广告。
videoElements = Array.from(videoElements).filter(
(element) => element.classList.value !== "bili-video-card is-rcmd"
);
}
// 返回处理后的结果
return videoElements;
}
// 判断是否为已经屏蔽处理过的视频元素(延迟处理中)
function isAlreadyBlockedChildElement(videoElement) {
// // 确认是否为已经修改 元素已隐藏 跳过
// if (videoElement.style.display == "none") {
// // consoleLogOutput(operationInfo, "元素已隐藏 跳过剩下主函数步骤");
// return true;
// }
// 确认是否为已经修改 元素已透明 延迟处理中 跳过
if (videoElement.style.filter == "blur(5px)") {
// consoleLogOutput(operationInfo, "元素已透明 延迟处理中 跳过剩下主函数步骤");
return true;
}
// // 获取子元素,以确认是否为已经修改
// if (videoElement.firstElementChild.className == "blockedOverlay") {
// // consoleLogOutput(videoElement, "获取子元素,确认是已屏蔽处理过,跳过剩下主函数步骤");
// return true;
// }
}
// 标记为屏蔽目标,并记录命中的规则
function markAsBlockedTarget(videoBv, blockedType, blockedItem) {
// 将该 Bv号 标记为屏蔽目标
videoInfoDict[videoBv].blockedTarget = true;
// 确保 videoInfoDict[videoBv].triggeredBlockedRules 已定义为数组
if (!videoInfoDict[videoBv].triggeredBlockedRules) {
videoInfoDict[videoBv].triggeredBlockedRules = [];
}
let blockedRulesItem;
// 屏蔽叠加层的提示只显示类型而不显示命中项
if (blockedParameter.blockedOverlayOnlyDisplaysType_Switch) {
blockedRulesItem = blockedType;
} else {
blockedRulesItem = blockedType + ": " + blockedItem;
}
// 检查是否已经这条记录
if (!videoInfoDict[videoBv].triggeredBlockedRules.includes(blockedRulesItem)) {
// 将触发屏蔽的原因添加到 videoInfoDict[videoBv].triggeredBlockedRules
videoInfoDict[videoBv].triggeredBlockedRules.push(blockedRulesItem);
}
}
// 网页获取视频元素的Bv号和标题
function getBvAndTitle(videoElement) {
// 从视频元素中获取所有a标签链接
const videoLinkElements = videoElement.querySelectorAll("a");
// Bv号
let videoBv;
// Av号转Bv号,用于兼容 bv2av (https://greasyfork.org/zh-CN/scripts/398535),代码来源:https://socialsisteryi.github.io/bilibili-API-collect/docs/misc/bvid_desc.html#bv-av%E7%AE%97%E6%B3%95
function av2bv(aid) {
const XOR_CODE = 23442827791579n;
const MASK_CODE = 2251799813685247n;
const MAX_AID = 1n << 51n;
const BASE = 58n;
const data = "FcwAPNKTMug3GV5Lj7EJnHpWsx4tb8haYeviqBz6rkCy12mUSDQX9RdoZf";
const bytes = ["B", "V", "1", "0", "0", "0", "0", "0", "0", "0", "0", "0"];
let bvIndex = bytes.length - 1;
let tmp = (MAX_AID | BigInt(aid)) ^ XOR_CODE;
while (tmp > 0) {
bytes[bvIndex] = data[Number(tmp % BigInt(BASE))];
tmp = tmp / BASE;
bvIndex -= 1;
}
[bytes[3], bytes[9]] = [bytes[9], bytes[3]];
[bytes[4], bytes[7]] = [bytes[7], bytes[4]];
return bytes.join("");
}
// 循环处理所有a标签链接
for (let videoLinkElement of videoLinkElements) {
// 已经有Bv号不需要继续了,跳过
if (videoBv) {
continue;
}
// 处理排行榜的多链接特殊情况,符合就跳过
if (videoLinkElement.className == "other-link") {
continue;
}
// 获取的链接,如果是Av链接的格式
let videoAvTemp = videoLinkElement.href.match(/\/(av)(\d+)/);
if (videoAvTemp) {
// 从链接中获取Av号 转为 Bv号
videoBv = av2bv(videoAvTemp[2]);
}
// 获取的链接,如果是Bv链接的格式
let videoBvTemp = videoLinkElement.href.match(/\/(BV\w+)/);
if (videoBvTemp) {
// 从链接中获取到 视频Bv号
videoBv = videoBvTemp[1];
}
// 没拿Bv号不需要继续了,跳过
if (!videoBv) {
continue;
}
// 确保 videoInfoDict[videoBv] 已定义
if (!videoInfoDict[videoBv]) {
videoInfoDict[videoBv] = {};
}
// 视频链接
videoInfoDict[videoBv].videoLink = videoLinkElement.href;
}
// 没有拿到Bv号,提前结束
if (!videoBv) {
consoleLogOutput(videoElement, "getBvAndTitle() 没有拿到Bv号 提前结束 跳过剩下主函数步骤");
return false;
}
// 视频标题 , 从视频元素中获取第一个带 title 属性且不为 span 的标签
videoInfoDict[videoBv].videoTitle = videoElement.querySelector("[title]:not(span)").title;
return videoBv;
}
// 处理匹配的屏蔽标题
function handleBlockedTitle(videoBv) {
// 判断是否拿到视频标题
if (!videoInfoDict[videoBv].videoTitle) {
return;
}
// 记录触发的规则内容
// let blockedRulesItemText = "";
// 是否启用正则
if (blockedParameter.blockedTitle_UseRegular) {
// 使用 屏蔽标题数组 与 视频标题 进行匹配
const blockedTitleHitItem = blockedParameter.blockedTitle_Array.find((blockedTitleItem) => {
// 正则化屏蔽标题
const blockedTitleRegEx = new RegExp(blockedTitleItem);
// 判断 正则化的屏蔽标题 是否匹配 视频标题
if (blockedTitleRegEx.test(videoInfoDict[videoBv].videoTitle)) {
// blockedRulesItemText = videoInfoDict[videoBv].videoTitle;
return true;
}
});
if (blockedTitleHitItem) {
// 标记为屏蔽目标并记录触发的规则
markAsBlockedTarget(videoBv, "屏蔽标题", blockedTitleHitItem);
}
} else {
// 使用 屏蔽标题数组 与 视频标题 进行匹配
const blockedTitleHitItem = blockedParameter.blockedTitle_Array.find((blockedTitleItem) => {
// 判断 屏蔽标题 是否匹配 视频标题
if (blockedTitleItem === videoInfoDict[videoBv].videoTitle) {
// blockedRulesItemText = videoInfoDict[videoBv].videoTitle;
return true;
}
});
if (blockedTitleHitItem) {
// 标记为屏蔽目标并记录触发的规则
markAsBlockedTarget(videoBv, "屏蔽标题", blockedTitleHitItem);
}
}
}
// 网页获取视频UP名和UpUid (已经有API获取为什么还要网页获取?因为快……)
function getNameAndUid(videoElement, videoBv) {
// 如果已经有 BV号 对应的 Up主名称 Up主Uid 记录,跳过
if (videoInfoDict[videoBv].videoUpName && videoInfoDict[videoBv].videoUpUid) {
return;
}
// 从视频元素中获取所有a标签链接
const videoLinkElements = videoElement.querySelectorAll("a");
// 循环处理所有a标签链接
for (let videoLinkElement of videoLinkElements) {
// 获取的链接,如果与 Uid 的链接格式匹配的话
const uidLink = videoLinkElement.href.match(/space\.bilibili\.com\/(\d+)/);
if (uidLink) {
// 视频UpUid
videoInfoDict[videoBv].videoUpUid = uidLink[1];
// 视频Up名称
videoInfoDict[videoBv].videoUpName = videoLinkElement.querySelector("span").innerText;
}
}
}
// API获取视频信息
function getVideoApiInfo(videoBv) {
// 如果已经有BV号对应的记录,跳过
if (videoInfoDict[videoBv].videoDuration) {
return;
}
// 当 lastVideoInfoApiRequestTime 上次API获取视频信息的时间存在,并且,和当前的时间差小于3秒时,跳过
const currentTime = new Date(); //获取当前时间
if (
videoInfoDict[videoBv].lastVideoInfoApiRequestTime &&
currentTime - videoInfoDict[videoBv].lastVideoInfoApiRequestTime < 3000
) {
// consoleLogOutput(videoBv, "getVideoApiInfo() 距离上次 Fetch 获取视频信息还未超过3秒钟");
return;
}
videoInfoDict[videoBv].lastVideoInfoApiRequestTime = currentTime;
// 通过API获取视频UP信息
fetch(`https://api.bilibili.com/x/web-interface/view?bvid=${videoBv}`)
.then((response) => response.json())
.then((videoApiInfoJson) => {
// API获取的UP主名称:
videoInfoDict[videoBv].videoUpName = videoApiInfoJson.data.owner.name;
// API获取的UP主Uid:
videoInfoDict[videoBv].videoUpUid = videoApiInfoJson.data.owner.mid;
// API获取的视频AVid:
videoInfoDict[videoBv].videoAVid = videoApiInfoJson.data.aid;
// API获取的视频时长
videoInfoDict[videoBv].videoDuration = videoApiInfoJson.data.duration;
// API获取的视频分区
videoInfoDict[videoBv].videoPartitions = videoApiInfoJson.data.tname;
// API获取的视频播放数
videoInfoDict[videoBv].videoView = videoApiInfoJson.data.stat.view;
// API获取的视频点赞数
videoInfoDict[videoBv].videoLike = videoApiInfoJson.data.stat.like;
// 计算视频点赞率保留2位小数
videoInfoDict[videoBv].videoLikesRate = (
(videoInfoDict[videoBv].videoLike / videoInfoDict[videoBv].videoView) *
100
).toFixed(2);
// // API获取的视频投币数
// videoInfoDict[videoBv].videoCoin = videoApiInfoJson.data.stat.coin;
// // API获取的视频收藏数
// videoInfoDict[videoBv].videoFavorite = videoApiInfoJson.data.stat.favorite;
// // API获取的视频分享数
// videoInfoDict[videoBv].videoShare = videoApiInfoJson.data.stat.share;
// // API获取的视频评论数
// videoInfoDict[videoBv].videoReply = videoApiInfoJson.data.stat.reply;
// // API获取的视频弹幕数
// videoInfoDict[videoBv].videoDanmaku = videoApiInfoJson.data.stat.danmaku;
// API获取的视频是否为充电专属
videoInfoDict[videoBv].videoChargingExclusive = videoApiInfoJson.data.is_upower_exclusive;
// API获取的视频分辨率
if (!videoInfoDict[videoBv].videoResolution) {
videoInfoDict[videoBv].videoResolution = {};
}
videoInfoDict[videoBv].videoResolution.width = videoApiInfoJson.data.dimension.width;
videoInfoDict[videoBv].videoResolution.height = videoApiInfoJson.data.dimension.height;
FuckYouBilibiliRecommendationSystem();
})
.catch((error) => consoleLogOutput(videoBv, "getVideoApiInfo() Fetch错误:", error));
}
// 处理匹配短时长视频
function handleBlockedShortDuration(videoBv) {
// 判断是否拿到视频时长
if (!videoInfoDict[videoBv].videoDuration) {
return;
}
// 判断设置的屏蔽短时长视频值 是否大于 视频时长
if (blockedParameter.blockedShortDuration > videoInfoDict[videoBv].videoDuration) {
// 标记为屏蔽目标并记录触发的规则
markAsBlockedTarget(videoBv, "屏蔽短时长视频", videoInfoDict[videoBv].videoDuration + "秒");
}
}
// 处理 屏蔽低播放量视频
function handleBlockedBelowVideoViews(videoBv) {
// 判断是否拿到视频播放量
if (!videoInfoDict[videoBv].videoView) {
return;
}
// 判断设置的屏蔽视频点赞率值 是否大于 视频的点赞率
if (blockedParameter.blockedBelowVideoViews > videoInfoDict[videoBv].videoView) {
// 标记为屏蔽目标并记录触发的规则
markAsBlockedTarget(videoBv, "屏蔽低播放量", videoInfoDict[videoBv].videoView + "次");
}
}
// 处理匹配屏蔽低于指定点赞率的视频
function handleBlockedBelowLikesRate(videoBv) {
// 判断是否拿到视频点赞数
if (!videoInfoDict[videoBv].videoLikesRate) {
return;
}
// 判断设置的屏蔽视频点赞率值 是否大于 视频的点赞率
if (blockedParameter.blockedBelowLikesRate > videoInfoDict[videoBv].videoLikesRate) {
// 标记为屏蔽目标并记录触发的规则
markAsBlockedTarget(videoBv, "屏蔽低点赞率", videoInfoDict[videoBv].videoLikesRate + "%");
}
}
// 处理匹配屏蔽竖屏视频
function handleBlockedPortraitVideo(videoBv) {
// 判断是否拿到视频分辨率
if (!videoInfoDict[videoBv].videoResolution.width) {
return;
}
// 横向分辨率小于纵向分辨率就是竖屏
if (videoInfoDict[videoBv].videoResolution.width < videoInfoDict[videoBv].videoResolution.height) {
// 标记为屏蔽目标并记录触发的规则
markAsBlockedTarget(
videoBv,
"屏蔽竖屏视频",
`${videoInfoDict[videoBv].videoResolution.width} x ${videoInfoDict[videoBv].videoResolution.height}`
);
}
}
// 处理匹配 屏蔽充电专属视频
function handleBlockedChargingExclusive(videoBv) {
// 判断设置的屏蔽充电专属视频是否有启用标记
if (videoInfoDict[videoBv].videoChargingExclusive) {
// 标记为屏蔽目标并记录触发的规则
markAsBlockedTarget(videoBv, "屏蔽充电专属的视频", videoInfoDict[videoBv].videoUpName);
}
}
// 处理匹配的屏蔽Up主名称或Up主Uid
function handleBlockedNameOrUid(videoBv) {
// 判断是否拿到Up主名称或Up主Uid
if (!videoInfoDict[videoBv].videoUpUid) {
return;
}
// 记录触发的规则内容
let blockedRulesItemText = "";
// 是否启用正则
if (blockedParameter.blockedNameOrUid_UseRegular) {
// 使用 屏蔽Up名称和Uid数组 与 视频Up主Uid 和 视频Up主名称 进行匹配
const blockedNameOrUidHitItem = blockedParameter.blockedNameOrUid_Array.find((blockedNameOrUidItem) => {
// 正则化屏蔽Up主名称、视频Up主Uid
const blockedNameOrUidRegEx = new RegExp(blockedNameOrUidItem);
// 只有UP名称有正则的意义,Uid依然是直接对比
if (blockedNameOrUidRegEx.test(videoInfoDict[videoBv].videoUpName)) {
blockedRulesItemText = videoInfoDict[videoBv].videoUpName;
return true;
}
if (blockedNameOrUidItem == videoInfoDict[videoBv].videoUpUid) {
blockedRulesItemText = videoInfoDict[videoBv].videoUpUid;
return true;
}
});
if (blockedNameOrUidHitItem) {
// 标记为屏蔽目标并记录触发的规则
markAsBlockedTarget(videoBv, "屏蔽UP", blockedRulesItemText);
}
} else {
// 使用 屏蔽Up名称和Uid数组 与 视频Up主Uid 和 视频Up主名称 进行匹配
const blockedNameOrUidHitItem = blockedParameter.blockedNameOrUid_Array.find((blockedNameOrUidItem) => {
if (blockedNameOrUidItem == videoInfoDict[videoBv].videoUpName) {
blockedRulesItemText = videoInfoDict[videoBv].videoUpName;
return true;
}
if (blockedNameOrUidItem == videoInfoDict[videoBv].videoUpUid) {
blockedRulesItemText = videoInfoDict[videoBv].videoUpUid;
return true;
}
});
if (blockedNameOrUidHitItem) {
// 标记为屏蔽目标并记录触发的规则
markAsBlockedTarget(videoBv, "屏蔽UP", blockedRulesItemText);
}
}
}
// 处理匹配的屏蔽视频分区
function handleBlockedVideoPartitions(videoBv) {
// 判断是否拿到视频分区
if (!videoInfoDict[videoBv].videoPartitions) {
return;
}
// 记录触发的规则内容
let blockedRulesItemText = "";
// 是否启用正则
if (blockedParameter.blockedVideoPartitions_UseRegular) {
// 使用 屏蔽视频分区数组 与 视频分区 进行匹配
const blockedVideoPartitionsHitItem = blockedParameter.blockedVideoPartitions_Array.find(
(blockedVideoPartitionsItem) => {
// 正则化屏蔽视频标签
const blockedVideoPartitionsRegEx = new RegExp(blockedVideoPartitionsItem);
if (blockedVideoPartitionsRegEx.test(videoInfoDict[videoBv].videoPartitions)) {
blockedRulesItemText = videoInfoDict[videoBv].videoPartitions;
return true;
}
}
);
if (blockedVideoPartitionsHitItem) {
// 标记为屏蔽目标并记录触发的规则
markAsBlockedTarget(videoBv, "屏蔽分区", blockedRulesItemText);
}
} else {
// 使用 屏蔽视频分区数组 与 视频分区 进行匹配
const blockedVideoPartitionsHitItem = blockedParameter.blockedVideoPartitions_Array.find(
(blockedVideoPartitionsItem) => {
if (blockedVideoPartitionsItem == videoInfoDict[videoBv].videoPartitions) {
blockedRulesItemText = videoInfoDict[videoBv].videoPartitions;
return true;
}
}
);
if (blockedVideoPartitionsHitItem) {
// 标记为屏蔽目标并记录触发的规则
markAsBlockedTarget(videoBv, "屏蔽分区", blockedRulesItemText);
}
}
}
// API获取视频标签
function getVideoApiTags(videoBv) {
// 如果已经有BV号对应的记录,跳过
if (videoInfoDict[videoBv].videoTags) {
return;
}
// 当 lastVideoTagApiRequestTime 上次API获取视频标签的时间存在,并且,和当前的时间差小于3秒时,跳过
const currentTime = new Date(); //获取当前时间
if (
videoInfoDict[videoBv].lastVideoTagApiRequestTime &&
currentTime - videoInfoDict[videoBv].lastVideoTagApiRequestTime < 3000
) {
// consoleLogOutput(videoBv, "getVideoApiTags() 距离上次 Fetch 获取视频信息还未超过3秒钟");
return;
}
videoInfoDict[videoBv].lastVideoTagApiRequestTime = currentTime;
// 获取视频标签
fetch(`https://api.bilibili.com/x/web-interface/view/detail/tag?bvid=${videoBv}`)
.then((response) => response.json())
.then((videoApiTagsJson) => {
// API获取标签对象,提取标签名字数组
videoInfoDict[videoBv].videoTags = videoApiTagsJson.data.map((tagsArray) => tagsArray.tag_name);
FuckYouBilibiliRecommendationSystem();
})
.catch((error) => consoleLogOutput(videoBv, "getVideoApiTags() Fetch错误:", error));
}
// 处理匹配的屏蔽标签
function handleBlockedTag(videoBv) {
// 判断是否拿到视频标签
if (!videoInfoDict[videoBv].videoTags) {
return;
}
// 记录触发的规则内容
let blockedRulesItemText = "";
// 是否启用正则
if (blockedParameter.blockedTag_UseRegular) {
// 使用 屏蔽标签数组 与 视频标题数组 进行匹配
const blockedTagHitItem = blockedParameter.blockedTag_Array.find((blockedTagItem) => {
// 正则化屏蔽视频标签
const blockedTagRegEx = new RegExp(blockedTagItem);
// 使用 屏蔽标签正则 和 视频标题数组 进行匹配
const videoTagHitItem = videoInfoDict[videoBv].videoTags.find((videoTagItem) =>
blockedTagRegEx.test(videoTagItem)
);
if (videoTagHitItem) {
blockedRulesItemText = videoTagHitItem;
return true;
}
});
if (blockedTagHitItem) {
// 标记为屏蔽目标并记录触发的规则
markAsBlockedTarget(videoBv, "屏蔽标签", blockedRulesItemText);
}
} else {
// 使用 屏蔽标签数组 与 视频标题数组 进行匹配
const blockedTagHitItem = blockedParameter.blockedTag_Array.find((blockedTagItem) => {
// 使用 屏蔽标签 和 视频标题数组 进行匹配
const videoTagHitItem = videoInfoDict[videoBv].videoTags.find(
(videoTagItem) => blockedTagItem == videoTagItem
);
if (videoTagHitItem) {
blockedRulesItemText = videoTagHitItem;
return true;
}
});
if (blockedTagHitItem) {
// 标记为屏蔽目标并记录触发的规则
markAsBlockedTarget(videoBv, "屏蔽标签", blockedRulesItemText);
}
}
}
// 处理匹配屏蔽双重屏蔽标签
function handleDoubleBlockedTag(videoBv) {
// 判断是否拿到视频标签
if (!videoInfoDict[videoBv].videoTags) {
return;
}
// 记录触发的规则内容
let blockedRulesItemText = "";
// 是否启用正则
if (blockedParameter.doubleBlockedTag_UseRegular) {
// 使用 双重屏蔽标签数组 与 视频标签 进行匹配
const doubleBlockedTagHitItem = blockedParameter.doubleBlockedTag_Array.find((doubleBlockedTag) => {
// 以 "|" 分割成数组,同时都能匹配上才是符合
const doubleBlockedTagSplitArray = doubleBlockedTag.split("|");
const doubleBlockedTagRegEx0 = new RegExp(doubleBlockedTagSplitArray[0]);
const doubleBlockedTagRegEx1 = new RegExp(doubleBlockedTagSplitArray[1]);
const videoTagHitItem0 = videoInfoDict[videoBv].videoTags.find((videoTagItem) =>
doubleBlockedTagRegEx0.test(videoTagItem)
);
const videoTagHitItem1 = videoInfoDict[videoBv].videoTags.find((videoTagItem) =>
doubleBlockedTagRegEx1.test(videoTagItem)
);
if (videoTagHitItem0 && videoTagHitItem1) {
blockedRulesItemText = `${videoTagHitItem0},${videoTagHitItem1}`;
return true;
}
});
if (doubleBlockedTagHitItem) {
// 标记为屏蔽目标并记录触发的规则
markAsBlockedTarget(videoBv, "屏蔽双重标签", blockedRulesItemText);
}
} else {
// 使用 双重屏蔽标签数组 与 视频标签 进行匹配
const doubleBlockedTagHitItem = blockedParameter.doubleBlockedTag_Array.find((doubleBlockedTag) => {
// 以 "|" 分割成数组,同时都能匹配上才是符合
const doubleBlockedTagSplitArray = doubleBlockedTag.split("|");
const videoTagHitItem0 = videoInfoDict[videoBv].videoTags.find(
(videoTagItem) => doubleBlockedTagSplitArray[0] == videoTagItem
);
const videoTagHitItem1 = videoInfoDict[videoBv].videoTags.find(
(videoTagItem) => doubleBlockedTagSplitArray[1] == videoTagItem
);
if (videoTagHitItem0 && videoTagHitItem1) {
blockedRulesItemText = `${videoTagHitItem0},${videoTagHitItem1}`;
return true;
}
});
if (doubleBlockedTagHitItem) {
// 标记为屏蔽目标并记录触发的规则
markAsBlockedTarget(videoBv, "屏蔽双重标签", blockedRulesItemText);
}
}
}
// API获取视频评论区
let apiRequestDelayTime = 0;
function getVideoApiComments(videoBv) {
// 如果已经有BV号对应的记录,跳过
if (videoInfoDict[videoBv].filteredComments === false || videoInfoDict[videoBv].filteredComments === true) {
return;
}
// 当 lastVideoCommentsApiRequestTime 上次API获取视频评论区的时间存在,并且,和当前的时间差小于3秒时,跳过
const currentTime = new Date(); //获取当前时间
if (
videoInfoDict[videoBv].lastVideoCommentsApiRequestTime &&
currentTime - videoInfoDict[videoBv].lastVideoCommentsApiRequestTime < 3000
) {
// consoleLogOutput(videoBv, "getVideoApiComments() 距离上次 Fetch 获取视频信息还未超过3秒钟");
return;
}
// 获取评论区的API貌似对频繁请求的容忍度很低,只能错开来请求,apiRequestDelayTime 延迟。
// 所以设置了每次调用 getVideoApiComments() 都会增加延迟,例如:每次加 50ms 再请求下一个请求。
// lastVideoCommentsApiRequestTime(上次API获取视频评论区的时间) 本质是为了限制每个BV号3秒只能请求一次,
// 但是加了延迟之后,到后面 apiRequestDelayTime 延迟本身就会超过3秒了。
// 还是会出现多次请求的问题,可能影响不大,但是还是把延迟值加进了 lastVideoCommentsApiRequestTime 里面。
// 这也相当于把 lastVideoCommentsApiRequestTime 修正为了正确请求时间。
let apiRequestDelayTimeData = new Date(apiRequestDelayTime);
videoInfoDict[videoBv].lastVideoCommentsApiRequestTime = new Date(
currentTime.getTime() + apiRequestDelayTimeData.getTime()
);
// apiRequestDelayTime 的最大值限制问题
// 如果不做限制的话,这个值可能会无限增大,导致最后加载的视频元素的请求也永远等不到生效时间。
// 以 videoInfoDict 对象的长度来做最大值限制貌似会比较合理一点。但是这个对象也可能会无限增大从而导致后面的请求等太久。
// 如果把 videoInfoDict[videoBv].filteredComments 筛选为 null 后的统计数值x延迟时间,做为最大延迟时间比较好?
// lastVideoCommentsApiRequestTime 也保证了每个Bv号的对应请求3秒只出现一次,这样就不用担心重复请求的问题。
// 但是本质上这一堆处理只是为了:防止频繁请求 https://api.bilibili.com/x/v2/reply 出现拒绝,同时为了效率的问题,每个Bv号只应该请求一次。
// 统计 videoInfoDict 中,视频Bv下面的 filteredComments 不存在的数量。
function filteredCommentsCount() {
let nullCount = 0;
for (const video in videoInfoDict) {
if (videoInfoDict[video].hasOwnProperty("filteredComments") == false) {
nullCount++;
}
}
return nullCount;
}
// 最大的延迟时间上限
let apiRequestDelayTimeMax = filteredCommentsCount() * 100;
// consoleLogOutput("最大的延迟时间上限", apiRequestDelayTimeMax);
// 每次调用增加的延迟 > 最大的延迟时间上限后 重置为0
if (apiRequestDelayTime > apiRequestDelayTimeMax) {
apiRequestDelayTime = 0;
}
setTimeout(() => {
// 设置请求的 URL 和参数
const url = "https://api.bilibili.com/x/v2/reply";
const params = {
type: 1, // 评论区类型代码
oid: videoBv, // 目标评论区 id
sort: 0, // 排序方式,默认为0,0:按时间,1:按点赞数,2:按回复数
ps: 1, // 每页项数,默认为20,定义域:1-20
pn: 1, // 页码,默认为1
nohot: 0, // 是否不显示热评,默认为0,1:不显示,0:显示
};
// 将参数转换为 URL 搜索字符串
const searchParams = new URLSearchParams(params).toString();
// 获取视频评论区
fetch(`${url}?${searchParams}`)
.then((response) => response.json())
.then((VideoApiCommentsJson) => {
// API获取精选评论标记
videoInfoDict[videoBv].filteredComments = VideoApiCommentsJson.data?.control?.web_selection;
// API获取置顶评论内容
videoInfoDict[videoBv].topComment = VideoApiCommentsJson.data.upper.top?.content?.message;
FuckYouBilibiliRecommendationSystem();
})
.catch((error) => consoleLogOutput(videoBv, "getVideoApiComments() Fetch错误:", error));
}, apiRequestDelayTime);
// 每次调用增加的延迟
// consoleLogOutput("本次调用增加延迟", apiRequestDelayTime);
apiRequestDelayTime = apiRequestDelayTime + 100;
}
// 处理匹配 屏蔽精选评论的视频
function handleBlockedFilteredCommentsVideo(videoBv) {
// 判断设置的屏蔽精选评论的视频是否有启用标记
if (videoInfoDict[videoBv].filteredComments) {
// 标记为屏蔽目标并记录触发的规则
markAsBlockedTarget(videoBv, "屏蔽精选评论的视频", videoInfoDict[videoBv].videoUpName);
}
}
// 处理匹配 屏蔽置顶评论内容
function handleBlockedTopComment(videoBv) {
// 判断是否拿到视频置顶评论
if (!videoInfoDict[videoBv].topComment) {
return;
}
// 记录触发的规则内容
// let blockedRulesItemText = "";
// 是否启用正则
if (blockedParameter.blockedTopComment_UseRegular) {
// 使用 屏蔽置顶评论数组 与 置顶评论 进行匹配
const blockedTopCommentHitItem = blockedParameter.blockedTopComment_Array.find((blockedTopComment) => {
// 正则化屏蔽置顶评论
const blockedTitleRegEx = new RegExp(blockedTopComment);
// 判断 正则化的屏蔽置顶评论 是否匹配 置顶评论
if (blockedTitleRegEx.test(videoInfoDict[videoBv].topComment)) {
return true;
}
});
if (blockedTopCommentHitItem) {
// 标记为屏蔽目标并记录触发的规则
markAsBlockedTarget(videoBv, "屏蔽置顶评论", blockedTopCommentHitItem);
}
} else {
// 使用 屏蔽置顶评论数组 与 置顶评论 进行匹配
const blockedTopCommentHitItem = blockedParameter.blockedTopComment_Array.find((blockedTopComment) => {
// 判断 屏蔽置顶评论 是否匹配 置顶评论
if (blockedTopComment === videoInfoDict[videoBv].topComment) {
return true;
}
});
if (blockedTopCommentHitItem) {
// 标记为屏蔽目标并记录触发的规则
markAsBlockedTarget(videoBv, "屏蔽置顶评论", blockedTopCommentHitItem);
}
}
}
// 处理匹配的白名单Up主和Uid
function handleWhitelistNameOrUid(videoBv) {
// 判断是否拿到Up主名称或Up主Uid
if (!videoInfoDict[videoBv].videoUpUid) {
return;
}
// 使用 白名单Up主和Uid数组 与 视频Up主Uid 和 视频Up主名称 进行匹配
const videoNameOrUid = blockedParameter.whitelistNameOrUid_Array.find((whitelistNameOrUidItem) => {
if (whitelistNameOrUidItem == videoInfoDict[videoBv].videoUpName) {
return true;
}
if (whitelistNameOrUidItem == videoInfoDict[videoBv].videoUpUid) {
return true;
}
});
if (videoNameOrUid) {
// 标记为白名单目标
videoInfoDict[videoBv].whiteListTargets = true;
}
}
// 隐藏非视频元素
function hideNonVideoElements() {
// 判断当前页面URL是否以 https://www.bilibili.com/ 开头,即首页
if (window.location.href.startsWith("https://www.bilibili.com/")) {
// 隐藏首页的番剧、国创、直播等左上角有标的元素,以及左上角没标的直播
const adElements_1 = document.querySelectorAll("div.floor-single-card, div.bili-live-card");
adElements_1.forEach(function (element) {
element.style.display = "none";
});
}
// 判断当前页面URL是否以 https://search.bilibili.com/all 开头,即搜索页——综合
if (window.location.href.startsWith("https://search.bilibili.com/all")) {
// 隐藏 搜索页——综合 下的 直播卡片
const adElements_2 = document.querySelectorAll("div.bili-video-card:has(div.bili-video-card__info--living)");
adElements_2.forEach(function (element) {
element.parentNode.style.display = "none";
element.style.display = "none";
});
}
// 隐藏首页广告,那些没有“enable-no-interest” CSS类的视频卡片元素
const adElements_3 = document.querySelectorAll("div.bili-video-card.is-rcmd:not(.enable-no-interest)");
adElements_3.forEach(function (element) {
// 检查其父元素是否是 .feed-card
if (element.closest("div.feed-card") !== null) {
// 如果是,选择其父元素并应用样式
element.closest("div.feed-card").style.display = "none";
} else {
// 如果不是,直接在视频元素上应用样式
element.style.display = "none";
}
});
// 隐藏视频播放页右侧广告、视频相关的游戏推荐、视频相关的特殊推荐、大家围观的直播
const adElements_4 = document.querySelectorAll(
"div#slide_ad, a.ad-report, div.video-page-game-card-small, div.video-page-special-card-small, div.pop-live-small-mode"
);
adElements_4.forEach(function (element) {
element.style.display = "none";
});
}
// 屏蔽或者取消屏蔽
function blockedOrUnblocked(videoElement, videoBv, setTimeoutStatu = false) {
// 是白名单目标,是屏蔽目标,没有隐藏、没有叠加层:跳过
if (
videoInfoDict[videoBv].whiteListTargets &&
videoInfoDict[videoBv].blockedTarget &&
videoElement.style.display != "none" &&
videoElement.firstElementChild.className != "blockedOverlay"
) {
return;
}
// 是白名单目标,是屏蔽目标, 有隐藏或有叠加层:去除隐藏或叠加层
if (
videoInfoDict[videoBv].whiteListTargets &&
videoInfoDict[videoBv].blockedTarget &&
(videoElement.style.display == "none" || videoElement.firstElementChild.className == "blockedOverlay")
) {
// 去除叠加层
removeHiddenOrOverlay(videoElement, videoBv, setTimeoutStatu);
return;
}
// 不是白名单目标,是屏蔽目标, 有隐藏或有叠加层:跳过
if (
videoInfoDict[videoBv].whiteListTargets != true &&
videoInfoDict[videoBv].blockedTarget &&
(videoElement.style.display == "none" || videoElement.firstElementChild.className == "blockedOverlay")
) {
return;
}
// 不是白名单目标,是屏蔽目标, 没有隐藏、没有叠加层:隐藏或添加叠加层
if (
videoInfoDict[videoBv].whiteListTargets != true &&
videoInfoDict[videoBv].blockedTarget &&
videoElement.style.display != "none" &&
videoElement.firstElementChild.className != "blockedOverlay"
) {
// 隐藏或添加叠加层
addHiddenOrOverlay(videoElement, videoBv, setTimeoutStatu);
return;
}
// 隐藏或添加叠加层
function addHiddenOrOverlay(videoElement, videoBv, setTimeoutStatu) {
// 是否为隐藏视频模式?
if (blockedParameter.hideVideoMode_Switch == true) {
// 隐藏视频
// 判断当前页面URL是否以 https://search.bilibili.com/ 开头,即搜索页面,修改父元素
if (window.location.href.startsWith("https://search.bilibili.com/")) {
videoElement.parentNode.style.display = "none";
// 为什么改了父元素,还要改元素本身?为了方便上面的判断。
videoElement.style.display = "none";
}
// 如果是父元素是feed-card,修改父元素
else if (videoElement.closest("div.feed-card") !== null) {
videoElement.closest("div.feed-card").style.display = "none";
videoElement.style.display = "none";
} else {
videoElement.style.display = "none";
}
} else {
// 添加叠加层
// Bug记录:
// 位置: 视频播放页面 (即 https://www.bilibili.com/video/BVxxxxxx 页面下)
// 行为: 添加屏蔽叠加层 这个操作 只因为 屏蔽标签 的方式来触发时 (如果还触发了 屏蔽标题 屏蔽短时长 这一类,是不会出现这个Bug的。)
// 症状: 渲染异常,右侧视频推荐列表的封面图片不可见;评论区丢失;页面头部的搜索框丢失 (div.center-search__bar 丢失);
// 处理: 延迟添加 overlay 可解决,先暂时把元素变成透明/模糊的,等3秒,页面完全加载完了,再创建创建屏蔽叠加层,再把元素改回正常。
// 猜测: 我一开始以为是使用 fetch 获取API造成的,因为只有 屏蔽标签 这个操作必须通过 fetch 获取标签信息的。
// 但是出现 屏蔽标题 屏蔽短时长 多种触发的情况下,又不会触发这个Bug了,想不懂,我也不会调试这种加载过程。
// 在 视频播放页面 "card-box" 创建屏蔽叠加层操作作延迟处理
if (videoElement.firstElementChild.className == "card-box" && setTimeoutStatu == false) {
// 元素先改模糊
// videoElement.style.opacity = "0";
videoElement.style.filter = "blur(5px)";
// 延迟3秒
setTimeout(() => {
// 创建屏蔽叠加层
blockedOrUnblocked(videoElement, videoBv, true);
// 元素再改回正常
// videoElement.style.opacity = "1";
videoElement.style.filter = "none";
}, 3000);
return;
}
// 获取 videoElement 的尺寸
const elementRect = videoElement.getBoundingClientRect();
// 叠加层参数(背景)
let overlay = document.createElement("div");
overlay.className = "blockedOverlay";
overlay.style.position = "absolute";
overlay.style.width = elementRect.width + "px"; // 使用 videoElement 的宽度
overlay.style.height = elementRect.height + "px"; // 使用 videoElement 的高度
overlay.style.backgroundColor = "rgba(60, 60, 60, 0.85)";
overlay.style.display = "flex";
overlay.style.justifyContent = "center";
overlay.style.alignItems = "center";
overlay.style.zIndex = "10";
overlay.style.backdropFilter = "blur(6px)";
overlay.style.borderRadius = "6px";
// 叠加层文本参数(背景)
let overlayText = document.createElement("div");
if (videoElement.firstElementChild.className == "card-box") {
overlayText.style.fontSize = "1.25em";
}
// 使用 videoInfoDict[videoBv] 里面的存储的触发规则的第1条来做为提示文字
overlayText.innerText = videoInfoDict[videoBv].triggeredBlockedRules[0];
overlayText.style.color = "rgb(250,250,250)";
overlay.appendChild(overlayText);
// 添加叠加层为最前面的子元素
videoElement.insertAdjacentElement("afterbegin", overlay);
}
}
// 去除隐藏或叠加层
function removeHiddenOrOverlay(videoElement) {
// 是否为隐藏视频模式?
if (blockedParameter.hideVideoMode_Switch == true) {
// 取消隐藏
// 判断当前页面URL是否以 https://search.bilibili.com/ 开头,即搜索页面
if (window.location.href.startsWith("https://search.bilibili.com/")) {
videoElement.parentNode.style.display = "";
videoElement.style.display = "";
}
// 如果是父元素是feed-card
else if (videoElement.closest("div.feed-card") !== null) {
videoElement.closest("div.feed-card").style.display = "";
videoElement.style.display = "";
} else {
videoElement.style.display = "";
}
} else {
// 删除叠加层
if (videoElement.firstElementChild.className == "blockedOverlay") {
videoElement.removeChild(videoElement.firstElementChild);
}
}
}
}
// 同步屏蔽叠加层与父元素的尺寸
function syncBlockedOverlayAndParentNodeRect() {
// 获取所有的屏蔽叠加层
const blockedOverlays = document.querySelectorAll("div.blockedOverlay");
blockedOverlays.forEach(function (element) {
// 获取父元素的尺寸
const parentNodeElementRect = element.parentNode.getBoundingClientRect();
// 修改屏蔽叠加层的大小
element.style.width = parentNodeElementRect.width + "px"; // 使用 父元素的尺寸 的宽度
element.style.height = parentNodeElementRect.height + "px"; // 使用 父元素的尺寸 的高度
});
}
// -----------------主流程函数----------------------
// 屏蔽Bilibili上的符合屏蔽条件的视频
function FuckYouBilibiliRecommendationSystem() {
// 是否启用 隐藏非视频元素
if (blockedParameter.hideNonVideoElements_Switch) {
// 隐藏非视频元素
hideNonVideoElements();
}
// 判断是否和上次的输出的字典不一样
if (objectDifferent(lastConsoleVideoInfoDict, videoInfoDict)) {
// 输出整个视频信息字典
consoleLogOutput(Object.keys(videoInfoDict).length, "个视频信息: ", videoInfoDict);
// 将本次输出的视频信息字典保存起来作参考
lastConsoleVideoInfoDict = Object.assign({}, videoInfoDict);
}
// 获取所有包含B站视频相关标签的视频元素
const videoElements = getVideoElements();
// 遍历每个视频元素
for (let videoElement of videoElements) {
// 判断是否为已经屏蔽处理过的子元素
if (isAlreadyBlockedChildElement(videoElement)) {
// 如果是已经屏蔽处理过的子元素,跳过后续操作
continue;
}
// 网页获取视频元素的Bv号和标题
let videoBv = getBvAndTitle(videoElement);
// 如果没有拿到Bv号,跳过后续操作
if (!videoBv) {
continue;
}
// 是否启用 屏蔽标题
if (blockedParameter.blockedTitle_Switch && blockedParameter.blockedTitle_Array.length > 0) {
// 判断处理匹配的屏蔽标题
handleBlockedTitle(videoBv);
}
// 网页获取视频Up名和UpUid
getNameAndUid(videoElement, videoBv);
// 通过API获取视频信息
getVideoApiInfo(videoBv);
// 是否启用 屏蔽Up主名称或Up主Uid
if (blockedParameter.blockedNameOrUid_Switch && blockedParameter.blockedNameOrUid_Array.length > 0) {
// 判断处理匹配的屏蔽Up主名称或Up主Uid
handleBlockedNameOrUid(videoBv);
}
// 是否启用 屏蔽视频分区
if (
blockedParameter.blockedVideoPartitions_Switch &&
blockedParameter.blockedVideoPartitions_Array.length > 0
) {
// 判断处理匹配 屏蔽视频分区
handleBlockedVideoPartitions(videoBv);
}
// 是否启用 屏蔽短时长视频
if (blockedParameter.blockedShortDuration_Switch && blockedParameter.blockedShortDuration > 0) {
// 判断处理匹配的短时长视频
handleBlockedShortDuration(videoBv);
}
// 是否启用 屏蔽低播放量视频
if (blockedParameter.blockedBelowVideoViews_Switch && blockedParameter.blockedBelowVideoViews > 0) {
// 判断处理匹配的低播放量视频
handleBlockedBelowVideoViews(videoBv);
}
// 是否启用 屏蔽低于指定点赞率的视频
if (blockedParameter.blockedBelowLikesRate_Switch && blockedParameter.blockedBelowLikesRate > 0) {
// 判断处理 屏蔽低于指定点赞率的视频
handleBlockedBelowLikesRate(videoBv);
}
// 是否启用 屏蔽竖屏视频
if (blockedParameter.blockedPortraitVideo_Switch) {
// 判断处理 屏蔽竖屏视频
handleBlockedPortraitVideo(videoBv);
}
// 是否启用 屏蔽充电专属视频
if (blockedParameter.blockedChargingExclusive_Switch) {
// 判断处理 蔽充电专属视频
handleBlockedChargingExclusive(videoBv);
}
// 通过API获取视频标签
getVideoApiTags(videoBv);
// 是否启用 屏蔽标签
if (blockedParameter.blockedTag_Switch && blockedParameter.blockedTag_Array.length > 0) {
// 判断处理 屏蔽标签
handleBlockedTag(videoBv);
}
// 是否启用 屏蔽双重屏蔽标签
if (blockedParameter.doubleBlockedTag_Switch && blockedParameter.doubleBlockedTag_Array.length > 0) {
// 判断处理 屏蔽双重屏蔽标签
handleDoubleBlockedTag(videoBv);
}
// API获取视频评论区
getVideoApiComments(videoBv);
// 是否启用 屏蔽精选评论的视频
if (blockedParameter.blockedFilteredCommentsVideo_Switch) {
// 判断处理 屏蔽精选评论的视频
handleBlockedFilteredCommentsVideo(videoBv);
}
// 是否启用 屏蔽置顶评论
if (blockedParameter.blockedTopComment_Switch && blockedParameter.blockedTopComment_Array.length > 0) {
// 判断处理 屏蔽精选评论的视频
handleBlockedTopComment(videoBv);
}
// 是否启用 白名单Up主和Uid
if (blockedParameter.whitelistNameOrUid_Switch && blockedParameter.whitelistNameOrUid_Array.length > 0) {
// 判断处理 白名单Up主和Uid
handleWhitelistNameOrUid(videoBv);
}
// 屏蔽或者取消屏蔽
blockedOrUnblocked(videoElement, videoBv);
// 同步屏蔽叠加层与父元素的尺寸
syncBlockedOverlayAndParentNodeRect();
}
}
// 页面加载完成后运行脚本
window.addEventListener("load", FuckYouBilibiliRecommendationSystem);
// 窗口尺寸变化时运行脚本
window.addEventListener("resize", FuckYouBilibiliRecommendationSystem);
// 定义 MutationObserver 的回调函数
function mutationCallback() {
// 在这里运行你的脚本
FuckYouBilibiliRecommendationSystem();
}
// 创建一个 MutationObserver 实例,观察 body 元素的子节点变化
let observer = new MutationObserver(mutationCallback);
let targetNode = document.body;
// 配置观察器的选项
let config = { childList: true, subtree: true };
// 启动观察器并传入回调函数和配置选项
observer.observe(targetNode, config);