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