Greasy Fork is available in English.

斗鱼屏蔽ID消息(blockspeech)

根据ID屏蔽直播间的发言(持久化存储、傻瓜式使用/管理)

  1. // ==UserScript==
  2. // @name 斗鱼屏蔽ID消息(blockspeech)
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0.1
  5. // @description 根据ID屏蔽直播间的发言(持久化存储、傻瓜式使用/管理)
  6. // @author Ginyang
  7. // @match *://www.douyu.com/*
  8. // @icon https://www.douyu.com/favicon.ico
  9. // @run-at document-start
  10. // @grant GM_setValue
  11. // @grant GM_getValue
  12. // @grant GM_registerMenuCommand
  13. // @license MIT
  14. // ==/UserScript==
  15.  
  16. /************************* v1.0.1 更新内容*************************
  17. *
  18. 1、【新增/优化】新增消息提示,现在不用进入控制台查看信息了,直接会在页面上方给出提示消息
  19. 2、【修复】进入直播间可能会因为页面未加载完全导致插件加载失败(现在添加了window.onload判断)
  20. 3、【修复】Firefox中input输入框的宽度超出设置页面
  21. 4、【优化】Firefox中的滚动条样式不起作用(这是火狐的内核导致的,提供的样式美化太少了,我又懒得自己覆盖)
  22. 5、【优化】代码结构优化
  23.  
  24. *************************************************************** */
  25.  
  26. (function () {
  27. 'use strict';
  28. window.onload = function () {
  29. console.log("bs load success");
  30. // 全局变量
  31. var bs_observer = null;
  32. // 定义消息为全局对象
  33. var message = null;
  34.  
  35. function bsStyleInit() {
  36. let bs_style_css = `
  37. /***************************遮罩层overlay、对话框dialog***************************/
  38. #bs_overlay {
  39. display: block;
  40. position: fixed;
  41. top: 0;
  42. left: 0;
  43. width: 100%;
  44. height: 100%;
  45. background-color: rgba(0, 0, 0, 0.5);
  46. z-index: 9999;
  47. }
  48. @keyframes show {
  49. 0% {
  50. transform: rotateX(30deg);
  51. }
  52. 58% {
  53. opacity: 1;
  54. transform: rotateX(-12deg);
  55. }
  56. 100% {
  57. opacity: 1;
  58. }
  59. }
  60. #bs_dialog {
  61. display: block;
  62. position: fixed;
  63. top: 50%;
  64. left: 50%;
  65. margin-top: -160px;
  66. margin-left: -160px;
  67. width: 320px;
  68. height: 330px;
  69. background-color: #fff;
  70. color: #333;
  71. font-size: 16px;
  72. border-radius: 5px;
  73. box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
  74. padding: 20px;
  75. transition: all 0.3s ease-in-out;
  76. /* transform-style: preserve-3d; */
  77. transform-origin: center center;
  78. animation: show 0.3s ease-in-out;
  79. }
  80. #bs_dialog h2{
  81. font-size: 24px;
  82. display: block;
  83. margin-bottom: 12px;
  84. font-weight: bold;
  85. }
  86. /***************************关闭close***************************/
  87. #bs_btn_close {
  88. position: absolute;
  89. top: 20px;
  90. right: 20px;
  91. margin: 3px;
  92. width: 24px;
  93. height: 24px;
  94. background: white;
  95. cursor: pointer;
  96. box-sizing: border-box;
  97. transition: all 0.3s ease-in-out;
  98. }
  99. #bs_btn_close:hover::before,
  100. #bs_btn_close:hover::after {
  101. background: red;
  102. }
  103. #bs_btn_close:before {
  104. position: absolute;
  105. content: '';
  106. width: 1px;
  107. height: 25px;
  108. background: #999;
  109. transform: rotate(45deg);
  110. top: -3px;
  111. left: 11px;
  112. }
  113. #bs_btn_close:after {
  114. content: '';
  115. position: absolute;
  116. width: 1px;
  117. height: 25px;
  118. background: #999;
  119. transform: rotate(-45deg);
  120. top: -3px;
  121. left: 11px;
  122. }
  123. /*************** 表格样式 *************/
  124. #bs_table {
  125. display: block;
  126. width: 300px;
  127. height: 220px;
  128. margin: auto;
  129. border-collapse: collapse;
  130. overflow-y: scroll;
  131. scrollbar-width: thin;
  132. }
  133. #bs_table td {
  134. border-bottom: 1px solid #ddd;
  135. font-size: 16px;
  136. padding: 5px 10px;
  137. }
  138. #bs_table td:first-child {
  139. width: 250px;
  140. text-align: left;
  141. max-width: 250px;
  142. word-break: break-all;
  143. }
  144. #bs_table td:last-child {
  145. width: 50px;
  146. text-align: right;
  147. }
  148. #bs_table tr:last-child td {
  149. border-bottom: none;
  150. }
  151. /* ****************插入行table ******************/
  152. #row_insert {
  153. margin: auto;
  154. margin-bottom: 10px;
  155. }
  156. /*****************滚动条******************/
  157. #bs_dialog ::-webkit-scrollbar {
  158. width: 5px;
  159. height: 10px;
  160. }
  161. #bs_dialog ::-webkit-scrollbar-track {
  162. width: 6px;
  163. background: rgba(#101F1C, 0.1);
  164. -webkit-border-radius: 2em;
  165. -moz-border-radius: 2em;
  166. border-radius: 2em;
  167. }
  168. #bs_dialog ::-webkit-scrollbar-thumb {
  169. background-color: rgba(144, 147, 153, .3);
  170. background-clip: padding-box;
  171. min-height: 28px;
  172. -webkit-border-radius: 2em;
  173. -moz-border-radius: 2em;
  174. border-radius: 2em;
  175. transition: background-color .3s;
  176. cursor: pointer;
  177. }
  178. #bs_dialog ::-webkit-scrollbar-thumb:hover {
  179. background-color: rgba(144, 147, 153, .5);
  180. }
  181. /**************底部按钮*****************/
  182. #bs_tool_row {
  183. width: 300px;
  184. display:flex;
  185. justify-content: center;
  186. align-items: center;
  187. margin: auto;
  188. margin-top: 10px;
  189. }
  190. #bs_tool_row button {
  191. width: 50px;
  192. padding: auto;
  193. margin: 0 5px;
  194. }
  195. #bs_overlay button:focus {
  196. outline: none;
  197. }
  198. #bs_overlay button {
  199. border: none;
  200. background-color: rgba(23, 171, 227, .8);
  201. color: #eee;
  202. border-radius: 5px;
  203. cursor: pointer;
  204. line-height: 28px;
  205. height: 28px;
  206. padding: 0 5px;
  207. }
  208. #bs_overlay button:hover {
  209. background-color: rgba(23, 171, 227, 1);
  210. }
  211. /******************输入框input*******************/
  212. #bs_input {
  213. width: 200px;
  214. border-radius: 5px;
  215. border: 1px solid;
  216. padding-left: 5px;
  217. }
  218. #bs_input:focus {
  219. box-shadow: 0 0 6px #2993e9;
  220. }
  221. /**********************************message_css********************************/
  222. #message-container {
  223. position: fixed;
  224. left: 0;
  225. top: 0;
  226. right: 0;
  227. display: flex;
  228. flex-direction: column;
  229. align-items: center;
  230. z-index: 10000;
  231. pointer-events: none; /* 允许鼠标事件穿透 */
  232. }
  233. #message-container .message {
  234. background: #fff;
  235. margin: 10px 0;
  236. padding: 0 10px;
  237. height: 40px;
  238. box-shadow: 0 0 10px 0 #eee;
  239. font-size: 14px;
  240. border-radius: 3px;
  241. display: flex;
  242. align-items: center;
  243. transition: height 0.2s ease-in-out, margin 0.2s ease-in-out
  244. }
  245. #message-container .message .text {
  246. color: #333;
  247. padding: 0 20px 0 5px
  248. }
  249. #message-container .message .close {
  250. cursor: pointer;
  251. color: #999
  252. }
  253. #message-container .message .icon-info {
  254. color: #0482f8
  255. }
  256. #message-container .message .icon-error {
  257. color: #f83504
  258. }
  259. #message-container .message .icon-success {
  260. color: #06a35a
  261. }
  262. #message-container .message .icon-warning {
  263. color: #ceca07
  264. }
  265. #message-container .message .icon-loading {
  266. color: #0482f8
  267. }
  268. @keyframes message-move-in {
  269. 0% {
  270. opacity: 0;
  271. transform: translateY(-100%)
  272. }
  273. 100% {
  274. opacity: 1;
  275. transform: translateY(0)
  276. }
  277. }
  278. #message-container .message.move-in {
  279. animation: message-move-in 0.3s ease-in-out
  280. }
  281. @keyframes message-move-out {
  282. 0% {
  283. opacity: 1;
  284. transform: translateY(0)
  285. }
  286. 100% {
  287. opacity: 0;
  288. transform: translateY(-100%)
  289. }
  290. }
  291. #message-container .message.move-out {
  292. animation: message-move-out 0.3s ease-in-out;
  293. animation-fill-mode: forwards
  294. }
  295. /************************外部样式***************************/
  296. @font-face {font-family: "icon";
  297. src: url('//at.alicdn.com/t/font_1117508_wxidm5ry7od.eot?t=1554098097778'); /* IE9 */
  298. src: url('//at.alicdn.com/t/font_1117508_wxidm5ry7od.eot?t=1554098097778#iefix') format('embedded-opentype'), /* IE6-IE8 */
  299. url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAASwAAsAAAAACXAAAARhAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDXgqFdIUpATYCJAMcCxAABCAFhD0HXBtNCFGUL0qC7GuMwfUModQ5d89yCS2IguQlYTzox9H5PfHlpWFpEC0ubQKL/QJcYxBBtFZWz/RCiEgotIBChV2EASSXCBX5xofBiPtz7s0nnCJ450fACtP2peU0NJSTZudnZywngIyp2Q4m293vbjhIKItjihNIZS3s4mx2bTEk1jI8gciSLJCwesNvAaBfaMOve98XmEY5wnfz3a39QU5QZFVbdF5i+gcD/3G/4l/jK/C2ASA6KCowynZ8xxew8qJK+MQORO8xnOLqb1h0n09guNkGZLZsHVyQsoTLAnHcdykpB7QygxR6RZuwN8VNUNCXS/jGDev347+CKZoKj7Pj4KZDVn79eUjAP1zXN0C85syQD6JiJSKJ84nWk3JOYKWc4Z+/mbcHMVqvaH/98v5qf3X63Pk8/P8Eq3EwjKZ4qhS9/c/TEi0uHQnEAUm7kDyNSeXXO5Pkt51J8NuJSeH/DpOG/4cyi4p1i0djFD4DsZ8RG81VIUlYY9YzIfBOsZLGd25VXu8jbXf6O5mXeVhHN3GEsYVtgIyXm9AHradeL9uATxQOXxaLRydnjMJbK6L0zDFsY6Jt8bFHRO6RB/FTztgwMNYuFWkR8SNHnjiJz5JGGDLNn6gcgzdXhWUVo8vr46LysuGJDUxSWT6CbcHi0jX9JpvoOrSRi+KG10H6vQ0GY25JMkc36XmPmdEouyf9E33O07RhdBOWZPYHx3yQMC19ZCzZWkLAR35/5xmxDLYu9AqejrN5PTy2lgc0d06bS6k1uX9wWvJPVHBJZp7qnXWdMCJ3gR5WVL//cZmeBgxOanWDbCXDjsu4WzpKRUciLwh3CYkhh/Y3byXAmC7LvYr4Hs70do3p3dBqyD5xPqoJ9l+te2FzLY0T/svpCG1o9hqPZHJ33jEGNEMzFch0HfRQKdIox2/yaAiNEun5XePlysEZfBnivwZ9vTqLA8PGZXIbZLU687F4Dc46J/785DmxDLYu9IYAhKBE8yY+gOzpaGSIjSCNxyPLQad7CAZzBLGO9++/I0k9Awh1e/aHQUxK4TEZBN9kz/LCloUvZiR4c3V9wKqrB3uFwrz7bFSSASAVAeq6fPO1voHgXIHOGjnOkNpw6uHuco731Zx8UuFUQprkQdGZBlWaiySxEBojLIVW2gDDrTB98AgTWHIih2E5wyD0XkAx2iuoeq+RJIagMdkntHrvYbh9/o85wuJo9C6RanTAegbf5glTNbpqx5+hayIqFY7mvUKpfPDA6NCIP7yNCcoqG9SDO6Y1AyZ5DFulL4ZRxCGVPEBbD3lap3PDwyzpjYZsHpOqzSREacgBLM+Az8YlmOHUqpHVZ5DLiFAyQWCZryCIlcKBTDBqyEgGsi1NMut2Fas8cI0JJI0tYiQuBrY6N6OIAhxIk2cFkE0b4lXwpeYM80asqHSoW3m8P4dgODxBFyVqZDTRRhe9PDL/tykpY9uoVCni1PETt2BHXGHpkcokEI9SckkIAAAA') format('woff2'),
  300. url('//at.alicdn.com/t/font_1117508_wxidm5ry7od.woff?t=1554098097778') format('woff'),
  301. url('//at.alicdn.com/t/font_1117508_wxidm5ry7od.ttf?t=1554098097778') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
  302. url('//at.alicdn.com/t/font_1117508_wxidm5ry7od.svg?t=1554098097778#icon') format('svg'); /* iOS 4.1- */
  303. }
  304. .icon {
  305. font-family: "icon" !important;
  306. font-size: 16px;
  307. font-style: normal;
  308. -webkit-font-smoothing: antialiased;
  309. -moz-osx-font-smoothing: grayscale;
  310. }
  311. .icon-info:before {
  312. content: "\\e72a";
  313. }
  314. .icon-success:before {
  315. content: "\\e730";
  316. }
  317. .icon-loading:before {
  318. content: "\\e6a6";
  319. }
  320. .icon-close:before {
  321. content: "\\e6e9";
  322. }
  323. .icon-warning:before {
  324. content: "\\ebad";
  325. }
  326. .icon-error:before {
  327. content: "\\ebb2";
  328. }
  329. `;
  330. let bs_style_sheet = document.createElement('style');
  331. bs_style_sheet.innerHTML = bs_style_css;
  332. document.body.appendChild(bs_style_sheet);
  333. };
  334.  
  335.  
  336.  
  337. /*****************************配置对话框函数*************************/
  338.  
  339. var isButtonClicked = false;
  340. function openConfigDialog() {
  341. if (isButtonClicked) {
  342. return; // 如果按钮已被点击,则忽略后续点击事件
  343. }
  344. isButtonClicked = true;
  345.  
  346. // 创建一个遮罩层及其内部的对话框
  347. const bs_overlay_html = `
  348. <div id="bs_dialog">
  349. <h2 style="margin-top: -4px;">BlockSpeech
  350. <a title="前往插件详情页面查看更多插件信息"style="font-size: 8px; color: #17abe3; text-decoration: underline;"target="_blank"href="https://greasyfork.org/zh-CN/scripts/472846">插件信息</a>
  351. </h2>
  352. <table id="row_insert">
  353. <tr>
  354. <td>ID:</td>
  355. <td><input id="bs_input" type="text" placeholder="请输入需要屏蔽的ID"></td>
  356. <td><button id="bs_btn_insert">添加</button></td>
  357. </tr>
  358. </table>
  359. <table id="bs_table"></table>
  360. <div id="bs_tool_row"><button id="bs_btn_save">保存</button><button id="bs_btn_clear">清空</button></div>
  361. <div id="bs_btn_close"></div>
  362. </div>
  363. `;
  364. let bs_overlay = document.createElement('div');
  365. bs_overlay.id = 'bs_overlay';
  366. bs_overlay.innerHTML = bs_overlay_html;
  367. document.body.appendChild(bs_overlay);
  368.  
  369. /****************************************各个function*****************************************/
  370.  
  371. // 获取全局变量bs_table
  372. let bs_table = document.getElementById('bs_table');
  373.  
  374. // 清空表格
  375. function bs_tableClear() {
  376. bs_table.innerHTML = '';
  377. }
  378.  
  379. // 保存表格到GM_value
  380. function bs_tableSave() {
  381. let bs_list = [];
  382. document.querySelectorAll('.bs_user_id').forEach(function (item) {
  383. bs_list.unshift(item.textContent);
  384. })
  385. GM_setValue("bs_list", bs_list);
  386. console.log("【blockspeech】list:" + bs_list);
  387. console.log("【blockspeech】save ok");
  388. stopObserver();
  389. startObserver(bs_list);
  390. }
  391.  
  392. // 删除行
  393. function bs_deleteRow(btn_del) {
  394. // 获取按钮的父元素<tr>
  395. let row_del = btn_del.parentNode.parentNode;
  396. // 从表格中删除该行
  397. row_del.parentNode.removeChild(row_del);
  398. }
  399.  
  400.  
  401. // 插入行
  402. function bs_insertRow(userid) {
  403. let new_row = bs_table.insertRow(0);
  404. let cell1 = new_row.insertCell(0);
  405. cell1.className = 'bs_user_id';
  406. cell1.innerHTML = userid;
  407. let cell2 = new_row.insertCell(1);
  408. let bs_btn_del = document.createElement("button");
  409. bs_btn_del.innerText = "删除";
  410. // 添加点击事件
  411. bs_btn_del.addEventListener("click", function () {
  412. bs_deleteRow(this);
  413. });
  414. cell2.appendChild(bs_btn_del);
  415. }
  416.  
  417. // 判断input内容
  418. function judgeIfok(new_id) {
  419. if (new_id == '') {
  420. console.log("【blockspeech】user id can not be null");
  421. message.show({
  422. type: 'warning',
  423. text: 'ID内容不能为空',
  424. });
  425. return false;
  426. }
  427. let user_id_objlist = document.querySelectorAll('.bs_user_id');
  428. let user_id_list = [];
  429. user_id_objlist.forEach(function (obj) {
  430. user_id_list.push(obj.innerHTML);
  431. })
  432. if (user_id_list.includes(new_id)) {
  433. console.log("【blockspeech】user id had been added");
  434. message.show({
  435. type: 'info',
  436. text: '此ID已被添加,请勿重复添加',
  437. });
  438. return false;
  439. }
  440. return true;
  441. }
  442.  
  443. bs_Init();
  444.  
  445. // 初始化表格
  446. function bs_Init() {
  447. bs_tableClear();
  448. let bs_list = GM_getValue("bs_list");
  449. if (bs_list) {
  450. bs_list.forEach(function (item) {
  451. bs_insertRow(item);
  452. });
  453. }
  454. }
  455.  
  456.  
  457. /****************************************添加点击事件*****************************************/
  458.  
  459. // 给插入按钮添加点击事件
  460. document.getElementById('bs_btn_insert').addEventListener("click", function () {
  461. let bs_input = document.getElementById('bs_input');
  462. if (judgeIfok(bs_input.value.trim())) {
  463. bs_insertRow(bs_input.value.trim());
  464. message.show({
  465. type: 'success',
  466. text: '添加"' + bs_input.value.trim() + '"成功',
  467. });
  468. }
  469. bs_input.value = '';
  470. });
  471.  
  472. // 给保存按钮添加点击事件
  473. document.getElementById('bs_btn_save').addEventListener("click", () => {
  474. bs_tableSave();
  475. message.show({
  476. type: 'success',
  477. text: '保存成功',
  478. });
  479. });
  480.  
  481. // 给清空按钮添加点击事件
  482. document.getElementById('bs_btn_clear').addEventListener("click", () => {
  483. bs_tableClear();
  484. message.show({
  485. type: 'success',
  486. text: '清空列表成功',
  487. });
  488. });
  489.  
  490. // 给关闭按钮添加点击事件
  491. let bs_btn_close = document.getElementById('bs_btn_close');
  492. bs_btn_close.addEventListener('click', function () {
  493. bs_overlay.remove();
  494. isButtonClicked = false;
  495. });
  496. }
  497.  
  498. function bs_startMonitor() {
  499. // 直播间内执行
  500. if (document.querySelector('.layout-Player-aside')) {// 如果页面有侧边栏则该页面为直播间
  501. console.log("【blockspeech】Is live room.");
  502.  
  503. let bs_list = GM_getValue('bs_list');
  504. if (bs_list) {
  505. setTimeout(() => startObserver(bs_list), 8000);
  506. }
  507. }
  508. }
  509.  
  510. function startObserver(bs_list = GM_getValue('bs_list')) {
  511. let chat_list = document.querySelector("#js-barrage-list");
  512. if (chat_list) {
  513. // 创建 MutationObserver 对象
  514. bs_observer = new MutationObserver(function (mutations) {
  515. // 遍历每个变化
  516. mutations.forEach(function (mutation) {
  517. // 遍历每个被添加的元素
  518. mutation.addedNodes.forEach(function (node) {
  519. // 判断节点元素
  520. if (node.nodeType === 1 && node.nodeName === 'LI' && node.classList.contains('Barrage-listItem')) {
  521. // 获取用户昵称对象
  522. let userchat = node.querySelector(".Barrage-nickName");
  523. // 获取第用户昵称 文本内容
  524. let userid = userchat.title;
  525. if (bs_list.includes(userid)) {
  526. node.remove();
  527. console.log("【blockspeech】remove chat from " + userid + " ok");
  528. }
  529. }
  530. });
  531. });
  532. });
  533. // 配置 MutationObserver 对象
  534. let config = { childList: true, subtree: true };
  535. // 在聊天信息列表上开始监视变化
  536. bs_observer.observe(chat_list, config);
  537. console.log("【blockspeech】strat observer success...");
  538. message.show({
  539. type: 'success',
  540. text: '屏蔽ID消息开启成功',
  541. });
  542. } else {
  543. console.log("【blockspeech】因网络原因屏蔽失败,请刷新页面或者点击设置页面的保存按钮重新加载屏蔽");
  544. }
  545. } // startObserver -- funciton
  546.  
  547. function stopObserver() {
  548. if (bs_observer) {
  549. bs_observer.disconnect();
  550. bs_observer = null;
  551. console.log('【blockspeech】Stop observer ok');
  552. } else {
  553. console.log('【blockspeech】there is no observer task');
  554. }
  555. }
  556.  
  557. /********************************** message_js****************************/
  558. class Message {
  559. constructor() {
  560. const containerId = 'message-container';
  561. this.containerEl = document.getElementById(containerId);
  562. if (!this.containerEl) {
  563. this.containerEl = document.createElement('div');
  564. this.containerEl.id = containerId;
  565. document.body.appendChild(this.containerEl)
  566. }
  567. }
  568. show({
  569. type = 'info',
  570. text = '',
  571. duration = 2000,
  572. closeable = false
  573. }) {
  574. let messageEl = document.createElement('div');
  575. messageEl.className = 'message move-in';
  576. messageEl.innerHTML = `<span class="icon icon-${type}"></span><div class="text">${text}</div>`;
  577. if (closeable) {
  578. let closeEl = document.createElement('div');
  579. closeEl.className = 'close icon icon-close';
  580. messageEl.appendChild(closeEl);
  581. closeEl.addEventListener('click', () => {
  582. this.close(messageEl)
  583. })
  584. }
  585. this.containerEl.appendChild(messageEl);
  586. if (duration > 0) {
  587. setTimeout(() => {
  588. this.close(messageEl)
  589. }, duration)
  590. }
  591. }
  592. close(messageEl) {
  593. messageEl.className = messageEl.className.replace('move-in', '');
  594. messageEl.className += 'move-out';
  595. messageEl.addEventListener('animationend', () => {
  596. messageEl.setAttribute('style', 'height: 0; margin: 0')
  597. });
  598. messageEl.addEventListener('transitionend', () => {
  599. messageEl.remove()
  600. })
  601. }
  602. }
  603.  
  604. // 主函数main
  605. function main() {
  606. // 初始化css
  607. bsStyleInit();
  608.  
  609. // 消息对象实例化
  610. message = new Message();
  611.  
  612. // 添加配置选项
  613. GM_registerMenuCommand('配置屏蔽ID', openConfigDialog);
  614.  
  615. // 启动监听
  616. window.onload = function () {
  617. console.log("【blockspeech】window.onload...");
  618. bs_startMonitor();
  619. }
  620. }
  621.  
  622. // 进入main
  623. main();
  624. } // window.onload
  625. })(); // 主function -- function