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 "View all N replies" button

当前为 2024-03-06 提交的版本,查看 最新版本

  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 "View all N 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.6
  11. // @license MIT
  12. // @grant none
  13. // @require https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js
  14. // @run-at document-end
  15. // ==/UserScript==
  16.  
  17. (function () {
  18. 'use strict';
  19.  
  20. var videoIdAtLastCheck = "";
  21. var btnClick = null;
  22. var waitVideo;
  23. var i;
  24.  
  25. var observerBody = null;
  26. var observerSubs = null;
  27. var flgSubsDone = false;
  28. var observerDesc = null;
  29. var observerComments = null;
  30. var observerCommentsNotif = null;
  31. var observerComPost = null;
  32.  
  33. //---
  34. //--- Listen to global page changes
  35. //---
  36. const callbackBody = function (mutationsList, observer) {
  37.  
  38. var pathArray = window.location.pathname.split('/');
  39. var firstPath = pathArray[1];
  40. var lastPath = pathArray[pathArray.length - 1];
  41.  
  42. //Check whether video is new to expand description
  43. if (firstPath === "watch") {
  44. //var player = $( "div#content ytd-watch-flexy" );
  45. var player = document.querySelectorAll("div#content ytd-watch-flexy");
  46. if (player != null && player.length > 0) {
  47. clearInterval(waitVideo); //Stop waiting for video
  48. var videoId = player[0].getAttribute("video-id");
  49. player = null;
  50.  
  51. if (videoIdAtLastCheck != videoId) {
  52. videoIdAtLastCheck = videoId;
  53. expandDesc();
  54. }
  55. }
  56. }
  57.  
  58. /* //---
  59. //--- Listen to description and expand it
  60. //---
  61. if (observerDesc == null) {
  62. const callbackDesc = function (mutationsList, observer) {
  63. //Check whether video is new to expand description
  64. if (firstPath === "watch") {
  65. var player = $( "div#content ytd-watch-flexy" );
  66. if (player != null && player.length > 0) {
  67. clearInterval(waitVideo); //Stop waiting for video
  68. var videoId = player[0].getAttribute("video-id");
  69. player = null;
  70.  
  71. if (videoIdAtLastCheck != videoId) {
  72. videoIdAtLastCheck = videoId;
  73. console.log("new video");
  74. expandDesc();
  75. }
  76. }
  77. }
  78. }
  79. let nodeDesc = document.querySelector("#player");//document.querySelector(".watch-active-metadata");
  80. if (nodeDesc != null) {
  81. observerDesc = new MutationObserver(callbackDesc);
  82. observerDesc.observe(nodeDesc, {childList: true, subtree: true, attributes: true, characterData: false});
  83. console.log("desc observer added");
  84. }
  85. }*/
  86.  
  87. //Remove subscriptions observer after subscriptions have been expanded
  88. if (flgSubsDone && observerSubs != null) {
  89. observerSubs.disconnect(); //Expand subscriptions only once
  90. observerSubs = null;
  91. }
  92.  
  93. //---
  94. //--- Listen to community posts and expand them
  95. //---
  96. if (observerComPost == null && lastPath === "community") {
  97. const callbackComPost = function (mutationsList, observer) {
  98. expandComPosts();
  99. }
  100. let nodeComPost = document.querySelector("#primary #contents #contents");
  101. if (nodeComPost != null) {
  102. observerComPost = new MutationObserver(callbackComPost);
  103. observerComPost.observe(nodeComPost, {childList: true, subtree: true, attributes: true, characterData: false});
  104. }
  105. }
  106. //Remove community post observer on non-community pages
  107. if (observerComPost != null && lastPath != "community") {
  108. observerComPost.disconnect();
  109. observerComPost = null;
  110. }
  111.  
  112. //---
  113. //--- Listen to comments and expand them
  114. //---
  115. if (observerComments == null && (firstPath === "watch" || firstPath === "post" || firstPath === "shorts" || lastPath === "community")) {
  116. const callbackComments = function (mutationsList, observer) {
  117. expandComments();
  118. }
  119. let nodeComments = null;
  120. if (firstPath === "shorts") {
  121. nodeComments = document.querySelector("ytd-shorts ytd-comments:not([hidden='']");
  122. if (nodeComments != null) {
  123. observerComments = new MutationObserver(callbackComments);
  124. observerComments.observe(nodeComments, {childList: true, subtree: true, attributes: true, characterData: false});
  125. }
  126. } else {
  127. nodeComments = document.querySelector("#primary ytd-comments:not([hidden='']");
  128. if (nodeComments != null) {
  129. observerComments = new MutationObserver(callbackComments);
  130. observerComments.observe(nodeComments, {childList: true, subtree: true, attributes: true, characterData: false});
  131. }
  132. }
  133. }
  134. //Remove comments observer
  135. if (observerComments != null && firstPath != "watch" && firstPath != "shorts" && firstPath != "post" && lastPath != "community") {
  136. observerComments.disconnect();
  137. observerComments = null;
  138. }
  139.  
  140. //---
  141. //--- Listen to comments in notification submenu and expand them
  142. //---
  143. if (observerCommentsNotif == null) {
  144. const callbackCommentsNotif = function (mutationsList, observer) {
  145. expandCommentsNotif();
  146. }
  147. let nodeCommentsNotif = null;
  148. nodeCommentsNotif = document.querySelector("ytd-popup-container #contentWrapper");
  149. if (nodeCommentsNotif != null) {
  150. observerCommentsNotif = new MutationObserver(callbackCommentsNotif);
  151. observerCommentsNotif.observe(nodeCommentsNotif, {childList: true, subtree: true, attributes: true, characterData: false});
  152. }
  153. }
  154. }
  155.  
  156. let nodeBody = document.querySelector("body");
  157. if (nodeBody != null) {
  158. const observerBody = new MutationObserver(callbackBody);
  159. observerBody.observe(nodeBody, {childList: true, subtree: true, attributes: true, characterData: false});
  160. }
  161.  
  162. //---
  163. //--- Listen to subscriptions and expand them
  164. //---
  165. const callbackSubs = function (mutationsList, observer) {
  166. expandSubs();
  167. }
  168. let nodeSubs = document.querySelector("div#guide-wrapper");
  169. if (nodeSubs != null) {
  170. observerSubs = new MutationObserver(callbackSubs);
  171. observerSubs.observe(nodeSubs, {childList: true, subtree: true, attributes: true, characterData: false});
  172. }
  173.  
  174. /*//Check what buttons to click after each scroll of main window
  175. $( window ).scroll(function() {
  176. //expandComPosts();
  177. expandComments();
  178. });*/
  179.  
  180.  
  181. //---------------------------------------
  182. // Expand description
  183. //---------------------------------------
  184. function expandDesc() {
  185. //Expand description
  186. /*btnClick = $( "div#description ytd-text-inline-expander tp-yt-paper-button#expand[role='button']:not([hidden=''])" );
  187. if (btnClick != null && btnClick.length > 0) {// && isVisible(btnClick)) {
  188. btnClick[0].click();
  189. }*/
  190.  
  191. //Expand description - suggested by gcobc12632
  192. //btnClick = $( "yt-interaction#description-interaction" );
  193. btnClick = document.querySelector("yt-interaction#description-interaction");
  194. if (btnClick != null) {// && isVisible(btnClick)) {
  195. btnClick.click();
  196. }
  197.  
  198. //Expand description after 7ktTube | 2016 REDUX script
  199. //btnClick = $( "div#meta-contents ytd-video-secondary-info-renderer div ytd-expander tp-yt-paper-button#more:not([hidden=''])" );
  200. btnClick = document.querySelectorAll("div#meta-contents ytd-video-secondary-info-renderer div ytd-expander tp-yt-paper-button#more:not([hidden=''])");
  201. if (btnClick != null && btnClick.length > 0) {// && isVisible(btnClick)) {
  202. btnClick[0].click();
  203. }
  204. }
  205.  
  206. //---------------------------------------
  207. // Expand post in community section
  208. //---------------------------------------
  209. function expandComPosts() {
  210. //btnClick = $( "div#post tp-yt-paper-button#more:not([hidden='']) > span.more-button" );
  211. btnClick = document.querySelectorAll("#post tp-yt-paper-button#more:not([hidden='']) > span.more-button");
  212. if (btnClick != null && btnClick.length > 0) {
  213. for (i = 0; i < btnClick.length; i++) {
  214. btnClick[i].click();
  215. btnClick[i].parentNode.previousElementSibling.setAttribute("style", "display:none;"); //Hide "Show less" button
  216. }
  217. }
  218. }
  219.  
  220. //---------------------------------------
  221. // Expand comments
  222. //---------------------------------------
  223. function expandComments() {
  224. //Expand long comments and hide "show less" button in comments section
  225. //btnClick = $( "ytd-comment-renderer tp-yt-paper-button#more:not([hidden='']) > span.more-button" );
  226. btnClick = document.querySelectorAll("ytd-comment-renderer tp-yt-paper-button#more:not([hidden='']) > span.more-button");
  227. if (btnClick != null) {
  228. for (i = 0; i < btnClick.length; i++) {
  229. btnClick[i].click();
  230. btnClick[i].setAttribute("clicked-by-script", "true"); //Do not click it again
  231. btnClick[i].parentNode.previousElementSibling.setAttribute("style", "display:none;"); //Hide "Show less" button
  232. }
  233. }
  234.  
  235. //Show all replies upon pressing "Show more replies" button (not in notification submenu)
  236. //btnClick = $( "#primary div#replies div#expander div#expander-contents:not([hidden]) div#button.ytd-continuation-item-renderer ytd-button-renderer.ytd-continuation-item-renderer button.yt-spec-button-shape-next:not([clicked-by-script='true'])" );
  237. btnClick = document.querySelectorAll("#primary div#replies div#expander div#expander-contents:not([hidden]) div#button.ytd-continuation-item-renderer ytd-button-renderer.ytd-continuation-item-renderer button.yt-spec-button-shape-next:not([clicked-by-script='true'])");
  238. if (btnClick != null) {
  239. for (i = 0; i < btnClick.length; i++) {
  240. btnClick[i].click();
  241. btnClick[i].setAttribute("clicked-by-script", "true"); //Do not click it again
  242. }
  243. }
  244.  
  245. //Show all replies upon pressing "View all N replies" button (7ktTube | 2016 REDUX script)
  246. //btnClick = $( "#primary div#replies div#expander div#expander-contents:not([hidden]) div#button.ytd-continuation-item-renderer ytd-button-renderer tp-yt-paper-button#button[role='button']:not([clicked-by-script='true'])" );
  247. btnClick = document.querySelectorAll("#primary div#replies div#expander div#expander-contents:not([hidden]) div#button.ytd-continuation-item-renderer ytd-button-renderer tp-yt-paper-button#button[role='button']:not([clicked-by-script='true'])");
  248. if (btnClick != null) {
  249. for (i = 0; i < btnClick.length; i++) {
  250. btnClick[i].click();
  251. btnClick[i].setAttribute("clicked-by-script", "true"); //Do not click it again
  252. }
  253. }
  254. }
  255.  
  256. //---------------------------------------
  257. // Expand comments in notification submenu
  258. //---------------------------------------
  259. function expandCommentsNotif() {
  260. //Expand long comments and hide "show less" button in notification submenu
  261. //btnClick = $( "#submenu ytd-comment-renderer #comment-content tp-yt-paper-button#more:not([hidden='']) > span.more-button[slot='more-button']:not([clicked-by-script='true'])" );
  262. btnClick = document.querySelectorAll("#submenu ytd-comment-renderer #comment-content tp-yt-paper-button#more:not([hidden='']) > span.more-button[slot='more-button']:not([clicked-by-script='true'])");
  263. if (btnClick != null) {
  264. for (i = 0; i < btnClick.length; i++) {
  265. btnClick[i].click();
  266. btnClick[i].setAttribute("clicked-by-script", "true"); //Do not click it again
  267. btnClick[i].parentNode.previousElementSibling.setAttribute("style", "display:none;"); //Hide "Show less" button
  268. }
  269. }
  270. }
  271.  
  272. //---------------------------------------
  273. // Show all subscriptions
  274. //---------------------------------------
  275. function expandSubs() {
  276. //btnClick = $( "#guide div#sections div#items ytd-guide-collapsible-entry-renderer.ytd-guide-section-renderer[can-show-more=''] #expander-item" );
  277. btnClick = document.querySelectorAll("#guide div#sections div#items ytd-guide-collapsible-entry-renderer.ytd-guide-section-renderer[can-show-more=''] #expander-item");
  278. if (btnClick != null) {
  279. for (i = 0; i < btnClick.length; i++) {
  280. if (isVisible(btnClick[i])) {
  281. btnClick[i].click();
  282. flgSubsDone = true;
  283. }
  284. }
  285. }
  286. }
  287.  
  288. //---------------------------------------
  289. // Check all the parents of element to find whether it is visible or not
  290. //---------------------------------------
  291. function isVisible(pObj) {
  292. if (pObj != null) {
  293. var checkNext = true;
  294. var vObj = pObj;
  295.  
  296. while (checkNext) {
  297. checkNext = false;
  298. //console.log("checking element " + vObj.tagName + "#" + vObj.id + ": '" + document.defaultView.getComputedStyle(vObj,null)['display'] + "'");
  299. if (document.defaultView.getComputedStyle(vObj,null)['display'] != "none") {
  300. if (vObj.parentElement != null) {
  301. vObj = vObj.parentElement;
  302. checkNext = true;
  303. }
  304. } else {
  305. return false;
  306. }
  307. }
  308. return true;
  309. }
  310. return false;
  311. }
  312.  
  313. })();
  314.  
  315. /*//Detect spinner at main comments
  316. var spinnerMain = $( "#primary div#replies div#expander tp-yt-paper-spinner#spinner[active]" );
  317. if (spinnerMain != null && spinnerMain.length > 0) {
  318. console.log("main active spinner detected");
  319. spinnerActive = true;
  320.  
  321. //Listen to spinner changes
  322. const spinnerCallback = function (mutationsList, observer) {
  323. expandComments();
  324.  
  325. //spinnerMain = $( "#primary div#replies div#expander tp-yt-paper-spinner#spinner[active]" );
  326. if (spinnerMain[0].getAttribute("active") == null || spinnerMain[0].getAttribute("active") == "") {
  327. console.log("main spinner deactivated");
  328. spinnerObserver.disconnet();
  329. }
  330. }
  331.  
  332. var spinnerNode = document.querySelector("#primary div#replies div#expander tp-yt-paper-spinner#spinner[active]");
  333. if (spinnerNode != null) {
  334. const spinnerObserver = new MutationObserver(spinnerCallback);
  335. spinnerObserver.observe(spinnerNode, {childList: true, subtree: true, attributes: true, characterData: true});
  336. }
  337.  
  338. } else if (spinnerActive) {
  339. console.log("spinner stopped");
  340. spinnerActive = false;
  341. expandComments();
  342. }*/