search-web-api 1.0.13
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 +307 -0
- package/demo/index.ts +38 -0
- package/demo/openapi.ts +402 -0
- package/demo/routes/autocomplete.ts +124 -0
- package/demo/routes/search.ts +61 -0
- package/docs/AUTOCOMPLETE.md +296 -0
- package/docs/CATEGORY_SEARCH.md +265 -0
- package/package.json +32 -0
- package/src/autocomplete/autocomplete-ai-next-word-predictor.ts +47 -0
- package/src/autocomplete/autocomplete-search-engine-backends.ts +231 -0
- package/src/category-registry.ts +6 -0
- package/src/config/search-engine-constants.ts +216 -0
- package/src/constants.ts +6 -0
- package/src/engine-descriptions.ts +7 -0
- package/src/engine-status.ts +7 -0
- package/src/engine.ts +6 -0
- package/src/registry/search-engine-category-registry.ts +200 -0
- package/src/registry/search-engine-descriptions.ts +121 -0
- package/src/registry/search-engine-status-tracker.ts +240 -0
- package/src/result-container.ts +8 -0
- package/src/search/search-engines-registry-list.ts +205 -0
- package/src/search/search-query-executor.ts +168 -0
- package/src/search/search-result-container.ts +421 -0
- package/src/search-web-types.ts +7 -0
- package/src/search.ts +10 -0
- package/src/sources/academic/arxiv.ts +72 -0
- package/src/sources/academic/core.ts +65 -0
- package/src/sources/academic/crossref.ts +75 -0
- package/src/sources/academic/doaj.ts +63 -0
- package/src/sources/academic/google_scholar.ts +53 -0
- package/src/sources/academic/openalex.ts +75 -0
- package/src/sources/academic/pubmed.ts +96 -0
- package/src/sources/academic/semantic_scholar.ts +77 -0
- package/src/sources/academic/wikidata.ts +44 -0
- package/src/sources/general/baidu.ts +59 -0
- package/src/sources/general/bing.ts +30 -0
- package/src/sources/general/brave.ts +54 -0
- package/src/sources/general/duckduckgo.ts +49 -0
- package/src/sources/general/google.ts +68 -0
- package/src/sources/general/mojeek.ts +56 -0
- package/src/sources/general/qwant.ts +37 -0
- package/src/sources/general/startpage.ts +44 -0
- package/src/sources/general/yahoo.ts +41 -0
- package/src/sources/general/yandex.ts +56 -0
- package/src/sources/images/bing_images.ts +68 -0
- package/src/sources/images/deviantart.ts +71 -0
- package/src/sources/images/flickr.ts +132 -0
- package/src/sources/images/google_images.ts +101 -0
- package/src/sources/images/imgur.ts +58 -0
- package/src/sources/images/openclipart.ts +52 -0
- package/src/sources/images/pixabay.ts +56 -0
- package/src/sources/images/unsplash.ts +38 -0
- package/src/sources/images/wallhaven.ts +50 -0
- package/src/sources/it/crates.ts +41 -0
- package/src/sources/it/dockerhub.ts +47 -0
- package/src/sources/it/github.ts +37 -0
- package/src/sources/it/gitlab.ts +55 -0
- package/src/sources/it/npm.ts +36 -0
- package/src/sources/it/packagist.ts +43 -0
- package/src/sources/it/pypi.ts +30 -0
- package/src/sources/it/rubygems.ts +43 -0
- package/src/sources/it/stackoverflow.ts +39 -0
- package/src/sources/maps/apple_maps.ts +105 -0
- package/src/sources/maps/openstreetmap.ts +34 -0
- package/src/sources/maps/photon.ts +77 -0
- package/src/sources/news/bing_news.ts +95 -0
- package/src/sources/news/google_news.ts +80 -0
- package/src/sources/news/hackernews.ts +94 -0
- package/src/sources/news/yahoo_news.ts +77 -0
- package/src/sources/shopping/ebay.ts +96 -0
- package/src/sources/social/mastodon.ts +46 -0
- package/src/sources/social/medium.ts +52 -0
- package/src/sources/social/reddit.ts +48 -0
- package/src/sources/social/soundcloud.ts +64 -0
- package/src/sources/social/twitter.ts +56 -0
- package/src/sources/specialized/annas_archive.ts +97 -0
- package/src/sources/specialized/archive.ts +48 -0
- package/src/sources/specialized/genius.ts +43 -0
- package/src/sources/specialized/goodreads.ts +62 -0
- package/src/sources/specialized/imdb.ts +55 -0
- package/src/sources/specialized/openlibrary.ts +59 -0
- package/src/sources/specialized/wikipedia.ts +37 -0
- package/src/sources/specialized/wttr.ts +98 -0
- package/src/sources/torrents/1337x.ts +43 -0
- package/src/sources/torrents/eztv.ts +47 -0
- package/src/sources/torrents/kickass.ts +63 -0
- package/src/sources/torrents/nyaa.ts +53 -0
- package/src/sources/torrents/solidtorrents.ts +68 -0
- package/src/sources/torrents/thepiratebay.ts +57 -0
- package/src/sources/torrents/yts.ts +54 -0
- package/src/sources/videos/bing_videos.ts +91 -0
- package/src/sources/videos/dailymotion.ts +101 -0
- package/src/sources/videos/invidious.ts +86 -0
- package/src/sources/videos/peertube.ts +76 -0
- package/src/sources/videos/vimeo.ts +67 -0
- package/src/sources/videos/youtube.ts +78 -0
- package/src/suggest-next-words/autocomplete-ai.ts +38 -0
- package/src/suggest-next-words/autocomplete-search-engines.ts +384 -0
- package/src/suggest-next-words/misspelled-typos-8k.json +1 -0
- package/src/types/search-engine-interface.ts +27 -0
- package/src/types/search-result-types.ts +405 -0
- package/test/api.test.ts +128 -0
- package/test/autocomplete-ai.test.ts +20 -0
- package/test/autocomplete-engines.test.ts +131 -0
- package/test/engine-health-suite.test.ts +350 -0
- package/test/search.test.ts +69 -0
- package/test/sources-unit.test.ts +1152 -0
- package/test/sources.test.ts +182 -0
- package/test/test-utils.ts +81 -0
- package/tsconfig.json +16 -0
- package/vitest.config.ts +20 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provides search query autocomplete/suggestions from various search engines.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import grab from "grab-url";
|
|
6
|
+
import { parseHTML } from "linkedom";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Autocomplete function type
|
|
10
|
+
*/
|
|
11
|
+
type AutocompleteFunction = (
|
|
12
|
+
query: string,
|
|
13
|
+
locale?: string,
|
|
14
|
+
) => Promise<string[]>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get autocomplete suggestions with error handling
|
|
18
|
+
*/
|
|
19
|
+
async function getSuggestions(url: string, options: any = {}): Promise<any> {
|
|
20
|
+
try {
|
|
21
|
+
const response = await grab(url, {
|
|
22
|
+
timeout: 3,
|
|
23
|
+
responseType: options.responseType || "json",
|
|
24
|
+
...options,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
if (typeof response === "object" && "data" in response) {
|
|
28
|
+
return response.data;
|
|
29
|
+
}
|
|
30
|
+
return response;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error("Autocomplete error:", error);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Baidu autocomplete
|
|
39
|
+
*/
|
|
40
|
+
export async function baidu(
|
|
41
|
+
query: string,
|
|
42
|
+
_locale?: string,
|
|
43
|
+
): Promise<string[]> {
|
|
44
|
+
const params = new URLSearchParams({
|
|
45
|
+
ie: "utf-8",
|
|
46
|
+
json: "1",
|
|
47
|
+
prod: "pc",
|
|
48
|
+
wd: query,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const url = `https://www.baidu.com/sugrec?${params.toString()}`;
|
|
52
|
+
const data = await getSuggestions(url);
|
|
53
|
+
const results: string[] = [];
|
|
54
|
+
|
|
55
|
+
if (data && data.g) {
|
|
56
|
+
for (const item of data.g) {
|
|
57
|
+
results.push(item.q);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return results;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Brave autocomplete
|
|
66
|
+
*/
|
|
67
|
+
export async function brave(
|
|
68
|
+
query: string,
|
|
69
|
+
_locale?: string,
|
|
70
|
+
): Promise<string[]> {
|
|
71
|
+
const params = new URLSearchParams({ q: query });
|
|
72
|
+
const url = `https://search.brave.com/api/suggest?${params.toString()}`;
|
|
73
|
+
|
|
74
|
+
const data = await getSuggestions(url, {
|
|
75
|
+
headers: {
|
|
76
|
+
Cookie: "country=all",
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (data && Array.isArray(data) && data.length > 1) {
|
|
81
|
+
return data[1];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* DuckDuckGo autocomplete
|
|
89
|
+
*/
|
|
90
|
+
export async function duckduckgo(
|
|
91
|
+
query: string,
|
|
92
|
+
locale: string = "en-US",
|
|
93
|
+
): Promise<string[]> {
|
|
94
|
+
// Extract region code (e.g., 'us-en' from 'en-US')
|
|
95
|
+
const region = locale.toLowerCase().split("-").reverse().join("-");
|
|
96
|
+
|
|
97
|
+
const params = new URLSearchParams({
|
|
98
|
+
q: query,
|
|
99
|
+
kl: region,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const url = `https://duckduckgo.com/ac/?type=list&${params.toString()}`;
|
|
103
|
+
const data = await getSuggestions(url);
|
|
104
|
+
|
|
105
|
+
if (data && Array.isArray(data) && data.length > 1) {
|
|
106
|
+
return data[1];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Google autocomplete
|
|
114
|
+
*/
|
|
115
|
+
export async function google(
|
|
116
|
+
query: string,
|
|
117
|
+
locale: string = "en",
|
|
118
|
+
): Promise<string[]> {
|
|
119
|
+
// Map locale to Google subdomain
|
|
120
|
+
const subdomainMap: { [key: string]: string } = {
|
|
121
|
+
de: "google.de",
|
|
122
|
+
fr: "google.fr",
|
|
123
|
+
es: "google.es",
|
|
124
|
+
it: "google.it",
|
|
125
|
+
nl: "google.nl",
|
|
126
|
+
pt: "google.pt",
|
|
127
|
+
ru: "google.ru",
|
|
128
|
+
ja: "google.co.jp",
|
|
129
|
+
zh: "google.com.hk",
|
|
130
|
+
ko: "google.co.kr",
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const lang = locale.split("-")[0];
|
|
134
|
+
const subdomain = subdomainMap[lang] || "google.com";
|
|
135
|
+
|
|
136
|
+
const params = new URLSearchParams({
|
|
137
|
+
q: query,
|
|
138
|
+
client: "gws-wiz",
|
|
139
|
+
hl: lang,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const url = `https://${subdomain}/complete/search?${params.toString()}`;
|
|
143
|
+
const response = await getSuggestions(url, { responseType: "text" });
|
|
144
|
+
|
|
145
|
+
const results: string[] = [];
|
|
146
|
+
|
|
147
|
+
if (response) {
|
|
148
|
+
try {
|
|
149
|
+
// Extract JSON from response
|
|
150
|
+
const jsonStart = response.indexOf("[");
|
|
151
|
+
const jsonEnd = response.lastIndexOf("]") + 1;
|
|
152
|
+
|
|
153
|
+
if (jsonStart >= 0 && jsonEnd > jsonStart) {
|
|
154
|
+
const jsonText = response.substring(jsonStart, jsonEnd);
|
|
155
|
+
const data = JSON.parse(jsonText);
|
|
156
|
+
|
|
157
|
+
if (data[0]) {
|
|
158
|
+
for (const item of data[0]) {
|
|
159
|
+
// Parse HTML entities
|
|
160
|
+
const { document } = parseHTML(item[0]);
|
|
161
|
+
const text = document.body?.textContent?.trim();
|
|
162
|
+
if (text) {
|
|
163
|
+
results.push(text);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
} catch (e) {
|
|
169
|
+
// Ignore parse errors
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return results;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Qwant autocomplete
|
|
178
|
+
*/
|
|
179
|
+
export async function qwant(
|
|
180
|
+
query: string,
|
|
181
|
+
locale: string = "en_US",
|
|
182
|
+
): Promise<string[]> {
|
|
183
|
+
const params = new URLSearchParams({
|
|
184
|
+
q: query,
|
|
185
|
+
locale: locale.replace("-", "_"),
|
|
186
|
+
version: "2",
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const url = `https://api.qwant.com/v3/suggest?${params.toString()}`;
|
|
190
|
+
const data = await getSuggestions(url);
|
|
191
|
+
const results: string[] = [];
|
|
192
|
+
|
|
193
|
+
if (data && data.status === "success" && data.data && data.data.items) {
|
|
194
|
+
for (const item of data.data.items) {
|
|
195
|
+
results.push(item.value);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return results;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Startpage autocomplete
|
|
204
|
+
*/
|
|
205
|
+
export async function startpage(
|
|
206
|
+
query: string,
|
|
207
|
+
locale: string = "en",
|
|
208
|
+
): Promise<string[]> {
|
|
209
|
+
const langMap: { [key: string]: string } = {
|
|
210
|
+
da: "dansk",
|
|
211
|
+
de: "deutsch",
|
|
212
|
+
en: "english",
|
|
213
|
+
es: "espanol",
|
|
214
|
+
fr: "francais",
|
|
215
|
+
nb: "norsk",
|
|
216
|
+
nl: "nederlands",
|
|
217
|
+
pl: "polski",
|
|
218
|
+
pt: "portugues",
|
|
219
|
+
sv: "svenska",
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const baseLang = locale.split("-")[0];
|
|
223
|
+
const lui = langMap[baseLang] || "english";
|
|
224
|
+
|
|
225
|
+
const params = new URLSearchParams({
|
|
226
|
+
q: query,
|
|
227
|
+
format: "opensearch",
|
|
228
|
+
segment: "startpage.defaultffx",
|
|
229
|
+
lui: lui,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const url = `https://www.startpage.com/suggestions?${params.toString()}`;
|
|
233
|
+
const data = await getSuggestions(url, {
|
|
234
|
+
headers: {
|
|
235
|
+
"User-Agent":
|
|
236
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
if (
|
|
241
|
+
data &&
|
|
242
|
+
Array.isArray(data) &&
|
|
243
|
+
data.length >= 2 &&
|
|
244
|
+
Array.isArray(data[1])
|
|
245
|
+
) {
|
|
246
|
+
return data[1];
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return [];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Wikipedia autocomplete
|
|
254
|
+
*/
|
|
255
|
+
export async function wikipedia(
|
|
256
|
+
query: string,
|
|
257
|
+
locale: string = "en",
|
|
258
|
+
): Promise<string[]> {
|
|
259
|
+
const langMap: { [key: string]: string } = {
|
|
260
|
+
en: "en.wikipedia.org",
|
|
261
|
+
de: "de.wikipedia.org",
|
|
262
|
+
fr: "fr.wikipedia.org",
|
|
263
|
+
es: "es.wikipedia.org",
|
|
264
|
+
it: "it.wikipedia.org",
|
|
265
|
+
nl: "nl.wikipedia.org",
|
|
266
|
+
pt: "pt.wikipedia.org",
|
|
267
|
+
ru: "ru.wikipedia.org",
|
|
268
|
+
ja: "ja.wikipedia.org",
|
|
269
|
+
zh: "zh.wikipedia.org",
|
|
270
|
+
ar: "ar.wikipedia.org",
|
|
271
|
+
ko: "ko.wikipedia.org",
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const lang = locale.split("-")[0];
|
|
275
|
+
const netloc = langMap[lang] || "en.wikipedia.org";
|
|
276
|
+
|
|
277
|
+
const params = new URLSearchParams({
|
|
278
|
+
action: "opensearch",
|
|
279
|
+
format: "json",
|
|
280
|
+
formatversion: "2",
|
|
281
|
+
search: query,
|
|
282
|
+
namespace: "0",
|
|
283
|
+
limit: "10",
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
const url = `https://${netloc}/w/api.php?${params.toString()}`;
|
|
287
|
+
const data = await getSuggestions(url);
|
|
288
|
+
|
|
289
|
+
if (data && Array.isArray(data) && data.length > 1) {
|
|
290
|
+
return data[1];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return [];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Yandex autocomplete
|
|
298
|
+
*/
|
|
299
|
+
export async function yandex(
|
|
300
|
+
query: string,
|
|
301
|
+
_locale?: string,
|
|
302
|
+
): Promise<string[]> {
|
|
303
|
+
const params = new URLSearchParams({ part: query });
|
|
304
|
+
const url = `https://suggest.yandex.com/suggest-ff.cgi?${params.toString()}`;
|
|
305
|
+
|
|
306
|
+
const data = await getSuggestions(url);
|
|
307
|
+
|
|
308
|
+
if (data && Array.isArray(data) && data.length > 1) {
|
|
309
|
+
return data[1];
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return [];
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Available autocomplete backends
|
|
317
|
+
*/
|
|
318
|
+
export const backends: { [key: string]: AutocompleteFunction } = {
|
|
319
|
+
baidu,
|
|
320
|
+
brave,
|
|
321
|
+
duckduckgo,
|
|
322
|
+
google,
|
|
323
|
+
qwant,
|
|
324
|
+
startpage,
|
|
325
|
+
wikipedia,
|
|
326
|
+
yandex,
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Get autocomplete suggestions from a specific backend
|
|
331
|
+
*
|
|
332
|
+
* @param backendName - Name of the autocomplete backend
|
|
333
|
+
* @param query - Search query
|
|
334
|
+
* @param locale - Locale/language code (e.g., 'en-US', 'de-DE')
|
|
335
|
+
* @returns Array of suggestion strings
|
|
336
|
+
*/
|
|
337
|
+
export async function searchAutocomplete(
|
|
338
|
+
backendName: string,
|
|
339
|
+
query: string,
|
|
340
|
+
locale: string = "en-US",
|
|
341
|
+
): Promise<string[]> {
|
|
342
|
+
const backend = backends[backendName];
|
|
343
|
+
|
|
344
|
+
if (!backend) {
|
|
345
|
+
console.warn(`Autocomplete backend '${backendName}' not found`);
|
|
346
|
+
return [];
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
return await backend(query, locale);
|
|
351
|
+
} catch (error) {
|
|
352
|
+
console.error(`Autocomplete error for ${backendName}:`, error);
|
|
353
|
+
return [];
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Get autocomplete suggestions from multiple backends and merge them
|
|
359
|
+
*
|
|
360
|
+
* @param backendNames - Array of backend names to query
|
|
361
|
+
* @param query - Search query
|
|
362
|
+
* @param locale - Locale/language code
|
|
363
|
+
* @returns Merged and deduplicated array of suggestions
|
|
364
|
+
*/
|
|
365
|
+
export async function searchAutocompleteMulti(
|
|
366
|
+
backendNames: string[],
|
|
367
|
+
query: string,
|
|
368
|
+
locale: string = "en-US",
|
|
369
|
+
): Promise<string[]> {
|
|
370
|
+
const promises = backendNames.map((name) =>
|
|
371
|
+
searchAutocomplete(name, query, locale),
|
|
372
|
+
);
|
|
373
|
+
const results = await Promise.all(promises);
|
|
374
|
+
|
|
375
|
+
// Merge and deduplicate
|
|
376
|
+
const merged = new Set<string>();
|
|
377
|
+
for (const result of results) {
|
|
378
|
+
for (const suggestion of result) {
|
|
379
|
+
merged.add(suggestion);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return Array.from(merged);
|
|
384
|
+
}
|