Spaces:
Paused
Paused
| import { PreviewServer, ViteDevServer } from "vite"; | |
| import { RateLimiterMemory } from "rate-limiter-flexible"; | |
| import { argon2Verify } from "hash-wasm"; | |
| import { incrementSearchesSinceLastRestart } from "./searchesSinceLastRestart"; | |
| import { rankSearchResults } from "./rankSearchResults"; | |
| import { getSearchToken } from "./searchToken"; | |
| import { fetchSearXNG } from "./fetchSearXNG"; | |
| import { isVerifiedToken, addVerifiedToken } from "./verifiedTokens"; | |
| export function searchEndpointServerHook< | |
| T extends ViteDevServer | PreviewServer, | |
| >(server: T) { | |
| const rateLimiter = new RateLimiterMemory({ | |
| points: 2, | |
| duration: 10, | |
| }); | |
| server.middlewares.use(async (request, response, next) => { | |
| if (!request.url.startsWith("/search")) return next(); | |
| const { searchParams } = new URL( | |
| request.url, | |
| `http://${request.headers.host}`, | |
| ); | |
| const limitParam = searchParams.get("limit"); | |
| const limit = | |
| limitParam && Number(limitParam) > 0 ? Number(limitParam) : undefined; | |
| const query = searchParams.get("q"); | |
| if (!query) { | |
| response.statusCode = 400; | |
| response.end("Missing the query parameter."); | |
| return; | |
| } | |
| const token = searchParams.get("token"); | |
| if (!isVerifiedToken(token)) { | |
| let isValidToken = false; | |
| try { | |
| isValidToken = await argon2Verify({ | |
| password: getSearchToken(), | |
| hash: token, | |
| }); | |
| } catch (error) { | |
| void error; | |
| } | |
| if (isValidToken) { | |
| addVerifiedToken(token); | |
| } else { | |
| response.statusCode = 401; | |
| response.end("Unauthorized."); | |
| return; | |
| } | |
| } | |
| try { | |
| await rateLimiter.consume(token); | |
| } catch { | |
| response.statusCode = 429; | |
| response.end("Too many requests."); | |
| return; | |
| } | |
| const { textResults, imageResults } = await fetchSearXNG(query, limit); | |
| incrementSearchesSinceLastRestart(); | |
| if (textResults.length === 0 && imageResults.length === 0) { | |
| response.setHeader("Content-Type", "application/json"); | |
| response.end(JSON.stringify({ textResults: [], imageResults: [] })); | |
| return; | |
| } | |
| try { | |
| const rankedTextResults = await rankSearchResults(query, textResults); | |
| response.setHeader("Content-Type", "application/json"); | |
| response.end( | |
| JSON.stringify({ textResults: rankedTextResults, imageResults }), | |
| ); | |
| } catch (error) { | |
| const errorMessage = | |
| error instanceof Error ? error.message : String(error); | |
| console.error(`Error ranking search results: ${errorMessage}`); | |
| response.setHeader("Content-Type", "application/json"); | |
| response.end(JSON.stringify({ textResults, imageResults })); | |
| } | |
| }); | |
| } | |