Files
thunderbird-paperless-conne…/README.md

365 lines
14 KiB
Markdown

# Paperless-Connector für Thunderbird / Betterbird
Legt Anhänge und ganze E-Mails in einem Paperless-ngx-Archiv ab und fügt
Dokumente aus dem Archiv als Anhang oder Freigabelink in Entwürfe ein.
Schwestererweiterung zur LibreOffice-Fassung; dieselben Erkenntnisse zur
Paperless-Schnittstelle, aber vollständig eigener Code — Thunderbird kennt
WebExtensions, LibreOffice UNO.
---
## Funktionen
| Ort | Befehl | Wirkung |
|---|---|---|
| Kontextmenü am Anhang | Anhang in Paperless ablegen | ausgewählte Anhänge als Dokument |
| Kontextmenü in der Nachrichtenliste | E-Mail in Paperless ablegen | als PDF mit eingebettetem Original, wahlweise als `.eml` |
| Beides | Ablauf | Dateiauswahl, dann je Datei Ziel und Klassifizierung |
| Schaltfläche im Nachrichtenfenster | dasselbe für die angezeigte Nachricht | |
| Schaltfläche im Verfassenfenster | Aus Paperless einfügen | als Anhang oder als Freigabelink |
Beim Ablegen stehen dieselben zwei Ziele zur Wahl wie in der
LibreOffice-Fassung: ein **neues Dokument** oder eine **neue Version eines
bestehenden**. Der zweite Fall deckt die Lücke, die Dokumentenverwaltungen
gern offenlassen:
> Die Rechnung kommt als PDF im Anhang. Später entsteht die bearbeitbare
> Fassung. Beides gehört zusammen — aber das PDF war zuerst da.
---
## Was beim Ablegen passiert
**Anhänge einzeln wählbar.** Kleine Bilder und Signaturen sind vorab
abgewählt — sie gehören zur Darstellung der Nachricht, nicht zum Inhalt.
Wer sie alle mit archiviert, hat nach einem Jahr hunderte Logos im Bestand.
Die Schwelle liegt bei 8 KB und ist einstellbar.
**Die ganze Nachricht als PDF.** Betreff als Überschrift, darunter ein
grau hinterlegter Kopfblock mit Konto, Absender, Empfängern, Datum und
Anlagen, dann der Nachrichtentext. Zitierte Abschnitte stehen grau und
eingerückt — bei langen Verläufen sieht man so auf einen Blick, was neu
ist.
Eingebettet werden die vollständige Originalnachricht als `.eml` sowie
alle Anhänge im Original. Es geht also nichts verloren, auch wenn die
Darstellung nicht originalgetreu ist.
Wer lieber die Rohfassung will, stellt das in den Einstellungen um oder
wählt im ersten Fenster je Datei „Original ablegen".
**Titel aus dem Betreff.** Präfixe wie `AW:` oder `Fwd:` werden entfernt —
sie tragen im Archiv nichts bei und stören beim Sortieren.
**Zwei Schritte, dann je Datei einer.**
```
Fenster 1 Welche Dateien sollen abgelegt werden?
[x] Rechnung.pdf (•) Original ( ) PDF aufbereiten
[x] Anschreiben.html (•) Original ( ) PDF aufbereiten
Fenster 2 Je Datei: Ziel und Klassifizierung
┌─ Ziel ──────────┐ ┌─ Klassifizierung ─┐
│ ( ) Neu │ │ Titel [.......] │
│ (•) An bestehen-│ │ Erstellt[....] │
│ des anhängen│ │ Korresp.[.......] │
│ [suche] │ │ Typ [.......] │
│ Trefferliste │ │ Tags [suche] │
└─────────────────┘ └───────────────────┘
```
Die Trennung hat einen praktischen Grund: **Ziel und Klassifizierung sind
je Datei verschieden.** Ein einzelner Dialog für alle würde die Angaben
der ersten Datei auf alle übertragen — bei einer Rechnung samt
Lastschriftmandat wäre das falsch.
**Klassifizierung des Ziels wird übernommen.** Wählt man ein bestehendes
Dokument aus der Trefferliste, stehen Titel, Datum, Korrespondent, Typ und
Tags schon im rechten Kasten und lassen sich anpassen. Eine neue Version
gehört inhaltlich zum selben Vorgang; die Angaben neu einzutippen wäre
Arbeit für nichts, und ein abweichender Korrespondent brächte die Version
vom Original weg.
**Ein Aufruf je Datei.** Bewusst nicht alle in einem Durchgang: Manifest V3
beendet die Ereignisseite, sobald sie untätig scheint, und ein Aufruf über
zehn Anhänge lief regelmäßig in `Receiving end does not exist`. Nebeneffekt:
Der Fortschritt ist sichtbar, und scheitert eine Datei, sind die übrigen
trotzdem abgelegt.
### Warum kein originalgetreues Abbild
HTML-Nachrichten werden als Text gesetzt. Eine WebExtension hat keine
Darstellungsmaschine, die HTML nach PDF überträgt, und **Thunderbird
bietet Erweiterungen keine Druckfunktion an** — die tabs-Schnittstelle
kennt in Manifest V3 kein `saveAsPDF`, anders als in Firefox. Geprüft an
der API-Dokumentation für Thunderbird 156.
Für die Umwandlung von HTML nach Text übernimmt
`messengerUtilities.convertToPlainText()` die Arbeit. Das ist dieselbe
Funktion, die beim Antworten in Nur-Text greift, und liefert deutlich
bessere Ergebnisse als eine selbstgebaute Entschlackung.
### Der PDF-Erzeuger
Selbst geschrieben, in `lib/pdf.js`. Der Grund ist Vorsatz: Eine
Erweiterung soll nichts nachladen, und eine mitgelieferte Bibliothek wie
`pdf-lib` wäre rund ein Megabyte für einen Bruchteil ihrer Fähigkeiten.
Gebraucht werden hier nur Text und eingebettete Dateien.
Erzeugt wird PDF 1.7 mit den eingebauten Schriften Helvetica und
Helvetica-Bold. Die sind in jedem Betrachter vorhanden und müssen nicht
eingebettet werden.
**Ausdrücklich kein PDF/A.** Dafür wären eingebettete Schriftschnitte, ein
Farbprofil und XMP-Metadaten nötig — und vor allem eine Prüfung mit
veraPDF. Ein Dokument als PDF/A zu bezeichnen, ohne es geprüft zu haben,
wäre eine leere Behauptung.
Einstellbar sind Papierformat (A4, A5, Letter, Legal), die vier Ränder in
Millimetern und die Grundschriftgröße (Vorgabe 11 Punkt).
### Das Konto im Kopfblock
Bei Nachrichten an Blindkopie-Empfänger geht aus An und Kopie nicht
hervor, welches Konto sie empfangen hat. Im Archiv ist das später die
entscheidende Angabe.
Ermittelt wird sie auf zwei Wegen: über die accounts-Schnittstelle, die
allerdings die Berechtigung `accountsRead` braucht, und ersatzweise über
die Zustellkopfzeilen `Delivered-To`, `X-Original-To` und Verwandte.
Letztere setzt der zustellende Server, und sie sind nicht immer vorhanden
— Postfix und Dovecot setzen sie zuverlässig, Microsoft-Umgebungen oft
nicht. Findet sich nichts, fehlt die Zeile im PDF ganz.
---
## Einfügen in Entwürfe
**Als Anhang.** Das Original wird geladen (`?original=true`), nicht die
Archivfassung — bei ZUGFeRD bleibt damit die eingebettete XML erhalten.
Die Version ist wählbar, mit Datum und Format.
**Als Freigabelink.** Zeitlich begrenzt, ohne Anmeldung abrufbar, auf
Wunsch mit einem Tag am Dokument. Eingefügt werden Name (fett), Adresse
und Gültigkeit:
```
Einladung Generalversammlung
https://dms.example.org/share/ZYGwdw…
Link gültig bis 11.09.2026
```
Die Adresse steht sichtbar, nicht nur als Verweisziel — bei einem Link,
der ohne Anmeldung Zugriff gewährt, sollte der Empfänger sehen, wohin er
führt.
**Der Tag ist abschaltbar.** Ein leeres Tag-Feld bedeutet: keine
Kennzeichnung setzen. Wer es dauerhaft nicht will, leert das Feld in den
Einstellungen.
> **Einschränkung:** Die Compose-Schnittstelle kennt kein Einfügen an der
> Schreibmarke. Der Nachrichtentext muss vollständig gelesen und
> zurückgeschrieben werden. Der Link landet deshalb am Ende des Textes,
> nicht an der Cursorposition. Bei HTML-Nachrichten wird er vor dem
> schließenden `</body>` eingesetzt.
Wann was: Der Anhang für alles, was auch in fünf Jahren noch lesbar sein
muss, wenn der Link längst abgelaufen ist. Der Link für große Dateien und
für Empfänger, die ohnehin Zugriff auf das Archiv haben sollen.
---
## Einrichten
1. Add-ons-Verwaltung → **Paperless-Connector** → Einstellungen
2. Basisadresse eintragen, etwa `https://dms.example.org` — ohne
abschließenden Schrägstrich
3. API-Token eintragen, erzeugt im **Benutzerprofil** von Paperless
4. Speichern — dabei wird das Zugriffsrecht für genau diese Adresse
erbeten und die Verbindung sofort geprüft
Das Zugriffsrecht steht bewusst **nicht** pauschal im Manifest. Eine
Erweiterung, die von Haus aus jede Adresse erreichen darf, ist eine
unnötig große Angriffsfläche.
---
## Bauen und installieren
```bash
./build.sh
```
Zum Ausprobieren ohne Installation:
```
Extras → Entwicklerwerkzeuge → Debuggen von Add-ons
→ Temporäres Add-on laden → manifest.json wählen
```
Das ist beim Entwickeln der bessere Weg: Änderungen sind nach einem Klick
auf „Neu laden" wirksam, und die Konsole zeigt Fehler im Klartext.
Dauerhaft:
```
Add-ons-Verwaltung → Zahnrad → Add-on aus Datei installieren
→ paperless-connector-<version>.xpi
```
---
## Bekannte Einschränkung: Zugriff über IP-Adresse
Der Zugang über `https://` und einen Rechnernamen funktioniert. Der
direkte Weg über eine LAN-Adresse wie `http://192.168.1.5:8000` schlägt
dagegen mit `NetworkError` fehl, auch wenn das Zugriffsrecht erteilt
wurde.
**Die Ursache ist nicht abschließend geklärt.** Eine Vermutung: Neuere
Gecko-Versionen schränken Anfragen aus erweiterten Kontexten in private
Adressbereiche ein. Falls das zutrifft, lässt es sich von der Erweiterung
aus nicht umgehen.
Praktisch ist das verschmerzbar — der Weg über den Reverse Proxy ist
ohnehin der bessere, weil er verschlüsselt ist. Wer es untersuchen möchte,
findet in `about:networking` und in der Netzwerkanalyse der
Add-on-Debugansicht Ansatzpunkte.
## Manifest-Version
Gebaut nach **Manifest V3** mit den Thunderbird-Besonderheiten:
- `background.scripts` als Ereignisseite, **kein** Service Worker —
Thunderbird unterstützt keine
- `compose_action` und `message_display_action` neben `action`
- `optional_host_permissions` statt fester Adressen
Sollte die eingesetzte Version noch MV2 verlangen, sind vier Änderungen
nötig: `manifest_version` auf 2, `optional_host_permissions` nach
`optional_permissions`, `host_permissions` entfällt, und
`background.type: module` bleibt wie es ist.
---
## Sprachen
Deutsch, Englisch und Französisch über die eingebaute Verwaltung
(`_locales/`). Die Sprache richtet sich nach der Oberfläche von
Thunderbird. Eine weitere Sprache ergänzt man, indem man ein Verzeichnis
mit `messages.json` anlegt — die Schlüssel stehen in
`_locales/de/messages.json`.
---
## Sicherheit
Ausführlich in [SICHERHEIT.md](SICHERHEIT.md). Die Lage ist entspannter als
bei der LibreOffice-Fassung, weil hier **keine fremden Dokumente geöffnet**
werden — der heikelste Punkt entfällt. Die wichtigsten Maßnahmen:
- Zugriffsrecht nur für die eingetragene Adresse, zur Laufzeit erbeten
- Nur die sechs tatsächlich benötigten Berechtigungen — `compose.send` war
im ersten Entwurf enthalten, ohne je benutzt zu werden, und ist entfernt
- Nur `http` und `https` als Basisadresse
- Dateinamen aus Anhängen werden bereinigt — sie stammen vom Absender
- Anhangdaten werden erst beim Hochladen geladen, nicht beim Öffnen des
Dialogs
- Freigabelinks und Dokumentnamen werden beim Einfügen HTML-maskiert
- Aufträge zwischen Dialog und Ereignisseite werden nach 30 Minuten
verworfen
---
## Aufbau
```
manifest.json
background.js Menüs, Schaltflächen, Vermittlung
lib/
client.js REST-Client (fetch, FormData)
settings.js Einstellungen, Dateinamen, Hilfsmittel
pdf.js PDF-Erzeuger ohne Fremdbibliothek
mailpdf.js Aufbereitung einer Nachricht
ui/
store.html/.js Schritt 1: welche Dateien
schritt.html/.js Schritt 2: Ziel und Klassifizierung je Datei
insert.html/.js Einfügen als Anhang oder Link
options.html/.js Verbindung einrichten
gemeinsam.js/.css geteilte Hilfsmittel
_locales/{de,en,fr}/
icons/
```
Die Dialoge sprechen **nicht** selbst mit Paperless, sondern lassen die
Ereignisseite arbeiten. So liegen Token und Verbindungslogik an einer
Stelle, und die Dialoge bleiben reine Oberfläche.
Damit die Ereignisseite währenddessen nicht beendet wird, hält jeder
Dialog eine offene Verbindung dorthin und sendet alle 15 Sekunden ein
Lebenszeichen. Manifest V3 räumt sie sonst mitten im Vorgang ab —
Wartezeiten zählen dort nicht als Ereignis.
---
### Eine Stolperstelle bei der Gestaltung
Die Dialoge sind auf feste Fensterhöhe mit blätternden Listen ausgelegt.
Drei Dinge mussten dafür stimmen, und jedes einzelne führte für sich zum
Überlauf:
| | |
|---|---|
| `height: 100vh` am Body | mit `min-height` wächst die Seite über das Fenster hinaus |
| kein `fieldset` als Flex-Behälter | dessen innerer anonymer Kasten löst `height: 100%` nicht auf — deshalb `<section class="kasten">` |
| kein `flex-wrap` bei den Spalten | mit Umbruch bemisst sich die Zeilenhöhe nach dem Inhalt, nicht nach dem Behälter |
## Vor einer Veröffentlichung
| Datei | Was |
|---|---|
| `manifest.json` | `browser_specific_settings.gecko.id`, `author`, `homepage_url` |
| `LICENSE` | Rechteinhaber |
Die Kennung sollte einer Domain folgen, die dir gehört. Aktuell steht dort
`paperless@example.org`.
### Anforderungen der Prüfung auf addons.thunderbird.net
Aus der Ablehnung von 1.16.0 übernommen, seit 1.16.1 umgesetzt:
- **Kein async-Listener an `runtime.onMessage`.** Ein async-Listener
gibt immer ein Promise zurück und beansprucht damit jede Nachricht. Der
Listener prüft deshalb synchron Absender und Befehl und gibt nur für
bekannte Befehle das Promise zurück, sonst `false`. Neue Befehle kommen
in die Tabelle `BEFEHLE` in `background.js`.
- **Keine ungenutzten Dateien im Paket.** `build.sh` packt nur die in
`DATEIEN` aufgeführten Dateien und weist auf alles andere im Quellbaum
hin. `make_icons.py` erzeugt weiterhin alle Symbole samt SVG — im Paket
landen nur die sieben, die Manifest und Code verwenden.
- **Beschreibung mit Bedienungsanleitung und Bildschirmfotos** auf der
Add-on-Seite, siehe `ATN-BESCHREIBUNG.md`.
- Vor dem Hochladen den Linter laufen lassen:
```bash
git clone https://github.com/thunderbird/webext-linter
cd webext-linter && npm install
node verify.js /pfad/zu/paperless-connector-<version>.xpi
```
---
## Lizenz
MIT, siehe [LICENSE](LICENSE).
## Haftung
Die Erweiterung schreibt in ein Dokumentenarchiv. Sie wurde gegen eine
Paperless-Instanz entwickelt, aber nicht formal getestet. Die MIT-Lizenz
schließt jede Gewährleistung aus.
Vor dem ersten produktiven Einsatz: eine Testnachricht ablegen und in
Paperless nachsehen, ob Titel, Datum und Anhänge stimmen.