@oevortex/ddg_search 1.2.0 → 1.2.2
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/CHANGELOG.md +47 -26
- package/README.md +3 -3
- package/babel.config.js +11 -11
- package/bin/cli.js +6 -6
- package/package.json +2 -2
- package/src/tools/searchTool.js +40 -40
- package/src/utils/search.js +322 -322
- package/src/utils/search_iask.js +228 -228
- package/src/utils/search_monica.js +238 -238
- package/test.setup.js +72 -119
package/src/utils/search_iask.js
CHANGED
|
@@ -107,233 +107,233 @@ function formatHtml(htmlContent) {
|
|
|
107
107
|
return outputLines.join('');
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
/**
|
|
111
|
-
* Search using IAsk AI via WebSocket (Phoenix LiveView)
|
|
112
|
-
* @param {string} prompt - The search query or prompt
|
|
113
|
-
* @param {string} mode - Search mode: 'question', 'academic', 'forums', 'wiki', 'thinking'
|
|
114
|
-
* @param {string|null} detailLevel - Detail level: 'concise', 'detailed', 'comprehensive'
|
|
115
|
-
* @returns {Promise<string>} The search results
|
|
116
|
-
*/
|
|
117
|
-
async function searchIAsk(prompt, mode = 'thinking', detailLevel = null) {
|
|
118
|
-
// Input validation
|
|
119
|
-
if (!prompt || typeof prompt !== 'string') {
|
|
120
|
-
throw new Error('Invalid prompt: prompt must be a non-empty string');
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Validate mode
|
|
124
|
-
if (!VALID_MODES.includes(mode)) {
|
|
125
|
-
throw new Error(`Invalid mode: ${mode}. Valid modes are: ${VALID_MODES.join(', ')}`);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Validate detail level
|
|
129
|
-
if (detailLevel && !VALID_DETAIL_LEVELS.includes(detailLevel)) {
|
|
130
|
-
throw new Error(`Invalid detail level: ${detailLevel}. Valid levels are: ${VALID_DETAIL_LEVELS.join(', ')}`);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
console.log(`IAsk search starting: "${prompt}" (mode: ${mode}, detailLevel: ${detailLevel || 'default'})`);
|
|
134
|
-
|
|
135
|
-
// Clear old cache entries
|
|
136
|
-
clearOldCache();
|
|
137
|
-
|
|
138
|
-
const cacheKey = getCacheKey(prompt, mode, detailLevel);
|
|
139
|
-
const cachedResults = resultsCache.get(cacheKey);
|
|
140
|
-
|
|
141
|
-
if (cachedResults && Date.now() - cachedResults.timestamp < CACHE_DURATION) {
|
|
142
|
-
console.log(`Cache hit for IAsk query: "${prompt}"`);
|
|
143
|
-
return cachedResults.results;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Build URL parameters
|
|
147
|
-
const params = new URLSearchParams({ mode, q: prompt });
|
|
148
|
-
if (detailLevel) {
|
|
149
|
-
params.append('options[detail_level]', detailLevel);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Create a cookie jar for session management
|
|
153
|
-
const jar = new CookieJar();
|
|
154
|
-
const client = wrapper(axios.create({ jar }));
|
|
155
|
-
|
|
156
|
-
try {
|
|
157
|
-
// Get initial page and extract tokens
|
|
158
|
-
console.log('Fetching IAsk AI initial page...');
|
|
159
|
-
const response = await client.get(API_ENDPOINT, {
|
|
160
|
-
params: Object.fromEntries(params),
|
|
161
|
-
timeout: DEFAULT_TIMEOUT,
|
|
162
|
-
headers: {
|
|
163
|
-
'User-Agent': getRandomUserAgent()
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
const $ = cheerio.load(response.data);
|
|
168
|
-
|
|
169
|
-
const phxNode = $('[id^="phx-"]').first();
|
|
170
|
-
const csrfToken = $('[name="csrf-token"]').attr('content');
|
|
171
|
-
const phxId = phxNode.attr('id');
|
|
172
|
-
const phxSession = phxNode.attr('data-phx-session');
|
|
173
|
-
|
|
174
|
-
if (!phxId || !csrfToken) {
|
|
175
|
-
throw new Error('Failed to extract required tokens from IAsk AI page');
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Get the actual response URL (after any redirects)
|
|
179
|
-
const responseUrl = response.request.res?.responseUrl || response.config.url;
|
|
180
|
-
|
|
181
|
-
// Get cookies from the jar for WebSocket connection
|
|
182
|
-
const cookies = await jar.getCookies(API_ENDPOINT);
|
|
183
|
-
const cookieString = cookies.map(c => `${c.key}=${c.value}`).join('; ');
|
|
184
|
-
|
|
185
|
-
// Build WebSocket URL
|
|
186
|
-
const wsParams = new URLSearchParams({
|
|
187
|
-
'_csrf_token': csrfToken,
|
|
188
|
-
'vsn': '2.0.0'
|
|
189
|
-
});
|
|
190
|
-
const wsUrl = `wss://iask.ai/live/websocket?${wsParams.toString()}`;
|
|
191
|
-
|
|
192
|
-
return new Promise((resolve, reject) => {
|
|
193
|
-
const ws = new WebSocket(wsUrl, {
|
|
194
|
-
headers: {
|
|
195
|
-
'Cookie': cookieString,
|
|
196
|
-
'User-Agent': getRandomUserAgent(),
|
|
197
|
-
'Origin': 'https://iask.ai'
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
let buffer = '';
|
|
202
|
-
let timeoutId;
|
|
203
|
-
let connectionTimeoutId;
|
|
204
|
-
|
|
205
|
-
// Set connection timeout
|
|
206
|
-
connectionTimeoutId = setTimeout(() => {
|
|
207
|
-
ws.close();
|
|
208
|
-
reject(new Error('IAsk connection timeout: unable to establish WebSocket connection'));
|
|
209
|
-
}, 15000);
|
|
210
|
-
|
|
211
|
-
ws.on('open', () => {
|
|
212
|
-
clearTimeout(connectionTimeoutId);
|
|
213
|
-
console.log('IAsk WebSocket connection established');
|
|
214
|
-
|
|
215
|
-
// Send phx_join message
|
|
216
|
-
ws.send(JSON.stringify([
|
|
217
|
-
null,
|
|
218
|
-
null,
|
|
219
|
-
`lv:${phxId}`,
|
|
220
|
-
'phx_join',
|
|
221
|
-
{
|
|
222
|
-
params: { _csrf_token: csrfToken },
|
|
223
|
-
url: responseUrl,
|
|
224
|
-
session: phxSession
|
|
225
|
-
}
|
|
226
|
-
]));
|
|
227
|
-
|
|
228
|
-
// Set message timeout
|
|
229
|
-
timeoutId = setTimeout(() => {
|
|
230
|
-
ws.close();
|
|
231
|
-
if (buffer) {
|
|
232
|
-
resolve(buffer || 'No results found.');
|
|
233
|
-
} else {
|
|
234
|
-
reject(new Error('IAsk response timeout: no response received'));
|
|
235
|
-
}
|
|
236
|
-
}, DEFAULT_TIMEOUT);
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
ws.on('message', (data) => {
|
|
240
|
-
try {
|
|
241
|
-
const msg = JSON.parse(data.toString());
|
|
242
|
-
if (!msg) return;
|
|
243
|
-
|
|
244
|
-
const diff = msg[4];
|
|
245
|
-
if (!diff) return;
|
|
246
|
-
|
|
247
|
-
let chunk = null;
|
|
248
|
-
|
|
249
|
-
try {
|
|
250
|
-
// Try to get chunk from diff.e[0][1].data
|
|
251
|
-
if (diff.e) {
|
|
252
|
-
chunk = diff.e[0][1].data;
|
|
253
|
-
|
|
254
|
-
if (chunk) {
|
|
255
|
-
let formatted;
|
|
256
|
-
if (/<[^>]+>/.test(chunk)) {
|
|
257
|
-
formatted = formatHtml(chunk);
|
|
258
|
-
} else {
|
|
259
|
-
formatted = chunk.replace(/<br\/>/g, '\n');
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
buffer += formatted;
|
|
263
|
-
}
|
|
264
|
-
} else {
|
|
265
|
-
throw new Error('No diff.e');
|
|
266
|
-
}
|
|
267
|
-
} catch {
|
|
268
|
-
// Fallback to cacheFind
|
|
269
|
-
const cache = cacheFind(diff);
|
|
270
|
-
if (cache) {
|
|
271
|
-
let formatted;
|
|
272
|
-
if (/<[^>]+>/.test(cache)) {
|
|
273
|
-
formatted = formatHtml(cache);
|
|
274
|
-
} else {
|
|
275
|
-
formatted = cache;
|
|
276
|
-
}
|
|
277
|
-
buffer += formatted;
|
|
278
|
-
// Close after cache find
|
|
279
|
-
ws.close();
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
} catch (err) {
|
|
284
|
-
console.error('Error parsing IAsk message:', err.message);
|
|
285
|
-
}
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
ws.on('close', () => {
|
|
289
|
-
clearTimeout(timeoutId);
|
|
290
|
-
clearTimeout(connectionTimeoutId);
|
|
291
|
-
|
|
292
|
-
console.log(`IAsk search completed: ${buffer.length} characters received`);
|
|
293
|
-
|
|
294
|
-
// Cache the result
|
|
295
|
-
if (buffer) {
|
|
296
|
-
resultsCache.set(cacheKey, {
|
|
297
|
-
results: buffer,
|
|
298
|
-
timestamp: Date.now()
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
resolve(buffer || 'No results found.');
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
ws.on('error', (err) => {
|
|
306
|
-
clearTimeout(timeoutId);
|
|
307
|
-
clearTimeout(connectionTimeoutId);
|
|
308
|
-
console.error('IAsk WebSocket error:', err.message);
|
|
309
|
-
|
|
310
|
-
if (err.message.includes('timeout')) {
|
|
311
|
-
reject(new Error('IAsk WebSocket timeout: connection took too long'));
|
|
312
|
-
} else if (err.message.includes('connection refused')) {
|
|
313
|
-
reject(new Error('IAsk connection refused: service may be unavailable'));
|
|
314
|
-
} else {
|
|
315
|
-
reject(new Error(`IAsk WebSocket error: ${err.message}`));
|
|
316
|
-
}
|
|
317
|
-
});
|
|
318
|
-
});
|
|
319
|
-
} catch (error) {
|
|
320
|
-
console.error('Error in IAsk search:', error.message);
|
|
321
|
-
|
|
322
|
-
// Enhanced error handling
|
|
323
|
-
if (error.code === 'ENOTFOUND') {
|
|
324
|
-
throw new Error('IAsk network error: unable to resolve host');
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
if (error.code === 'ECONNREFUSED') {
|
|
328
|
-
throw new Error('IAsk network error: connection refused');
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
if (error.message.includes('timeout')) {
|
|
332
|
-
throw new Error(`IAsk timeout: ${error.message}`);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
throw new Error(`IAsk search failed for "${prompt}": ${error.message}`);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
110
|
+
/**
|
|
111
|
+
* Search using IAsk AI via WebSocket (Phoenix LiveView)
|
|
112
|
+
* @param {string} prompt - The search query or prompt
|
|
113
|
+
* @param {string} mode - Search mode: 'question', 'academic', 'forums', 'wiki', 'thinking'
|
|
114
|
+
* @param {string|null} detailLevel - Detail level: 'concise', 'detailed', 'comprehensive'
|
|
115
|
+
* @returns {Promise<string>} The search results
|
|
116
|
+
*/
|
|
117
|
+
async function searchIAsk(prompt, mode = 'thinking', detailLevel = null) {
|
|
118
|
+
// Input validation
|
|
119
|
+
if (!prompt || typeof prompt !== 'string') {
|
|
120
|
+
throw new Error('Invalid prompt: prompt must be a non-empty string');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Validate mode
|
|
124
|
+
if (!VALID_MODES.includes(mode)) {
|
|
125
|
+
throw new Error(`Invalid mode: ${mode}. Valid modes are: ${VALID_MODES.join(', ')}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Validate detail level
|
|
129
|
+
if (detailLevel && !VALID_DETAIL_LEVELS.includes(detailLevel)) {
|
|
130
|
+
throw new Error(`Invalid detail level: ${detailLevel}. Valid levels are: ${VALID_DETAIL_LEVELS.join(', ')}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
console.log(`IAsk search starting: "${prompt}" (mode: ${mode}, detailLevel: ${detailLevel || 'default'})`);
|
|
134
|
+
|
|
135
|
+
// Clear old cache entries
|
|
136
|
+
clearOldCache();
|
|
137
|
+
|
|
138
|
+
const cacheKey = getCacheKey(prompt, mode, detailLevel);
|
|
139
|
+
const cachedResults = resultsCache.get(cacheKey);
|
|
140
|
+
|
|
141
|
+
if (cachedResults && Date.now() - cachedResults.timestamp < CACHE_DURATION) {
|
|
142
|
+
console.log(`Cache hit for IAsk query: "${prompt}"`);
|
|
143
|
+
return cachedResults.results;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Build URL parameters
|
|
147
|
+
const params = new URLSearchParams({ mode, q: prompt });
|
|
148
|
+
if (detailLevel) {
|
|
149
|
+
params.append('options[detail_level]', detailLevel);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Create a cookie jar for session management
|
|
153
|
+
const jar = new CookieJar();
|
|
154
|
+
const client = wrapper(axios.create({ jar }));
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
// Get initial page and extract tokens
|
|
158
|
+
console.log('Fetching IAsk AI initial page...');
|
|
159
|
+
const response = await client.get(API_ENDPOINT, {
|
|
160
|
+
params: Object.fromEntries(params),
|
|
161
|
+
timeout: DEFAULT_TIMEOUT,
|
|
162
|
+
headers: {
|
|
163
|
+
'User-Agent': getRandomUserAgent()
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const $ = cheerio.load(response.data);
|
|
168
|
+
|
|
169
|
+
const phxNode = $('[id^="phx-"]').first();
|
|
170
|
+
const csrfToken = $('[name="csrf-token"]').attr('content');
|
|
171
|
+
const phxId = phxNode.attr('id');
|
|
172
|
+
const phxSession = phxNode.attr('data-phx-session');
|
|
173
|
+
|
|
174
|
+
if (!phxId || !csrfToken) {
|
|
175
|
+
throw new Error('Failed to extract required tokens from IAsk AI page');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Get the actual response URL (after any redirects)
|
|
179
|
+
const responseUrl = response.request.res?.responseUrl || response.config.url;
|
|
180
|
+
|
|
181
|
+
// Get cookies from the jar for WebSocket connection
|
|
182
|
+
const cookies = await jar.getCookies(API_ENDPOINT);
|
|
183
|
+
const cookieString = cookies.map(c => `${c.key}=${c.value}`).join('; ');
|
|
184
|
+
|
|
185
|
+
// Build WebSocket URL
|
|
186
|
+
const wsParams = new URLSearchParams({
|
|
187
|
+
'_csrf_token': csrfToken,
|
|
188
|
+
'vsn': '2.0.0'
|
|
189
|
+
});
|
|
190
|
+
const wsUrl = `wss://iask.ai/live/websocket?${wsParams.toString()}`;
|
|
191
|
+
|
|
192
|
+
return new Promise((resolve, reject) => {
|
|
193
|
+
const ws = new WebSocket(wsUrl, {
|
|
194
|
+
headers: {
|
|
195
|
+
'Cookie': cookieString,
|
|
196
|
+
'User-Agent': getRandomUserAgent(),
|
|
197
|
+
'Origin': 'https://iask.ai'
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
let buffer = '';
|
|
202
|
+
let timeoutId;
|
|
203
|
+
let connectionTimeoutId;
|
|
204
|
+
|
|
205
|
+
// Set connection timeout
|
|
206
|
+
connectionTimeoutId = setTimeout(() => {
|
|
207
|
+
ws.close();
|
|
208
|
+
reject(new Error('IAsk connection timeout: unable to establish WebSocket connection'));
|
|
209
|
+
}, 15000);
|
|
210
|
+
|
|
211
|
+
ws.on('open', () => {
|
|
212
|
+
clearTimeout(connectionTimeoutId);
|
|
213
|
+
console.log('IAsk WebSocket connection established');
|
|
214
|
+
|
|
215
|
+
// Send phx_join message
|
|
216
|
+
ws.send(JSON.stringify([
|
|
217
|
+
null,
|
|
218
|
+
null,
|
|
219
|
+
`lv:${phxId}`,
|
|
220
|
+
'phx_join',
|
|
221
|
+
{
|
|
222
|
+
params: { _csrf_token: csrfToken },
|
|
223
|
+
url: responseUrl,
|
|
224
|
+
session: phxSession
|
|
225
|
+
}
|
|
226
|
+
]));
|
|
227
|
+
|
|
228
|
+
// Set message timeout
|
|
229
|
+
timeoutId = setTimeout(() => {
|
|
230
|
+
ws.close();
|
|
231
|
+
if (buffer) {
|
|
232
|
+
resolve(buffer || 'No results found.');
|
|
233
|
+
} else {
|
|
234
|
+
reject(new Error('IAsk response timeout: no response received'));
|
|
235
|
+
}
|
|
236
|
+
}, DEFAULT_TIMEOUT);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
ws.on('message', (data) => {
|
|
240
|
+
try {
|
|
241
|
+
const msg = JSON.parse(data.toString());
|
|
242
|
+
if (!msg) return;
|
|
243
|
+
|
|
244
|
+
const diff = msg[4];
|
|
245
|
+
if (!diff) return;
|
|
246
|
+
|
|
247
|
+
let chunk = null;
|
|
248
|
+
|
|
249
|
+
try {
|
|
250
|
+
// Try to get chunk from diff.e[0][1].data
|
|
251
|
+
if (diff.e) {
|
|
252
|
+
chunk = diff.e[0][1].data;
|
|
253
|
+
|
|
254
|
+
if (chunk) {
|
|
255
|
+
let formatted;
|
|
256
|
+
if (/<[^>]+>/.test(chunk)) {
|
|
257
|
+
formatted = formatHtml(chunk);
|
|
258
|
+
} else {
|
|
259
|
+
formatted = chunk.replace(/<br\/>/g, '\n');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
buffer += formatted;
|
|
263
|
+
}
|
|
264
|
+
} else {
|
|
265
|
+
throw new Error('No diff.e');
|
|
266
|
+
}
|
|
267
|
+
} catch {
|
|
268
|
+
// Fallback to cacheFind
|
|
269
|
+
const cache = cacheFind(diff);
|
|
270
|
+
if (cache) {
|
|
271
|
+
let formatted;
|
|
272
|
+
if (/<[^>]+>/.test(cache)) {
|
|
273
|
+
formatted = formatHtml(cache);
|
|
274
|
+
} else {
|
|
275
|
+
formatted = cache;
|
|
276
|
+
}
|
|
277
|
+
buffer += formatted;
|
|
278
|
+
// Close after cache find
|
|
279
|
+
ws.close();
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
} catch (err) {
|
|
284
|
+
console.error('Error parsing IAsk message:', err.message);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
ws.on('close', () => {
|
|
289
|
+
clearTimeout(timeoutId);
|
|
290
|
+
clearTimeout(connectionTimeoutId);
|
|
291
|
+
|
|
292
|
+
console.log(`IAsk search completed: ${buffer.length} characters received`);
|
|
293
|
+
|
|
294
|
+
// Cache the result
|
|
295
|
+
if (buffer) {
|
|
296
|
+
resultsCache.set(cacheKey, {
|
|
297
|
+
results: buffer,
|
|
298
|
+
timestamp: Date.now()
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
resolve(buffer || 'No results found.');
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
ws.on('error', (err) => {
|
|
306
|
+
clearTimeout(timeoutId);
|
|
307
|
+
clearTimeout(connectionTimeoutId);
|
|
308
|
+
console.error('IAsk WebSocket error:', err.message);
|
|
309
|
+
|
|
310
|
+
if (err.message.includes('timeout')) {
|
|
311
|
+
reject(new Error('IAsk WebSocket timeout: connection took too long'));
|
|
312
|
+
} else if (err.message.includes('connection refused')) {
|
|
313
|
+
reject(new Error('IAsk connection refused: service may be unavailable'));
|
|
314
|
+
} else {
|
|
315
|
+
reject(new Error(`IAsk WebSocket error: ${err.message}`));
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
} catch (error) {
|
|
320
|
+
console.error('Error in IAsk search:', error.message);
|
|
321
|
+
|
|
322
|
+
// Enhanced error handling
|
|
323
|
+
if (error.code === 'ENOTFOUND') {
|
|
324
|
+
throw new Error('IAsk network error: unable to resolve host');
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (error.code === 'ECONNREFUSED') {
|
|
328
|
+
throw new Error('IAsk network error: connection refused');
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (error.message.includes('timeout')) {
|
|
332
|
+
throw new Error(`IAsk timeout: ${error.message}`);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
throw new Error(`IAsk search failed for "${prompt}": ${error.message}`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
338
|
|
|
339
339
|
export { searchIAsk, VALID_MODES, VALID_DETAIL_LEVELS };
|