n8n-nodes-duckduckgo-search 32.2.0 → 32.3.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 +1 -1
- package/dist/nodes/DuckDuckGo/DuckDuckGo.node.js +5 -4
- package/dist/nodes/DuckDuckGo/__tests__/DuckDuckGo.test.js +36 -5
- package/dist/nodes/DuckDuckGo/adaptiveRateLimiter.js +6 -6
- package/dist/nodes/DuckDuckGo/constants.js +28 -1
- package/dist/package.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# DuckDuckGo Search Node for n8n
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/n8n-nodes-duckduckgo-search)
|
|
4
4
|
[](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.
|
|
@@ -243,12 +243,13 @@ class DuckDuckGo {
|
|
|
243
243
|
const performSearch = async () => {
|
|
244
244
|
var _a, _b, _c, _d;
|
|
245
245
|
const timeout = 45000;
|
|
246
|
+
const needleOptions = (0, constants_1.getNeedleOptions)();
|
|
246
247
|
if (operation === types_1.DuckDuckGoOperation.Search) {
|
|
247
248
|
const searchResult = await (0, reliability_1.executeWithRetry)(() => (0, reliability_1.withTimeout)((0, duck_duck_scrape_1.search)(query, {
|
|
248
249
|
safeSearch: getSafeSearchType(safeSearch),
|
|
249
250
|
locale: region || locale,
|
|
250
251
|
time: getSearchTimeType(timePeriod),
|
|
251
|
-
}), timeout, 'Web Search'), `Web Search: "${query}"`, reliability_1.DEFAULT_RETRY_CONFIG);
|
|
252
|
+
}, needleOptions), timeout, 'Web Search'), `Web Search: "${query}"`, reliability_1.DEFAULT_RETRY_CONFIG);
|
|
252
253
|
if ((_a = searchResult === null || searchResult === void 0 ? void 0 : searchResult.results) === null || _a === void 0 ? void 0 : _a.length) {
|
|
253
254
|
results = (0, processors_1.processWebSearchResults)(searchResult.results, itemIndex, searchResult).slice(0, maxResults);
|
|
254
255
|
}
|
|
@@ -269,7 +270,7 @@ class DuckDuckGo {
|
|
|
269
270
|
const searchResult = await (0, reliability_1.executeWithRetry)(() => (0, reliability_1.withTimeout)((0, duck_duck_scrape_1.searchImages)(query, {
|
|
270
271
|
safeSearch: getSafeSearchType(safeSearch),
|
|
271
272
|
locale: region || locale,
|
|
272
|
-
}), timeout, 'Image Search'), `Image Search: "${query}"`, reliability_1.DEFAULT_RETRY_CONFIG);
|
|
273
|
+
}, needleOptions), timeout, 'Image Search'), `Image Search: "${query}"`, reliability_1.DEFAULT_RETRY_CONFIG);
|
|
273
274
|
if ((_b = searchResult === null || searchResult === void 0 ? void 0 : searchResult.results) === null || _b === void 0 ? void 0 : _b.length) {
|
|
274
275
|
results = (0, processors_1.processImageSearchResults)(searchResult.results, itemIndex).slice(0, maxResults);
|
|
275
276
|
}
|
|
@@ -291,7 +292,7 @@ class DuckDuckGo {
|
|
|
291
292
|
safeSearch: getSafeSearchType(safeSearch),
|
|
292
293
|
locale: region || locale,
|
|
293
294
|
time: getSearchTimeType(timePeriod),
|
|
294
|
-
}), timeout, 'News Search'), `News Search: "${query}"`, reliability_1.DEFAULT_RETRY_CONFIG);
|
|
295
|
+
}, needleOptions), timeout, 'News Search'), `News Search: "${query}"`, reliability_1.DEFAULT_RETRY_CONFIG);
|
|
295
296
|
if ((_c = searchResult === null || searchResult === void 0 ? void 0 : searchResult.results) === null || _c === void 0 ? void 0 : _c.length) {
|
|
296
297
|
results = (0, processors_1.processNewsSearchResults)(searchResult.results, itemIndex).slice(0, maxResults);
|
|
297
298
|
}
|
|
@@ -312,7 +313,7 @@ class DuckDuckGo {
|
|
|
312
313
|
const searchResult = await (0, reliability_1.executeWithRetry)(() => (0, reliability_1.withTimeout)((0, duck_duck_scrape_1.searchVideos)(query, {
|
|
313
314
|
safeSearch: getSafeSearchType(safeSearch),
|
|
314
315
|
locale: region || locale,
|
|
315
|
-
}), timeout, 'Video Search'), `Video Search: "${query}"`, reliability_1.DEFAULT_RETRY_CONFIG);
|
|
316
|
+
}, needleOptions), timeout, 'Video Search'), `Video Search: "${query}"`, reliability_1.DEFAULT_RETRY_CONFIG);
|
|
316
317
|
if ((_d = searchResult === null || searchResult === void 0 ? void 0 : searchResult.results) === null || _d === void 0 ? void 0 : _d.length) {
|
|
317
318
|
results = (0, processors_1.processVideoSearchResults)(searchResult.results, itemIndex).slice(0, maxResults);
|
|
318
319
|
}
|
|
@@ -25,6 +25,15 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
26
|
const DuckDuckGo_node_1 = require("../DuckDuckGo.node");
|
|
27
27
|
const duckDuckScrape = __importStar(require("duck-duck-scrape"));
|
|
28
|
+
jest.mock('../adaptiveRateLimiter', () => ({
|
|
29
|
+
getAdaptiveRateLimiter: jest.fn().mockReturnValue({
|
|
30
|
+
wait: jest.fn().mockResolvedValue(undefined),
|
|
31
|
+
reportSuccess: jest.fn(),
|
|
32
|
+
reportRateLimitFailure: jest.fn(),
|
|
33
|
+
reportOtherFailure: jest.fn(),
|
|
34
|
+
}),
|
|
35
|
+
resetAdaptiveRateLimiter: jest.fn(),
|
|
36
|
+
}));
|
|
28
37
|
jest.mock('duck-duck-scrape', () => ({
|
|
29
38
|
search: jest.fn(),
|
|
30
39
|
searchNews: jest.fn(),
|
|
@@ -95,7 +104,9 @@ describe('DuckDuckGo Node', () => {
|
|
|
95
104
|
setupNodeParameters('search', 'test query');
|
|
96
105
|
duckDuckScrape.search.mockResolvedValue(mockResults);
|
|
97
106
|
const result = await duckDuckGoNode.execute.call(mockExecuteFunction);
|
|
98
|
-
expect(duckDuckScrape.search).toHaveBeenCalledWith('test query', expect.any(Object)
|
|
107
|
+
expect(duckDuckScrape.search).toHaveBeenCalledWith('test query', expect.any(Object), expect.objectContaining({
|
|
108
|
+
headers: expect.any(Object),
|
|
109
|
+
}));
|
|
99
110
|
expect(result).toHaveLength(1);
|
|
100
111
|
expect(result[0]).toHaveLength(2);
|
|
101
112
|
expect(result[0][0].json).toHaveProperty('title', 'Result 1');
|
|
@@ -131,7 +142,9 @@ describe('DuckDuckGo Node', () => {
|
|
|
131
142
|
setupNodeParameters('searchImages', 'cat');
|
|
132
143
|
duckDuckScrape.searchImages.mockResolvedValue(mockResults);
|
|
133
144
|
const result = await duckDuckGoNode.execute.call(mockExecuteFunction);
|
|
134
|
-
expect(duckDuckScrape.searchImages).toHaveBeenCalledWith('cat', expect.any(Object)
|
|
145
|
+
expect(duckDuckScrape.searchImages).toHaveBeenCalledWith('cat', expect.any(Object), expect.objectContaining({
|
|
146
|
+
headers: expect.any(Object),
|
|
147
|
+
}));
|
|
135
148
|
expect(result[0][0].json).toHaveProperty('sourceType', 'image');
|
|
136
149
|
expect(result[0][0].json).toHaveProperty('imageUrl', 'https://example.com/img1.jpg');
|
|
137
150
|
});
|
|
@@ -146,7 +159,9 @@ describe('DuckDuckGo Node', () => {
|
|
|
146
159
|
setupNodeParameters('searchNews', 'tech news');
|
|
147
160
|
duckDuckScrape.searchNews.mockResolvedValue(mockResults);
|
|
148
161
|
const result = await duckDuckGoNode.execute.call(mockExecuteFunction);
|
|
149
|
-
expect(duckDuckScrape.searchNews).toHaveBeenCalledWith('tech news', expect.any(Object)
|
|
162
|
+
expect(duckDuckScrape.searchNews).toHaveBeenCalledWith('tech news', expect.any(Object), expect.objectContaining({
|
|
163
|
+
headers: expect.any(Object),
|
|
164
|
+
}));
|
|
150
165
|
expect(result[0][0].json).toHaveProperty('sourceType', 'news');
|
|
151
166
|
});
|
|
152
167
|
});
|
|
@@ -160,7 +175,9 @@ describe('DuckDuckGo Node', () => {
|
|
|
160
175
|
setupNodeParameters('searchVideos', 'tutorial');
|
|
161
176
|
duckDuckScrape.searchVideos.mockResolvedValue(mockResults);
|
|
162
177
|
const result = await duckDuckGoNode.execute.call(mockExecuteFunction);
|
|
163
|
-
expect(duckDuckScrape.searchVideos).toHaveBeenCalledWith('tutorial', expect.any(Object)
|
|
178
|
+
expect(duckDuckScrape.searchVideos).toHaveBeenCalledWith('tutorial', expect.any(Object), expect.objectContaining({
|
|
179
|
+
headers: expect.any(Object),
|
|
180
|
+
}));
|
|
164
181
|
expect(result[0][0].json).toHaveProperty('sourceType', 'video');
|
|
165
182
|
});
|
|
166
183
|
});
|
|
@@ -171,7 +188,7 @@ describe('DuckDuckGo Node', () => {
|
|
|
171
188
|
await duckDuckGoNode.execute.call(mockExecuteFunction);
|
|
172
189
|
expect(duckDuckScrape.search).toHaveBeenCalledWith('test', expect.objectContaining({
|
|
173
190
|
locale: 'fr-fr',
|
|
174
|
-
}));
|
|
191
|
+
}), expect.any(Object));
|
|
175
192
|
});
|
|
176
193
|
it('should limit results to maxResults', async () => {
|
|
177
194
|
setupNodeParameters('search', 'test', { maxResults: 2 });
|
|
@@ -185,4 +202,18 @@ describe('DuckDuckGo Node', () => {
|
|
|
185
202
|
expect(result[0]).toHaveLength(2);
|
|
186
203
|
});
|
|
187
204
|
});
|
|
205
|
+
describe('Needle Options', () => {
|
|
206
|
+
it('should pass custom headers with User-Agent to search functions', async () => {
|
|
207
|
+
setupNodeParameters('search', 'test');
|
|
208
|
+
duckDuckScrape.search.mockResolvedValue({ results: [] });
|
|
209
|
+
await duckDuckGoNode.execute.call(mockExecuteFunction);
|
|
210
|
+
const callArgs = duckDuckScrape.search.mock.calls[0];
|
|
211
|
+
const needleOptions = callArgs[2];
|
|
212
|
+
expect(needleOptions).toHaveProperty('headers');
|
|
213
|
+
expect(needleOptions.headers).toHaveProperty('user-agent');
|
|
214
|
+
expect(needleOptions.headers).toHaveProperty('sec-ch-ua');
|
|
215
|
+
expect(needleOptions.headers).toHaveProperty('accept');
|
|
216
|
+
expect(needleOptions).toHaveProperty('response_timeout', 30000);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
188
219
|
});
|
|
@@ -3,14 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.resetOperationRateLimiter = exports.resetAdaptiveRateLimiter = exports.getAdaptiveRateLimiter = exports.AdaptiveRateLimiter = exports.OPERATION_RATE_LIMITS = void 0;
|
|
4
4
|
exports.OPERATION_RATE_LIMITS = {
|
|
5
5
|
search: {
|
|
6
|
-
initialDelay:
|
|
7
|
-
minDelay:
|
|
8
|
-
maxDelay:
|
|
6
|
+
initialDelay: 5000,
|
|
7
|
+
minDelay: 4000,
|
|
8
|
+
maxDelay: 15000,
|
|
9
9
|
},
|
|
10
10
|
searchImages: {
|
|
11
|
-
initialDelay:
|
|
12
|
-
minDelay:
|
|
13
|
-
maxDelay:
|
|
11
|
+
initialDelay: 5000,
|
|
12
|
+
minDelay: 4000,
|
|
13
|
+
maxDelay: 15000,
|
|
14
14
|
},
|
|
15
15
|
searchNews: {
|
|
16
16
|
initialDelay: 2000,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getRandomUserAgent = exports.USER_AGENTS = exports.NODE_INFO = exports.DEFAULT_PARAMETERS = exports.REGIONS = void 0;
|
|
3
|
+
exports.getNeedleOptions = 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' },
|
|
@@ -83,3 +83,30 @@ function getRandomUserAgent() {
|
|
|
83
83
|
return exports.USER_AGENTS[Math.floor(Math.random() * exports.USER_AGENTS.length)];
|
|
84
84
|
}
|
|
85
85
|
exports.getRandomUserAgent = getRandomUserAgent;
|
|
86
|
+
function getNeedleOptions() {
|
|
87
|
+
const userAgent = getRandomUserAgent();
|
|
88
|
+
const chromeVersionMatch = userAgent.match(/Chrome\/(\d+)/);
|
|
89
|
+
const chromeVersion = chromeVersionMatch ? chromeVersionMatch[1] : '121';
|
|
90
|
+
return {
|
|
91
|
+
headers: {
|
|
92
|
+
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
|
|
93
|
+
'accept-language': 'en-US,en;q=0.9',
|
|
94
|
+
'cache-control': 'no-cache',
|
|
95
|
+
'pragma': 'no-cache',
|
|
96
|
+
'sec-ch-ua': `"Not A(Brand";v="8", "Chromium";v="${chromeVersion}", "Google Chrome";v="${chromeVersion}"`,
|
|
97
|
+
'sec-ch-ua-mobile': '?0',
|
|
98
|
+
'sec-ch-ua-platform': '"Windows"',
|
|
99
|
+
'sec-fetch-dest': 'document',
|
|
100
|
+
'sec-fetch-mode': 'navigate',
|
|
101
|
+
'sec-fetch-site': 'none',
|
|
102
|
+
'sec-fetch-user': '?1',
|
|
103
|
+
'sec-gpc': '1',
|
|
104
|
+
'upgrade-insecure-requests': '1',
|
|
105
|
+
'user-agent': userAgent,
|
|
106
|
+
},
|
|
107
|
+
follow_max: 5,
|
|
108
|
+
response_timeout: 30000,
|
|
109
|
+
read_timeout: 30000,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
exports.getNeedleOptions = getNeedleOptions;
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-duckduckgo-search",
|
|
3
|
-
"version": "32.
|
|
3
|
+
"version": "32.3.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.
|
|
3
|
+
"version": "32.3.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",
|