Bitchute - Extras

Adds extra functionality to Bitchute, such as: mark watched videos and allow to block comments based on username and content.

  1. // ==UserScript==
  2. // @name Bitchute - Extras
  3. // @author "Dilxe"
  4. // @namespace https://github.com/Dilxe/
  5. // @version 0.91.1
  6. // @icon https://i.imgur.com/iarBFdu.png
  7. // @description Adds extra functionality to Bitchute, such as: mark watched videos and allow to block comments based on username and content.
  8. // @match https://www.bitchute.com/*
  9. // @grant GM.getValue
  10. // @grant GM.setValue
  11. // @grant GM.deleteValue
  12. // @grant GM.getResourceUrl
  13. // @grant GM_addStyle
  14. // @require http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js
  15. // @require https://gf.qytechs.cn/scripts/383527-wait-for-key-elements/code/Wait_for_key_elements.js?version=701631
  16. // @run-at document-idle
  17. // ==/UserScript==
  18.  
  19. // Others //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  20. // For Tampermonkey https://github.com/Tampermonkey/tampermonkey/issues/848#issuecomment-569205508
  21. /* globals, jQuery, $, waitForKeyElements */
  22.  
  23. // Mutation Observer ------------------------------------------------------------------------------------------------------------------------------
  24. //// Dynamically detects changes to the HTML. It's used here to, when the user scrolls through the videos list, mark the older videos as they load.
  25. //// Source: https://stackoverflow.com/a/11546242
  26.  
  27. function detectMutation()
  28. {
  29. if (document.getElementById("mutation-detector") == null)
  30. {
  31. MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
  32.  
  33. if(!document.URL.includes('video'))
  34. {
  35. var observer = new MutationObserver(
  36. function(mutations, observer)
  37. {
  38. // fired when a mutation occurs
  39. //console.log(mutations, observer);
  40. markWatchedVideos();
  41. // ...
  42. });
  43.  
  44. var configMain = { attributes: false, childList: true, characterData: false, subtree: false };
  45.  
  46. // define what element should be observed by the observer
  47. // and what types of mutations trigger the callback
  48.  
  49. if (document.URL.includes('channel') == true) { observer.observe(document.getElementsByClassName("channel-videos-list")[0], configMain); }
  50. else if (document.URL.length <= 25) { observer.observe(document.getElementsByClassName("row auto-clear")[1], configMain); }
  51. }
  52.  
  53.  
  54. //Day-Night theme change detector
  55.  
  56. var observerTheme = new MutationObserver(
  57. function(mutations, observerTheme)
  58. {
  59. if (document.getElementById("night-theme").classList == "user-link")
  60. {
  61. document.getElementById("btn-debug").setAttribute("style","position:absolute; top:29%; right:25%; background-color:#dee0dd; color:#4d4b4e; font-size: 0.9vw; width: 12vw; height: 1.9vw; outline: #d2d2d2 outset 2px;");
  62. document.getElementById("txt-amount-removed-comments").setAttribute("style","display:block; position:absolute; bottom:1%; right:0.5%; width:49.3%; height:88%; overflow:auto; background-color:#dee0dd; color:#4d4b4e; border:4px solid #4d4b4e; text-align:center; z-index:inherit;");
  63. document.getElementById("div-amount-removed-comments").setAttribute("style","position:absolute; top:1%; right:0.5%; background-color:#dee0dd; color:#4d4b4e; font-size:1vw; font-weight:bold; width:49.3%; height:11%; border:4px solid #4d4b4e; line-height:275%;");
  64. document.getElementById("div-debug").setAttribute("style","display:none; position:absolute; top:69%; left:19.9%; width:60.1vw; height:30vw; background-color:#dee0dd; color:#4d4b4e; text-align:center; z-index:inherit;");
  65. document.getElementById("txt-debug-list").setAttribute("style","display:block; position:absolute; bottom:-1%; left:-1%; width:102%; height:92%; overflow:auto; background-color:#dee0dd; color:#4d4b4e; border:4px solid #4d4b4e; text-align:center; z-index:inherit;");
  66. document.getElementById("div-debug-editable-list").setAttribute("style","display:block; position:absolute; top:1%; left:0.5%; width:49.3%; height:98%; background-color:#dee0dd; color:#4d4b4e; border:4px solid #4d4b4e; text-align:center; z-index:inherit;");
  67. document.getElementById("btn-save-list").setAttribute("style","position:absolute; top:1.3%; left:61.5%; background-color:#dee0dd; color:#4d4b4e; font-size:0.9vw; width:11vw; height:1.9vw; outline: #d2d2d2 outset 2px;");
  68. document.getElementById("div-marked-videos-title").setAttribute("style","position:absolute; top:-1%; left:-1%; background-color:#dee0dd; color:#4d4b4e; font-size:0.9vw; font-weight:bold; line-height:275%; border:4px solid #4d4b4e; width:61.7%;");
  69. for (commentNum = 0; commentNum < document.getElementsByClassName('comment-wrapper').length; commentNum++)
  70. {
  71. document.getElementById("menu_" + commentNum).setAttribute("style","display:inherit; position:relative; background-color:rgb(238, 238, 238); text-align:center; font-size:.9em; line-height:inherit; opacity:0.75; border: outset; margin: auto; width: 85px; border-radius: 15px;");
  72. }
  73. }
  74. else
  75. {
  76. document.getElementById("btn-debug").setAttribute("style","position:absolute; top:29%; right:25%; background-color:#211f22; color:#908f90; font-size: 0.9vw; width: 12vw; height: 1.9vw; outline: none;");
  77. document.getElementById("txt-amount-removed-comments").setAttribute("style","display:block; position:absolute; bottom:1%; right:0.5%; width:49.3%; height:88%; overflow:auto; background-color:#211f22; color:#908f90; border:4px solid white; text-align:center; z-index:inherit;");
  78. document.getElementById("div-amount-removed-comments").setAttribute("style","position:absolute; top:1%; right:0.5%; background-color:#211f22; color:#908f90; font-size:1vw; font-weight:bold; width:49.3%; height:11%; border:4px solid white; line-height:275%;");
  79. document.getElementById("div-debug").setAttribute("style","display:none; position:absolute; top:69%; left:19.9%; width:60.1vw; height:30vw; background-color:#211f22; color:#908f90; text-align:center; z-index:inherit;");
  80. document.getElementById("txt-debug-list").setAttribute("style","display:block; position:absolute; bottom:-1%; left:-1%; width:102%; height:92%; overflow:auto; background-color:#211f22; color:#908f90; border:4px solid white; text-align:center; z-index:inherit;");
  81. document.getElementById("div-debug-editable-list").setAttribute("style","display:block; position:absolute; top:1%; left:0.5%; width:49.3%; height:98%; background-color:#211f22; color:#908f90; border:4px solid white; text-align:center; z-index:inherit;");
  82. document.getElementById("btn-save-list").setAttribute("style","position:absolute; top:1.3%; left:61.5%; background-color:#211f22; color:#908f90; font-size:0.9vw; width:11vw; height:1.9vw; outline: none;");
  83. document.getElementById("div-marked-videos-title").setAttribute("style","position:absolute; top:-1%; left:-1%; background-color:#211f22; color:#908f90; font-size:0.9vw; font-weight:bold; line-height:275%; border:4px solid white; width:61.7%;");
  84. for (commentNum = 0; commentNum < document.getElementsByClassName('comment-wrapper').length; commentNum++)
  85. {
  86. document.getElementById("menu_" + commentNum).setAttribute("style","display:inherit; position:relative; background-color:rgb(23, 23, 23); text-align:center; font-size:.9em; line-height:inherit; opacity:0.5; border: outset; margin: auto; width: 85px; border-radius: 15px;");
  87. }
  88. }
  89. }
  90. );
  91.  
  92.  
  93. var configTheme = { attributes: true, childList: false, characterData: false, subtree: false };
  94.  
  95. observerTheme.observe(document.getElementById("night-theme"), configTheme);
  96. }
  97. elementForDataDisplay("","div","mutation-detector","display:none;",'nav-top-menu');
  98. }
  99.  
  100.  
  101.  
  102.  
  103.  
  104.  
  105. // Detect URL Change ---------------------------------------------------------
  106. //// This is to avoid having to refresh the page (F5) or open in a new window.
  107. /*--- Note, gmMain () will fire under all these conditions:
  108. 1) The page initially loads or does an HTML reload (F5, etc.).
  109. 2) The scheme, host, or port change. These all cause the browser to
  110. load a fresh page.
  111. 3) AJAX changes the URL (even if it does not trigger a new HTML load).
  112. Source: https://stackoverflow.com/a/18997637
  113. */
  114. var fireOnHashChangesToo = true;
  115. var pageURLCheckTimer = setInterval (
  116. function ()
  117. {
  118. if (this.lastPathStr !== location.pathname || this.lastQueryStr !== location.search || (fireOnHashChangesToo && this.lastHashStr !== location.hash))
  119. {
  120. this.lastPathStr = location.pathname;
  121. this.lastQueryStr = location.search;
  122. this.lastHashStr = location.hash;
  123. // [For Debugging] - If the message (of the amount of removed comments) exists, it'll be removed.
  124. if(document.getElementById('div-debug') != null)
  125. {
  126. document.getElementById('div-debug').remove();
  127. document.getElementById('btn-debug').remove();
  128. }
  129. window.addEventListener ("hashchange", gmMain, false);
  130. document.addEventListener("visibilitychange", gmMain); //https://stackoverflow.com/questions/1760250/how-to-tell-if-browser-tab-is-active#comment111113309_1760250
  131. gmMain();
  132. }
  133. } , 111);
  134.  
  135.  
  136.  
  137.  
  138. function gmMain()
  139. {
  140. if (document.getElementById("loader-container").style.display == "none")
  141. {
  142. if(document.URL.includes('video') == true)
  143. {
  144. /* waitForKeyElements() - Needs jQuery.
  145. It's used for the same reasons one would use setTimeout, while being more exact due to only running the code after the chosen elements are loaded.
  146. Source: https://stackoverflow.com/a/17385193 // https://stackoverflow.com/questions/16290039/script-stops-working-after-first-run */
  147.  
  148.  
  149. waitForKeyElements ( "#comment-list", filterComments );
  150. waitForKeyElements ( ".sidebar-video", markWatchedVideos() );
  151. }
  152.  
  153. else if(document.URL.includes('channel') == true)
  154. {
  155. waitForKeyElements ( "#channel-videos", markWatchedVideos() );
  156. }
  157.  
  158. else
  159. {
  160. detectMutation();
  161. waitForKeyElements ( ".row.auto-clear", markWatchedVideos() );
  162. }
  163. }
  164. else
  165. {
  166. setTimeout(gmMain, 1000);
  167. }
  168. }
  169.  
  170.  
  171.  
  172.  
  173.  
  174.  
  175. // Main //
  176.  
  177. // Mark Watched Videos ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  178.  
  179. async function markWatchedVideos()
  180. {
  181. let allVideos = []; // Creates an array to put all the videos on the page. Needed because channel videos have different classes.
  182. const globalVideos = document.getElementsByClassName('video-card'); // Fetches the videos from the element
  183. const channelVideos = document.getElementsByClassName('channel-videos-image-container'); // Same as above but for the channels
  184. Array.prototype.push.apply(allVideos, Array.from(globalVideos)); // Add the videos to the array
  185. Array.prototype.push.apply(allVideos, Array.from(channelVideos)); // Same as above but for the channels
  186. let totalNumVideos = allVideos.length; // Counts the total nº of videos
  187. let watchedVideos = await GM.getValue("videoHREF"); // Loads the list of watched videos
  188.  
  189. // If it's the first time using the script it would get an undefined error without this.
  190. if (! watchedVideos)
  191. {
  192. await GM.setValue("videoHREF", "");
  193. }
  194. // Checks the video count. If bigger than X threshold, remove an older video.
  195. if (watchedVideos.split("|").length > 4000)
  196. {
  197. let olderVideoRemoved = watchedVideos.replace("|" + watchedVideos.split("|")[1],"");
  198. await GM.setValue("videoHREF", olderVideoRemoved);
  199. }
  200.  
  201. // Checks if it's a video page, has been watched and, therefore on GM(GreaseMonkey)'s list. If it's not, gets added.
  202. if (document.URL.includes('video') == true && watchedVideos.indexOf(document.baseURI.split("/")[4]) == -1)
  203. {
  204. let markCurrentVideo = watchedVideos + '|' + document.baseURI.split("/")[4];
  205. await GM.setValue("videoHREF", markCurrentVideo);
  206. }
  207.  
  208.  
  209.  
  210. // Checks video by video whether they're watched, if so, marks.
  211. for (videoNum = 0; videoNum < totalNumVideos; videoNum++)
  212. {
  213. // If it's in the list, add CSS
  214. if (watchedVideos.match(allVideos[videoNum].children[0].pathname.slice(7,allVideos[videoNum].children[0].pathname.length-1)) != null)
  215. {
  216. const div = document.createElement('thumbnailOverlay');
  217. div.style.background = '#2c2a2d';
  218. div.style.borderRadius = '2px';
  219. div.style.color = '#908f90';
  220. div.innerHTML = 'WATCHED';
  221. div.style.fontSize = '11px';
  222. div.style.right = '4px';
  223. div.style.padding = '3px 4px 3px 4px';
  224. div.style.position = 'absolute';
  225. div.style.top = '4px';
  226. div.style.fontFamily = 'Roboto, Arial, sans-serif';
  227.  
  228.  
  229. if (allVideos[videoNum].className == "video-card")
  230. {
  231. document.getElementsByClassName('video-card-image')[videoNum].style.opacity = '0.25';
  232. }
  233.  
  234. else if (allVideos[videoNum].className == "channel-videos-image-container")
  235. {
  236. document.getElementsByClassName('channel-videos-image')[videoNum-globalVideos.length].style.opacity = '0.25';
  237. }
  238.  
  239. allVideos[videoNum].appendChild(div);
  240. }
  241. }
  242. }
  243.  
  244. // Mark Watched Videos - END //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  245.  
  246.  
  247.  
  248.  
  249.  
  250.  
  251. // Filter Comments by Word ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  252.  
  253. async function filterComments()
  254. {
  255. if(document.getElementById('div-amount-removed-comments') == null && document.getElementById("menu_0") == null)
  256. {
  257. const comment = document.getElementsByClassName('comment-wrapper'); // Fetches the comments from the element
  258. const totalNumComments = comment.length; // Counts nº of comments
  259. let regexWholeWord = /\b(example2|example1)\b/gi;
  260. let regexCombLetters = /(example word|\[comment removed\]|W­w­w|3vvs)/gi;
  261. let userWhiteList = /(exampleUser4|exampleUser3)/g;
  262. let userBlackList = await GM.getValue("blackList"); // Loads the list of watched videos
  263.  
  264. let themeColor; let themeTxtClr; let thmBorderClr; let thmBtnOutline;
  265. let borderTopRemoved = false;
  266.  
  267. // [For Debugging] //////////////////////////////////////////////////////////////////////////////////////////
  268. let allComments = ""; // Variable where the removed comments are stored.
  269. let nRemoved = 0; // Counts removed comments.
  270.  
  271.  
  272.  
  273. if(document.getElementById("night-theme").classList == "user-link")
  274. {
  275. themeColor = "#dee0dd"; themeTxtClr = "#4d4b4e"; thmBorderClr = "#4d4b4e"; thmBtnOutline = "outline: #d2d2d2 outset 2px;";
  276. createDebug("blackList",userBlackList,"Blacklist: ", " Users", themeColor, themeTxtClr, thmBorderClr, thmBtnOutline);
  277. }
  278.  
  279. else
  280. {
  281. themeColor = "#211f22"; themeTxtClr = "#908f90"; thmBorderClr = "white"; thmBtnOutline = "outline: none;";
  282. createDebug("blackList",userBlackList,"Blacklist: ", " Users", themeColor, themeTxtClr, thmBorderClr, thmBtnOutline);
  283. }
  284.  
  285.  
  286.  
  287. // If it's the first time using the script it would get an undefined error without this.
  288. if (document.getElementById("txt-debug-list").value == "undefined")
  289. {
  290. await GM.setValue("blackList", "/(exampleUser1)/g");
  291. alert("Script initialized. Please refresh page.");
  292. }
  293.  
  294. let parsedBlacklist = new RegExp(document.getElementById("txt-debug-list").value.substr(1).slice(0,-2),"g");
  295. // Checks comment by comment whether they contain any word from the regex lists; if they do those comments'll be removed.
  296. for (commentNum = 0; commentNum < totalNumComments; commentNum++)
  297. {
  298. const innerHtmlUser = comment[commentNum].innerHTML.split(">")[6].split("<")[0];
  299. const textContentComment = comment[commentNum].getElementsByTagName("p")[0].textContent;
  300.  
  301. if (innerHtmlUser.match(parsedBlacklist) != null && comment[commentNum].style.display != 'none' && innerHtmlUser.match(userWhiteList) == null ||
  302. comment[commentNum].getElementsByClassName("reply-to").length != 0 &&
  303. comment[commentNum].getElementsByClassName("reply-to")[0].textContent.match(parsedBlacklist) != null)
  304. {
  305. // Removes comment
  306. if (comment[commentNum].parentNode.parentNode.className != "child-comments")
  307. {
  308. comment[commentNum].parentNode.style.display = 'none';
  309. }
  310. else
  311. {
  312. comment[commentNum].style.display = 'none';
  313. }
  314.  
  315. // [For Debugging - Lists all removed comments and the reason for their blocked state] //////////////////////////////////////////////////////////
  316. if (comment[commentNum].getElementsByClassName("reply-to").length != 0 &&
  317. comment[commentNum].getElementsByClassName("reply-to")[0].textContent.match(parsedBlacklist) != null)
  318. {
  319. allComments += '\n' + commentNum + ' - innerHTML\n\nUser [ ' + innerHtmlUser + ' ]\n\n * \n\nReason (reply-to): ' +
  320. comment[commentNum].children[2].innerText.split(comment[commentNum].children[2].children[0].textContent)[1] +
  321. '\n\n\n' + '-'.repeat(60) + '\n' + '-'.repeat(60) + '\n\n';
  322. }
  323. else
  324. {
  325. allComments += '\n' + commentNum + ' - innerHTML\n\nUser [ ' + innerHtmlUser + ' ]\n\n * \n\nReason (userBlackList): ' +
  326. innerHtmlUser.match(parsedBlacklist) + '\n\n\n' + '-'.repeat(60) + '\n' + '-'.repeat(60) + '\n\n';
  327. }
  328.  
  329. nRemoved += 1;
  330. }
  331.  
  332.  
  333.  
  334. else if (textContentComment.match(regexWholeWord) != null && comment[commentNum].style.display != 'none' && innerHtmlUser.match(userWhiteList) == null ||
  335. textContentComment.match(regexCombLetters) != null && comment[commentNum].style.display != 'none' && innerHtmlUser.match(userWhiteList) == null)
  336. {
  337.  
  338. // Removes comment
  339. if (comment[commentNum].parentNode.parentNode.className != "child-comments") { comment[commentNum].parentNode.style.display = 'none'; }
  340. else { comment[commentNum].style.display = 'none'; }
  341.  
  342.  
  343. // [For Debugging] //////////////////////////////////////////////////////////////////////////////////////////////////////
  344. allComments += '\n' + commentNum + ' - textContent\n\nUser [ ' + innerHtmlUser + ' ]\n\n"' +
  345. textContentComment + '"\n\n * \n\nReason (regexWholeWord): ' +
  346. textContentComment.match(regexWholeWord) + '\n\nReason (regexCombLetters): ' +
  347. textContentComment.match(regexCombLetters) + '\n\n\n' + '-'.repeat(60) + '\n' + '-'.repeat(60) + '\n\n';
  348.  
  349. nRemoved += 1;
  350. }
  351. }
  352.  
  353.  
  354.  
  355. // If original top comment is hidden, removes top-border of new top comment to match original's look.
  356. if(document.getElementById('comment-list').childElementCount > 1 && comment[0].parentNode.style.display == 'none')
  357. {
  358. for (cmmnt = 0; cmmnt < totalNumComments; cmmnt++)
  359. {
  360. if (borderTopRemoved == true) { break; }
  361.  
  362. else if (document.getElementById('comment-list').children[cmmnt].style.display == '')
  363. {
  364. document.getElementById('comment-list').children[cmmnt].children[0].style.borderTop = 'none';
  365. borderTopRemoved = true;
  366. }
  367. }
  368. }
  369. // [Removed Comments List - For Debugging] - Creates a div, from the function (btnForDataDisplay), with the message below.
  370. // Comments list
  371. const commentsElementType = "div";
  372. const commentsElementId = "txt-amount-removed-comments";
  373. const commentsElementStyle = "display:block; position:absolute; bottom:1%; right:0.5%; width:49.3%; height:88%; overflow:auto; " +
  374. "background-color:" + themeColor + "; color:" + themeTxtClr + "; border:4px solid " + thmBorderClr + "; text-align:center; z-index:inherit;";
  375. const removedCommentsElementStyle = "position:absolute; top:1%; right:0.5%; background-color:" + themeColor + "; color:" + themeTxtClr +
  376. "; font-size:1vw; font-weight:bold; width:49.3%; height:11%; border:4px solid " + thmBorderClr + "; line-height:275%;";
  377.  
  378. // List
  379. elementForDataDisplay(nRemoved + ' Comment(s) Removed!','div','div-amount-removed-comments',removedCommentsElementStyle,'div-debug');
  380. elementForDataDisplay(allComments,commentsElementType,commentsElementId,commentsElementStyle,'div-debug');
  381. document.getElementById(commentsElementId).scrollTo(0,0);
  382.  
  383. // [W.I.P] - Adds a button to block comments.
  384. addCommentBlockBtn(totalNumComments);
  385. }
  386. }
  387.  
  388. // Filter Comments by Word - END //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  389.  
  390.  
  391.  
  392.  
  393.  
  394.  
  395. // [For Debugging] - Create an element where the removed comments will be displayed (for debugging reasons) //////////////////////////////////////////////////////////
  396. //// Instead of using JS's alert(), an element is instead created to display the comments.
  397. //// Base source: https://stackoverflow.com/a/19020973
  398.  
  399. async function createDebug(GMvalue, cUserBlackList, listName, listOf, bgColor, txtColor, borderColor, btnOutline)
  400. {
  401. if(document.getElementById("btn-debug") == null)
  402. {
  403. // Background div and btn for textarea & removed comments
  404. elementForDataDisplay( "", "div", "div-debug", "display:none; position:absolute; top:69%; left:19.9%; width:60.1vw; height:30vw; background-color:" + bgColor + "; " +
  405. "color:" + txtColor + "; text-align:center; z-index:inherit;", 'nav-top-menu' );
  406. // Debug Button
  407. const btnDebugInnerTxt = 'Debug Menu';
  408. const btnDebugId = "btn-debug";
  409. const btnDebugStyle = "position:absolute; top:29%; right:25%; background-color:" + bgColor + "; color:" + txtColor + "; font-size: 0.9vw; width: 12vw; height: 1.9vw;" + btnOutline;
  410. btnForDataDisplay(btnDebugInnerTxt,btnDebugId,btnDebugStyle,document.getElementById("div-debug"));
  411.  
  412. const divTitleElementStyle = "position:absolute; top:-1%; left:-1%; background-color:" + bgColor + "; color:" + txtColor + "; font-size:0.9vw; font-weight:bold; " +
  413. "line-height:275%; border:4px solid " + borderColor + "; width:61.7%;";
  414.  
  415. // Textarea
  416. const debugListElementType = "textarea";
  417. const debugListElementId = "txt-debug-list";
  418. const debugListElementPlacement = "div-debug-editable-list";
  419. const textDebugListElementStyle = "display:block; position:absolute; bottom:-1%; left:-1%; width:102%; height:92%; overflow:auto; " +
  420. "background-color:" + bgColor + "; color:" + txtColor + "; border:4px solid " + borderColor + "; text-align:center; z-index:inherit;";
  421.  
  422.  
  423. // Background div for textarea & button
  424. elementForDataDisplay( "", "div", "div-debug-editable-list", "display:block; position:absolute; top:1%; left:0.5%; width:49.3%; height:98%; background-color:" + bgColor + "; " +
  425. "color:" + txtColor + "; border:4px solid " + borderColor + "; text-align:center; z-index:inherit;", 'div-debug' );
  426.  
  427. btnSaveList(bgColor, txtColor, btnOutline);
  428. // async onclick - source: https://stackoverflow.com/a/67509739
  429. document.getElementById("btn-save-list").onclick = async ()=>{alert("List saved!"); await GM.setValue(GMvalue, document.getElementById("txt-debug-list").value)};
  430.  
  431. // Textarea with watched videos list
  432. elementForDataDisplay( cUserBlackList, debugListElementType, debugListElementId, textDebugListElementStyle, debugListElementPlacement );
  433. document.getElementById(debugListElementId).scrollTo(0,0);
  434.  
  435. // Title Element
  436. elementForDataDisplay(listName + document.getElementById("txt-debug-list").innerHTML.split("|").length + listOf + " | Editable List","div","div-marked-videos-title",divTitleElementStyle,"div-debug-editable-list");
  437. }
  438. detectMutation();
  439. }
  440.  
  441.  
  442.  
  443. function btnSaveList(btnBgColor, btnTxtColor, svBtnOutline)
  444. {
  445. var btnElement = document.createElement("button");
  446. btnElement.setAttribute("id","btn-save-list");
  447. btnElement.setAttribute("style","position:absolute; top:1.3%; left:61.5%; background-color:" + btnBgColor + "; color:" + btnTxtColor + "; font-size:0.9vw; width:11vw; height:1.9vw;" + svBtnOutline);
  448. btnElement.innerHTML = "Save List";
  449. document.getElementById('div-debug-editable-list').appendChild(btnElement);
  450. }
  451.  
  452.  
  453. // Reusable block - Create an element where text is displayed
  454. function elementForDataDisplay(textData,elementType,elementId,elementStyle,elementHtmlPlacement)
  455. {
  456. var txtElemnt = document.createElement(elementType);
  457. txtElemnt.setAttribute("id",elementId);
  458. txtElemnt.setAttribute("style",elementStyle);
  459. txtElemnt.innerText = textData;
  460. document.getElementById(elementHtmlPlacement).appendChild(txtElemnt);
  461. }
  462.  
  463.  
  464. // Reusable block - Create the button to open/close the element where the text is displayed
  465. function btnForDataDisplay(btnTxt, btnId, btnStyle, btnTargetId)
  466. {
  467. var btnElement = document.createElement("button");
  468. btnElement.setAttribute("id",btnId);
  469. btnElement.setAttribute("style",btnStyle);
  470. btnElement.innerHTML = btnTxt;
  471. document.getElementById('nav-top-menu').appendChild(btnElement);
  472. document.getElementById(btnId).onclick = function (){if (btnTargetId.style.display !== "none") { btnTargetId.style.display = "none"; } else { btnTargetId.style.display = "block"; } };
  473. }
  474.  
  475.  
  476.  
  477.  
  478.  
  479.  
  480. // [W.I.P.] - Add button to each comment that opens a blocking menu /////////////////////////////////////////////////////////////////////////
  481. //// Later on it'll allow the user to choose a way to block the comment (keywords or username), and do it on the front-end.
  482.  
  483. // Create comment's menu button
  484. function addCommentBlockBtn(CommentAmmount)
  485. {
  486. let btnColor;
  487. let btnOpacity;
  488. if(document.getElementById("night-theme").classList == "user-link") { btnColor = "rgb(238, 238, 238)"; btnOpacity = "0.75"; }
  489. else { btnColor = "rgb(23, 23, 23)"; btnOpacity = "0.5"; }
  490. if(document.getElementsByClassName('comment-wrapper')[0].children[3].children[2].innerHTML.indexOf("btnMenuScript") == -1)
  491. {
  492. for (commentNum = 0; commentNum < CommentAmmount; commentNum++)
  493. {
  494.  
  495. var menuElement = document.createElement("button");
  496.  
  497. menuElement.setAttribute("id","menu_" + commentNum);
  498. menuElement.setAttribute("class","action");
  499. menuElement.setAttribute("style","display:inherit; position:relative; background-color:"+ btnColor + "; text-align:center; font-size:.9em; line-height:inherit; opacity:" + btnOpacity + "; border: outset; margin: auto; width: 85px; border-radius: 15px;");
  500. menuElement.innerText = "Block User";
  501.  
  502. document.getElementsByClassName('comment-wrapper')[commentNum].children[3].children[2].appendChild(menuElement);
  503.  
  504. blockComment(commentNum);
  505. }
  506. }
  507.  
  508. else
  509. {
  510. return;
  511. }
  512. }
  513.  
  514. // Create Comment's 'Block by Username' Button
  515. async function blockComment(num)
  516. {
  517. var blacklistStart = document.getElementById("txt-debug-list").value.substring(0,2);
  518. var blacklistAfterNew = document.getElementById("txt-debug-list").value.substring(2);
  519.  
  520. // Display confirmation message
  521. var userName = document.getElementById("menu_" + num).parentElement.parentElement.parentElement.innerHTML.split('>')[6].slice(0,-6).toString();
  522. var sentence = "Block " + userName + "?";
  523. document.getElementById("menu_" + num).onclick = async ()=>{if (confirm(sentence) == true) {await GM.setValue("blackList", blacklistStart + userName + "|" + blacklistAfterNew); alert("Blocked! Please refresh page."); document.getElementsByClassName("comment")[num].style.display = "none";}};
  524. }

QingJ © 2025

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