Content Filter

Hide not interested content

当前为 2024-05-24 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Content Filter
  3. // @name:ja コンテンツフィルター
  4. // @namespace https://greasyfork.org/en/users/1264733
  5. // @version 2024-05-24
  6. // @description Hide not interested content
  7. // @description:ja 興味のない内容を隠す
  8. // @author LE37
  9. // @license MIT
  10. // @include /^https:\/\/www\.alphapolis\.co\.jp\/(author|novel)\//
  11. // @include /^https:\/\/www\.bing\.com\/search\?/
  12. // @include /^https:\/\/www\.google\.com\/search\?/
  13. // @include /^https:\/\/greasyfork\.org\/[A-z-]+\/(discuss|scripts)/
  14. // @include /^https:\/\/(www\.|)ign\.com\//
  15. // @include /^https:\/\/kakuyomu\.jp\/(genr|pick|rank|rece|user|works\/)/
  16. // @include /^https:\/\/forum\.palemoon\.org\/view(forum|topic)/
  17. // @include /^https:\/\/(mypage|ncode)\.syosetu\.com\/[A-z0-9]+\//
  18. // @include /^https:\/\/yomou\.syosetu\.com\/rank\//
  19. // @include /^https:\/\/novelcom\.syosetu\.com\/impression\//
  20. // @include /^https:\/\/syosetu\.org\/\?mode=r(ank|evi)/
  21. // @include /^https:\/\/syosetu\.org\/(novel|user)\/[0-9]+\//
  22. // @include /^https:\/\/forum\.vivaldi\.net\//
  23. // @include /^https:\/\/social\.vivaldi\.net\//
  24. // @include /^https:\/\/(www\.|)yandex\.com\/search\//
  25. // @exclude https://www.alphapolis.co.jp/novel/ranking/annual
  26. // @exclude https://yomou.syosetu.com/rank/top/
  27. // @grant GM_getValue
  28. // @grant GM_setValue
  29. // @grant GM_registerMenuCommand
  30. // ==/UserScript==
  31.  
  32. (()=>{
  33. 'use strict';
  34. // GM Key
  35. let gMk;
  36. // Script firetime
  37. let cFt = 0;
  38. // MutationObserver targetNode, IntersectionObserver target
  39. let eOn, eIo;
  40. // Author/Novel page
  41. let cAp = false;
  42. // Shadowroot content
  43. let cSr = false;
  44. // Shadowhost, Shadowroot
  45. let eSh , eSr;
  46. // Target nodelist, UserLink, UserID, Tag, Alter
  47. let eNo, eUl, sId, sTg, eAt;
  48.  
  49. // Select mode
  50. let cSv = false;
  51. // Mobile
  52. const rMb = navigator.userAgent.includes("Mobile");
  53. // Current url
  54. const uRi = location.href;
  55. switch (true) {
  56. // Alphapolis
  57. case /^https:\/\/www\.alphapolis\.co\.jp\/.*\/comment/.test(uRi):
  58. gMk = "APS";
  59. eNo = "div.comment";
  60. eUl = "span.name>a";
  61. sId = /detail\/(\d+)$/;
  62. break;
  63. case /^https:\/\/www\.alphapolis\.co\.jp\/novel\/(index|ranki)/.test(uRi):
  64. gMk = "APS";
  65. eNo = "div.section";
  66. eUl = "div.author>a";
  67. sId = /detail\/(\d+)$/;
  68. sTg = "li.tag a";
  69. break;
  70. case /^https:\/\/www\.alphapolis\.co\.jp\/(author|novel\/[0-9]+\/[0-9]+$)/.test(uRi):
  71. gMk = "APS";
  72. cAp = true;
  73. eUl = uRi.includes("author") ? "div.name>h1" : "div.author a";
  74. sId = /detail\/(\d+)$/;
  75. break;
  76. // BingSearch
  77. case /^https:\/\/www\.bing\.com\/search\?/.test(uRi):
  78. gMk = "BNG";
  79. eNo = "li.b_algo";
  80. eUl = null;
  81. eAt = "cite";
  82. sId = /((?<=https:\/\/)[^\/]+)/;
  83. break;
  84. // GoogleSearch
  85. case /^https:\/\/www\.google\.com\/search\?/.test(uRi):
  86. gMk = "GGS";
  87. eNo = "div#rso>div";
  88. eUl = "a[jsname]";
  89. sId = /((?<=https:\/\/)[^\/]+)/;
  90. break;
  91. // Greasyfork
  92. case /^https:\/\/greasyfork\.org\/[A-z-]+\/script/.test(uRi):
  93. gMk = "GFK";
  94. eNo = "li[data-script-id]";
  95. eUl = null;
  96. sId = /(.*)/;
  97. sTg = "dd.script-list-author a";
  98. eAt = "a.script-link";
  99. break;
  100. case /^https:\/\/greasyfork\.org\/[A-z-]+\/discus/.test(uRi):
  101. gMk = "GFK";
  102. eNo = "div.discussion-list-container";
  103. eUl = null;
  104. sId = /(.*)/;
  105. sTg = "a.user-link";
  106. eAt = "a.script-link";
  107. break;
  108. // Hameln
  109. case /^https:\/\/syosetu\.org\/\?mode=rank/.test(uRi):
  110. gMk = "HML";
  111. eNo = rMb ? "div.search_box" : "div.section3";
  112. eUl = null;
  113. sId = /:(.*)/;
  114. sTg = rMb ? 'span[id^="tag_"]' : 'div.all_keyword:nth-child(9) a';
  115. eAt = rMb ? "p:nth-child(2)" : "div.blo_title_sak";
  116. break;
  117. case /^https:\/\/syosetu\.org\/\?mode=revi/.test(uRi):
  118. gMk = "HML";
  119. eNo = rMb ? "div.search_box" : "div.section3";
  120. eUl = null;
  121. sId = /([^\s]+)/;
  122. eAt = rMb ? "h4" : "h3";
  123. break;
  124. case /^https:\/\/syosetu\.org\/novel\/[0-9]+\/$/.test(uRi):
  125. gMk = "HML";
  126. cAp = true;
  127. eNo = rMb ? "div.search_box" : "div.section3";
  128. eUl = null;
  129. sId = /([^/]+)/;
  130. eAt = 'span[itemprop="author"]';
  131. break;
  132. case /^https:\/\/syosetu\.org\/user\/[0-9]+\/$/.test(uRi):
  133. gMk = "HML";
  134. cAp = true;
  135. eNo = rMb ? "div.search_box" : "div.section3";
  136. eUl = null;
  137. sId = /([^/]+)/;
  138. eAt = rMb ? 'h3>a' : 'h3';
  139. break;
  140. // IGN
  141. case /^https:\/\/(www\.|)ign\.com\//.test(uRi):
  142. gMk = "IGN";
  143. cFt = 2;
  144. eOn = "body";
  145. cSr = true;
  146. eNo = "li";
  147. eUl = null;
  148. sId = /(.*)/;
  149. eAt = 'span[data-spot-im-class="message-username"]';
  150. break;
  151. // Kakuyomu
  152. case /^https:\/\/kakuyomu\.jp\/(genr|picku|ranki|recen)/.test(uRi):
  153. gMk = "KYU";
  154. eNo = "div.widget-work";
  155. eUl = "a.widget-workCard-authorLabel";
  156. sId = /users\/(.*)$/;
  157. sTg = "a[itemprop='keywords']";
  158. break;
  159. case /^https:\/\/kakuyomu\.jp\/.*\/comme/.test(uRi):
  160. gMk = "KYU";
  161. cFt = 1;
  162. eNo = rMb ? 'div[class^="NewBox_box__"]>ul>li' : 'ul:nth-child(1) li';
  163. eUl = 'div.partialGiftWidgetActivityName>a';
  164. sId = /users\/(.*)$/;
  165. break;
  166. case /^https:\/\/kakuyomu\.jp\/.*\/episo/.test(uRi):
  167. gMk = "KYU";
  168. cFt = 2;
  169. eOn = "#episodeFooter-cheerComments-panel-mainContents";
  170. eNo = "ul.widget-cheerCommentList li";
  171. eUl = "h5.widget-cheerComment-author a";
  172. sId = /users\/(.*)$/;
  173. break;
  174. case /^https:\/\/kakuyomu\.jp\/(users\/|works\/[0-9]+$)/.test(uRi):
  175. gMk = "KYU";
  176. cFt = 1;
  177. cAp = true;
  178. eUl = uRi.includes("users") ? 'div[class^="HeaderText"]>a' : 'div.partialGiftWidgetActivityName>a';
  179. sId = /users\/(.*)$/;
  180. break;
  181. // Narou
  182. case /^https:\/\/novelcom\.syosetu\.com\/impre/.test(uRi):
  183. gMk = "NUC";
  184. eNo = rMb ? "div.impression" : "div.waku";
  185. eUl = "div.comment_authorbox>div>a";
  186. sId = /\/(\d+)/;
  187. eAt = "div.comment_authorbox>div";
  188. break;
  189. case /^https:\/\/(mypage|ncode)\.syosetu\.com\/[A-z0-9]+\/$/.test(uRi):
  190. gMk = "NRK";
  191. cAp = true;
  192. eUl = uRi.includes("ncode") ? 'div.novel_writername>a' : 'div.p-userheader__username';
  193. sId = /\/(\d+)/;
  194. break;
  195. case /^https:\/\/yomou\.syosetu\.com\/rank\//.test(uRi):
  196. gMk = "NRK";
  197. eNo = "div.p-ranklist-item";
  198. eUl = "div.p-ranklist-item__author a";
  199. sId = /\/(\d+)/;
  200. sTg = "div.p-ranklist-item__keyword a";
  201. break;
  202. // PalemoonForum
  203. case /^https:\/\/forum\.palemoon\.org\/viewtopic/.test(uRi):
  204. gMk = "PMF";
  205. eNo = "#page-body div.post";
  206. eUl = 'a[class^="username"]';
  207. sId = /u=(\d+)/;
  208. break;
  209. case /^https:\/\/forum\.palemoon\.org\/viewforum/.test(uRi):
  210. gMk = "PMF";
  211. eNo = "ul.topiclist>li";
  212. eUl = "div.topic-poster>a";
  213. sId = /u=(\d+)/;
  214. break;
  215. // VivaldiForm
  216. case /^https:\/\/forum\.vivaldi\.net\//.test(uRi):
  217. gMk = "VVF";
  218. cFt = 2;
  219. sId = /user\/(.*)/;
  220. break;
  221. // VivaldiSocial
  222. case /^https:\/\/social\.vivaldi\.net\//.test(uRi):
  223. gMk = "VVS";
  224. cFt = 2;
  225. eOn = "body";
  226. eIo = ".load-more";
  227. eNo = "div.item-list>article";
  228. eUl = null;
  229. sId = /(.*)/;
  230. eAt = "strong.display-name__html";
  231. break;
  232. // Yandex
  233. case /^https:\/\/(www\.|)yandex\.com\//.test(uRi):
  234. gMk = "YDX";
  235. eNo = rMb ? "div.serp-item" : "#search-result>li";
  236. eUl = rMb ? null : "div.Path>a.Link";
  237. sId = rMb ? /(.*)/ : /\/([^\/]+)/;
  238. eAt = "span.Path-Item>b";
  239. break;
  240. }
  241. //console.log( {gMk, cFt, eOn, eIo, cAp, cSr, eNo, eUl, sId, sTg, eAt} );
  242.  
  243. // GM Menu
  244. GM_registerMenuCommand("View", SVM);
  245. GM_registerMenuCommand("Sort", UST);
  246. // Read List
  247. const URD = GM_getValue(gMk);
  248. let tlo = URD ? URD : { BAL:[], BTL:[] };
  249. const tal = tlo.BAL;
  250. const ttl = tlo.BTL;
  251. // Save List
  252. function USV() {
  253. tlo = { BAL:tal, BTL:ttl };
  254. GM_setValue(gMk, tlo);
  255. }
  256. // Sort List
  257. function UST() {
  258. tal.sort();
  259. ttl.sort();
  260. USV();
  261. }
  262.  
  263. const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
  264. // Script Fire Time
  265. switch (cFt) {
  266. case 1:
  267. // Delay
  268. wait(1500).then(() => FMD());
  269. break;
  270. case 2:
  271. // MutationObserver
  272. MOC();
  273. break;
  274. case 3:
  275. // IntersectionObserver
  276. IOC();
  277. break;
  278. case 0:
  279. default:
  280. // Normal
  281. FMD();
  282. }
  283. // MutationObserver
  284. function MOC() {
  285. switch (gMk) {
  286. case "KYU": {
  287. // Comments in Episode
  288. // Button load comments
  289. const bLc = document.querySelector('#episodeFooter-action-cheerCommentsButton');
  290. bLc.addEventListener("click", (e) => {
  291. waitForElement(eNo, 2000).then(() => { FMD(); });
  292. });
  293. break;
  294. }
  295. case "IGN":
  296. case "VVF": {
  297. let pUi = "";
  298. const observer = new MutationObserver(mutations => {
  299. if (location.href !== pUi) {
  300. pUi = location.href;
  301. if (location.pathname.startsWith("/articles")) {
  302. wait(1000).then(() => IGN());
  303. } else if (location.pathname.startsWith("/topic")) {
  304. eNo = "ul.posts>li";
  305. eUl = "small.d-flex a";
  306. FMD();
  307. } else if (location.pathname.startsWith("/category")) {
  308. eNo = "ul.topic-list li";
  309. eUl = "small.hidden-xs>a";
  310. FMD();
  311. }
  312. }
  313. });
  314. observer.observe(document, { childList: true, subtree: true });
  315. break;
  316. }
  317. default:
  318. waitForElement(eNo, 2000).then(() => {
  319. FMD();
  320. // Vivaldi Social
  321. if (gMk === "VVS") wait(1500).then(() => IOC());
  322. });
  323. }
  324. }
  325. function IGN() {
  326. eSh = eSr = undefined;
  327. waitForElement(eNo, 5000).then(() => {
  328. FMD();
  329. if (eSr) {
  330. // Button load more messages
  331. const bLm = eSr.querySelector('.spcv_load-more-messages');
  332. if (bLm) {
  333. bLm.addEventListener("click", (e) => {
  334. wait(2000).then(() => FMD());
  335. });
  336. }
  337. }
  338. }).catch((error) => {
  339. //console.log(error);
  340. FMD();
  341. });
  342. }
  343. // Wait for an element to be loaded
  344. function waitForElement(selector, timeout) {
  345. return new Promise((resolve, reject) => {
  346. var timer = false;
  347. // Spot im shadownode
  348. if (cSr) {
  349. if (eSr && eSr.querySelectorAll(selector).length) return resolve();
  350. } else {
  351. if (document.querySelectorAll(selector).length) return resolve();
  352. }
  353. const obs = new MutationObserver(mutations => {
  354. // Spot im shadownode
  355. if (cSr) {
  356. eSh = document.querySelector('div[data-spotim-module]').firstElementChild;
  357. if (eSh) {
  358. eSr = eSh.shadowRoot;
  359. if (eSr && eSr.querySelectorAll(selector).length) {
  360. obs.disconnect();
  361. if (timer !== false) clearTimeout(timer);
  362. return resolve();
  363. }
  364. }
  365. } else {
  366. if (document.querySelectorAll(selector).length) {
  367. obs.disconnect();
  368. if (timer !== false) clearTimeout(timer);
  369. return resolve();
  370. }
  371. }
  372. });
  373. // Spot im shadownode
  374. const obn = eSr ? eSr.querySelectorAll('.spcv_conversation')[0] : document.querySelector(eOn);
  375. obs.observe(obn, { childList: true, subtree: true });
  376. if (timeout) {
  377. timer = setTimeout(() => {
  378. obs.disconnect();
  379. reject();
  380. }, timeout);
  381. }
  382. });
  383. }
  384. // IntersectionObserver
  385. function IOC() {
  386. const ioc = new IntersectionObserver((entries) => {
  387. if (entries[0].intersectionRatio <= 0) return;
  388. ioc.disconnect();
  389. // Vivaldi Social
  390. if (gMk === "VVS") wait(1500).then(() => MOC());
  391. });
  392. ioc.observe(document.querySelector(eIo));
  393. }
  394.  
  395. // Filtering mode
  396. function FMD() {
  397. if (cAp) {
  398. // Filtering single target
  399. FST();
  400. } else {
  401. // Filtering multiple targets
  402. FMT();
  403. }
  404. if (!document.getElementById("cFbtn")) {
  405. CFB();
  406. }
  407. }
  408. // Filtering single target
  409. function FST() {
  410. let rBk = false;
  411. const eLk = eUl ? document.querySelector(eUl) : document.querySelector(eAt);
  412. let uId;
  413. // Narou author page fix
  414. if (gMk === "NRK") {
  415. uId = eLk.href ? eLk.href.match(sId)[1] : uRi.match(sId)[1];
  416. } else {
  417. uId = eUl ? eLk.href.match(sId)[1] : eLk.textContent.match(sId)[1];
  418. }
  419. rBk = CHK(eLk, "a", tal, uId);
  420. eLk.style.color = rBk ? "fuchsia" : "dodgerblue";
  421. }
  422. // Filtering multiple targets
  423. function FMT() {
  424. const no = eSr ? eSr.querySelectorAll(eNo) : document.querySelectorAll(eNo);
  425. for (let i = 0; i < no.length; i++) {
  426. let rBk = false;
  427. let uId;
  428. // Filtering content contain single id(link) or text
  429. let eLk = eUl ? no[i].querySelector(eUl) : no[i].querySelector(eAt);
  430. if (eLk !== null || gMk === "NUC" || gMk === "VVS") {
  431. // Narou nologin user fix
  432. // Vivaldi Social element out of view fix
  433. if (!eLk) {
  434. eLk = gMk === "VVS" ? no[i].querySelector("span") : no[i].querySelector(eAt);
  435. uId = gMk === "VVS" ? eLk.textContent.match(sId)[1].replace(/\s/g,'') : eLk.textContent.split("\n")[2];
  436. } else {
  437. uId = eUl ? eLk.href.match(sId)[1] : eLk.textContent.match(sId)[1];
  438. // Vivaldi Social id fix for foreground & background
  439. if (gMk ==="VVS") {
  440. let emoji = "";
  441. const ino = no[i].querySelectorAll(eAt + " img");
  442. if (ino) {
  443. for (let k = 0; k < ino.length; k++) {
  444. emoji += ino[k].alt;
  445. }
  446. }
  447. uId = eLk.textContent.match(sId)[1] + emoji;
  448. uId = uId.replace(/\s/g,'');
  449. }
  450. }
  451. //console.log(uId);
  452. // Vivaldi Social choose the unchanged class as target
  453. rBk = gMk === "VVS" ? CHK(no[i].querySelector("div.status__wrapper"), "a", tal, uId) : CHK(eLk, "a", tal, uId);
  454. }
  455. const eNes = no[i].nextElementSibling;
  456. if (sTg && !rBk) {
  457. // Filtering content contain multiple tags(text)
  458. // Tag node
  459. let tno;
  460. // Hameln mobile origin tag, custom tag
  461. let tot, tct;
  462. if (gMk === "HML" && rMb) {
  463. tot = no[i].querySelector(".trigger p:nth-child(4)");
  464. tct = no[i].querySelector(sTg);
  465. if (!tct) {
  466. tno = tot.textContent.slice(3).match(/[^\s]+/g);
  467. tot.innerHTML = "";
  468. } else {
  469. tno = no[i].querySelectorAll(sTg);
  470. }
  471. } else {
  472. tno = no[i].querySelectorAll(sTg);
  473. }
  474. for (let j = 0; j < tno.length; j++) {
  475. let tag;
  476. if (tot && !tct) {
  477. tag = tno[j];
  478. tot.innerHTML += '<span id="tag_' + j + '">' + tag + '</span>';
  479. } else {
  480. // Greasyfork fix
  481. tag = gMk === "GFK" ? tno[j].href.match(/s\/(\d+)/)[1] : tno[j].textContent;
  482. }
  483. //console.log(tag);
  484. rBk = tot && !tct ? CHK(no[i].querySelector("span#tag_"+j), "t", ttl, tag) : CHK(tno[j], "t", ttl, tag);
  485. if (rBk) break;
  486. }
  487. }
  488. // Blocked Show Type
  489. if (!cSv) {
  490. no[i].style.display = rBk ? "none" : "";
  491. no[i].style.opacity = "1";
  492. } else {
  493. no[i].style.display = "";
  494. no[i].style.opacity = rBk ? "0.5" : "1";
  495. }
  496. }
  497. }
  498. // CheckKeyword
  499. function CHK(ele, n, l, s) {
  500. const result = gMk === "GFK" && n === "a" ? l.some((v) => (new RegExp(v, "i")).test(s)) : l.some((v) => s === v);
  501. if (!ele.getAttribute("data-lkw")) {
  502. ele.setAttribute("data-lkw", n + s);
  503. }
  504. if (cSv) {
  505. ele.style.border = result ? "thin solid fuchsia" : "thin solid dodgerblue";
  506. } else {
  507. ele.style.border = "none";
  508. }
  509. return result;
  510. }
  511.  
  512. // Select mode
  513. function SVM() {
  514. const cButton = document.getElementById("cFbtn");
  515. if (!cSv) {
  516. cSv = true;
  517. cButton.textContent = "📙";
  518. document.addEventListener("click", PAC, true);
  519. } else {
  520. cSv = false;
  521. cButton.textContent = "📘";
  522. document.removeEventListener("click", PAC, true);
  523. // Auto save list
  524. USV();
  525. }
  526. FMD();
  527. }
  528. // PreventAnchorChange
  529. function PAC(e) {
  530. e.preventDefault();
  531. const targetElement = cSr ? e.composedPath()[0] : e.target;
  532. // Vivaldi Forum & Social fix
  533. if (gMk === "VVF" || gMk === "VVS") {
  534. e.stopPropagation();
  535. // StopPropagation fix
  536. if (targetElement.closest("#cFbtn")) SVM();
  537. }
  538. const tEle = targetElement.getAttribute("data-lkw") ? targetElement
  539. : targetElement.parentElement.getAttribute("data-lkw") ? targetElement.parentElement
  540. : null;
  541. //console.log( {tEle, tEle.getAttribute("data-lkw")} );
  542. if (tEle) {
  543. const tda = tEle.getAttribute("data-lkw");
  544. const tlst = tda.slice(0, 1) === "a" ? tal : ttl;
  545. let tid = tda.slice(1);
  546. if (gMk === "GFK" && tda.slice(0, 1) === "a") {
  547. const t = prompt("Enter or use the first 5 letters as keyword", tid);
  548. tid = t !== null ? t : tid.slice(0, 5);
  549. //console.log(tid);
  550. }
  551. const li = tlst.findIndex((v) => v === tid);
  552. if (li !== -1) {
  553. tlst.splice(li,1);
  554. } else {
  555. tlst.push(tid);
  556. }
  557. FMD();
  558. }
  559. // IGN Popup temp fix
  560. if (gMk === "IGN") {
  561. wait(500).then(() => {
  562. const bsh = document.body.lastChild.shadowRoot;
  563. if (bsh) {
  564. const bsr = bsh.querySelector('button[title="Close the modal"]');
  565. if (bsr) bsr.click();
  566. }
  567. });
  568. }
  569. return false;
  570. }
  571.  
  572. // Create Float Button
  573. function CFB() {
  574. const cButton = document.body.appendChild(document.createElement("button"));
  575. // Button Style
  576. cButton.id = "cFbtn";
  577. cButton.textContent = "📘";
  578. cButton.style = "position: fixed; bottom: 20%; right: 10%; width: 44px; height: 44px; z-index: 9999; font-size: 200%; opacity: 50%; cursor:pointer; border: none; padding: unset;";
  579. cButton.type = "button";
  580. cButton.addEventListener("click", (e) => { SVM(); });
  581. }
  582. })();