// ==UserScript== // @name Bluesky Community Verifications // @namespace https://tangled.sh/@dunkirk.sh/bunplayground // @version 0.1 // @description Shows verification badges from trusted community members on Bluesky // @author Kieran Klukas // @match https://bsky.app/* // @grant none // @run-at document-end // ==/UserScript== (() => { // Script has already been initialized check if (window.bskyTrustedUsersInitialized) { console.log("Trusted Users script already initialized"); return; } // Mark script as initialized window.bskyTrustedUsersInitialized = true; // Define storage keys const TRUSTED_USERS_STORAGE_KEY = "bsky_trusted_users"; const VERIFICATION_CACHE_STORAGE_KEY = "bsky_verification_cache"; const CACHE_EXPIRY_TIME = 24 * 60 * 60 * 1000; // 24 hours // Function to get trusted users from local storage const getTrustedUsers = () => { const storedUsers = localStorage.getItem(TRUSTED_USERS_STORAGE_KEY); return storedUsers ? JSON.parse(storedUsers) : []; }; // Function to save trusted users to local storage const saveTrustedUsers = (users) => { localStorage.setItem(TRUSTED_USERS_STORAGE_KEY, JSON.stringify(users)); }; // Function to add a trusted user const addTrustedUser = (handle) => { const users = getTrustedUsers(); if (!users.includes(handle)) { users.push(handle); saveTrustedUsers(users); } }; // Function to remove a trusted user const removeTrustedUser = (handle) => { const users = getTrustedUsers(); const updatedUsers = users.filter((user) => user !== handle); saveTrustedUsers(updatedUsers); }; // Cache functions const getVerificationCache = () => { const cache = localStorage.getItem(VERIFICATION_CACHE_STORAGE_KEY); return cache ? JSON.parse(cache) : {}; }; const saveVerificationCache = (cache) => { localStorage.setItem(VERIFICATION_CACHE_STORAGE_KEY, JSON.stringify(cache)); }; const getCachedVerifications = (user) => { const cache = getVerificationCache(); return cache[user] || null; }; const cacheVerifications = (user, records) => { const cache = getVerificationCache(); cache[user] = { records, timestamp: Date.now(), }; saveVerificationCache(cache); }; const isCacheValid = (cacheEntry) => { return cacheEntry && Date.now() - cacheEntry.timestamp < CACHE_EXPIRY_TIME; }; // Function to remove a specific user from the verification cache const removeUserFromCache = (handle) => { const cache = getVerificationCache(); if (cache[handle]) { delete cache[handle]; saveVerificationCache(cache); console.log(`Removed ${handle} from verification cache`); } }; const clearCache = () => { localStorage.removeItem(VERIFICATION_CACHE_STORAGE_KEY); console.log("Verification cache cleared"); }; // Store all verifiers for a profile let profileVerifiers = []; // Store current profile DID let currentProfileDid = null; // Function to check if a trusted user has verified the current profile const checkTrustedUserVerifications = async (profileDid) => { currentProfileDid = profileDid; // Store for recheck functionality const trustedUsers = getTrustedUsers(); profileVerifiers = []; // Reset the verifiers list if (trustedUsers.length === 0) { console.log("No trusted users to check for verifications"); return false; } console.log(`Checking if any trusted users have verified ${profileDid}`); // Use Promise.all to fetch all verification data in parallel const verificationPromises = trustedUsers.map(async (trustedUser) => { try { // Helper function to fetch all verification records with pagination const fetchAllVerifications = async (user) => { // Check cache first const cachedData = getCachedVerifications(user); if (cachedData && isCacheValid(cachedData)) { console.log(`Using cached verification data for ${user}`); return cachedData.records; } console.log(`Fetching fresh verification data for ${user}`); let allRecords = []; let cursor = null; let hasMore = true; while (hasMore) { const url = cursor ? `https://bsky.social/xrpc/com.atproto.repo.listRecords?repo=${user}&collection=app.bsky.graph.verification&cursor=${cursor}` : `https://bsky.social/xrpc/com.atproto.repo.listRecords?repo=${user}&collection=app.bsky.graph.verification`; const response = await fetch(url); const data = await response.json(); if (data.records && data.records.length > 0) { allRecords = [...allRecords, ...data.records]; } if (data.cursor) { cursor = data.cursor; } else { hasMore = false; } } // Save to cache cacheVerifications(user, allRecords); return allRecords; }; // Fetch all verification records for this trusted user const records = await fetchAllVerifications(trustedUser); console.log(`Received verification data from ${trustedUser}`, { records, }); // Check if this trusted user has verified the current profile if (records.length > 0) { for (const record of records) { if (record.value && record.value.subject === profileDid) { console.log( `${profileDid} is verified by trusted user ${trustedUser}`, ); // Add to verifiers list profileVerifiers.push(trustedUser); break; // Once we find a verification, we can stop checking } } } return { trustedUser, success: true }; } catch (error) { console.error( `Error checking verifications from ${trustedUser}:`, error, ); return { trustedUser, success: false, error }; } }); // Wait for all verification checks to complete const results = await Promise.all(verificationPromises); // Log summary of API calls console.log(`API calls completed: ${results.length}`); console.log(`Successful calls: ${results.filter((r) => r.success).length}`); console.log(`Failed calls: ${results.filter((r) => !r.success).length}`); // If we have verifiers, display the badge if (profileVerifiers.length > 0) { displayVerificationBadge(profileVerifiers); return true; } console.log(`${profileDid} is not verified by any trusted users`); // Add recheck button even when no verifications are found createPillButtons(); return false; }; // Function to create a pill with recheck and settings buttons const createPillButtons = () => { // Remove existing buttons if any const existingPill = document.getElementById( "trusted-users-pill-container", ); if (existingPill) { existingPill.remove(); } // Create pill container const pillContainer = document.createElement("div"); pillContainer.id = "trusted-users-pill-container"; pillContainer.style.cssText = ` position: fixed; bottom: 20px; right: 20px; z-index: 10000; display: flex; border-radius: 20px; overflow: hidden; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); `; // Create recheck button (left half of pill) const recheckButton = document.createElement("button"); recheckButton.id = "trusted-users-recheck-button"; recheckButton.innerHTML = "↻ Recheck"; recheckButton.style.cssText = ` padding: 10px 15px; background-color: #2D578D; color: white; border: none; cursor: pointer; font-weight: bold; border-top-left-radius: 20px; border-bottom-left-radius: 20px; `; // Add click event to recheck recheckButton.addEventListener("click", async () => { if (currentProfileDid) { // Remove any existing badges when rechecking const existingBadge = document.getElementById( "user-trusted-verification-badge", ); if (existingBadge) { existingBadge.remove(); } // Show loading state recheckButton.innerHTML = "⟳ Checking..."; recheckButton.disabled = true; // Recheck verifications await checkTrustedUserVerifications(currentProfileDid); // Reset button recheckButton.innerHTML = "↻ Recheck"; recheckButton.disabled = false; } }); // Create vertical divider const divider = document.createElement("div"); divider.style.cssText = ` width: 1px; background-color: rgba(255, 255, 255, 0.3); `; // Create settings button (right half of pill) const settingsButton = document.createElement("button"); settingsButton.id = "bsky-trusted-settings-button"; settingsButton.textContent = "Settings"; settingsButton.style.cssText = ` padding: 10px 15px; background-color: #2D578D; color: white; border: none; cursor: pointer; font-weight: bold; border-top-right-radius: 20px; border-bottom-right-radius: 20px; `; // Add elements to pill pillContainer.appendChild(recheckButton); pillContainer.appendChild(divider); pillContainer.appendChild(settingsButton); // Add pill to page document.body.appendChild(pillContainer); // Add event listener to settings button settingsButton.addEventListener("click", () => { if (settingsModal) { settingsModal.style.display = "flex"; updateTrustedUsersList(); } else { createSettingsModal(); } }); }; // Function to display verification badge on the profile const displayVerificationBadge = (verifierHandles) => { // Find the profile header or name element to add the badge to const nameElements = document.querySelectorAll( '[data-testid="profileHeaderDisplayName"]', ); const nameElement = nameElements[nameElements.length - 1]; console.log(nameElement); if (nameElement) { // Remove existing badge if present const existingBadge = document.getElementById( "user-trusted-verification-badge", ); if (existingBadge) { existingBadge.remove(); } const badge = document.createElement("span"); badge.id = "user-trusted-verification-badge"; badge.innerHTML = "✓"; // Create tooltip text with all verifiers const verifiersText = verifierHandles.length > 1 ? `Verified by: ${verifierHandles.join(", ")}` : `Verified by ${verifierHandles[0]}`; badge.title = verifiersText; badge.style.cssText = ` background-color: #0070ff; color: white; border-radius: 50%; width: 18px; height: 18px; margin-left: 8px; font-size: 12px; font-weight: bold; cursor: help; display: inline-flex; align-items: center; justify-content: center; `; // Add a click event to show all verifiers badge.addEventListener("click", (e) => { e.stopPropagation(); showVerifiersPopup(verifierHandles); }); nameElement.appendChild(badge); } // Also add pill buttons when verification is found createPillButtons(); }; // Function to show a popup with all verifiers const showVerifiersPopup = (verifierHandles) => { // Remove existing popup if any const existingPopup = document.getElementById("verifiers-popup"); if (existingPopup) { existingPopup.remove(); } // Create popup const popup = document.createElement("div"); popup.id = "verifiers-popup"; popup.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: #24273A; padding: 20px; border-radius: 10px; z-index: 10002; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); max-width: 400px; width: 90%; `; // Create popup content popup.innerHTML = `
No trusted users added yet.
"; return; } for (const user of users) { const userItem = document.createElement("div"); userItem.style.cssText = ` display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid #eee; `; userItem.innerHTML = ` ${user} `; trustedUsersList.appendChild(userItem); } // Add event listeners to remove buttons const removeButtons = document.querySelectorAll(".remove-user"); for (const btn of removeButtons) { btn.addEventListener("click", (e) => { const handle = e.target.getAttribute("data-handle"); removeTrustedUser(handle); removeUserFromCache(handle); updateTrustedUsersList(); }); } }; const searchUsers = async (searchQuery) => { if (!searchQuery || searchQuery.length < 2) return []; try { const response = await fetch( `https://public.api.bsky.app/xrpc/app.bsky.actor.searchActors?term=${encodeURIComponent(searchQuery)}&limit=5`, ); const data = await response.json(); return data.actors || []; } catch (error) { console.error("Error searching for users:", error); return []; } }; // Function to create and show the autocomplete dropdown const showAutocompleteResults = (results, inputElement) => { // Remove existing dropdown if any const existingDropdown = document.getElementById("autocomplete-dropdown"); if (existingDropdown) existingDropdown.remove(); if (results.length === 0) return; // Create dropdown const dropdown = document.createElement("div"); dropdown.id = "autocomplete-dropdown"; dropdown.style.cssText = ` position: absolute; background-color: #2A2E3D; border: 1px solid #444; border-radius: 4px; box-shadow: 0 4px 8px rgba(0,0,0,0.2); max-height: 300px; overflow-y: auto; width: ${inputElement.offsetWidth}px; z-index: 10002; margin-top: 2px; `; // Position dropdown below input const inputRect = inputElement.getBoundingClientRect(); dropdown.style.left = `${inputRect.left}px`; dropdown.style.top = `${inputRect.bottom}px`; // Add results to dropdown for (const user of results) { const userItem = document.createElement("div"); userItem.className = "autocomplete-item"; userItem.style.cssText = ` display: flex; align-items: center; padding: 8px 12px; cursor: pointer; color: white; border-bottom: 1px solid #444; `; userItem.onmouseover = () => { userItem.style.backgroundColor = "#3A3F55"; }; userItem.onmouseout = () => { userItem.style.backgroundColor = ""; }; // Add profile picture const avatar = document.createElement("img"); avatar.src = user.avatar || "https://bsky.app/static/default-avatar.png"; avatar.style.cssText = ` width: 32px; height: 32px; border-radius: 50%; margin-right: 10px; object-fit: cover; `; // Add user info const userInfo = document.createElement("div"); userInfo.style.cssText = ` display: flex; flex-direction: column; `; const displayName = document.createElement("div"); displayName.textContent = user.displayName || user.handle; displayName.style.fontWeight = "bold"; const handle = document.createElement("div"); handle.textContent = user.handle; handle.style.fontSize = "0.8em"; handle.style.opacity = "0.8"; userInfo.appendChild(displayName); userInfo.appendChild(handle); userItem.appendChild(avatar); userItem.appendChild(userInfo); // Handle click on user item userItem.addEventListener("click", () => { inputElement.value = user.handle; dropdown.remove(); }); dropdown.appendChild(userItem); } document.body.appendChild(dropdown); // Close dropdown when clicking outside document.addEventListener("click", function closeDropdown(e) { if (e.target !== inputElement && !dropdown.contains(e.target)) { dropdown.remove(); document.removeEventListener("click", closeDropdown); } }); }; // Function to import verifications from the current user const importVerificationsFromSelf = async () => { try { // Check if we can determine the current user const bskyStorageData = localStorage.getItem("BSKY_STORAGE"); let userData = null; if (bskyStorageData) { try { const bskyStorage = JSON.parse(bskyStorageData); if (bskyStorage.session.currentAccount) { userData = bskyStorage.session.currentAccount; } } catch (error) { console.error("Error parsing BSKY_STORAGE data:", error); } } if (!userData || !userData.handle) { alert( "Could not determine your Bluesky handle. Please ensure you're logged in.", ); return; } if (!userData || !userData.handle) { alert( "Unable to determine your Bluesky handle. Make sure you're logged in.", ); return; } const userHandle = userData.handle; // Show loading state const importButton = document.getElementById("importVerificationsBtn"); const originalText = importButton.textContent; importButton.textContent = "Importing..."; importButton.disabled = true; // Fetch verification records from the user's account with pagination let allRecords = []; let cursor = null; let hasMore = true; while (hasMore) { const url = cursor ? `https://bsky.social/xrpc/com.atproto.repo.listRecords?repo=${userHandle}&collection=app.bsky.graph.verification&cursor=${cursor}` : `https://bsky.social/xrpc/com.atproto.repo.listRecords?repo=${userHandle}&collection=app.bsky.graph.verification`; const verificationResponse = await fetch(url); const data = await verificationResponse.json(); if (data.records && data.records.length > 0) { allRecords = [...allRecords, ...data.records]; } if (data.cursor) { cursor = data.cursor; } else { hasMore = false; } } const verificationData = { records: allRecords }; if (!verificationData.records || verificationData.records.length === 0) { alert("No verification records found in your account."); importButton.textContent = originalText; importButton.disabled = false; return; } // Extract the handles of verified users const verifiedUsers = []; for (const record of verificationData.records) { console.log(record.value.handle); verifiedUsers.push(record.value.handle); } // Add all found users to trusted users let addedCount = 0; for (const handle of verifiedUsers) { const existingUsers = getTrustedUsers(); if (!existingUsers.includes(handle)) { addTrustedUser(handle); addedCount++; } } // Update the UI updateTrustedUsersList(); // Reset button state importButton.textContent = originalText; importButton.disabled = false; // Show result alert( `Successfully imported ${addedCount} verified users from your account.`, ); } catch (error) { console.error("Error importing verifications:", error); alert("Error importing verifications. Check console for details."); const importButton = document.getElementById("importVerificationsBtn"); if (importButton) { importButton.textContent = "Import Verifications"; importButton.disabled = false; } } }; // Function to create the settings modal const createSettingsModal = () => { // Create modal container settingsModal = document.createElement("div"); settingsModal.id = "bsky-trusted-settings-modal"; settingsModal.style.cssText = ` display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 10001; justify-content: center; align-items: center; `; // Create modal content const modalContent = document.createElement("div"); modalContent.style.cssText = ` background-color: #24273A; padding: 20px; border-radius: 10px; width: 400px; max-height: 80vh; overflow-y: auto; `; // Create modal header const modalHeader = document.createElement("div"); modalHeader.innerHTML = `Add Bluesky handles you trust: