@wrongstack/plugins 0.268.0 → 0.269.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 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 class="result__a" href="([^"]+)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?<a class="result__snippet"[^>]*>([\s\S]*?)<\/a>/g;
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] ?? "").replace(/<[^>]+>/g, "").trim();
1354
- const snippet = (m[3] ?? "").replace(/<[^>]+>/g, "").trim();
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(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/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}`);
@@ -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 class="result__a" href="([^"]+)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?<a class="result__snippet"[^>]*>([\s\S]*?)<\/a>/g;
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] ?? "").replace(/<[^>]+>/g, "").trim();
23
- const snippet = (m[3] ?? "").replace(/<[^>]+>/g, "").trim();
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(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/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.268.0",
3
+ "version": "0.269.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.268.0"
66
+ "@wrongstack/core": "0.269.0"
67
67
  },
68
68
  "scripts": {
69
69
  "build": "tsup",