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