Spaces:
Running
Running
Web search: fallback chain DDG → Bing RSS → Google HTML (HF egress hay bị block bởi DDG/Google HTML)
Browse files
index.ts
CHANGED
|
@@ -1458,7 +1458,29 @@ const server = Bun.serve({
|
|
| 1458 |
}
|
| 1459 |
return res;
|
| 1460 |
};
|
| 1461 |
-
// 2)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1462 |
const ggl = async (): Promise<Array<any>> => {
|
| 1463 |
const r = await fetch("https://www.google.com/search?q=" + encodeURIComponent(q) + "&num=10&hl=vi", { headers: { "User-Agent": UA, "Accept-Language": "vi-VN,vi;q=0.9" }, signal: AbortSignal.timeout(12000) });
|
| 1464 |
const h = await r.text();
|
|
@@ -1507,11 +1529,14 @@ const server = Bun.serve({
|
|
| 1507 |
try {
|
| 1508 |
let results: Array<any> = [];
|
| 1509 |
try { results = await ddg(); } catch (_e) { results = []; }
|
|
|
|
|
|
|
|
|
|
| 1510 |
if (!results.length) {
|
| 1511 |
try { results = await ggl(); } catch (_e) { results = []; }
|
| 1512 |
}
|
| 1513 |
if (!results.length) return Response.json({ error: "Web search unreachable." }, { status: 502 });
|
| 1514 |
-
return Response.json({ results: dedupeFn(results), engine: "ddg+google" });
|
| 1515 |
} catch { return Response.json({ error: "Web search unreachable." }, { status: 502 }); }
|
| 1516 |
}},
|
| 1517 |
"/api/web/content": { GET: async (req: Request) => {
|
|
|
|
| 1458 |
}
|
| 1459 |
return res;
|
| 1460 |
};
|
| 1461 |
+
// 2) Bing RSS fallback (used when DDG returns nothing / is unreachable).
|
| 1462 |
+
// RSS endpoints are far more permissive than HTML scraping from HF egress.
|
| 1463 |
+
const bingRss = async (): Promise<Array<any>> => {
|
| 1464 |
+
const r = await fetch("https://www.bing.com/search?format=rss&q=" + encodeURIComponent(q), { headers: { "User-Agent": UA, "Accept-Language": "vi-VN,vi;q=0.9" }, signal: AbortSignal.timeout(12000) });
|
| 1465 |
+
const h = await r.text();
|
| 1466 |
+
const res: Array<any> = [];
|
| 1467 |
+
const items = [...h.matchAll(/<item>([\s\S]*?)<\/item>/gi)];
|
| 1468 |
+
for (const it of items) {
|
| 1469 |
+
const body = it[1];
|
| 1470 |
+
const tm = body.match(/<title>(?:<!\[CDATA\[)?([\s\S]*?)(?:\]\]>)?<\/title>/i);
|
| 1471 |
+
const lm2 = body.match(/<link>(?:<!\[CDATA\[)?([\s\S]*?)(?:\]\]>)?<\/link>/i);
|
| 1472 |
+
const dm = body.match(/<description>(?:<!\[CDATA\[)?([\s\S]*?)(?:\]\]>)?<\/description>/i);
|
| 1473 |
+
const title = tm ? dec(tm[1]).trim() : '';
|
| 1474 |
+
let href = lm2 ? lm2[1].trim() : '';
|
| 1475 |
+
const snippet = dm ? dec(dm[1]).replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim().slice(0, 160) : '';
|
| 1476 |
+
if (!title || !href || !/^https?:\/\//i.test(href)) continue;
|
| 1477 |
+
let source = '';
|
| 1478 |
+
try { source = new URL(href).hostname.replace(/^www\./, ''); } catch (_) {}
|
| 1479 |
+
res.push({ title, snippet, url: href, source });
|
| 1480 |
+
}
|
| 1481 |
+
return res;
|
| 1482 |
+
};
|
| 1483 |
+
// 3) Google HTML fallback (last resort — often blocked from HF egress)
|
| 1484 |
const ggl = async (): Promise<Array<any>> => {
|
| 1485 |
const r = await fetch("https://www.google.com/search?q=" + encodeURIComponent(q) + "&num=10&hl=vi", { headers: { "User-Agent": UA, "Accept-Language": "vi-VN,vi;q=0.9" }, signal: AbortSignal.timeout(12000) });
|
| 1486 |
const h = await r.text();
|
|
|
|
| 1529 |
try {
|
| 1530 |
let results: Array<any> = [];
|
| 1531 |
try { results = await ddg(); } catch (_e) { results = []; }
|
| 1532 |
+
if (!results.length) {
|
| 1533 |
+
try { results = await bingRss(); } catch (_e) { results = []; }
|
| 1534 |
+
}
|
| 1535 |
if (!results.length) {
|
| 1536 |
try { results = await ggl(); } catch (_e) { results = []; }
|
| 1537 |
}
|
| 1538 |
if (!results.length) return Response.json({ error: "Web search unreachable." }, { status: 502 });
|
| 1539 |
+
return Response.json({ results: dedupeFn(results), engine: "ddg+google+bing" });
|
| 1540 |
} catch { return Response.json({ error: "Web search unreachable." }, { status: 502 }); }
|
| 1541 |
}},
|
| 1542 |
"/api/web/content": { GET: async (req: Request) => {
|