@wrongstack/plugins 0.268.0 → 0.270.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +29 -4
- package/dist/web-search.js +29 -4
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1345,13 +1345,13 @@ async function duckduckgoSearch(query, numResults) {
|
|
|
1345
1345
|
if (!resp.ok) throw new Error(`DuckDuckGo search failed: ${resp.status}`);
|
|
1346
1346
|
const html = await resp.text();
|
|
1347
1347
|
const results = [];
|
|
1348
|
-
const resultRe = /<a
|
|
1348
|
+
const resultRe = /<a\b[^>]*class="[^"]*result__a[^"]*"[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>([\s\S]*?)(?=<a\b[^>]*class="[^"]*result__a[^"]*"|<\/body>|$)/gi;
|
|
1349
1349
|
let m;
|
|
1350
1350
|
while ((m = resultRe.exec(html)) !== null && results.length < numResults) {
|
|
1351
|
-
const url2 = m[1];
|
|
1351
|
+
const url2 = normalizeDuckDuckGoUrl(m[1]);
|
|
1352
1352
|
if (!url2) continue;
|
|
1353
|
-
const title = (m[2] ?? "")
|
|
1354
|
-
const snippet = (m[3] ?? "")
|
|
1353
|
+
const title = htmlToText(m[2] ?? "");
|
|
1354
|
+
const snippet = extractDuckDuckGoSnippet(m[3] ?? "");
|
|
1355
1355
|
results.push({
|
|
1356
1356
|
url: url2,
|
|
1357
1357
|
title,
|
|
@@ -1361,8 +1361,33 @@ async function duckduckgoSearch(query, numResults) {
|
|
|
1361
1361
|
cached: false
|
|
1362
1362
|
});
|
|
1363
1363
|
}
|
|
1364
|
+
if (results.length === 0 && /result__a|result__snippet|result__body|anomaly-modal|captcha/i.test(html)) {
|
|
1365
|
+
throw new Error("DuckDuckGo response format was not recognized or was blocked");
|
|
1366
|
+
}
|
|
1364
1367
|
return results;
|
|
1365
1368
|
}
|
|
1369
|
+
function normalizeDuckDuckGoUrl(rawUrl) {
|
|
1370
|
+
if (!rawUrl) return null;
|
|
1371
|
+
let url = htmlToText(rawUrl);
|
|
1372
|
+
if (url.startsWith("//duckduckgo.com/l/")) url = `https:${url}`;
|
|
1373
|
+
if (url.startsWith("/l/")) url = new URL(url, "https://duckduckgo.com").toString();
|
|
1374
|
+
try {
|
|
1375
|
+
const parsed = new URL(url);
|
|
1376
|
+
const redirected = parsed.searchParams.get("uddg");
|
|
1377
|
+
if (redirected) return redirected;
|
|
1378
|
+
return parsed.toString();
|
|
1379
|
+
} catch {
|
|
1380
|
+
return null;
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
function htmlToText(html) {
|
|
1384
|
+
return html.replace(/<[^>]+>/g, "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/\s+/g, " ").trim();
|
|
1385
|
+
}
|
|
1386
|
+
function extractDuckDuckGoSnippet(html) {
|
|
1387
|
+
const snippetRe = /<(?:a|div)\b[^>]*class="[^"]*(?:result__snippet|result__body)[^"]*"[^>]*>([\s\S]*?)<\/(?:a|div)>/i;
|
|
1388
|
+
const snippetMatch = snippetRe.exec(html);
|
|
1389
|
+
return htmlToText(snippetMatch?.[1] ?? "");
|
|
1390
|
+
}
|
|
1366
1391
|
function assertSafeIp(ip) {
|
|
1367
1392
|
if (isIPv4(ip) && isPrivateIPv4(ip)) {
|
|
1368
1393
|
throw new Error(`Blocked private/loopback address: ${ip}`);
|
package/dist/web-search.js
CHANGED
|
@@ -14,13 +14,13 @@ async function duckduckgoSearch(query, numResults) {
|
|
|
14
14
|
if (!resp.ok) throw new Error(`DuckDuckGo search failed: ${resp.status}`);
|
|
15
15
|
const html = await resp.text();
|
|
16
16
|
const results = [];
|
|
17
|
-
const resultRe = /<a
|
|
17
|
+
const resultRe = /<a\b[^>]*class="[^"]*result__a[^"]*"[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>([\s\S]*?)(?=<a\b[^>]*class="[^"]*result__a[^"]*"|<\/body>|$)/gi;
|
|
18
18
|
let m;
|
|
19
19
|
while ((m = resultRe.exec(html)) !== null && results.length < numResults) {
|
|
20
|
-
const url2 = m[1];
|
|
20
|
+
const url2 = normalizeDuckDuckGoUrl(m[1]);
|
|
21
21
|
if (!url2) continue;
|
|
22
|
-
const title = (m[2] ?? "")
|
|
23
|
-
const snippet = (m[3] ?? "")
|
|
22
|
+
const title = htmlToText(m[2] ?? "");
|
|
23
|
+
const snippet = extractDuckDuckGoSnippet(m[3] ?? "");
|
|
24
24
|
results.push({
|
|
25
25
|
url: url2,
|
|
26
26
|
title,
|
|
@@ -30,8 +30,33 @@ async function duckduckgoSearch(query, numResults) {
|
|
|
30
30
|
cached: false
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
|
+
if (results.length === 0 && /result__a|result__snippet|result__body|anomaly-modal|captcha/i.test(html)) {
|
|
34
|
+
throw new Error("DuckDuckGo response format was not recognized or was blocked");
|
|
35
|
+
}
|
|
33
36
|
return results;
|
|
34
37
|
}
|
|
38
|
+
function normalizeDuckDuckGoUrl(rawUrl) {
|
|
39
|
+
if (!rawUrl) return null;
|
|
40
|
+
let url = htmlToText(rawUrl);
|
|
41
|
+
if (url.startsWith("//duckduckgo.com/l/")) url = `https:${url}`;
|
|
42
|
+
if (url.startsWith("/l/")) url = new URL(url, "https://duckduckgo.com").toString();
|
|
43
|
+
try {
|
|
44
|
+
const parsed = new URL(url);
|
|
45
|
+
const redirected = parsed.searchParams.get("uddg");
|
|
46
|
+
if (redirected) return redirected;
|
|
47
|
+
return parsed.toString();
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function htmlToText(html) {
|
|
53
|
+
return html.replace(/<[^>]+>/g, "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/\s+/g, " ").trim();
|
|
54
|
+
}
|
|
55
|
+
function extractDuckDuckGoSnippet(html) {
|
|
56
|
+
const snippetRe = /<(?:a|div)\b[^>]*class="[^"]*(?:result__snippet|result__body)[^"]*"[^>]*>([\s\S]*?)<\/(?:a|div)>/i;
|
|
57
|
+
const snippetMatch = snippetRe.exec(html);
|
|
58
|
+
return htmlToText(snippetMatch?.[1] ?? "");
|
|
59
|
+
}
|
|
35
60
|
function assertSafeIp(ip) {
|
|
36
61
|
if (isIPv4(ip) && isPrivateIPv4(ip)) {
|
|
37
62
|
throw new Error(`Blocked private/loopback address: ${ip}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wrongstack/plugins",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.270.0",
|
|
4
4
|
"description": "Official WrongStack plugin collection — auto-doc, git-autocommit, shell-check, cost-tracker, file-watcher, web-search, json-path, cron, template-engine, semver-bump",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ECOSTACK TECHNOLOGY OÜ",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"vitest": "^4.1.8"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"@wrongstack/core": "0.
|
|
66
|
+
"@wrongstack/core": "0.270.0"
|
|
67
67
|
},
|
|
68
68
|
"scripts": {
|
|
69
69
|
"build": "tsup",
|