Skip to content

Instantly share code, notes, and snippets.

@Chucky2401
Created August 3, 2025 11:21
Show Gist options
  • Select an option

  • Save Chucky2401/3f73eee7a4d57eeaec47a3f909d58333 to your computer and use it in GitHub Desktop.

Select an option

Save Chucky2401/3f73eee7a4d57eeaec47a3f909d58333 to your computer and use it in GitHub Desktop.
Youtube | Add link to slef hosted Invidious
// ==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