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