CC直播间净化

隐藏CC直播页面中的大部分广告, 并且当直播结束跳转其他直播间时, 自动关闭页面

  1. // ==UserScript==
  2. // @name CC直播间净化
  3. // @description 隐藏CC直播页面中的大部分广告, 并且当直播结束跳转其他直播间时, 自动关闭页面
  4. // @name:en CCLiveClean
  5. // @description:en Hide almost CC live Element.
  6. // @author Yiero
  7. // @version 1.1.0
  8. // @match https://cc.163.com/*
  9. // @match https://act/m/daily/anchor_end_countdown/*
  10. // @grant GM_addStyle
  11. // @grant GM_setValue
  12. // @grant GM_getValue
  13. // @grant GM_deleteValue
  14. // @grant GM_listValues
  15. // @grant GM_registerMenuCommand
  16. // @grant GM_unregisterMenuCommand
  17. // @run-at document-start
  18. // @icon https://cc.163.com/favicon.ico
  19. // @namespace https://github.com/AliubYiero/TamperMonkeyScripts/
  20. // @license GPL
  21. // ==/UserScript==
  22. var __defProp = Object.defineProperty;
  23.  
  24. var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, {
  25. enumerable: true,
  26. configurable: true,
  27. writable: true,
  28. value: value
  29. }) : obj[key] = value;
  30.  
  31. var __publicField = (obj, key, value) => {
  32. __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
  33. return value;
  34. };
  35.  
  36. class Info {
  37. constructor(projectName) {
  38. __publicField(this, "projectName");
  39. __publicField(this, "header");
  40. this.projectName = projectName;
  41. this.header = `[${projectName}]`;
  42. }
  43. log(...msg) {
  44. (() => {})(...this.contentInfo(...msg));
  45. }
  46. info(...msg) {
  47. console.info(...this.contentInfo(...msg));
  48. }
  49. warn(...msg) {
  50. console.warn(...this.contentInfo(...msg));
  51. }
  52. error(...msg) {
  53. console.error(...this.contentInfo(...msg));
  54. }
  55. contentInfo(...msg) {
  56. return [ this.header, `[${(new Date).toLocaleString("zh-ch")}]`, ...msg ];
  57. }
  58. }
  59.  
  60. class CSSRule {
  61. constructor() {
  62. __publicField(this, "cssRuleSet", new Set);
  63. __publicField(this, "styleDom", document.createElement("style"));
  64. }
  65. push(selector, rule) {
  66. let ruleString = "";
  67. for (let ruleKey in rule) {
  68. const ruleValue = rule[ruleKey];
  69. ruleString += `${ruleKey}:${ruleValue};`;
  70. }
  71. this.cssRuleSet.add(`${selector} {${ruleString}}`);
  72. }
  73. pushImportant(selector, rule) {
  74. let ruleString = "";
  75. for (let ruleKey in rule) {
  76. let ruleValue = rule[ruleKey];
  77. if (typeof ruleValue === "string") {
  78. ruleValue = ruleValue.replace("!important", "");
  79. }
  80. ruleString += `${ruleKey}:${ruleValue} !important;`;
  81. }
  82. this.cssRuleSet.add(`${selector} {${ruleString}}`);
  83. }
  84. pushHide(selector) {
  85. this.pushImportant(selector, {
  86. display: "none"
  87. });
  88. }
  89. pushHideList(selectorList) {
  90. selectorList.forEach((selector => {
  91. this.pushImportant(selector, {
  92. display: "none"
  93. });
  94. }));
  95. }
  96. pushList(ruleList) {
  97. ruleList.forEach((({selector: selector, rule: rule}) => {
  98. this.push(selector, rule);
  99. }));
  100. }
  101. pushImportantList(ruleList) {
  102. ruleList.forEach((({selector: selector, rule: rule}) => {
  103. this.pushImportant(selector, rule);
  104. }));
  105. }
  106. submit() {
  107. this.removeAll();
  108. new Info("AddStyle").log(Array.from(this.cssRuleSet).join(" "));
  109. this.styleDom = GM_addStyle(Array.from(this.cssRuleSet).join(" "));
  110. }
  111. removeAll() {
  112. if (this.styleDom) {
  113. this.styleDom.remove();
  114. }
  115. }
  116. }
  117.  
  118. const hideSelectorList = {
  119. main: [ ".ad-ct", "#webChat", "#js-side-nav", ".index-module_container_1pK9d", "::-webkit-scrollbar" ],
  120. headerNav: [ ".menu-location", "#my-follow, #my-record, #download, #menu-be-anchor", "#guard-head-avatar-red-dot-msg, .red-dot" ],
  121. danmuBar: [ "#room-tabs", "#gift-banner", ".gift-simp-banner", ".room-boardcast", ".activity-notify", ".gift_item", ".chat-msg-folder" ],
  122. liveTitle: [ "#achievement, .live-type, .live-guard, .live-fans-badge-diamond, .anchor-friends", "#plugins2374, #plugins9970, #plugins9670, #plugins9977, #plugins9412, #plugins9997, #plugins9089, #plugins6666, #plugins9217, #plugins2511, #plugins1609, #plugins9913, #plugins1016, #plugins14, #plugins5985, #plugins1353, #plugins1, #plugins9321 " ],
  123. live: [ "#recommend-module", "#live_left_bottom_box_wrap", ".video-watermark", "#new-player-banner, #player-banner, #new-player-banner, #mounts_player, #mounts_banner", ".gameH5Theater .user-tool-bar" ]
  124. };
  125.  
  126. const anchor_end_countdownHideSelectorList = {
  127. live: [ ".ui-wrap" ]
  128. };
  129.  
  130. const prefSelectorList = {
  131. main: {
  132. ".room-main-container": {
  133. "margin-top": "20px"
  134. }
  135. },
  136. headerNav: {
  137. ".user-do": {
  138. "margin-right": "50%",
  139. transform: "translateX(50%)"
  140. }
  141. },
  142. live: {
  143. ".page-right-container": {
  144. width: "100%"
  145. },
  146. "#live_player": {
  147. height: "100%"
  148. }
  149. },
  150. danmuBar: {
  151. ".chat-list-short": {
  152. height: "calc(100% - 110px)"
  153. },
  154. "#chat-list-con": {
  155. height: "100%"
  156. }
  157. }
  158. };
  159.  
  160. function addCCNewStyle() {
  161. const cssRule = new CSSRule;
  162. cssRule.pushHideList(Object.values(hideSelectorList).flat());
  163. const transformedPrefSelectorList = Object.entries(Object.values(prefSelectorList).flat().reduce(((result, current) => ({
  164. ...result,
  165. ...current
  166. })))).map((([selector, rule]) => ({
  167. selector: selector,
  168. rule: rule
  169. })));
  170. cssRule.pushImportantList(transformedPrefSelectorList);
  171. cssRule.submit();
  172. }
  173.  
  174. function addCCIframeNewStyle() {
  175. const cssRule = new CSSRule;
  176. cssRule.pushHideList(Object.values(anchor_end_countdownHideSelectorList).flat());
  177. cssRule.submit();
  178. }
  179.  
  180. function freshListenerPushState(callback, s = 1) {
  181. let _pushState = window.history.pushState;
  182. window.history.pushState = function() {
  183. setTimeout(callback, s * 1e3);
  184. return _pushState.apply(this, arguments);
  185. };
  186. }
  187.  
  188. const live = {
  189. id: "",
  190. historyId: ""
  191. };
  192.  
  193. Object.defineProperty(live, "id", {
  194. get() {
  195. const liveIdMatch = document.URL.match(/https:\/\/cc.163.com\/(\d+)/);
  196. if (liveIdMatch && liveIdMatch[1]) {
  197. const liveId = liveIdMatch[1];
  198. sessionStorage.setItem("localLiveId", liveId);
  199. return liveId;
  200. }
  201. return "";
  202. }
  203. });
  204.  
  205. Object.defineProperty(live, "historyId", {
  206. get() {
  207. return sessionStorage.getItem("localLiveId") || "";
  208. }
  209. });
  210.  
  211. function equalLiveId() {
  212. freshListenerPushState((() => {
  213. if (live.historyId !== live.id) {
  214. window.close();
  215. }
  216. }));
  217. }
  218.  
  219. function getElement(parent = document.body, selector, timeoutPerSecond = 0, getElementDelayPerSecond = 0) {
  220. return new Promise((resolve => {
  221. let result = parent.querySelector(selector);
  222. if (result) {
  223. return resolve(result);
  224. }
  225. let timer;
  226. const mutationObserver = window.MutationObserver || window.WebkitMutationObserver || window.MozMutationObserver;
  227. if (mutationObserver) {
  228. const observer = new mutationObserver((mutations => {
  229. for (let mutation of mutations) {
  230. for (let addedNode of mutation.addedNodes) {
  231. if (addedNode instanceof Element) {
  232. result = addedNode.matches(selector) ? addedNode : addedNode.querySelector(selector);
  233. if (result) {
  234. observer.disconnect();
  235. timer && clearTimeout(timer);
  236. setTimeout((() => resolve(result)), getElementDelayPerSecond * 1e3);
  237. }
  238. }
  239. }
  240. }
  241. }));
  242. observer.observe(parent, {
  243. childList: true,
  244. subtree: true
  245. });
  246. if (timeoutPerSecond > 0) {
  247. timer = setTimeout((() => {
  248. observer.disconnect();
  249. return resolve(null);
  250. }), timeoutPerSecond * 1e3);
  251. }
  252. }
  253. }));
  254. }
  255.  
  256. document.querySelector.bind(document);
  257.  
  258. const getEls = document.querySelectorAll.bind(document);
  259.  
  260. async function selectOriginBanSetting() {
  261. await getElement(document.body, ".ban-effect-list", 0, 1);
  262. const banList = getEls(".ban-effect-list > li:not(.selected)");
  263. banList.forEach((banItem => {
  264. banItem.click();
  265. }));
  266. }
  267.  
  268. function isMatchURL(...regExpList) {
  269. const matchResultList = [];
  270. regExpList.forEach((regExp => {
  271. if (typeof regExp === "string") {
  272. regExp = new RegExp(regExp);
  273. }
  274. matchResultList.push(!!document.URL.match(regExp));
  275. }));
  276. return matchResultList.includes(true);
  277. }
  278.  
  279. class EntryBranch {
  280. constructor() {
  281. __publicField(this, "branchList", []);
  282. }
  283. add(condition, callback) {
  284. this.branchList.push([ condition, callback ]);
  285. }
  286. run() {
  287. const entry = this.branchList.find((entry2 => entry2[0]()));
  288. if (entry) {
  289. entry[1]();
  290. }
  291. }
  292. }
  293.  
  294. class GMConfigMenu {
  295. constructor(callback) {
  296. __publicField(this, "menuId", 0);
  297. __publicField(this, "callback");
  298. this.callback = callback;
  299. }
  300. open(title) {
  301. if (this.menuId) {
  302. this.close();
  303. }
  304. this.menuId = GM_registerMenuCommand(title, this.callback);
  305. }
  306. close() {
  307. GM_unregisterMenuCommand(this.menuId);
  308. this.menuId = 0;
  309. }
  310. }
  311.  
  312. class GMStorage {
  313. constructor(key) {
  314. __publicField(this, "key");
  315. this.key = key;
  316. }
  317. set(value) {
  318. dispatchEvent(new CustomEvent("GMStorageUpdate", {
  319. detail: {
  320. newValue: value,
  321. oldValue: this.get(),
  322. target: this.key
  323. }
  324. }));
  325. GM_setValue(this.key, value);
  326. }
  327. get(defaultValue = null) {
  328. return GM_getValue(this.key, defaultValue);
  329. }
  330. remove() {
  331. dispatchEvent(new CustomEvent("GMStorageUpdate", {
  332. detail: {
  333. newValue: null,
  334. oldValue: this.get(),
  335. target: this.key
  336. }
  337. }));
  338. GM_deleteValue(this.key);
  339. }
  340. }
  341.  
  342. class WhiteList extends GMStorage {
  343. constructor() {
  344. super("liveIdWhiteList");
  345. }
  346. get whiteList() {
  347. return this.get([ 361433, 239802416 ]);
  348. }
  349. add(liveId) {
  350. const whiteList2 = this.whiteList;
  351. whiteList2.push(liveId);
  352. this.set(whiteList2);
  353. }
  354. has(liveId) {
  355. return this.whiteList.includes(liveId);
  356. }
  357. delete(liveId) {
  358. this.set(this.whiteList.filter((whiteLiveId => whiteLiveId !== liveId)));
  359. }
  360. }
  361.  
  362. const whiteList = new WhiteList;
  363.  
  364. function disabledNotWhiteListUrl(liveId) {
  365. if (!whiteList.has(liveId)) {
  366. window.close();
  367. return;
  368. }
  369. }
  370.  
  371. function registerConfigBtn(liveId) {
  372. new GMConfigMenu((() => {
  373. const result = prompt(`输入需要添加白名单的直播间的数字Id (网页地址中的数字Id):\n当前白名单:\n[${whiteList.whiteList.join(", ")}]`);
  374. if (result) {
  375. whiteList.add(Number(result));
  376. }
  377. })).open("添加直播间白名单");
  378. new GMConfigMenu((() => {
  379. const result = prompt(`输入需要删除白名单的直播间数字Id(网页地址中的数字Id):\n当前白名单:\n[${whiteList.whiteList.join(", ")}]`, String(liveId || whiteList.whiteList[0] || ""));
  380. if (result) {
  381. whiteList.delete(Number(result));
  382. }
  383. })).open("删除直播间白名单");
  384. }
  385.  
  386. async function mainPageEntry() {
  387. disabledNotWhiteListUrl(Number(live.id));
  388. registerConfigBtn(Number(live.id));
  389. addCCNewStyle();
  390. await selectOriginBanSetting();
  391. equalLiveId();
  392. }
  393.  
  394. function iframeEntry() {
  395. addCCIframeNewStyle();
  396. }
  397.  
  398. (async () => {
  399. const entryBranch = new EntryBranch;
  400. entryBranch.add((() => isMatchURL(/^https?:\/\/cc.163.com\/$/)), registerConfigBtn);
  401. entryBranch.add((() => isMatchURL(/^https?:\/\/cc.163.com\/(\d+)/)), mainPageEntry);
  402. entryBranch.add((() => isMatchURL("act/m/daily/anchor_end_countdown/index.html")), iframeEntry);
  403. entryBranch.run();
  404. })();