About tab for Invidious

A userscript that adds an about tab to channel pages in Invidious

  1. // ==UserScript==
  2. // @name About tab for Invidious
  3. // @namespace https://greasyfork.org/en/users/1028674-yacine-book
  4. // @version 1.0.1
  5. // @license MIT
  6. // @description A userscript that adds an about tab to channel pages in Invidious
  7. // @author Yacine Book
  8. // @match https://yewtu.be/channel/*
  9. // @match https://invidious.flokinet.to/channel/*
  10. // @match https://invidious.protokolla.fi/channel/*
  11. // @icon https://www.google.com/s2/favicons?sz=64&domain=yewtu.be
  12. // @connect yt.lemnoslife.com
  13. // @connect *
  14. // @run-at document-end
  15. // @grant GM.xmlHttpRequest
  16. // @grant GM_xmlhttpRequest
  17. // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
  18. // ==/UserScript==
  19.  
  20. (function() {
  21. 'use strict';
  22.  
  23. const queryString = window.location.search;
  24. console.log(queryString);
  25.  
  26. const urlParams = new URLSearchParams(queryString);
  27.  
  28. const about = urlParams.get('/about')
  29. console.log(about);
  30.  
  31. const node = document.getElementById("descriptionWrapper");
  32. node.setAttribute("hidden", "");
  33.  
  34. const tabContainer = document.querySelector(".flexible.title~.pure-g.h-box>.pure-u-1-2:nth-of-type(1)");
  35. const aboutTabContainer = document.createElement("div");
  36. aboutTabContainer.classList.add("pure-u-1", "pure-md-1-3");
  37. tabContainer.appendChild(aboutTabContainer);
  38. const aboutTab = document.createElement("a");
  39. aboutTab.innerHTML = 'About';
  40. aboutTab.href = '/channel/' + window.location.pathname.split("/").slice(2, 3) + '/?/about=1';
  41. aboutTab.classList.add("about-tab");
  42. aboutTabContainer.appendChild(aboutTab);
  43.  
  44. const style = document.createElement('style');
  45. style.innerHTML = `
  46. .about-page-columns {
  47. display: flex;
  48. flex-direction: row;
  49. flex-wrap: wrap;
  50. }
  51. #descriptionWrapper {
  52. max-width: unset;
  53. border-bottom: 1px solid;
  54. padding-bottom: 10px;
  55. margin-bottom: 10px;
  56. }
  57. .ap-col {
  58. flex: 2;
  59. }
  60. .ap-col.num-2 {
  61. flex: 1;
  62. }
  63. .ap-col.num-1 {
  64. min-width: 0;
  65. padding-right: 60px;
  66. }
  67.  
  68. p#apJoinDate.join-date-mobile {
  69. display: none;
  70. }
  71.  
  72. div#linkListContainer.link-list-mobile {
  73. display: none;
  74. }
  75.  
  76. @media (max-width: 760px) {
  77. .about-page-columns {
  78. flex-direction: column;
  79. }
  80. .ap-col.num-1 {
  81. padding-right: 0;
  82. }
  83. p#apJoinDate.join-date-mobile {
  84. display: block;
  85. }
  86. .ap-col.num-2 p {
  87. text-align: center;
  88. }
  89. .ap-col.num-2 p#apJoinDate {
  90. display: none;
  91. }
  92. .ap-redirect-links-container {
  93. text-align: center;
  94. }
  95. .ap-col.num-2 h3.about-page-subheadline {
  96. display: none;
  97. }
  98. div#linkListContainer.link-list-mobile {
  99. display: block;
  100. }
  101. h3.about-page-subheadline#subheadlineLinks {
  102. display: none;
  103. }
  104. h3.about-page-subheadline#subheadlineLinks~#descriptionWrapper {
  105. display: none;
  106. }
  107. }
  108.  
  109. h3.about-page-subheadline {
  110. margin-bottom: 5px;
  111. }
  112. .ap-col.num-2 h3.about-page-subheadline, .ap-col.num-2 p {
  113. margin-bottom: 0px;
  114. margin-top: 10px;
  115. padding: 8px 0;
  116. border-bottom: 1px solid;
  117. }
  118. .ap-col.num-2 p {
  119. margin-top: 0;
  120. }
  121. .flexible.title~.pure-g.h-box>.pure-u-1-2:nth-of-type(1) .pure-u-1.pure-md-1-3:nth-of-type(1), .flexible.title~.pure-g.h-box>.pure-u-1-2:nth-of-type(1) .pure-u-1.pure-md-1-3:nth-of-type(2) {
  122. display: none;
  123. }
  124. .ap-redirect-links-container {
  125. display: flex;
  126. flex-direction: column;
  127. margin-top: 13px;
  128. }
  129. .ap-redirect-links-container a {
  130. padding: 8px 0;
  131. border-bottom: 1px solid;
  132. font-size: 14px;
  133. }
  134. .channel-external-link {
  135. margin: 10px 0;
  136. font-size: 14px;
  137. display: flex;
  138. flex-direction: row;
  139. align-items: center;
  140. }
  141. `;
  142. style.id = 'aboutPageStyle';
  143. style.type = 'text/css';
  144. document.querySelector("html").appendChild(style);
  145.  
  146. if (about == "1") {
  147. node.removeAttribute("hidden");
  148.  
  149. const hBox = document.createElement("div");
  150. hBox.classList.add("h-box");
  151. document.getElementById("contents").appendChild(hBox);
  152.  
  153. const aboutPage = document.createElement("div");
  154. aboutPage.classList.add("about-page");
  155. hBox.appendChild(aboutPage);
  156.  
  157. const aboutPageCols = document.createElement("div");
  158. aboutPageCols.classList.add("about-page-columns");
  159. aboutPage.appendChild(aboutPageCols);
  160.  
  161. const apCol1 = document.createElement("div");
  162. apCol1.classList.add("ap-col", "num-1");
  163. aboutPageCols.appendChild(apCol1);
  164.  
  165. const apCol2 = document.createElement("div");
  166. apCol2.classList.add("ap-col", "num-2");
  167. aboutPageCols.appendChild(apCol2);
  168.  
  169. const aboutPageHeader = document.createElement("h3");
  170. aboutPageHeader.classList.add("about-page-subheadline");
  171. aboutPageHeader.innerHTML = 'About';
  172. apCol1.appendChild(aboutPageHeader);
  173.  
  174. const aboutPageHeader2 = document.createElement("h3");
  175. aboutPageHeader2.classList.add("about-page-subheadline");
  176. aboutPageHeader2.innerHTML = 'Stats';
  177. apCol2.appendChild(aboutPageHeader2);
  178.  
  179. const aboutPageJoin = document.createElement("p");
  180. aboutPageJoin.id = "apJoinDate";
  181. aboutPageJoin.innerHTML = `
  182. <div style="opacity: 0.5; font-style: italic;">Retrieving info from server...</div>
  183. `;
  184. apCol2.appendChild(aboutPageJoin);
  185.  
  186. const aboutPageViews = document.createElement("p");
  187. aboutPageViews.id = "apViewCount";
  188. aboutPageViews.innerHTML = `
  189. <div style="opacity: 0.5; font-style: italic;">Retrieving info from server...</div>
  190. `;
  191. apCol2.appendChild(aboutPageViews);
  192.  
  193. const aboutPageVideoCount = document.createElement("p");
  194. aboutPageVideoCount.id = "apVideoCount";
  195. aboutPageVideoCount.innerHTML = `
  196. <div style="opacity: 0.5; font-style: italic;">Retrieving info from server...</div>
  197. `;
  198. apCol2.appendChild(aboutPageVideoCount);
  199.  
  200. const apLinksCont = document.createElement("div");
  201. apLinksCont.classList.add("ap-redirect-links-container");
  202. apCol2.appendChild(apLinksCont);
  203.  
  204. const node2 = document.getElementById("descriptionWrapper");
  205. const clone = node2.cloneNode(true);
  206. node.setAttribute("hidden", "");
  207. apCol1.appendChild(clone);
  208.  
  209. const aboutPageJoinMob = document.createElement("p");
  210. aboutPageJoinMob.id = "apJoinDate";
  211. aboutPageJoinMob.classList.add("join-date-mobile");
  212. aboutPageJoinMob.innerHTML = `
  213. <div style="opacity: 0.5; margin: -37px 0; font-style: italic;">Retrieving info from server...</div>
  214. `;
  215. clone.appendChild(aboutPageJoinMob);
  216.  
  217. const aboutPageLinks = document.createElement("div");
  218. aboutPageLinks.id = "linkListContainer";
  219.  
  220. const aboutPageLinksMob = document.createElement("div");
  221. aboutPageLinksMob.id = "linkListContainer";
  222. aboutPageLinksMob.classList.add("link-list-mobile");
  223. clone.appendChild(aboutPageLinksMob);
  224.  
  225. const viewOnYT = document.querySelector('a[href*="youtube.com"]');
  226. apLinksCont.appendChild(viewOnYT);
  227. const switchInstance = document.querySelector('a[href*="redirect.invidious.io"]');
  228. apLinksCont.appendChild(switchInstance);
  229.  
  230. const hideVideos = document.createElement('style');
  231. hideVideos.innerHTML = `
  232. .pure-u-1.pure-u-md-1-4 {
  233. display: none!important;
  234. }
  235. .page-nav-container {
  236. display: none;
  237. }
  238. `;
  239. document.querySelector("body").appendChild(hideVideos);
  240.  
  241. const footer = document.querySelector("footer");
  242. document.getElementById("contents").appendChild(footer);
  243.  
  244. const newAboutTab = document.createElement("b");
  245. newAboutTab.innerHTML = 'About';
  246. newAboutTab.classList.add("about-tab");
  247. aboutTab.replaceWith(newAboutTab);
  248.  
  249. var videoTab = document.querySelectorAll('b');
  250. for(var i=0;i<videoTab.length;i++){
  251. console.log(videoTab[i].innerHTML)
  252. if(videoTab[i].innerHTML == 'Videos'){
  253. var newVideoTab = document.createElement('a');
  254. newVideoTab.innerHTML = 'Videos';
  255. newVideoTab.href = '/channel/' + window.location.pathname.split("/").slice(2, 3) + '/videos';
  256. videoTab[i].replaceWith(newVideoTab);
  257. break;
  258. }
  259. }
  260.  
  261. const options = document.querySelector(".flexible.title~.pure-g.h-box>.pure-u-1-2~.pure-u-1-2 .pure-md-1-3");
  262. options.remove();
  263. const options2 = document.querySelector(".flexible.title~.pure-g.h-box>.pure-u-1-2~.pure-u-1-2 .pure-md-1-3");
  264. options2.remove();
  265. const options3 = document.querySelector(".flexible.title~.pure-g.h-box>.pure-u-1-2~.pure-u-1-2 .pure-md-1-3");
  266. options3.remove();
  267.  
  268. const ucid = window.location.pathname.split("/").slice(2, 3);
  269. const descWrapper = document.getElementById("descriptionWrapper");
  270.  
  271. if (descWrapper && aboutPageJoin) {
  272. GM.xmlHttpRequest({
  273. url: "https://yt.lemnoslife.com/noKey/channels?part=snippet,status&id=" + ucid,
  274. onload: (response) => {
  275. const data = JSON.parse(response.responseText);
  276. console.log(data);
  277.  
  278. aboutPageJoin.innerHTML = '<span>Joined </span>' + data.items[0].snippet.publishedAt.toLocaleString();
  279. aboutPageJoinMob.innerHTML = '<span>Joined </span>' + data.items[0].snippet.publishedAt.toLocaleString();
  280. },
  281. });
  282. }
  283.  
  284. if (descWrapper && aboutPageViews) {
  285. GM.xmlHttpRequest({
  286. url: "https://yt.lemnoslife.com/channels?part=snippet,status,about&id=" + ucid,
  287. onload: (response) => {
  288. const data = JSON.parse(response.responseText);
  289. console.log(data);
  290.  
  291. aboutPageViews.innerHTML = "<b>" + data.items[0].about.stats.viewCount.toLocaleString() + "</b> views";
  292. aboutPageVideoCount.innerHTML = "<b>" + data.items[0].about.stats.videoCount.toLocaleString() + "</b> videos";
  293. if (data.items[0].about.details.location) {
  294. const aboutPageHeaderDetails = document.createElement("h3");
  295. aboutPageHeaderDetails.classList.add("about-page-subheadline");
  296. aboutPageHeaderDetails.id = "subheadlineDetails";
  297. aboutPageHeaderDetails.innerHTML = 'Details';
  298. apCol1.appendChild(aboutPageHeaderDetails);
  299.  
  300. const detailsWrapper = document.createElement("div");
  301. detailsWrapper.id = "descriptionWrapper";
  302. apCol1.appendChild(detailsWrapper);
  303.  
  304. const detailsWrapperP = document.createElement("p");
  305. detailsWrapperP.innerHTML = '<b>Location: </b>' + data.items[0].about.details.location;
  306. detailsWrapperP.style = "opacity: 0.8;";
  307. detailsWrapper.appendChild(detailsWrapperP);
  308. }
  309.  
  310. if (!data.items[0].about.links.length == 0) {
  311. const aboutPageHeaderLinks = document.createElement("h3");
  312. aboutPageHeaderLinks.classList.add("about-page-subheadline");
  313. aboutPageHeaderLinks.id = "subheadlineLinks";
  314. aboutPageHeaderLinks.innerHTML = 'Links';
  315. apCol1.appendChild(aboutPageHeaderLinks);
  316.  
  317. const linksWrapper = document.createElement("div");
  318. linksWrapper.id = "descriptionWrapper";
  319. apCol1.appendChild(linksWrapper);
  320. linksWrapper.appendChild(aboutPageLinks);
  321. data.items[0].about.links.forEach(item => {
  322. const externalLink = document.createElement("div");
  323. externalLink.classList.add("channel-external-link");
  324. aboutPageLinks.appendChild(externalLink);
  325.  
  326. const externalLinkTextCont = document.createElement("div");
  327. externalLinkTextCont.classList.add("channel-external-link-text-container");
  328. externalLink.appendChild(externalLinkTextCont);
  329.  
  330. const externalLinkText = document.createElement("a");
  331. externalLinkText.classList.add("channel-external-link-text");
  332. externalLinkText.innerHTML = item.title;
  333. externalLinkText.href = item.url;
  334. externalLinkText.target = "_blank";
  335. externalLinkTextCont.appendChild(externalLinkText);
  336. });
  337. data.items[0].about.links.forEach(item => {
  338. const externalLink = document.createElement("div");
  339. externalLink.classList.add("channel-external-link");
  340. aboutPageLinksMob.appendChild(externalLink);
  341.  
  342. const externalLinkTextCont = document.createElement("div");
  343. externalLinkTextCont.classList.add("channel-external-link-text-container");
  344. externalLink.appendChild(externalLinkTextCont);
  345.  
  346. const externalLinkText = document.createElement("a");
  347. externalLinkText.classList.add("channel-external-link-text");
  348. externalLinkText.innerHTML = item.title;
  349. externalLinkText.href = item.url;
  350. externalLinkText.target = "_blank";
  351. externalLinkTextCont.appendChild(externalLinkText);
  352. });
  353. }
  354.  
  355. },
  356. });
  357. }
  358. }
  359. })();