mirror of
https://github.com/direct-img/direct-img.link.git
synced 2026-10-16 17:57:53 +00:00
Feat: Fall back to Bing when Brave refuses, and serve donate.webp when Bing blocks too
Brave errors (e.g. its spending limit being hit) now fall back to Bing like empty results do, with the error in a Brave Unavailable alert. If Bing fails too (HTTP error or a page without results, which for Bing means it's blocking us), a new papercraft donate.webp is served instead of an AI image, and nothing is cached so the query works once either search is back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+16
-5
@@ -215,6 +215,11 @@ COMMIT TRANSACTION;`;
|
||||
// The query's cached results, or a fresh search. pos holds the indexes of the working images found so far and next
|
||||
// the first URL not tried yet, so a known ?i= downloads only its own image and a new one carries on from next.
|
||||
let { urls, pos, next } = list || { urls: await (free ? freeImageUrls(query, src, env) : webImageUrls(query, env, note)), pos: [], next: 0 };
|
||||
// Brave and Bing both failed: ask for donations instead of an AI image, and cache nothing since it's temporary
|
||||
if (!urls) {
|
||||
release();
|
||||
return asset("donate.webp");
|
||||
}
|
||||
// Starting at a known image skips the ones before it, so it can't add to pos
|
||||
const known = pos.length >= i;
|
||||
let at = known ? pos[i - 1] : next, found = known ? i - 1 : pos.length, imgResult = null, timedOut = false;
|
||||
@@ -279,12 +284,18 @@ function parseIndex(searchParams) {
|
||||
return /^\d+$/.test(i) && +i >= 1 && +i <= MAX_INDEX ? +i : null;
|
||||
}
|
||||
|
||||
// Brave results, or Bing's if Brave has none
|
||||
// Brave results, or Bing's if Brave has none or refuses (e.g. once its spending limit is hit). null if Bing fails too,
|
||||
// which means we're out of searches rather than that nothing exists.
|
||||
async function webImageUrls(query, env, note) {
|
||||
const urls = await braveImageSearch(query, env.BRAVE_API_KEY);
|
||||
if (urls?.length) return urls;
|
||||
note({ title: "Brave Search Empty", message: `No results for: ${query}. Trying Bing Fallback.`, tags: "warning,mag", priority: 3 });
|
||||
return (await bingImageSearchFallback(query)) || [];
|
||||
const brave = await braveImageSearch(query, env.BRAVE_API_KEY);
|
||||
if (brave.urls?.length) return brave.urls;
|
||||
note(brave.error
|
||||
? { title: "Brave Unavailable", message: `Brave failed for: ${query}. Trying Bing Fallback.\nBrave: ${brave.error}`, tags: "warning,mag", priority: 3 }
|
||||
: { title: "Brave Search Empty", message: `No results for: ${query}. Trying Bing Fallback.`, tags: "warning,mag", priority: 3 });
|
||||
const bing = await bingImageSearchFallback(query);
|
||||
if (bing.urls) return bing.urls;
|
||||
note({ title: "Bing Blocked, Served donate.webp", message: `Bing failed for: ${query}\nBing: ${bing.error}\nBrave: ${brave.error || "no results"}`, tags: "rotating_light,x", priority: 4 });
|
||||
return null;
|
||||
}
|
||||
|
||||
async function setupSurreal(env) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// { 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.
|
||||
@@ -8,24 +10,14 @@ export async function bingImageSearchFallback(query) {
|
||||
"Cookie": "SRCHHPGUSR=ADLT=OFF"
|
||||
}
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
if (!res.ok) return { error: `HTTP ${res.status}` };
|
||||
const html = await res.text();
|
||||
const urls = [];
|
||||
|
||||
|
||||
// Bing encodes the image payload inside HTML entity attributes and script tags
|
||||
const regex1 = /"murl":"(https:[^&"]+)"/g;
|
||||
const regex2 = /"murl":"(https:[^"]+)"/g;
|
||||
|
||||
let match;
|
||||
while ((match = regex1.exec(html)) !== null) {
|
||||
urls.push(match[1].replace(/\\\//g, '/'));
|
||||
}
|
||||
while ((match = regex2.exec(html)) !== null) {
|
||||
urls.push(match[1].replace(/\\\//g, '/'));
|
||||
}
|
||||
|
||||
return urls.length > 0 ? [...new Set(urls)] : null;
|
||||
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 null;
|
||||
return { error: err.message };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
// { urls } ([] if none found), or { error } if Brave refused or failed, e.g. once our rate or spending limit is hit
|
||||
export async function braveImageSearch(query, apiKey) {
|
||||
const res = await fetch(`https://api.search.brave.com/res/v1/images/search?q=${encodeURIComponent(query)}&count=50&safesearch=off`, {
|
||||
headers: { "Accept": "application/json", "X-Subscription-Token": apiKey },
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
const data = await res.json();
|
||||
return data.results?.map(r => r.properties?.url || r.thumbnail?.src).filter(url => !!url) || null;
|
||||
try {
|
||||
const res = await fetch(`https://api.search.brave.com/res/v1/images/search?q=${encodeURIComponent(query)}&count=50&safesearch=off`, {
|
||||
headers: { "Accept": "application/json", "X-Subscription-Token": apiKey },
|
||||
});
|
||||
if (!res.ok) return { error: `HTTP ${res.status}: ${(await res.text()).slice(0, 300)}` };
|
||||
const data = await res.json();
|
||||
return { urls: data.results?.map(r => r.properties?.url || r.thumbnail?.src).filter(url => !!url) || [] };
|
||||
} catch (err) {
|
||||
return { error: err.message };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user