mirror of
https://github.com/direct-img/direct-img.link.git
synced 2026-10-29 07:24:52 +00:00
Feat: Split searches between Brave and Firecrawl, and drop the Bing scraper
A third of queries (by query hash) search Firecrawl first and the rest Brave, so Firecrawl's free 500 searches a month and Brave's ~1,000 run out together. Each falls back to the other on an error or no results. Searches that find nothing serve bad.webp, both providers failing serves donate.webp, and AI images only fill ?i= past a search's working images. A Firecrawl query's ?i= past 10 serves an AI image without searching. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// AI images for ai.direct-img.link, and the fallback when a search finds nothing
|
||||
// AI images for ai.direct-img.link, and the fallback for an ?i= past a search's working images
|
||||
const SOURCES = { "auroraimg-6m": auroraImage };
|
||||
export const DEFAULT_AI_SOURCE = "auroraimg-6m";
|
||||
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
// { urls }, or { error } if Bing failed. Bing finds something for nearly any query (even gibberish), so a page without
|
||||
// results almost always means it's blocking us (e.g. with a CAPTCHA) or changed its page format.
|
||||
export async function bingImageSearchFallback(query) {
|
||||
try {
|
||||
// SafeSearch off, like Brave. The cookie is what actually sets it; adlt= alone is ignored.
|
||||
const res = await fetch(`https://www.bing.com/images/search?q=${encodeURIComponent(query)}&adlt=off`, {
|
||||
headers: {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36",
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
||||
"Cookie": "SRCHHPGUSR=ADLT=OFF"
|
||||
}
|
||||
});
|
||||
if (!res.ok) return { error: `HTTP ${res.status}` };
|
||||
const html = await res.text();
|
||||
|
||||
// Bing encodes the image payload inside HTML entity attributes and script tags
|
||||
const urls = [...html.matchAll(/"murl":"(https:[^&"]+)"/g), ...html.matchAll(/"murl":"(https:[^"]+)"/g)]
|
||||
.map(m => m[1].replace(/\\\//g, '/'));
|
||||
return urls.length ? { urls: [...new Set(urls)] } : { error: `No results in page (${html.length} bytes, ${res.url})` };
|
||||
} catch (err) {
|
||||
return { error: err.message };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Results per search. Firecrawl charges 2 credits per 10, so the free plan's 1,000 credits a month buy 500 searches
|
||||
export const FIRECRAWL_RESULTS = 10;
|
||||
|
||||
// { urls } ([] if none found), or { error } if Firecrawl refused or failed, e.g. 402 once the month's credits run out,
|
||||
// or 429 past the free plan's 10 searches a minute or 2 at once
|
||||
export async function firecrawlImageSearch(query, apiKey) {
|
||||
try {
|
||||
const res = await fetch("https://api.firecrawl.dev/v2/search", {
|
||||
method: "POST",
|
||||
headers: { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
||||
// SafeSearch off, like Brave. It's off by default too, but set so a default change can't filter results
|
||||
body: JSON.stringify({ query, sources: ["images"], limit: FIRECRAWL_RESULTS, safe: false }),
|
||||
signal: AbortSignal.timeout(10000)
|
||||
});
|
||||
if (!res.ok) return { error: `HTTP ${res.status}: ${(await res.text()).slice(0, 300)}` };
|
||||
const data = await res.json();
|
||||
if (!data.success) return { error: JSON.stringify(data).slice(0, 300) };
|
||||
return { urls: data.data?.images?.map(r => r.imageUrl).filter(url => !!url) || [] };
|
||||
} catch (err) {
|
||||
return { error: err.name === "TimeoutError" ? "Timeout" : err.message };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user