some files for the teal.town personal data server.
at main 4.8 kB view raw
1<html> 2 <head> 3 <meta name="color-scheme" content="light dark" /> 4 </head> 5 <body style="pointer-events: auto"> 6 <pre 7 id="out" 8 style=" 9 overflow-wrap: break-word; 10 white-space: pre-wrap; 11 pointer-events: auto; 12 margin: 0; 13 font-family: monospace; 14 " 15 ></pre> 16 17 <script> 18 (async () => { 19 const PDS_URL = "teal.town"; 20 // --- fetch server info --- 21 const serverInfo = await fetch( 22 `https://${PDS_URL}/xrpc/com.atproto.server.describeServer` 23 ) 24 .then((r) => r.json()) 25 .catch(() => ({})); 26 27 const email = serverInfo.contact?.email || "admin@teal.town"; 28 const links = serverInfo.links || {}; 29 30 // --- fetch & build neighbors --- 31 const listUrl = `https://${PDS_URL}/xrpc/com.atproto.sync.listRepos?limit=9`; 32 const repos = await fetch(listUrl) 33 .then((r) => r.json()) 34 .catch(() => ({ repos: [] })); 35 const dids = (repos.repos || []).map((r) => r.did).filter(Boolean); 36 37 const handles = await Promise.all( 38 dids.map(async (did) => { 39 try { 40 const url = new URL( 41 "https://slingshot.mmatt.net/xrpc/com.bad-example.identity.resolveMiniDoc" 42 ); 43 url.searchParams.set("identifier", did); 44 const res = await fetch(url).then((r) => r.json()); 45 return { handle: res.handle || null, did }; 46 } catch { 47 return null; 48 } 49 }) 50 ); 51 52 const valid = handles.filter(Boolean); 53 const cols = 3; 54 const gap = " "; 55 56 const lines = [ 57 `welcome to teal.town!`, 58 ``, 59 `<a target="_blank" href="https://pdsmoover.com/moover/teal.town">move to town!</a>`, 60 ``, 61 `contact your webmaster:`, 62 ` \\ o /`, 63 ` | <a href="mailto:${email}">${email}</a>`, 64 ` / \\`, 65 ``, 66 ]; 67 68 // --- legal stuff (links left, guy right) --- 69 if (links.privacyPolicy || links.termsOfService) { 70 const ppPlain = links.privacyPolicy 71 ? `privacy policy: ${links.privacyPolicy}` 72 : ""; 73 const tosPlain = links.termsOfService 74 ? `terms of service: ${links.termsOfService}` 75 : ""; 76 const longest = Math.max(ppPlain.length, tosPlain.length); 77 const offset = longest + 10; 78 79 lines.push(`legal stuff:`); 80 if (links.privacyPolicy) { 81 const plain = `privacy policy: ${links.privacyPolicy}`; 82 const padded = plain.padEnd(offset, " "); 83 const html = padded.replace( 84 links.privacyPolicy, 85 `<a href="${links.privacyPolicy}">${links.privacyPolicy}</a>` 86 ); 87 lines.push(html + ` \\ o / `); 88 } 89 if (links.termsOfService) { 90 const plain = `terms of service: ${links.termsOfService}`; 91 const padded = plain.padEnd(offset, " "); 92 const html = padded.replace( 93 links.termsOfService, 94 `<a href="${links.termsOfService}">${links.termsOfService}</a>` 95 ); 96 lines.push(html + `💼 |`); 97 } 98 lines.push("".padEnd(offset, " ") + ` / \\`); 99 lines.push(``); 100 } 101 102 lines.push(`meet your neighbors:`); 103 104 for (let r = 0; r < valid.length; r += cols) { 105 const chunk = valid.slice(r, r + cols); 106 const strip = [[], [], [], [], []]; // 5 rows: head, arms, legs, handle, icons 107 108 for (const { handle, did } of chunk) { 109 const len = handle.length; 110 const pad = (len - 5) / 2; 111 const p1 = Math.max(0, Math.ceil(pad)); 112 const p2 = Math.max(0, Math.floor(pad)); 113 114 if (handle === "bailey.teal.town") { 115 strip[0].push(" ".repeat(p1) + "\\ 🤠 /" + " ".repeat(p2)); 116 } else { 117 strip[0].push(" ".repeat(p1) + "\\ o /" + " ".repeat(p2)); 118 } 119 strip[1].push(" ".repeat(p1 + 2) + "|" + " ".repeat(p2 + 2)); 120 strip[2].push(" ".repeat(p1 + 1) + "/ \\" + " ".repeat(p2 + 1)); 121 strip[3].push(handle); 122 123 const icons = `<a href="https://bsky.app/profile/${did}">🦋</a> <a href="https://pdsls.dev/at://${did}">📁</a>`; 124 // Use same padding as head row for perfect alignment with ASCII art 125 strip[4].push(" ".repeat(p1) + icons + " ".repeat(p2)); 126 } 127 128 for (const row of strip) lines.push(row.join(gap)); 129 lines.push(""); // blank line between 3-person rows 130 } 131 132 const pre = document.getElementById("out"); 133 pre.innerHTML += lines.join("\n"); 134 })(); 135 </script> 136 </body> 137</html>