Youtube: expand description and long comments; show all the replies

Video description, long comments and list of subscriptions are expanded automatically; all the replies are shown after pressing "Show more replies" button

  1. // ==UserScript==
  2. // @name Youtube: expand description and long comments; show all the replies
  3. // @description Video description, long comments and list of subscriptions are expanded automatically; all the replies are shown after pressing "Show more replies" button
  4. // @author MK
  5. // @namespace max44
  6. // @homepage https://greasyfork.org/en/users/309172-max44
  7. // @match *://*.youtube.com/*
  8. // @match *://*.youtu.be/*
  9. // @icon https://cdn.icon-icons.com/icons2/1488/PNG/512/5295-youtube-i_102568.png
  10. // @version 1.2.15
  11. // @license MIT
  12. // @require https://openuserjs.org/src/libs/sizzle/GM_config.js
  13. // @grant GM_getValue
  14. // @grant GM_setValue
  15. // @grant GM_registerMenuCommand
  16. // @run-at document-end
  17. // ==/UserScript==
  18.  
  19. (function () {
  20. 'use strict';
  21.  
  22. //WORKAROUND: This document requires 'TrustedHTML' assignment
  23. if (window.trustedTypes && trustedTypes.createPolicy) {
  24. if (!trustedTypes.defaultPolicy) {
  25. const passThroughFn = (x) => x;
  26. trustedTypes.createPolicy('default', {
  27. createHTML: passThroughFn,
  28. createScriptURL: passThroughFn,
  29. createScript: passThroughFn,
  30. });
  31. }
  32. }
  33.  
  34. var gm_css = `
  35. #yt_expand_desc_comments_replies * {
  36. font-family: Roboto, Arial, sans-serif !important;
  37. }
  38. #yt_expand_desc_comments_replies .config_header {
  39. font-size: 20px !important;
  40. font-weight: bold !important;
  41. }
  42. #yt_expand_desc_comments_replies .field_label {
  43. font-size: 13px !important;
  44. font-weight: 400 !important;
  45. }
  46. #yt_expand_desc_comments_replies input[type="text"] {
  47. width: 50px !important;
  48. font-size: 12px !important;
  49. font-weight: bold !important;
  50. border-radius: 3px !important;
  51. }
  52. #yt_expand_desc_comments_replies button {
  53. font-size: 12px !important;
  54. }`;
  55.  
  56. var gm_frameStyle = `border: 2px solid rgb(0, 0, 0); border-radius: 6px; height: 50%; width: 30%; margin: 0px; max-height: 350px; max-width: 95%; min-height: 350px; min-width: 500px; opacity: 1; overflow: auto; padding: 0px; position: fixed; z-index: 9999; display: block;`;
  57.  
  58. GM_config.init({
  59. id: 'yt_expand_desc_comments_replies',
  60. title: 'Settings for "' + GM_info.script.name + '" script',
  61. css: gm_css,
  62. frameStyle: gm_frameStyle,
  63. fields: {
  64. 'fldExpandDesc': {
  65. 'label': 'Expand video description',
  66. 'labelPos': 'above',
  67. 'type': 'checkbox',
  68. 'default': true
  69. },
  70. 'fldExpandLongComments': {
  71. 'label': 'Expand long comments',
  72. 'labelPos': 'above',
  73. 'type': 'checkbox',
  74. 'default': true
  75. },
  76. 'fldExpandLongReplies': {
  77. 'label': 'Expand long replies to comments',
  78. 'labelPos': 'above',
  79. 'type': 'checkbox',
  80. 'default': true
  81. },
  82. 'fldShowAllReplies': {
  83. 'label': 'Show all the replies to a comment',
  84. 'labelPos': 'above',
  85. 'type': 'checkbox',
  86. 'default': true
  87. },
  88. 'fldExpandSubs': {
  89. 'label': 'Expand list of subscriptions',
  90. 'labelPos': 'above',
  91. 'type': 'checkbox',
  92. 'default': true
  93. }
  94. }
  95. });
  96.  
  97. GM_registerMenuCommand('Settings', () => {
  98. GM_config.open();
  99. });
  100.  
  101. var videoIdAtLastCheck = "";
  102. var btnClick = null;
  103. var waitVideo;
  104. var i;
  105.  
  106. var observerBody = null;
  107. var observerSubs = null;
  108. var flgSubsDone = false;
  109. var observerDesc = null;
  110. var observerComments = null;
  111. //var observerCommentsTabview = null;
  112. var observerCommentsNotif = null;
  113. var observerComPost = null;
  114.  
  115. const delay = ms => new Promise(res => setTimeout(res, ms));
  116. const waitAndScroll = async () => {
  117. await delay(500);
  118. window.scrollTo(0, 0);
  119. };
  120.  
  121.  
  122. //---
  123. //--- Listen to global page changes
  124. //---
  125. const callbackBody = function (mutationsList, observer) {
  126.  
  127. var pathArray = window.location.pathname.split('/');
  128. var firstPath = pathArray[1];
  129. var lastPath = pathArray[pathArray.length - 1];
  130.  
  131. //Check whether video is new to expand description
  132. if (firstPath === "watch" || firstPath === "live") {
  133. //var player = $( "div#content ytd-watch-flexy" );
  134. var player = document.querySelectorAll("div#content ytd-watch-flexy");
  135. if (player != null && player.length > 0) {
  136. clearInterval(waitVideo); //Stop waiting for video
  137. var videoId = player[0].getAttribute("video-id");
  138. player = null;
  139.  
  140. if (videoIdAtLastCheck != videoId) {
  141. videoIdAtLastCheck = videoId;
  142. expandDesc();
  143. waitAndScroll();
  144. }
  145. }
  146. }
  147.  
  148. /* //---
  149. //--- Listen to description and expand it
  150. //---
  151. if (observerDesc == null) {
  152. const callbackDesc = function (mutationsList, observer) {
  153. //Check whether video is new to expand description
  154. if (firstPath === "watch") {
  155. var player = $( "div#content ytd-watch-flexy" );
  156. if (player != null && player.length > 0) {
  157. clearInterval(waitVideo); //Stop waiting for video
  158. var videoId = player[0].getAttribute("video-id");
  159. player = null;
  160.  
  161. if (videoIdAtLastCheck != videoId) {
  162. videoIdAtLastCheck = videoId;
  163. console.log("new video");
  164. expandDesc();
  165. }
  166. }
  167. }
  168. }
  169. let nodeDesc = document.querySelector("#player");//document.querySelector(".watch-active-metadata");
  170. if (nodeDesc != null) {
  171. observerDesc = new MutationObserver(callbackDesc);
  172. observerDesc.observe(nodeDesc, {childList: true, subtree: true, attributes: true, characterData: false});
  173. console.log("desc observer added");
  174. }
  175. }*/
  176.  
  177. //Remove subscriptions observer after subscriptions have been expanded
  178. if (flgSubsDone && observerSubs != null) {
  179. observerSubs.disconnect(); //Expand subscriptions only once
  180. observerSubs = null;
  181. }
  182.  
  183. //---
  184. //--- Listen to community posts and expand them
  185. //---
  186. if (observerComPost == null && lastPath === "community") {
  187. const callbackComPost = function (mutationsList, observer) {
  188. expandComPosts();
  189. expandComments();
  190. }
  191. let nodeComPost = document.querySelector("#primary #contents #contents");
  192. if (nodeComPost != null) {
  193. observerComPost = new MutationObserver(callbackComPost);
  194. observerComPost.observe(nodeComPost, {childList: true, subtree: true, attributes: true, characterData: false});
  195. }
  196. }
  197. //Remove community post observer on non-community pages
  198. if (observerComPost != null && lastPath != "community") {
  199. observerComPost.disconnect();
  200. observerComPost = null;
  201. }
  202.  
  203. //---
  204. //--- Listen to comments and expand them
  205. //---
  206. if (observerComments == null && (firstPath === "watch" || firstPath === "live" || firstPath === "post" || firstPath === "shorts" || lastPath === "community")) {
  207. const callbackComments = function (mutationsList, observer) {
  208. expandComments();
  209. }
  210. let nodeComments = null;
  211. let nodeCommentsTabview = null;
  212. if (firstPath === "shorts") {
  213. nodeComments = document.querySelector("ytd-shorts ytd-comments:not([hidden='']");
  214. if (nodeComments != null) {
  215. observerComments = new MutationObserver(callbackComments);
  216. observerComments.observe(nodeComments, {childList: true, subtree: true, attributes: true, characterData: false});
  217. }
  218. } else {
  219. nodeComments = document.querySelector("#primary ytd-comments:not([hidden='']");
  220. if (nodeComments != null) {
  221. observerComments = new MutationObserver(callbackComments);
  222. observerComments.observe(nodeComments, {childList: true, subtree: true, attributes: true, characterData: false});
  223. }
  224. nodeCommentsTabview = document.querySelector("#right-tabs ytd-comments:not([hidden='']");
  225. if (nodeCommentsTabview != null) {
  226. observerComments = new MutationObserver(callbackComments);
  227. observerComments.observe(nodeCommentsTabview, {childList: true, subtree: true, attributes: true, characterData: false});
  228. }
  229. }
  230. }
  231. //Remove comments observer
  232. if (observerComments != null && firstPath != "watch" && firstPath != "live" && firstPath != "shorts" && firstPath != "post" && lastPath != "community") {
  233. observerComments.disconnect();
  234. observerComments = null;
  235. }
  236.  
  237. //---
  238. //--- Listen to comments in notification submenu and expand them
  239. //---
  240. if (observerCommentsNotif == null) {
  241. const callbackCommentsNotif = function (mutationsList, observer) {
  242. expandCommentsNotif();
  243. }
  244. let nodeCommentsNotif = null;
  245. nodeCommentsNotif = document.querySelector("ytd-popup-container #contentWrapper");
  246. if (nodeCommentsNotif != null) {
  247. observerCommentsNotif = new MutationObserver(callbackCommentsNotif);
  248. observerCommentsNotif.observe(nodeCommentsNotif, {childList: true, subtree: true, attributes: true, characterData: false});
  249. }
  250. }
  251. }
  252.  
  253. let nodeBody = document.querySelector("body");
  254. if (nodeBody != null) {
  255. const observerBody = new MutationObserver(callbackBody);
  256. observerBody.observe(nodeBody, {childList: true, subtree: true, attributes: true, characterData: false});
  257. }
  258.  
  259. //---
  260. //--- Listen to subscriptions and expand them
  261. //---
  262. const callbackSubs = function (mutationsList, observer) {
  263. expandSubs();
  264. }
  265. let nodeSubs = document.querySelector("div#guide-wrapper");
  266. if (nodeSubs != null) {
  267. observerSubs = new MutationObserver(callbackSubs);
  268. observerSubs.observe(nodeSubs, {childList: true, subtree: true, attributes: true, characterData: false});
  269. }
  270.  
  271.  
  272. //---------------------------------------
  273. // Expand description
  274. //---------------------------------------
  275. function expandDesc() {
  276. if (GM_config.fields['fldExpandDesc'].value) {
  277. //Expand description
  278. /*btnClick = $( "div#description ytd-text-inline-expander tp-yt-paper-button#expand[role='button']:not([hidden=''])" );
  279. if (btnClick != null && btnClick.length > 0) {// && isVisible(btnClick)) {
  280. btnClick[0].click();
  281. }*/
  282.  
  283. //Expand description - suggested by gcobc12632
  284. //btnClick = $( "yt-interaction#description-interaction" );
  285. btnClick = document.querySelector("yt-interaction#description-interaction");
  286. if (btnClick != null) {// && isVisible(btnClick)) {
  287. btnClick.click();
  288. return;
  289. }
  290.  
  291. //Expand description after 7ktTube | 2016 REDUX script
  292. //btnClick = $( "div#meta-contents ytd-video-secondary-info-renderer div ytd-expander tp-yt-paper-button#more:not([hidden=''])" );
  293. btnClick = document.querySelectorAll("div#meta-contents ytd-video-secondary-info-renderer div ytd-expander tp-yt-paper-button#more:not([hidden=''])");
  294. if (btnClick != null && btnClick.length > 0) {// && isVisible(btnClick)) {
  295. btnClick[0].click();
  296. }
  297. }
  298. }
  299.  
  300. //---------------------------------------
  301. // Expand post and comments in community section
  302. //---------------------------------------
  303. function expandComPosts() {
  304. if (GM_config.fields['fldExpandLongComments'].value) {
  305. //Expand long post in community section
  306. btnClick = document.querySelectorAll("#post tp-yt-paper-button#more:not([hidden='']) > span.more-button");
  307. if (btnClick != null && btnClick.length > 0) {
  308. for (i = 0; i < btnClick.length; i++) {
  309. btnClick[i].click();
  310. btnClick[i].parentNode.previousElementSibling.setAttribute("style", "display:none;"); //Hide "Show less" button
  311. }
  312. }
  313. }
  314. }
  315.  
  316. //---------------------------------------
  317. // Expand comments
  318. //---------------------------------------
  319. function expandComments() {
  320. if (GM_config.fields['fldExpandLongComments'].value) {
  321. //Expand long comments and hide "show less" button in comments section
  322. btnClick = document.querySelectorAll("ytd-comments ytd-comment-thread-renderer > ytd-comment-view-model#comment tp-yt-paper-button#more:not([hidden='']) > span.more-button");
  323. if (btnClick != null) {
  324. for (i = 0; i < btnClick.length; i++) {
  325. btnClick[i].click();
  326. btnClick[i].setAttribute("clicked-by-script", "true"); //Do not click it again
  327. btnClick[i].parentNode.previousElementSibling.setAttribute("style", "display:none;"); //Hide "Show less" button
  328. }
  329. }
  330. }
  331.  
  332. if (GM_config.fields['fldExpandLongReplies'].value) {
  333. //Expand long replies and hide "show less" button in comments section
  334. btnClick = document.querySelectorAll("ytd-comments #replies tp-yt-paper-button#more:not([hidden='']) > span.more-button");
  335. if (btnClick != null) {
  336. for (i = 0; i < btnClick.length; i++) {
  337. btnClick[i].click();
  338. btnClick[i].setAttribute("clicked-by-script", "true"); //Do not click it again
  339. btnClick[i].parentNode.previousElementSibling.setAttribute("style", "display:none;"); //Hide "Show less" button
  340. }
  341. }
  342. }
  343.  
  344. if (GM_config.fields['fldShowAllReplies'].value) {
  345. //Show all replies upon pressing "Show more replies" button (not in notification submenu)
  346. btnClick = document.querySelectorAll("#primary div#replies div#expander div#expander-contents div#button.ytd-continuation-item-renderer:not([hidden]) ytd-button-renderer.ytd-continuation-item-renderer button.yt-spec-button-shape-next:not([clicked-by-script='true'])");
  347. if (btnClick != null) {
  348. for (i = 0; i < btnClick.length; i++) {
  349. btnClick[i].click();
  350. btnClick[i].setAttribute("clicked-by-script", "true"); //Do not click it again
  351. }
  352. }
  353. //Rearm "Show more replies" button
  354. btnClick = document.querySelectorAll("#primary div#replies div#expander div#expander-contents div#button.ytd-continuation-item-renderer[hidden=''] ytd-button-renderer.ytd-continuation-item-renderer button.yt-spec-button-shape-next[clicked-by-script='true']");
  355. if (btnClick != null) {
  356. for (i = 0; i < btnClick.length; i++) {
  357. btnClick[i].removeAttribute("clicked-by-script", "true"); //Click it again when it becomes not hidden
  358. }
  359. }
  360.  
  361. //Show all replies upon pressing "View more comments" button (7ktTube | 2016 REDUX script)
  362. btnClick = document.querySelectorAll("#primary div#replies div#expander div#expander-contents div#button.ytd-continuation-item-renderer:not([hidden]) ytd-button-renderer tp-yt-paper-button#button[role='button']:not([clicked-by-script='true'])");
  363. if (btnClick != null) {
  364. for (i = 0; i < btnClick.length; i++) {
  365. btnClick[i].click();
  366. btnClick[i].setAttribute("clicked-by-script", "true"); //Do not click it again
  367. }
  368. }
  369. //Rearm "View more comments" button (7ktTube | 2016 REDUX script)
  370. btnClick = document.querySelectorAll("#primary div#replies div#expander div#expander-contents div#button.ytd-continuation-item-renderer[hidden=''] ytd-button-renderer tp-yt-paper-button#button[role='button'][clicked-by-script='true']");
  371. if (btnClick != null) {
  372. for (i = 0; i < btnClick.length; i++) {
  373. btnClick[i].click();
  374. btnClick[i].removeAttribute("clicked-by-script", "true"); //Click it again when it becomes not hidden
  375. }
  376. }
  377. }
  378. }
  379.  
  380. //---------------------------------------
  381. // Expand comments in notification submenu
  382. //---------------------------------------
  383. function expandCommentsNotif() {
  384. if (GM_config.fields['fldExpandLongComments'].value) {
  385. //Expand long comments and hide "show less" button in notification submenu
  386. btnClick = document.querySelectorAll("#submenu ytd-comment-thread-renderer > #comment tp-yt-paper-button#more:not([hidden='']) > span.more-button[slot='more-button']:not([clicked-by-script='true'])");
  387. if (btnClick != null) {
  388. for (i = 0; i < btnClick.length; i++) {
  389. btnClick[i].click();
  390. btnClick[i].setAttribute("clicked-by-script", "true"); //Do not click it again
  391. btnClick[i].parentNode.previousElementSibling.setAttribute("style", "display:none;"); //Hide "Show less" button
  392. }
  393. }
  394. }
  395.  
  396. if (GM_config.fields['fldExpandLongReplies'].value) {
  397. //Expand long replies and hide "show less" button in notification submenu
  398. btnClick = document.querySelectorAll("#submenu ytd-comment-thread-renderer > #replies tp-yt-paper-button#more:not([hidden='']) > span.more-button[slot='more-button']:not([clicked-by-script='true'])");
  399. if (btnClick != null) {
  400. for (i = 0; i < btnClick.length; i++) {
  401. btnClick[i].click();
  402. btnClick[i].setAttribute("clicked-by-script", "true"); //Do not click it again
  403. btnClick[i].parentNode.previousElementSibling.setAttribute("style", "display:none;"); //Hide "Show less" button
  404. }
  405. }
  406. }
  407. }
  408.  
  409. //---------------------------------------
  410. // Show all subscriptions
  411. //---------------------------------------
  412. function expandSubs() {
  413. if (GM_config.fields['fldExpandSubs'].value) {
  414. //btnClick = $( "#guide div#sections div#items ytd-guide-collapsible-entry-renderer.ytd-guide-section-renderer[can-show-more=''] #expander-item" );
  415. btnClick = document.querySelectorAll("#guide div#sections div#items ytd-guide-collapsible-entry-renderer.ytd-guide-section-renderer:not([expanded='']) #expander-item");
  416. if (btnClick != null) {
  417. for (i = 0; i < btnClick.length; i++) {
  418. if (isVisible(btnClick[i])) {
  419. btnClick[i].click();
  420. flgSubsDone = true;
  421. }
  422. }
  423. }
  424. }
  425. }
  426.  
  427. //---------------------------------------
  428. // Check all the parents of element to find whether it is visible or not
  429. //---------------------------------------
  430. function isVisible(pObj) {
  431. if (pObj != null) {
  432. var checkNext = true;
  433. var vObj = pObj;
  434.  
  435. while (checkNext) {
  436. checkNext = false;
  437. //console.log("checking element " + vObj.tagName + "#" + vObj.id + ": '" + document.defaultView.getComputedStyle(vObj,null)['display'] + "'");
  438. if (document.defaultView.getComputedStyle(vObj,null)['display'] != "none") {
  439. if (vObj.parentElement != null) {
  440. vObj = vObj.parentElement;
  441. checkNext = true;
  442. }
  443. } else {
  444. return false;
  445. }
  446. }
  447. return true;
  448. }
  449. return false;
  450. }
  451.  
  452.  
  453. })();
  454.  
  455. /*//Detect spinner at main comments
  456. var spinnerMain = $( "#primary div#replies div#expander tp-yt-paper-spinner#spinner[active]" );
  457. if (spinnerMain != null && spinnerMain.length > 0) {
  458. console.log("main active spinner detected");
  459. spinnerActive = true;
  460.  
  461. //Listen to spinner changes
  462. const spinnerCallback = function (mutationsList, observer) {
  463. expandComments();
  464.  
  465. //spinnerMain = $( "#primary div#replies div#expander tp-yt-paper-spinner#spinner[active]" );
  466. if (spinnerMain[0].getAttribute("active") == null || spinnerMain[0].getAttribute("active") == "") {
  467. console.log("main spinner deactivated");
  468. spinnerObserver.disconnet();
  469. }
  470. }
  471.  
  472. var spinnerNode = document.querySelector("#primary div#replies div#expander tp-yt-paper-spinner#spinner[active]");
  473. if (spinnerNode != null) {
  474. const spinnerObserver = new MutationObserver(spinnerCallback);
  475. spinnerObserver.observe(spinnerNode, {childList: true, subtree: true, attributes: true, characterData: true});
  476. }
  477.  
  478. } else if (spinnerActive) {
  479. console.log("spinner stopped");
  480. spinnerActive = false;
  481. expandComments();
  482. }*/