the home of serif.blue
1(() => { 2 // Define a storage key for trusted users 3 const TRUSTED_USERS_STORAGE_KEY = "bsky_trusted_users"; 4 5 // Function to get trusted users from local storage 6 const getTrustedUsers = () => { 7 const storedUsers = localStorage.getItem(TRUSTED_USERS_STORAGE_KEY); 8 return storedUsers ? JSON.parse(storedUsers) : []; 9 }; 10 11 // Function to save trusted users to local storage 12 const saveTrustedUsers = (users) => { 13 localStorage.setItem(TRUSTED_USERS_STORAGE_KEY, JSON.stringify(users)); 14 }; 15 16 // Function to add a trusted user 17 const addTrustedUser = (handle) => { 18 const users = getTrustedUsers(); 19 if (!users.includes(handle)) { 20 users.push(handle); 21 saveTrustedUsers(users); 22 } 23 }; 24 25 // Function to remove a trusted user 26 const removeTrustedUser = (handle) => { 27 const users = getTrustedUsers(); 28 const updatedUsers = users.filter((user) => user !== handle); 29 saveTrustedUsers(updatedUsers); 30 }; 31 32 // Store all verifiers for a profile 33 let profileVerifiers = []; 34 35 // Function to check if a trusted user has verified the current profile 36 const checkTrustedUserVerifications = async (currentProfileDid) => { 37 const trustedUsers = getTrustedUsers(); 38 profileVerifiers = []; // Reset the verifiers list 39 40 if (trustedUsers.length === 0) { 41 console.log("No trusted users to check for verifications"); 42 return false; 43 } 44 45 console.log( 46 `Checking if any trusted users have verified ${currentProfileDid}`, 47 ); 48 49 let isVerifiedByTrustedUser = false; 50 51 // Use Promise.all to fetch all verification data in parallel 52 const verificationPromises = trustedUsers.map(async (trustedUser) => { 53 try { 54 const response = await fetch( 55 `https://bsky.social/xrpc/com.atproto.repo.listRecords?repo=${trustedUser}&collection=app.bsky.graph.verification`, 56 ); 57 const data = await response.json(); 58 59 // Check if this trusted user has verified the current profile 60 if (data.records && data.records.length > 0) { 61 for (const record of data.records) { 62 if (record.value && record.value.subject === currentProfileDid) { 63 console.log( 64 `${currentProfileDid} is verified by trusted user ${trustedUser}`, 65 ); 66 67 // Add to verifiers list 68 profileVerifiers.push(trustedUser); 69 isVerifiedByTrustedUser = true; 70 } 71 } 72 } 73 return { trustedUser, success: true }; 74 } catch (error) { 75 console.error( 76 `Error checking verifications from ${trustedUser}:`, 77 error, 78 ); 79 return { trustedUser, success: false, error }; 80 } 81 }); 82 83 // Wait for all verification checks to complete 84 const results = await Promise.all(verificationPromises); 85 86 // Log summary of API calls 87 console.log(`API calls completed: ${results.length}`); 88 console.log(`Successful calls: ${results.filter((r) => r.success).length}`); 89 console.log(`Failed calls: ${results.filter((r) => !r.success).length}`); 90 91 // If we have verifiers, display the badge 92 if (profileVerifiers.length > 0) { 93 displayVerificationBadge(profileVerifiers); 94 return true; 95 } 96 97 console.log(`${currentProfileDid} is not verified by any trusted users`); 98 return false; 99 }; 100 101 // Function to display verification badge on the profile 102 const displayVerificationBadge = (verifierHandles) => { 103 // Find the profile header or name element to add the badge to 104 const nameElement = document.querySelector( 105 '[data-testid="profileHeaderDisplayName"]', 106 ); 107 108 if (nameElement) { 109 // Check if badge already exists 110 if (!document.getElementById("user-trusted-verification-badge")) { 111 const badge = document.createElement("span"); 112 badge.id = "user-trusted-verification-badge"; 113 badge.innerHTML = "✓"; 114 115 // Create tooltip text with all verifiers 116 const verifiersText = 117 verifierHandles.length > 1 118 ? `Verified by: ${verifierHandles.join(", ")}` 119 : `Verified by ${verifierHandles[0]}`; 120 121 badge.title = verifiersText; 122 badge.style.cssText = ` 123 background-color: #0070ff; 124 color: white; 125 border-radius: 50%; 126 width: 18px; 127 height: 18px; 128 margin-left: 8px; 129 font-size: 12px; 130 font-weight: bold; 131 cursor: help; 132 display: inline-flex; 133 align-items: center; 134 justify-content: center; 135 `; 136 137 // Add a click event to show all verifiers 138 badge.addEventListener("click", (e) => { 139 e.stopPropagation(); 140 showVerifiersPopup(verifierHandles); 141 }); 142 143 nameElement.appendChild(badge); 144 } 145 } 146 }; 147 148 // Function to show a popup with all verifiers 149 const showVerifiersPopup = (verifierHandles) => { 150 // Remove existing popup if any 151 const existingPopup = document.getElementById("verifiers-popup"); 152 if (existingPopup) { 153 existingPopup.remove(); 154 } 155 156 // Create popup 157 const popup = document.createElement("div"); 158 popup.id = "verifiers-popup"; 159 popup.style.cssText = ` 160 position: fixed; 161 top: 50%; 162 left: 50%; 163 transform: translate(-50%, -50%); 164 background-color: #24273A; 165 padding: 20px; 166 border-radius: 10px; 167 z-index: 10002; 168 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); 169 max-width: 400px; 170 width: 90%; 171 `; 172 173 // Create popup content 174 popup.innerHTML = ` 175 <h3 style="margin-top: 0; color: white;">Profile Verifiers</h3> 176 <div style="max-height: 300px; overflow-y: auto;"> 177 ${verifierHandles 178 .map( 179 (handle) => ` 180 <div style="padding: 8px 0; border-bottom: 1px solid #444; color: white;"> 181 ${handle} 182 </div> 183 `, 184 ) 185 .join("")} 186 </div> 187 <button id="close-verifiers-popup" style=" 188 margin-top: 15px; 189 padding: 8px 15px; 190 background-color: #473A3A; 191 color: white; 192 border: none; 193 border-radius: 4px; 194 cursor: pointer; 195 ">Close</button> 196 `; 197 198 // Add to body 199 document.body.appendChild(popup); 200 201 // Add close handler 202 document 203 .getElementById("close-verifiers-popup") 204 .addEventListener("click", () => { 205 popup.remove(); 206 }); 207 208 // Close when clicking outside 209 document.addEventListener("click", function closePopup(e) { 210 if (!popup.contains(e.target)) { 211 popup.remove(); 212 document.removeEventListener("click", closePopup); 213 } 214 }); 215 }; 216 217 // Create settings button and modal 218 const createSettingsUI = () => { 219 // Create settings button 220 const settingsButton = document.createElement("button"); 221 settingsButton.textContent = "Trusted Users Settings"; 222 settingsButton.style.cssText = ` 223 position: fixed; 224 bottom: 20px; 225 right: 20px; 226 z-index: 10000; 227 padding: 10px 15px; 228 background-color: #2D578D; 229 color: white; 230 border: none; 231 border-radius: 20px; 232 cursor: pointer; 233 font-weight: bold; 234 `; 235 236 // Create modal container 237 const modal = document.createElement("div"); 238 modal.style.cssText = ` 239 display: none; 240 position: fixed; 241 top: 0; 242 left: 0; 243 width: 100%; 244 height: 100%; 245 background-color: rgba(0, 0, 0, 0.5); 246 z-index: 10001; 247 justify-content: center; 248 align-items: center; 249 `; 250 251 // Create modal content 252 const modalContent = document.createElement("div"); 253 modalContent.style.cssText = ` 254 background-color: #24273A; 255 padding: 20px; 256 border-radius: 10px; 257 width: 400px; 258 max-height: 80vh; 259 overflow-y: auto; 260 `; 261 262 // Create modal header 263 const modalHeader = document.createElement("div"); 264 modalHeader.innerHTML = `<h2 style="margin-top: 0;">Trusted Bluesky Users</h2>`; 265 266 // Create input form 267 const form = document.createElement("div"); 268 form.innerHTML = ` 269 <p>Add Bluesky handles you trust:</p> 270 <div style="display: flex; margin-bottom: 15px;"> 271 <input id="trustedUserInput" type="text" placeholder="username.bsky.social" style="flex: 1; padding: 8px; margin-right: 10px; border: 1px solid #ccc; border-radius: 4px;"> 272 <button id="addTrustedUserBtn" style="background-color: #2D578D; color: white; border: none; border-radius: 4px; padding: 8px 15px; cursor: pointer;">Add</button> 273 </div> 274 `; 275 276 // Create trusted users list 277 const trustedUsersList = document.createElement("div"); 278 trustedUsersList.id = "trustedUsersList"; 279 trustedUsersList.style.cssText = ` 280 margin-top: 15px; 281 border-top: 1px solid #eee; 282 padding-top: 15px; 283 `; 284 285 // Create close button 286 const closeButton = document.createElement("button"); 287 closeButton.textContent = "Close"; 288 closeButton.style.cssText = ` 289 margin-top: 20px; 290 padding: 8px 15px; 291 background-color: #473A3A; 292 border: none; 293 border-radius: 4px; 294 cursor: pointer; 295 `; 296 297 // Assemble modal 298 modalContent.appendChild(modalHeader); 299 modalContent.appendChild(form); 300 modalContent.appendChild(trustedUsersList); 301 modalContent.appendChild(closeButton); 302 modal.appendChild(modalContent); 303 304 // Add elements to the document 305 document.body.appendChild(settingsButton); 306 document.body.appendChild(modal); 307 308 // Function to update the list of trusted users in the UI 309 const updateTrustedUsersList = () => { 310 const users = getTrustedUsers(); 311 trustedUsersList.innerHTML = ""; 312 313 if (users.length === 0) { 314 trustedUsersList.innerHTML = "<p>No trusted users added yet.</p>"; 315 return; 316 } 317 318 for (const user of users) { 319 const userItem = document.createElement("div"); 320 userItem.style.cssText = ` 321 display: flex; 322 justify-content: space-between; 323 align-items: center; 324 padding: 8px 0; 325 border-bottom: 1px solid #eee; 326 `; 327 328 userItem.innerHTML = ` 329 <span>${user}</span> 330 <button class="remove-user" data-handle="${user}" style="background-color: #CE3838; color: white; border: none; border-radius: 4px; padding: 5px 10px; cursor: pointer;">Remove</button> 331 `; 332 333 trustedUsersList.appendChild(userItem); 334 } 335 336 // Add event listeners to remove buttons 337 const removeButtons = document.querySelectorAll(".remove-user"); 338 for (const btn of removeButtons) { 339 btn.addEventListener("click", (e) => { 340 const handle = e.target.getAttribute("data-handle"); 341 removeTrustedUser(handle); 342 updateTrustedUsersList(); 343 }); 344 } 345 }; 346 347 // Event listeners 348 settingsButton.addEventListener("click", () => { 349 modal.style.display = "flex"; 350 updateTrustedUsersList(); 351 }); 352 353 closeButton.addEventListener("click", () => { 354 modal.style.display = "none"; 355 }); 356 357 document 358 .getElementById("addTrustedUserBtn") 359 .addEventListener("click", () => { 360 const input = document.getElementById("trustedUserInput"); 361 const handle = input.value.trim(); 362 if (handle) { 363 addTrustedUser(handle); 364 input.value = ""; 365 updateTrustedUsersList(); 366 } 367 }); 368 369 // Close modal when clicking outside 370 modal.addEventListener("click", (e) => { 371 if (e.target === modal) { 372 modal.style.display = "none"; 373 } 374 }); 375 }; 376 377 const currentUrl = window.location.href; 378 if (currentUrl.includes("bsky.app/profile/")) { 379 const handle = currentUrl.split("/profile/")[1].split("/")[0]; 380 console.log("Extracted handle:", handle); 381 382 // Create and add the settings UI 383 createSettingsUI(); 384 385 // Fetch user profile data 386 fetch( 387 `https://bsky.social/xrpc/com.atproto.repo.getRecord?repo=${handle}&collection=app.bsky.actor.profile&rkey=self`, 388 ) 389 .then((response) => response.json()) 390 .then((data) => { 391 console.log("User profile data:", data); 392 393 // Extract the DID from the profile data 394 const did = data.uri.split("/")[2]; 395 console.log("User DID:", did); 396 397 // Now fetch the app.bsky.graph.verification data specifically 398 fetch( 399 `https://bsky.social/xrpc/com.atproto.repo.listRecords?repo=${handle}&collection=app.bsky.graph.verification`, 400 ) 401 .then((response) => response.json()) 402 .then((verificationData) => { 403 console.log("Verification data:", verificationData); 404 if ( 405 verificationData.records && 406 verificationData.records.length > 0 407 ) { 408 console.log( 409 "User has app.bsky.graph.verification:", 410 verificationData.records, 411 ); 412 } else { 413 console.log("User does not have app.bsky.graph.verification"); 414 } 415 416 // Check if any trusted users have verified this profile using the DID 417 checkTrustedUserVerifications(did); 418 }) 419 .catch((verificationError) => { 420 console.error( 421 "Error fetching verification data:", 422 verificationError, 423 ); 424 }); 425 }) 426 .catch((error) => { 427 console.error("Error checking profile:", error); 428 }); 429 430 console.log("Example domain detected"); 431 } 432})();