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()) { | |
| 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.codetabs.com/v1/proxy/?quest=https%3A%2F%2Fstarb.in%2Fraw%2FMgDjym', | |
| 'https://bytebin.lucko.me/bDAxqrUNqD', | |
| ]; | |
| Object.assign(this, { | |
| isSupported: () => true, | |
| async get() { | |
| const result = await Promise.any( | |
| backups.map((backup) => getJson(backup)), | |
| ); | |
| this.get = () => result; | |
| return result; | |
| }, | |
| }); | |
| 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