(() => { // 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; }; 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) { await 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(); } }); }; const findProfileHeaderWithRetry = (retryCount = 0, maxRetries = 10) => { const nameElements = document.querySelectorAll( '[data-testid="profileHeaderDisplayName"]', ); const nameElement = nameElements[nameElements.length - 1]; if (nameElement) { console.log("Profile header found"); return nameElement; } if (retryCount < maxRetries) { // Retry with exponential backoff const delay = Math.min(100 * 1.5 ** retryCount, 2000); console.log( `Profile header not found, retrying in ${delay}ms (attempt ${retryCount + 1}/${maxRetries})`, ); return new Promise((resolve) => { setTimeout(() => { resolve(findProfileHeaderWithRetry(retryCount + 1, maxRetries)); }, delay); }); } console.log("Failed to find profile header after maximum retries"); return null; }; // Function to display verification badge on the profile const displayVerificationBadge = async (verifierHandles) => { // Find the profile header or name element to add the badge to const nameElement = await findProfileHeaderWithRetry(); 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); updateTrustedUsersList(); }); } }; // 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: