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
package/demo/openapi.ts
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
export const openAPISpec = {
|
|
2
|
+
openapi: '3.0.0',
|
|
3
|
+
info: {
|
|
4
|
+
title: 'SearXNG HonoX API',
|
|
5
|
+
version: '0.1.0',
|
|
6
|
+
description: 'A search aggregator API built with Hono that queries multiple search engines and returns unified results.',
|
|
7
|
+
},
|
|
8
|
+
servers: [
|
|
9
|
+
{
|
|
10
|
+
url: 'http://localhost:3000',
|
|
11
|
+
description: 'Development server',
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
paths: {
|
|
15
|
+
'/search': {
|
|
16
|
+
get: {
|
|
17
|
+
summary: 'Search across multiple engines',
|
|
18
|
+
description: 'Performs a search query across multiple search engines and returns unified results.',
|
|
19
|
+
operationId: 'search',
|
|
20
|
+
parameters: [
|
|
21
|
+
{
|
|
22
|
+
name: 'q',
|
|
23
|
+
in: 'query',
|
|
24
|
+
required: true,
|
|
25
|
+
description: 'The search query string',
|
|
26
|
+
schema: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
example: 'typescript tutorial',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'format',
|
|
33
|
+
in: 'query',
|
|
34
|
+
required: false,
|
|
35
|
+
description: 'Response format (currently only JSON is supported)',
|
|
36
|
+
schema: {
|
|
37
|
+
type: 'string',
|
|
38
|
+
enum: ['json'],
|
|
39
|
+
default: 'json',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'pageno',
|
|
44
|
+
in: 'query',
|
|
45
|
+
required: false,
|
|
46
|
+
description: 'Page number for pagination (starts at 1)',
|
|
47
|
+
schema: {
|
|
48
|
+
type: 'integer',
|
|
49
|
+
minimum: 1,
|
|
50
|
+
default: 1,
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'engines',
|
|
55
|
+
in: 'query',
|
|
56
|
+
required: false,
|
|
57
|
+
description: 'Comma-separated list of engine names to use for search',
|
|
58
|
+
schema: {
|
|
59
|
+
type: 'string',
|
|
60
|
+
example: 'google,bing,duckduckgo',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'categories',
|
|
65
|
+
in: 'query',
|
|
66
|
+
required: false,
|
|
67
|
+
description: 'Comma-separated list of categories to filter engines',
|
|
68
|
+
schema: {
|
|
69
|
+
type: 'string',
|
|
70
|
+
example: 'general,images',
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
responses: {
|
|
75
|
+
'200': {
|
|
76
|
+
description: 'Successful search response',
|
|
77
|
+
content: {
|
|
78
|
+
'application/json': {
|
|
79
|
+
schema: {
|
|
80
|
+
type: 'object',
|
|
81
|
+
properties: {
|
|
82
|
+
query: {
|
|
83
|
+
type: 'string',
|
|
84
|
+
description: 'The search query that was executed',
|
|
85
|
+
example: 'typescript tutorial',
|
|
86
|
+
},
|
|
87
|
+
results: {
|
|
88
|
+
type: 'array',
|
|
89
|
+
description: 'Array of search results from various engines',
|
|
90
|
+
items: {
|
|
91
|
+
$ref: '#/components/schemas/EngineResult',
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
required: ['query', 'results'],
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
'400': {
|
|
101
|
+
description: 'Bad request - missing or invalid parameters',
|
|
102
|
+
content: {
|
|
103
|
+
'application/json': {
|
|
104
|
+
schema: {
|
|
105
|
+
$ref: '#/components/schemas/Error',
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
'/scrape': {
|
|
114
|
+
post: {
|
|
115
|
+
summary: 'Scrape a URL using Puppeteer',
|
|
116
|
+
description: 'Scrapes a web page using Puppeteer headless browser and returns the content. Useful for JavaScript-rendered pages and bypassing basic bot detection.',
|
|
117
|
+
operationId: 'scrape',
|
|
118
|
+
requestBody: {
|
|
119
|
+
required: true,
|
|
120
|
+
content: {
|
|
121
|
+
'application/json': {
|
|
122
|
+
schema: {
|
|
123
|
+
type: 'object',
|
|
124
|
+
properties: {
|
|
125
|
+
url: {
|
|
126
|
+
type: 'string',
|
|
127
|
+
description: 'The URL to scrape',
|
|
128
|
+
example: 'https://example.com',
|
|
129
|
+
},
|
|
130
|
+
waitFor: {
|
|
131
|
+
type: 'string',
|
|
132
|
+
description: 'CSS selector to wait for before capturing content',
|
|
133
|
+
example: '.main-content',
|
|
134
|
+
},
|
|
135
|
+
timeout: {
|
|
136
|
+
type: 'integer',
|
|
137
|
+
description: 'Maximum time to wait in milliseconds',
|
|
138
|
+
default: 30000,
|
|
139
|
+
example: 30000,
|
|
140
|
+
},
|
|
141
|
+
screenshot: {
|
|
142
|
+
type: 'boolean',
|
|
143
|
+
description: 'Whether to capture a screenshot',
|
|
144
|
+
default: false,
|
|
145
|
+
},
|
|
146
|
+
javascript: {
|
|
147
|
+
type: 'boolean',
|
|
148
|
+
description: 'Whether to enable JavaScript execution',
|
|
149
|
+
default: true,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
required: ['url'],
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
responses: {
|
|
158
|
+
'200': {
|
|
159
|
+
description: 'Successful scrape response',
|
|
160
|
+
content: {
|
|
161
|
+
'application/json': {
|
|
162
|
+
schema: {
|
|
163
|
+
type: 'object',
|
|
164
|
+
properties: {
|
|
165
|
+
url: {
|
|
166
|
+
type: 'string',
|
|
167
|
+
description: 'The scraped URL',
|
|
168
|
+
},
|
|
169
|
+
html: {
|
|
170
|
+
type: 'string',
|
|
171
|
+
description: 'The HTML content of the page',
|
|
172
|
+
},
|
|
173
|
+
text: {
|
|
174
|
+
type: 'string',
|
|
175
|
+
description: 'The extracted text content',
|
|
176
|
+
},
|
|
177
|
+
title: {
|
|
178
|
+
type: 'string',
|
|
179
|
+
description: 'The page title',
|
|
180
|
+
},
|
|
181
|
+
screenshot: {
|
|
182
|
+
type: 'string',
|
|
183
|
+
description: 'Base64-encoded screenshot (if requested)',
|
|
184
|
+
},
|
|
185
|
+
loadTime: {
|
|
186
|
+
type: 'integer',
|
|
187
|
+
description: 'Time taken to load the page in milliseconds',
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
required: ['url', 'html', 'text', 'title'],
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
'400': {
|
|
196
|
+
description: 'Bad request - missing or invalid parameters',
|
|
197
|
+
content: {
|
|
198
|
+
'application/json': {
|
|
199
|
+
schema: {
|
|
200
|
+
$ref: '#/components/schemas/Error',
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
'500': {
|
|
206
|
+
description: 'Server error - scraping failed',
|
|
207
|
+
content: {
|
|
208
|
+
'application/json': {
|
|
209
|
+
schema: {
|
|
210
|
+
$ref: '#/components/schemas/Error',
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
'/status': {
|
|
219
|
+
get: {
|
|
220
|
+
summary: 'Get engine status information',
|
|
221
|
+
description: 'Returns the health status, success/failure rates, and statistics for all search engines or a specific engine.',
|
|
222
|
+
operationId: 'getStatus',
|
|
223
|
+
parameters: [
|
|
224
|
+
{
|
|
225
|
+
name: 'engine',
|
|
226
|
+
in: 'query',
|
|
227
|
+
required: false,
|
|
228
|
+
description: 'Get status for a specific engine',
|
|
229
|
+
schema: {
|
|
230
|
+
type: 'string',
|
|
231
|
+
example: 'google',
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
responses: {
|
|
236
|
+
'200': {
|
|
237
|
+
description: 'Successful status response',
|
|
238
|
+
content: {
|
|
239
|
+
'application/json': {
|
|
240
|
+
schema: {
|
|
241
|
+
type: 'object',
|
|
242
|
+
properties: {
|
|
243
|
+
engines: {
|
|
244
|
+
type: 'array',
|
|
245
|
+
description: 'Array of engine statuses',
|
|
246
|
+
items: {
|
|
247
|
+
$ref: '#/components/schemas/EngineStatus',
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
summary: {
|
|
251
|
+
type: 'object',
|
|
252
|
+
properties: {
|
|
253
|
+
total: {
|
|
254
|
+
type: 'integer',
|
|
255
|
+
description: 'Total number of engines',
|
|
256
|
+
},
|
|
257
|
+
active: {
|
|
258
|
+
type: 'integer',
|
|
259
|
+
description: 'Number of active engines',
|
|
260
|
+
},
|
|
261
|
+
failed: {
|
|
262
|
+
type: 'integer',
|
|
263
|
+
description: 'Number of failed engines',
|
|
264
|
+
},
|
|
265
|
+
disabled: {
|
|
266
|
+
type: 'integer',
|
|
267
|
+
description: 'Number of disabled engines',
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
'404': {
|
|
277
|
+
description: 'Engine not found',
|
|
278
|
+
content: {
|
|
279
|
+
'application/json': {
|
|
280
|
+
schema: {
|
|
281
|
+
$ref: '#/components/schemas/Error',
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
components: {
|
|
291
|
+
schemas: {
|
|
292
|
+
EngineResult: {
|
|
293
|
+
type: 'object',
|
|
294
|
+
description: 'A single search result from an engine',
|
|
295
|
+
properties: {
|
|
296
|
+
url: {
|
|
297
|
+
type: 'string',
|
|
298
|
+
description: 'URL of the result',
|
|
299
|
+
example: 'https://example.com/article',
|
|
300
|
+
},
|
|
301
|
+
link: {
|
|
302
|
+
type: 'string',
|
|
303
|
+
description: 'Alternative link field for the result',
|
|
304
|
+
example: 'https://example.com/article',
|
|
305
|
+
},
|
|
306
|
+
title: {
|
|
307
|
+
type: 'string',
|
|
308
|
+
description: 'Title of the search result',
|
|
309
|
+
example: 'TypeScript Tutorial - Learn TypeScript',
|
|
310
|
+
},
|
|
311
|
+
content: {
|
|
312
|
+
type: 'string',
|
|
313
|
+
description: 'Content or description of the search result',
|
|
314
|
+
example: 'A comprehensive guide to TypeScript programming...',
|
|
315
|
+
},
|
|
316
|
+
thumbnail: {
|
|
317
|
+
type: 'string',
|
|
318
|
+
description: 'URL to a thumbnail image (if available)',
|
|
319
|
+
example: 'https://example.com/thumb.jpg',
|
|
320
|
+
},
|
|
321
|
+
engine: {
|
|
322
|
+
type: 'string',
|
|
323
|
+
description: 'Name of the engine that provided this result',
|
|
324
|
+
example: 'google',
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
required: ['title', 'content'],
|
|
328
|
+
},
|
|
329
|
+
Error: {
|
|
330
|
+
type: 'object',
|
|
331
|
+
properties: {
|
|
332
|
+
error: {
|
|
333
|
+
type: 'string',
|
|
334
|
+
description: 'Error message',
|
|
335
|
+
example: 'Missing query parameter',
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
required: ['error'],
|
|
339
|
+
},
|
|
340
|
+
EngineStatus: {
|
|
341
|
+
type: 'object',
|
|
342
|
+
description: 'Status information for a search engine',
|
|
343
|
+
properties: {
|
|
344
|
+
name: {
|
|
345
|
+
type: 'string',
|
|
346
|
+
description: 'Engine name',
|
|
347
|
+
example: 'google',
|
|
348
|
+
},
|
|
349
|
+
status: {
|
|
350
|
+
type: 'string',
|
|
351
|
+
enum: ['active', 'failed', 'disabled'],
|
|
352
|
+
description: 'Current engine status',
|
|
353
|
+
example: 'active',
|
|
354
|
+
},
|
|
355
|
+
lastCheck: {
|
|
356
|
+
type: 'string',
|
|
357
|
+
format: 'date-time',
|
|
358
|
+
description: 'Last time the engine was checked',
|
|
359
|
+
},
|
|
360
|
+
lastSuccess: {
|
|
361
|
+
type: 'string',
|
|
362
|
+
format: 'date-time',
|
|
363
|
+
description: 'Last successful request',
|
|
364
|
+
},
|
|
365
|
+
lastFailure: {
|
|
366
|
+
type: 'string',
|
|
367
|
+
format: 'date-time',
|
|
368
|
+
description: 'Last failed request',
|
|
369
|
+
},
|
|
370
|
+
failureCount: {
|
|
371
|
+
type: 'integer',
|
|
372
|
+
description: 'Number of failures',
|
|
373
|
+
},
|
|
374
|
+
successCount: {
|
|
375
|
+
type: 'integer',
|
|
376
|
+
description: 'Number of successes',
|
|
377
|
+
},
|
|
378
|
+
totalRequests: {
|
|
379
|
+
type: 'integer',
|
|
380
|
+
description: 'Total number of requests',
|
|
381
|
+
},
|
|
382
|
+
averageResponseTime: {
|
|
383
|
+
type: 'number',
|
|
384
|
+
description: 'Average response time in milliseconds',
|
|
385
|
+
},
|
|
386
|
+
lastError: {
|
|
387
|
+
type: 'string',
|
|
388
|
+
description: 'Last error message',
|
|
389
|
+
},
|
|
390
|
+
categories: {
|
|
391
|
+
type: 'array',
|
|
392
|
+
items: {
|
|
393
|
+
type: 'string',
|
|
394
|
+
},
|
|
395
|
+
description: 'Engine categories',
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
};
|
|
402
|
+
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Autocomplete API Route
|
|
3
|
+
*
|
|
4
|
+
* Provides search query autocomplete/suggestions endpoint
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Hono } from "hono";
|
|
8
|
+
import {
|
|
9
|
+
searchAutocomplete,
|
|
10
|
+
searchAutocompleteMulti,
|
|
11
|
+
backends,
|
|
12
|
+
} from "../../lib/suggest-next-words/autocomplete-search-engines";
|
|
13
|
+
|
|
14
|
+
const autocompleteRoute = new Hono();
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* GET /autocomplete
|
|
18
|
+
* Get autocomplete suggestions from a specific backend
|
|
19
|
+
*
|
|
20
|
+
* Query parameters:
|
|
21
|
+
* - q: Search query (required)
|
|
22
|
+
* - backend: Autocomplete backend name (default: 'google')
|
|
23
|
+
* - locale: Language/locale code (default: 'en-US')
|
|
24
|
+
*
|
|
25
|
+
* Example: /autocomplete?q=typescript&backend=google&locale=en-US
|
|
26
|
+
*/
|
|
27
|
+
autocompleteRoute.get("/", async (c) => {
|
|
28
|
+
const q = c.req.query("q");
|
|
29
|
+
const backend = c.req.query("backend") || "google";
|
|
30
|
+
const locale = c.req.query("locale") || "en-US";
|
|
31
|
+
|
|
32
|
+
if (!q) {
|
|
33
|
+
return c.json(
|
|
34
|
+
{
|
|
35
|
+
error: 'Query parameter "q" is required',
|
|
36
|
+
suggestions: [],
|
|
37
|
+
},
|
|
38
|
+
400
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const suggestions = await searchAutocomplete(backend, q, locale);
|
|
43
|
+
|
|
44
|
+
return c.json({
|
|
45
|
+
query: q,
|
|
46
|
+
backend,
|
|
47
|
+
locale,
|
|
48
|
+
suggestions,
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* GET /autocomplete/multi
|
|
54
|
+
* Get autocomplete suggestions from multiple backends
|
|
55
|
+
*
|
|
56
|
+
* Query parameters:
|
|
57
|
+
* - q: Search query (required)
|
|
58
|
+
* - backends: Comma-separated list of backend names (default: 'google,duckduckgo')
|
|
59
|
+
* - locale: Language/locale code (default: 'en-US')
|
|
60
|
+
*
|
|
61
|
+
* Example: /autocomplete/multi?q=python&backends=google,duckduckgo,wikipedia
|
|
62
|
+
*/
|
|
63
|
+
autocompleteRoute.get("/multi", async (c) => {
|
|
64
|
+
const q = c.req.query("q");
|
|
65
|
+
const backendsParam = c.req.query("backends") || "google,duckduckgo";
|
|
66
|
+
const locale = c.req.query("locale") || "en-US";
|
|
67
|
+
|
|
68
|
+
if (!q) {
|
|
69
|
+
return c.json(
|
|
70
|
+
{
|
|
71
|
+
error: 'Query parameter "q" is required',
|
|
72
|
+
suggestions: [],
|
|
73
|
+
},
|
|
74
|
+
400
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const backendNames = backendsParam.split(",").map((b) => b.trim());
|
|
79
|
+
const suggestions = await searchAutocompleteMulti(backendNames, q, locale);
|
|
80
|
+
|
|
81
|
+
return c.json({
|
|
82
|
+
query: q,
|
|
83
|
+
backends: backendNames,
|
|
84
|
+
locale,
|
|
85
|
+
count: suggestions.length,
|
|
86
|
+
suggestions,
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* GET /autocomplete/backends
|
|
92
|
+
* List available autocomplete backends
|
|
93
|
+
*/
|
|
94
|
+
autocompleteRoute.get("/backends", (c) => {
|
|
95
|
+
const availableBackends = Object.keys(backends).map((name) => ({
|
|
96
|
+
name,
|
|
97
|
+
description: getBackendDescription(name),
|
|
98
|
+
}));
|
|
99
|
+
|
|
100
|
+
return c.json({
|
|
101
|
+
backends: availableBackends,
|
|
102
|
+
count: availableBackends.length,
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get description for autocomplete backend
|
|
108
|
+
*/
|
|
109
|
+
function getBackendDescription(name: string): string {
|
|
110
|
+
const descriptions: { [key: string]: string } = {
|
|
111
|
+
baidu: "Baidu (Chinese search engine)",
|
|
112
|
+
brave: "Brave Search",
|
|
113
|
+
duckduckgo: "DuckDuckGo (privacy-focused)",
|
|
114
|
+
google: "Google Search",
|
|
115
|
+
qwant: "Qwant (European search engine)",
|
|
116
|
+
startpage: "Startpage (privacy-focused)",
|
|
117
|
+
wikipedia: "Wikipedia",
|
|
118
|
+
yandex: "Yandex (Russian search engine)",
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
return descriptions[name] || name;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export default autocompleteRoute;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { Search } from '../../lib/search-web/search.js';
|
|
3
|
+
|
|
4
|
+
const app = new Hono();
|
|
5
|
+
|
|
6
|
+
const search = new Search();
|
|
7
|
+
|
|
8
|
+
app.get('/search', async (c) => {
|
|
9
|
+
const query = c.req.query('q');
|
|
10
|
+
const format = c.req.query('format') || 'json';
|
|
11
|
+
const pageno = parseInt(c.req.query('pageno') || '1');
|
|
12
|
+
const enginesParam = c.req.query('engines');
|
|
13
|
+
const categoriesParam = c.req.query('categories');
|
|
14
|
+
|
|
15
|
+
if (!query) {
|
|
16
|
+
return c.json({ error: 'Missing query parameter' }, 400);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (format !== 'json') {
|
|
20
|
+
return c.json({ error: 'Only JSON format is supported' }, 400);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const engineNames = enginesParam ? enginesParam.split(',') : undefined;
|
|
24
|
+
const categories = categoriesParam ? categoriesParam.split(',') : undefined;
|
|
25
|
+
|
|
26
|
+
const results = await search.search(query, pageno, engineNames, categories);
|
|
27
|
+
|
|
28
|
+
if (format === 'json') {
|
|
29
|
+
return c.json({
|
|
30
|
+
query,
|
|
31
|
+
results
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return c.text('Only JSON format is supported in this version.');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
app.get('/status', async (c) => {
|
|
39
|
+
const engineParam = c.req.query('engine');
|
|
40
|
+
|
|
41
|
+
if (engineParam) {
|
|
42
|
+
const status = search.getEngineStatus(engineParam);
|
|
43
|
+
if (!status) {
|
|
44
|
+
return c.json({ error: 'Engine not found' }, 404);
|
|
45
|
+
}
|
|
46
|
+
return c.json(status);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const allStatuses = search.getAllEngineStatuses();
|
|
50
|
+
return c.json({
|
|
51
|
+
engines: allStatuses,
|
|
52
|
+
summary: {
|
|
53
|
+
total: allStatuses.length,
|
|
54
|
+
active: allStatuses.filter((s: any) => s.status === 'active').length,
|
|
55
|
+
failed: allStatuses.filter((s: any) => s.status === 'failed').length,
|
|
56
|
+
disabled: allStatuses.filter((s: any) => s.status === 'disabled').length,
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
export default app;
|