Skip to content

Instantly share code, notes, and snippets.

@blueset
Created June 17, 2025 02:36
Show Gist options
  • Save blueset/43d08a2998e691da7453e14b98fc96e5 to your computer and use it in GitHub Desktop.
Save blueset/43d08a2998e691da7453e14b98fc96e5 to your computer and use it in GitHub Desktop.
Add indications to accounts on Threads for if it is federated with the ActivityPub fediverse.
// ==UserScript==
// @name Threads Federation Detector
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Detect federated accounts on Threads. Add indications to accounts on Threads for if it is federated with the ActivityPub fediverse.
// @author Eana Hufwe
// @match https://www.threads.com/*
// @match https://www.threads.net/*
// @match https://threads.com/*
// @match https://threads.net/*
// @grant GM_xmlhttpRequest
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
// Cache for webfinger results
const federationCache = new Map();
const visitedLinks = new WeakSet();
// Add CSS for the federation indicator
const style = document.createElement('style');
style.textContent = `
a.federation-indicator.federated {
filter: drop-shadow(0 0 3px #00d084) drop-shadow(0 0 6px #00d084);
}
a.federation-indicator:not(.federated) {
filter: drop-shadow(0 0 3px #ff3b30) drop-shadow(0 0 6px #ff3b30);
text-decoration: line-through;
}
`;
document.head.appendChild(style);
function checkWebfinger(account) {
return new Promise((resolve) => {
GM_xmlhttpRequest({
method: 'GET',
url: `https://threads.net/.well-known/webfinger?resource=acct:${account}@threads.net`,
onload: function(response) {
resolve(response.status === 200);
},
onerror: function() {
resolve(false);
}
});
});
}
async function processAccountLink(link) {
if (link.classList.contains('federation-indicator')) {
return;
}
const href = link.getAttribute('href');
const match = href.match(/^\/@([a-zA-Z0-9_\-\.]+)$/);
if (!match) {
return;
}
const account = match[1];
// Check cache first
if (federationCache.has(account)) {
if (federationCache.get(account)) {
addFederationIndicator(link);
} else {
addNonFederationIndicator(link);
}
return;
}
// Make webfinger request
const isFederated = await checkWebfinger(account);
console.log("checkWebfinger", account, isFederated);
federationCache.set(account, isFederated);
if (isFederated) {
addFederationIndicator(link);
} else {
addNonFederationIndicator(link);
}
}
function addFederationIndicator(link) {
// Check if indicator already exists
if (link.classList.contains('federation-indicator')) {
return;
}
link.classList.add('federation-indicator', 'federated');
link.title = 'Federated account';
}
function addNonFederationIndicator(link) {
// Check if indicator already exists
if (link.classList.contains('federation-indicator')) {
return;
}
link.classList.add('federation-indicator');
link.title = 'Non-federated account';
}
function scanForAccountLinks() {
const links = document.querySelectorAll('a[href^="/@"]');
links.forEach(processAccountLink);
}
// Initial scan when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', scanForAccountLinks);
} else {
scanForAccountLinks();
}
// Watch for dynamic content changes
const observer = new MutationObserver((mutations) => {
let hasNewLinks = false;
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
const shouldLog = node?.innerHtml?.match(/href="@\/[a-zA-Z0-9_\-\.]+"/g);
if (node.matches('a[href^="/@"]')) {
if (shouldLog) console.log("added node match", node);
processAccountLink(node);
hasNewLinks = true;
} else if (node.querySelector) {
const newLinks = node.querySelectorAll('a[href^="/@"]');
if (shouldLog) console.log("added node qs", node, newLinks);
if (newLinks.length > 0) {
newLinks.forEach(processAccountLink);
hasNewLinks = true;
}
} else {
if (shouldLog) console.log("added node else", node);
}
}
});
});
});
observer.observe(document.body || document.documentElement, {
childList: true,
subtree: true
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment