n8n-nodes-duckduckgo-search 32.1.0 → 32.2.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # DuckDuckGo Search Node for n8n
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/n8n-nodes-duckduckgo-search.svg?v=32.1.0)](https://www.npmjs.com/package/n8n-nodes-duckduckgo-search)
3
+ [![npm version](https://img.shields.io/npm/v/n8n-nodes-duckduckgo-search.svg?v=32.2.0)](https://www.npmjs.com/package/n8n-nodes-duckduckgo-search)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
6
  A professional-grade n8n community node for DuckDuckGo search with enterprise-level error handling, validation, and quality assurance. Search the web, find images, discover news, and explore videos with privacy-focused, reliable results.
@@ -9,6 +9,7 @@ const constants_1 = require("./constants");
9
9
  const errors_1 = require("./errors");
10
10
  const validation_1 = require("./validation");
11
11
  const reliability_1 = require("./reliability");
12
+ const adaptiveRateLimiter_1 = require("./adaptiveRateLimiter");
12
13
  const LOCALE_OPTIONS = [
13
14
  { name: 'English (US)', value: 'en-us' },
14
15
  { name: 'English (UK)', value: 'uk-en' },
@@ -207,10 +208,11 @@ class DuckDuckGo {
207
208
  async execute() {
208
209
  const items = this.getInputData();
209
210
  const returnData = [];
210
- const requestQueue = (0, reliability_1.getRequestQueue)(1000);
211
211
  for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
212
212
  try {
213
213
  const operation = this.getNodeParameter('operation', itemIndex);
214
+ const adaptiveRateLimiter = (0, adaptiveRateLimiter_1.getAdaptiveRateLimiter)(operation);
215
+ await adaptiveRateLimiter.wait();
214
216
  const rawQuery = this.getNodeParameter('query', itemIndex);
215
217
  const rawLocale = this.getNodeParameter('locale', itemIndex);
216
218
  const rawMaxResults = this.getNodeParameter('maxResults', itemIndex);
@@ -240,13 +242,13 @@ class DuckDuckGo {
240
242
  let results = [];
241
243
  const performSearch = async () => {
242
244
  var _a, _b, _c, _d;
243
- const timeout = 30000;
245
+ const timeout = 45000;
244
246
  if (operation === types_1.DuckDuckGoOperation.Search) {
245
- const searchResult = await requestQueue.enqueue(() => (0, reliability_1.executeWithRetry)(() => (0, reliability_1.withTimeout)((0, duck_duck_scrape_1.search)(query, {
247
+ const searchResult = await (0, reliability_1.executeWithRetry)(() => (0, reliability_1.withTimeout)((0, duck_duck_scrape_1.search)(query, {
246
248
  safeSearch: getSafeSearchType(safeSearch),
247
249
  locale: region || locale,
248
250
  time: getSearchTimeType(timePeriod),
249
- }), timeout, 'Web Search'), `Web Search: "${query}"`, reliability_1.DEFAULT_RETRY_CONFIG));
251
+ }), timeout, 'Web Search'), `Web Search: "${query}"`, reliability_1.DEFAULT_RETRY_CONFIG);
250
252
  if ((_a = searchResult === null || searchResult === void 0 ? void 0 : searchResult.results) === null || _a === void 0 ? void 0 : _a.length) {
251
253
  results = (0, processors_1.processWebSearchResults)(searchResult.results, itemIndex, searchResult).slice(0, maxResults);
252
254
  }
@@ -264,10 +266,10 @@ class DuckDuckGo {
264
266
  }
265
267
  }
266
268
  else if (operation === types_1.DuckDuckGoOperation.SearchImages) {
267
- const searchResult = await requestQueue.enqueue(() => (0, reliability_1.executeWithRetry)(() => (0, reliability_1.withTimeout)((0, duck_duck_scrape_1.searchImages)(query, {
269
+ const searchResult = await (0, reliability_1.executeWithRetry)(() => (0, reliability_1.withTimeout)((0, duck_duck_scrape_1.searchImages)(query, {
268
270
  safeSearch: getSafeSearchType(safeSearch),
269
271
  locale: region || locale,
270
- }), timeout, 'Image Search'), `Image Search: "${query}"`, reliability_1.DEFAULT_RETRY_CONFIG));
272
+ }), timeout, 'Image Search'), `Image Search: "${query}"`, reliability_1.DEFAULT_RETRY_CONFIG);
271
273
  if ((_b = searchResult === null || searchResult === void 0 ? void 0 : searchResult.results) === null || _b === void 0 ? void 0 : _b.length) {
272
274
  results = (0, processors_1.processImageSearchResults)(searchResult.results, itemIndex).slice(0, maxResults);
273
275
  }
@@ -285,11 +287,11 @@ class DuckDuckGo {
285
287
  }
286
288
  }
287
289
  else if (operation === types_1.DuckDuckGoOperation.SearchNews) {
288
- const searchResult = await requestQueue.enqueue(() => (0, reliability_1.executeWithRetry)(() => (0, reliability_1.withTimeout)((0, duck_duck_scrape_1.searchNews)(query, {
290
+ const searchResult = await (0, reliability_1.executeWithRetry)(() => (0, reliability_1.withTimeout)((0, duck_duck_scrape_1.searchNews)(query, {
289
291
  safeSearch: getSafeSearchType(safeSearch),
290
292
  locale: region || locale,
291
293
  time: getSearchTimeType(timePeriod),
292
- }), timeout, 'News Search'), `News Search: "${query}"`, reliability_1.DEFAULT_RETRY_CONFIG));
294
+ }), timeout, 'News Search'), `News Search: "${query}"`, reliability_1.DEFAULT_RETRY_CONFIG);
293
295
  if ((_c = searchResult === null || searchResult === void 0 ? void 0 : searchResult.results) === null || _c === void 0 ? void 0 : _c.length) {
294
296
  results = (0, processors_1.processNewsSearchResults)(searchResult.results, itemIndex).slice(0, maxResults);
295
297
  }
@@ -307,10 +309,10 @@ class DuckDuckGo {
307
309
  }
308
310
  }
309
311
  else if (operation === types_1.DuckDuckGoOperation.SearchVideos) {
310
- const searchResult = await requestQueue.enqueue(() => (0, reliability_1.executeWithRetry)(() => (0, reliability_1.withTimeout)((0, duck_duck_scrape_1.searchVideos)(query, {
312
+ const searchResult = await (0, reliability_1.executeWithRetry)(() => (0, reliability_1.withTimeout)((0, duck_duck_scrape_1.searchVideos)(query, {
311
313
  safeSearch: getSafeSearchType(safeSearch),
312
314
  locale: region || locale,
313
- }), timeout, 'Video Search'), `Video Search: "${query}"`, reliability_1.DEFAULT_RETRY_CONFIG));
315
+ }), timeout, 'Video Search'), `Video Search: "${query}"`, reliability_1.DEFAULT_RETRY_CONFIG);
314
316
  if ((_d = searchResult === null || searchResult === void 0 ? void 0 : searchResult.results) === null || _d === void 0 ? void 0 : _d.length) {
315
317
  results = (0, processors_1.processVideoSearchResults)(searchResult.results, itemIndex).slice(0, maxResults);
316
318
  }
@@ -333,12 +335,22 @@ class DuckDuckGo {
333
335
  return results;
334
336
  };
335
337
  const searchResults = await performSearch();
338
+ const rateLimiter = (0, adaptiveRateLimiter_1.getAdaptiveRateLimiter)(operation);
339
+ rateLimiter.reportSuccess();
336
340
  returnData.push(...searchResults);
337
341
  }
338
342
  catch (error) {
343
+ const operation = this.getNodeParameter('operation', itemIndex);
344
+ const rateLimiter = (0, adaptiveRateLimiter_1.getAdaptiveRateLimiter)(operation);
339
345
  const classifiedError = error instanceof errors_1.DuckDuckGoError
340
346
  ? error
341
347
  : (0, errors_1.classifyError)(error);
348
+ if (classifiedError.category === 'rate_limit') {
349
+ rateLimiter.reportRateLimitFailure();
350
+ }
351
+ else {
352
+ rateLimiter.reportOtherFailure();
353
+ }
342
354
  if (this.continueOnFail()) {
343
355
  returnData.push({
344
356
  json: {
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resetOperationRateLimiter = exports.resetAdaptiveRateLimiter = exports.getAdaptiveRateLimiter = exports.AdaptiveRateLimiter = exports.OPERATION_RATE_LIMITS = void 0;
4
+ exports.OPERATION_RATE_LIMITS = {
5
+ search: {
6
+ initialDelay: 3000,
7
+ minDelay: 2500,
8
+ maxDelay: 10000,
9
+ },
10
+ searchImages: {
11
+ initialDelay: 3000,
12
+ minDelay: 2500,
13
+ maxDelay: 10000,
14
+ },
15
+ searchNews: {
16
+ initialDelay: 2000,
17
+ minDelay: 1500,
18
+ maxDelay: 8000,
19
+ },
20
+ searchVideos: {
21
+ initialDelay: 2000,
22
+ minDelay: 1500,
23
+ maxDelay: 8000,
24
+ },
25
+ };
26
+ class AdaptiveRateLimiter {
27
+ constructor(operationType = 'search', initialDelay) {
28
+ this.increaseMultiplier = 1.5;
29
+ this.decreaseMultiplier = 0.9;
30
+ this.consecutiveSuccesses = 0;
31
+ this.lastRequestTime = 0;
32
+ this.operationType = operationType;
33
+ const config = exports.OPERATION_RATE_LIMITS[operationType] || exports.OPERATION_RATE_LIMITS.search;
34
+ this.minDelay = config.minDelay;
35
+ this.maxDelay = config.maxDelay;
36
+ this.currentDelay = initialDelay || config.initialDelay;
37
+ this.currentDelay = Math.max(this.minDelay, Math.min(this.currentDelay, this.maxDelay));
38
+ }
39
+ async wait() {
40
+ const now = Date.now();
41
+ const timeSinceLastRequest = now - this.lastRequestTime;
42
+ if (timeSinceLastRequest < this.currentDelay) {
43
+ const waitTime = this.currentDelay - timeSinceLastRequest;
44
+ const jitter = Math.random() * 500;
45
+ await this.sleep(waitTime + jitter);
46
+ }
47
+ else {
48
+ const jitter = Math.random() * 300;
49
+ await this.sleep(jitter);
50
+ }
51
+ this.lastRequestTime = Date.now();
52
+ }
53
+ reportSuccess() {
54
+ this.consecutiveSuccesses++;
55
+ if (this.consecutiveSuccesses >= 3) {
56
+ const newDelay = this.currentDelay * this.decreaseMultiplier;
57
+ this.currentDelay = Math.max(this.minDelay, newDelay);
58
+ this.consecutiveSuccesses = 0;
59
+ if (process.env.NODE_ENV === 'development') {
60
+ console.log(`[AdaptiveRateLimiter] Decreased delay to ${Math.round(this.currentDelay)}ms`);
61
+ }
62
+ }
63
+ }
64
+ reportRateLimitFailure() {
65
+ this.consecutiveSuccesses = 0;
66
+ const newDelay = this.currentDelay * this.increaseMultiplier;
67
+ this.currentDelay = Math.min(this.maxDelay, newDelay);
68
+ if (process.env.NODE_ENV === 'development') {
69
+ console.log(`[AdaptiveRateLimiter] Increased delay to ${Math.round(this.currentDelay)}ms after rate limit`);
70
+ }
71
+ }
72
+ reportOtherFailure() {
73
+ this.consecutiveSuccesses = 0;
74
+ }
75
+ getCurrentDelay() {
76
+ return this.currentDelay;
77
+ }
78
+ reset(initialDelay) {
79
+ const config = exports.OPERATION_RATE_LIMITS[this.operationType] || exports.OPERATION_RATE_LIMITS.search;
80
+ this.currentDelay = initialDelay || config.initialDelay;
81
+ this.currentDelay = Math.max(this.minDelay, Math.min(this.currentDelay, this.maxDelay));
82
+ this.consecutiveSuccesses = 0;
83
+ this.lastRequestTime = 0;
84
+ }
85
+ getOperationType() {
86
+ return this.operationType;
87
+ }
88
+ sleep(ms) {
89
+ return new Promise(resolve => setTimeout(resolve, ms));
90
+ }
91
+ }
92
+ exports.AdaptiveRateLimiter = AdaptiveRateLimiter;
93
+ const globalAdaptiveRateLimiters = new Map();
94
+ function getAdaptiveRateLimiter(operationType = 'search', initialDelay) {
95
+ if (!globalAdaptiveRateLimiters.has(operationType)) {
96
+ globalAdaptiveRateLimiters.set(operationType, new AdaptiveRateLimiter(operationType, initialDelay));
97
+ }
98
+ return globalAdaptiveRateLimiters.get(operationType);
99
+ }
100
+ exports.getAdaptiveRateLimiter = getAdaptiveRateLimiter;
101
+ function resetAdaptiveRateLimiter() {
102
+ globalAdaptiveRateLimiters.clear();
103
+ }
104
+ exports.resetAdaptiveRateLimiter = resetAdaptiveRateLimiter;
105
+ function resetOperationRateLimiter(operationType) {
106
+ globalAdaptiveRateLimiters.delete(operationType);
107
+ }
108
+ exports.resetOperationRateLimiter = resetOperationRateLimiter;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.BROWSER_USER_AGENT = exports.NODE_INFO = exports.DEFAULT_PARAMETERS = exports.REGIONS = void 0;
3
+ exports.getRandomUserAgent = exports.USER_AGENTS = exports.NODE_INFO = exports.DEFAULT_PARAMETERS = exports.REGIONS = void 0;
4
4
  exports.REGIONS = [
5
5
  { name: 'Argentina', value: 'ar-es' },
6
6
  { name: 'Australia', value: 'au-en' },
@@ -63,4 +63,23 @@ exports.NODE_INFO = {
63
63
  VERSION: 1,
64
64
  DESCRIPTION: 'Search using DuckDuckGo',
65
65
  };
66
- exports.BROWSER_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36';
66
+ exports.USER_AGENTS = [
67
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
68
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
69
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
70
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
71
+ 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
72
+ 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
73
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0',
74
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0',
75
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:122.0) Gecko/20100101 Firefox/122.0',
76
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0',
77
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15',
78
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.15',
79
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0',
80
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0',
81
+ ];
82
+ function getRandomUserAgent() {
83
+ return exports.USER_AGENTS[Math.floor(Math.random() * exports.USER_AGENTS.length)];
84
+ }
85
+ exports.getRandomUserAgent = getRandomUserAgent;
@@ -156,7 +156,9 @@ function classifyError(error) {
156
156
  lowerMessage.includes('502') ||
157
157
  lowerMessage.includes('503') ||
158
158
  lowerMessage.includes('504') ||
159
- lowerMessage.includes('server error')) {
159
+ lowerMessage.includes('server error') ||
160
+ lowerMessage.includes('temporarily unavailable') ||
161
+ lowerMessage.includes('internal server error')) {
160
162
  const statusMatch = message.match(/(\d{3})/);
161
163
  const statusCode = statusMatch ? parseInt(statusMatch[1], 10) : 500;
162
164
  return new ServerError(statusCode, error instanceof Error ? error : undefined);
@@ -7,10 +7,10 @@ const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
7
7
  exports.sleep = sleep;
8
8
  exports.DEFAULT_RETRY_CONFIG = {
9
9
  maxRetries: 3,
10
- initialDelay: 1000,
11
- maxDelay: 8000,
10
+ initialDelay: 2000,
11
+ maxDelay: 15000,
12
12
  backoffMultiplier: 2,
13
- jitterFactor: 0.3,
13
+ jitterFactor: 0.5,
14
14
  retryableCategories: new Set(['network', 'rate_limit', 'timeout']),
15
15
  };
16
16
  async function executeWithRetry(fn, context = 'operation', config = exports.DEFAULT_RETRY_CONFIG) {
@@ -62,7 +62,7 @@ async function withTimeout(promise, timeoutMs, operation) {
62
62
  }
63
63
  exports.withTimeout = withTimeout;
64
64
  class RequestQueue {
65
- constructor(minInterval = 1000, maxConcurrent = 1) {
65
+ constructor(minInterval = 3000, maxConcurrent = 1) {
66
66
  this.queue = [];
67
67
  this.processing = false;
68
68
  this.lastRequestTime = 0;
@@ -97,7 +97,12 @@ class RequestQueue {
97
97
  const timeSinceLastRequest = now - this.lastRequestTime;
98
98
  if (timeSinceLastRequest < this.minInterval) {
99
99
  const delay = this.minInterval - timeSinceLastRequest;
100
- await (0, exports.sleep)(delay);
100
+ const jitter = Math.random() * 1000;
101
+ await (0, exports.sleep)(delay + jitter);
102
+ }
103
+ else {
104
+ const smallJitter = Math.random() * 500;
105
+ await (0, exports.sleep)(smallJitter);
101
106
  }
102
107
  const fn = this.queue.shift();
103
108
  if (fn) {
@@ -126,7 +131,7 @@ class RequestQueue {
126
131
  }
127
132
  exports.RequestQueue = RequestQueue;
128
133
  let globalRequestQueue = null;
129
- function getRequestQueue(minInterval = 1000) {
134
+ function getRequestQueue(minInterval = 3000) {
130
135
  if (!globalRequestQueue) {
131
136
  globalRequestQueue = new RequestQueue(minInterval);
132
137
  }
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-duckduckgo-search",
3
- "version": "32.1.0",
3
+ "version": "32.2.0",
4
4
  "description": "Professional-grade n8n community node for DuckDuckGo search with comprehensive error handling, validation, and quality filtering.",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-duckduckgo-search",
3
- "version": "32.1.0",
3
+ "version": "32.2.0",
4
4
  "description": "Professional-grade n8n community node for DuckDuckGo search with comprehensive error handling, validation, and quality filtering.",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",