@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.
@@ -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 };