From 7a2f17bfe9399a1d5714a9a98527d9f788417130 Mon Sep 17 00:00:00 2001 From: ztimson Date: Tue, 25 Aug 2026 00:42:59 -0400 Subject: [PATCH] Use kiwix search completely, no fuzzy ranking --- package.json | 2 +- src/server.js | 26 ++++++++------------------ src/utils.js | 7 ------- 3 files changed, 9 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index 85797ac..fde6432 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ztimson/zim-utils", - "version": "0.3.2", + "version": "0.3.3", "description": "Native, dependency-light ZIM archive reader/searcher and Kiwix catalog downloader for Node.js", "author": "Zak Timson", "license": "MIT", diff --git a/src/server.js b/src/server.js index 9e742bc..38565bb 100644 --- a/src/server.js +++ b/src/server.js @@ -6,8 +6,7 @@ import net from 'node:net'; import fs from 'node:fs'; import path from 'node:path'; import {fileURLToPath} from 'node:url'; -import {decodeHtml, fromXml} from '@ztimson/utils'; -import {fuzzyMatch, weightedScore} from './utils.js'; +import {fromXml} from '@ztimson/utils'; const execFileAsync = promisify(execFile); const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -180,30 +179,23 @@ export class KiwixServer { return {mimetype: res.headers.get('content-type'), data: Buffer.from(await res.arrayBuffer())}; } - /** - * Two-pass fulltext search across every local ZIM: xapian prefilter (kiwix-serve's - * own index), then fuzzy-reranked by title so the strongest matches surface first. - * Returns a flat array matching the catalog/list shape: {id, title, name, category, ..., href, score} - */ + /** Fulltext search across every local ZIM via kiwix-serve's own xapian index */ async search(terms, limit = 20) { this.#assertRunning(); - const termList = String(terms).split(',').map(t => t.trim()).filter(Boolean); + const termList = String(terms).split(/[,\s]+/).map(t => t.trim()).filter(Boolean); if (!termList.length) return []; const params = new URLSearchParams({pattern: termList.join(' '), format: 'xml', pageLength: String(limit)}); const res = await fetch(`${this.baseUrl}/search?${params}`); if (!res.ok) return []; - const xml = await res.text(); - let found = fromXml(xml); - found = found?.rss?.channel?.item || []; - + const found = fromXml(await res.text())?.rss?.channel?.item || []; const books = await this.list(); const bookMap = new Map(books.map(b => [b.title, b])); - const enriched = found.map(hit => { + return found.map(hit => { const book = bookMap.get(hit.book.title); - if(!book) return null; + if (!book) return null; const prefix = `/content/${book.href}/`; const page = hit.link.startsWith(prefix) ? hit.link.slice(prefix.length) : hit.link.replace(/^\/+/, ''); return { @@ -216,10 +208,8 @@ export class KiwixServer { icon: book.icon, viewer: this.baseUrl + hit.link, summary: hit.description, - score: weightedScore(hit.title, termList) + weightedScore(hit.description, termList), + score: +hit.score || 0, }; - }).filter(hit => !!hit && hit.score > 0); - - return enriched.toSorted((a, b) => b.score - a.score).slice(0, limit); + }).filter(Boolean); } } diff --git a/src/utils.js b/src/utils.js index d58ca33..341c5bb 100644 --- a/src/utils.js +++ b/src/utils.js @@ -40,10 +40,3 @@ export function fuzzyMatch(target, ...terms) { similarities, }; } - -export function weightedScore(text, termList) { - if (!text) return 0; - const covered = termList.reduce((sum, t) => sum + t.length, 0); - const coverage = Math.min(1, covered / text.length); - return fuzzyMatch(text, ...termList).max * coverage; -}