vipcare 0.3.9 → 0.3.10
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/lib/fetchers/search.js +144 -25
- package/package.json +1 -1
package/lib/fetchers/search.js
CHANGED
|
@@ -1,57 +1,176 @@
|
|
|
1
1
|
import { execFileSync } from 'child_process';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import path from 'path';
|
|
2
5
|
import { checkTool } from '../config.js';
|
|
3
6
|
|
|
4
|
-
|
|
5
|
-
if (checkTool('ddgs'))
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
function findDdgs() {
|
|
8
|
+
if (checkTool('ddgs')) return 'ddgs';
|
|
9
|
+
|
|
10
|
+
const candidates = [
|
|
11
|
+
path.join(os.homedir(), 'Library', 'Python', '3.9', 'bin', 'ddgs'),
|
|
12
|
+
path.join(os.homedir(), 'Library', 'Python', '3.10', 'bin', 'ddgs'),
|
|
13
|
+
path.join(os.homedir(), 'Library', 'Python', '3.11', 'bin', 'ddgs'),
|
|
14
|
+
path.join(os.homedir(), 'Library', 'Python', '3.12', 'bin', 'ddgs'),
|
|
15
|
+
path.join(os.homedir(), 'Library', 'Python', '3.13', 'bin', 'ddgs'),
|
|
16
|
+
path.join(os.homedir(), '.local', 'bin', 'ddgs'),
|
|
17
|
+
'/opt/homebrew/bin/ddgs',
|
|
18
|
+
'/usr/local/bin/ddgs',
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
for (const p of candidates) {
|
|
22
|
+
if (fs.existsSync(p)) return p;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function searchViaDdgs(query, maxResults) {
|
|
29
|
+
const ddgsPath = findDdgs();
|
|
30
|
+
if (!ddgsPath) return null;
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const output = execFileSync(ddgsPath, ['text', query, '-m', String(maxResults), '-o', 'json'], {
|
|
34
|
+
encoding: 'utf-8',
|
|
35
|
+
timeout: 15000,
|
|
36
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const parsed = JSON.parse(output);
|
|
40
|
+
if (!Array.isArray(parsed)) return null;
|
|
41
|
+
return parsed.map(r => ({
|
|
42
|
+
title: String(r.title || ''),
|
|
43
|
+
url: String(r.href || r.url || ''),
|
|
44
|
+
body: String(r.body || ''),
|
|
45
|
+
}));
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function searchViaDdgApi(query) {
|
|
52
|
+
// DuckDuckGo Instant Answer API — always works, no bot detection
|
|
53
|
+
try {
|
|
54
|
+
const encoded = encodeURIComponent(query);
|
|
55
|
+
const output = execFileSync('curl', [
|
|
56
|
+
'-s', `https://api.duckduckgo.com/?q=${encoded}&format=json&no_html=1`,
|
|
57
|
+
], {
|
|
58
|
+
encoding: 'utf-8',
|
|
59
|
+
timeout: 10000,
|
|
60
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const data = JSON.parse(output);
|
|
64
|
+
const results = [];
|
|
65
|
+
|
|
66
|
+
// Main abstract
|
|
67
|
+
if (data.Abstract) {
|
|
68
|
+
results.push({
|
|
69
|
+
title: data.Heading || query,
|
|
70
|
+
url: data.AbstractURL || '',
|
|
71
|
+
body: data.Abstract,
|
|
11
72
|
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Related topics
|
|
76
|
+
if (data.RelatedTopics) {
|
|
77
|
+
for (const topic of data.RelatedTopics) {
|
|
78
|
+
if (topic.Text && topic.FirstURL) {
|
|
79
|
+
results.push({
|
|
80
|
+
title: topic.Text.substring(0, 80),
|
|
81
|
+
url: topic.FirstURL,
|
|
82
|
+
body: topic.Text,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
// Subtopics
|
|
86
|
+
if (topic.Topics) {
|
|
87
|
+
for (const sub of topic.Topics) {
|
|
88
|
+
if (sub.Text && sub.FirstURL) {
|
|
89
|
+
results.push({
|
|
90
|
+
title: sub.Text.substring(0, 80),
|
|
91
|
+
url: sub.FirstURL,
|
|
92
|
+
body: sub.Text,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
12
99
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
100
|
+
// Infobox
|
|
101
|
+
if (data.Infobox?.content) {
|
|
102
|
+
for (const item of data.Infobox.content) {
|
|
103
|
+
if (item.label && item.value) {
|
|
104
|
+
results.push({
|
|
105
|
+
title: `${item.label}: ${item.value}`,
|
|
106
|
+
url: '',
|
|
107
|
+
body: `${item.label}: ${item.value}`,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
22
111
|
}
|
|
112
|
+
|
|
113
|
+
return results.length > 0 ? results : null;
|
|
114
|
+
} catch {
|
|
115
|
+
return null;
|
|
23
116
|
}
|
|
117
|
+
}
|
|
24
118
|
|
|
25
|
-
|
|
119
|
+
function searchViaCurl(query, maxResults) {
|
|
120
|
+
// Fallback: DuckDuckGo HTML search
|
|
26
121
|
try {
|
|
27
122
|
const encoded = encodeURIComponent(query);
|
|
28
|
-
const output = execFileSync('curl', [
|
|
123
|
+
const output = execFileSync('curl', [
|
|
124
|
+
'-s', '-L',
|
|
125
|
+
`https://html.duckduckgo.com/html/?q=${encoded}`,
|
|
126
|
+
'-H', 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)',
|
|
127
|
+
], {
|
|
29
128
|
encoding: 'utf-8',
|
|
30
129
|
timeout: 10000,
|
|
31
130
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
32
131
|
});
|
|
33
132
|
|
|
34
133
|
const results = [];
|
|
35
|
-
const
|
|
36
|
-
const snippetRegex = /<
|
|
134
|
+
const resultRegex = /<a[^>]+class="result__a"[^>]*href="([^"]+)"[^>]*>([^<]*(?:<[^>]+>[^<]*)*)<\/a>/g;
|
|
135
|
+
const snippetRegex = /<a[^>]+class="result__snippet"[^>]*>([^<]*(?:<[^>]+>[^<]*)*)<\/a>/g;
|
|
37
136
|
|
|
38
137
|
let match;
|
|
39
|
-
while ((match =
|
|
40
|
-
|
|
138
|
+
while ((match = resultRegex.exec(output)) && results.length < maxResults) {
|
|
139
|
+
const title = match[2].replace(/<[^>]+>/g, '').trim();
|
|
140
|
+
let url = match[1];
|
|
141
|
+
const uddg = url.match(/uddg=([^&]+)/);
|
|
142
|
+
if (uddg) url = decodeURIComponent(uddg[1]);
|
|
143
|
+
results.push({ title, url, body: '' });
|
|
41
144
|
}
|
|
42
145
|
|
|
43
146
|
let i = 0;
|
|
44
147
|
while ((match = snippetRegex.exec(output)) && i < results.length) {
|
|
45
|
-
results[i].body = match[1].trim();
|
|
148
|
+
results[i].body = match[1].replace(/<[^>]+>/g, '').trim();
|
|
46
149
|
i++;
|
|
47
150
|
}
|
|
48
151
|
|
|
49
|
-
return results;
|
|
152
|
+
return results.length > 0 ? results : null;
|
|
50
153
|
} catch {
|
|
51
|
-
return
|
|
154
|
+
return null;
|
|
52
155
|
}
|
|
53
156
|
}
|
|
54
157
|
|
|
158
|
+
export function search(query, maxResults = 5) {
|
|
159
|
+
// Try ddgs CLI first (best results)
|
|
160
|
+
const ddgsResults = searchViaDdgs(query, maxResults);
|
|
161
|
+
if (ddgsResults?.length) return ddgsResults;
|
|
162
|
+
|
|
163
|
+
// Try DDG Instant Answer API (always works, but limited)
|
|
164
|
+
const apiResults = searchViaDdgApi(query);
|
|
165
|
+
if (apiResults?.length) return apiResults;
|
|
166
|
+
|
|
167
|
+
// Last resort: DDG HTML (may be blocked by CAPTCHA)
|
|
168
|
+
const curlResults = searchViaCurl(query, maxResults);
|
|
169
|
+
if (curlResults?.length) return curlResults;
|
|
170
|
+
|
|
171
|
+
return [];
|
|
172
|
+
}
|
|
173
|
+
|
|
55
174
|
export function searchPerson(name, company) {
|
|
56
175
|
const queries = [`"${name}"`];
|
|
57
176
|
if (company) queries.push(`"${name}" "${company}"`);
|