KOReader Plugin-Entwicklung
Die Oberfläche von KOReader basiert auf einfachem Lua (LuaJIT) mit einem kleinen Widget-Toolkit. Ein Plugin ist ein Verzeichnis namens <name>.koplugin/ mit einem main.lua (Code) und einer _meta.lua (Anzeigeinfo). Es gibt keinen Build-Schritt, daher ist der schnellste Weg, korrekt zu sein, die nachfolgenden bewährten Konventionen zu befolgen und mit den mitgelieferten Skripten zu überprüfen, anstatt APIs zu erraten.
Offizielle Dokumentation: https://koreader.rocks/doc/ (API-Referenz, aus dem Quellcode generiert) und der Quellcode unter https://github.com/koreader/koreader (plugins/ enthält ~40 echte Plugins; plugins/hello.koplugin ist das minimale Beispiel). Bei Unsicherheit über eine Signatur prüfen Sie die Dokumentationsseite des Moduls oder lesen Sie den Quellcode – KOReader-APIs ändern sich zwischen Releases, und die Dokumentationsseite hat Vorrang vor dem Gedächtnis.
Arbeitsablauf
- Scaffold (neues Plugin):
python3 <skill>/scripts/new_plugin.py <id> "<Vollständiger Name>" [--dir <wo>]erstellt<id>.koplugin/aus der Vorlage mit Menüeintrag, Dispatcher-Aktion, Einstellungspersistenz und Lebenszyklus-Hooks. Bearbeiten Sie von dort aus, anstatt von Grund auf zu schreiben. - Implementieren Sie mit den Mustern in dieser Datei; öffnen Sie die Referenzdateien nur, wenn Sie sie benötigen:
references/plugin-anatomy.md– Dateiaufbau, Laderregeln,is_doc_only, Initialisierungsreihenfolge, Menü-sorting_hint-Wertereferences/ui-widgets.md– InfoMessage, ConfirmBox, InputDialog, MultiInputDialog, Menu, ButtonDialog, SpinWidget, Notification mit funktionierenden Codeausschnittenreferences/events-and-lifecycle.md–onXxx-Handler, Broadcasting, Scheduling, Dispatcher, Reader- vs. Dateimanager-Kontextreferences/settings-and-storage.md– LuaSettings, G_reader_settings, dokumentbezogene Einstellungen, DataStorage-Pfadereferences/testing-and-verification.md– Emulator (./kodev run), luacheck, busted-Spezifikationen, Installation auf dem Gerät und Log-Lesenreferences/pitfalls.md– Fehler, die KOReader zum Absturz bringen oder stillschweigend nichts bewirken; überfliegen Sie diese vor dem Abschluss
- Verifizieren (immer, bevor Sie sagen, dass es funktioniert):
python3 <skill>/scripts/validate_plugin.py <pfad/zu/x.koplugin>. Es prüft Struktur,_meta.lua, Menüverdrahtung, gettext-Verwendung, LuaJIT-Syntax (luajit -bl) undluacheck, falls diese Tools vorhanden sind. Beheben Sie jeden FEHLER; lesen Sie jede WARNUNG. Sagen Sie dann ehrlich, was verifiziert wurde und was nicht – der Validator kann das Laufzeitverhalten nicht beweisen; das kann nur der Emulator oder ein Gerät (siehe Testreferenz).
Das minimal lebensfähige Plugin
-- _meta.lua
local _ = require("gettext")
return {
fullname = _("Mein Plugin"),
description = _([[Ein Satz, der in der Plugin-Verwaltung angezeigt wird.]]),
}
-- main.lua
local Dispatcher = require("dispatcher")
local InfoMessage = require("ui/widget/infomessage")
local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local _ = require("gettext")
local MyPlugin = WidgetContainer:extend{
name = "myplugin", -- eindeutige ID, entspricht dem Verzeichnis-Präfix
is_doc_only = false, -- true = nur geladen, während ein Buch geöffnet ist
}
function MyPlugin:onDispatcherRegisterActions()
Dispatcher:registerAction("myplugin_hello", {
category = "none", event = "MyPluginHello",
title = _("Mein Plugin: Hallo"), general = true,
})
end
function MyPlugin:init()
self:onDispatcherRegisterActions()
self.ui.menu:registerToMainMenu(self)
end
function MyPlugin:addToMainMenu(menu_items)
menu_items.myplugin = {
text = _("Mein Plugin"),
sorting_hint = "more_tools",
callback = function() self:onMyPluginHello() end,
}
end
function MyPlugin:onMyPluginHello()
UIManager:show(InfoMessage:new{ text = _("Hallo von meinem Plugin") })
return true -- Ereignis verarbeitet
end
return MyPlugin
Wichtige Regeln (und warum)
- Jeden benutzersichtbaren Text in
_()kapseln – KOReader wird über gettext übersetzt; nicht gekapselte Zeichenfolgen können nie lokalisiert werden. Verwenden SieT = require("ffi/util").templatefür Interpolation (T(_("Seite %1"), n)), nicht..-Verkettung, da die Wortreihenfolge sprachabhängig ist. - Ereignisse sind Methoden namens
on<EreignisName>; geben Sietruenur zurück, wenn Sie das Ereignis verarbeitet haben, andernfalls empfangen andere Widgets (einschließlich des KOReader-Kerns) es nicht mehr. - Blockieren Sie niemals den UI-Thread. Netzwerkaufrufe, große Dateiscans und
os.executefrieren ein single-threaded e‑Ink-Gerät ein. Verwenden SieNetworkMgr:runWhenOnline,UIManager:scheduleIn,Trapper:wrap(Unterprozess + Fortschritt) für langwierige Arbeiten. - Persistieren Sie mit
LuaSettings, leeren Sie bewusst. Einstellungsdateien befinden sich inDataStorage:getSettingsDir(); rufen Sie:flush()nach Änderungen auf (oder inonFlushSettings) – ein Absturz oder eine entladene Batterie würde sie sonst verlieren. - Bereinigen Sie Timer (
UIManager:unschedule(fn)) und Widgets, die Sie inonCloseWidget/onCloseDocumentgeöffnet haben; undichte geplante Callbacks wecken das Gerät ständig auf und entladen den Akku. - E‑Ink-bewusste UI: Bevorzugen Sie
InfoMessagemittimeout, vermeiden Sie Animationen, halten Sie Aktualisierungen minimal (UIManager:setDirty(widget, "ui")nur auf dem geänderten Widget). - Verwenden Sie
logger(require("logger");logger.dbg/info/warn/err) anstelle vonprint; die Ausgabe geht im Emulator auf stdout und auf dem Gerät incrash.log. - Schützen Sie optionale Abhängigkeiten (
self.ui.statistics,self.ui.document,Device:hasFrontlight()): dasselbe Plugin kann im Dateimanager oder auf Geräten ohne das entsprechende Feature geladen werden.
Rückmeldung
Geben Sie an, was Sie erstellt/geändert haben, den genauen Validator-Befehl und sein Ergebnis sowie was unverifiziert bleibt (z. B. „nicht im Emulator ausgeführt“). Wenn der Validator oder luacheck nicht verfügbar war, sagen Sie dies, anstatt eine saubere Prüfung zu suggerieren.