# Paperless-Connector für LibreOffice Erweiterung, die Dokumente direkt aus einem Paperless-ngx-Archiv öffnet, Bearbeitungen als **neue Version desselben Dokuments** ablegt, die Klassifizierung bearbeitet und zeitlich begrenzte Freigabelinks erzeugt. Voraussetzung ist Paperless-ngx **3.0 oder neuer** — erst dort gibt es die Dokumentversionierung. --- ## Funktionen | Befehl | Wirkung | |---|---| | **Aus Paperless öffnen** | Suche, Trefferliste, Versionsliste — lädt das **Original** | | **Als neue Version ablegen** | hängt die Fassung als Version an das bestehende Dokument | | **In Paperless ablegen** | neues Dokument **oder** neue Version eines beliebigen anderen Dokuments | | **Klassifizierung bearbeiten** | Titel, Datum, Korrespondent, Dokumenttyp, Tags | | **Freigabelink erzeugen** | zeitlich begrenzter, unauthentifizierter Link, optional mit Kennzeichnung | | **Status anzeigen** | Kennung, Titel, Versionsanzahl des aktuellen Dokuments | | **Verbindung einrichten** | Basisadresse und API-Token | Erreichbar über das Menü **Extras → Paperless** und über eine eigene Symbolleiste. --- ## Sprachen Die Oberfläche liegt in **Deutsch, Englisch und Französisch** vor. Die Sprache richtet sich nach der Einstellung von LibreOffice, nicht nach der des Betriebssystems — maßgeblich ist die Sprache, in der die Erweiterung erscheint. Übersetzt wird in `pythonpath/paperless_i18n.py`. Als Schlüssel dient der deutsche Text selbst; fehlt eine Übersetzung, erscheint das deutsche Original. Eine weitere Sprache ergänzt man, indem man ein Wörterbuch nach dem Muster von `EN` anlegt und in `KATALOG` einträgt. Die Menü- und Symbolleistentexte liegen getrennt davon in `Addons.xcu`; dort sind sie je Sprache als `xml:lang`-Werte hinterlegt. ## Sicherheit Ausführlich in [SICHERHEIT.md](SICHERHEIT.md). Die wichtigsten Punkte: - **Makros werden nie ausgeführt.** Heruntergeladene Dateien werden mit `MacroExecutionMode = NEVER_EXECUTE` geladen — auch eine Rückfrage gibt es nicht. Ein Dokument aus dem Archiv ist fremder Inhalt, selbst aus dem eigenen Bestand. - **Keine externen Inhalte beim Öffnen.** `UpdateDocMode = NO_UPDATE`. - **Dateinamen aus der Antwort werden vollständig bereinigt**, beide Pfadtrennzeichen, Windows-Gerätenamen, Punkte am Ende. - **Arbeitsverzeichnis im Benutzerprofil**, nicht in `/tmp` — dort wäre ein Symlink-Angriff möglich. - **Nur http und https** als Basisadresse und bei Weiterleitungen. - **Größenbegrenzung** auf Antworten. ## Bewusste Entwurfsentscheidungen ### Eigene Schaltflächen statt Speichern-Knopf Paperless als echtes Speicherziel in die Datei-Dialoge einzuhängen würde einen **UCB Content Provider** erfordern — C++-UNO-Arbeit in einer sehr dünn dokumentierten Ecke, für ein Einzelprojekt nicht tragfähig. Eigene Schaltflächen sind ohnehin die ehrlichere Lösung: Es bleibt sichtbar, wann eine neue Version im Archiv entsteht, und ein versehentliches Strg+S erzeugt keine Archivversion. ### Prüfungen vor der Freigabe Ein Link auf einen Stand, der nur lokal existiert, wäre irreführend — der Empfänger bekäme etwas anderes zu sehen als der Absender vor sich hat. Deshalb wird vorher geprüft: | Zustand | Reaktion | |---|---| | Dokument nicht in Paperless | Angebot, es zuerst abzulegen | | lokale Änderungen nicht abgelegt | Auswahl: abbrechen, trotzdem teilen, oder erst neue Version ablegen | Zusätzlich lässt sich das Dokument beim Teilen mit einem **Tag** kennzeichnen, voreingestellt `Sharelink`. Das macht geteilte Dokumente später gezielt auffindbar — Paperless zeigt zwar die eigenen Freigaben an, aber ein Tag lässt sich in gespeicherten Ansichten und Regeln verwenden. Der zuletzt verwendete Tagname wird gemerkt. Ist er noch nicht vorhanden, wird er nach Rückfrage angelegt. Vorhandene Tags des Dokuments bleiben erhalten. ### Drei Wege ins Archiv | Befehl | Ergebnis | |---|---| | **Als neue Version ablegen** | eine weitere Fassung **desselben** Dokuments | | **In Paperless ablegen** → Neues Dokument | eigenständiges Dokument mit eigener Versionskette und Seriennummer | | **In Paperless ablegen** → An bestehendes anhängen | neue Version eines **beliebigen anderen** Dokuments | Der dritte Weg deckt eine Lücke, die Dokumentenverwaltungen gern offenlassen: > Die Rechnung kommt als PDF ins Archiv. Später entsteht die bearbeitbare > Fassung. Beides gehört zusammen — aber das PDF war zuerst da. „Als neue Version ablegen" zielt immer auf das eigene Wurzeldokument und hilft hier nicht. Über die Zielauswahl lässt sich dagegen jedes vorhandene Dokument suchen und die geöffnete Datei daran anhängen. Beim neuen Dokument lässt sich die Klassifizierung des Ursprungs übernehmen — Dokumenttyp, Korrespondent, Tags, Zusatzfelder und Eigentümer. Zusatzfelder mit **Herkunftsbezug** werden bewusst nicht kopiert — wer aus einem anderen System migriert hat, führt dort oft ein Feld mit der Fremdkennung, und eine Kopie mit derselben Kennung wäre falsch. Welche Feldnamen das betrifft, steht in den Einstellungen unter `herkunftsfelder`. In beiden Fällen kann das geöffnete Fenster danach dem Ziel zugeordnet werden. Weitere Versionen landen dann dort. Der Befehl funktioniert auch mit Dateien, die **nicht** aus Paperless stammen — dann ist er der Weg, sie überhaupt erst einzuliefern. Und er funktioniert mit Dokumenten, die **noch nie gespeichert** wurden. Ein frisch angelegtes „Unbenannt 1" hat keinen Speicherort; es wird dann in dasselbe temporäre Verzeichnis geschrieben, das auch beim Öffnen benutzt wird, im zur Anwendung passenden ODF-Format. Der eingegebene Titel bestimmt dabei den Dateinamen, unter dem Paperless das Original ablegt. ### Ein Dialog von oben nach unten ``` ┌─ Suche ──────────────────────────────────────── [Suchen] ─┐ │ │ │ Dokumente ASN Datum Titel │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ 11263 2024-02-11 Mietvertrag │ │ │ │ 11264 2024-03-02 Nebenkostenabrechnung │ │ │ └──────────────────────────────────────────────────────┘ │ │ │ │ Versionen Nr. Datum Format Bezeichnung │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ • 3 2026-09-01 PDF LibreOffice 2026-09-01 │ │ │ │ 2 2025-06-04 ODF Text │ │ │ │ 1 2024-02-11 ODF Text │ │ │ └──────────────────────────────────────────────────────┘ │ │ Bearbeitbar. Änderungen können als neue Version … │ │ ☐ Schreibgeschützt öffnen [Öffnen] [Abbrechen] │ └────────────────────────────────────────────────────────────┘ ``` Die Spaltenüberschrift steht jeweils **unter** der Bezeichnung des Abschnitts und über der Liste, nicht daneben. Beide Listen verwenden eine dicht laufende Schrift mit festen Spaltenbreiten und senkrechten Strichen als Trenner. Die Schriftgröße wird bewusst nicht festgelegt, sondern vom Dialog geerbt — also die Vorgabe des Systems. Mit der sonst üblichen Proportionalschrift wären mit Leerzeichen aufgefüllte Spalten **nicht** ausgerichtet — die Überschrift stünde woanders als der Inhalt. Die Trefferliste zeigt je Dokument **einen** Eintrag, nämlich das Wurzeldokument. Bei Auswahl füllt sich darunter die Versionsliste mit Nummer, Datum, **Dateiformat** und Bezeichnung. Vorausgewählt ist die aktuellste Version. Ein **Punkt** markiert Fassungen, die LibreOffice nach der Formattabelle nicht bearbeiten kann — etwa ein PDF, während eine ältere Version noch das ODT ist. ### Öffnungsart Unten im Dialog wird die Öffnungsart gewählt. Die Vorbelegung ist ein **Vorschlag, keine Vorschrift** — die Formaterkennung kann danebenliegen, und dann soll der Anwender trotzdem bearbeitbar öffnen können. | Fall | Vorbelegung | Hinweis | |---|---|---| | bekannt und bearbeitbar | Bearbeitbar | — | | **nicht erkannt** | Bearbeitbar | zeigt Dateiname und MIME-Typ, damit das Format gemeldet und ergänzt werden kann | | bekannt, nicht bearbeitbar (PDF, E-Mail) | Schreibgeschützt | bearbeitbar bleibt wählbar | Beim Zurückschreiben eines unbekannten Formats kommt eine Rückfrage: Es gibt dann keinen passenden Speicherfilter, LibreOffice speichert im eigenen Format, und die Dateiendung passt danach möglicherweise nicht mehr zum Inhalt. Welche Formate als bearbeitbar gelten, steht in `pythonpath/paperless_formats.py`. Die Tabelle lässt sich dort ergänzen — wenn im Hinweistext regelmäßig derselbe MIME-Typ auftaucht, gehört er hinein. Die Formatangabe steht nicht in der eingebetteten `versions`-Liste, deshalb ruft die Erweiterung je Version einen Datensatz ab. Die Ergebnisse werden zwischengespeichert, damit das Blättern in der Trefferliste nicht bei jedem Tastendruck erneut abfragt. ### Immer das Original laden Der Download erfolgt mit `?original=true`. Bei ZUGFeRD bleibt damit die eingebettete XML erhalten, bei E-Mails die Anhänge. Die von Paperless erzeugte Archivfassung wäre eine Ableitung. ### Kennung in den Dokumenteigenschaften Die Paperless-ID wird in den **benutzerdefinierten Dokumenteigenschaften** abgelegt (`PaperlessDocumentId`). Diese überleben einen ODF-Speichervorgang und wandern sogar mit, wenn die Datei kopiert wird. Als Rückfallebene dient eine lokale Zuordnungsdatei über den Datei-URL — für Formate, die keine solchen Eigenschaften kennen. ### Betriebssystemneutral Die Erweiterung läuft unter Linux, Windows und macOS. Was dafür nötig war: | Punkt | Umsetzung | |---|---| | Konfigurationsort | `%APPDATA%` unter Windows, sonst `XDG_CONFIG_HOME` bzw. `~/.config` | | Pfade | durchgehend `os.path.join`, nie feste Trennzeichen | | Datei-URLs | `unohelper.systemPathToFileUrl` statt eigener Zusammensetzung | | Dateinamen | Windows-Verbotszeichen und Gerätenamen werden auch unter Linux entfernt | Eine Einschränkung: `chmod` ist unter Windows weitgehend wirkungslos. Die Einstellungsdatei mit dem Token liegt dort im Benutzerprofil, auf das andere Benutzer standardmäßig keinen Zugriff haben — der Schutz kommt also vom Betriebssystem statt von den Dateirechten. `build.sh` ist ein Shell-Skript und läuft nur unter Linux und macOS. Es wird ausschließlich zur Entwicklung gebraucht; die fertige `.oxt` lässt sich überall installieren. ### Ohne `requests` Die in LibreOffice mitgelieferte Python-Laufzeit bringt nur die Standardbibliothek mit. Alles läuft über `urllib`, inklusive selbstgebautem Multipart-Upload. --- ## Bauen und installieren ```bash ./build.sh unopkg add --force paperless-libreoffice-1.1.0.oxt ``` > **Zur Versionsabhängigkeit:** `OpenOffice.org-minimal-version` in > `description.xml` bezieht sich auf die OpenOffice-**Referenzversion**, und > die ist bei LibreOffice dauerhaft auf **4.1** eingefroren — unabhängig > davon, ob die installierte Version 7, 24 oder 25 heißt. Ein Wert wie `7.0` > schließt dort jede Installation aus und führt zu „Die Erweiterung benötigt > mindestens OpenOffice.org Referenzversion 7.0". > > Für echte LibreOffice-Versionen gibt es `lo:LibreOffice-minimal-version` > im Namensraum `http://libreoffice.org/extensions/description/2011`. LibreOffice danach **vollständig beenden**, auch den Schnellstarter: ```bash pkill soffice ``` Entfernen: ```bash unopkg remove org.example.paperless.libreoffice ``` Alternativ über Extras → Extension Manager. --- ## Einrichten 1. **Extras → Paperless → Verbindung einrichten** 2. Basisadresse eintragen, etwa `https://dms.example.org` — ohne abschließenden Schrägstrich 3. API-Token eintragen. Erzeugt wird er im **Benutzerprofil** von Paperless, nicht in der allgemeinen Verwaltung 4. Speichern — die Verbindung wird sofort geprüft Die Einstellungen liegen unter `~/.config/paperless-libreoffice/settings.json` mit Modus 600. > **Zum Token:** Er liegt im Klartext in dieser Datei. Das entspricht dem, > was andere Desktop-Anwendungen tun, ist aber kein Geheimnisspeicher. Wer > es strenger will, müsste die Erweiterung an den Systemschlüsselbund > anbinden — über `com.sun.star.task.PasswordContainer` oder libsecret. --- ## Ablauf im Alltag ``` Extras → Paperless → Aus Paperless öffnen… Suchbegriff oder Archiv-Seriennummer eingeben Dokument auswählen → öffnet sich in LibreOffice … bearbeiten, zwischendurch normal mit Strg+S speichern (bleibt lokal, das Archiv bleibt unberührt) Extras → Paperless → Als neue Version ablegen Rückfrage bestätigen → Upload → Bestätigung mit Versions-ID ``` Die bisherige Fassung bleibt als Version erhalten. In Paperless entsteht kein neuer Eintrag, sondern eine Version am bestehenden Dokument. --- ## Fehlersuche Die Erweiterung erscheint nicht im Menü: ```bash unopkg list | grep -i paperless ``` Fehlt sie, wurde LibreOffice nicht vollständig beendet. Python-Fehler beim Laden erscheinen auf der Konsole: ```bash soffice --writer ``` Ausführlichere Meldungen mit: ```bash SAL_LOG="+WARN+INFO.extensions" soffice --writer ``` ### Bekannte Stolpersteine **Dokument ohne Speicherort.** Ein noch nie gespeichertes Dokument wird für die Ablage automatisch temporär geschrieben. In LibreOffice bleibt es danach weiterhin ungespeichert — das ist Absicht, damit nicht ungefragt eine Datei im Dateisystem entsteht, von der niemand weiß. **„Dieses Dokument stammt nicht aus Paperless."** Die Kennung wurde nicht gefunden. Das passiert, wenn die Datei über den normalen Öffnen-Dialog geladen wurde oder das Format keine benutzerdefinierten Dokumenteigenschaften kennt. **Speichern in Fremdformaten.** Beim Zurückspeichern eines DOCX oder XLSX wird der passende Filter gewählt (siehe `FORMATS` in `pythonpath/paperless_formats.py`). Fehlt ein Format dort, speichert LibreOffice als ODF — dann stimmt die Endung nicht mehr zum Inhalt. **Freigabelinks.** Der Endpunkt heißt je nach Paperless-Version `share_links/` oder `sharelinks/`. Beide werden probiert. **HTTP 403 nur über die Domain, nicht über die IP.** Klassisches Zeichen dafür, dass ein Schutzsystem vor der Anwendung den User-Agent abweist. `urllib` sendet ohne Zutun `Python-urllib/3.x`, und diese Kennung steht in den Sperrlisten von CrowdSec, fail2ban und gängigen WAF-Regelwerken. Die Erweiterung setzt deshalb eine eigene Kennung. Wird auch die abgewiesen, lässt sie sich in `paperless_client.py` über die Konstante `USER_AGENT` ändern. Zum Nachstellen: ```bash curl -s -o /dev/null -w '%{http_code}\n' -A "Python-urllib/3.10" \ -H "Authorization: Token $PT" https://dms.example.org/api/documents/ ``` 403 gegenüber 200 mit der curl-Vorgabe bestätigt die Ursache. Die Fehlermeldung kann dabei von einem völlig anderen Dienst stammen als dem, den man ansprechen wollte. --- ## Symbole Alle Symbole teilen dieselbe Grundform — ein Blatt mit umgeknickter Ecke in Paperless-Grün — damit die Leiste als zusammengehörige Gruppe erkennbar bleibt. Unterschieden wird über eine farbige Scheibe unten rechts: | Befehl | Beizeichen | |---|---| | **Markensymbol** | gefüllte grüne Fläche mit weißem Blatt, ganz links in der Leiste | | Öffnen | blauer Pfeil nach unten | | Neue Version | oranger Pfeil nach oben mit Plus | | In Paperless ablegen | oranger Pfeil nach oben mit Sternchen, zweites Blatt dahinter | | Klassifizierung | violettes Etikett | | Teilen | türkise verbundene Punkte | | Einstellungen | graues Zahnrad | Das Markensymbol steht am Anfang der Leiste, durch einen Trenner von den Befehlen abgesetzt. Es ist mit **Status anzeigen** belegt, also nicht nur Zierde. Durch die gefüllte Fläche unterscheidet es sich deutlich von den Bordmitteln von LibreOffice — man sieht auf einen Blick, wo die Paperless-Leiste beginnt und endet. ### Format Registriert sind **SVG**. LibreOffice skaliert sie auf die eingestellte Symbolgröße (Extras → Optionen → Ansicht → Symbolgröße: Klein, Groß, Sehr groß). Mit PNG wären nur zwei feste Größen möglich, und bei „Sehr groß" würde hochgerechnet. Erzeugt werden die Symbole mit `make_icons.py` aus einer **gemeinsamen Geometriebeschreibung** — SVG und PNG können deshalb nicht auseinanderlaufen. Das Skript braucht Pillow für die PNG-Ausgabe und wird nur zur Entwicklung gebraucht; die fertigen Dateien liegen unter `icons/` und sind im Paket. Zusätzlich zu den SVG enthält das Paket PNG in 16, 26 und 32 Pixeln als Rückfallebene. Sollte die SVG-Unterstützung in einer LibreOffice-Version nicht greifen und gar kein Symbol erscheinen, in `Addons.xcu` umstellen: ``` ImageSmallURL → %origin%/icons/paperless-NAME_16.png ImageBigURL → %origin%/icons/paperless-NAME_26.png ``` ### Reihenfolge in der Leiste ``` [Paperless] │ [Öffnen] [Neue Version] [Ablegen unter] [Klassifizierung] [Teilen] │ [Verbindung] ``` ## Aufbau ``` paperless_addon.py Protokoll-Handler, Befehlslogik make_icons.py erzeugt die Symbole (nur zur Entwicklung) pythonpath/ paperless_client.py REST-Client, nur Standardbibliothek paperless_config.py Einstellungen, Dokumentzuordnung paperless_dialogs.py UNO-Dialoge, programmatisch aufgebaut paperless_formats.py Formate, Filter, Bearbeitbarkeit Addons.xcu Menü, Symbolleiste, Symbole icons/ 16x16 und 26x26 PNG je Befehl ProtocolHandler.xcu Registrierung von paperless.Command:* ``` Der Protokoll-Handler ist der Kern: Menüeinträge lösen URLs wie `paperless.Command:Open` aus, die LibreOffice an die registrierte Klasse weiterreicht. --- ## Stand und Grenzen Version 1.1.0. Das Gerüst ist vollständig und die Client-Logik getestet, aber die Erweiterung ist an einer echten Instanz noch nicht über längere Zeit erprobt. Nicht enthalten: - **Zusatzfelder** im Klassifizierungsdialog — nur die Grundfelder - **Fortschrittsanzeige** beim Upload — bei großen Dateien wirkt LibreOffice währenddessen eingefroren - **Übersetzungen** über die Oberflächentexte hinaus - **Konfliktbehandlung**, wenn dasselbe Dokument parallel anderswo geändert wurde Der letzte Punkt ist der relevanteste für den Mehrbenutzerbetrieb. Paperless kennt keine Sperren; wer zeitgleich arbeitet, erzeugt zwei Versionen statt eines Konflikts. Bei einem kleinen Kreis ist das verschmerzbar. Die Software wird ohne jegliche Gewähr oder Haftungsübernahme bereitgestellt. Die Nutzung erfolgt auf eigene Gefahr.