- // ==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://gf.qytechs.cn/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://gf.qytechs.cn/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);