diff --git a/lib/client.js b/lib/client.js index 5135863..c0ab9ac 100644 --- a/lib/client.js +++ b/lib/client.js @@ -183,16 +183,113 @@ export class PaperlessClient { return true; } + /** + * Dokumente suchen. Vier Wege, in dieser Reihenfolge: + * + * 1. Archiv-Seriennummer, wenn die Eingabe nur aus Ziffern besteht. + * Sie ist ein eigenes Feld - die Volltextsuche findet sie NICHT. + * + * 2. Die GLOBALE SUCHE von Paperless (/api/search/). Dieselbe + * Schnittstelle, die die Suchleiste oben in der Weboberfläche + * bedient. Sie verbindet Titelsuche und Volltext und liefert + * damit die Ergebnisse, die man aus der Oberfläche kennt. + * + * 3. Teilzeichenkette in Titel und Inhalt. Greift, wenn die globale + * Suche nichts liefert oder in der eingesetzten Version fehlt. + * + * 4. Volltextsuche über den Index, mit UND-Verknüpfung. Sie + * beherrscht Wortstämme und findet auch Beugungsformen. + * + * Zwei Fallstricke, die hier berücksichtigt sind: + * + * * Bei der Volltextsuche darf KEIN ordering mitgegeben werden. + * Paperless sortiert dort nach Relevanz; eine eigene + * Sortierangabe führte dazu, dass die Suchanfrage verworfen wurde + * und schlicht die zuletzt hinzugefügten Dokumente kamen. + * + * * Ohne UND-Verknüpfung sucht der Index mit ODER. "Anschreiben + * Markus Straub" lieferte dann alles, was irgendeines der drei + * Wörter enthält - der gesuchte Treffer ging darin unter. + */ async search(query, limit = 50) { + const q = String(query || "").trim(); + + if (!q) return this.#suchlauf({ ordering: "-added" }, limit); + + // 1. Seriennummer + if (/^\d+$/.test(q)) { + const asn = await this.#suchlauf({ archive_serial_number: q }, limit); + if (asn.length) return asn; + } + + // 2. Globale Suche wie in der Weboberfläche + const global = await this.#globaleSuche(q, limit); + if (global.length) return global; + + // 3. Titel und Inhalt + const text = await this.#suchlauf( + { title_content: q, ordering: "-created" }, limit); + if (text.length) return text; + + // 4. Volltext mit UND, ohne ordering. + // + // Nach dem Ersetzen der Sonderzeichen ERNEUT zerlegen: Aus + // "Müller-Lüdenscheidt" würde sonst ein Wort mit Leerzeichen, und + // die UND-Verknüpfung wäre wieder aufgehoben. + const worte = q.replace(/[+\-&|!(){}[\]^"~*?:\\/]/g, " ") + .split(/\s+/).filter(Boolean); + if (!worte.length) return []; + return this.#suchlauf({ query: worte.join(" AND ") }, limit); + } + + /** + * Die Schnittstelle hinter der Suchleiste der Weboberfläche. + * + * Sie gibt es erst ab neueren Paperless-Ständen, und die Form der + * Antwort ist nicht überall gleich - mal ein Feld "documents", mal + * "results". Deshalb wird beides angenommen und bei jedem Fehler + * stillschweigend auf den nächsten Weg ausgewichen. + */ + async #globaleSuche(q, limit) { + let antwort; + try { + const params = new URLSearchParams({ query: q, + page_size: String(limit) }); + antwort = await this.#json(`search/?${params}`); + } catch { + return []; + } + if (!antwort) return []; + + for (const schluessel of ["documents", "results"]) { + const rows = antwort[schluessel]; + if (Array.isArray(rows) && rows.length) { + // Die globale Suche liefert je nach Version verschiedene + // Objektarten. Nur echte Dokumente durchlassen. + return rows + .filter((r) => r && typeof r === "object" + && r.id !== undefined && r.title !== undefined) + .slice(0, limit); + } + } + return []; + } + + async #suchlauf(zusatz, limit) { const params = new URLSearchParams({ page_size: String(limit), - ordering: "-added", - fields: "id,title,created,added,archive_serial_number,mime_type," + - "original_file_name" + // Ohne diese Einschränkung liefert Paperless je Treffer auch den + // vollständigen Inhalt - bei fünfzig Treffern mehrere Megabyte. + fields: "id,title,created,added,archive_serial_number,mime_type," + + "original_file_name" }); - if (query) params.set("query", query); - const data = await this.#json(`documents/?${params}`); - return data?.results || []; + for (const [k, v] of Object.entries(zusatz)) params.set(k, String(v)); + try { + const data = await this.#json(`documents/?${params}`); + return data?.results || []; + } catch { + return []; + } } document(id) { diff --git a/manifest.json b/manifest.json index d0545d0..96d1f6b 100644 --- a/manifest.json +++ b/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 3, "name": "__MSG_extName__", "description": "__MSG_extDescription__", - "version": "1.14.3", + "version": "1.16.0", "author": "Marco Morath", "homepage_url": "https://gitea.cloud.mmorath.de/marco.morath/thunderbird-paperless-connector", "default_locale": "de",