Bilibili 按标签、标题、时长、UP主屏蔽视频

对Bilibili的视频卡片,以标签、标题、UP主、时长、竖屏、充电、评论等信息来屏蔽视频,附带去除视频卡片中的直播、广告、推广内容的功能。

  1. // ==UserScript==
  2. // @name Bilibili 按标签、标题、时长、UP主屏蔽视频
  3. // @namespace https://github.com/tjxwork
  4. // @version 1.1.5
  5. // @note
  6. // @note 新版本的视频介绍,来拯救一下我可怜的播放量吧 ●︿●
  7. // @note 应该是目前B站最强的屏蔽视频插件?【tjxgame】
  8. // @note https://www.bilibili.com/video/BV1WJ4m1u79n
  9. // @note
  10. // @note 作者的爱发电:https://afdian.com/a/tjxgame
  11. // @note 欢迎订阅支持、提需求,您的赞助支持就是维护更新的最大动力!
  12. // @note
  13. // @note v1.1.5 修正导致缓存记录对象的 videoLink 记录出错的部分代码; 修改赞助按钮的跳出连接; (我真的是不知道什么鬼运气,去哪哪崩,刚开的爱发电也崩了。)
  14. // @note v1.1.4 添加新功能:“屏蔽叠加层的提示只显示类型”,有部分用户可能连命中的屏蔽词都不想看到,但是又倾向使用叠加层模式,所以增加了这个开关。
  15. // @note 感谢来自爱发电的赞助需求。
  16. // @note v1.1.3 兼容脚本处理:[bv2av](https://gf.qytechs.cn/zh-CN/scripts/398535)(此脚本会将视频链接替换为旧的 AV 号链接),感谢 @Henry-ZHR 的提出;
  17. // @note 不完善功能修复:每次触发运行时,会将屏蔽叠加背景层与父元素尺寸进行同步,解决了页面布局变化时叠加层不跟随变化,感谢 @Henry-ZHR 的建议;
  18. // @note “隐藏首页等页面的非视频元素” 功能生效范围增加:隐藏 搜索页——综合 下的 直播卡片
  19. // @note v1.1.2 添加新功能:“按置顶评论屏蔽”;
  20. // @note 注意:“按置顶评论屏蔽”、“屏蔽精选评论的视频” 这两个功能都用到了获取评论的API,
  21. // @note 这个API对请求频率非常敏感,频繁刷新或者开启新页面会导致B站拒绝请求,正常浏览一般不会出现拒绝问题。
  22. // @note v1.1.1 添加新功能:“屏蔽充电专属的视频”;
  23. // @note v1.1.0 添加新功能:“屏蔽精选评论的视频”,骗子视频大概率会开启精选评论;
  24. // @note “隐藏首页等页面的非视频元素” 功能生效范围增加:隐藏视频播放页右侧视频相关的游戏推荐;
  25. // @note 控制台输出日志优化:现在只有发生变化的时候才会输出;
  26. // @note v1.0.2 “隐藏首页等页面的非视频元素” 功能生效范围增加:隐藏视频播放页右侧最下方的“大家围观的直播”
  27. // @note v1.0.1 修正了B站旧版首页的顶部推荐条失效的Bug;
  28. // @note 如果用旧版首页只是想要更多的顶部推荐的话,建议使用 bilibili-app-recommend 来获取更多的推荐。
  29. // @note 现在版本B站首页的推荐卡片有广告的问题,可以通过本脚本的 “隐藏首页等页面的非视频元素” 功能来解决。
  30. // @note v1.0.0 菜单UI使用Vue3重构,现在不用担心缩放问题挡住UI了,界面更加现代化;
  31. // @note 改进了判断逻辑,现在可以使用白名单来避免误杀关注的UP了;
  32. // @note 新增功能:视频分区屏蔽、播放量屏蔽、点赞率屏蔽、竖屏视频屏蔽、UP主名称正则屏蔽、隐藏非视频元素、白名单避免屏蔽指定UP。
  33. // @description 对Bilibili的视频卡片,以标签、标题、UP主、时长、竖屏、充电、评论等信息来屏蔽视频,附带去除视频卡片中的直播、广告、推广内容的功能。
  34. // @author tjxwork
  35. // @license CC-BY-NC-SA
  36. // @icon https://www.bilibili.com/favicon.ico
  37. // @match https://www.bilibili.com/*
  38. // @match https://www.bilibili.com/v/popular/all/*
  39. // @match https://www.bilibili.com/v/popular/weekly/*
  40. // @match https://www.bilibili.com/v/popular/history/*
  41. // @exclude https://www.bilibili.com/anime/*
  42. // @exclude https://www.bilibili.com/movie/*
  43. // @exclude https://www.bilibili.com/guochuang/*
  44. // @exclude https://www.bilibili.com/variety/*
  45. // @exclude https://www.bilibili.com/tv/*
  46. // @exclude https://www.bilibili.com/documentary*
  47. // @exclude https://www.bilibili.com/mooc/*
  48. // @exclude https://www.bilibili.com/v/virtual/*
  49. // @exclude https://www.bilibili.com/v/popular/music/*
  50. // @exclude https://www.bilibili.com/v/popular/drama/*
  51. // @match https://search.bilibili.com/*
  52. // @exclude https://search.bilibili.com/live
  53. // @grant GM_registerMenuCommand
  54. // @grant GM_setValue
  55. // @grant GM_getValue
  56. // @grant GM_addStyle
  57. // @require https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-w/vue/3.2.31/vue.global.min.js
  58. // ==/UserScript==
  59.  
  60. "use strict";
  61.  
  62. // --------------------参数变量初始化--------------------
  63.  
  64. // 初始化屏蔽参数变量,从 油猴扩展存储 读取到 blockedParameter
  65. let blockedParameter = GM_getValue("GM_blockedParameter", {
  66. // 屏蔽标题
  67. blockedTitle_Switch: true,
  68. blockedTitle_UseRegular: true,
  69. blockedTitle_Array: [],
  70.  
  71. // 屏蔽Up主和Uid
  72. blockedNameOrUid_Switch: true,
  73. blockedNameOrUid_UseRegular: false,
  74. blockedNameOrUid_Array: [],
  75.  
  76. // 屏蔽视频分区
  77. blockedVideoPartitions_Switch: true,
  78. blockedVideoPartitions_UseRegular: false,
  79. blockedVideoPartitions_Array: [],
  80.  
  81. // 屏蔽标签
  82. blockedTag_Switch: true,
  83. blockedTag_UseRegular: true,
  84. blockedTag_Array: [],
  85.  
  86. // 屏蔽双重屏蔽标签
  87. doubleBlockedTag_Switch: true,
  88. doubleBlockedTag_UseRegular: true,
  89. doubleBlockedTag_Array: [],
  90.  
  91. // 屏蔽短时长视频
  92. blockedShortDuration_Switch: false,
  93. blockedShortDuration: 0,
  94.  
  95. // 屏蔽低播放量视频
  96. blockedBelowVideoViews_Switch: false,
  97. blockedBelowVideoViews: 0,
  98.  
  99. // 屏蔽低于指定点赞率的视频
  100. blockedBelowLikesRate_Switch: false,
  101. blockedBelowLikesRate: 0,
  102.  
  103. // 屏蔽竖屏视频
  104. blockedPortraitVideo_Switch: false,
  105.  
  106. // 屏蔽充电专属的视频
  107. blockedChargingExclusive_Switch: false,
  108.  
  109. // 屏蔽精选评论的视频
  110. blockedFilteredCommentsVideo_Switch: false,
  111.  
  112. // 屏蔽置顶评论
  113. blockedTopComment_Switch: false,
  114. blockedTopComment_UseRegular: true,
  115. blockedTopComment_Array: [],
  116.  
  117. // 白名单Up主和Uid
  118. whitelistNameOrUid_Switch: false,
  119. whitelistNameOrUid_Array: [],
  120.  
  121. // 隐藏非视频元素
  122. hideNonVideoElements_Switch: true,
  123.  
  124. // 屏蔽叠加层的提示只显示类型而不显示命中项
  125. blockedOverlayOnlyDisplaysType_Switch: false,
  126.  
  127. // 隐藏视频而非叠加层模式
  128. hideVideoMode_Switch: false,
  129.  
  130. // 控制台输出日志
  131. consoleOutputLog_Switch: false,
  132. });
  133.  
  134. // 旧参数适配
  135. function oldParameterAdaptation(obj) {
  136. //判断是否为旧参数,是的话就修改为新参数结构
  137. if (Object.prototype.hasOwnProperty.call(obj, "blockedTitleArray")) {
  138. // 屏蔽标题
  139. obj["blockedTitle_Switch"] = true;
  140. obj["blockedTitle_UseRegular"] = true;
  141. obj["blockedTitle_Array"] = obj["blockedTitleArray"];
  142. delete obj["blockedTitleArray"];
  143.  
  144. // 屏蔽Up主和Uid
  145. obj["blockedNameOrUid_Switch"] = true;
  146. obj["blockedNameOrUid_UseRegular"] = true;
  147. obj["blockedNameOrUid_Array"] = obj["blockedNameOrUidArray"];
  148. delete obj["blockedNameOrUidArray"];
  149.  
  150. // 屏蔽视频分区
  151. obj["blockedVideoPartitions_Switch"] = false;
  152. obj["blockedVideoPartitions_UseRegular"] = false;
  153. obj["blockedVideoPartitions_Array"] = [];
  154.  
  155. // 屏蔽标签
  156. obj["blockedTag_Switch"] = true;
  157. obj["blockedTag_UseRegular"] = true;
  158. obj["blockedTag_Array"] = obj["blockedTagArray"];
  159. delete obj["blockedTagArray"];
  160.  
  161. // 屏蔽双重屏蔽标签
  162. obj["doubleBlockedTag_Switch"] = true;
  163. obj["doubleBlockedTag_UseRegular"] = true;
  164. obj["doubleBlockedTag_Array"] = obj["doubleBlockedTagArray"];
  165. delete obj["doubleBlockedTagArray"];
  166.  
  167. // 屏蔽短时长视频
  168. obj["blockedShortDuration_Switch"] = true;
  169.  
  170. // 白名单Up主和Uid
  171. obj["whitelistNameOrUid_Switch"] = false;
  172. obj["whitelistNameOrUid_Array"] = [];
  173.  
  174. // 隐藏视频而非叠加层模式
  175. obj["hideVideoMode_Switch"] = obj["hideVideoModeSwitch"];
  176. delete obj["hideVideoModeSwitch"];
  177.  
  178. // 控制台输出日志
  179. obj["consoleOutputLog_Switch"] = obj["consoleOutputLogSwitch"];
  180. delete obj["consoleOutputLogSwitch"];
  181. }
  182. }
  183. oldParameterAdaptation(blockedParameter);
  184.  
  185. // --------------------菜单UI部分--------------------
  186.  
  187. // 菜单UI的CSS,使用 GM_addStyle 注入 CSS
  188. GM_addStyle(`
  189. :root {
  190. /* 主窗体背景色 */
  191. --uiBackgroundColor: rgb(48, 48, 48);
  192. /* 输入模块背景色 */
  193. --uiInputContainerBackgroundColor: rgb(64, 64, 64);
  194. /* 输入框背景色 */
  195. --uiInputBoxBackgroundColor: rgb(89, 89, 89);
  196. /* 滚动条背景色 */
  197. --uiScrollbarBackgroundColor: rgb(141, 141, 141);
  198. /* 文字颜色 */
  199. --uiTextColor: rgb(250, 250, 250);
  200. /* 按钮色 */
  201. --uiButtonColor: rgb(0, 174, 236);
  202. /* 边框色 */
  203. --uiBorderColor: rgba(0, 0, 0, 0);
  204. /* 提醒框背景色 */
  205. --uiPromptBoxColor: rgb(42, 44, 53);
  206. /* 屏蔽叠加层背景色 */
  207. --blockedOverlayColor: rgba(60, 60, 60, 0.85);
  208. /* 字体大小 */
  209. --fontSize: 14px;
  210. /* 行高 */
  211. --lineHeight: 24px;
  212. /* 圆角 */
  213. --borderRadius: 4px;
  214. }
  215.  
  216. /* 菜单UI */
  217. #blockedMenuUi {
  218. font-size: var(--fontSize);
  219. position: fixed;
  220. bottom: 4vh;
  221. right: 2vw;
  222. z-index: 1005;
  223. width: 460px;
  224. max-height: 90vh;
  225. overflow-y: auto;
  226. background-color: var(--uiBackgroundColor);
  227. }
  228.  
  229. #blockedMenuUi,
  230. #blockedMenuUi * {
  231. color: var(--uiTextColor);
  232. box-sizing: border-box;
  233. border-style: solid;
  234. border-width: 0px;
  235. border-color: var(--uiBorderColor);
  236. border-radius: var(--borderRadius);
  237. line-height: var(--lineHeight);
  238. vertical-align: middle;
  239. font-family: "Cascadia Mono", Monaco, Consolas, "PingFang SC", "Helvetica Neue", "Microsoft YaHei", sans-serif;
  240. }
  241.  
  242. /* 滚动条 */
  243. #blockedMenuUi::-webkit-scrollbar,
  244. #blockedMenuUi ul::-webkit-scrollbar {
  245. width: 7px;
  246. }
  247.  
  248. /* 滚动条 轨道*/
  249. #blockedMenuUi::-webkit-scrollbar-track,
  250. #blockedMenuUi ul::-webkit-scrollbar-track {
  251. background: var(--uiScrollbarBackgroundColor);
  252. border-radius: 7px;
  253. }
  254.  
  255. /* 滚动条 滑块*/
  256. #blockedMenuUi::-webkit-scrollbar-thumb,
  257. #blockedMenuUi ul::-webkit-scrollbar-thumb {
  258. background: var(--uiInputContainerBackgroundColor);
  259. border-radius: 7px;
  260. }
  261.  
  262. /* 滚动条 滑块 鼠标经过 */
  263. #blockedMenuUi::-webkit-scrollbar-thumb:hover,
  264. #blockedMenuUi ul::-webkit-scrollbar-thumb:hover {
  265. background: var(--uiInputBoxBackgroundColor);
  266. border-radius: 7px;
  267. }
  268.  
  269. /* 滚动条 滑块 鼠标点击 */
  270. #blockedMenuUi::-webkit-scrollbar-thumb:active,
  271. #blockedMenuUi ul::-webkit-scrollbar-thumb:active {
  272. background: var(--uiButtonColor);
  273. border-radius: 7px;
  274. }
  275.  
  276. #menuTitle {
  277. font-size: 17px;
  278. text-align: center;
  279. margin: 10px;
  280. }
  281.  
  282. .menuOptions {
  283. background-color: var(--uiInputContainerBackgroundColor);
  284. padding: 10px;
  285. margin: 0 10px;
  286. margin-bottom: 10px;
  287. }
  288.  
  289. .titleLabelLeft {
  290. display: inline-block;
  291. width: 275px;
  292. margin-bottom: 5px;
  293. }
  294.  
  295. .titleLabelRight {
  296. display: inline-block;
  297. margin-bottom: 5px;
  298. }
  299.  
  300. #blockedMenuUi label {
  301. font-size: 16px;
  302. vertical-align: middle;
  303. }
  304.  
  305. #blockedMenuUi input {
  306. background-color: var(--uiInputBoxBackgroundColor);
  307. font-size: var(--fontSize);
  308. line-height: var(--lineHeight);
  309. border-radius: var(--borderRadius);
  310. padding: 0 5px;
  311. margin-bottom: 5px;
  312. width: 360px;
  313. vertical-align: middle;
  314. }
  315.  
  316. #blockedMenuUi input[type="number"] {
  317. width: 4em;
  318. margin: 0 5px;
  319. padding: 0 5px;
  320. text-align: right;
  321. appearance: none;
  322. }
  323.  
  324. #blockedMenuUi input[type="number"]::-webkit-inner-spin-button,
  325. #blockedMenuUi input[type="number"]::-webkit-outer-spin-button {
  326. -webkit-appearance: none;
  327. margin: 0;
  328. }
  329.  
  330. #blockedMenuUi input[type="checkbox"] {
  331. width: 16px;
  332. height: 16px;
  333. margin: 0;
  334. margin-bottom: 2.5px;
  335. margin-right: 5px;
  336. appearance: none;
  337. border: 1.5px solid var(--uiTextColor);
  338. border-radius: 8px;
  339. }
  340.  
  341. #blockedMenuUi input[type="checkbox"]:checked {
  342. border: 3px solid;
  343. background-color: var(--uiButtonColor);
  344. }
  345.  
  346. #blockedMenuUi button {
  347. line-height: var(--lineHeight);
  348. border-radius: var(--borderRadius);
  349. padding: 0;
  350. margin-bottom: 5px;
  351. margin-left: 5px;
  352. width: 45px;
  353. vertical-align: middle;
  354. background-color: var(--uiButtonColor);
  355. transition: background-color 0.1s ease;
  356. }
  357.  
  358. #blockedMenuUi button:hover {
  359. background-color: rgb(17, 154, 204);
  360. }
  361.  
  362. #blockedMenuUi button:active {
  363. background-color: rgb(62, 203, 255);
  364. }
  365.  
  366. #blockedMenuUi ul {
  367. background-color: var(--uiInputBoxBackgroundColor);
  368. font-size: 14px;
  369. padding: 5px 5px 0px 0px;
  370. margin-inline: 0px;
  371. margin: 0;
  372. width: 100%;
  373. min-height: 34px;
  374. max-height: 92px;
  375. overflow-y: auto;
  376. }
  377.  
  378. #blockedMenuUi li {
  379. line-height: var(--lineHeight);
  380. border-radius: var(--borderRadius);
  381. display: inline-block;
  382. padding: 0 5px;
  383. margin-bottom: 5px;
  384. margin-left: 5px;
  385. vertical-align: middle;
  386. background-color: var(--uiButtonColor);
  387. }
  388.  
  389.  
  390. #blockedMenuUi li button {
  391. width: 20px;
  392. margin: 0px;
  393. padding: 0 0 3px 0;
  394. font-size: 24px;
  395. line-height: 18px;
  396. border: 0px;
  397. }
  398.  
  399. #blockedMenuUi li button:hover {
  400. background-color: var(--uiButtonColor);
  401. color: rgb(221, 221, 221);
  402. }
  403.  
  404. #blockedMenuUi li button:active {
  405. background-color: var(--uiButtonColor);
  406. color: var(--uiButtonColor);
  407. }
  408.  
  409. #blockedMenuUi textarea {
  410. background-color: var(--uiInputBoxBackgroundColor);
  411. font-size: 14px;
  412. padding: 0 5px;
  413. width: 100%;
  414. resize: none;
  415. }
  416.  
  417. #menuButtonContainer {
  418. position: sticky;
  419. right: 0;
  420. bottom: 0;
  421. width: 100%;
  422. background-color: var(--uiBackgroundColor);
  423. margin-top: -10px;
  424. }
  425.  
  426. #menuButtonContainer button {
  427. line-height: var(--lineHeight);
  428. border-radius: var(--borderRadius);
  429. font-size: 16px;
  430. border: 0;
  431. padding: 0;
  432. margin-top: 10px;
  433. margin-bottom: 10px;
  434. margin-left: 10px;
  435. height: 45px;
  436. width: 45px;
  437. vertical-align: middle;
  438. background-color: var(--uiButtonColor);
  439. }
  440.  
  441. #menuButtonContainer label {
  442. line-height: 45px;
  443. border-radius: var(--borderRadius);
  444. display: inline-block;
  445. border: 0;
  446. padding: 0;
  447. margin: 10px 20px;
  448. height: 45px;
  449. width: 130px;
  450. vertical-align: middle;
  451. text-align: center;
  452. background-color: var(--uiInputBoxBackgroundColor);
  453. transition: opacity 1s;
  454. }
  455.  
  456. /* 支付宝微信二维码 */
  457. #alipayWeChatQrCode {
  458. position: fixed;
  459. top: 52%;
  460. left: 16%;
  461. transform: translate(0%, -50%);
  462. box-shadow: 0 8px 8px rgb(85 85 85 / 85%);
  463. }
  464.  
  465. `);
  466.  
  467. // 菜单UI的HTML
  468. let menuUiHTML = `
  469.  
  470. <div id="blockedMenuUi">
  471. <div id="menuTitle">Bilibili按标签、标题、时长、UP主屏蔽视频 v1.1.5</div>
  472.  
  473. <div id="menuOptionsList">
  474. <div class="menuOptions">
  475. <div class="titleLabelLeft">
  476. <label><input type="checkbox" v-model="menuUiSettings.blockedTitle_Switch" />按标题屏蔽 </label>
  477. </div>
  478.  
  479. <div class="titleLabelRight">
  480. <label><input type="checkbox" v-model="menuUiSettings.blockedTitle_UseRegular" />启用正则</label>
  481. </div>
  482.  
  483. <input type="text" placeholder="多项输入请用英文逗号间隔" spellcheck="false"
  484. v-model="tempInputValue.blockedTitle_Array" /><button
  485. @click="addArrayButton(tempInputValue, menuUiSettings, 'blockedTitle_Array')">添加</button>
  486.  
  487. <ul>
  488. <li v-for="(value, index) in menuUiSettings.blockedTitle_Array">
  489. {{value}}<button @click="delArrayButton(index, menuUiSettings.blockedTitle_Array)">×</button>
  490. </li>
  491. </ul>
  492. </div>
  493.  
  494. <div class="menuOptions">
  495. <div class="titleLabelLeft">
  496. <label><input type="checkbox" v-model="menuUiSettings.blockedNameOrUid_Switch" />按UP名称或Uid屏蔽</label>
  497. </div>
  498.  
  499. <div class="titleLabelRight">
  500. <label><input type="checkbox" v-model="menuUiSettings.blockedNameOrUid_UseRegular" />启用正则</label>
  501. </div>
  502.  
  503. <input type="text" placeholder="多项输入请用英文逗号间隔" spellcheck="false"
  504. v-model="tempInputValue.blockedNameOrUid_Array" /><button
  505. @click="addArrayButton(tempInputValue, menuUiSettings, 'blockedNameOrUid_Array')">添加</button>
  506.  
  507. <ul>
  508. <li v-for="(value, index) in menuUiSettings.blockedNameOrUid_Array">
  509. {{value}}<button @click="delArrayButton(index, menuUiSettings.blockedNameOrUid_Array)">×</button>
  510. </li>
  511. </ul>
  512. </div>
  513.  
  514. <div class="menuOptions">
  515. <div class="titleLabelLeft">
  516. <label><input type="checkbox" v-model="menuUiSettings.blockedVideoPartitions_Switch" />按视频分区屏蔽</label>
  517. </div>
  518.  
  519. <div class="titleLabelRight">
  520. <label><input type="checkbox" v-model="menuUiSettings.blockedVideoPartitions_UseRegular" />启用正则</label>
  521. </div>
  522.  
  523. <input type="text" placeholder="多项输入请用英文逗号间隔" spellcheck="false"
  524. v-model="tempInputValue.blockedVideoPartitions_Array" /><button
  525. @click="addArrayButton(tempInputValue, menuUiSettings, 'blockedVideoPartitions_Array')">添加</button>
  526.  
  527. <ul>
  528. <li v-for="(value, index) in menuUiSettings.blockedVideoPartitions_Array">
  529. {{value}}<button
  530. @click="delArrayButton(index, menuUiSettings.blockedVideoPartitions_Array)">×</button>
  531. </li>
  532. </ul>
  533. </div>
  534.  
  535.  
  536. <div class="menuOptions">
  537. <div class="titleLabelLeft">
  538. <label><input type="checkbox" v-model="menuUiSettings.blockedTag_Switch" />按标签屏蔽</label>
  539. </div>
  540.  
  541. <div class="titleLabelRight">
  542. <label><input type="checkbox" v-model="menuUiSettings.blockedTag_UseRegular" />启用正则</label>
  543. </div>
  544.  
  545. <input type="text" placeholder="多项输入请用英文逗号间隔" spellcheck="false"
  546. v-model="tempInputValue.blockedTag_Array" /><button
  547. @click="addArrayButton(tempInputValue, menuUiSettings, 'blockedTag_Array')">添加</button>
  548.  
  549. <ul>
  550. <li v-for="(value, index) in menuUiSettings.blockedTag_Array">
  551. {{value}}<button @click="delArrayButton(index, menuUiSettings.blockedTag_Array)">×</button>
  552. </li>
  553. </ul>
  554. </div>
  555.  
  556. <div class="menuOptions">
  557. <div class="titleLabelLeft">
  558. <label><input type="checkbox" v-model="menuUiSettings.doubleBlockedTag_Switch" />按双重标签屏蔽</label>
  559. </div>
  560.  
  561. <div class="titleLabelRight">
  562. <label><input type="checkbox" v-model="menuUiSettings.doubleBlockedTag_UseRegular" />启用正则</label>
  563. </div>
  564.  
  565. <input type="text" placeholder='多项输入请用英文逗号间隔(以"A标签|B标签"格式添加)' spellcheck="false"
  566. v-model="tempInputValue.doubleBlockedTag_Array" /><button
  567. @click="addArrayButton(tempInputValue, menuUiSettings, 'doubleBlockedTag_Array' )">添加</button>
  568.  
  569. <ul>
  570. <li v-for="(value, index) in menuUiSettings.doubleBlockedTag_Array">
  571. {{value}}<button @click="delArrayButton(index, menuUiSettings.doubleBlockedTag_Array)">×</button>
  572. </li>
  573. </ul>
  574. </div>
  575.  
  576.  
  577. <div class="menuOptions">
  578. <div class="titleLabelLeft">
  579. <label><input type="checkbox" v-model="menuUiSettings.blockedTopComment_Switch" />按置顶评论屏蔽 </label>
  580. </div>
  581.  
  582. <div class="titleLabelRight">
  583. <label><input type="checkbox" v-model="menuUiSettings.blockedTopComment_UseRegular" />启用正则</label>
  584. </div>
  585.  
  586. <input type="text" placeholder="多项输入请用英文逗号间隔" spellcheck="false"
  587. v-model="tempInputValue.blockedTopComment_Array" /><button
  588. @click="addArrayButton(tempInputValue, menuUiSettings, 'blockedTopComment_Array')">添加</button>
  589.  
  590. <ul>
  591. <li v-for="(value, index) in menuUiSettings.blockedTopComment_Array">
  592. {{value}}<button @click="delArrayButton(index, menuUiSettings.blockedTopComment_Array)">×</button>
  593. </li>
  594. </ul>
  595. </div>
  596.  
  597. <div class="menuOptions">
  598. <div class="titleLabelLeft">
  599. <label><input type="checkbox"
  600. v-model="menuUiSettings.whitelistNameOrUid_Switch" />按UP名称或Uid避免屏蔽(白名单)</label>
  601. </div>
  602.  
  603. <input type="text" placeholder='多项输入请用英文逗号间隔' spellcheck="false"
  604. v-model="tempInputValue.whitelistNameOrUid_Array" /><button
  605. @click="addArrayButton(tempInputValue, menuUiSettings, 'whitelistNameOrUid_Array' )">添加</button>
  606.  
  607. <ul>
  608. <li v-for="(value, index) in menuUiSettings.whitelistNameOrUid_Array">
  609. {{value}}<button @click="delArrayButton(index, menuUiSettings.whitelistNameOrUid_Array)">×</button>
  610. </li>
  611. </ul>
  612. </div>
  613.  
  614. <div class="menuOptions">
  615. <div class="titleLabelLeft">
  616. <label><input type="checkbox" v-model="menuUiSettings.blockedShortDuration_Switch" />屏蔽低于指定时长的视频</label>
  617. </div>
  618. <input type="number" spellcheck="false" v-model="menuUiSettings.blockedShortDuration" />
  619. <label>秒</label>
  620. </div>
  621.  
  622. <div class="menuOptions">
  623. <div class="titleLabelLeft">
  624. <label><input type="checkbox"
  625. v-model="menuUiSettings.blockedBelowVideoViews_Switch" />屏蔽低于指定播放量的视频</label>
  626. </div>
  627. <input type="number" spellcheck="false" v-model="menuUiSettings.blockedBelowVideoViews" />
  628. <label>次</label>
  629. </div>
  630.  
  631. <div class="menuOptions">
  632. <div class="titleLabelLeft">
  633. <label><input type="checkbox"
  634. v-model="menuUiSettings.blockedBelowLikesRate_Switch" />屏蔽低于指定点赞率的视频</label>
  635. </div>
  636. <input type="number" spellcheck="false" v-model="menuUiSettings.blockedBelowLikesRate" />
  637. <label>%</label>
  638. </div>
  639.  
  640.  
  641. <div class="menuOptions">
  642. <label><input type="checkbox" v-model="menuUiSettings.blockedPortraitVideo_Switch" />屏蔽竖屏视频</label>
  643. </div>
  644.  
  645. <div class="menuOptions">
  646. <label><input type="checkbox" v-model="menuUiSettings.blockedChargingExclusive_Switch" />屏蔽充电专属的视频</label>
  647. </div>
  648.  
  649. <div class="menuOptions">
  650. <label><input type="checkbox"
  651. v-model="menuUiSettings.blockedFilteredCommentsVideo_Switch" />屏蔽精选评论的视频</label>
  652. </div>
  653.  
  654. <div class="menuOptions">
  655. <label><input type="checkbox"
  656. v-model="menuUiSettings.hideNonVideoElements_Switch" />隐藏首页等页面的非视频元素</label>
  657. </div>
  658.  
  659. <div class="menuOptions">
  660. <label><input type="checkbox" v-model="menuUiSettings.blockedOverlayOnlyDisplaysType_Switch" />屏蔽叠加层的提示只显示类型</label>
  661. </div>
  662.  
  663. <div class="menuOptions">
  664. <label><input type="checkbox" v-model="menuUiSettings.hideVideoMode_Switch" />隐藏视频而不是使用叠加层覆盖</label>
  665. </div>
  666.  
  667. <div class="menuOptions">
  668. <label><input type="checkbox" v-model="menuUiSettings.consoleOutputLog_Switch" />控制台输出日志开关</label>
  669. </div>
  670.  
  671. </div>
  672.  
  673. <div id="menuButtonContainer">
  674. <button @click="refreshButton()">读取</button>
  675. <button @click="saveButton()">保存</button>
  676. <button @click="closeButton()">关闭</button>
  677. <button @click="authorButton()">作者</button>
  678. <button @click="supportButton()">赞助</button>
  679.  
  680.  
  681. <label :style="{ opacity: tempInputValue.promptText_Opacity }"
  682. v-show="tempInputValue.promptText_Switch">{{tempInputValue.promptText}}</label>
  683. </div>
  684.  
  685. <div id="alipayWeChatQrCode" v-show="tempInputValue.QrCode_Switch">
  686. <img src="https://tc.dhmip.cn/imgs/2023/12/09/a8e5fff3320dc195.png" alt="感谢赞助">
  687. </div>
  688.  
  689. </div>
  690.  
  691. `;
  692.  
  693. // 菜单UI
  694. function blockedMenuUi() {
  695. // 检查页面中是否已经存在这个元素
  696. if (!document.getElementById("blockedMenuUi")) {
  697. // 如果不存在,将菜单弹窗添加到页面
  698. // 创建Div作为菜单容器
  699. let menuUi = document.createElement("div");
  700. menuUi.innerHTML = menuUiHTML;
  701. document.body.appendChild(menuUi);
  702. } else {
  703. console.log("菜单 #blockedMenuUi 已存在");
  704. return;
  705. }
  706.  
  707. // 让油猴脚本的Vue代码能网页中正常工作。
  708. unsafeWindow.Vue = Vue;
  709.  
  710. const { createApp, reactive, toRaw } = Vue;
  711.  
  712. createApp({
  713. setup() {
  714. // 设置选项数据
  715. const menuUiSettings = reactive({});
  716.  
  717. // 临时存储的各数组对应的输入值
  718. const tempInputValue = reactive({
  719. blockedTitle_Array: "",
  720. blockedNameOrUid_Array: "",
  721. blockedVideoPartitions_Array: "",
  722. blockedTag_Array: "",
  723. doubleBlockedTag_Array: "",
  724. blockedTopComment_Array: "",
  725. whitelistNameOrUid_Array: "",
  726. // 临时提示文本
  727. promptText_Switch: true,
  728. promptText_Opacity: 0,
  729. promptText: "",
  730. // 二维码显示开关
  731. QrCode_Switch: false,
  732. });
  733.  
  734. function showPromptText(text) {
  735. // tempInputValue.promptText_Switch = true; // 显示 label 元素
  736. tempInputValue.promptText_Opacity = 1;
  737. tempInputValue.promptText = text;
  738. // 1.5秒后隐藏 label 元素
  739. setTimeout(() => {
  740. // tempInputValue.promptText_Switch = false;
  741. tempInputValue.promptText_Opacity = 0;
  742. }, 1500);
  743. }
  744.  
  745. // 添加数组项目
  746. const addArrayButton = (tempInputValue, menuUiSettings, keyName) => {
  747. // 确保 menuUiSettings[keyName] 是一个数组
  748. if (!Array.isArray(menuUiSettings[keyName])) {
  749. menuUiSettings[keyName] = [];
  750. }
  751. // 双重标签的特殊处理 判断是否为空
  752. if (keyName == "doubleBlockedTag_Array" && tempInputValue[keyName].trim()) {
  753. // 使用 split 按逗号分隔,然后映射去除每个标签的首尾空白
  754. const items = tempInputValue[keyName]
  755. .split(",")
  756. .map((item) => item.split("|").map((str) => str.trim()))
  757. .filter((subArray) => subArray.length === 2 && subArray.every((str) => str !== ""));
  758.  
  759. items.forEach((secondSplitItem) => {
  760. // 将两个标签重新组合成一个字符串,并添加到设置数据中
  761. const formattedItem = secondSplitItem.join("|");
  762. menuUiSettings[keyName].push(formattedItem);
  763. });
  764.  
  765. // 清空输入框内容
  766. tempInputValue[keyName] = "";
  767.  
  768. return;
  769. }
  770.  
  771. // 判断是否为空
  772. if (tempInputValue[keyName].trim()) {
  773. // 用逗号分隔值并去除每项的空格后添加到数组
  774. const items = tempInputValue[keyName].split(",").map((item) => item.trim());
  775.  
  776. menuUiSettings[keyName].push(...items);
  777.  
  778. // 清空输入框内容
  779. tempInputValue[keyName] = "";
  780. }
  781. };
  782.  
  783. //删除数组项目
  784. const delArrayButton = (index, array) => {
  785. //splice(要删除元素的索引位置, 要删除的元素数量)
  786. array.splice(index, 1);
  787. };
  788.  
  789. // 读取按钮 深拷贝函数,递归处理嵌套对象,普通对象 to 普通对象/响应式对象
  790. function deepCopy(source, target) {
  791. for (let key in source) {
  792. if (typeof source[key] === "object" && source[key] !== null) {
  793. target[key] = Array.isArray(source[key]) ? [] : {}; // 根据类型创建空对象或数组
  794. deepCopy(source[key], target[key]); // 递归拷贝子对象
  795. } else {
  796. target[key] = source[key]; // 复制基本类型和函数等
  797. }
  798. }
  799. }
  800.  
  801. // 读取按钮
  802. const refreshButton = () => {
  803. // 使用 deepCopy 函数进行深拷贝
  804. deepCopy(blockedParameter, menuUiSettings);
  805.  
  806. showPromptText("读取数据");
  807. };
  808.  
  809. // 保存按钮 深拷贝函数,递归处理响应式对象,响应式对象 to 普通对象
  810. function deepCopyReactiveObject(reactiveObj, targetObj) {
  811. for (let key in reactiveObj) {
  812. const rawValue = toRaw(reactiveObj[key]); // 获取属性的原始值
  813.  
  814. if (typeof rawValue === "object" && rawValue !== null) {
  815. targetObj[key] = Array.isArray(rawValue) ? [] : {}; // 根据类型创建空对象或数组
  816. deepCopyReactiveObject(rawValue, targetObj[key]); // 递归处理嵌套的响应式子对象
  817. } else {
  818. targetObj[key] = rawValue; // 复制基本类型和函数等
  819. }
  820. }
  821. }
  822.  
  823. // 保存按钮
  824. const saveButton = () => {
  825. // 将响应式对象深拷贝到普通对象 blockedParameter
  826. deepCopyReactiveObject(menuUiSettings, blockedParameter);
  827.  
  828. // 将全局屏蔽参数对象变量 blockedParameter 保存到油猴扩展存储中
  829. GM_setValue("GM_blockedParameter", blockedParameter);
  830.  
  831. showPromptText("保存数据");
  832.  
  833. // 触发一次主函数,以立刻生效
  834. FuckYouBilibiliRecommendationSystem();
  835. };
  836.  
  837. // 关闭按钮
  838. const closeButton = () => {
  839. // 获取需要删除的元素
  840. let elementToRemove = document.getElementById("blockedMenuUi");
  841.  
  842. // 确保元素存在再进行删除操作
  843. if (elementToRemove) {
  844. // 先获取父元素
  845. let parentElement = elementToRemove.parentNode;
  846.  
  847. // 在父元素删除指定的元素
  848. parentElement.removeChild(elementToRemove);
  849. }
  850. };
  851.  
  852. // 作者主页
  853. const authorButton = () => {
  854. setTimeout(() => {
  855. window.open("https://space.bilibili.com/351422438", "_blank");
  856. }, 1000);
  857. showPromptText("欢迎关注!");
  858. };
  859.  
  860. // 赞助作者
  861. const supportButton = () => {
  862. if (!tempInputValue.QrCode_Switch) {
  863. setTimeout(() => {
  864. window.open("https://afdian.com/a/tjxgame", "_blank");
  865. }, 1000);
  866. tempInputValue.QrCode_Switch = true;
  867. } else {
  868. tempInputValue.QrCode_Switch = false;
  869. }
  870.  
  871. showPromptText("感谢老板!");
  872. };
  873.  
  874. // 打开菜单时,先加载一次数据
  875. refreshButton();
  876.  
  877. return {
  878. menuUiSettings,
  879. tempInputValue,
  880. addArrayButton,
  881. delArrayButton,
  882. refreshButton,
  883. saveButton,
  884. closeButton,
  885. supportButton,
  886. authorButton,
  887. };
  888. },
  889. }).mount("#blockedMenuUi");
  890. }
  891.  
  892. // 在油猴扩展中添加脚本菜单选项
  893. GM_registerMenuCommand("屏蔽参数面板", blockedMenuUi);
  894.  
  895. // -----------------------逻辑处理部分--------------------------
  896.  
  897. // 视频的详细信息对象,以videoBv为键, 用于同窗口内的缓存查询
  898. let videoInfoDict = {};
  899.  
  900. // 上次输出的视频详细信息对象,用于控制台判断是否输出日志
  901. let lastConsoleVideoInfoDict = {};
  902.  
  903. // videoInfoDict 的参考内容结构
  904. // videoInfoDict = {
  905. // BV12i4y1e73B: {
  906. // videoLink: "https://www.bilibili.com/video/BV12i4y1e73B/",
  907. // videoTitle: "B站按 标签 标题 时长 UP主来屏蔽视频 油猴插件【tjxgame】",
  908. // videoUpName: "tjxgame",
  909. // videoUpUid: 351422438,
  910. // videoPartitions: "软件应用",
  911. // videoTags: [
  912. // "科技2023年终总结",
  913. // "视频",
  914. // "教程",
  915. // "tjxwork",
  916. // "软件分享",
  917. // "插件",
  918. // "标签",
  919. // "屏蔽",
  920. // "油猴",
  921. // "tjxgame",
  922. // "2023热门年度盘点",
  923. // ],
  924. // topComment : "大更新,新视频!\nhttps://www.bilibili.com/video/BV1WJ4m1u79n/\n\nv1.0.0 菜单UI使用Vue3重构,现在不用担心缩放问题挡住UI了,界面更加现代化;\n改进了判断逻辑,现在可以使用白名单来避免误杀关注的UP了;\n新增功能:视频分区屏蔽、播放量屏蔽、点赞率屏蔽、竖屏视频屏蔽、UP主名称正则屏蔽、隐藏非视频元素、白名单避免屏蔽指定UP。"
  925. // whiteListTargets: true,
  926. // videoDuration: 259,
  927. // videoView: 9067,
  928. // videoLike: 507,
  929. // videoLikesRate: "5.59",
  930. // videoResolution: {
  931. // width: 3840,
  932. // height: 2160,
  933. // },
  934. // videoChargingExclusive : false
  935. // filteredComments: false,
  936. // blockedTarget: true,
  937. // triggeredBlockedRules: [
  938. // "屏蔽短时长视频: 259秒",
  939. // "屏蔽低播放量: 9067次",
  940. // "屏蔽低点赞率: 5.59%",
  941. // "屏蔽标题: tjxgame",
  942. // "屏蔽UP: tjxgame",
  943. // "屏蔽分区: 软件应用",
  944. // "屏蔽标签: 标签",
  945. // "屏蔽双重标签: 油猴,插件",
  946. // ],
  947. // lastVideoInfoApiRequestTime: "2024-06-21T09:17:10.389Z",
  948. // lastVideoTagApiRequestTime: "2024-06-21T09:17:10.389Z",
  949. // lastVideoCommentsApiRequestTime: "2024-06-21T09:17:10.389Z",
  950. // },
  951. // };
  952.  
  953. // 日志输出,根据 consoleOutputLog_Switch 标志来决定是否输出日志
  954. function consoleLogOutput(...args) {
  955. // 启用控制台日志输出
  956. if (blockedParameter.consoleOutputLog_Switch) {
  957. // 获取当前时间的时分秒毫秒部分
  958. let now = new Date();
  959. let hours = now.getHours().toString().padStart(2, "0");
  960. let minutes = now.getMinutes().toString().padStart(2, "0");
  961. let seconds = now.getSeconds().toString().padStart(2, "0");
  962. let milliseconds = now.getMilliseconds().toString().padStart(3, "0");
  963.  
  964. // 将时间信息添加到日志消息中
  965. let logTime = `${hours}:${minutes}:${seconds}.${milliseconds}`;
  966.  
  967. // 合并时间信息和 args 成为一个数组
  968. let logArray = [logTime, ...args];
  969. console.log(...logArray);
  970. }
  971. }
  972.  
  973. // 简单对比对象是否不同
  974. function objectDifferent(obj1, obj2) {
  975. if (Object.keys(obj1).length !== Object.keys(obj2).length) {
  976. return true;
  977. }
  978. for (const key in obj1) {
  979. if (obj1[key] !== obj2[key]) {
  980. return true;
  981. }
  982. }
  983. return false;
  984. }
  985.  
  986. // 获取视频元素
  987. function getVideoElements() {
  988. // // 获取所有有可能是视频元素的标签 (BewlyBewly插件的首页特殊处理)
  989. // let bewlyBewly = document.getElementById("bewly");
  990. // if (bewlyBewly) {
  991.  
  992. // // BewlyBewly插件使用shadowDOM,要在shadowDOM下面找元素
  993. // let shadowRoot = bewlyBewly.shadowRoot;
  994. // videoElements = shadowRoot.querySelectorAll("div.video-card.group");
  995.  
  996. // // 过滤掉没有包含a标签的元素
  997. // videoElements = Array.from(videoElements).filter((element) => element.querySelector("a"));
  998.  
  999. // // 返回处理后的结果
  1000. // return videoElements;
  1001. // }
  1002. // BewlyBewly 更新后失效……
  1003.  
  1004. // 获取所有有可能是视频元素的标签
  1005. let videoElements = document.querySelectorAll(
  1006. // div.bili-video-card 首页(https://www.bilibili.com/)、分区首页(https://www.bilibili.com/v/*)、搜索页面(https://search.bilibili.com/*)
  1007. // div.video-page-card-small 播放页右侧推荐(https://www.bilibili.com/video/BV****)
  1008. // li.bili-rank-list-video__item 分区首页-子分区右侧热门(https://www.bilibili.com/v/*)
  1009. // div.video-card 综合热门(https://www.bilibili.com/v/popular/all) 、每周必看(https://www.bilibili.com/v/popular/weekly) 、入站必刷(https://www.bilibili.com/v/popular/history)
  1010. // li.rank-item 排行榜(https://www.bilibili.com/v/popular/rank/all)
  1011. // div.video-card-reco 旧版首页推送(https://www.bilibili.com/)
  1012. // div.video-card-common 旧版首页分区(https://www.bilibili.com/)
  1013. // div.rank-wrap 旧版首页分区右侧排行(https://www.bilibili.com/)
  1014. "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"
  1015. );
  1016.  
  1017. // 过滤掉没有包含a标签的元素
  1018. videoElements = Array.from(videoElements).filter((element) => element.querySelector("a"));
  1019.  
  1020. // 判断是否存在旧版首页的顶部推荐条,为空的情况下再进行剔除广告元素,因为旧版首页的顶部推荐条,和新版的广告元素的类值一样……
  1021. if (document.querySelector("div.recommend-container__2-line") == null) {
  1022. // 过滤掉 CSS类刚好为 'bili-video-card is-rcmd' 的元素,因为是广告。
  1023. videoElements = Array.from(videoElements).filter(
  1024. (element) => element.classList.value !== "bili-video-card is-rcmd"
  1025. );
  1026. }
  1027.  
  1028. // 返回处理后的结果
  1029. return videoElements;
  1030. }
  1031.  
  1032. // 判断是否为已经屏蔽处理过的视频元素(延迟处理中)
  1033. function isAlreadyBlockedChildElement(videoElement) {
  1034. // // 确认是否为已经修改 元素已隐藏 跳过
  1035. // if (videoElement.style.display == "none") {
  1036. // // consoleLogOutput(operationInfo, "元素已隐藏 跳过剩下主函数步骤");
  1037. // return true;
  1038. // }
  1039.  
  1040. // 确认是否为已经修改 元素已透明 延迟处理中 跳过
  1041. if (videoElement.style.filter == "blur(5px)") {
  1042. // consoleLogOutput(operationInfo, "元素已透明 延迟处理中 跳过剩下主函数步骤");
  1043. return true;
  1044. }
  1045.  
  1046. // // 获取子元素,以确认是否为已经修改
  1047. // if (videoElement.firstElementChild.className == "blockedOverlay") {
  1048. // // consoleLogOutput(videoElement, "获取子元素,确认是已屏蔽处理过,跳过剩下主函数步骤");
  1049. // return true;
  1050. // }
  1051. }
  1052.  
  1053. // 标记为屏蔽目标,并记录命中的规则
  1054. function markAsBlockedTarget(videoBv, blockedType, blockedItem) {
  1055. // 将该 Bv号 标记为屏蔽目标
  1056. videoInfoDict[videoBv].blockedTarget = true;
  1057.  
  1058. // 确保 videoInfoDict[videoBv].triggeredBlockedRules 已定义为数组
  1059. if (!videoInfoDict[videoBv].triggeredBlockedRules) {
  1060. videoInfoDict[videoBv].triggeredBlockedRules = [];
  1061. }
  1062.  
  1063. let blockedRulesItem;
  1064.  
  1065. // 屏蔽叠加层的提示只显示类型而不显示命中项
  1066. if (blockedParameter.blockedOverlayOnlyDisplaysType_Switch) {
  1067. blockedRulesItem = blockedType;
  1068. } else {
  1069. blockedRulesItem = blockedType + ": " + blockedItem;
  1070. }
  1071.  
  1072. // 检查是否已经这条记录
  1073. if (!videoInfoDict[videoBv].triggeredBlockedRules.includes(blockedRulesItem)) {
  1074. // 将触发屏蔽的原因添加到 videoInfoDict[videoBv].triggeredBlockedRules
  1075. videoInfoDict[videoBv].triggeredBlockedRules.push(blockedRulesItem);
  1076. }
  1077. }
  1078.  
  1079. // 网页获取视频元素的Bv号和标题
  1080. function getBvAndTitle(videoElement) {
  1081. // 从视频元素中获取所有a标签链接
  1082. const videoLinkElements = videoElement.querySelectorAll("a");
  1083.  
  1084. // Bv号
  1085. let videoBv;
  1086.  
  1087. // 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
  1088. function av2bv(aid) {
  1089. const XOR_CODE = 23442827791579n;
  1090. const MASK_CODE = 2251799813685247n;
  1091. const MAX_AID = 1n << 51n;
  1092. const BASE = 58n;
  1093. const data = "FcwAPNKTMug3GV5Lj7EJnHpWsx4tb8haYeviqBz6rkCy12mUSDQX9RdoZf";
  1094. const bytes = ["B", "V", "1", "0", "0", "0", "0", "0", "0", "0", "0", "0"];
  1095. let bvIndex = bytes.length - 1;
  1096. let tmp = (MAX_AID | BigInt(aid)) ^ XOR_CODE;
  1097. while (tmp > 0) {
  1098. bytes[bvIndex] = data[Number(tmp % BigInt(BASE))];
  1099. tmp = tmp / BASE;
  1100. bvIndex -= 1;
  1101. }
  1102. [bytes[3], bytes[9]] = [bytes[9], bytes[3]];
  1103. [bytes[4], bytes[7]] = [bytes[7], bytes[4]];
  1104. return bytes.join("");
  1105. }
  1106.  
  1107. // 循环处理所有a标签链接
  1108. for (let videoLinkElement of videoLinkElements) {
  1109. // 已经有Bv号不需要继续了,跳过
  1110. if (videoBv) {
  1111. continue;
  1112. }
  1113.  
  1114. // 处理排行榜的多链接特殊情况,符合就跳过
  1115. if (videoLinkElement.className == "other-link") {
  1116. continue;
  1117. }
  1118.  
  1119. // 获取的链接,如果是Av链接的格式
  1120. let videoAvTemp = videoLinkElement.href.match(/\/(av)(\d+)/);
  1121. if (videoAvTemp) {
  1122. // 从链接中获取Av号 转为 Bv号
  1123. videoBv = av2bv(videoAvTemp[2]);
  1124. }
  1125.  
  1126. // 获取的链接,如果是Bv链接的格式
  1127. let videoBvTemp = videoLinkElement.href.match(/\/(BV\w+)/);
  1128. if (videoBvTemp) {
  1129. // 从链接中获取到 视频Bv号
  1130. videoBv = videoBvTemp[1];
  1131. }
  1132.  
  1133. // 没拿Bv号不需要继续了,跳过
  1134. if (!videoBv) {
  1135. continue;
  1136. }
  1137.  
  1138. // 确保 videoInfoDict[videoBv] 已定义
  1139. if (!videoInfoDict[videoBv]) {
  1140. videoInfoDict[videoBv] = {};
  1141. }
  1142.  
  1143. // 视频链接
  1144. videoInfoDict[videoBv].videoLink = videoLinkElement.href;
  1145. }
  1146.  
  1147. // 没有拿到Bv号,提前结束
  1148. if (!videoBv) {
  1149. consoleLogOutput(videoElement, "getBvAndTitle() 没有拿到Bv号 提前结束 跳过剩下主函数步骤");
  1150. return false;
  1151. }
  1152.  
  1153. // 视频标题 , 从视频元素中获取第一个带 title 属性且不为 span 的标签
  1154. videoInfoDict[videoBv].videoTitle = videoElement.querySelector("[title]:not(span)").title;
  1155.  
  1156. return videoBv;
  1157. }
  1158.  
  1159. // 处理匹配的屏蔽标题
  1160. function handleBlockedTitle(videoBv) {
  1161. // 判断是否拿到视频标题
  1162. if (!videoInfoDict[videoBv].videoTitle) {
  1163. return;
  1164. }
  1165.  
  1166. // 记录触发的规则内容
  1167. // let blockedRulesItemText = "";
  1168.  
  1169. // 是否启用正则
  1170. if (blockedParameter.blockedTitle_UseRegular) {
  1171. // 使用 屏蔽标题数组 与 视频标题 进行匹配
  1172. const blockedTitleHitItem = blockedParameter.blockedTitle_Array.find((blockedTitleItem) => {
  1173. // 正则化屏蔽标题
  1174. const blockedTitleRegEx = new RegExp(blockedTitleItem);
  1175. // 判断 正则化的屏蔽标题 是否匹配 视频标题
  1176. if (blockedTitleRegEx.test(videoInfoDict[videoBv].videoTitle)) {
  1177. // blockedRulesItemText = videoInfoDict[videoBv].videoTitle;
  1178. return true;
  1179. }
  1180. });
  1181.  
  1182. if (blockedTitleHitItem) {
  1183. // 标记为屏蔽目标并记录触发的规则
  1184. markAsBlockedTarget(videoBv, "屏蔽标题", blockedTitleHitItem);
  1185. }
  1186. } else {
  1187. // 使用 屏蔽标题数组 与 视频标题 进行匹配
  1188. const blockedTitleHitItem = blockedParameter.blockedTitle_Array.find((blockedTitleItem) => {
  1189. // 判断 屏蔽标题 是否匹配 视频标题
  1190. if (blockedTitleItem === videoInfoDict[videoBv].videoTitle) {
  1191. // blockedRulesItemText = videoInfoDict[videoBv].videoTitle;
  1192. return true;
  1193. }
  1194. });
  1195.  
  1196. if (blockedTitleHitItem) {
  1197. // 标记为屏蔽目标并记录触发的规则
  1198. markAsBlockedTarget(videoBv, "屏蔽标题", blockedTitleHitItem);
  1199. }
  1200. }
  1201. }
  1202.  
  1203. // 网页获取视频UP名和UpUid (已经有API获取为什么还要网页获取?因为快……)
  1204. function getNameAndUid(videoElement, videoBv) {
  1205. // 如果已经有 BV号 对应的 Up主名称 Up主Uid 记录,跳过
  1206. if (videoInfoDict[videoBv].videoUpName && videoInfoDict[videoBv].videoUpUid) {
  1207. return;
  1208. }
  1209.  
  1210. // 从视频元素中获取所有a标签链接
  1211. const videoLinkElements = videoElement.querySelectorAll("a");
  1212.  
  1213. // 循环处理所有a标签链接
  1214. for (let videoLinkElement of videoLinkElements) {
  1215. // 获取的链接,如果与 Uid 的链接格式匹配的话
  1216. const uidLink = videoLinkElement.href.match(/space\.bilibili\.com\/(\d+)/);
  1217. if (uidLink) {
  1218. // 视频UpUid
  1219. videoInfoDict[videoBv].videoUpUid = uidLink[1];
  1220.  
  1221. // 视频Up名称
  1222. videoInfoDict[videoBv].videoUpName = videoLinkElement.querySelector("span").innerText;
  1223. }
  1224. }
  1225. }
  1226.  
  1227. // API获取视频信息
  1228. function getVideoApiInfo(videoBv) {
  1229. // 如果已经有BV号对应的记录,跳过
  1230. if (videoInfoDict[videoBv].videoDuration) {
  1231. return;
  1232. }
  1233.  
  1234. // 当 lastVideoInfoApiRequestTime 上次API获取视频信息的时间存在,并且,和当前的时间差小于3秒时,跳过
  1235. const currentTime = new Date(); //获取当前时间
  1236. if (
  1237. videoInfoDict[videoBv].lastVideoInfoApiRequestTime &&
  1238. currentTime - videoInfoDict[videoBv].lastVideoInfoApiRequestTime < 3000
  1239. ) {
  1240. // consoleLogOutput(videoBv, "getVideoApiInfo() 距离上次 Fetch 获取视频信息还未超过3秒钟");
  1241. return;
  1242. }
  1243. videoInfoDict[videoBv].lastVideoInfoApiRequestTime = currentTime;
  1244.  
  1245. // 通过API获取视频UP信息
  1246. fetch(`https://api.bilibili.com/x/web-interface/view?bvid=${videoBv}`)
  1247. .then((response) => response.json())
  1248. .then((videoApiInfoJson) => {
  1249. // API获取的UP主名称:
  1250. videoInfoDict[videoBv].videoUpName = videoApiInfoJson.data.owner.name;
  1251.  
  1252. // API获取的UP主Uid:
  1253. videoInfoDict[videoBv].videoUpUid = videoApiInfoJson.data.owner.mid;
  1254.  
  1255. // API获取的视频AVid:
  1256. videoInfoDict[videoBv].videoAVid = videoApiInfoJson.data.aid;
  1257.  
  1258. // API获取的视频时长
  1259. videoInfoDict[videoBv].videoDuration = videoApiInfoJson.data.duration;
  1260.  
  1261. // API获取的视频分区
  1262. videoInfoDict[videoBv].videoPartitions = videoApiInfoJson.data.tname;
  1263.  
  1264. // API获取的视频播放数
  1265. videoInfoDict[videoBv].videoView = videoApiInfoJson.data.stat.view;
  1266.  
  1267. // API获取的视频点赞数
  1268. videoInfoDict[videoBv].videoLike = videoApiInfoJson.data.stat.like;
  1269.  
  1270. // 计算视频点赞率保留2位小数
  1271. videoInfoDict[videoBv].videoLikesRate = (
  1272. (videoInfoDict[videoBv].videoLike / videoInfoDict[videoBv].videoView) *
  1273. 100
  1274. ).toFixed(2);
  1275.  
  1276. // // API获取的视频投币数
  1277. // videoInfoDict[videoBv].videoCoin = videoApiInfoJson.data.stat.coin;
  1278.  
  1279. // // API获取的视频收藏数
  1280. // videoInfoDict[videoBv].videoFavorite = videoApiInfoJson.data.stat.favorite;
  1281.  
  1282. // // API获取的视频分享数
  1283. // videoInfoDict[videoBv].videoShare = videoApiInfoJson.data.stat.share;
  1284.  
  1285. // // API获取的视频评论数
  1286. // videoInfoDict[videoBv].videoReply = videoApiInfoJson.data.stat.reply;
  1287.  
  1288. // // API获取的视频弹幕数
  1289. // videoInfoDict[videoBv].videoDanmaku = videoApiInfoJson.data.stat.danmaku;
  1290.  
  1291. // API获取的视频是否为充电专属
  1292. videoInfoDict[videoBv].videoChargingExclusive = videoApiInfoJson.data.is_upower_exclusive;
  1293.  
  1294. // API获取的视频分辨率
  1295. if (!videoInfoDict[videoBv].videoResolution) {
  1296. videoInfoDict[videoBv].videoResolution = {};
  1297. }
  1298. videoInfoDict[videoBv].videoResolution.width = videoApiInfoJson.data.dimension.width;
  1299. videoInfoDict[videoBv].videoResolution.height = videoApiInfoJson.data.dimension.height;
  1300.  
  1301. FuckYouBilibiliRecommendationSystem();
  1302. })
  1303. .catch((error) => consoleLogOutput(videoBv, "getVideoApiInfo() Fetch错误:", error));
  1304. }
  1305.  
  1306. // 处理匹配短时长视频
  1307. function handleBlockedShortDuration(videoBv) {
  1308. // 判断是否拿到视频时长
  1309. if (!videoInfoDict[videoBv].videoDuration) {
  1310. return;
  1311. }
  1312.  
  1313. // 判断设置的屏蔽短时长视频值 是否大于 视频时长
  1314. if (blockedParameter.blockedShortDuration > videoInfoDict[videoBv].videoDuration) {
  1315. // 标记为屏蔽目标并记录触发的规则
  1316. markAsBlockedTarget(videoBv, "屏蔽短时长视频", videoInfoDict[videoBv].videoDuration + "秒");
  1317. }
  1318. }
  1319.  
  1320. // 处理 屏蔽低播放量视频
  1321. function handleBlockedBelowVideoViews(videoBv) {
  1322. // 判断是否拿到视频播放量
  1323. if (!videoInfoDict[videoBv].videoView) {
  1324. return;
  1325. }
  1326.  
  1327. // 判断设置的屏蔽视频点赞率值 是否大于 视频的点赞率
  1328. if (blockedParameter.blockedBelowVideoViews > videoInfoDict[videoBv].videoView) {
  1329. // 标记为屏蔽目标并记录触发的规则
  1330. markAsBlockedTarget(videoBv, "屏蔽低播放量", videoInfoDict[videoBv].videoView + "次");
  1331. }
  1332. }
  1333.  
  1334. // 处理匹配屏蔽低于指定点赞率的视频
  1335. function handleBlockedBelowLikesRate(videoBv) {
  1336. // 判断是否拿到视频点赞数
  1337. if (!videoInfoDict[videoBv].videoLikesRate) {
  1338. return;
  1339. }
  1340.  
  1341. // 判断设置的屏蔽视频点赞率值 是否大于 视频的点赞率
  1342. if (blockedParameter.blockedBelowLikesRate > videoInfoDict[videoBv].videoLikesRate) {
  1343. // 标记为屏蔽目标并记录触发的规则
  1344. markAsBlockedTarget(videoBv, "屏蔽低点赞率", videoInfoDict[videoBv].videoLikesRate + "%");
  1345. }
  1346. }
  1347.  
  1348. // 处理匹配屏蔽竖屏视频
  1349. function handleBlockedPortraitVideo(videoBv) {
  1350. // 判断是否拿到视频分辨率
  1351. if (!videoInfoDict[videoBv].videoResolution.width) {
  1352. return;
  1353. }
  1354.  
  1355. // 横向分辨率小于纵向分辨率就是竖屏
  1356. if (videoInfoDict[videoBv].videoResolution.width < videoInfoDict[videoBv].videoResolution.height) {
  1357. // 标记为屏蔽目标并记录触发的规则
  1358. markAsBlockedTarget(
  1359. videoBv,
  1360. "屏蔽竖屏视频",
  1361. `${videoInfoDict[videoBv].videoResolution.width} x ${videoInfoDict[videoBv].videoResolution.height}`
  1362. );
  1363. }
  1364. }
  1365.  
  1366. // 处理匹配 屏蔽充电专属视频
  1367. function handleBlockedChargingExclusive(videoBv) {
  1368. // 判断设置的屏蔽充电专属视频是否有启用标记
  1369. if (videoInfoDict[videoBv].videoChargingExclusive) {
  1370. // 标记为屏蔽目标并记录触发的规则
  1371. markAsBlockedTarget(videoBv, "屏蔽充电专属的视频", videoInfoDict[videoBv].videoUpName);
  1372. }
  1373. }
  1374.  
  1375. // 处理匹配的屏蔽Up主名称或Up主Uid
  1376. function handleBlockedNameOrUid(videoBv) {
  1377. // 判断是否拿到Up主名称或Up主Uid
  1378. if (!videoInfoDict[videoBv].videoUpUid) {
  1379. return;
  1380. }
  1381.  
  1382. // 记录触发的规则内容
  1383. let blockedRulesItemText = "";
  1384.  
  1385. // 是否启用正则
  1386. if (blockedParameter.blockedNameOrUid_UseRegular) {
  1387. // 使用 屏蔽Up名称和Uid数组 与 视频Up主Uid 和 视频Up主名称 进行匹配
  1388. const blockedNameOrUidHitItem = blockedParameter.blockedNameOrUid_Array.find((blockedNameOrUidItem) => {
  1389. // 正则化屏蔽Up主名称、视频Up主Uid
  1390. const blockedNameOrUidRegEx = new RegExp(blockedNameOrUidItem);
  1391.  
  1392. // 只有UP名称有正则的意义,Uid依然是直接对比
  1393. if (blockedNameOrUidRegEx.test(videoInfoDict[videoBv].videoUpName)) {
  1394. blockedRulesItemText = videoInfoDict[videoBv].videoUpName;
  1395. return true;
  1396. }
  1397.  
  1398. if (blockedNameOrUidItem == videoInfoDict[videoBv].videoUpUid) {
  1399. blockedRulesItemText = videoInfoDict[videoBv].videoUpUid;
  1400. return true;
  1401. }
  1402. });
  1403.  
  1404. if (blockedNameOrUidHitItem) {
  1405. // 标记为屏蔽目标并记录触发的规则
  1406. markAsBlockedTarget(videoBv, "屏蔽UP", blockedRulesItemText);
  1407. }
  1408. } else {
  1409. // 使用 屏蔽Up名称和Uid数组 与 视频Up主Uid 和 视频Up主名称 进行匹配
  1410. const blockedNameOrUidHitItem = blockedParameter.blockedNameOrUid_Array.find((blockedNameOrUidItem) => {
  1411. if (blockedNameOrUidItem == videoInfoDict[videoBv].videoUpName) {
  1412. blockedRulesItemText = videoInfoDict[videoBv].videoUpName;
  1413. return true;
  1414. }
  1415.  
  1416. if (blockedNameOrUidItem == videoInfoDict[videoBv].videoUpUid) {
  1417. blockedRulesItemText = videoInfoDict[videoBv].videoUpUid;
  1418. return true;
  1419. }
  1420. });
  1421.  
  1422. if (blockedNameOrUidHitItem) {
  1423. // 标记为屏蔽目标并记录触发的规则
  1424. markAsBlockedTarget(videoBv, "屏蔽UP", blockedRulesItemText);
  1425. }
  1426. }
  1427. }
  1428.  
  1429. // 处理匹配的屏蔽视频分区
  1430. function handleBlockedVideoPartitions(videoBv) {
  1431. // 判断是否拿到视频分区
  1432. if (!videoInfoDict[videoBv].videoPartitions) {
  1433. return;
  1434. }
  1435.  
  1436. // 记录触发的规则内容
  1437. let blockedRulesItemText = "";
  1438.  
  1439. // 是否启用正则
  1440. if (blockedParameter.blockedVideoPartitions_UseRegular) {
  1441. // 使用 屏蔽视频分区数组 与 视频分区 进行匹配
  1442. const blockedVideoPartitionsHitItem = blockedParameter.blockedVideoPartitions_Array.find(
  1443. (blockedVideoPartitionsItem) => {
  1444. // 正则化屏蔽视频标签
  1445. const blockedVideoPartitionsRegEx = new RegExp(blockedVideoPartitionsItem);
  1446.  
  1447. if (blockedVideoPartitionsRegEx.test(videoInfoDict[videoBv].videoPartitions)) {
  1448. blockedRulesItemText = videoInfoDict[videoBv].videoPartitions;
  1449. return true;
  1450. }
  1451. }
  1452. );
  1453.  
  1454. if (blockedVideoPartitionsHitItem) {
  1455. // 标记为屏蔽目标并记录触发的规则
  1456. markAsBlockedTarget(videoBv, "屏蔽分区", blockedRulesItemText);
  1457. }
  1458. } else {
  1459. // 使用 屏蔽视频分区数组 与 视频分区 进行匹配
  1460. const blockedVideoPartitionsHitItem = blockedParameter.blockedVideoPartitions_Array.find(
  1461. (blockedVideoPartitionsItem) => {
  1462. if (blockedVideoPartitionsItem == videoInfoDict[videoBv].videoPartitions) {
  1463. blockedRulesItemText = videoInfoDict[videoBv].videoPartitions;
  1464. return true;
  1465. }
  1466. }
  1467. );
  1468.  
  1469. if (blockedVideoPartitionsHitItem) {
  1470. // 标记为屏蔽目标并记录触发的规则
  1471. markAsBlockedTarget(videoBv, "屏蔽分区", blockedRulesItemText);
  1472. }
  1473. }
  1474. }
  1475.  
  1476. // API获取视频标签
  1477. function getVideoApiTags(videoBv) {
  1478. // 如果已经有BV号对应的记录,跳过
  1479. if (videoInfoDict[videoBv].videoTags) {
  1480. return;
  1481. }
  1482.  
  1483. // 当 lastVideoTagApiRequestTime 上次API获取视频标签的时间存在,并且,和当前的时间差小于3秒时,跳过
  1484. const currentTime = new Date(); //获取当前时间
  1485. if (
  1486. videoInfoDict[videoBv].lastVideoTagApiRequestTime &&
  1487. currentTime - videoInfoDict[videoBv].lastVideoTagApiRequestTime < 3000
  1488. ) {
  1489. // consoleLogOutput(videoBv, "getVideoApiTags() 距离上次 Fetch 获取视频信息还未超过3秒钟");
  1490. return;
  1491. }
  1492. videoInfoDict[videoBv].lastVideoTagApiRequestTime = currentTime;
  1493.  
  1494. // 获取视频标签
  1495. fetch(`https://api.bilibili.com/x/web-interface/view/detail/tag?bvid=${videoBv}`)
  1496. .then((response) => response.json())
  1497. .then((videoApiTagsJson) => {
  1498. // API获取标签对象,提取标签名字数组
  1499. videoInfoDict[videoBv].videoTags = videoApiTagsJson.data.map((tagsArray) => tagsArray.tag_name);
  1500.  
  1501. FuckYouBilibiliRecommendationSystem();
  1502. })
  1503. .catch((error) => consoleLogOutput(videoBv, "getVideoApiTags() Fetch错误:", error));
  1504. }
  1505.  
  1506. // 处理匹配的屏蔽标签
  1507. function handleBlockedTag(videoBv) {
  1508. // 判断是否拿到视频标签
  1509. if (!videoInfoDict[videoBv].videoTags) {
  1510. return;
  1511. }
  1512.  
  1513. // 记录触发的规则内容
  1514. let blockedRulesItemText = "";
  1515.  
  1516. // 是否启用正则
  1517. if (blockedParameter.blockedTag_UseRegular) {
  1518. // 使用 屏蔽标签数组 与 视频标题数组 进行匹配
  1519. const blockedTagHitItem = blockedParameter.blockedTag_Array.find((blockedTagItem) => {
  1520. // 正则化屏蔽视频标签
  1521. const blockedTagRegEx = new RegExp(blockedTagItem);
  1522. // 使用 屏蔽标签正则 和 视频标题数组 进行匹配
  1523. const videoTagHitItem = videoInfoDict[videoBv].videoTags.find((videoTagItem) =>
  1524. blockedTagRegEx.test(videoTagItem)
  1525. );
  1526.  
  1527. if (videoTagHitItem) {
  1528. blockedRulesItemText = videoTagHitItem;
  1529. return true;
  1530. }
  1531. });
  1532.  
  1533. if (blockedTagHitItem) {
  1534. // 标记为屏蔽目标并记录触发的规则
  1535. markAsBlockedTarget(videoBv, "屏蔽标签", blockedRulesItemText);
  1536. }
  1537. } else {
  1538. // 使用 屏蔽标签数组 与 视频标题数组 进行匹配
  1539. const blockedTagHitItem = blockedParameter.blockedTag_Array.find((blockedTagItem) => {
  1540. // 使用 屏蔽标签 和 视频标题数组 进行匹配
  1541. const videoTagHitItem = videoInfoDict[videoBv].videoTags.find(
  1542. (videoTagItem) => blockedTagItem == videoTagItem
  1543. );
  1544.  
  1545. if (videoTagHitItem) {
  1546. blockedRulesItemText = videoTagHitItem;
  1547. return true;
  1548. }
  1549. });
  1550.  
  1551. if (blockedTagHitItem) {
  1552. // 标记为屏蔽目标并记录触发的规则
  1553. markAsBlockedTarget(videoBv, "屏蔽标签", blockedRulesItemText);
  1554. }
  1555. }
  1556. }
  1557.  
  1558. // 处理匹配屏蔽双重屏蔽标签
  1559. function handleDoubleBlockedTag(videoBv) {
  1560. // 判断是否拿到视频标签
  1561. if (!videoInfoDict[videoBv].videoTags) {
  1562. return;
  1563. }
  1564.  
  1565. // 记录触发的规则内容
  1566. let blockedRulesItemText = "";
  1567.  
  1568. // 是否启用正则
  1569. if (blockedParameter.doubleBlockedTag_UseRegular) {
  1570. // 使用 双重屏蔽标签数组 与 视频标签 进行匹配
  1571. const doubleBlockedTagHitItem = blockedParameter.doubleBlockedTag_Array.find((doubleBlockedTag) => {
  1572. // 以 "|" 分割成数组,同时都能匹配上才是符合
  1573. const doubleBlockedTagSplitArray = doubleBlockedTag.split("|");
  1574. const doubleBlockedTagRegEx0 = new RegExp(doubleBlockedTagSplitArray[0]);
  1575. const doubleBlockedTagRegEx1 = new RegExp(doubleBlockedTagSplitArray[1]);
  1576.  
  1577. const videoTagHitItem0 = videoInfoDict[videoBv].videoTags.find((videoTagItem) =>
  1578. doubleBlockedTagRegEx0.test(videoTagItem)
  1579. );
  1580. const videoTagHitItem1 = videoInfoDict[videoBv].videoTags.find((videoTagItem) =>
  1581. doubleBlockedTagRegEx1.test(videoTagItem)
  1582. );
  1583.  
  1584. if (videoTagHitItem0 && videoTagHitItem1) {
  1585. blockedRulesItemText = `${videoTagHitItem0},${videoTagHitItem1}`;
  1586. return true;
  1587. }
  1588. });
  1589.  
  1590. if (doubleBlockedTagHitItem) {
  1591. // 标记为屏蔽目标并记录触发的规则
  1592. markAsBlockedTarget(videoBv, "屏蔽双重标签", blockedRulesItemText);
  1593. }
  1594. } else {
  1595. // 使用 双重屏蔽标签数组 与 视频标签 进行匹配
  1596. const doubleBlockedTagHitItem = blockedParameter.doubleBlockedTag_Array.find((doubleBlockedTag) => {
  1597. // 以 "|" 分割成数组,同时都能匹配上才是符合
  1598. const doubleBlockedTagSplitArray = doubleBlockedTag.split("|");
  1599.  
  1600. const videoTagHitItem0 = videoInfoDict[videoBv].videoTags.find(
  1601. (videoTagItem) => doubleBlockedTagSplitArray[0] == videoTagItem
  1602. );
  1603. const videoTagHitItem1 = videoInfoDict[videoBv].videoTags.find(
  1604. (videoTagItem) => doubleBlockedTagSplitArray[1] == videoTagItem
  1605. );
  1606.  
  1607. if (videoTagHitItem0 && videoTagHitItem1) {
  1608. blockedRulesItemText = `${videoTagHitItem0},${videoTagHitItem1}`;
  1609. return true;
  1610. }
  1611. });
  1612.  
  1613. if (doubleBlockedTagHitItem) {
  1614. // 标记为屏蔽目标并记录触发的规则
  1615. markAsBlockedTarget(videoBv, "屏蔽双重标签", blockedRulesItemText);
  1616. }
  1617. }
  1618. }
  1619.  
  1620. // API获取视频评论区
  1621. let apiRequestDelayTime = 0;
  1622. function getVideoApiComments(videoBv) {
  1623. // 如果已经有BV号对应的记录,跳过
  1624. if (videoInfoDict[videoBv].filteredComments === false || videoInfoDict[videoBv].filteredComments === true) {
  1625. return;
  1626. }
  1627.  
  1628. // 当 lastVideoCommentsApiRequestTime 上次API获取视频评论区的时间存在,并且,和当前的时间差小于3秒时,跳过
  1629. const currentTime = new Date(); //获取当前时间
  1630. if (
  1631. videoInfoDict[videoBv].lastVideoCommentsApiRequestTime &&
  1632. currentTime - videoInfoDict[videoBv].lastVideoCommentsApiRequestTime < 3000
  1633. ) {
  1634. // consoleLogOutput(videoBv, "getVideoApiComments() 距离上次 Fetch 获取视频信息还未超过3秒钟");
  1635. return;
  1636. }
  1637. // 获取评论区的API貌似对频繁请求的容忍度很低,只能错开来请求,apiRequestDelayTime 延迟。
  1638. // 所以设置了每次调用 getVideoApiComments() 都会增加延迟,例如:每次加 50ms 再请求下一个请求。
  1639. // lastVideoCommentsApiRequestTime(上次API获取视频评论区的时间) 本质是为了限制每个BV号3秒只能请求一次,
  1640. // 但是加了延迟之后,到后面 apiRequestDelayTime 延迟本身就会超过3秒了。
  1641. // 还是会出现多次请求的问题,可能影响不大,但是还是把延迟值加进了 lastVideoCommentsApiRequestTime 里面。
  1642. // 这也相当于把 lastVideoCommentsApiRequestTime 修正为了正确请求时间。
  1643. let apiRequestDelayTimeData = new Date(apiRequestDelayTime);
  1644. videoInfoDict[videoBv].lastVideoCommentsApiRequestTime = new Date(
  1645. currentTime.getTime() + apiRequestDelayTimeData.getTime()
  1646. );
  1647.  
  1648. // apiRequestDelayTime 的最大值限制问题
  1649. // 如果不做限制的话,这个值可能会无限增大,导致最后加载的视频元素的请求也永远等不到生效时间。
  1650. // 以 videoInfoDict 对象的长度来做最大值限制貌似会比较合理一点。但是这个对象也可能会无限增大从而导致后面的请求等太久。
  1651. // 如果把 videoInfoDict[videoBv].filteredComments 筛选为 null 后的统计数值x延迟时间,做为最大延迟时间比较好?
  1652. // lastVideoCommentsApiRequestTime 也保证了每个Bv号的对应请求3秒只出现一次,这样就不用担心重复请求的问题。
  1653. // 但是本质上这一堆处理只是为了:防止频繁请求 https://api.bilibili.com/x/v2/reply 出现拒绝,同时为了效率的问题,每个Bv号只应该请求一次。
  1654.  
  1655. // 统计 videoInfoDict 中,视频Bv下面的 filteredComments 不存在的数量。
  1656. function filteredCommentsCount() {
  1657. let nullCount = 0;
  1658. for (const video in videoInfoDict) {
  1659. if (videoInfoDict[video].hasOwnProperty("filteredComments") == false) {
  1660. nullCount++;
  1661. }
  1662. }
  1663. return nullCount;
  1664. }
  1665.  
  1666. // 最大的延迟时间上限
  1667. let apiRequestDelayTimeMax = filteredCommentsCount() * 100;
  1668. // consoleLogOutput("最大的延迟时间上限", apiRequestDelayTimeMax);
  1669.  
  1670. // 每次调用增加的延迟 > 最大的延迟时间上限后 重置为0
  1671. if (apiRequestDelayTime > apiRequestDelayTimeMax) {
  1672. apiRequestDelayTime = 0;
  1673. }
  1674.  
  1675. setTimeout(() => {
  1676. // 设置请求的 URL 和参数
  1677. const url = "https://api.bilibili.com/x/v2/reply";
  1678. const params = {
  1679. type: 1, // 评论区类型代码
  1680. oid: videoBv, // 目标评论区 id
  1681. sort: 0, // 排序方式,默认为0,0:按时间,1:按点赞数,2:按回复数
  1682. ps: 1, // 每页项数,默认为20,定义域:1-20
  1683. pn: 1, // 页码,默认为1
  1684. nohot: 0, // 是否不显示热评,默认为0,1:不显示,0:显示
  1685. };
  1686. // 将参数转换为 URL 搜索字符串
  1687. const searchParams = new URLSearchParams(params).toString();
  1688.  
  1689. // 获取视频评论区
  1690. fetch(`${url}?${searchParams}`)
  1691. .then((response) => response.json())
  1692. .then((VideoApiCommentsJson) => {
  1693. // API获取精选评论标记
  1694. videoInfoDict[videoBv].filteredComments = VideoApiCommentsJson.data?.control?.web_selection;
  1695.  
  1696. // API获取置顶评论内容
  1697. videoInfoDict[videoBv].topComment = VideoApiCommentsJson.data.upper.top?.content?.message;
  1698.  
  1699. FuckYouBilibiliRecommendationSystem();
  1700. })
  1701. .catch((error) => consoleLogOutput(videoBv, "getVideoApiComments() Fetch错误:", error));
  1702. }, apiRequestDelayTime);
  1703.  
  1704. // 每次调用增加的延迟
  1705. // consoleLogOutput("本次调用增加延迟", apiRequestDelayTime);
  1706. apiRequestDelayTime = apiRequestDelayTime + 100;
  1707. }
  1708.  
  1709. // 处理匹配 屏蔽精选评论的视频
  1710. function handleBlockedFilteredCommentsVideo(videoBv) {
  1711. // 判断设置的屏蔽精选评论的视频是否有启用标记
  1712. if (videoInfoDict[videoBv].filteredComments) {
  1713. // 标记为屏蔽目标并记录触发的规则
  1714. markAsBlockedTarget(videoBv, "屏蔽精选评论的视频", videoInfoDict[videoBv].videoUpName);
  1715. }
  1716. }
  1717.  
  1718. // 处理匹配 屏蔽置顶评论内容
  1719. function handleBlockedTopComment(videoBv) {
  1720. // 判断是否拿到视频置顶评论
  1721. if (!videoInfoDict[videoBv].topComment) {
  1722. return;
  1723. }
  1724.  
  1725. // 记录触发的规则内容
  1726. // let blockedRulesItemText = "";
  1727.  
  1728. // 是否启用正则
  1729. if (blockedParameter.blockedTopComment_UseRegular) {
  1730. // 使用 屏蔽置顶评论数组 与 置顶评论 进行匹配
  1731. const blockedTopCommentHitItem = blockedParameter.blockedTopComment_Array.find((blockedTopComment) => {
  1732. // 正则化屏蔽置顶评论
  1733. const blockedTitleRegEx = new RegExp(blockedTopComment);
  1734. // 判断 正则化的屏蔽置顶评论 是否匹配 置顶评论
  1735. if (blockedTitleRegEx.test(videoInfoDict[videoBv].topComment)) {
  1736. return true;
  1737. }
  1738. });
  1739.  
  1740. if (blockedTopCommentHitItem) {
  1741. // 标记为屏蔽目标并记录触发的规则
  1742. markAsBlockedTarget(videoBv, "屏蔽置顶评论", blockedTopCommentHitItem);
  1743. }
  1744. } else {
  1745. // 使用 屏蔽置顶评论数组 与 置顶评论 进行匹配
  1746. const blockedTopCommentHitItem = blockedParameter.blockedTopComment_Array.find((blockedTopComment) => {
  1747. // 判断 屏蔽置顶评论 是否匹配 置顶评论
  1748. if (blockedTopComment === videoInfoDict[videoBv].topComment) {
  1749. return true;
  1750. }
  1751. });
  1752.  
  1753. if (blockedTopCommentHitItem) {
  1754. // 标记为屏蔽目标并记录触发的规则
  1755. markAsBlockedTarget(videoBv, "屏蔽置顶评论", blockedTopCommentHitItem);
  1756. }
  1757. }
  1758. }
  1759.  
  1760. // 处理匹配的白名单Up主和Uid
  1761. function handleWhitelistNameOrUid(videoBv) {
  1762. // 判断是否拿到Up主名称或Up主Uid
  1763. if (!videoInfoDict[videoBv].videoUpUid) {
  1764. return;
  1765. }
  1766.  
  1767. // 使用 白名单Up主和Uid数组 与 视频Up主Uid 和 视频Up主名称 进行匹配
  1768. const videoNameOrUid = blockedParameter.whitelistNameOrUid_Array.find((whitelistNameOrUidItem) => {
  1769. if (whitelistNameOrUidItem == videoInfoDict[videoBv].videoUpName) {
  1770. return true;
  1771. }
  1772.  
  1773. if (whitelistNameOrUidItem == videoInfoDict[videoBv].videoUpUid) {
  1774. return true;
  1775. }
  1776. });
  1777.  
  1778. if (videoNameOrUid) {
  1779. // 标记为白名单目标
  1780. videoInfoDict[videoBv].whiteListTargets = true;
  1781. }
  1782. }
  1783.  
  1784. // 隐藏非视频元素
  1785. function hideNonVideoElements() {
  1786. // 判断当前页面URL是否以 https://www.bilibili.com/ 开头,即首页
  1787. if (window.location.href.startsWith("https://www.bilibili.com/")) {
  1788. // 隐藏首页的番剧、国创、直播等左上角有标的元素,以及左上角没标的直播
  1789. const adElements_1 = document.querySelectorAll("div.floor-single-card, div.bili-live-card");
  1790. adElements_1.forEach(function (element) {
  1791. element.style.display = "none";
  1792. });
  1793. }
  1794.  
  1795. // 判断当前页面URL是否以 https://search.bilibili.com/all 开头,即搜索页——综合
  1796. if (window.location.href.startsWith("https://search.bilibili.com/all")) {
  1797. // 隐藏 搜索页——综合 下的 直播卡片
  1798. const adElements_2 = document.querySelectorAll("div.bili-video-card:has(div.bili-video-card__info--living)");
  1799. adElements_2.forEach(function (element) {
  1800. element.parentNode.style.display = "none";
  1801. element.style.display = "none";
  1802. });
  1803. }
  1804.  
  1805. // 隐藏首页广告,那些没有“enable-no-interest” CSS类的视频卡片元素
  1806. const adElements_3 = document.querySelectorAll("div.bili-video-card.is-rcmd:not(.enable-no-interest)");
  1807. adElements_3.forEach(function (element) {
  1808. // 检查其父元素是否是 .feed-card
  1809. if (element.closest("div.feed-card") !== null) {
  1810. // 如果是,选择其父元素并应用样式
  1811. element.closest("div.feed-card").style.display = "none";
  1812. } else {
  1813. // 如果不是,直接在视频元素上应用样式
  1814. element.style.display = "none";
  1815. }
  1816. });
  1817.  
  1818. // 隐藏视频播放页右侧广告、视频相关的游戏推荐、视频相关的特殊推荐、大家围观的直播
  1819. const adElements_4 = document.querySelectorAll(
  1820. "div#slide_ad, a.ad-report, div.video-page-game-card-small, div.video-page-special-card-small, div.pop-live-small-mode"
  1821. );
  1822. adElements_4.forEach(function (element) {
  1823. element.style.display = "none";
  1824. });
  1825. }
  1826.  
  1827. // 屏蔽或者取消屏蔽
  1828. function blockedOrUnblocked(videoElement, videoBv, setTimeoutStatu = false) {
  1829. // 是白名单目标,是屏蔽目标,没有隐藏、没有叠加层:跳过
  1830. if (
  1831. videoInfoDict[videoBv].whiteListTargets &&
  1832. videoInfoDict[videoBv].blockedTarget &&
  1833. videoElement.style.display != "none" &&
  1834. videoElement.firstElementChild.className != "blockedOverlay"
  1835. ) {
  1836. return;
  1837. }
  1838.  
  1839. // 是白名单目标,是屏蔽目标, 有隐藏或有叠加层:去除隐藏或叠加层
  1840. if (
  1841. videoInfoDict[videoBv].whiteListTargets &&
  1842. videoInfoDict[videoBv].blockedTarget &&
  1843. (videoElement.style.display == "none" || videoElement.firstElementChild.className == "blockedOverlay")
  1844. ) {
  1845. // 去除叠加层
  1846. removeHiddenOrOverlay(videoElement, videoBv, setTimeoutStatu);
  1847. return;
  1848. }
  1849.  
  1850. // 不是白名单目标,是屏蔽目标, 有隐藏或有叠加层:跳过
  1851. if (
  1852. videoInfoDict[videoBv].whiteListTargets != true &&
  1853. videoInfoDict[videoBv].blockedTarget &&
  1854. (videoElement.style.display == "none" || videoElement.firstElementChild.className == "blockedOverlay")
  1855. ) {
  1856. return;
  1857. }
  1858.  
  1859. // 不是白名单目标,是屏蔽目标, 没有隐藏、没有叠加层:隐藏或添加叠加层
  1860. if (
  1861. videoInfoDict[videoBv].whiteListTargets != true &&
  1862. videoInfoDict[videoBv].blockedTarget &&
  1863. videoElement.style.display != "none" &&
  1864. videoElement.firstElementChild.className != "blockedOverlay"
  1865. ) {
  1866. // 隐藏或添加叠加层
  1867. addHiddenOrOverlay(videoElement, videoBv, setTimeoutStatu);
  1868. return;
  1869. }
  1870.  
  1871. // 隐藏或添加叠加层
  1872. function addHiddenOrOverlay(videoElement, videoBv, setTimeoutStatu) {
  1873. // 是否为隐藏视频模式?
  1874. if (blockedParameter.hideVideoMode_Switch == true) {
  1875. // 隐藏视频
  1876.  
  1877. // 判断当前页面URL是否以 https://search.bilibili.com/ 开头,即搜索页面,修改父元素
  1878. if (window.location.href.startsWith("https://search.bilibili.com/")) {
  1879. videoElement.parentNode.style.display = "none";
  1880. // 为什么改了父元素,还要改元素本身?为了方便上面的判断。
  1881. videoElement.style.display = "none";
  1882. }
  1883. // 如果是父元素是feed-card,修改父元素
  1884. else if (videoElement.closest("div.feed-card") !== null) {
  1885. videoElement.closest("div.feed-card").style.display = "none";
  1886. videoElement.style.display = "none";
  1887. } else {
  1888. videoElement.style.display = "none";
  1889. }
  1890. } else {
  1891. // 添加叠加层
  1892.  
  1893. // Bug记录:
  1894. // 位置: 视频播放页面 (即 https://www.bilibili.com/video/BVxxxxxx 页面下)
  1895. // 行为: 添加屏蔽叠加层 这个操作 只因为 屏蔽标签 的方式来触发时 (如果还触发了 屏蔽标题 屏蔽短时长 这一类,是不会出现这个Bug的。)
  1896. // 症状: 渲染异常,右侧视频推荐列表的封面图片不可见;评论区丢失;页面头部的搜索框丢失 (div.center-search__bar 丢失);
  1897. // 处理: 延迟添加 overlay 可解决,先暂时把元素变成透明/模糊的,等3秒,页面完全加载完了,再创建创建屏蔽叠加层,再把元素改回正常。
  1898. // 猜测: 我一开始以为是使用 fetch 获取API造成的,因为只有 屏蔽标签 这个操作必须通过 fetch 获取标签信息的。
  1899. // 但是出现 屏蔽标题 屏蔽短时长 多种触发的情况下,又不会触发这个Bug了,想不懂,我也不会调试这种加载过程。
  1900.  
  1901. // 在 视频播放页面 "card-box" 创建屏蔽叠加层操作作延迟处理
  1902. if (videoElement.firstElementChild.className == "card-box" && setTimeoutStatu == false) {
  1903. // 元素先改模糊
  1904. // videoElement.style.opacity = "0";
  1905. videoElement.style.filter = "blur(5px)";
  1906. // 延迟3秒
  1907. setTimeout(() => {
  1908. // 创建屏蔽叠加层
  1909. blockedOrUnblocked(videoElement, videoBv, true);
  1910. // 元素再改回正常
  1911. // videoElement.style.opacity = "1";
  1912. videoElement.style.filter = "none";
  1913. }, 3000);
  1914.  
  1915. return;
  1916. }
  1917.  
  1918. // 获取 videoElement 的尺寸
  1919. const elementRect = videoElement.getBoundingClientRect();
  1920.  
  1921. // 叠加层参数(背景)
  1922. let overlay = document.createElement("div");
  1923. overlay.className = "blockedOverlay";
  1924. overlay.style.position = "absolute";
  1925. overlay.style.width = elementRect.width + "px"; // 使用 videoElement 的宽度
  1926. overlay.style.height = elementRect.height + "px"; // 使用 videoElement 的高度
  1927. overlay.style.backgroundColor = "rgba(60, 60, 60, 0.85)";
  1928. overlay.style.display = "flex";
  1929. overlay.style.justifyContent = "center";
  1930. overlay.style.alignItems = "center";
  1931. overlay.style.zIndex = "10";
  1932. overlay.style.backdropFilter = "blur(6px)";
  1933. overlay.style.borderRadius = "6px";
  1934.  
  1935. // 叠加层文本参数(背景)
  1936. let overlayText = document.createElement("div");
  1937. if (videoElement.firstElementChild.className == "card-box") {
  1938. overlayText.style.fontSize = "1.25em";
  1939. }
  1940. // 使用 videoInfoDict[videoBv] 里面的存储的触发规则的第1条来做为提示文字
  1941. overlayText.innerText = videoInfoDict[videoBv].triggeredBlockedRules[0];
  1942. overlayText.style.color = "rgb(250,250,250)";
  1943. overlay.appendChild(overlayText);
  1944.  
  1945. // 添加叠加层为最前面的子元素
  1946. videoElement.insertAdjacentElement("afterbegin", overlay);
  1947. }
  1948. }
  1949.  
  1950. // 去除隐藏或叠加层
  1951. function removeHiddenOrOverlay(videoElement) {
  1952. // 是否为隐藏视频模式?
  1953. if (blockedParameter.hideVideoMode_Switch == true) {
  1954. // 取消隐藏
  1955.  
  1956. // 判断当前页面URL是否以 https://search.bilibili.com/ 开头,即搜索页面
  1957. if (window.location.href.startsWith("https://search.bilibili.com/")) {
  1958. videoElement.parentNode.style.display = "";
  1959. videoElement.style.display = "";
  1960. }
  1961. // 如果是父元素是feed-card
  1962. else if (videoElement.closest("div.feed-card") !== null) {
  1963. videoElement.closest("div.feed-card").style.display = "";
  1964. videoElement.style.display = "";
  1965. } else {
  1966. videoElement.style.display = "";
  1967. }
  1968. } else {
  1969. // 删除叠加层
  1970. if (videoElement.firstElementChild.className == "blockedOverlay") {
  1971. videoElement.removeChild(videoElement.firstElementChild);
  1972. }
  1973. }
  1974. }
  1975. }
  1976.  
  1977. // 同步屏蔽叠加层与父元素的尺寸
  1978. function syncBlockedOverlayAndParentNodeRect() {
  1979. // 获取所有的屏蔽叠加层
  1980. const blockedOverlays = document.querySelectorAll("div.blockedOverlay");
  1981.  
  1982. blockedOverlays.forEach(function (element) {
  1983. // 获取父元素的尺寸
  1984. const parentNodeElementRect = element.parentNode.getBoundingClientRect();
  1985. // 修改屏蔽叠加层的大小
  1986. element.style.width = parentNodeElementRect.width + "px"; // 使用 父元素的尺寸 的宽度
  1987. element.style.height = parentNodeElementRect.height + "px"; // 使用 父元素的尺寸 的高度
  1988. });
  1989. }
  1990.  
  1991. // -----------------主流程函数----------------------
  1992.  
  1993. // 屏蔽Bilibili上的符合屏蔽条件的视频
  1994. function FuckYouBilibiliRecommendationSystem() {
  1995. // 是否启用 隐藏非视频元素
  1996. if (blockedParameter.hideNonVideoElements_Switch) {
  1997. // 隐藏非视频元素
  1998. hideNonVideoElements();
  1999. }
  2000.  
  2001. // 判断是否和上次的输出的字典不一样
  2002. if (objectDifferent(lastConsoleVideoInfoDict, videoInfoDict)) {
  2003. // 输出整个视频信息字典
  2004. consoleLogOutput(Object.keys(videoInfoDict).length, "个视频信息: ", videoInfoDict);
  2005.  
  2006. // 将本次输出的视频信息字典保存起来作参考
  2007. lastConsoleVideoInfoDict = Object.assign({}, videoInfoDict);
  2008. }
  2009.  
  2010. // 获取所有包含B站视频相关标签的视频元素
  2011. const videoElements = getVideoElements();
  2012.  
  2013. // 遍历每个视频元素
  2014. for (let videoElement of videoElements) {
  2015. // 判断是否为已经屏蔽处理过的子元素
  2016. if (isAlreadyBlockedChildElement(videoElement)) {
  2017. // 如果是已经屏蔽处理过的子元素,跳过后续操作
  2018. continue;
  2019. }
  2020.  
  2021. // 网页获取视频元素的Bv号和标题
  2022. let videoBv = getBvAndTitle(videoElement);
  2023.  
  2024. // 如果没有拿到Bv号,跳过后续操作
  2025. if (!videoBv) {
  2026. continue;
  2027. }
  2028.  
  2029. // 是否启用 屏蔽标题
  2030. if (blockedParameter.blockedTitle_Switch && blockedParameter.blockedTitle_Array.length > 0) {
  2031. // 判断处理匹配的屏蔽标题
  2032. handleBlockedTitle(videoBv);
  2033. }
  2034.  
  2035. // 网页获取视频Up名和UpUid
  2036. getNameAndUid(videoElement, videoBv);
  2037.  
  2038. // 通过API获取视频信息
  2039. getVideoApiInfo(videoBv);
  2040.  
  2041. // 是否启用 屏蔽Up主名称或Up主Uid
  2042. if (blockedParameter.blockedNameOrUid_Switch && blockedParameter.blockedNameOrUid_Array.length > 0) {
  2043. // 判断处理匹配的屏蔽Up主名称或Up主Uid
  2044. handleBlockedNameOrUid(videoBv);
  2045. }
  2046.  
  2047. // 是否启用 屏蔽视频分区
  2048. if (
  2049. blockedParameter.blockedVideoPartitions_Switch &&
  2050. blockedParameter.blockedVideoPartitions_Array.length > 0
  2051. ) {
  2052. // 判断处理匹配 屏蔽视频分区
  2053. handleBlockedVideoPartitions(videoBv);
  2054. }
  2055.  
  2056. // 是否启用 屏蔽短时长视频
  2057. if (blockedParameter.blockedShortDuration_Switch && blockedParameter.blockedShortDuration > 0) {
  2058. // 判断处理匹配的短时长视频
  2059. handleBlockedShortDuration(videoBv);
  2060. }
  2061.  
  2062. // 是否启用 屏蔽低播放量视频
  2063. if (blockedParameter.blockedBelowVideoViews_Switch && blockedParameter.blockedBelowVideoViews > 0) {
  2064. // 判断处理匹配的低播放量视频
  2065. handleBlockedBelowVideoViews(videoBv);
  2066. }
  2067.  
  2068. // 是否启用 屏蔽低于指定点赞率的视频
  2069. if (blockedParameter.blockedBelowLikesRate_Switch && blockedParameter.blockedBelowLikesRate > 0) {
  2070. // 判断处理 屏蔽低于指定点赞率的视频
  2071. handleBlockedBelowLikesRate(videoBv);
  2072. }
  2073.  
  2074. // 是否启用 屏蔽竖屏视频
  2075. if (blockedParameter.blockedPortraitVideo_Switch) {
  2076. // 判断处理 屏蔽竖屏视频
  2077. handleBlockedPortraitVideo(videoBv);
  2078. }
  2079.  
  2080. // 是否启用 屏蔽充电专属视频
  2081. if (blockedParameter.blockedChargingExclusive_Switch) {
  2082. // 判断处理 蔽充电专属视频
  2083. handleBlockedChargingExclusive(videoBv);
  2084. }
  2085.  
  2086. // 通过API获取视频标签
  2087. getVideoApiTags(videoBv);
  2088.  
  2089. // 是否启用 屏蔽标签
  2090. if (blockedParameter.blockedTag_Switch && blockedParameter.blockedTag_Array.length > 0) {
  2091. // 判断处理 屏蔽标签
  2092. handleBlockedTag(videoBv);
  2093. }
  2094.  
  2095. // 是否启用 屏蔽双重屏蔽标签
  2096. if (blockedParameter.doubleBlockedTag_Switch && blockedParameter.doubleBlockedTag_Array.length > 0) {
  2097. // 判断处理 屏蔽双重屏蔽标签
  2098. handleDoubleBlockedTag(videoBv);
  2099. }
  2100.  
  2101. // API获取视频评论区
  2102. getVideoApiComments(videoBv);
  2103.  
  2104. // 是否启用 屏蔽精选评论的视频
  2105. if (blockedParameter.blockedFilteredCommentsVideo_Switch) {
  2106. // 判断处理 屏蔽精选评论的视频
  2107. handleBlockedFilteredCommentsVideo(videoBv);
  2108. }
  2109.  
  2110. // 是否启用 屏蔽置顶评论
  2111. if (blockedParameter.blockedTopComment_Switch && blockedParameter.blockedTopComment_Array.length > 0) {
  2112. // 判断处理 屏蔽精选评论的视频
  2113. handleBlockedTopComment(videoBv);
  2114. }
  2115.  
  2116. // 是否启用 白名单Up主和Uid
  2117. if (blockedParameter.whitelistNameOrUid_Switch && blockedParameter.whitelistNameOrUid_Array.length > 0) {
  2118. // 判断处理 白名单Up主和Uid
  2119. handleWhitelistNameOrUid(videoBv);
  2120. }
  2121.  
  2122. // 屏蔽或者取消屏蔽
  2123. blockedOrUnblocked(videoElement, videoBv);
  2124.  
  2125. // 同步屏蔽叠加层与父元素的尺寸
  2126. syncBlockedOverlayAndParentNodeRect();
  2127. }
  2128. }
  2129.  
  2130. // 页面加载完成后运行脚本
  2131. window.addEventListener("load", FuckYouBilibiliRecommendationSystem);
  2132.  
  2133. // 窗口尺寸变化时运行脚本
  2134. window.addEventListener("resize", FuckYouBilibiliRecommendationSystem);
  2135.  
  2136. // 定义 MutationObserver 的回调函数
  2137. function mutationCallback() {
  2138. // 在这里运行你的脚本
  2139. FuckYouBilibiliRecommendationSystem();
  2140. }
  2141. // 创建一个 MutationObserver 实例,观察 body 元素的子节点变化
  2142. let observer = new MutationObserver(mutationCallback);
  2143. let targetNode = document.body;
  2144. // 配置观察器的选项
  2145. let config = { childList: true, subtree: true };
  2146. // 启动观察器并传入回调函数和配置选项
  2147. observer.observe(targetNode, config);

QingJ © 2025

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