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

QingJ © 2025

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