Files
glimpse/kagi.js
2026-04-26 12:49:28 -04:00

66 lines
1.7 KiB
JavaScript

import { createDriver } from "./driver.js";
export class SearchProviderError extends Error {
constructor(code, message) {
super(message);
this.code = code;
}
}
// Kagi Search Extraction
export const kagiSearchScript = `return Array.from(document.querySelectorAll("div > .__sri-title"))
.map((i) => i.parentElement)
.map((i) => ({
title: i.children[0].querySelector("a").innerText,
url: i.children[0].querySelector("a").getAttribute("href"),
description: i.querySelector(".__sri-body").innerText,
}));`;
// Build Kagi Search Url
export function buildKagiSearchUrl(query, token) {
return `https://kagi.com/search?token=${encodeURIComponent(token)}&q=${encodeURIComponent(query)}`;
}
// Search Kagi
export async function searchKagi({
query,
token = process.env.KAGI_TOKEN,
headless = true,
existingUrl,
timeoutMs = 5000,
intervalMs = 200,
} = {}) {
if (!query) {
throw new SearchProviderError("QUERY_REQUIRED", "query is required");
}
// Validate Kagi Token
if (!token) {
throw new SearchProviderError(
"KAGI_TOKEN_REQUIRED",
"Kagi search requires --token or the KAGI_TOKEN environment variable.",
);
}
const driver = await createDriver({ headless, existingUrl });
try {
// Navigate To Kagi
await driver.get(buildKagiSearchUrl(query, token));
// Poll For Results
let result = [];
const start = Date.now();
while (Date.now() - start < timeoutMs) {
result = await driver.executeScript(kagiSearchScript);
if (Array.isArray(result) && result.length > 0) break;
await new Promise((resolve) => setTimeout(resolve, intervalMs));
}
return result;
} finally {
await driver.quit();
}
}