您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Redirect specific sites by replacing part of the URL.
// ==UserScript== // @name URL Replacer/Redirector // @namespace https://github.com/theborg3of5/Userscripts/ // @version 2.0 // @description Redirect specific sites by replacing part of the URL. // @author Gavin Borg // @require https://openuserjs.org/src/libs/sizzle/GM_config.js // @match https://gf.qytechs.cn/en/scripts/403100-url-replacer-redirector // @grant GM_registerMenuCommand // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM.getValue // @grant GM.setValue // ==/UserScript== // Grant GM_*value for legacy Greasemonkey, GM.*value for Greasemonkey 4+ // Settings conversion should be removed after a while, including: // - convertOldStyleSettings() // - Grant for GM_deleteValue // Our configuration instance - this loads/saves settings and handles the config popup. const Config = new GM_config(); (async function () { 'use strict'; // Find the site that we matched const startURL = window.location.href; const currentSite = getUserSiteForURL(startURL); // Add a menu item to the menu to launch the config GM_registerMenuCommand('Configure redirect settings', () => Config.open()); // Set up and load config let configSettings = buildConfigSettings(currentSite); await initConfigAsync(configSettings); // await because we need to read from the resulting (async-loaded) values // Convert old-style settings if we find them. await convertOldStyleSettings(configSettings); // Get replacement settings for the current URL const replaceSettings = getSettingsForSite(currentSite); if (!replaceSettings) { return; } // Build new URL const newURL = transformURL(startURL, replaceSettings); // Redirect to the new URL if (startURL === newURL) { logToConsole("Current URL is same as redirection target: " + newURL); return } window.location.replace(newURL); })(); // Get the site (entry from user includes/matches) that matches the current URL. function getUserSiteForURL(startURL) { for (const site of getUserSites()) { // Use a RegExp so we check case-insensitively let siteRegex = ""; if (site.startsWith("/")) { siteRegex = new RegExp(site.slice(1, -1), "i"); // If the site starts with a /, treat it as a regex (but remove the leading/trailing /) } else { siteRegex = new RegExp(site.replace(/\*/g, "[^ ]*"), "i"); // Otherwise replace * wildcards with regex-style [^ ]* wildcards } if (siteRegex.test(startURL)) { return site; // First match always wins } } } // We support both includes and matches, but only the user-overridden ones of each. function getUserSites() { return GM_info.script.options.override.use_matches.concat(GM_info.script.options.override.use_includes); } // Perform the replacements specified by the given settings. function transformURL(startURL, siteSettings) { const { prefix, suffix, targetStrings, replacementStrings } = siteSettings; // Transform the URL let newURL = startURL; for (let i = 0; i < targetStrings.length; i++) { let toReplace = prefix + targetStrings[i] + suffix; const replaceWith = prefix + replacementStrings[i] + suffix; // Use a RegEx to allow case-insensitive matching toReplace = new RegExp(escapeRegex(toReplace), "i"); // Escape any regex characters - we don't support actual regex matching. newURL = newURL.replace(toReplace, replaceWith); } return newURL; } // From https://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript/3561711#3561711 function escapeRegex(string) { return string.replace(/[-[\]{}()*+!<=:?.\/\\^$|#\s,]/g, '\\$&'); } // Log a message to the console with a prefix so we know it's from this script. function logToConsole(message) { console.log("URL Replacer/Redirector: " + message); } //#region Config handling // Build the settings object for GM_config.init() function buildConfigSettings(currentSite) { // Build fields for each site const fields = buildSiteFields(currentSite); const styles = ` /* Float the target strings fields to the left so that they can line up with their corresponding replacements */ div[id*=${fieldTargetStrings("")}] { float: left; } /* We use one section sub-header on the current site to call it out. We're overriding the default settings from the framework (which include the ID), so !important is needed for most of these properties. */ .section_desc { float: right !important; background: #00FF00 !important; color: black !important; width: fit-content !important; font-weight: bold !important; padding: 4px !important; margin: 0px auto !important; border-top: none !important; border-radius: 0px 0px 10px 10px !important; }"; `.replaceAll("\n", ""); // This format is nicer to read but the newlines cause issues in the config framework, so remove them return { id: "URLReplacerRedirectorConfig", title: "URL Replacer/Redirector Config", fields: fields, css: styles, }; } // Build the specific fields in the config function buildSiteFields(currentSite) { let fields = {}; for (const site of getUserSites()) { // Section headers are the site URL as the user entered them const sectionName = [site]; if (currentSite === site) { sectionName.push("This site"); // If this is the matched site, add a subheader to call it out } fields[fieldPrefix(site)] = { section: sectionName, // Section definition just goes on the first field inside type: "text", label: "Prefix", labelPos: "left", size: 75, title: "This string (if set) must appear directly before the target string in the URL.", } fields[fieldSuffix(site)] = { type: "text", label: "Suffix", labelPos: "left", size: 75, title: "This string (if set) must appear directly after the target string in the URL.", } fields[fieldTargetStrings(site)] = { type: "textarea", label: "Targets", labelPos: "above", title: "Enter one target per line. Each target will be replaced by its corresponding replacement.", } fields[fieldReplacementStrings(site)] = { type: "textarea", label: "Replacements", labelPos: "above", title: "Enter one replacement per line. Each replacement with replace its corresponding target.", } fields[fieldClearSite(site)] = { type: "button", label: "Clear redirects for this site", title: "Clear all fields for this site, removing all redirection.", save: false, // Don't save this field, it's just a button click: function (siteToClear) { return () => { Config.set(fieldPrefix(siteToClear), ""); Config.set(fieldSuffix(siteToClear), ""); Config.set(fieldTargetStrings(siteToClear), ""); Config.set(fieldReplacementStrings(siteToClear), ""); } }(site), // Immediately invoke this wrapper with the current site so the inner function can capture it } } return fields; } // This is just a Promise wrapper for GM_config.init that allows us to await initialization. async function initConfigAsync(settings) { return new Promise((resolve) => { // Have the init event (which should fire once config is done loading) resolve the promise settings["events"] = {init: resolve}; Config.init(settings); }); } // Get the settings for the given site. function getSettingsForSite(site) { if (!site) { console.log("No matching site found for URL"); return null; } // Retrieve and return the settings return { prefix: Config.get(fieldPrefix(site)), suffix: Config.get(fieldSuffix(site)), targetStrings: Config.get(fieldTargetStrings(site)).split("\n"), replacementStrings: Config.get(fieldReplacementStrings(site)).split("\n"), } } //#region Field name "constants" based on their corresponding sites // These are also the keys used with [GM_]Config.get/set. function fieldPrefix(site) { return "Prefix_" + site; } function fieldSuffix(site) { return "Suffix_" + site; } function fieldTargetStrings(site) { return "TargetString_" + site; } function fieldReplacementStrings(site) { return "ReplacementString_" + site; } function fieldClearSite(site) { return "ClearSite_" + site; } //#endregion Field name "constants" based on their corresponding sites //#endregion Config handling // Convert settings from the old style (simple GM_setValue/GM_getValue storage, 1 config for all // sites) to the new style (GM_config, one set of settings per site). async function convertOldStyleSettings(gmConfigSettings) { // Check the only really required setting (for the script to do anything) const replaceAry = GM_getValue("replaceTheseStrings"); if (!replaceAry) { return; // No old settings to convert, done. } logToConsole("Old-style settings found"); // Safety check: if we ALSO have new-style settings, leave it alone. if (GM_getValue("URLReplacerRedirectorConfig")) { logToConsole("New-style settings already exist, not converting old settings."); return; } const replacePrefix = GM_getValue("replacePrefix"); const replaceSuffix = GM_getValue("replaceSuffix"); // Old style: 1 config for ALL sites // New style: 1 config PER site // So, the conversion is just to copy the config onto each site. logToConsole("Starting settings conversion..."); for (const site of getUserSites()) { // Split replaceAry into targets (keys) and replacements (values) let targetsAry = []; let replacementsAry = []; for (const target in replaceAry) { targetsAry.push(target); replacementsAry.push(replaceAry[target]); } Config.set(fieldPrefix(site), replacePrefix); Config.set(fieldSuffix(site), replaceSuffix); Config.set(fieldTargetStrings(site), targetsAry.join("\n")); Config.set(fieldReplacementStrings(site), replacementsAry.join("\n")); } // Save new settings Config.save(); logToConsole("New-style settings saved."); // Remove the old-style settings so we don't do this again each time. GM_deleteValue("replaceTheseStrings"); GM_deleteValue("replacePrefix"); GM_deleteValue("replaceSuffix"); logToConsole("Old-style settings removed."); logToConsole("Conversion complete."); }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址