Invidify Youtube

Werbung auf Youtube umgehen, indem Video-Links direkt auf Invidious verweisen

  1. // ==UserScript==
  2. // @name Invidify Youtube
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.9.0
  5. // @description Werbung auf Youtube umgehen, indem Video-Links direkt auf Invidious verweisen
  6. // @author Thomas Theiner
  7. // @match https://www.youtube.com/*
  8. // @match https://youtube.com/*
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
  10. // @grant none
  11. // @license MIT
  12. // @run-at document-start
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. window.addEventListener("load", e => {
  19. console.log("INVIDIFY initialisiert");
  20.  
  21. let configButton = document.createElement("button");
  22. configButton.type = "button";
  23. configButton.innerHTML = `<img src="https://websocket.bplaced.net/gear.png" />`;
  24. configButton.style.cssText = `z-index: 3000;
  25. background-color: #dddddd;
  26. position: fixed;
  27. top: 15px;
  28. left: 270px;
  29. border-radius: 3px;
  30. border-color: #dddddd;
  31. padding: 2px 10px 2px 10px;`;
  32.  
  33. let newButton = document.createElement("button");
  34. newButton.type = "button";
  35. newButton.innerText = "Invidify";
  36.  
  37. newButton.style.cssText = `z-index: 3000;
  38. background-color: #dddddd;
  39. position: fixed;
  40. top: 15px;
  41. left: 200px;
  42. border-radius: 3px;
  43. border-color: #dddddd;
  44. padding: 5px 10px 5px 10px;`;
  45.  
  46. let bodyEl = document.querySelector("body");
  47. bodyEl.prepend(configButton);
  48. bodyEl.prepend(newButton);
  49.  
  50. let instanceURL = window.localStorage.getItem("invidiousInstance");
  51. if(!instanceURL) {
  52. instanceURL = "https://iv.datura.network/";
  53. window.localStorage.setItem("invidiousInstance", instanceURL);
  54. }
  55.  
  56. newButton.addEventListener("click", e => {
  57.  
  58. // get the correct ytd-browse element
  59. let currentPage = window.location.href;
  60.  
  61. let ytdBrowse = "home";
  62. let relExclude = "";
  63. let ytdClass = "ytd-thumbnail";
  64.  
  65. if(currentPage.includes("feed")) {
  66. ytdBrowse = currentPage.match(/\/feed\/(.*)$/)[1];
  67. if(ytdBrowse.includes("?")) {
  68. ytdBrowse = ytdBrowse.match(/(.*)\?/)[1];
  69. }
  70. } else if(currentPage.includes("/watch?v=")) {
  71. ytdBrowse = "";
  72. relExclude = "[rel]:not([rel='null'])";
  73. } else if(currentPage.includes("gaming")) {
  74. ytdBrowse = "channels";
  75. } else if(currentPage.includes("UCYfdidRxbB8Qhf0Nx7ioOYw")) {
  76. ytdBrowse = "news";
  77. } else if(currentPage.includes("UCEgdi0XIXXZ-qJOFPf4JSKw")) {
  78. ytdBrowse = "sports";
  79. } else if(currentPage.includes("UCtFRv9O2AHqOZjjynzrv-xg")) {
  80. ytdBrowse = "learning";
  81. } else if(currentPage.includes("UC4R8DWoMoI7CAwX8_LjQHig")) {
  82. ytdBrowse = "live";
  83. } else if(currentPage.includes("/@") || currentPage.includes("/channel/")) {
  84. ytdBrowse = "channels";
  85. if(currentPage.includes("shorts")) {
  86. ytdClass = "reel-item-endpoint";
  87. }
  88. }
  89.  
  90. console.log("currentPage:", currentPage, "Section:", ytdBrowse, "relExclude:", relExclude);
  91.  
  92. let allLinks = ytdBrowse ? document.querySelector(`ytd-browse[page-subtype=${ytdBrowse}]`)?.querySelectorAll(`a.${ytdClass}${relExclude}`) : document.querySelector("ytd-watch-flexy").querySelectorAll(`a.${ytdClass}${relExclude}`);
  93.  
  94. if(currentPage.includes("results")) {
  95. allLinks = document.querySelector("ytd-two-column-search-results-renderer").querySelectorAll(`a.${ytdClass}${relExclude}`);
  96. }
  97.  
  98. console.log("allLinks", allLinks.length);
  99.  
  100. let infoLookup = {};
  101. let linkCollection = new Set();
  102. for(let link of allLinks) {
  103.  
  104. if(link.href.includes("/watch?v=") || link.href.includes("/shorts/")) {
  105. let videoID;
  106. if(link.href.includes("/watch?v=")) {
  107. videoID = link.href.match(/\/watch\?v\=(.*)$/)[1];
  108. }
  109. if(link.href.includes("/shorts/")) {
  110. videoID = link.href.match(/\/shorts\/(.*)$/)[1];
  111. }
  112.  
  113. if(videoID.includes("&")) {
  114. videoID = videoID.match(/(.*?)\&/)[1];
  115. }
  116. let image = link.querySelector("img");
  117. if(image) {
  118.  
  119. if(!image.src) {
  120. if(!instanceURL.includes("piped")) {
  121. image.src = `${instanceURL}vi/${videoID}/maxres.jpg`;
  122. } else {
  123. image.src = `https://pipedimg.adminforge.de/vi/${videoID}/hq720.jpg?host=i.ytimg.com`;
  124. }
  125. }
  126.  
  127. let details = link.parentNode.parentNode.parentNode.querySelector("#details");
  128.  
  129. if(!details) {
  130. details = link.parentNode.parentNode.querySelector(".details");
  131.  
  132. if(!details) {
  133. details = link.parentNode.parentNode.querySelector("#meta");
  134. }
  135. }
  136.  
  137. if(!details || !details.querySelector("#video-title")) {
  138. console.log("INVIDIFY: DETAILS NOT FOUND!", currentPage, videoID);
  139.  
  140. // could be shorts
  141. if(currentPage.includes("/shorts")) {
  142. console.log("Was a SHORTS video ...", videoID);
  143. linkCollection.add(videoID);
  144. infoLookup[videoID] = {
  145. imageSrc: image.src,
  146. title: "",
  147. channelName: "",
  148. channelLink: "",
  149. metaData: "",
  150. length: ""
  151. }
  152. }
  153. } else {
  154. linkCollection.add(videoID);
  155.  
  156. let title = details.querySelector("#video-title").innerText;
  157.  
  158. let channelInfo = details.querySelector("#channel-name");
  159. let channelName = channelInfo.innerText;
  160. let channelLink = channelInfo.querySelector("a")?.href;
  161.  
  162. let partChannelLink = channelLink?.match(/\/(\@.*)/) ? channelLink?.match(/\/(\@.*)/)[1] : channelLink?.match(/\/(channel.*)/) ? channelLink?.match(/\/(channel.*)/)[1] : channelLink?.match(/\/c\/(.*)/)[1];
  163.  
  164. let rowsInName = channelName.split("\n");
  165. if(rowsInName.length > 1) {
  166. channelName = rowsInName[2].trim();
  167. }
  168.  
  169. let metaData = details.querySelector("#metadata-line").innerText;
  170.  
  171. let timeStatus = link.querySelector("#time-status")?.innerText.trim();
  172. console.log(videoID, title, timeStatus, channelName, channelLink);
  173. // add Info
  174. infoLookup[videoID] = {
  175. imageSrc: image.src,
  176. title: title,
  177. channelName: channelName,
  178. channelLink: partChannelLink,
  179. metaData: metaData,
  180. length: timeStatus
  181. };
  182. }
  183.  
  184. }
  185. }
  186.  
  187. }
  188. let videoIDs = Array.from(linkCollection);
  189.  
  190. // prepend current videoID
  191. if(currentPage.includes("/watch?v=") || currentPage.includes("/shorts/")) {
  192. let videoID;
  193. if(currentPage.includes("/watch?v=")) {
  194. videoID = currentPage.match(/\/watch\?v\=(.*)$/)[1];
  195. }
  196. if(currentPage.includes("/shorts/")) {
  197. videoID = currentPage.match(/\/shorts\/(.*)$/)[1];
  198. }
  199. if(videoID.includes("&")) {
  200. videoID = videoID.match(/(.*)\&/)[1];
  201. }
  202. videoIDs.unshift(videoID);
  203.  
  204. let primaryVideo = document.querySelector("#primary.ytd-watch-flexy");
  205.  
  206. let metaInfo = primaryVideo.querySelector("#below #view-count").getAttribute("aria-label") + primaryVideo.querySelector("#below #date-text").getAttribute("aria-label");
  207. if(primaryVideo.querySelector("#info-container #info")) {
  208. metaInfo += " " + primaryVideo.querySelector("#info-container #info").innerText;
  209. }
  210.  
  211. infoLookup[videoID] = {
  212. imageSrc: instanceURL.includes("piped") ? `https://pipedimg.adminforge.de/vi/${videoID}/hq720.jpg?host=i.ytimg.com` : `${instanceURL}vi/${videoID}/maxres.jpg`,
  213. title: primaryVideo.querySelector("#below #title").innerText,
  214. channelName: primaryVideo.querySelector("#below #channel-name").innerText,
  215. channelLink: primaryVideo.querySelector("#below #channel-name").querySelector("a").href.match(/\/(\@.*)/)[1],
  216. metaData: metaInfo,
  217. length: "-:--"
  218. };
  219. }
  220.  
  221. // opening new window/tab
  222. let newWindow = window.open("", "_blank");
  223. let newDocument = newWindow.document;
  224.  
  225. newDocument.body.style.cssText = `font-family: Roboto, Arial, sans-serif;`;
  226.  
  227. let newLogoDiv = document.createElement("div");
  228. newLogoDiv.style.cssText = "margin-bottom: 20px;";
  229.  
  230. let newLogoImage = document.createElement("img");
  231. newLogoImage.src = "https://websocket.bplaced.net/invidify.png";
  232. newLogoDiv.appendChild(newLogoImage);
  233.  
  234. newDocument.body.appendChild(newLogoDiv);
  235.  
  236. for(let videoID of videoIDs) {
  237.  
  238. let newCard = document.createElement("div");
  239. newCard.style.cssText = `position: relative; margin: 10px; border-radius: 5px; width: 310px; height: 350px; display: inline-block; vertical-align: top;`;
  240.  
  241. let newLink = newDocument.createElement("a");
  242. newLink.href = `${instanceURL}watch?v=${videoID}`;
  243. newLink.target = "_blank";
  244. newLink.style.cssText = "text-decoration: none; color: black;";
  245. newLink.title = infoLookup[videoID].title;
  246.  
  247. let newImage = new Image();
  248. newImage.src = infoLookup[videoID].imageSrc;
  249.  
  250. console.log("Hier wird das Dingen geladen");
  251.  
  252. newImage.width = "310";
  253. newImage.style.cssText = "border-radius: 12px;";
  254. newLink.appendChild(newImage);
  255.  
  256. newImage.onload = () => {
  257. if(newImage.height === 233) {
  258. newImage.onload = null;
  259. newImage.src = instanceURL.includes("piped") ? `https://pipedimg.adminforge.de/vi/${videoID}/hq720.jpg?host=i.ytimg.com` : `${instanceURL}vi/${videoID}/maxres.jpg`;
  260. }
  261. };
  262.  
  263. if(infoLookup[videoID].length) {
  264. let newTimeBox = newDocument.createElement("div");
  265. newTimeBox.style.cssText = "color: white; background-color: black; border-radius: 4px; position: relative; float:right; top: -23px; padding: 2px; font-size: 0.75rem; margin-right: 5px;";
  266. newTimeBox.innerText = infoLookup[videoID].length;
  267.  
  268. newLink.appendChild(newTimeBox);
  269. }
  270.  
  271. let newTitleBox = document.createElement("div");
  272. newTitleBox.style.cssText = "position: relative; height: 3.2rem;";
  273.  
  274. let newNode = document.createElement("h3");
  275. newNode.style.cssText = "font-size: 1.1rem; line-height: 1.5rem; font-weight: 600; max-height: 3rem; overflow: hidden; text-overflow: ellipsis; -webkit-box-orient: vertical; display: -webkit-box; -webkit-line-clamp: 2;";
  276. newNode.innerText = infoLookup[videoID].title;
  277.  
  278. newTitleBox.appendChild(newNode);
  279. newLink.appendChild(newTitleBox);
  280.  
  281. newCard.appendChild(newLink);
  282.  
  283. let newChannel = document.createElement("h4");
  284. newChannel.style.cssText = "font-size: 0.9rem; font-weight: 100; color: #737373;";
  285. newChannel.innerHTML = infoLookup[videoID].channelName + "<br/>" + infoLookup[videoID].metaData;
  286.  
  287. if(infoLookup[videoID].channelLink) {
  288. let newChannelLink = document.createElement("a");
  289. newChannelLink.style.cssText = "text-decoration: none;";
  290. newChannelLink.target = "_blank";
  291. newChannelLink.href = `${instanceURL}${infoLookup[videoID].channelLink}`;
  292.  
  293. newChannelLink.appendChild(newChannel);
  294. newCard.appendChild(newChannelLink);
  295. } else {
  296. newCard.appendChild(newChannel);
  297. }
  298. newDocument.body.appendChild(newCard);
  299. }
  300.  
  301. }, false);
  302.  
  303. document.addEventListener("fullscreenchange", (e) => {
  304. if(document.fullscreenElement) {
  305. newButton.style.zIndex = -1;
  306. configButton.style.zIndex = -1;
  307. } else {
  308. newButton.style.zIndex = 3000;
  309. configButton.style.zIndex = 3000;
  310. }
  311. }, false);
  312.  
  313. let isOpen = false;
  314.  
  315. configButton.addEventListener("click", e => {
  316. e.stopPropagation();
  317.  
  318. if(!isOpen) {
  319. isOpen = true;
  320. instanceURL = window.localStorage.getItem("invidiousInstance");
  321.  
  322. let configDiv = document.createElement("div");
  323. configDiv.id = "configDiv";
  324. configDiv.style.cssText = `z-index: 3000;
  325. background-color: #f3f3f3;
  326. position: fixed;
  327. top: 50px;
  328. left: 270px;
  329. border-radius: 3px;
  330. border: 1px solid #000000;
  331. font-size: 1.2rem;
  332. padding: 20px;`;
  333.  
  334. let configInput = document.createElement("input");
  335. configInput.size = "50";
  336. configInput.id = "invidiousConfig";
  337. configInput.type = "text";
  338. configInput.style.cssText = "margin-bottom: 20px;";
  339. configInput.value = instanceURL;
  340.  
  341. let configLabel = document.createElement("label");
  342. configLabel.id = "configLabel";
  343. configLabel.htmlFor = "invidiousConfig";
  344. configLabel.innerText = "Invidious Instance Domain or URL";
  345.  
  346. let configBr = document.createElement("br");
  347.  
  348. let configButtonDiv = document.createElement("div");
  349. configButtonDiv.id = "configButtons";
  350.  
  351. let configOk = document.createElement("button");
  352. configOk.type = "button";
  353. configOk.innerText = "Ok";
  354. configOk.style.cssText = "margin-right: 10px;";
  355.  
  356. let configCancel = document.createElement("button");
  357. configCancel.type = "button";
  358. configCancel.innerText = "Cancel";
  359.  
  360. configButtonDiv.appendChild(configOk);
  361. configButtonDiv.appendChild(configCancel);
  362.  
  363. configDiv.appendChild(configLabel);
  364. configDiv.appendChild(configBr);
  365. configDiv.appendChild(configInput);
  366. configDiv.appendChild(configButtonDiv);
  367.  
  368. document.body.prepend(configDiv);
  369.  
  370. function closeConfig() {
  371. configDiv.parentNode.removeChild(configDiv);
  372. isOpen = false;
  373. }
  374.  
  375. configOk.addEventListener("click", btnE => {
  376. instanceURL = configInput.value;
  377. if(!instanceURL.startsWith("https://")) {
  378. instanceURL = "https://" + instanceURL;
  379. }
  380. if(!instanceURL.endsWith("/")) {
  381. instanceURL += "/";
  382. }
  383. window.localStorage.setItem("invidiousInstance", instanceURL);
  384. closeConfig();
  385. }, false);
  386.  
  387. configCancel.addEventListener("click", btnE => {
  388. closeConfig();
  389. }, false);
  390.  
  391. console.log("Outside click handler added!");
  392. document.body.addEventListener("click", e => {
  393. console.log(e.target);
  394. if(isOpen && e.target.id !== "configDiv" && e.target.id !== "invidiousConfig" && e.target.id !== "configButtons" && e.target.id !== "configLabel") {
  395. closeConfig();
  396. }
  397. }, false);
  398.  
  399. }
  400. }, false);
  401. }, false);
  402. })();