atmosphere explorer pds.ls
tool typescript atproto

better opengraph support

handle.invalid d7a041b1 bbba69b1

verified
+208
+1
index.html
··· 4 4 <meta charset="utf-8" /> 5 5 <meta name="viewport" content="width=device-width, initial-scale=1" /> 6 6 <link rel="icon" href="/favicon.ico" /> 7 + <meta name="theme-color" content="#76c4e5" /> 7 8 <meta property="og:title" content="PDSls" /> 8 9 <meta property="og:type" content="website" /> 9 10 <meta property="og:url" content="https://pds.ls" />
+207
public/_worker.js
··· 1 + const BOT_UAS = [ 2 + "Discordbot", 3 + "Twitterbot", 4 + "facebookexternalhit", 5 + "LinkedInBot", 6 + "Slackbot-LinkExpanding", 7 + "TelegramBot", 8 + "WhatsApp", 9 + "Iframely", 10 + "Embedly", 11 + "redditbot", 12 + "Cardyb", 13 + ]; 14 + 15 + function isBot(ua) { 16 + return BOT_UAS.some((b) => ua.includes(b)); 17 + } 18 + 19 + function esc(s) { 20 + return s 21 + .replace(/&/g, "&amp;") 22 + .replace(/</g, "&lt;") 23 + .replace(/>/g, "&gt;") 24 + .replace(/"/g, "&quot;"); 25 + } 26 + 27 + async function resolveDidDoc(did) { 28 + let docUrl; 29 + if (did.startsWith("did:plc:")) { 30 + docUrl = `https://plc.directory/${did}`; 31 + } else if (did.startsWith("did:web:")) { 32 + const host = did.slice("did:web:".length); 33 + docUrl = `https://${host}/.well-known/did.json`; 34 + } else { 35 + return null; 36 + } 37 + 38 + const res = await fetch(docUrl, { signal: AbortSignal.timeout(3000) }); 39 + if (!res.ok) return null; 40 + return res.json(); 41 + } 42 + 43 + function pdsFromDoc(doc) { 44 + return doc.service?.find((s) => s.id === "#atproto_pds")?.serviceEndpoint ?? null; 45 + } 46 + 47 + function handleFromDoc(doc) { 48 + const aka = doc.alsoKnownAs?.find((a) => a.startsWith("at://")); 49 + return aka ? aka.slice("at://".length) : null; 50 + } 51 + 52 + const STATIC_ROUTES = { 53 + "/": { title: "PDSls", description: "Browse the public data on atproto" }, 54 + "/jetstream": { 55 + title: "Jetstream", 56 + description: "A simplified event stream with support for collection and DID filtering.", 57 + }, 58 + "/firehose": { title: "Firehose", description: "The raw event stream from a relay or PDS." }, 59 + "/spacedust": { 60 + title: "Spacedust", 61 + description: "A stream of links showing interactions across the network.", 62 + }, 63 + "/labels": { title: "Labels", description: "Query labels applied to accounts and records." }, 64 + "/car": { 65 + title: "Archive tools", 66 + description: "Tools for working with CAR (Content Addressable aRchive) files.", 67 + }, 68 + "/car/explore": { 69 + title: "Explore archive", 70 + description: "Upload a CAR file to explore its contents.", 71 + }, 72 + "/car/unpack": { 73 + title: "Unpack archive", 74 + description: "Upload a CAR file to extract all records into a ZIP archive.", 75 + }, 76 + "/settings": { title: "Settings", description: "Browse the public data on atproto" }, 77 + }; 78 + 79 + async function resolveOgData(pathname) { 80 + if (pathname in STATIC_ROUTES) return STATIC_ROUTES[pathname]; 81 + 82 + let title = "PDSls"; 83 + let description = "Browse the public data on atproto"; 84 + 85 + const segments = pathname.slice(1).split("/").filter(Boolean); 86 + const isAtUrl = segments[0] === "at:"; 87 + 88 + if (isAtUrl) { 89 + // at://did[/collection[/rkey]] 90 + const [, did, collection, rkey] = segments; 91 + 92 + if (!did) { 93 + // bare /at: — use defaults 94 + } else if (!collection) { 95 + const doc = await resolveDidDoc(did).catch(() => null); 96 + const handle = doc ? handleFromDoc(doc) : null; 97 + const pdsUrl = doc ? pdsFromDoc(doc) : null; 98 + const pdsHost = pdsUrl ? pdsUrl.replace("https://", "").replace("http://", "") : null; 99 + 100 + title = handle ? `${handle} (${did})` : did; 101 + description = pdsHost ? `Hosted on ${pdsHost}` : `Repository for ${did}`; 102 + } else if (!rkey) { 103 + const doc = await resolveDidDoc(did).catch(() => null); 104 + const handle = doc ? handleFromDoc(doc) : null; 105 + title = `at://${handle ?? did}/${collection}`; 106 + description = `List of ${collection} records for ${handle ?? did}`; 107 + } else { 108 + description = `View the ${rkey} record in ${collection} from ${did}`; 109 + 110 + const doc = await resolveDidDoc(did).catch(() => null); 111 + const handle = doc ? handleFromDoc(doc) : null; 112 + title = `at://${handle ?? did}/${collection}/${rkey}`; 113 + } 114 + } else { 115 + // /pds 116 + const [pds] = segments; 117 + if (pds) { 118 + title = pds; 119 + description = `Browse the repositories at ${pds}`; 120 + } 121 + } 122 + 123 + return { title, description }; 124 + } 125 + 126 + class OgTagRewriter { 127 + constructor(ogData, url) { 128 + this.ogData = ogData; 129 + this.url = url; 130 + } 131 + 132 + element(element) { 133 + const property = element.getAttribute("property"); 134 + const name = element.getAttribute("name"); 135 + 136 + if ( 137 + property === "og:title" || 138 + property === "og:description" || 139 + property === "og:url" || 140 + property === "og:type" || 141 + property === "og:site_name" || 142 + property === "description" || 143 + name === "description" || 144 + name === "twitter:card" || 145 + name === "twitter:title" || 146 + name === "twitter:description" 147 + ) { 148 + element.remove(); 149 + } 150 + } 151 + } 152 + 153 + class HeadEndRewriter { 154 + constructor(ogData, url) { 155 + this.ogData = ogData; 156 + this.url = url; 157 + } 158 + 159 + element(element) { 160 + const t = esc(this.ogData.title); 161 + const d = esc(this.ogData.description); 162 + const u = esc(this.url); 163 + 164 + element.append( 165 + `<meta property="og:title" content="${t}" /> 166 + <meta property="og:type" content="website" /> 167 + <meta property="og:url" content="${u}" /> 168 + <meta property="og:description" content="${d}" /> 169 + <meta property="og:site_name" content="PDSls" /> 170 + <meta name="description" content="${d}" /> 171 + <meta name="twitter:card" content="summary" /> 172 + <meta name="twitter:title" content="${t}" /> 173 + <meta name="twitter:description" content="${d}" />`, 174 + { html: true }, 175 + ); 176 + } 177 + } 178 + 179 + export default { 180 + async fetch(request, env) { 181 + const ua = request.headers.get("user-agent") ?? ""; 182 + 183 + if (!isBot(ua)) { 184 + return env.ASSETS.fetch(request); 185 + } 186 + 187 + const url = new URL(request.url); 188 + 189 + let ogData; 190 + try { 191 + ogData = await resolveOgData(url.pathname); 192 + } catch { 193 + return env.ASSETS.fetch(request); 194 + } 195 + 196 + const response = await env.ASSETS.fetch(request); 197 + const contentType = response.headers.get("content-type") ?? ""; 198 + if (!contentType.includes("text/html")) { 199 + return response; 200 + } 201 + 202 + return new HTMLRewriter() 203 + .on("meta", new OgTagRewriter(ogData, request.url)) 204 + .on("head", new HeadEndRewriter(ogData, request.url)) 205 + .transform(response); 206 + }, 207 + };