Skip to content

Instantly share code, notes, and snippets.

@purplebar0
Created June 14, 2026 18:50
Show Gist options
  • Select an option

  • Save purplebar0/faf7c789f69214755e09ed41412d9f1d to your computer and use it in GitHub Desktop.

Select an option

Save purplebar0/faf7c789f69214755e09ed41412d9f1d to your computer and use it in GitHub Desktop.
Bypass Bluesky age verification by removing labels
// ==UserScript==
// @name Bluesky - remove labels
// @version 1.0
// @match https://bsky.app/*
// @match https://main.bsky.dev/*
// @grant GM_xmlhttpRequest
// @grant unsafeWindow
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
// Post data endpoints to filter
const TARGET_PATHS = [
'/xrpc/app.bsky.feed.getAuthorFeed',
'/xrpc/app.bsky.unspecced.getPostThreadV2'
];
/**
* Checks if a given URL matches any of the configured target paths.
* @param {string} url - The full URL string.
* @returns {boolean} - True if the path matches one of the targets.
*/
function isTargetUrl(url) {
try {
const parsedUrl = new URL(url, location.origin);
const path = parsedUrl.pathname + parsedUrl.search;
return TARGET_PATHS.some(pattern => {
if (pattern instanceof RegExp) {
return pattern.test(path);
}
return path.startsWith(pattern);
});
} catch (e) {
return false;
}
}
/**
* Recursively replaces any value associated with the key "labels" (if array) with [].
*/
function cleanLabels(obj) {
if (!obj || typeof obj !== 'object') return obj;
if (Array.isArray(obj)) {
return obj.map(item => cleanLabels(item));
}
const newObj = {};
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
if (key === 'labels' && Array.isArray(obj[key])) {
newObj[key] = [];
} else {
newObj[key] = cleanLabels(obj[key]);
}
}
}
return newObj;
}
/**
* Overrides the fetch API to intercept responses from matching paths.
*/
function overrideFetch() {
const originalFetch = unsafeWindow.fetch;
unsafeWindow.fetch = function(...args) {
// Extract URL string from arguments
let urlStr = '';
if (args[0] instanceof Request) {
urlStr = args[0].url;
} else if (typeof args[0] === 'string') {
urlStr = args[0];
}
if (urlStr && isTargetUrl(urlStr)) {
return new Promise((resolve, reject) => {
const options = args[1] || {};
GM_xmlhttpRequest({
method: options.method || 'GET',
url: urlStr,
headers: options.headers || {},
data: options.body || null,
onload: function(response) {
try {
const json = JSON.parse(response.responseText);
const cleanedJson = cleanLabels(json);
const newBody = JSON.stringify(cleanedJson);
// Reconstruct headers
const headers = new Headers();
if (response.responseHeaders) {
const rawHeaders = response.responseHeaders.split(/\r\n/);
rawHeaders.forEach(line => {
const parts = line.split(': ');
if (parts.length >= 2) {
headers.append(parts[0], parts.slice(1).join(': '));
}
});
}
headers.set('Content-Type', 'application/json');
const newResponse = new Response(newBody, {
status: response.status,
statusText: response.statusText,
headers: headers
});
resolve(newResponse);
} catch (e) {
console.warn('Intercepted non-JSON or parse error:', e);
// Fallback to original
const fallbackResponse = new Response(response.responseText, {
status: response.status,
statusText: response.statusText,
headers: response.responseHeaders
});
resolve(fallbackResponse);
}
},
onerror: function(err) {
reject(err);
}
});
});
}
return originalFetch.apply(this, args);
};
}
overrideFetch();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment