some files for the teal.town personal data server.
at main 141 lines 5.0 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.microcosm.blue/xrpc/com.bad-example.identity.resolveMiniDoc", 42 ); 43 url.searchParams.set("identifier", did); 44 const res = await fetch(url, { 45 headers: { 46 "User-Agent": "teal.town-homepage/1.0", 47 }, 48 }).then((r) => r.json()); 49 return { handle: res.handle || null, did }; 50 } catch { 51 return null; 52 } 53 }), 54 ); 55 56 const valid = handles.filter(Boolean); 57 const cols = 3; 58 const gap = " "; 59 60 const lines = [ 61 `welcome to teal.town!`, 62 ``, 63 `<a target="_blank" href="https://pdsmoover.com/moover/teal.town">move to town!</a>`, 64 ``, 65 `contact your webmaster:`, 66 ` \\ o /`, 67 ` | <a href="mailto:${email}">${email}</a>`, 68 ` / \\`, 69 ``, 70 ]; 71 72 // --- legal stuff (links left, guy right) --- 73 if (links.privacyPolicy || links.termsOfService) { 74 const ppPlain = links.privacyPolicy 75 ? `privacy policy: ${links.privacyPolicy}` 76 : ""; 77 const tosPlain = links.termsOfService 78 ? `terms of service: ${links.termsOfService}` 79 : ""; 80 const longest = Math.max(ppPlain.length, tosPlain.length); 81 const offset = longest + 10; 82 83 lines.push(`legal stuff:`); 84 if (links.privacyPolicy) { 85 const plain = `privacy policy: ${links.privacyPolicy}`; 86 const padded = plain.padEnd(offset, " "); 87 const html = padded.replace( 88 links.privacyPolicy, 89 `<a href="${links.privacyPolicy}">${links.privacyPolicy}</a>`, 90 ); 91 lines.push(html + ` \\ o / `); 92 } 93 if (links.termsOfService) { 94 const plain = `terms of service: ${links.termsOfService}`; 95 const padded = plain.padEnd(offset, " "); 96 const html = padded.replace( 97 links.termsOfService, 98 `<a href="${links.termsOfService}">${links.termsOfService}</a>`, 99 ); 100 lines.push(html + `💼 |`); 101 } 102 lines.push("".padEnd(offset, " ") + ` / \\`); 103 lines.push(``); 104 } 105 106 lines.push(`meet your neighbors:`); 107 108 for (let r = 0; r < valid.length; r += cols) { 109 const chunk = valid.slice(r, r + cols); 110 const strip = [[], [], [], [], []]; // 5 rows: head, arms, legs, handle, icons 111 112 for (const { handle, did } of chunk) { 113 const len = handle.length; 114 const pad = (len - 5) / 2; 115 const p1 = Math.max(0, Math.ceil(pad)); 116 const p2 = Math.max(0, Math.floor(pad)); 117 118 if (handle === "bailey.teal.town") { 119 strip[0].push(" ".repeat(p1) + "\\ 🤠 /" + " ".repeat(p2)); 120 } else { 121 strip[0].push(" ".repeat(p1) + "\\ o /" + " ".repeat(p2)); 122 } 123 strip[1].push(" ".repeat(p1 + 2) + "|" + " ".repeat(p2 + 2)); 124 strip[2].push(" ".repeat(p1 + 1) + "/ \\" + " ".repeat(p2 + 1)); 125 strip[3].push(handle); 126 127 const icons = `<a href="https://bsky.app/profile/${did}">🦋</a> <a href="https://pdsls.dev/at://${did}">📁</a>`; 128 // Use same padding as head row for perfect alignment with ASCII art 129 strip[4].push(" ".repeat(p1) + icons + " ".repeat(p2)); 130 } 131 132 for (const row of strip) lines.push(row.join(gap)); 133 lines.push(""); // blank line between 3-person rows 134 } 135 136 const pre = document.getElementById("out"); 137 pre.innerHTML += lines.join("\n"); 138 })(); 139 </script> 140 </body> 141</html>