Last active
January 8, 2026 15:42
-
-
Save examosa/50eb28bc16a006b62b3f43893ab457e7 to your computer and use it in GitHub Desktop.
Userscripts
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 Shortside | |
| // @version 1.2.1 | |
| // @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()) { | |
| this.isSupported = () => false; | |
| 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: () => | |
| typeof localStorage === 'object' && | |
| typeof localStorage.getItem === 'function' && | |
| typeof localStorage.setItem === 'function', | |
| get() { | |
| const item = localStorage.getItem(cacheKey); | |
| return JSON.parse(item); | |
| }, | |
| async set(value) { | |
| localStorage.setItem(cacheKey, JSON.stringify(value)); | |
| }, | |
| }, | |
| backup: { | |
| async isSupported() { | |
| // Report set() as unsupported | |
| this.isSupported = () => false; | |
| 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.codetabs.com/v1/proxy/?quest=https%3A%2F%2Fstarb.in%2Fraw%2FMgDjym', | |
| 'https://bytebin.lucko.me/bDAxqrUNqD', | |
| ]; | |
| this.get = async () => { | |
| for (const backup of backups) { | |
| const result = await getJson(backup); | |
| if (result) return result; | |
| } | |
| }; | |
| return true; | |
| } catch { | |
| return false; | |
| } | |
| }, | |
| }, | |
| }; | |
| 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; | |
| } | |
| async function run() { | |
| const instances = await fetchInstances(); | |
| const replacements = Object.entries({ | |
| fandom: ['breezewiki'], | |
| geeksforgeeks: ['ducksforducks', 'nerdsfornerds'], | |
| medium: ['libmedium', 'scribe'], | |
| quora: ['quetre'], | |
| reddit: ['eddrit', 'photon-reddit', 'redlib'], | |
| stackoverflow: ['anonymousoverflow'], | |
| }); | |
| 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