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 Redirect to Farside
// @version 1.5
// @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/redirect-to-farside.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/*
// @runAt document_start
// ==/UserScript==
const replacements = Object.entries({
'www.geeksforgeeks.org': 'ducksforducks.private.coffee',
});
const replacement = replacements.find(([hostname]) => hostname === location.hostname);
const target = replacement
? location.href.replace(...replacement)
: `https://farside.link/${location.href}`;
location.replace(target);
// ==UserScript==
// @name Shortside
// @version 1.0.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/*
// @runAt document_start
// ==/UserScript==
const cdn = (path) => new URL(path, 'https://nobsdelivr.private.coffee/');
const chooseRandom = (list) => list.at(Math.floor(Math.random() * list.length));
async function createCache() {
const cacheKey = '1392945386074857472';
// Greasemonkey API
if (typeof GM === 'object') {
const gmCache = {
async get() {
try {
const value = await GM.getValue(cacheKey);
return JSON.parse(value);
} catch {
return null;
}
},
set: (value) => GM.setValue(cacheKey, JSON.stringify(value)),
};
return gmCache;
}
// Vanilla Pudding
const store = await import(
cdn('npm/@vanilla-pudding/message@1.4.0/+esm')
).then(
(module) => {
const ext = module.useExt();
if (!ext.getVersion()) {
return null;
}
return ext.bgt.extLocalStore;
},
() => null,
);
if (store) {
const messageCache = {
get: () => store.getByStrict(cacheKey),
set: (value) => store.set(cacheKey, value, 10),
};
return messageCache;
}
// JSON Blob
const blobCache = {
async get() {
const response = await fetch(
`https://jsonblob.com/api/jsonBlob/${cacheKey}`,
);
if (response.ok) {
return await response.json();
}
return null;
},
set() {
console.warn('Unable to cache with JSON Blob');
},
};
return blobCache;
}
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),
})),
},
};
async function fetchInstances() {
const cache = await 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'],
});
try {
const promises = Object.values(providers).map(async (provider) => {
const response = await fetch(cdn(`gh/${provider.source}`));
if (!response.ok) {
return [];
}
const result = await response.json();
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 [, 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