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.17
  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 flgNewVideo = false;
  103. var flgTabviewDesc = false;
  104. var btnClick = null;
  105. var i;
  106. //var waitVideo;
  107.  
  108. var observerBody = null;
  109. var observerSubs = null;
  110. var flgSubsDone = false;
  111. var observerDesc = null;
  112. var observerDescTabview = null;
  113. var observerComments = null;
  114. //var observerCommentsTabview = null;
  115. var observerCommentsNotif = null;
  116. var observerComPost = null;
  117.  
  118. const delay = ms => new Promise(res => setTimeout(res, ms));
  119. const waitAndScroll = async () => {
  120. await delay(500);
  121. window.scrollTo(0, 0);
  122. };
  123.  
  124.  
  125. //---
  126. //--- Listen to global page changes
  127. //---
  128. const callbackBody = function (mutationsList, observer) {
  129.  
  130. var pathArray = window.location.pathname.split('/');
  131. var firstPath = pathArray[1];
  132. var lastPath = pathArray[pathArray.length - 1];
  133.  
  134. //Check whether video is new to expand description
  135. if (firstPath === "watch" || firstPath === "live") {
  136. var player = document.querySelectorAll("div#content ytd-watch-flexy");
  137. if (player != null && player.length > 0) {
  138. var videoId = player[0].getAttribute("video-id");
  139. player = null;
  140.  
  141. if (videoIdAtLastCheck != videoId) {
  142. videoIdAtLastCheck = videoId;
  143. flgNewVideo = true;
  144. flgTabviewDesc = true;
  145. //console.log("new video " + " / " + videoIdAtLastCheck);
  146. }
  147. }
  148. }
  149.  
  150. if (flgNewVideo) {
  151. //console.log("do desc");
  152. expandDesc();
  153. }
  154.  
  155. //---
  156. //--- Listen to Tabview description and expand it
  157. //---
  158. if (flgTabviewDesc) {
  159. if (observerDescTabview == null && (firstPath === "watch" || firstPath === "live")) {
  160. const callbackDescTabview = function (mutationsList, observer) {
  161. expandDescTabview();
  162. }
  163. let nodeDescTabview = document.querySelector("secondary-wrapper");
  164. if (nodeDescTabview != null) {
  165. observerDescTabview = new MutationObserver(callbackDescTabview);
  166. observerDescTabview.observe(nodeDescTabview, {childList: true, subtree: true, attributes: true, characterData: false});
  167. }
  168. }
  169. }
  170. //Remove Tabview description observer on non-video pages
  171. if (observerDescTabview != null && firstPath != "watch" && firstPath != "live") {
  172. observerDescTabview.disconnect();
  173. observerDescTabview = null;
  174. }
  175.  
  176. //Remove subscriptions observer after subscriptions have been expanded
  177. if (flgSubsDone && observerSubs != null) {
  178. observerSubs.disconnect(); //Expand subscriptions only once
  179. observerSubs = null;
  180. }
  181.  
  182. //---
  183. //--- Listen to community posts and expand them
  184. //---
  185. if (observerComPost == null && lastPath === "community") {
  186. const callbackComPost = function (mutationsList, observer) {
  187. expandComPosts();
  188. expandComments();
  189. }
  190. let nodeComPost = document.querySelector("#primary #contents #contents");
  191. if (nodeComPost != null) {
  192. observerComPost = new MutationObserver(callbackComPost);
  193. observerComPost.observe(nodeComPost, {childList: true, subtree: true, attributes: true, characterData: false});
  194. }
  195. }
  196. //Remove community post observer on non-community pages
  197. if (observerComPost != null && lastPath != "community") {
  198. observerComPost.disconnect();
  199. observerComPost = null;
  200. }
  201.  
  202. //---
  203. //--- Listen to comments and expand them
  204. //---
  205. if (observerComments == null && (firstPath === "watch" || firstPath === "live" || firstPath === "post" || firstPath === "shorts" || lastPath === "community")) {
  206. const callbackComments = function (mutationsList, observer) {
  207. expandComments();
  208. }
  209. let nodeComments = null;
  210. if (firstPath === "shorts") {
  211. nodeComments = document.querySelector("ytd-shorts ytd-comments:not([hidden=''])");
  212. if (nodeComments != null) {
  213. observerComments = new MutationObserver(callbackComments);
  214. observerComments.observe(nodeComments, {childList: true, subtree: true, attributes: true, characterData: false});
  215. }
  216. } else {
  217. nodeComments = document.querySelector("#primary ytd-comments:not([hidden=''])");
  218. if (nodeComments != null) {
  219. observerComments = new MutationObserver(callbackComments);
  220. observerComments.observe(nodeComments, {childList: true, subtree: true, attributes: true, characterData: false});
  221. } else {
  222. nodeComments = document.querySelector("#tab-comments ytd-comments:not([hidden=''])");
  223. if (nodeComments != null) {
  224. observerComments = new MutationObserver(callbackComments);
  225. observerComments.observe(nodeComments, {childList: true, subtree: true, attributes: true, characterData: false});
  226. }
  227. }
  228. }
  229. }
  230. //Remove comments observer
  231. if (observerComments != null && firstPath != "watch" && firstPath != "live" && firstPath != "shorts" && firstPath != "post" && lastPath != "community") {
  232. observerComments.disconnect();
  233. observerComments = null;
  234. }
  235.  
  236. //---
  237. //--- Listen to comments in notification submenu and expand them
  238. //---
  239. if (observerCommentsNotif == null) {
  240. const callbackCommentsNotif = function (mutationsList, observer) {
  241. expandCommentsNotif();
  242. }
  243. let nodeCommentsNotif = null;
  244. nodeCommentsNotif = document.querySelector("ytd-popup-container #contentWrapper");
  245. if (nodeCommentsNotif != null) {
  246. observerCommentsNotif = new MutationObserver(callbackCommentsNotif);
  247. observerCommentsNotif.observe(nodeCommentsNotif, {childList: true, subtree: true, attributes: true, characterData: false});
  248. }
  249. }
  250. }
  251.  
  252. let nodeBody = document.querySelector("body");
  253. if (nodeBody != null) {
  254. const observerBody = new MutationObserver(callbackBody);
  255. observerBody.observe(nodeBody, {childList: true, subtree: true, attributes: true, characterData: false});
  256. }
  257.  
  258. //---
  259. //--- Listen to subscriptions and expand them
  260. //---
  261. const callbackSubs = function (mutationsList, observer) {
  262. expandSubs();
  263. }
  264. let nodeSubs = document.querySelector("div#guide-wrapper");
  265. if (nodeSubs != null) {
  266. observerSubs = new MutationObserver(callbackSubs);
  267. observerSubs.observe(nodeSubs, {childList: true, subtree: true, attributes: true, characterData: false});
  268. }
  269.  
  270.  
  271. //---------------------------------------
  272. // Expand description
  273. //---------------------------------------
  274. function expandDesc() {
  275. if (GM_config.fields['fldExpandDesc'].value) {
  276. //Expand description
  277. btnClick = document.querySelector("#primary tp-yt-paper-button#expand");
  278. if (btnClick != null /*&& isVisible(btnClick)*/) {
  279. btnClick.click();
  280. //console.log("common desc");
  281. flgNewVideo = false;
  282. waitAndScroll();
  283. //return;
  284. }
  285.  
  286. //Expand description - suggested by gcobc12632
  287. /*btnClick = document.querySelector("yt-interaction#description-interaction");
  288. if (btnClick != null) {// && isVisible(btnClick)) {
  289. btnClick.click();
  290. return;
  291. }*/
  292.  
  293. //Expand description after Tabview script
  294.  
  295. //Expand description after 7ktTube | 2016 REDUX script
  296. btnClick = document.querySelectorAll("div#meta-contents ytd-video-secondary-info-renderer div ytd-expander tp-yt-paper-button#more:not([hidden=''])");
  297. if (btnClick != null && btnClick.length > 0 /*&& isVisible(btnClick)*/) {
  298. btnClick[0].click();
  299. flgNewVideo = false;
  300. waitAndScroll();
  301. }
  302. }
  303. }
  304. //---------------------------------------
  305. // Expand description of Tabview script
  306. //---------------------------------------
  307. function expandDescTabview() {
  308. if (GM_config.fields['fldExpandDesc'].value) {
  309. btnClick = document.querySelector("#right-tabs .tyt-main-info tp-yt-paper-button#expand");
  310. if (btnClick != null /*&& isVisible(btnClick)*/) {
  311. btnClick.click();
  312. //console.log("tabview desc");
  313. flgTabviewDesc = false;
  314. waitAndScroll();
  315. observerDescTabview.disconnect();
  316. observerDescTabview = null;
  317. }
  318. }
  319. }
  320.  
  321. //---------------------------------------
  322. // Expand post and comments in community section
  323. //---------------------------------------
  324. function expandComPosts() {
  325. if (GM_config.fields['fldExpandLongComments'].value) {
  326. //Expand long post in community section
  327. btnClick = document.querySelectorAll("#post tp-yt-paper-button#more:not([hidden='']) > span.more-button");
  328. if (btnClick != null && btnClick.length > 0) {
  329. for (i = 0; i < btnClick.length; i++) {
  330. btnClick[i].click();
  331. btnClick[i].parentNode.previousElementSibling.setAttribute("style", "display:none;"); //Hide "Show less" button
  332. }
  333. }
  334. }
  335. }
  336.  
  337. //---------------------------------------
  338. // Expand comments
  339. //---------------------------------------
  340. function expandComments() {
  341. if (GM_config.fields['fldExpandLongComments'].value) {
  342. //Expand long comments and hide "show less" button in comments section
  343. btnClick = document.querySelectorAll("ytd-comments ytd-comment-thread-renderer > ytd-comment-view-model#comment tp-yt-paper-button#more:not([hidden='']) > span.more-button");
  344. if (btnClick != null) {
  345. for (i = 0; i < btnClick.length; i++) {
  346. btnClick[i].click();
  347. btnClick[i].setAttribute("clicked-by-script", "true"); //Do not click it again
  348. btnClick[i].parentNode.previousElementSibling.setAttribute("style", "display:none;"); //Hide "Show less" button
  349. }
  350. }
  351. }
  352.  
  353. if (GM_config.fields['fldExpandLongReplies'].value) {
  354. //Expand long replies and hide "show less" button in comments section
  355. btnClick = document.querySelectorAll("ytd-comments #replies tp-yt-paper-button#more:not([hidden='']) > span.more-button");
  356. if (btnClick != null) {
  357. for (i = 0; i < btnClick.length; i++) {
  358. btnClick[i].click();
  359. btnClick[i].setAttribute("clicked-by-script", "true"); //Do not click it again
  360. btnClick[i].parentNode.previousElementSibling.setAttribute("style", "display:none;"); //Hide "Show less" button
  361. }
  362. }
  363. }
  364.  
  365. if (GM_config.fields['fldShowAllReplies'].value) {
  366. //Show all replies upon pressing "Show more replies" button (not in notification submenu)
  367. 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']), #right-tabs #tab-comments 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'])");
  368. if (btnClick != null) {
  369. for (i = 0; i < btnClick.length; i++) {
  370. btnClick[i].click();
  371. btnClick[i].setAttribute("clicked-by-script", "true"); //Do not click it again
  372. }
  373. }
  374. //Rearm "Show more replies" button
  375. 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'], #right-tabs #tab-comments 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']");
  376. if (btnClick != null) {
  377. for (i = 0; i < btnClick.length; i++) {
  378. btnClick[i].removeAttribute("clicked-by-script", "true"); //Click it again when it becomes not hidden
  379. }
  380. }
  381.  
  382. //Show all replies upon pressing "View more comments" button (7ktTube | 2016 REDUX script)
  383. 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'])");
  384. if (btnClick != null) {
  385. for (i = 0; i < btnClick.length; i++) {
  386. btnClick[i].click();
  387. btnClick[i].setAttribute("clicked-by-script", "true"); //Do not click it again
  388. }
  389. }
  390. //Rearm "View more comments" button (7ktTube | 2016 REDUX script)
  391. 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']");
  392. if (btnClick != null) {
  393. for (i = 0; i < btnClick.length; i++) {
  394. btnClick[i].click();
  395. btnClick[i].removeAttribute("clicked-by-script", "true"); //Click it again when it becomes not hidden
  396. }
  397. }
  398. }
  399. }
  400.  
  401. //---------------------------------------
  402. // Expand comments in notification submenu
  403. //---------------------------------------
  404. function expandCommentsNotif() {
  405. if (GM_config.fields['fldExpandLongComments'].value) {
  406. //Expand long comments and hide "show less" button in notification submenu
  407. 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'])");
  408. if (btnClick != null) {
  409. for (i = 0; i < btnClick.length; i++) {
  410. btnClick[i].click();
  411. btnClick[i].setAttribute("clicked-by-script", "true"); //Do not click it again
  412. btnClick[i].parentNode.previousElementSibling.setAttribute("style", "display:none;"); //Hide "Show less" button
  413. }
  414. }
  415. }
  416.  
  417. if (GM_config.fields['fldExpandLongReplies'].value) {
  418. //Expand long replies and hide "show less" button in notification submenu
  419. 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'])");
  420. if (btnClick != null) {
  421. for (i = 0; i < btnClick.length; i++) {
  422. btnClick[i].click();
  423. btnClick[i].setAttribute("clicked-by-script", "true"); //Do not click it again
  424. btnClick[i].parentNode.previousElementSibling.setAttribute("style", "display:none;"); //Hide "Show less" button
  425. }
  426. }
  427. }
  428. }
  429.  
  430. //---------------------------------------
  431. // Show all subscriptions
  432. //---------------------------------------
  433. function expandSubs() {
  434. if (GM_config.fields['fldExpandSubs'].value) {
  435. btnClick = document.querySelectorAll("tp-yt-app-drawer[opened] #sections.ytd-guide-renderer ytd-guide-collapsible-entry-renderer.ytd-guide-section-renderer:not([expanded='']) #expander-item");
  436. if (btnClick != null) {
  437. for (i = 0; i < btnClick.length; i++) {
  438. if (isVisible(btnClick[i])) {
  439. btnClick[i].click();
  440. flgSubsDone = true;
  441. }
  442. }
  443. }
  444. }
  445. }
  446.  
  447. //---------------------------------------
  448. // Check all the parents of element to find whether it is visible or not
  449. //---------------------------------------
  450. function isVisible(pObj) {
  451. if (pObj != null) {
  452. var checkNext = true;
  453. var vObj = pObj;
  454.  
  455. while (checkNext) {
  456. checkNext = false;
  457. //console.log("checking element " + vObj.tagName + "#" + vObj.id + ": '" + document.defaultView.getComputedStyle(vObj,null)['display'] + "'");
  458. if (document.defaultView.getComputedStyle(vObj,null)['display'] != "none") {
  459. if (vObj.parentElement != null) {
  460. vObj = vObj.parentElement;
  461. checkNext = true;
  462. }
  463. } else {
  464. return false;
  465. }
  466. }
  467. return true;
  468. }
  469. return false;
  470. }
  471.  
  472.  
  473. })();
  474.  
  475. /*//Detect spinner at main comments
  476. var spinnerMain = $( "#primary div#replies div#expander tp-yt-paper-spinner#spinner[active]" );
  477. if (spinnerMain != null && spinnerMain.length > 0) {
  478. console.log("main active spinner detected");
  479. spinnerActive = true;
  480.  
  481. //Listen to spinner changes
  482. const spinnerCallback = function (mutationsList, observer) {
  483. expandComments();
  484.  
  485. //spinnerMain = $( "#primary div#replies div#expander tp-yt-paper-spinner#spinner[active]" );
  486. if (spinnerMain[0].getAttribute("active") == null || spinnerMain[0].getAttribute("active") == "") {
  487. console.log("main spinner deactivated");
  488. spinnerObserver.disconnet();
  489. }
  490. }
  491.  
  492. var spinnerNode = document.querySelector("#primary div#replies div#expander tp-yt-paper-spinner#spinner[active]");
  493. if (spinnerNode != null) {
  494. const spinnerObserver = new MutationObserver(spinnerCallback);
  495. spinnerObserver.observe(spinnerNode, {childList: true, subtree: true, attributes: true, characterData: true});
  496. }
  497.  
  498. } else if (spinnerActive) {
  499. console.log("spinner stopped");
  500. spinnerActive = false;
  501. expandComments();
  502. }*/