https://github.com/einaregilsson/Redirector
// ==UserScript==
// @name Redirector
// @namespace https://facaikotei.github.io/
// @version 4.0.0
// @description https://github.com/einaregilsson/Redirector
// @author (c)2025 facaikotei (c)2016 Einar Egilsson
// @match *://*/*
// @grant GM_getValue
// @grant GM_setValue
// @require https://raw.githubusercontent.com/einaregilsson/Redirector/refs/tags/v3.5.2/js/redirect.js#sha256=706cd9538fc6a8417ff7ddf9a65c389600a4a334f0220802632e1411a6432d97
// @icon https://raw.githubusercontent.com/einaregilsson/Redirector/refs/tags/v3.5.2/images/icon-dark-theme-128.png
// @run-at document-start
// @license MIT
// @website https://greasyfork.org/users/1305953
// ==/UserScript==
var log = console.log;
var partitionedRedirects = {
main_frame: [
new Redirect({
includePattern: 'https://x.com/*',
redirectUrl: 'https://farside.link/nitter/$1',
patternType: 'W',
}),
new Redirect({
includePattern: 'https://www.reddit.com/*',
redirectUrl: 'https://farside.link/redlib/$1',
patternType: 'W',
}),
new Redirect({
includePattern: '^https://(|[^/]+\\.)medium\\.com/[^?#]*-([0-9a-f]{11,12})(?=$|[?#])',
redirectUrl: 'https://freedium-mirror.cfd/$2',
patternType: 'R',
}),
new Redirect({
includePattern: '^https://www\\.pixiv\\.net/(artworks|users)/(\\d+)(?=$|[/?#])',
redirectUrl: 'https://pixiv.perennialte.ch/$1/$2',
patternType: 'R',
}),
new Redirect({
includePattern: '^https://(|[^/]+\\.)moegirl\\.[^/]+(/[a-z][^/]*/|/)(.*)',
excludePattern: '^https://moegirl\\.(icu|uk)/',
redirectUrl: 'https://moegirl.icu/$3',
patternType: 'R',
}),
],
};
var ignoreNextRequest = GM_getValue('ignoreNextRequest', {});
var justRedirected = GM_getValue('justRedirected', {});
var redirectThreshold = 3;
var enableNotifications = false;
var sendNotifications = console.log;
//This is the actual function that gets called for each request and must
//decide whether or not we want to redirect.
function checkRedirects(details) {
//We only allow GET request to be redirected, don't want to accidentally redirect
//sensitive POST parameters
if (details.method != 'GET') {
return {};
}
log('Checking: ' + details.type + ': ' + details.url);
var list = partitionedRedirects[details.type];
if (!list) {
log('No list for type: ' + details.type);
return {};
}
var timestamp = ignoreNextRequest[details.url];
if (timestamp) {
log('Ignoring ' + details.url + ', was just redirected ' + (new Date().getTime() - timestamp) + 'ms ago');
delete ignoreNextRequest[details.url];
return {};
}
for (var i = 0; i < list.length; i++) {
var r = list[i];
var result = r.getMatch(details.url);
if (result.isMatch) {
//Check if we're stuck in a loop where we keep redirecting this, in that
//case ignore!
var data = justRedirected[details.url];
var threshold = 3000;
if (!data || ((new Date().getTime() - data.timestamp) > threshold)) { //Obsolete after 3 seconds
justRedirected[details.url] = { timestamp: new Date().getTime(), count: 1 };
} else {
data.count++;
justRedirected[details.url] = data;
if (data.count >= redirectThreshold) {
log('Ignoring ' + details.url + ' because we have redirected it ' + data.count + ' times in the last ' + threshold + 'ms');
return {};
}
}
log('Redirecting ' + details.url + ' ===> ' + result.redirectTo + ', type: ' + details.type + ', pattern: ' + r.includePattern + ' which is in Rule : ' + r.description);
if (enableNotifications) {
sendNotifications(r, details.url, result.redirectTo);
}
ignoreNextRequest[result.redirectTo] = new Date().getTime();
return { redirectUrl: result.redirectTo };
}
}
return {};
}
(function () {
'use strict';
let { redirectUrl } = checkRedirects({
method: 'GET',
type: 'main_frame',
url: location.href,
});
GM_setValue('ignoreNextRequest', ignoreNextRequest);
GM_setValue('justRedirected', justRedirected);
if (redirectUrl) {
location.replace(redirectUrl);
}
})();