import { search as browserSearch } from "@/lib/threads-browser/search"; import { cacheGet, cacheSet } from "../cache"; import { getSearchConfig } from "../config"; import { logSearchEvent } from "../logger"; import type { SearchProvider, SearchRequest, SearchResponse, SearchResult } from "../types"; export class CrawlerSearchProvider implements SearchProvider { constructor(private storageState: string | null) {} name() { return "crawler" as const; } enabled(): boolean { return getSearchConfig().crawler.enabled && !!this.storageState?.trim(); } async search(req: SearchRequest): Promise { if (!this.enabled() || !this.storageState) { return { provider: "crawler", results: [], status: "unavailable" }; } const cfg = getSearchConfig(); const cacheKey = `crawler:threads:${req.keyword}:${req.limit}`; const cached = cacheGet("crawler", cacheKey); if (cached) { return { provider: "crawler", results: cached, status: "success", skipReason: "cache_hit" }; } try { const posts = await browserSearch(this.storageState, req.keyword, req.limit); const results: SearchResult[] = posts.map((p) => ({ title: p.text.slice(0, 120), url: p.permalink ?? "", snippet: p.text, author: p.authorName ?? "", publishedAt: p.postedAt, source: "crawler" as const, threadId: p.externalId, })); cacheSet("crawler", cacheKey, results, cfg.crawler.cacheTtlMs); logSearchEvent({ kind: "provider", provider: "crawler", status: "success", keyword: req.keyword, count: results.length, }); return { provider: "crawler", results, status: "success" }; } catch { logSearchEvent({ kind: "provider", provider: "crawler", status: "unavailable", keyword: req.keyword, }); return { provider: "crawler", results: [], status: "unavailable" }; } } }