Created
August 3, 2025 11:21
-
-
Save Chucky2401/3f73eee7a4d57eeaec47a3f909d58333 to your computer and use it in GitHub Desktop.
Youtube | Add link to slef hosted Invidious
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Invidious | |
| // @namespace http://tampermonkey.net/ | |
| // @version 1.1.0 | |
| // @description Add Invidious link under Youtube title on home page and search results, and a button on video page | |
| // @author Tristan_JVShow | |
| // @match https://www.youtube.com/* | |
| // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com | |
| // @grant GM_registerMenuCommand | |
| // @grant GM_setValue | |
| // @grant GM_getValue | |
| // ==/UserScript== | |
| (function() { | |
| 'use strict'; | |
| function addInvidiousLink() { | |
| const links = document.getElementsByClassName("yt-lockup-metadata-view-model-wiz__title"); | |
| const url = GM_getValue("invidiousUrl", "notSet"); | |
| for (const element of links) { | |
| const re = /https:\/\/www\.youtube\.com\/watch\?v=(.+)(?:&.+)?/ | |
| var link = element.href; | |
| var title = element.parentElement; | |
| if (title.querySelector('.go-to-invidious')) { | |
| continue; | |
| } | |
| const videoMatch = link.match(re); | |
| const videoId = videoMatch[1]; | |
| var div = document.createElement("a"); | |
| div.style.color = "white"; | |
| div.style.cursor = 'pointer'; | |
| div.style.fontWeight = 'bold'; | |
| div.style.color = "cornflowerblue"; | |
| div.classList.add("go-to-invidious"); | |
| div.innerHTML = "Watch on Invidious"; | |
| div.setAttribute("id", "goToInvidious"); | |
| div.href = url + "/watch?v=" + videoId; | |
| div.target = "_blank"; | |
| title.appendChild(div); | |
| } | |
| } | |
| function addInvidiousLinkSearch() { | |
| const links = document.getElementsByClassName("yt-simple-endpoint style-scope ytd-video-renderer"); | |
| const url = GM_getValue("invidiousUrl", "notSet"); | |
| for (const element of links) { | |
| if (element.id != 'video-title') { | |
| continue | |
| } | |
| const re = /https:\/\/www\.youtube\.com\/watch\?v=(.+)(?:&.+)?/ | |
| var link = element.href; | |
| var title = element.parentElement; | |
| if (title.querySelector('.go-to-invidious')) { | |
| continue; | |
| } | |
| const videoMatch = link.match(re); | |
| const videoId = videoMatch[1]; | |
| var div = document.createElement("a"); | |
| div.style.color = "white"; | |
| div.style.cursor = 'pointer'; | |
| div.style.fontWeight = 'bold'; | |
| div.style.color = "cornflowerblue"; | |
| div.classList.add("go-to-invidious"); | |
| div.innerHTML = "Watch on Invidious"; | |
| div.setAttribute("id", "goToInvidious"); | |
| div.href = url + "/watch?v=" + videoId; | |
| div.target = "_blank"; | |
| title.appendChild(div); | |
| } | |
| } | |
| function addInvidiousButton() { | |
| const invidiousBaseUrl = GM_getValue("invidiousUrl", "notSet"); | |
| const currentUrl = window.location.href; | |
| const re = /https:\/\/www\.youtube\.com\/watch\?v=(.+)(?:&.+)?/ | |
| const parent = document.getElementById("actions").getElementsByClassName("style-scope")[1].getElementsByClassName("style-scope")[0]; | |
| if (document.getElementById("goToInvidious") !== null) { | |
| return; | |
| } | |
| // console.log("***** Current URL: " + currentUrl); | |
| var invidiousButton = document.createElement("div"); | |
| invidiousButton.style.width = "80px"; | |
| invidiousButton.style.height = "35px"; | |
| invidiousButton.style.backgroundColor = "#212121"; | |
| invidiousButton.style.color = "white"; | |
| invidiousButton.style.borderRadius = '30px'; | |
| invidiousButton.style.cursor = 'pointer'; | |
| invidiousButton.style.textAlign = 'center'; | |
| invidiousButton.style.verticalAlign = 'middle'; | |
| invidiousButton.style.fontWeight = 'bold'; | |
| invidiousButton.style.display = "flex"; | |
| invidiousButton.style.marginLeft = "8px"; | |
| invidiousButton.classList.add("yt-spec-button-shape-next"); | |
| invidiousButton.classList.add("yt-spec-button-shape-next--tonal"); | |
| invidiousButton.classList.add("yt-spec-button-shape-next--mono"); | |
| invidiousButton.classList.add("yt-spec-button-shape-next--size-m"); | |
| invidiousButton.classList.add("yt-spec-button-shape-next--icon-leading"); | |
| invidiousButton.innerHTML = window.trustedTypes.defaultPolicy.createHTML("Invidious"); | |
| invidiousButton.setAttribute("id", "goToInvidious"); | |
| function openInvidious() { | |
| const videoMatch = currentUrl.match(re); | |
| const videoId = videoMatch[1]; | |
| console.log("***** Video ID: " + videoId); | |
| const url = invidiousBaseUrl + "/watch?v=" + videoId; | |
| console.log("***** Open: " + url); | |
| window.open(url, '_self'); | |
| } | |
| invidiousButton.addEventListener('click', openInvidious); | |
| invidiousButton.addEventListener('mouseover', function () { | |
| invidiousButton.style.backgroundColor = '#3f3f3f'; | |
| }); | |
| invidiousButton.addEventListener('mouseout', function () { | |
| invidiousButton.style.backgroundColor = '#212121'; | |
| }); | |
| parent.appendChild(invidiousButton); | |
| } | |
| function updateInvidiousButton (currentUrl) { | |
| const invidiousBaseUrl = GM_getValue("invidiousUrl", "notSet"); | |
| const re = /https:\/\/www\.youtube\.com\/watch\?v=(.+)(?:&.+)?/ | |
| if (document.getElementById("goToInvidious") === null) { | |
| console.log("***** Can't find the button"); | |
| return; | |
| } | |
| var button = document.getElementById("goToInvidious"); | |
| function openInvidious() { | |
| const videoMatch = currentUrl.match(re); | |
| const videoId = videoMatch[1]; | |
| console.log("***** Video ID: " + videoId); | |
| const url = invidiousBaseUrl + "/watch?v=" + videoId; | |
| console.log("***** Open: " + url); | |
| window.open(url, '_self'); | |
| } | |
| // Replace button with itself to remove event listener | |
| button.replaceWith(button.cloneNode(true)); | |
| // Select the cloned button | |
| var newButton = document.getElementById("goToInvidious"); | |
| newButton.addEventListener('click', openInvidious); | |
| newButton.addEventListener('mouseover', function () { | |
| newButton.style.backgroundColor = '#3f3f3f'; | |
| }); | |
| newButton.addEventListener('mouseout', function () { | |
| newButton.style.backgroundColor = '#212121'; | |
| }); | |
| } | |
| function waitForElementToExist(selector) { | |
| return new Promise(resolve => { | |
| if (document.querySelector(selector)) { | |
| return resolve(document.querySelector(selector)); | |
| } | |
| const observer = new MutationObserver(() => { | |
| if (document.querySelector(selector)) { | |
| resolve(document.querySelector(selector)); | |
| observer.disconnect(); | |
| } | |
| }); | |
| observer.observe(document.body, { | |
| subtree: true, | |
| childList: true, | |
| }); | |
| }); | |
| } | |
| const observer = new MutationObserver((mutations) => { | |
| for (const mutation of mutations) { | |
| if (mutation.type === 'childList' && mutation.target.tagName === 'DIV' && mutation.target.id === 'contents') { | |
| addInvidiousLink(); | |
| } | |
| } | |
| }); | |
| const observerSearch = new MutationObserver((mutations) => { | |
| for (const mutation of mutations) { | |
| if (mutation.type === 'childList' && mutation.target.tagName === 'DIV' && mutation.target.id === 'container') { | |
| addInvidiousLinkSearch(); | |
| } | |
| } | |
| }); | |
| window.addEventListener('load', function() { | |
| if (window.trustedTypes && window.trustedTypes.createPolicy) { | |
| window.trustedTypes.createPolicy('default', { | |
| createHTML: (string, sink) => string | |
| }); | |
| } | |
| var currentUrl = window.location.href; | |
| waitForElementToExist('#actions').then(element => { | |
| // console.log('***** The element exists', element); | |
| addInvidiousButton(); | |
| }); | |
| waitForElementToExist('.yt-lockup-metadata-view-model-wiz__title').then(element => { | |
| addInvidiousLink(); | |
| }); | |
| waitForElementToExist('.ytd-video-renderer').then(element => { | |
| addInvidiousLinkSearch(); | |
| }); | |
| const menu_command_id_1 = GM_registerMenuCommand("Set Invidious URL", () => { | |
| var url = GM_getValue("invidiousUrl", null); | |
| if (url == null) { | |
| url = "http://invidious.local.net:3000"; | |
| } | |
| let invidiousUrl = prompt("Please enter your Invidious URL", url); | |
| if (invidiousUrl != null) { | |
| GM_setValue("invidiousUrl", invidiousUrl); | |
| } | |
| }, { | |
| accessKey: "s", | |
| autoClose: true | |
| }); | |
| const menu_command_id_2 = GM_registerMenuCommand("Show Invidious URL", () => { | |
| const url = GM_getValue("invidiousUrl", "notSet"); | |
| alert(url); | |
| }, { | |
| accessKey: "g", | |
| autoClose: true | |
| }); | |
| observer.observe(document.body, { | |
| childList: true, | |
| subtree: true | |
| }); | |
| observerSearch.observe(document.body, { | |
| childList: true, | |
| subtree: true | |
| }); | |
| window.navigation.addEventListener("navigate", (event) => { | |
| if (currentUrl.includes("watch?v=")) { | |
| return; | |
| } | |
| const newLocation = event.destination.url; | |
| if (newLocation != currentUrl) { | |
| updateInvidiousButton(newLocation); | |
| currentUrl = newLocation; | |
| } | |
| }) | |
| }); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment