您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Redirects YouTube videos to an Invidious instance.
当前为
- // ==UserScript==
- // @name Redirect to Invidious
- // @author André Kugland
- // @description Redirects YouTube videos to an Invidious instance.
- // @namespace https://github.com/kugland
- // @license MIT
- // @version 0.3.3
- // @match https://www.youtube.com/*
- // @match https://m.youtube.com/*
- // @exclude *://music.youtube.com/*
- // @exclude *://*.music.youtube.com/*
- // @run-at document-start
- // @noframes
- // @grant GM_xmlhttpRequest
- // @grant GM.xmlhttpRequest
- // @homepageURL https://greasyfork.org/scripts/477967-redirect-to-invidious
- // ==/UserScript==
- /* This script is transpiled from TypeScript, that’s why it looks a bit weird. For the original
- source code, see https://github.com/kugland/invidious-redirect/. */
- (() => {
- // src/domhelper.ts
- function onload(callback) {
- if (document.readyState !== "loading") {
- callback();
- } else {
- document.addEventListener("DOMContentLoaded", callback);
- }
- }
- function element(html) {
- const template = document.createElement("template");
- template.innerHTML = html.trim();
- return template.content.firstChild;
- }
- // src/config.ts
- var DEFAULT_INSTANCE_URL = "https://invidious.protokolla.fi";
- var INSTANCES_JSON_URL = "https://raw.githubusercontent.com/kugland/invidious-redirect/master/instances.json";
- var INSTANCE_URL_KEY = "invidious-redirect--instance";
- var INSTANCES_KEY = "invidious-redirect--public-instances";
- var UPDATED_KEY = "invidious-redirect--public-instances-updated";
- var instanceUrl = localStorage.getItem(INSTANCE_URL_KEY) || DEFAULT_INSTANCE_URL;
- var publicInstances = JSON.parse(localStorage.getItem(INSTANCES_KEY) || "{}");
- var instancesUpdated = parseInt(localStorage.getItem(UPDATED_KEY) || "0");
- function getInstanceUrl() {
- return instanceUrl.replace(/\/$/, "").replace(/^https:\/\//, "");
- }
- function getFullInstanceUrl() {
- if (instanceUrl.startsWith("http://")) {
- return instanceUrl;
- } else {
- return `https://${instanceUrl}`;
- }
- }
- function setInstanceUrl(url) {
- instanceUrl = url.replace(/\/$/, "").replace(/^https:\/\//, "");
- localStorage.setItem(INSTANCE_URL_KEY, url);
- }
- async function getInstances() {
- const now = Date.now();
- const expired = now - instancesUpdated > 864e5;
- if (Object.keys(publicInstances).length !== 0 && !expired) {
- return publicInstances;
- } else {
- publicInstances = await loadInstances();
- instancesUpdated = now;
- localStorage.setItem(INSTANCES_KEY, JSON.stringify(publicInstances));
- localStorage.setItem(UPDATED_KEY, instancesUpdated.toString());
- return publicInstances;
- }
- }
- async function loadInstances() {
- const options = {
- method: "GET",
- headers: { "Content-Type": "application/json" }
- };
- return new Promise((resolve) => {
- const gm_xmlHttpRequest = (typeof GM !== "undefined" ? GM?.xmlHttpRequest : null) || (typeof GM_xmlhttpRequest !== "undefined" ? GM_xmlhttpRequest : null);
- if (gm_xmlHttpRequest) {
- gm_xmlHttpRequest({
- ...options,
- nocache: true,
- url: INSTANCES_JSON_URL,
- onload: (response) => resolve(JSON.parse(response.responseText))
- });
- } else if (false) {
- fetch("/instances.json", { cache: "no-cache", ...options }).then(async (response) => resolve(await response.json()));
- } else {
- throw new Error(
- "Unable to load instances.json. Is the script running in a userscript manager?"
- );
- }
- }).then((instances) => instances);
- }
- function clearInstances() {
- publicInstances = {};
- instancesUpdated = 0;
- localStorage.removeItem(INSTANCES_KEY);
- localStorage.removeItem(UPDATED_KEY);
- }
- // assets/refresh.svg
- var refresh_default = '<svg width="13" height="13" viewBox="0 0 130 130"><path d="M22 63a8 8 0 0 1-16 0 59 59 0 0 1 97.1-45v-6.3a8 8 0 1 1 16.1 0V37a8 8 0 0 1-8 8l-24.4 2.2a8 8 0 1 1-1.4-16l8-.7A43 43 0 0 0 22 63zm22.8 19.6a8 8 0 0 1 1.5 16l-10 .9a43 43 0 0 0 71.7-32 8 8 0 0 1 16 0 59 59 0 0 1-95.6 46.2v4.2a8 8 0 0 1-16 0v-25a8 8 0 0 1 7.2-8l25.3-2.4z" style="fill:currentColor"/></svg>\n';
- // assets/new-tab.svg
- var new_tab_default = '<svg width="16" height="16" viewBox="0 0 96 96"><path d="M83 13 44 52m16-40h23v23M45 22H12v62h62l-0-32" style="fill:none;stroke:currentColor;stroke-width:var(--stroke-width);stroke-linecap:round;stroke-linejoin:round"/></svg>\n';
- // src/select.ts
- var INVIDIOUS_INSTANCE_CONTAINER = "invidious-instance-container";
- async function showDialog() {
- const instances = await getInstances();
- const tableHtml = Object.keys(instances).map((uri) => `
- <tr data-url="https://${uri}">
- <td>${uri}</td>
- <td>${instances[uri].toLowerCase()}</td>
- <td><a href="https://${uri}" target="_blank">${new_tab_default}</a></td>
- </tr>
- `).join("");
- const dialog = element(`
- <div id="${INVIDIOUS_INSTANCE_CONTAINER}" ondragstart="return false;">
- <div>
- <header>
- <span>Select an Invidious instance</span>
- <span class="refresh">${refresh_default}</span>
- <span class="close">\u2715</span>
- <a class="rateme" href="https://greasyfork.org/en/scripts/477967-redirect-to-invidious/feedback" target="_blank">
- <div>
- Rate this script! <span class="emoji">\u{1F60A}</span>
- </div>
- </a>
- </header>
- <table>${tableHtml}</table>
- <footer>
- <div class="input-container">
- <span class="input-helper">Add http:// if it\u2019s not an https:// URL.</span>
- <input type="text" />
- </div>
- <button>Save</button>
- </footer>
- </div>
- </div>
- `);
- const input = dialog.querySelector("footer input");
- if (!input)
- return;
- input.value = getInstanceUrl() || "";
- input.placeholder = "invidious.snopyta.org";
- document.body.appendChild(dialog);
- dialog.addEventListener("click", (event) => {
- const target = event.target;
- if (!(target instanceof HTMLElement))
- return;
- const dialog2 = target.closest(`#${INVIDIOUS_INSTANCE_CONTAINER}`);
- const input2 = dialog2?.querySelector("footer input");
- if (!dialog2 || !input2)
- return;
- if (target.tagName != "A") {
- event.preventDefault();
- event.stopPropagation();
- }
- if (target.matches(".close")) {
- dialog2.remove();
- } else if (target.matches(".refresh")) {
- clearInstances();
- dialog2.remove();
- showDialog();
- } else if (target.matches("tr[data-url] *:not(a)")) {
- const url = target.closest("tr")?.getAttribute("data-url");
- if (url) {
- input2.value = url.replace(/^https:\/\//, "");
- }
- } else if (target.matches("footer button")) {
- try {
- new URL(`https://${input2.value}`);
- setInstanceUrl(input2.value);
- dialog2.remove();
- } catch (e) {
- alert("Invalid URL");
- }
- }
- }, true);
- }
- // assets/button.png
- var button_default = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAJFBMVEXv8e7Z4ePn6+kWt/CZzvChpKFrbWrT1dJVV1WJjIm2uLXCxMH33HXYAAAAp0lEQVR4AeXNIQ7CMABG4ceSsXSYIXFVFaCAC5BwgblNV4HDkMwiIA0YDMnkDMHWoHY5PPwGSfjsE4+fNbZIyXIBOszR1iu+lICWFmiuRGsOaPURbXOyKINb6FDyR/AoZlefURyNnuwxelKR6YmHVk2yK3qSd+iJKdATB9Be+PAEPakATIi8STzISVaiJ2lET4YFejIBPbmDnEy3ETmZ9REARr3lP7wAXHImU2sAU14AAAAASUVORK5CYII=";
- // css/style.css
- var style_default = '#set-invidious-url{position:fixed;bottom:0;right:0;height:48px;width:48px;z-index:99998;margin:1rem;cursor:pointer;border-radius:50%;box-shadow:0px 0px 3px #000;opacity:.5}#set-invidious-url:hover{opacity:1 !important}#invidious-instance-container{position:fixed;top:0;left:0;right:0;bottom:0;background-color:rgba(0,0,0,.2);backdrop-filter:blur(2px);display:grid;place-content:center;z-index:99999;overflow-y:auto}#invidious-instance-container,#invidious-instance-container *{font-family:mono;font-size:12px;box-sizing:border-box}#invidious-instance-container>div{background-color:#fff;box-shadow:10px 10px 20px rgba(0,0,0,.5);border-radius:5px;user-select:none}#invidious-instance-container header,#invidious-instance-container footer{background-color:#eee}#invidious-instance-container header{border-radius:5px 5px 0 0;display:grid;grid-template:"title refresh close" auto "rateme rateme rateme"/1fr auto auto;align-items:center;border-bottom:1px solid #ccc}#invidious-instance-container header>span{padding-left:10px}#invidious-instance-container footer{border-radius:0 0 5px 5px;padding:10px;display:grid;grid-template-columns:1fr auto;gap:10px;border-top:1px solid #ccc}#invidious-instance-container footer input{padding:5px;border:1px solid #ccc;border-radius:5px;width:100%}#invidious-instance-container footer button{padding:5px 10px;border:1px solid #ccc;border-radius:5px;cursor:pointer;background-color:#eee}#invidious-instance-container footer button:hover,#invidious-instance-container footer button:focus{background-color:#ddd}#invidious-instance-container footer button:active{background-color:#ccc}#invidious-instance-container table{border-collapse:collapse;margin:3px 0}#invidious-instance-container td{padding:0 10px;cursor:pointer}#invidious-instance-container td a{position:relative;top:1px;color:#888;text-decoration:none;--stroke-width: 8}#invidious-instance-container td a:hover{color:#000;--stroke-width: 12}#invidious-instance-container tr:hover td{background-color:#eee}#invidious-instance-container .input-helper{opacity:0;font-size:12px;position:absolute;background-color:rgba(0,0,0,.8);color:#fff;bottom:20px;left:0;right:0;padding:5px 10px;margin:0 25px;pointer-events:none;border-radius:5px;text-align:center;transition:.5s ease all}#invidious-instance-container .input-helper::after{content:"";position:absolute;top:100%;left:50%;border:solid rgba(0,0,0,0);height:0;width:0;border-top-color:#000;border-width:8px;margin-left:-8px}#invidious-instance-container .input-container{position:relative}#invidious-instance-container .input-container:hover .input-helper{opacity:1;bottom:30px}#invidious-instance-container .refresh,#invidious-instance-container .close{cursor:pointer;color:#000;text-decoration:none;font-size:20px;padding:5px 10px;border-top-right-radius:5px}#invidious-instance-container .refresh:hover,#invidious-instance-container .refresh:focus,#invidious-instance-container .close:hover,#invidious-instance-container .close:focus{font-weight:bold}#invidious-instance-container .refresh:hover,#invidious-instance-container .close:hover{color:#fff;background-color:rgba(255,0,0,.5)}#invidious-instance-container .refresh{border-top-right-radius:0}#invidious-instance-container .refresh:hover{background-color:rgba(0,192,0,.5)}#invidious-instance-container .rateme{justify-self:stretch;grid-area:rateme;display:flex;justify-content:center;background-color:#ddd;padding:5px 10px;color:#000;text-decoration:none}#invidious-instance-container .rateme .emoji{font-variant-emoji:emoji}#invidious-instance-container .rateme:hover,#invidious-instance-container .rateme:focus{font-weight:bold;background-color:#ccc}\n';
- // src/videourl.ts
- function getVideoId(url) {
- try {
- const baseUrl = true ? window.location.origin : "https://www.youtube.com";
- const urlObj = new URL(url, baseUrl);
- let videoId = null;
- if (urlObj.pathname === "/watch") {
- videoId = urlObj.searchParams.get("v");
- } else if (urlObj.pathname.startsWith("/shorts/")) {
- videoId = urlObj.pathname.slice(8);
- } else if (urlObj.pathname.startsWith("/live/")) {
- videoId = urlObj.pathname.slice(6);
- } else if (urlObj.hostname === "youtu.be") {
- videoId = urlObj.pathname.slice(1);
- }
- if (videoId) {
- return videoId;
- }
- } catch (e) {
- }
- const error = new Error(`Unable to parse URL: ${url}`);
- throw error;
- }
- // src/redirect.ts
- var currentUrl = window.location.href;
- function tryNavigate(href, replace = true) {
- try {
- const url = `${getFullInstanceUrl()}/watch?v=${getVideoId(href)}`;
- if (replace) {
- window.location.replace(url);
- } else {
- window.location.assign(url);
- }
- return true;
- } catch (e) {
- }
- return false;
- }
- tryNavigate(window.location.href, true);
- document.addEventListener("click", (event) => {
- if (event.target instanceof HTMLElement) {
- const href = event.target.closest("a")?.getAttribute("href");
- if (href && tryNavigate(href, false)) {
- event.preventDefault();
- event.stopPropagation();
- }
- }
- }, true);
- setInterval(() => {
- if (window.location.href !== currentUrl) {
- currentUrl = window.location.href;
- tryNavigate(window.location.href, true);
- }
- }, 150);
- // src/main.ts
- onload(() => {
- if (true) {
- let styles = element(`<style>${style_default}</style>`);
- document.head.appendChild(styles);
- }
- const button = element(`<img id="set-invidious-url" src="${button_default}" tabindex="-1">`);
- button.addEventListener("click", () => showDialog());
- document.body.appendChild(button);
- if (false) {
- localStorage.clear();
- showDialog();
- }
- });
- })();