Created
May 12, 2025 09:29
-
-
Save superRaytin/07b509cb8cb2dd3a641712bb61d26202 to your computer and use it in GitHub Desktop.
Local Resource Redirector | 本地资源代理重定向工具
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 Local Resource Redirector | 本地资源代理重定向工具 | |
| // @namespace https://github.com/superRaytin | |
| // @version 1.0 | |
| // @description Redirect specified remote resources to local files for testing, debugging, and development use. | |
| // @author superraytin | |
| // @match *://*/* | |
| // @grant GM_getValue | |
| // @grant GM_setValue | |
| // @grant GM_registerMenuCommand | |
| // @run-at document-start | |
| // ==/UserScript== | |
| (function () { | |
| "use strict"; | |
| let currentRules = GM_getValue("rules", []); | |
| function getRedirectedUrl(originalUrl) { | |
| for (const rule of currentRules) { | |
| if ( | |
| rule.enabled && | |
| rule.url && | |
| rule.localPath && | |
| originalUrl.includes(rule.url) | |
| ) { | |
| return rule.localPath; | |
| } | |
| } | |
| return null; | |
| } | |
| // 拦截 fetch 请求 | |
| const originalFetch = window.fetch; | |
| window.fetch = async (...args) => { | |
| const redirected = getRedirectedUrl(args[0]); | |
| if (redirected) args[0] = redirected; | |
| return originalFetch(...args); | |
| }; | |
| // 拦截 XHR 请求 | |
| const open = XMLHttpRequest.prototype.open; | |
| XMLHttpRequest.prototype.open = function (method, url) { | |
| const redirected = getRedirectedUrl(url); | |
| if (redirected) arguments[1] = redirected; | |
| return open.apply(this, arguments); | |
| }; | |
| // 拦截 script 元素动态加载 | |
| const originalCreateElement = document.createElement; | |
| document.createElement = function (tag) { | |
| const el = originalCreateElement.call(document, tag); | |
| if (tag.toLowerCase() === "script") { | |
| Object.defineProperty(el, "src", { | |
| set(val) { | |
| const redirected = getRedirectedUrl(val); | |
| el.setAttribute("src", redirected || val); | |
| }, | |
| get() { | |
| return el.getAttribute("src"); | |
| }, | |
| }); | |
| } | |
| return el; | |
| }; | |
| // 注册菜单命令打开配置界面 | |
| GM_registerMenuCommand("Configure Redirect Rules", openSettingsUI); | |
| function openSettingsUI() { | |
| const win = window.open("", "", "width=600,height=400"); | |
| win.document.write(`<html><head><title>Redirect Rules</title></head><body> | |
| <h2>Redirect Rules (模糊匹配)</h2> | |
| <table border="1" id="rulesTable" style="width:100%"> | |
| <tr><th>Enable</th><th>Match Substring (in remote URL)</th><th>Local Redirect URL</th><th>Delete</th></tr> | |
| </table> | |
| <button id="addRule">Add Rule</button> | |
| <button id="applyRules">Apply Rules</button> | |
| </body></html>`); | |
| const table = win.document.getElementById("rulesTable"); | |
| function renderTable() { | |
| Array.from(table.rows) | |
| .slice(1) | |
| .forEach((row) => row.remove()); | |
| currentRules.forEach((rule, index) => { | |
| const row = table.insertRow(); | |
| const checkbox = win.document.createElement("input"); | |
| checkbox.type = "checkbox"; | |
| checkbox.checked = rule.enabled; | |
| checkbox.onchange = () => { | |
| currentRules[index].enabled = checkbox.checked; | |
| }; | |
| const urlInput = win.document.createElement("input"); | |
| urlInput.type = "text"; | |
| urlInput.value = rule.url || ""; | |
| urlInput.style = "width:100%"; | |
| urlInput.oninput = () => { | |
| currentRules[index].url = urlInput.value; | |
| }; | |
| const pathInput = win.document.createElement("input"); | |
| pathInput.type = "text"; | |
| pathInput.value = rule.localPath || ""; | |
| pathInput.style = "width:100%"; | |
| pathInput.oninput = () => { | |
| currentRules[index].localPath = pathInput.value; | |
| }; | |
| const deleteBtn = win.document.createElement("button"); | |
| deleteBtn.textContent = "Delete"; | |
| deleteBtn.onclick = () => { | |
| currentRules.splice(index, 1); | |
| renderTable(); | |
| }; | |
| row.insertCell().appendChild(checkbox); | |
| row.insertCell().appendChild(urlInput); | |
| row.insertCell().appendChild(pathInput); | |
| row.insertCell().appendChild(deleteBtn); | |
| }); | |
| } | |
| win.document.getElementById("addRule").onclick = () => { | |
| currentRules.push({ enabled: true, url: "", localPath: "" }); | |
| renderTable(); | |
| }; | |
| win.document.getElementById("applyRules").onclick = () => { | |
| const cleaned = currentRules.filter( | |
| (r) => r.url?.trim() && r.localPath?.trim() | |
| ); | |
| GM_setValue("rules", cleaned); | |
| currentRules = cleaned; | |
| win.alert("Rules applied. Reload the target page to take effect."); | |
| renderTable(); | |
| }; | |
| renderTable(); | |
| } | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment