修复 cdn.jsdelivr.net 无法访问的问题
// ==UserScript==// @name Jsdelivr Auto Fallback// @namespace https://github.com/PipecraftNet/jsdelivr-auto-fallback// @version 0.3.3// @author ojbk// @description 修复 cdn.jsdelivr.net 无法访问的问题// @homepage https://github.com/PipecraftNet/jsdelivr-auto-fallback// @supportURL https://github.com/PipecraftNet/jsdelivr-auto-fallback/issues// @license MIT// @match *://*/*// @run-at document-start// @grant GM_setValue// @grant GM_getValue// ==/UserScript==((document) => {'use strict';let fastNode;let failed;let isRunning;const DEST_LIST = ['cdn.jsdelivr.net','fastly.jsdelivr.net','testingcf.jsdelivr.net','test1.jsdelivr.net','gcore.jsdelivr.net'];const PREFIX = '//';const SOURCE = DEST_LIST[0];const starTime = Date.now();const TIMEOUT = 2000;const STORE_KEY = 'jsdelivr-auto-fallback';const TEST_PATH = '/gh/PipecraftNet/jsdelivr-auto-fallback@main/empty.css?';const shouldReplace = (text) => text && text.includes(PREFIX + SOURCE);const replace = (text) => text.replace(PREFIX + SOURCE, PREFIX + fastNode);const setTimeout = window.setTimeout;const $ = document.querySelectorAll.bind(document);const replaceElementSrc = () => {let element;let value;for (element of $('link[rel="stylesheet"]')) {value = element.href;if (shouldReplace(value) && !value.includes(TEST_PATH)) {element.href = replace(value);}}for (element of $('script')) {value = element.src;if (shouldReplace(value)) {const newNode = document.createElement('script');newNode.src = replace(value);element.defer = true;element.src = '';element.before(newNode);element.remove();}}for (element of $('img')) {value = element.src;if (shouldReplace(value)) {// Used to cancel loading. Without this line it will remain pending status.element.src = '';element.src = replace(value);}}// All elements that have a style attributefor (element of $('*[style]')) {value = element.getAttribute('style');if (shouldReplace(value)) {element.setAttribute('style', replace(value));}}for (element of $('style')) {value = element.innerHTML;if (shouldReplace(value)) {element.innerHTML = replace(value);}}};const tryReplace = () => {if (!isRunning && failed && fastNode) {console.warn(SOURCE + ' is not available. Use ' + fastNode);isRunning = true;setTimeout(replaceElementSrc, 0);// Some need to wait for a whilesetTimeout(replaceElementSrc, 20);// Replace dynamically added elementssetInterval(replaceElementSrc, 500);}};const checkAvailable = (url, callback) => {let timeoutId;const newNode = document.createElement('link');const handleR###lt = (isSuccess) => {if (!timeoutId) {return;}clearTimeout(timeoutId);timeoutId = 0;// Used to cancel loading. Without this line it will remain pending status.if (!isSuccess) newNode.href = 'data:text/plain;base64,';newNode.remove();callback(isSuccess);};timeoutId = setTimeout(handleR###lt, TIMEOUT);newNode.addEventListener('error', () => handleR###lt(false));newNode.addEventListener('load', () => handleR###lt(true));newNode.rel = 'stylesheet';newNode.text = 'text/css';newNode.href = url + TEST_PATH + starTime;document.head.insertAdjacentElement('afterbegin', newNode);};const cached = (() => {try {// eslint-disable-next-line new-capreturn Object.assign({}, GM_getValue(STORE_KEY));} catch {return {};}})();const main = () => {cached.time = starTime;cached.failed = false;cached.fastNode = null;for (const url of DEST_LIST) {checkAvailable('https://' + url, (isAvailable) => {// console.log(url, Date.now() - starTime, Boolean(isAvailable));if (!isAvailable && url === SOURCE) {failed = true;cached.failed = true;}if (isAvailable && !fastNode) {fastNode = url;}if (isAvailable && !cached.fastNode) {cached.fastNode = url;}tryReplace();});}setTimeout(() => {// If all domains are timeoutif (failed && !fastNode) {fastNode = DEST_LIST[1];tryReplace();}// eslint-disable-next-line new-capGM_setValue(STORE_KEY, cached);}, TIMEOUT + 100);};if (cached.time &&starTime - cached.time < 60 * 60 * 1000 &&cached.failed &&cached.fastNode) {failed = true;fastNode = cached.fastNode;tryReplace();setTimeout(main, 1000);} else {main();}})(document);