Skip to content

Instantly share code, notes, and snippets.

@examosa
Last active January 8, 2026 15:42
Show Gist options
  • Select an option

  • Save examosa/50eb28bc16a006b62b3f43893ab457e7 to your computer and use it in GitHub Desktop.

Select an option

Save examosa/50eb28bc16a006b62b3f43893ab457e7 to your computer and use it in GitHub Desktop.
Userscripts
// ==UserScript==
// @name Shortside
// @version 1.2.0
// @description Redirects the configured websites to third-party privacy-respecting front ends.
// @author examosa
// @license AGPLv3-or-later
// @updateUrl https://gist.github.com/examosa/50eb28bc16a006b62b3f43893ab457e7/raw/shortside.user.js
// @downloadUrl https://gist.github.com/examosa/50eb28bc16a006b62b3f43893ab457e7/raw/shortside.user.js
// @match https://*.fandom.com/*
// @match https://*.medium.com/*
// @match https://stackoverflow.com/*
// @match https://www.reddit.com/*
// @match https://www.youtube.com/*
// @match https://www.quora.com/*
// @match https://www.geeksforgeeks.org/*
// @run-at document_start
// @allFrames false
// @noframes
// ==/UserScript==
const cdn = (path) => new URL(path, 'https://nobsdelivr.private.coffee/');
const withCatch =
(action) =>
(...args) =>
Promise.try(action, ...args).catch((error) => {
console.error('Caught error:', error);
});
const getJson = withCatch((url) =>
fetch(url, { headers: { Accept: 'application/json' } }).then((response) =>
response.json(),
),
);
function createCache() {
const cacheKey = 'SHORTSIDE_INSTANCES';
const drivers = {
vanillaPudding: {
async isSupported() {
const getMessage = withCatch(
() => import(cdn('npm/@vanilla-pudding/message@1.4.0/+esm')),
);
const message = await getMessage();
const ext = message?.useExt();
if (!ext?.getVersion()) {
return false;
}
const store = ext.bgt.extLocalStore;
Object.assign(this, {
isSupported: () => true,
get: () => store.getByStrict(cacheKey),
set: (value) => store.set(cacheKey, value, 10),
});
return true;
},
},
greaseMonkey: {
isSupported: () =>
typeof GM === 'object' &&
typeof GM.getValue === 'function' &&
typeof GM.setValue === 'function',
get: () => GM.getValue(cacheKey).then((value) => JSON.parse(value)),
set: (value) => GM.setValue(cacheKey, JSON.stringify(value)),
},
localStorage: {
isSupported: () => true,
get() {
const item = localStorage.getItem(cacheKey);
return JSON.parse(item);
},
async set(value) {
localStorage.setItem(cacheKey, JSON.stringify(value));
},
},
backup: {
async isSupported() {
try {
await fetch('https://httpbin.private.coffee/status/204');
const backups = [
'https://jsonblob.com/api/jsonBlob/1392945386074857472',
'https://api.npoint.io/6ee2cb1b5a1aa6f10be5',
'https://api.pastes.dev/VSjbFP63yq',
`https://api.cors.lol?url=${encodeURIComponent('https://starb.in/raw/MgDjym')}`,
'https://bytebin.lucko.me/bDAxqrUNqD',
];
Object.assign(this, {
isSupported: () => true,
get: () => Promise.any(backups.map((backup) => getJson(backup))),
});
return true;
} catch {
return false;
}
},
set(value) {
const original = this.isSupported;
this.isSupported = function () {
this.isSupported = original;
return false;
};
return cache.set(value);
},
},
};
async function eachDriver(call) {
const safeCall = withCatch(call);
for (const driver of Object.values(drivers)) {
if (!(await driver.isSupported())) continue;
const result = await safeCall(driver);
if (result) return result;
}
}
const cache = {
get: () => eachDriver((driver) => driver.get()),
set: (value) => eachDriver((driver) => driver.set(value).then(() => true)),
};
return cache;
}
async function fetchInstances() {
const cache = createCache();
const cached = await cache.get();
if (cached) {
return cached;
}
const instances = Object.assign(Object.create(null), {
ducksforducks: ['https://ducksforducks.private.coffee'],
nerdsfornerds: ['https://nn.vern.cc'],
});
const providers = {
libredirect: {
source: 'libredirect/instances/data.json',
normalize: (result) =>
Object.entries(result).map(([name, instances]) => ({
type: name.toLowerCase(),
instances: instances.clearnet,
})),
},
farside: {
source: 'benbusby/farside/services.json',
normalize: (result) =>
result.map((service) => ({
type: service.type,
instances: service.instances.concat(service.fallback),
})),
},
fastside: {
source: 'cofob/fastside/services.json',
normalize: (result) =>
result.services.map((service) => ({
type: service.type,
instances: service.instances
.filter((instance) => instance.tags.includes('clearnet'))
.map((instance) => instance.url),
})),
},
};
try {
const promises = Object.values(providers).map(async (provider) => {
const result = await getJson(cdn(`gh/${provider.source}`));
if (!result) {
return [];
}
return provider.normalize(result);
});
const sources = await Promise.all(promises).flat();
for (const source of sources) {
const list = (instances[source.type] ??= []);
for (const url of source.instances) {
if (!list.includes(url)) {
list.push(url);
}
}
}
await cache.set(instances);
} catch {}
return instances;
}
const replacements = Object.entries({
fandom: ['breezewiki'],
geeksforgeeks: ['ducksforducks', 'nerdsfornerds'],
medium: ['libmedium', 'scribe'],
quora: ['quetre'],
reddit: ['eddrit', 'photon-reddit', 'redlib'],
stackoverflow: ['anonymousoverflow'],
});
async function run() {
const instances = await fetchInstances();
const replacement = replacements.find(([host]) =>
location.hostname.includes(host),
);
if (replacement) {
const chooseRandom = (list) =>
list.at(Math.floor(Math.random() * list.length));
const [, options] = replacement;
const type = chooseRandom(options);
const instancesForType = instances[type];
if (instancesForType?.length > 0) {
const redirect = new URL(
location.pathname,
chooseRandom(instancesForType),
);
return location.replace(redirect);
}
}
location.replace(`https://farside.link/${location.href}`);
}
run();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment