bilibili直播净化

屏蔽聊天室礼物以及关键字, 净化聊天室环境

Tính đến 01-07-2024. Xem phiên bản mới nhất.

  1. // ==UserScript==
  2. // @name bilibili直播净化
  3. // @namespace https://github.com/lzghzr/GreasemonkeyJS
  4. // @version 4.2.41
  5. // @author lzghzr
  6. // @description 屏蔽聊天室礼物以及关键字, 净化聊天室环境
  7. // @icon data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGNpcmNsZSBjeD0iMTYiIGN5PSIxNiIgcj0iMTUiIHN0cm9rZT0iIzAwYWVlYyIgc3Ryb2tlLXdpZHRoPSIyIiBmaWxsPSJub25lIi8+PHRleHQgZm9udC1mYW1pbHk9Ik5vdG8gU2FucyBDSksgU0MiIGZvbnQtc2l6ZT0iMjIiIHg9IjUiIHk9IjIzIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iMCIgZmlsbD0iIzAwYWVlYyI+5ruaPC90ZXh0Pjwvc3ZnPg==
  8. // @supportURL https://github.com/lzghzr/GreasemonkeyJS/issues
  9. // @match https://live.bilibili.com/*
  10. // @match https://www.bilibili.com/blackboard/*
  11. // @license MIT
  12. // @require https://github.com/lzghzr/TampermonkeyJS/raw/5036ad2ff5875d313df654b8feca13cdb69d0d00/xhook/xhook.js#sha256=7l4G7dFdCp6GZibUe8Z3vl6jb6Q1yCjUrfUxI8k2KFY=
  13. // @compatible chrome 基础功能需要 88 以上支持 :not() 伪类,高级功能需要 105 及以上支持 :has() 伪类
  14. // @compatible edge 基础功能需要 88 以上支持 :not() 伪类,高级功能需要 105 及以上支持 :has() 伪类
  15. // @compatible firefox 基础功能需要 84 以上支持 :not() 伪类,高级功能需要 121 及以上支持 :has() 伪类
  16. // @grant GM_addStyle
  17. // @grant GM_getValue
  18. // @grant GM_setValue
  19. // @grant unsafeWindow
  20. // @run-at document-start
  21. // ==/UserScript==
  22. const W = typeof unsafeWindow === 'undefined' ? window : unsafeWindow;
  23. class NoVIP {
  24. elmStyleCSS;
  25. chatObserver;
  26. danmakuObserver;
  27. Start() {
  28. this.elmStyleCSS = GM_addStyle('');
  29. this.AddCSS();
  30. const chatMessage = new Map();
  31. this.chatObserver = new MutationObserver(mutations => {
  32. mutations.forEach(mutation => {
  33. mutation.addedNodes.forEach(addedNode => {
  34. if (addedNode instanceof HTMLDivElement && addedNode.classList.contains('danmaku-item')) {
  35. const chatNode = addedNode.querySelector('.danmaku-item-right');
  36. if (chatNode !== null) {
  37. const chatText = chatNode.innerText;
  38. const dateNow = Date.now();
  39. if (chatMessage.has(chatText) && dateNow - chatMessage.get(chatText) < 10_000) {
  40. addedNode.classList.add('NoVIP_chat_hide');
  41. }
  42. else {
  43. chatMessage.set(chatText, dateNow);
  44. }
  45. }
  46. }
  47. });
  48. });
  49. });
  50. const elmDivChatList = document.querySelector('#chat-items');
  51. if (elmDivChatList !== null) {
  52. this.chatObserver.observe(elmDivChatList, { childList: true });
  53. }
  54. const danmakuMessage = new Map();
  55. this.danmakuObserver = new MutationObserver(mutations => {
  56. mutations.forEach(mutation => {
  57. mutation.addedNodes.forEach(addedNode => {
  58. const danmakuNode = addedNode instanceof Text ? addedNode.parentElement : addedNode;
  59. if (danmakuNode?.classList?.contains('bili-dm')) {
  60. const danmakuText = danmakuNode.innerText.split(/ ?[x×]\d+$/);
  61. const dateNow = Date.now();
  62. if (danmakuMessage.has(danmakuText[0]) && dateNow - danmakuMessage.get(danmakuText[0]) < 10_000) {
  63. danmakuNode.classList.add('NoVIP_danmaku_hide');
  64. }
  65. else if (danmakuText[1] !== undefined) {
  66. danmakuNode.classList.add('NoVIP_danmaku_hide');
  67. }
  68. else {
  69. danmakuMessage.set(danmakuText[0], dateNow);
  70. }
  71. }
  72. });
  73. });
  74. });
  75. const elmDivDanmaku = document.querySelector('#live-player');
  76. if (elmDivDanmaku !== null) {
  77. this.danmakuObserver.observe(elmDivDanmaku, { childList: true, subtree: true });
  78. }
  79. setInterval(() => {
  80. const dateNow = Date.now();
  81. chatMessage.forEach((value, key) => {
  82. if (dateNow - value > 60_000) {
  83. chatMessage.delete(key);
  84. }
  85. });
  86. danmakuMessage.forEach((value, key) => {
  87. if (dateNow - value > 60_000) {
  88. danmakuMessage.delete(key);
  89. }
  90. });
  91. }, 60_000);
  92. const docObserver = new MutationObserver(mutations => {
  93. mutations.forEach(mutation => {
  94. mutation.addedNodes.forEach(addedNode => {
  95. if (addedNode instanceof HTMLDivElement) {
  96. if (addedNode.classList.contains('dialog-ctnr')) {
  97. const blockEffectCtnr = addedNode.querySelector('.block-effect-ctnr');
  98. if (blockEffectCtnr !== null) {
  99. this.AddUI(blockEffectCtnr);
  100. }
  101. }
  102. }
  103. });
  104. });
  105. });
  106. docObserver.observe(document, { childList: true, subtree: true });
  107. const block = localStorage.getItem('LIVE_BLCOK_EFFECT_STATE');
  108. if (block?.includes('2')) {
  109. localStorage.setItem('LIVE_BLCOK_EFFECT_STATE', '2');
  110. }
  111. else {
  112. localStorage.setItem('LIVE_BLCOK_EFFECT_STATE', '');
  113. }
  114. this.ChangeCSS();
  115. }
  116. NORoomSkin() {
  117. if (config.menu.noRoomSkin.enable) {
  118. W.roomBuffService.__NORoomSkin = true;
  119. W.roomBuffService.unmount();
  120. }
  121. else {
  122. W.roomBuffService.__NORoomSkin = false;
  123. W.roomBuffService.mount(W.roomBuffService.__NORoomSkin_skin);
  124. }
  125. }
  126. ChangeCSS() {
  127. let height = 62;
  128. let cssText = `
  129. /* 统一用户名颜色 */
  130. .chat-item .user-name {
  131. color: var(--brand_blue) !important;
  132. }`;
  133. if (config.menu.noGuardIcon.enable) {
  134. cssText += `
  135. /* 聊天背景 */
  136. .chat-item.chat-colorful-bubble {
  137. background-color: unset !important;
  138. border-image-source: unset !important;
  139. border-radius: unset !important;
  140. display: block !important;
  141. margin: unset !important;
  142. }
  143. /* 聊天背景 */
  144. .chat-item.chat-colorful-bubble div:has(div[style*="border-image-source"]),
  145. /* 欢迎提示条 */
  146. #welcome-area-bottom-vm:has(.sama-avatar-box),
  147. /* 粉丝勋章内标识 */
  148. .chat-item .fans-medal-item-ctnr .medal-guard,
  149. /* 舰队指挥官标识 */
  150. .chat-item .pilot-icon,
  151. .chat-item .pilot-icon ~ br,
  152. /* 订阅舰长 */
  153. .chat-item.guard-buy {
  154. display: none !important;
  155. }
  156. /* 兼容chrome 105以下版本 */
  157. @supports not selector(:has(a, b)) {
  158. .chat-item.chat-colorful-bubble div[style*="border-image-source"]
  159. #welcome-area-bottom-vm {
  160. display: none !important;
  161. }
  162. }`;
  163. }
  164. if (config.menu.noWealthMedalIcon.enable) {
  165. cssText += `
  166. /* 聊天背景, 存疑 */
  167. .chat-item.wealth-bubble {
  168. border-image-source: unset !important;
  169. }
  170. /* 聊天背景, 存疑 */
  171. .chat-item.has-bubble {
  172. border-image-source: unset !important;
  173. border-image-slice: unset !important;
  174. border-image-width: unset !important;
  175. box-sizing: unset !important;
  176. display: block !important;
  177. margin: unset !important;
  178. }
  179. /* 欢迎提示条 */
  180. #welcome-area-bottom-vm:has(.wealth-medal),
  181. /* 弹幕 */
  182. .bili-dm > .bili-icon,
  183. /* 聊天 */
  184. .chat-item .wealth-medal-ctnr {
  185. display: none !important;
  186. }
  187. /* 兼容chrome 105以下版本 */
  188. @supports not selector(:has(a, b)) {
  189. #welcome-area-bottom-vm {
  190. display: none !important;
  191. }
  192. }`;
  193. }
  194. if (config.menu.noGiftMsg.enable) {
  195. height -= 32;
  196. cssText += `
  197. /* 底部小礼物, 调整高度 */
  198. .chat-history-list.with-penury-gift {
  199. height: 100% !important;
  200. }
  201. /* 热门流量推荐 */
  202. .chat-item.hot-rank-msg,
  203. /* VIP标识 */
  204. #activity-welcome-area-vm,
  205. .chat-item .vip-icon,
  206. .chat-item.welcome-msg,
  207. /* 高能标识 */
  208. .chat-item.top3-notice,
  209. .chat-item .rank-icon,
  210. /* 分享直播间 */
  211. .chat-item.important-prompt-item,
  212. /* 礼物栏 */
  213. .gift-control-panel > *:not(.left-part-ctnr),
  214. #web-player__bottom-bar__container,
  215.  
  216. #chat-gift-bubble-vm,
  217. #penury-gift-msg,
  218. #gift-screen-animation-vm,
  219. #my-dear-haruna-vm .super-gift-bubbles,
  220. .chat-item.gift-item,
  221. .chat-item.system-msg,
  222.  
  223. .web-player-inject-wrap .announcement-wrapper,
  224. .bilibili-live-player-video-operable-container>div:first-child>div:last-child,
  225. .bilibili-live-player-video-gift,
  226. .bilibili-live-player-danmaku-gift {
  227. display: none !important;
  228. }`;
  229. }
  230. if (config.menu.noSystemMsg.enable) {
  231. height -= 30;
  232. cssText += `
  233. .chat-history-list.with-brush-prompt {
  234. height: 100% !important;
  235. }
  236. /* 目前只看到冲榜提示 */
  237. .chat-history-panel #all-guide-cards,
  238. /* 聊天下方滚动消息,进场、点赞之类的 */
  239. #brush-prompt,
  240. /* 初始系统提示 */
  241. .chat-item.convention-msg,
  242. /* 各种野生消息 */
  243. .chat-item.common-danmuku-msg,
  244. /* 各种野生消息 x2 */
  245. .chat-item.misc-msg,
  246. /* 各种野生消息 x3 (Toasts) */
  247. .link-toast,
  248. /* pk */
  249. .chat-item.new-video-pk-item-dm {
  250. display: none !important;
  251. }`;
  252. }
  253. if (config.menu.noSuperChat.enable) {
  254. cssText += `
  255. /* 调整 SuperChat 聊天框 */
  256. .chat-history-list {
  257. padding-top: 5px !important;
  258. }
  259. .chat-item.superChat-card-detail {
  260. margin-left: unset !important;
  261. margin-right: unset !important;
  262. min-height: unset !important;
  263. }
  264. .chat-item .card-item-middle-top {
  265. background-color: unset !important;
  266. background-image: unset !important;
  267. border: unset !important;
  268. display: inline !important;
  269. padding: unset !important;
  270. }
  271. .chat-item .card-item-middle-top-right {
  272. display: unset !important;
  273. }
  274. .chat-item .superChat-base {
  275. display: unset !important;
  276. height: unset !important;
  277. line-height: unset !important;
  278. vertical-align: unset !important;
  279. width: unset !important;
  280. }
  281. .chat-item .superChat-base .fans-medal-item-ctnr {
  282. margin-right: 4px !important;
  283. }
  284. .chat-item .name,
  285. .chat-item .card-item-name {
  286. display: unset !important;
  287. font-size: unset !important;
  288. font-weight: unset !important;
  289. height: unset !important;
  290. line-height: 20px !important;
  291. margin-left: unset !important;
  292. opacity: unset !important;
  293. overflow: unset !important;
  294. text-overflow: unset !important;
  295. vertical-align: unset !important;
  296. white-space: unset !important;
  297. width: unset !important;
  298. }
  299. .chat-item .card-item-name>span {
  300. color: var(--brand_blue) !important;
  301. }
  302. /* 为 SuperChat 用户名添加 : */
  303. .chat-item.superChat-card-detail .name:after,
  304. .chat-item.superChat-card-detail .card-item-name>span:after {
  305. content: ' : ';
  306. }
  307. .chat-item .card-item-middle-bottom {
  308. background-color: unset !important;
  309. display: unset !important;
  310. padding: unset !important;
  311. }
  312. .chat-item .input-contain {
  313. display: unset !important;
  314. }
  315. .chat-item .text {
  316. color: var(--text2) !important;
  317. }
  318. /* SuperChat 提示条 */
  319. #chat-msg-bubble-vm,
  320. /* SuperChat 保留条 */
  321. #pay-note-panel-vm,
  322. .chat-item .bottom-background,
  323. /* SuperChat 聊天条 右上角电池 */
  324. .chat-item .card-item-top-right,
  325. /* SuperChat 按钮 */
  326. #chat-control-panel-vm .super-chat {
  327. display: none !important;
  328. }`;
  329. }
  330. if (config.menu.noEmoticons.enable) {
  331. cssText += `
  332. #chat-control-panel-vm .emoticons-panel,
  333. .chat-item.chat-emoticon {
  334. display: none !important;
  335. }`;
  336. }
  337. if (config.menu.noEmotDanmaku.enable) {
  338. cssText += `
  339. .bili-dm > img:not(.bili-icon) {
  340. display: none !important;
  341. }`;
  342. }
  343. if (config.menu.noLikeBtn.enable) {
  344. cssText += `
  345. /* 点赞按钮 */
  346. #chat-control-panel-vm .like-btn,
  347. /* 点赞消息 */
  348. .chat-item[data-type="6"],
  349. /* 点赞数 */
  350. #head-info-vm .icon-ctnr:has(.like-icon) {
  351. display: none !important;
  352. }
  353. /* 兼容chrome 105以下版本 */
  354. @supports not selector(:has(a, b)) {
  355. #head-info-vm .like-icon,
  356. #head-info-vm .like-text {
  357. display: none !important;
  358. }
  359. }`;
  360. }
  361. if (config.menu.noGiftControl.enable) {
  362. cssText += `
  363. /* 排行榜 */
  364. .rank-list-section .gift-rank-cntr .top3-cntr .default,
  365. .rank-list-section .guard-rank-cntr:not(.open) .guard-empty {
  366. height: 42px !important;
  367. }
  368. .rank-list-section .guard-rank-cntr:not(.open) .guard-empty {
  369. background-size: contain !important;
  370. background-position: center !important;
  371. background-repeat: no-repeat !important;
  372. }
  373. .rank-list-section .gift-rank-cntr .top3-cntr .default-msg {
  374. bottom: -12px !important;
  375. }
  376. .rank-list-section,
  377. .rank-list-section.new .rank-list-ctnr[style*="height: 178px;"] {
  378. height: 98px !important;
  379. }
  380. .rank-list-section .tab-content,
  381. .rank-list-section .tab-content-pilot,
  382. .rank-list-section.new .guard-rank-cntr .rank-list-cntr {
  383. min-height: unset !important;
  384. }
  385. .rank-list-section .tab-content[style*="height: 9"],
  386. .rank-list-section .tab-content-pilot[style*="height: 9"],
  387. .rank-list-section .gift-rank-cntr .top3-cntr {
  388. height: 64px !important;
  389. }
  390. .rank-list-section .guard-rank-cntr .top3-cntr > span {
  391. height: 32px !important;
  392. }
  393. .rank-list-section.new .gift-rank-cntr .top3-cntr,
  394. .rank-list-section.new .guard-rank-cntr {
  395. height: unset !important;
  396. }
  397. .rank-list-section.new .gift-rank-cntr .top3-cntr {
  398. padding-top: 5px !important;
  399. }
  400. .rank-list-section.new .guard-rank-cntr .top3-cntr {
  401. top: 15px !important;
  402. }
  403. /* 调整聊天区 */
  404. .rank-list-section~.chat-history-panel {
  405. height: calc(100% - 98px - 145px) !important;
  406. }
  407. /* 直播分区 */
  408. .live-area {
  409. display: flex !important;
  410. }
  411. /* 排行榜 */
  412. .rank-list-section.new .gift-rank-cntr .top3 > div ~ div,
  413. .rank-list-section.new .guard-rank-cntr .top3-cntr > span ~ span,
  414. .rank-list-section.new .pilot,
  415. /* 人气榜 */
  416. #head-info-vm .popular-and-hot-rank,
  417. /* 礼物星球 */
  418. #head-info-vm .gift-planet-entry,
  419. /* 活动榜 */
  420. #head-info-vm .activity-entry,
  421. /* 粉丝团 */
  422. #head-info-vm .follow-ctnr,
  423. /* 礼物按钮 */
  424. #web-player-controller-wrap-el .web-live-player-gift-icon-wrap,
  425. /* 头像框 */
  426. .blive-avatar-pendant,
  427. /* 主播城市 */
  428. .anchor-location,
  429. /* 水印 */
  430. .web-player-icon-roomStatus,
  431. .blur-edges-ctnr,
  432. /* 遮罩 */
  433. #web-player-module-area-mask-panel {
  434. display: none !important;
  435. }`;
  436. }
  437. if (config.menu.noFansMedalIcon.enable) {
  438. cssText += `
  439. /* 团体勋章 */
  440. .chat-item .group-medal-ctnr,
  441. /* 团体勋章 底部提示条 */
  442. #brush-prompt .group-medal-ctnr,
  443. /* 粉丝勋章 聊天 */
  444. .chat-item .fans-medal-item-ctnr,
  445. /* 粉丝勋章 底部提示条 */
  446. #brush-prompt .fans-medal-item-ctnr {
  447. display: none !important;
  448. }`;
  449. }
  450. if (config.menu.noLiveTitleIcon.enable) {
  451. cssText += `
  452. .chat-item .title-label {
  453. display: none !important;
  454. }`;
  455. }
  456. if (config.menu.noRaffle.enable) {
  457. cssText += `
  458. body:not(.player-full-win):has(#anchor-guest-box-id)[style*="overflow: hidden;"] {
  459. overflow-y: overlay !important;
  460. }
  461. #shop-popover-vm,
  462. #anchor-guest-box-id,
  463. #player-effect-vm,
  464. #chat-draw-area-vm,
  465. /* 天选之类的 */
  466. .gift-control-panel .left-part-ctnr,
  467. .anchor-lottery-entry,
  468. .popular-main .lottery {
  469. display: none !important;
  470. }
  471. /* 兼容chrome 105以下版本 */
  472. @supports not selector(:has(a, b)) {
  473. body:not(.player-full-win)[style*="overflow: hidden;"] {
  474. overflow-y: overlay !important;
  475. }
  476. }`;
  477. }
  478. if (config.menu.noDanmakuColor.enable) {
  479. cssText += `
  480. .bili-dm {
  481. color: #ffffff !important;
  482. }`;
  483. }
  484. if (config.menu.noGameId.enable) {
  485. cssText += `
  486. /* 总容器 */
  487. .web-player-inject-wrap,
  488. /* PK */
  489. /* #pk-vm, */
  490. /* #awesome-pk-vm, */
  491. /* #chaos-pk-vm, */
  492. /* 多人连麦 */
  493. /* #multi-voice-index, */
  494. /* #multi-player, */
  495. /* 互动游戏 */
  496. #game-id,
  497. /* 连麦 */
  498. #chat-control-panel-vm .voice-rtc,
  499. /* 帮玩 */
  500. #chat-control-panel-vm .play-together-service-card-container,
  501. /* 一起玩 */
  502. #chat-control-panel-vm .play-together-entry,
  503. /* 神秘人 */
  504. .chat-item .common-nickname-medal {
  505. display: none !important;
  506. }`;
  507. }
  508. if (config.menu.noBBChat.enable) {
  509. cssText += `
  510. /* 官方 */
  511. #aside-area-vm #combo-card,
  512. #aside-area-vm #combo-danmaku-vm,
  513. #aside-area-vm .vote-card,
  514. /* 自定义 */
  515. .chat-item.NoVIP_chat_hide {
  516. display: none !important;
  517. }`;
  518. }
  519. if (config.menu.noBBDanmaku.enable) {
  520. cssText += `
  521. /* 官方 */
  522. .danmaku-item-container .bilibili-combo-danmaku-container,
  523. .danmaku-item-container .combo {
  524. display: none !important;
  525. }
  526. /* 自定义 */
  527. .bili-dm.NoVIP_danmaku_hide,
  528. /* 官方 */
  529. .danmaku-item-container .mode-adv {
  530. color: transparent !important;
  531. text-shadow: unset !important;
  532. }`;
  533. }
  534. cssText += `
  535. .chat-history-list.with-penury-gift.with-brush-prompt {
  536. height: calc(100% - ${height}px) !important;
  537. }`;
  538. this.NORoomSkin();
  539. this.elmStyleCSS.innerHTML = cssText;
  540. }
  541. AddUI(addedNode) {
  542. const elmUList = addedNode.firstElementChild;
  543. elmUList.childNodes.forEach(child => {
  544. if (child instanceof Comment) {
  545. child.remove();
  546. }
  547. });
  548. const listLength = elmUList.childElementCount;
  549. if (listLength > 10) {
  550. return;
  551. }
  552. const changeListener = (itemHTML, x) => {
  553. const itemSpan = itemHTML.querySelector('span');
  554. const itemInput = itemHTML.querySelector('input');
  555. itemInput.checked = config.menu[x].enable;
  556. itemInput.checked ? selectedCheckBox(itemSpan) : defaultCheckBox(itemSpan);
  557. itemInput.addEventListener('change', ev => {
  558. const evt = ev.target;
  559. evt.checked ? selectedCheckBox(itemSpan) : defaultCheckBox(itemSpan);
  560. config.menu[x].enable = evt.checked;
  561. GM_setValue('blnvConfig', encodeURI(JSON.stringify(config)));
  562. this.ChangeCSS();
  563. });
  564. };
  565. const selectedCheckBox = (spanClone) => {
  566. spanClone.classList.remove('checkbox-default');
  567. spanClone.classList.add('checkbox-selected');
  568. };
  569. const defaultCheckBox = (spanClone) => {
  570. spanClone.classList.remove('checkbox-selected');
  571. spanClone.classList.add('checkbox-default');
  572. };
  573. const replaceItem = (listNodes, x) => {
  574. for (const child of listNodes) {
  575. if (child.innerText === config.menu[x].replace) {
  576. return child;
  577. }
  578. }
  579. };
  580. const itemHTML = elmUList.firstElementChild.cloneNode(true);
  581. const itemInput = itemHTML.querySelector('input');
  582. const itemLabel = itemHTML.querySelector('label');
  583. itemInput.id = itemInput.id.replace(/\d/, '');
  584. itemLabel.htmlFor = itemLabel.htmlFor.replace(/\d/, '');
  585. let i = listLength + 10;
  586. const listNodes = elmUList.childNodes;
  587. for (const x in config.menu) {
  588. const child = replaceItem(listNodes, x);
  589. if (child === undefined) {
  590. const itemHTMLClone = itemHTML.cloneNode(true);
  591. const itemInputClone = itemHTMLClone.querySelector('input');
  592. const itemLabelClone = itemHTMLClone.querySelector('label');
  593. itemInputClone.id += i;
  594. itemLabelClone.htmlFor += i;
  595. i++;
  596. itemLabelClone.innerText = config.menu[x].name;
  597. changeListener(itemHTMLClone, x);
  598. elmUList.appendChild(itemHTMLClone);
  599. }
  600. else {
  601. const itemHTMLClone = child.cloneNode(true);
  602. const itemLabelClone = itemHTMLClone.querySelector('label');
  603. itemLabelClone.innerText = config.menu[x].name;
  604. changeListener(itemHTMLClone, x);
  605. child.remove();
  606. elmUList.appendChild(itemHTMLClone);
  607. }
  608. }
  609. }
  610. AddCSS() {
  611. GM_addStyle(`
  612. /* 多行菜单 */
  613. .border-box.dialog-ctnr.common-popup-wrap.top-left[style*="width: 200px;"] {
  614. width: 270px !important;
  615. }
  616. .block-effect-ctnr .item {
  617. float: left;
  618. }
  619. .block-effect-ctnr .item .cb-icon {
  620. left: unset !important;
  621. margin-left: -6px;
  622. }
  623. .block-effect-ctnr .item label {
  624. width: 84px;
  625. white-space: nowrap;
  626. overflow: hidden;
  627. text-overflow: ellipsis;
  628. }
  629. /* 隐藏网页全屏榜单 */
  630. .player-full-win .rank-list-section {
  631. display: none !important;
  632. }
  633. .player-full-win .chat-history-panel:not([style]) {
  634. height: calc(100% - 135px) !important;
  635. }
  636. .player-full-win .chat-history-panel.new {
  637. height: calc(100% - 135px) !important;
  638. }`);
  639. }
  640. }
  641. const defaultConfig = {
  642. version: 1710342294818,
  643. menu: {
  644. noGiftMsg: {
  645. name: '屏蔽礼物相关',
  646. replace: '屏蔽全部礼物及广播',
  647. enable: false
  648. },
  649. noSystemMsg: {
  650. name: '屏蔽系统消息',
  651. replace: '屏蔽进场信息',
  652. enable: false
  653. },
  654. noSuperChat: {
  655. name: '屏蔽醒目留言',
  656. replace: '屏蔽醒目留言',
  657. enable: false
  658. },
  659. noEmoticons: {
  660. name: '屏蔽表情聊天',
  661. replace: '屏蔽表情动画(右下角)',
  662. enable: false
  663. },
  664. noEmotDanmaku: {
  665. name: '屏蔽表情弹幕',
  666. replace: '屏蔽表情弹幕',
  667. enable: false
  668. },
  669. noLikeBtn: {
  670. name: '屏蔽点赞按钮',
  671. enable: false
  672. },
  673. noGiftControl: {
  674. name: '屏蔽活动控件',
  675. enable: false
  676. },
  677. noGuardIcon: {
  678. name: '屏蔽舰队标识',
  679. enable: false
  680. },
  681. noWealthMedalIcon: {
  682. name: '屏蔽荣耀勋章',
  683. enable: false
  684. },
  685. noFansMedalIcon: {
  686. name: '屏蔽粉丝勋章',
  687. enable: false
  688. },
  689. noLiveTitleIcon: {
  690. name: '屏蔽成就头衔',
  691. enable: false
  692. },
  693. noRaffle: {
  694. name: '屏蔽抽奖橱窗',
  695. enable: false
  696. },
  697. noDanmakuColor: {
  698. name: '屏蔽弹幕颜色',
  699. enable: false
  700. },
  701. noGameId: {
  702. name: '屏蔽互动游戏',
  703. enable: false
  704. },
  705. noBBChat: {
  706. name: '屏蔽刷屏聊天',
  707. enable: false
  708. },
  709. noBBDanmaku: {
  710. name: '屏蔽刷屏弹幕',
  711. enable: false
  712. },
  713. noRoomSkin: {
  714. name: '屏蔽房间皮肤',
  715. enable: false
  716. },
  717. noActivityPlat: {
  718. name: '屏蔽活动皮肤',
  719. enable: false
  720. },
  721. noRoundPlay: {
  722. name: '屏蔽视频轮播',
  723. enable: false
  724. },
  725. noSleep: {
  726. name: '屏蔽挂机检测',
  727. enable: false
  728. },
  729. invisible: {
  730. name: '隐身入场',
  731. enable: false
  732. }
  733. }
  734. };
  735. const userConfig = GM_getValue('blnvConfig', null) === null ? defaultConfig : JSON.parse(decodeURI(GM_getValue('blnvConfig')));
  736. let config;
  737. if (userConfig.version === undefined || userConfig.version < defaultConfig.version) {
  738. for (const x in defaultConfig.menu) {
  739. try {
  740. defaultConfig.menu[x].enable = userConfig.menu[x].enable;
  741. }
  742. catch (error) {
  743. console.error(...scriptName('载入配置失效'), error);
  744. }
  745. }
  746. config = defaultConfig;
  747. }
  748. else {
  749. config = userConfig;
  750. }
  751. if (!document.documentElement.hasAttribute('lab-style')) {
  752. if (config.menu.noActivityPlat.enable) {
  753. W.addEventListener("message", msg => {
  754. if (msg.origin === 'https://live.bilibili.com' && msg.data.startsWith('https://live.bilibili.com/blanc/')) {
  755. location.href = msg.data;
  756. }
  757. });
  758. }
  759. }
  760. else {
  761. W.getComputedStyle = new Proxy(W.getComputedStyle, {
  762. apply: function (target, _this, args) {
  763. if (args !== undefined && args[0] instanceof HTMLElement) {
  764. let htmlEle = Reflect.apply(target, _this, args);
  765. htmlEle = new Proxy(htmlEle, {
  766. get: function (_target, propertyKey) {
  767. if (propertyKey === 'display' && _target[propertyKey] === 'none') {
  768. return 'block';
  769. }
  770. return Reflect.get(_target, propertyKey);
  771. }
  772. });
  773. return htmlEle;
  774. }
  775. return Reflect.apply(target, _this, args);
  776. }
  777. });
  778. Object.defineProperty(W, '__NEPTUNE_IS_MY_WAIFU__', {});
  779. let push = 1 << 5;
  780. W.webpackChunklive_room = W.webpackChunklive_room || [];
  781. W.webpackChunklive_room.push = new Proxy(W.webpackChunklive_room.push, {
  782. apply: function (target, _this, args) {
  783. for (const [name, fn] of Object.entries(args[0][1])) {
  784. let fnStr = fn.toString();
  785. if (fnStr.includes('staticClass:"block-effect-icon-root"')) {
  786. const regexp = /(?<left>staticClass:"block-effect-icon-root"\},\[)"on"===(?<mut_t>\w+)\.blockEffectStatus\?(?<svg>(?<mut_n>\w+)\("svg".*?)\[\k<mut_n>\("path".*?blockEffectIconColor\}\}\)\]/s;
  787. const match = fnStr.match(regexp);
  788. if (match !== null) {
  789. fnStr = fnStr.replace(regexp, '$<left>$<svg>\[\
  790. $<mut_n>("circle",{attrs:{cx:"12",cy:"12",r:"10",stroke:$<mut_t>.blockEffectIconColor,"stroke-width":"1.5",fill:"none"}}),\
  791. $<mut_t>._v(" "),\
  792. $<mut_n>("text",{attrs:{"font-family":"Noto Sans CJK SC","font-size":"14",x:"5",y:"17",fill:$<mut_t>.blockEffectIconColor}},[$<mut_t>._v("滚")])\
  793. ]');
  794. console.info(...scriptName('脚本 icon 已加载'));
  795. }
  796. else {
  797. console.error(...scriptName('插入脚本 icon 失效'), fnStr);
  798. }
  799. push |= 1 << 0;
  800. }
  801. if (fnStr.includes('return this.chatList.children.length')) {
  802. const regexp = /(?<left>return )this\.chatList\.children\.length/s;
  803. const match = fnStr.match(regexp);
  804. if (match !== null) {
  805. fnStr = fnStr.replace(regexp, '$<left>this.chatList.querySelectorAll(".danmaku-item:not(.NoVIP_hide)").length');
  806. console.info(...scriptName('增强聊天显示 已加载'));
  807. }
  808. else {
  809. console.error(...scriptName('增强聊天显示失效'), fnStr);
  810. }
  811. push |= 1 << 1;
  812. }
  813. if (config.menu.noRoundPlay.enable) {
  814. if (fnStr.includes('case"PREPARING":')) {
  815. const regexp = /(?<left>case"PREPARING":)(?<right>\w+\((?<mut>\w+)\);break;)/s;
  816. const match = fnStr.match(regexp);
  817. if (match !== null) {
  818. fnStr = fnStr.replace(regexp, '$<left>$<mut>.round=0;$<right>');
  819. console.info(...scriptName('屏蔽下播轮播 已加载'));
  820. }
  821. else {
  822. console.error(...scriptName('屏蔽下播轮播失效'), fnStr);
  823. }
  824. push |= 1 << 2;
  825. }
  826. }
  827. else {
  828. push |= 1 << 2;
  829. }
  830. if (config.menu.noSleep.enable) {
  831. if (fnStr.includes('prototype.sleep=function(')) {
  832. const regexp = /(?<left>prototype\.sleep=function\(\w*\){)/;
  833. const match = fnStr.match(regexp);
  834. if (match !== null) {
  835. fnStr = fnStr.replace(regexp, '$<left>return;');
  836. console.info(...scriptName('屏蔽挂机检测 已加载'));
  837. }
  838. else {
  839. console.error(...scriptName('屏蔽挂机检测失效'), fnStr);
  840. }
  841. push |= 1 << 3;
  842. }
  843. }
  844. else {
  845. push |= 1 << 3;
  846. }
  847. if (config.menu.invisible.enable) {
  848. if (fnStr.includes('this.enterRoomTracker=new ')) {
  849. const regexp = /(?<left>this\.enterRoomTracker=new \w+),/s;
  850. const match = fnStr.match(regexp);
  851. if (match !== null) {
  852. fnStr = fnStr.replace(regexp, '$<left>,this.enterRoomTracker.report=()=>{},');
  853. console.info(...scriptName('房间心跳隐身 已加载'));
  854. }
  855. else {
  856. console.error(...scriptName('房间心跳隐身失效'), fnStr);
  857. }
  858. push |= 1 << 4;
  859. }
  860. }
  861. else {
  862. push |= 1 << 4;
  863. }
  864. if (fn.toString() !== fnStr) {
  865. args[0][1][name] = str2Fn(fnStr);
  866. }
  867. if (isAllBitsSet(push)) {
  868. W.webpackChunklive_room.push = target;
  869. break;
  870. }
  871. }
  872. return Reflect.apply(target, _this, args);
  873. }
  874. });
  875. let add = 1 << 1;
  876. Set.prototype.add = new Proxy(Set.prototype.add, {
  877. apply: function (target, _this, args) {
  878. if (args[0] && args[0] instanceof Function) {
  879. let fnStr = args[0].toString();
  880. if (config.menu.noRoundPlay.enable) {
  881. if (fnStr.includes('.Preparing:')) {
  882. const regexp = /(?<left>Preparing:)(?<right>.*?1===(?<mut>\w+)\.round)/s;
  883. const match = fnStr.match(regexp);
  884. if (match !== null) {
  885. fnStr = fnStr.replace(regexp, '$<left>$<mut>.round=0;$<right>');
  886. console.info(...scriptName('屏蔽下播轮播 已加载'));
  887. }
  888. else {
  889. console.error(...scriptName('屏蔽下播轮播失效'), fnStr);
  890. }
  891. add |= 1 << 0;
  892. }
  893. }
  894. else {
  895. add |= 1 << 0;
  896. }
  897. if (args[0].toString() !== fnStr) {
  898. args[0] = str2Fn(fnStr);
  899. }
  900. if (isAllBitsSet(add)) {
  901. Set.prototype.add = target;
  902. }
  903. }
  904. return Reflect.apply(target, _this, args);
  905. }
  906. });
  907. xhook.before((request) => {
  908. if (config.menu.invisible.enable) {
  909. if (request.url.includes('/web-room/v1/index/getInfoByUser')) {
  910. request.url = request.url.replace('not_mock_enter_effect=0', 'not_mock_enter_effect=1');
  911. console.info(...scriptName('隐身入场 已拦截'));
  912. }
  913. }
  914. if (config.menu.noRoundPlay.enable) {
  915. if (request.url.includes('/live/getRoundPlayVideo')) {
  916. request.url = request.url.replace(/room_id=\d+/, 'room_id=');
  917. console.info(...scriptName('屏蔽视频轮播 已拦截'));
  918. }
  919. }
  920. });
  921. xhook.after((request, response) => {
  922. if (request.url.includes('/xlive/app-room/v2/guardTab/topList')) {
  923. response.text = response.text.replace(/"anchor_guard_achieve_level":\d+/, '"anchor_guard_achieve_level":0');
  924. console.info(...scriptName('屏蔽大航海榜单背景图 已拦截'));
  925. }
  926. if (config.menu.noRoundPlay.enable) {
  927. if (request.url.includes('/xlive/web-room/v2/index/getRoomPlayInfo')) {
  928. response.text = response.text.replace('"live_status":2', '"live_status":0');
  929. console.info(...scriptName('屏蔽视频轮播 已拦截'));
  930. }
  931. }
  932. });
  933. if (config.menu.noActivityPlat.enable) {
  934. if (self === top) {
  935. if (location.pathname.startsWith('/blanc')) {
  936. history.replaceState(null, '', location.href.replace(`${location.origin}/blanc`, location.origin));
  937. }
  938. else {
  939. location.href = location.href.replace(location.origin, `${location.origin}/blanc`);
  940. }
  941. }
  942. else {
  943. top?.postMessage(location.origin + location.pathname, 'https://live.bilibili.com');
  944. top?.postMessage(location.origin + location.pathname, 'https://www.bilibili.com');
  945. }
  946. }
  947. document.addEventListener('readystatechange', () => {
  948. if (document.readyState === 'interactive') {
  949. if (W.roomBuffService.mount !== undefined) {
  950. W.roomBuffService.mount = new Proxy(W.roomBuffService.mount, {
  951. apply: function (target, _this, args) {
  952. if (args[0] !== undefined) {
  953. _this.__NORoomSkin_skin = args[0];
  954. if (args[0].id !== 0) {
  955. _this.__NORoomSkin_skin_id = args[0].id;
  956. }
  957. if (_this.__NORoomSkin) {
  958. args[0].id = 0;
  959. args[0] = {};
  960. }
  961. else if (args[0].id === 0 && args[0].start_time !== 0) {
  962. args[0].id = _this.__NORoomSkin_skin_id || 0;
  963. }
  964. }
  965. return Reflect.apply(target, _this, args);
  966. }
  967. });
  968. W.roomBuffService.unmount = new Proxy(W.roomBuffService.unmount, {
  969. apply: function (target, _this, args) {
  970. if (_this.__NORoomSkin_skin !== undefined) {
  971. _this.__NORoomSkin_skin.id = 0;
  972. }
  973. return Reflect.apply(target, _this, args);
  974. }
  975. });
  976. }
  977. }
  978. if (document.readyState === 'complete') {
  979. new NoVIP().Start();
  980. }
  981. });
  982. }
  983. function str2Fn(str) {
  984. const fnReg = str.match(/([^\{]*)\{(.*)\}$/s);
  985. if (fnReg !== null) {
  986. const [, head, body] = fnReg;
  987. const args = head.replaceAll(/function[^\(]*|[\s()=>]/g, '').split(',');
  988. return new Function(...args, body);
  989. }
  990. }
  991. function isAllBitsSet(value) {
  992. if (value === 0) {
  993. return false;
  994. }
  995. return (value & (value + 1)) === 0;
  996. }
  997. function scriptName(name) {
  998. return [
  999. `%c${GM_info.script.name}%c ${name}`,
  1000. "font-weight: bold; color: white; background-color: #FF6699; padding: 1px 4px; border-radius: 4px;",
  1001. "font-weight: bold; color: #FF6699;"
  1002. ];
  1003. }