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.
Files changed (111) hide show
  1. package/README.md +307 -0
  2. package/demo/index.ts +38 -0
  3. package/demo/openapi.ts +402 -0
  4. package/demo/routes/autocomplete.ts +124 -0
  5. package/demo/routes/search.ts +61 -0
  6. package/docs/AUTOCOMPLETE.md +296 -0
  7. package/docs/CATEGORY_SEARCH.md +265 -0
  8. package/package.json +32 -0
  9. package/src/autocomplete/autocomplete-ai-next-word-predictor.ts +47 -0
  10. package/src/autocomplete/autocomplete-search-engine-backends.ts +231 -0
  11. package/src/category-registry.ts +6 -0
  12. package/src/config/search-engine-constants.ts +216 -0
  13. package/src/constants.ts +6 -0
  14. package/src/engine-descriptions.ts +7 -0
  15. package/src/engine-status.ts +7 -0
  16. package/src/engine.ts +6 -0
  17. package/src/registry/search-engine-category-registry.ts +200 -0
  18. package/src/registry/search-engine-descriptions.ts +121 -0
  19. package/src/registry/search-engine-status-tracker.ts +240 -0
  20. package/src/result-container.ts +8 -0
  21. package/src/search/search-engines-registry-list.ts +205 -0
  22. package/src/search/search-query-executor.ts +168 -0
  23. package/src/search/search-result-container.ts +421 -0
  24. package/src/search-web-types.ts +7 -0
  25. package/src/search.ts +10 -0
  26. package/src/sources/academic/arxiv.ts +72 -0
  27. package/src/sources/academic/core.ts +65 -0
  28. package/src/sources/academic/crossref.ts +75 -0
  29. package/src/sources/academic/doaj.ts +63 -0
  30. package/src/sources/academic/google_scholar.ts +53 -0
  31. package/src/sources/academic/openalex.ts +75 -0
  32. package/src/sources/academic/pubmed.ts +96 -0
  33. package/src/sources/academic/semantic_scholar.ts +77 -0
  34. package/src/sources/academic/wikidata.ts +44 -0
  35. package/src/sources/general/baidu.ts +59 -0
  36. package/src/sources/general/bing.ts +30 -0
  37. package/src/sources/general/brave.ts +54 -0
  38. package/src/sources/general/duckduckgo.ts +49 -0
  39. package/src/sources/general/google.ts +68 -0
  40. package/src/sources/general/mojeek.ts +56 -0
  41. package/src/sources/general/qwant.ts +37 -0
  42. package/src/sources/general/startpage.ts +44 -0
  43. package/src/sources/general/yahoo.ts +41 -0
  44. package/src/sources/general/yandex.ts +56 -0
  45. package/src/sources/images/bing_images.ts +68 -0
  46. package/src/sources/images/deviantart.ts +71 -0
  47. package/src/sources/images/flickr.ts +132 -0
  48. package/src/sources/images/google_images.ts +101 -0
  49. package/src/sources/images/imgur.ts +58 -0
  50. package/src/sources/images/openclipart.ts +52 -0
  51. package/src/sources/images/pixabay.ts +56 -0
  52. package/src/sources/images/unsplash.ts +38 -0
  53. package/src/sources/images/wallhaven.ts +50 -0
  54. package/src/sources/it/crates.ts +41 -0
  55. package/src/sources/it/dockerhub.ts +47 -0
  56. package/src/sources/it/github.ts +37 -0
  57. package/src/sources/it/gitlab.ts +55 -0
  58. package/src/sources/it/npm.ts +36 -0
  59. package/src/sources/it/packagist.ts +43 -0
  60. package/src/sources/it/pypi.ts +30 -0
  61. package/src/sources/it/rubygems.ts +43 -0
  62. package/src/sources/it/stackoverflow.ts +39 -0
  63. package/src/sources/maps/apple_maps.ts +105 -0
  64. package/src/sources/maps/openstreetmap.ts +34 -0
  65. package/src/sources/maps/photon.ts +77 -0
  66. package/src/sources/news/bing_news.ts +95 -0
  67. package/src/sources/news/google_news.ts +80 -0
  68. package/src/sources/news/hackernews.ts +94 -0
  69. package/src/sources/news/yahoo_news.ts +77 -0
  70. package/src/sources/shopping/ebay.ts +96 -0
  71. package/src/sources/social/mastodon.ts +46 -0
  72. package/src/sources/social/medium.ts +52 -0
  73. package/src/sources/social/reddit.ts +48 -0
  74. package/src/sources/social/soundcloud.ts +64 -0
  75. package/src/sources/social/twitter.ts +56 -0
  76. package/src/sources/specialized/annas_archive.ts +97 -0
  77. package/src/sources/specialized/archive.ts +48 -0
  78. package/src/sources/specialized/genius.ts +43 -0
  79. package/src/sources/specialized/goodreads.ts +62 -0
  80. package/src/sources/specialized/imdb.ts +55 -0
  81. package/src/sources/specialized/openlibrary.ts +59 -0
  82. package/src/sources/specialized/wikipedia.ts +37 -0
  83. package/src/sources/specialized/wttr.ts +98 -0
  84. package/src/sources/torrents/1337x.ts +43 -0
  85. package/src/sources/torrents/eztv.ts +47 -0
  86. package/src/sources/torrents/kickass.ts +63 -0
  87. package/src/sources/torrents/nyaa.ts +53 -0
  88. package/src/sources/torrents/solidtorrents.ts +68 -0
  89. package/src/sources/torrents/thepiratebay.ts +57 -0
  90. package/src/sources/torrents/yts.ts +54 -0
  91. package/src/sources/videos/bing_videos.ts +91 -0
  92. package/src/sources/videos/dailymotion.ts +101 -0
  93. package/src/sources/videos/invidious.ts +86 -0
  94. package/src/sources/videos/peertube.ts +76 -0
  95. package/src/sources/videos/vimeo.ts +67 -0
  96. package/src/sources/videos/youtube.ts +78 -0
  97. package/src/suggest-next-words/autocomplete-ai.ts +38 -0
  98. package/src/suggest-next-words/autocomplete-search-engines.ts +384 -0
  99. package/src/suggest-next-words/misspelled-typos-8k.json +1 -0
  100. package/src/types/search-engine-interface.ts +27 -0
  101. package/src/types/search-result-types.ts +405 -0
  102. package/test/api.test.ts +128 -0
  103. package/test/autocomplete-ai.test.ts +20 -0
  104. package/test/autocomplete-engines.test.ts +131 -0
  105. package/test/engine-health-suite.test.ts +350 -0
  106. package/test/search.test.ts +69 -0
  107. package/test/sources-unit.test.ts +1152 -0
  108. package/test/sources.test.ts +182 -0
  109. package/test/test-utils.ts +81 -0
  110. package/tsconfig.json +16 -0
  111. 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
+ }