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})();