the home of serif.blue
1(() => { 2 // Script has already been initialized check 3 if (window.bskyTrustedUsersInitialized) { 4 console.log("Trusted Users script already initialized"); 5 return; 6 } 7 8 // Mark script as initialized 9 window.bskyTrustedUsersInitialized = true; 10 11 // Define a storage key for trusted users 12 const TRUSTED_USERS_STORAGE_KEY = "bsky_trusted_users"; 13 14 // Function to get trusted users from local storage 15 const getTrustedUsers = () => { 16 const storedUsers = localStorage.getItem(TRUSTED_USERS_STORAGE_KEY); 17 return storedUsers ? JSON.parse(storedUsers) : []; 18 }; 19 20 // Function to save trusted users to local storage 21 const saveTrustedUsers = (users) => { 22 localStorage.setItem(TRUSTED_USERS_STORAGE_KEY, JSON.stringify(users)); 23 }; 24 25 // Function to add a trusted user 26 const addTrustedUser = (handle) => { 27 const users = getTrustedUsers(); 28 if (!users.includes(handle)) { 29 users.push(handle); 30 saveTrustedUsers(users); 31 } 32 }; 33 34 // Function to remove a trusted user 35 const removeTrustedUser = (handle) => { 36 const users = getTrustedUsers(); 37 const updatedUsers = users.filter((user) => user !== handle); 38 saveTrustedUsers(updatedUsers); 39 }; 40 41 // Store all verifiers for a profile 42 let profileVerifiers = []; 43 44 // Store current profile DID 45 let currentProfileDid = null; 46 47 // Function to check if a trusted user has verified the current profile 48 const checkTrustedUserVerifications = async (profileDid) => { 49 currentProfileDid = profileDid; // Store for recheck functionality 50 const trustedUsers = getTrustedUsers(); 51 profileVerifiers = []; // Reset the verifiers list 52 53 if (trustedUsers.length === 0) { 54 console.log("No trusted users to check for verifications"); 55 return false; 56 } 57 58 console.log(`Checking if any trusted users have verified ${profileDid}`); 59 60 // Use Promise.all to fetch all verification data in parallel 61 const verificationPromises = trustedUsers.map(async (trustedUser) => { 62 try { 63 const response = await fetch( 64 `https://bsky.social/xrpc/com.atproto.repo.listRecords?repo=${trustedUser}&collection=app.bsky.graph.verification`, 65 ); 66 const data = await response.json(); 67 68 // Check if this trusted user has verified the current profile 69 if (data.records && data.records.length > 0) { 70 for (const record of data.records) { 71 if (record.value && record.value.subject === profileDid) { 72 console.log( 73 `${profileDid} is verified by trusted user ${trustedUser}`, 74 ); 75 76 // Add to verifiers list 77 profileVerifiers.push(trustedUser); 78 } 79 } 80 } 81 return { trustedUser, success: true }; 82 } catch (error) { 83 console.error( 84 `Error checking verifications from ${trustedUser}:`, 85 error, 86 ); 87 return { trustedUser, success: false, error }; 88 } 89 }); 90 91 // Wait for all verification checks to complete 92 const results = await Promise.all(verificationPromises); 93 94 // Log summary of API calls 95 console.log(`API calls completed: ${results.length}`); 96 console.log(`Successful calls: ${results.filter((r) => r.success).length}`); 97 console.log(`Failed calls: ${results.filter((r) => !r.success).length}`); 98 99 // If we have verifiers, display the badge 100 if (profileVerifiers.length > 0) { 101 displayVerificationBadge(profileVerifiers); 102 return true; 103 } 104 105 console.log(`${profileDid} is not verified by any trusted users`); 106 107 // Add recheck button even when no verifications are found 108 createPillButtons(); 109 110 return false; 111 }; 112 113 // Function to create a pill with recheck and settings buttons 114 const createPillButtons = () => { 115 // Remove existing buttons if any 116 const existingPill = document.getElementById( 117 "trusted-users-pill-container", 118 ); 119 if (existingPill) { 120 existingPill.remove(); 121 } 122 123 // Create pill container 124 const pillContainer = document.createElement("div"); 125 pillContainer.id = "trusted-users-pill-container"; 126 pillContainer.style.cssText = ` 127 position: fixed; 128 bottom: 20px; 129 right: 20px; 130 z-index: 10000; 131 display: flex; 132 border-radius: 20px; 133 overflow: hidden; 134 box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); 135 `; 136 137 // Create recheck button (left half of pill) 138 const recheckButton = document.createElement("button"); 139 recheckButton.id = "trusted-users-recheck-button"; 140 recheckButton.innerHTML = "↻ Recheck"; 141 recheckButton.style.cssText = ` 142 padding: 10px 15px; 143 background-color: #2D578D; 144 color: white; 145 border: none; 146 cursor: pointer; 147 font-weight: bold; 148 border-top-left-radius: 20px; 149 border-bottom-left-radius: 20px; 150 `; 151 152 // Add click event to recheck 153 recheckButton.addEventListener("click", async () => { 154 if (currentProfileDid) { 155 // Remove any existing badges when rechecking 156 const existingBadge = document.getElementById( 157 "user-trusted-verification-badge", 158 ); 159 if (existingBadge) { 160 existingBadge.remove(); 161 } 162 163 // Show loading state 164 recheckButton.innerHTML = "⟳ Checking..."; 165 recheckButton.disabled = true; 166 167 // Recheck verifications 168 await checkTrustedUserVerifications(currentProfileDid); 169 170 // Reset button 171 recheckButton.innerHTML = "↻ Recheck"; 172 recheckButton.disabled = false; 173 } 174 }); 175 176 // Create vertical divider 177 const divider = document.createElement("div"); 178 divider.style.cssText = ` 179 width: 1px; 180 background-color: rgba(255, 255, 255, 0.3); 181 `; 182 183 // Create settings button (right half of pill) 184 const settingsButton = document.createElement("button"); 185 settingsButton.id = "bsky-trusted-settings-button"; 186 settingsButton.textContent = "Settings"; 187 settingsButton.style.cssText = ` 188 padding: 10px 15px; 189 background-color: #2D578D; 190 color: white; 191 border: none; 192 cursor: pointer; 193 font-weight: bold; 194 border-top-right-radius: 20px; 195 border-bottom-right-radius: 20px; 196 `; 197 198 // Add elements to pill 199 pillContainer.appendChild(recheckButton); 200 pillContainer.appendChild(divider); 201 pillContainer.appendChild(settingsButton); 202 203 // Add pill to page 204 document.body.appendChild(pillContainer); 205 206 // Add event listener to settings button 207 settingsButton.addEventListener("click", () => { 208 if (settingsModal) { 209 settingsModal.style.display = "flex"; 210 updateTrustedUsersList(); 211 } else { 212 createSettingsModal(); 213 } 214 }); 215 }; 216 217 // Legacy function kept for compatibility but redirects to the new implementation 218 const addRecheckButton = () => { 219 createPillButtons(); 220 }; 221 222 // Function to display verification badge on the profile 223 const displayVerificationBadge = (verifierHandles) => { 224 // Find the profile header or name element to add the badge to 225 const nameElement = document.querySelector( 226 '[data-testid="profileHeaderDisplayName"]', 227 ); 228 229 if (nameElement) { 230 // Check if badge already exists 231 if (!document.getElementById("user-trusted-verification-badge")) { 232 const badge = document.createElement("span"); 233 badge.id = "user-trusted-verification-badge"; 234 badge.innerHTML = "✓"; 235 236 // Create tooltip text with all verifiers 237 const verifiersText = 238 verifierHandles.length > 1 239 ? `Verified by: ${verifierHandles.join(", ")}` 240 : `Verified by ${verifierHandles[0]}`; 241 242 badge.title = verifiersText; 243 badge.style.cssText = ` 244 background-color: #0070ff; 245 color: white; 246 border-radius: 50%; 247 width: 18px; 248 height: 18px; 249 margin-left: 8px; 250 font-size: 12px; 251 font-weight: bold; 252 cursor: help; 253 display: inline-flex; 254 align-items: center; 255 justify-content: center; 256 `; 257 258 // Add a click event to show all verifiers 259 badge.addEventListener("click", (e) => { 260 e.stopPropagation(); 261 showVerifiersPopup(verifierHandles); 262 }); 263 264 nameElement.appendChild(badge); 265 } 266 } 267 268 // Also add pill buttons when verification is found 269 createPillButtons(); 270 }; 271 272 // Function to show a popup with all verifiers 273 const showVerifiersPopup = (verifierHandles) => { 274 // Remove existing popup if any 275 const existingPopup = document.getElementById("verifiers-popup"); 276 if (existingPopup) { 277 existingPopup.remove(); 278 } 279 280 // Create popup 281 const popup = document.createElement("div"); 282 popup.id = "verifiers-popup"; 283 popup.style.cssText = ` 284 position: fixed; 285 top: 50%; 286 left: 50%; 287 transform: translate(-50%, -50%); 288 background-color: #24273A; 289 padding: 20px; 290 border-radius: 10px; 291 z-index: 10002; 292 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); 293 max-width: 400px; 294 width: 90%; 295 `; 296 297 // Create popup content 298 popup.innerHTML = ` 299 <h3 style="margin-top: 0; color: white;">Profile Verifiers</h3> 300 <div style="max-height: 300px; overflow-y: auto;"> 301 ${verifierHandles 302 .map( 303 (handle) => ` 304 <div style="padding: 8px 0; border-bottom: 1px solid #444; color: white;"> 305 ${handle} 306 </div> 307 `, 308 ) 309 .join("")} 310 </div> 311 <button id="close-verifiers-popup" style=" 312 margin-top: 15px; 313 padding: 8px 15px; 314 background-color: #473A3A; 315 color: white; 316 border: none; 317 border-radius: 4px; 318 cursor: pointer; 319 ">Close</button> 320 `; 321 322 // Add to body 323 document.body.appendChild(popup); 324 325 // Add close handler 326 document 327 .getElementById("close-verifiers-popup") 328 .addEventListener("click", () => { 329 popup.remove(); 330 }); 331 332 // Close when clicking outside 333 document.addEventListener("click", function closePopup(e) { 334 if (!popup.contains(e.target)) { 335 popup.remove(); 336 document.removeEventListener("click", closePopup); 337 } 338 }); 339 }; 340 341 // Create settings modal 342 let settingsModal = null; 343 344 // Function to update the list of trusted users in the UI 345 const updateTrustedUsersList = () => { 346 const trustedUsersList = document.getElementById("trustedUsersList"); 347 if (!trustedUsersList) return; 348 349 const users = getTrustedUsers(); 350 trustedUsersList.innerHTML = ""; 351 352 if (users.length === 0) { 353 trustedUsersList.innerHTML = "<p>No trusted users added yet.</p>"; 354 return; 355 } 356 357 for (const user of users) { 358 const userItem = document.createElement("div"); 359 userItem.style.cssText = ` 360 display: flex; 361 justify-content: space-between; 362 align-items: center; 363 padding: 8px 0; 364 border-bottom: 1px solid #eee; 365 `; 366 367 userItem.innerHTML = ` 368 <span>${user}</span> 369 <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> 370 `; 371 372 trustedUsersList.appendChild(userItem); 373 } 374 375 // Add event listeners to remove buttons 376 const removeButtons = document.querySelectorAll(".remove-user"); 377 for (const btn of removeButtons) { 378 btn.addEventListener("click", (e) => { 379 const handle = e.target.getAttribute("data-handle"); 380 removeTrustedUser(handle); 381 updateTrustedUsersList(); 382 }); 383 } 384 }; 385 386 // Function to create the settings modal 387 const createSettingsModal = () => { 388 // Create modal container 389 settingsModal = document.createElement("div"); 390 settingsModal.id = "bsky-trusted-settings-modal"; 391 settingsModal.style.cssText = ` 392 display: none; 393 position: fixed; 394 top: 0; 395 left: 0; 396 width: 100%; 397 height: 100%; 398 background-color: rgba(0, 0, 0, 0.5); 399 z-index: 10001; 400 justify-content: center; 401 align-items: center; 402 `; 403 404 // Create modal content 405 const modalContent = document.createElement("div"); 406 modalContent.style.cssText = ` 407 background-color: #24273A; 408 padding: 20px; 409 border-radius: 10px; 410 width: 400px; 411 max-height: 80vh; 412 overflow-y: auto; 413 `; 414 415 // Create modal header 416 const modalHeader = document.createElement("div"); 417 modalHeader.innerHTML = `<h2 style="margin-top: 0;">Trusted Bluesky Users</h2>`; 418 419 // Create input form 420 const form = document.createElement("div"); 421 form.innerHTML = ` 422 <p>Add Bluesky handles you trust:</p> 423 <div style="display: flex; margin-bottom: 15px;"> 424 <input id="trustedUserInput" type="text" placeholder="username.bsky.social" style="flex: 1; padding: 8px; margin-right: 10px; border: 1px solid #ccc; border-radius: 4px;"> 425 <button id="addTrustedUserBtn" style="background-color: #2D578D; color: white; border: none; border-radius: 4px; padding: 8px 15px; cursor: pointer;">Add</button> 426 </div> 427 `; 428 429 // Create trusted users list 430 const trustedUsersList = document.createElement("div"); 431 trustedUsersList.id = "trustedUsersList"; 432 trustedUsersList.style.cssText = ` 433 margin-top: 15px; 434 border-top: 1px solid #eee; 435 padding-top: 15px; 436 `; 437 438 // Create close button 439 const closeButton = document.createElement("button"); 440 closeButton.textContent = "Close"; 441 closeButton.style.cssText = ` 442 margin-top: 20px; 443 padding: 8px 15px; 444 background-color: #473A3A; 445 border: none; 446 border-radius: 4px; 447 cursor: pointer; 448 `; 449 450 // Assemble modal 451 modalContent.appendChild(modalHeader); 452 modalContent.appendChild(form); 453 modalContent.appendChild(trustedUsersList); 454 modalContent.appendChild(closeButton); 455 settingsModal.appendChild(modalContent); 456 457 // Add to document 458 document.body.appendChild(settingsModal); 459 460 // Event listeners 461 closeButton.addEventListener("click", () => { 462 settingsModal.style.display = "none"; 463 }); 464 465 // Add trusted user button event 466 document 467 .getElementById("addTrustedUserBtn") 468 .addEventListener("click", () => { 469 const input = document.getElementById("trustedUserInput"); 470 const handle = input.value.trim(); 471 if (handle) { 472 addTrustedUser(handle); 473 input.value = ""; 474 updateTrustedUsersList(); 475 } 476 }); 477 478 // Close modal when clicking outside 479 settingsModal.addEventListener("click", (e) => { 480 if (e.target === settingsModal) { 481 settingsModal.style.display = "none"; 482 } 483 }); 484 485 // Initialize the list 486 updateTrustedUsersList(); 487 }; 488 489 // Function to create the settings UI if it doesn't exist yet 490 const createSettingsUI = () => { 491 // Create pill with buttons 492 createPillButtons(); 493 494 // Create the settings modal if it doesn't exist yet 495 if (!settingsModal) { 496 createSettingsModal(); 497 } 498 }; 499 500 // Function to check the current profile 501 const checkCurrentProfile = () => { 502 const currentUrl = window.location.href; 503 if (currentUrl.includes("bsky.app/profile/")) { 504 const handle = currentUrl.split("/profile/")[1].split("/")[0]; 505 console.log("Extracted handle:", handle); 506 507 // Create and add the settings UI (only once) 508 createSettingsUI(); 509 510 // Fetch user profile data 511 fetch( 512 `https://bsky.social/xrpc/com.atproto.repo.getRecord?repo=${handle}&collection=app.bsky.actor.profile&rkey=self`, 513 ) 514 .then((response) => response.json()) 515 .then((data) => { 516 console.log("User profile data:", data); 517 518 // Extract the DID from the profile data 519 const did = data.uri.split("/")[2]; 520 console.log("User DID:", did); 521 522 // Now fetch the app.bsky.graph.verification data specifically 523 fetch( 524 `https://bsky.social/xrpc/com.atproto.repo.listRecords?repo=${handle}&collection=app.bsky.graph.verification`, 525 ) 526 .then((response) => response.json()) 527 .then((verificationData) => { 528 console.log("Verification data:", verificationData); 529 if ( 530 verificationData.records && 531 verificationData.records.length > 0 532 ) { 533 console.log( 534 "User has app.bsky.graph.verification:", 535 verificationData.records, 536 ); 537 } else { 538 console.log("User does not have app.bsky.graph.verification"); 539 } 540 541 // Check if any trusted users have verified this profile using the DID 542 checkTrustedUserVerifications(did); 543 }) 544 .catch((verificationError) => { 545 console.error( 546 "Error fetching verification data:", 547 verificationError, 548 ); 549 }); 550 }) 551 .catch((error) => { 552 console.error("Error checking profile:", error); 553 }); 554 555 console.log("Bluesky profile detected"); 556 } 557 }; 558 559 // Initial check 560 checkCurrentProfile(); 561 562 // Set up a MutationObserver to watch for URL changes 563 const observeUrlChanges = () => { 564 let lastUrl = location.href; 565 566 const observer = new MutationObserver(() => { 567 if (location.href !== lastUrl) { 568 lastUrl = location.href; 569 console.log("URL changed to:", location.href); 570 571 // Remove any existing badges when URL changes 572 const existingBadge = document.getElementById( 573 "user-trusted-verification-badge", 574 ); 575 if (existingBadge) { 576 existingBadge.remove(); 577 } 578 579 // Remove the pill container when URL changes 580 const existingPill = document.getElementById( 581 "trusted-users-pill-container", 582 ); 583 if (existingPill) { 584 existingPill.remove(); 585 } 586 587 // Check if we're on a profile page now 588 checkCurrentProfile(); 589 } 590 }); 591 592 observer.observe(document, { subtree: true, childList: true }); 593 }; 594 595 // Start observing for URL changes 596 observeUrlChanges(); 597})();