need a status for my status for my .. nate.tngl.io
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 265 lines 7.8 kB view raw
1import { Resvg, initWasm } from "@resvg/resvg-wasm"; 2import resvgWasm from "@resvg/resvg-wasm/index_bg.wasm"; 3 4const ALLOWED_HOSTS = new Set([ 5 "api.plyr.fm", 6 "moderation.plyr.fm", 7 "plyr-transcoder.fly.dev", 8 "plyr.fm", 9 "docs.plyr.fm", 10 "leaflet-search-backend.fly.dev", 11 "leaflet-search-tap.fly.dev", 12 "pub-search.waow.tech", 13 "relay.waow.tech", 14 "zlay.waow.tech", 15 "relay-eval.waow.tech", 16 "coral.fly.dev", 17 "coral-8hh.pages.dev", 18 "find-bufo.com", 19 "bufo-bot.fly.dev", 20 "pollz-backend.fly.dev", 21 "pollz.waow.tech", 22 "pds.zzstoatzz.io", 23 "typeahead.waow.tech", 24 "typeahead-ingester.fly.dev", 25 "prefect-server.waow.tech", 26 "prefect-metrics.waow.tech", 27 "zig-bsky-feed.fly.dev", 28 "at-me.wisp.place", 29 "status.zzstoatzz.io", 30 "pub-search-by-zzstoatzz.fastmcp.app", 31 "plyrfm.fastmcp.app", 32 "pdsx-by-zzstoatzz.fastmcp.app", 33 "relay-metrics.waow.tech", 34 "zlay-metrics.waow.tech", 35 "phi.zzstoatzz.io", 36]); 37 38const CORS_HEADERS = { 39 "Access-Control-Allow-Origin": "*", 40 "Access-Control-Allow-Methods": "GET, POST, OPTIONS", 41 "Access-Control-Allow-Headers": "Content-Type", 42}; 43 44interface Check { 45 url: string; 46 method?: string; 47 headers?: Record<string, string>; 48 body?: string; 49} 50 51function isAllowed(url: string): boolean { 52 try { 53 return ALLOWED_HOSTS.has(new URL(url).hostname); 54 } catch { 55 return false; 56 } 57} 58 59async function checkUrl( 60 check: Check 61): Promise<{ url: string; status: number; ok: boolean; ms: number }> { 62 const start = Date.now(); 63 try { 64 const resp = await fetch(check.url, { 65 method: check.method || "GET", 66 headers: check.headers, 67 body: check.body, 68 redirect: "follow", 69 signal: AbortSignal.timeout(10_000), 70 }); 71 return { 72 url: check.url, 73 status: resp.status, 74 ok: resp.ok, 75 ms: Date.now() - start, 76 }; 77 } catch { 78 return { url: check.url, status: 0, ok: false, ms: Date.now() - start }; 79 } 80} 81 82const TOTAL_SERVICES = 28; 83 84function buildOgSvg(): string { 85 const W = 1200; 86 const H = 630; 87 const cx = W / 2; // tree center x 88 89 let svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${H}" viewBox="0 0 ${W} ${H}">`; 90 91 // background + gradient 92 svg += `<defs>`; 93 svg += `<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">`; 94 svg += `<stop offset="0%" stop-color="#3fb950" stop-opacity="0.05"/>`; 95 svg += `<stop offset="100%" stop-color="#58a6ff" stop-opacity="0.05"/>`; 96 svg += `</linearGradient>`; 97 svg += `<radialGradient id="glow" cx="50%" cy="40%" r="50%">`; 98 svg += `<stop offset="0%" stop-color="#3fb950" stop-opacity="0.08"/>`; 99 svg += `<stop offset="100%" stop-color="#3fb950" stop-opacity="0"/>`; 100 svg += `</radialGradient>`; 101 svg += `</defs>`; 102 svg += `<rect width="${W}" height="${H}" fill="#0d1117"/>`; 103 svg += `<rect width="${W}" height="${H}" fill="url(#bg)"/>`; 104 svg += `<rect width="${W}" height="${H}" fill="url(#glow)"/>`; 105 106 // tree made of service dots — 24 dots arranged as an evergreen 107 // rows from top: 1, 2, 3, 4, 5, 6, 2 (trunk), 1 (trunk) = 24 108 const dotR = 7; 109 const spacingX = 28; 110 const spacingY = 32; 111 const treeTop = 80; 112 113 const treeRows = [1, 2, 3, 4, 5, 6, 2, 1]; 114 // colors: canopy is green shades, trunk is muted 115 const rowColors = [ 116 "#2ea043", // tip — darker green 117 "#3fb950", 118 "#3fb950", 119 "#3fb950", 120 "#56d364", 121 "#56d364", // wider — lighter green 122 "#8b6c3e", // trunk 123 "#8b6c3e", // trunk base 124 ]; 125 126 let dotIndex = 0; 127 for (let row = 0; row < treeRows.length; row++) { 128 const count = treeRows[row]; 129 const y = treeTop + row * spacingY; 130 const startX = cx - ((count - 1) * spacingX) / 2; 131 132 for (let j = 0; j < count; j++) { 133 const x = startX + j * spacingX; 134 const color = rowColors[row]; 135 // slight glow on canopy dots 136 if (row < 6) { 137 svg += `<circle cx="${x}" cy="${y}" r="${dotR + 4}" fill="${color}" opacity="0.15"/>`; 138 } 139 svg += `<circle cx="${x}" cy="${y}" r="${dotR}" fill="${color}" opacity="0.9"/>`; 140 dotIndex++; 141 } 142 } 143 144 // title below tree 145 const textY = treeTop + treeRows.length * spacingY + 40; 146 svg += `<text x="${cx}" y="${textY}" text-anchor="middle" fill="#f0f6fc" font-family="JetBrains Mono" font-size="48" font-weight="bold">evergreen</text>`; 147 svg += `<text x="${cx}" y="${textY + 35}" text-anchor="middle" fill="#8b949e" font-family="JetBrains Mono" font-size="16">status page for services by @zzstoatzz.io · ${TOTAL_SERVICES} endpoints</text>`; 148 149 // footer 150 svg += `<text x="${cx}" y="${H - 30}" text-anchor="middle" fill="#30363d" font-family="JetBrains Mono" font-size="13">nate.tngl.io</text>`; 151 152 svg += `</svg>`; 153 return svg; 154} 155 156let wasmInitialized = false; 157let fontRegular: Uint8Array | null = null; 158let fontBold: Uint8Array | null = null; 159 160async function loadFonts(): Promise<Uint8Array[]> { 161 const urls = [ 162 "https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/ttf/JetBrainsMono-Regular.ttf", 163 "https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/ttf/JetBrainsMono-Bold.ttf", 164 ]; 165 if (!fontRegular || !fontBold) { 166 const [reg, bold] = await Promise.all(urls.map((u) => fetch(u))); 167 fontRegular = new Uint8Array(await reg.arrayBuffer()); 168 fontBold = new Uint8Array(await bold.arrayBuffer()); 169 } 170 return [fontRegular, fontBold]; 171} 172 173async function handleOg(): Promise<Response> { 174 if (!wasmInitialized) { 175 await initWasm(resvgWasm); 176 wasmInitialized = true; 177 } 178 179 const fonts = await loadFonts(); 180 const svg = buildOgSvg(); 181 const resvg = new Resvg(svg, { 182 font: { 183 fontBuffers: fonts, 184 loadSystemFonts: false, 185 defaultFontFamily: "JetBrains Mono", 186 }, 187 fitTo: { mode: "width", value: 1200 }, 188 }); 189 const png = resvg.render(); 190 const pngData = png.asPng(); 191 192 return new Response(pngData, { 193 headers: { 194 "Content-Type": "image/png", 195 "Cache-Control": "public, max-age=3600", 196 ...CORS_HEADERS, 197 }, 198 }); 199} 200 201export default { 202 async fetch(request: Request): Promise<Response> { 203 if (request.method === "OPTIONS") { 204 return new Response(null, { status: 204, headers: CORS_HEADERS }); 205 } 206 207 const pathname = new URL(request.url).pathname; 208 209 // OG image endpoint 210 if (pathname === "/og") { 211 return handleOg(); 212 } 213 214 // batch POST — accepts { checks: [{ url, method?, headers?, body? }] } 215 if (request.method === "POST") { 216 let body: { checks?: Check[] }; 217 try { 218 body = await request.json(); 219 } catch { 220 return Response.json( 221 { error: "invalid json" }, 222 { status: 400, headers: CORS_HEADERS } 223 ); 224 } 225 226 const checks = body.checks; 227 if (!Array.isArray(checks) || checks.length === 0) { 228 return Response.json( 229 { error: "checks must be a non-empty array" }, 230 { status: 400, headers: CORS_HEADERS } 231 ); 232 } 233 234 const blocked = checks.filter((c) => !isAllowed(c.url)); 235 if (blocked.length > 0) { 236 return Response.json( 237 { error: "blocked hosts", blocked: blocked.map((c) => c.url) }, 238 { status: 403, headers: CORS_HEADERS } 239 ); 240 } 241 242 const results = await Promise.all(checks.map(checkUrl)); 243 return Response.json(results, { headers: CORS_HEADERS }); 244 } 245 246 // single GET 247 const url = new URL(request.url).searchParams.get("url"); 248 if (!url) { 249 return Response.json( 250 { error: "missing ?url= parameter" }, 251 { status: 400, headers: CORS_HEADERS } 252 ); 253 } 254 255 if (!isAllowed(url)) { 256 return Response.json( 257 { error: "host not allowed" }, 258 { status: 403, headers: CORS_HEADERS } 259 ); 260 } 261 262 const result = await checkUrl({ url }); 263 return Response.json(result, { headers: CORS_HEADERS }); 264 }, 265};