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 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); |
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.1.2 | |
| // @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 backups = [ | |
| 'https://jsonblob.com/api/jsonBlob/1392945386074857472', | |
| 'https://api.npoint.io/6ee2cb1b5a1aa6f10be5', | |
| 'https://api.pastes.dev/VSjbFP63yq', | |
| 'https://starb.in/raw/MgDjym', | |
| 'https://bytebin.lucko.me/bDAxqrUNqD', | |
| ]; | |
| const driverOf = (get, set) => ({ get, set }); | |
| async function withStore(call) { | |
| const message = await import( | |
| cdn('npm/@vanilla-pudding/message@1.4.0/+esm') | |
| ); | |
| const ext = message.useExt(); | |
| return call(ext.bgt.extLocalStore); | |
| } | |
| const drivers = { | |
| vanillaPudding: driverOf( | |
| () => withStore((store) => store.getByStrict(cacheKey)), | |
| (value) => withStore((store) => store.set(cacheKey, value, 10)), | |
| ), | |
| greaseMonkey: driverOf( | |
| () => GM.getValue(cacheKey).then((value) => JSON.parse(value)), | |
| (value) => GM.setValue(cacheKey, JSON.stringify(value)), | |
| ), | |
| localStorage: driverOf( | |
| () => { | |
| const item = localStorage.getItem(cacheKey); | |
| return JSON.parse(item); | |
| }, | |
| (value) => { | |
| localStorage.setItem(cacheKey, JSON.stringify(value)); | |
| }, | |
| ), | |
| backup: driverOf( | |
| async () => { | |
| for (const backup of backups) { | |
| const result = await getJson(backup); | |
| if (result) return result; | |
| } | |
| }, | |
| (value) => { | |
| if (backups.skip) return backups.skip = false; | |
| backups.skip = true; | |
| return cache.set(value); | |
| }, | |
| ), | |
| }; | |
| async function eachDriver(call) { | |
| const safeCall = withCatch(call); | |
| for (const driver of Object.values(drivers)) { | |
| 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