An API you can curl, or open in a browser, to receive Bluesky data as markdown!
11
fork

Configure Feed

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

Tighten rate limit to 10/min, add 8s API timeout

- Rate limit: 20 → 10 req/min per IP for more headroom against abuse
- 8s AbortController timeout on all Bluesky fetch calls so a hung
upstream releases the function slot before Vercel's 10s hard kill

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

jack 095801d1 9a6a4264

+12 -4
+11 -3
lib/bsky.ts
··· 11 11 12 12 // ─── Client ────────────────────────────────────────────────────────────────── 13 13 14 - const agent = new AtpAgent({ service: 'https://public.api.bsky.app' }) 14 + // Wrap fetch with an 8-second timeout so a slow/hung Bluesky API call 15 + // can't tie up a Vercel function slot until the hard 10s limit kills it. 16 + function fetchWithTimeout(url: RequestInfo | URL, init?: RequestInit): Promise<Response> { 17 + const controller = new AbortController() 18 + const timer = setTimeout(() => controller.abort(), 8_000) 19 + return fetch(url, { ...init, signal: controller.signal }).finally(() => clearTimeout(timer)) 20 + } 21 + 22 + const agent = new AtpAgent({ service: 'https://public.api.bsky.app', fetch: fetchWithTimeout }) 15 23 16 24 // Search requires the full api.bsky.app (public.api.bsky.app blocks it) 17 - const searchAgent = new AtpAgent({ service: 'https://api.bsky.app' }) 25 + const searchAgent = new AtpAgent({ service: 'https://api.bsky.app', fetch: fetchWithTimeout }) 18 26 19 27 /** Simple in-memory cache for handle → DID resolution within a process lifetime. */ 20 28 const didCache = new Map<string, string>() ··· 288 296 } 289 297 290 298 export async function getTrending(): Promise<TrendingData> { 291 - const res = await fetch( 299 + const res = await fetchWithTimeout( 292 300 'https://public.api.bsky.app/xrpc/app.bsky.unspecced.getTrendingTopics', 293 301 { next: { revalidate: 60 } } as RequestInit, 294 302 )
+1 -1
middleware.ts
··· 6 6 // sufficient to cap runaway single-IP abuse and protect Bluesky's API. 7 7 // --------------------------------------------------------------------------- 8 8 const WINDOW_MS = 60_000 // 1 minute 9 - const MAX_REQUESTS = 20 // per IP per window 9 + const MAX_REQUESTS = 10 // per IP per window 10 10 11 11 const ipWindows = new Map<string, number[]>() 12 12