SPB Git

spb/tendril Public

Tendril — web ingestion platform (scrape/crawl/map/search) on macOS Apple Silicon: WebKit fidelity, authenticated pages, deterministic testable extraction. A self-hosted Firecrawl alternative.

JavaScript 82.6% TypeScript 11.8% HTML 5.3%
1.4 KB · 50 lines typescript
Raw Blame History
1// author: simon-pierre boucher <contact@spboucher.ai>2import { httpFetch } from "@tendril/fetcher-http";3import { parseRobots, type Robots } from "./robots.js";45const ALLOW_ALL: Robots = { groups: [], sitemaps: [] };6const DISALLOW_ALL: Robots = { groups: [{ agents: ["*"], rules: [{ allow: false, pattern: "/" }] }], sitemaps: [] };78const OK_TTL_MS = 24 * 60 * 60 * 1000;9const DISALLOW_TTL_MS = 60 * 60 * 1000;1011interface Entry {12  robots: Robots;13  expiresAt: number;14}1516const cache = new Map<string, Entry>();1718/**19 * Fetch and cache robots.txt for an origin (§10.1). Cached 24h. A 5xx is treated20 * as disallow-all for 1h (per the spec's intent); a 4xx or network failure is21 * treated as allow-all. `origin` must be a scheme://host[:port] string.22 */23export async function getRobots(origin: string): Promise<Robots> {24  const now = Date.now();25  const cached = cache.get(origin);26  if (cached !== undefined && cached.expiresAt > now) return cached.robots;2728  const res = await httpFetch(`${origin}/robots.txt`, { timeout: 8_000 });29  let robots: Robots;30  let ttl = OK_TTL_MS;3132  if (!res.ok) {33    robots = ALLOW_ALL;34  } else if (res.value.status >= 500) {35    robots = DISALLOW_ALL;36    ttl = DISALLOW_TTL_MS;37  } else if (res.value.status >= 400) {38    robots = ALLOW_ALL;39  } else {40    robots = parseRobots(res.value.body);41  }4243  cache.set(origin, { robots, expiresAt: now + ttl });44  return robots;45}4647export function clearRobotsCache(): void {48  cache.clear();49}50