Développement de plugins KOReader
L'interface de KOReader est écrite en Lua pur (LuaJIT) avec une petite boîte à outils de widgets. Un plugin est un répertoire nommé <nom>.koplugin/ contenant un main.lua (code) et un _meta.lua (informations d'affichage). Il n'y a pas d'étape de compilation, donc le moyen le plus rapide d'être correct est de suivre les conventions éprouvées ci-dessous et de vérifier avec les scripts fournis plutôt que de deviner les API.
Documentation officielle : https://koreader.rocks/doc/ (référence API, générée à partir du code source) et le code source sur https://github.com/koreader/koreader (plugins/ contient ~40 plugins réels ; plugins/hello.koplugin est le plus minimal). En cas de doute sur une signature, consultez la page de documentation du module ou lisez le code source — les API de KOReader changent entre les versions, et le site de documentation fait autorité.
Flux de travail
- Générer (nouveau plugin) :
python3 <skill>/scripts/new_plugin.py <id> "<Nom complet>" [--dir <chemin>]crée<id>.koplugin/à partir du modèle avec entrée de menu, action Dispatcher, persistance des paramètres et hooks de cycle de vie déjà câblés. Modifiez ensuite plutôt que d'écrire à partir de zéro. - Implémenter en utilisant les modèles de ce fichier ; ouvrez les fichiers de référence uniquement quand vous en avez besoin :
references/plugin-anatomy.md— disposition des fichiers, règles du chargeur,is_doc_only, ordre d'initialisation,sorting_hintdu menureferences/ui-widgets.md— InfoMessage, ConfirmBox, InputDialog, MultiInputDialog, Menu, ButtonDialog, SpinWidget, Notification, avec extraits fonctionnelsreferences/events-and-lifecycle.md— gestionnairesonXxx, diffusion, planification, Dispatcher, contexte lecteur vs gestionnaire de fichiersreferences/settings-and-storage.md— LuaSettings, G_reader_settings, paramètres de document par livre, chemins DataStoragereferences/testing-and-verification.md— émulateur (./kodev run), luacheck, tests busted, installation sur appareil et lecture des logsreferences/pitfalls.md— erreurs qui plantent KOReader ou ne font rien silencieusement ; lisez avant de terminer
- Vérifier (toujours, avant de dire que ça marche) :
python3 <skill>/scripts/validate_plugin.py <chemin/vers/x.koplugin>. Il vérifie la structure,_meta.lua, le câblage du menu, l'utilisation de gettext, la syntaxe LuaJIT (luajit -bl) etluacheckquand ces outils existent. Corrigez chaque ERREUR ; lisez chaque ATTENTION. Dites ensuite honnêtement ce qui a et n'a pas été vérifié — le validateur ne peut pas prouver le comportement à l'exécution ; seul l'émulateur ou un appareil peut le faire (voir la référence de test).
Le plugin minimal viable
-- _meta.lua
local _ = require("gettext")
return {
fullname = _("Mon plugin"),
description = _("[[Une phrase affichée dans la gestion des plugins.]]"),
}
-- 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 MonPlugin = WidgetContainer:extend{
name = "monplugin", -- identifiant unique, correspond au préfixe du répertoire
is_doc_only = false, -- true = chargé uniquement quand un livre est ouvert
}
function MonPlugin:onDispatcherRegisterActions()
Dispatcher:registerAction("monplugin_bonjour", {
category = "none", event = "MonPluginBonjour",
title = _("Mon plugin : bonjour"), general = true,
})
end
function MonPlugin:init()
self:onDispatcherRegisterActions()
self.ui.menu:registerToMainMenu(self)
end
function MonPlugin:addToMainMenu(menu_items)
menu_items.monplugin = {
text = _("Mon plugin"),
sorting_hint = "more_tools",
callback = function() self:onMonPluginBonjour() end,
}
end
function MonPlugin:onMonPluginBonjour()
UIManager:show(InfoMessage:new{ text = _("Bonjour depuis mon plugin") })
return true -- événement consommé
end
return MonPlugin
Règles importantes (et pourquoi)
- Encadrez chaque chaîne visible par l'utilisateur dans
_()— KOReader est traduit via gettext ; les chaînes non encadrées ne peuvent jamais être localisées. UtilisezT = require("ffi/util").templatepour l'interpolation (T(_("Page %1"), n)), pas la concaténation.., car l'ordre des mots diffère selon la langue. - Les événements sont des méthodes nommées
on<NomEvenement>; ne retourneztrueque si vous avez consommé l'événement, sinon les autres widgets (y compris le cœur de KOReader) cessent de le recevoir. - Ne bloquez jamais le thread UI. Les appels réseau, les grands scans de fichiers et
os.executebloquent un appareil à encre électronique monothread. UtilisezNetworkMgr:runWhenOnline,UIManager:scheduleIn,Trapper:wrap(sous-processus + progression) pour les tâches longues. - Persistez avec
LuaSettings, videz délibérément. Les fichiers de paramètres se trouvent dansDataStorage:getSettingsDir(); appelez:flush()après les modifications (ou dansonFlushSettings) — un plantage ou une batterie déchargée les perdrait autrement. - Nettoyez les minuteurs (
UIManager:unschedule(fn)) et les widgets que vous avez ouverts dansonCloseWidget/onCloseDocument; les rappels programmés qui fuient réveillent l'appareil et épuisent la batterie. - UI adaptée à l'encre électronique : préférez
InfoMessageavectimeout, évitez les animations, minimisez les rafraîchissements (UIManager:setDirty(widget, "ui")uniquement sur le widget qui a changé). - Utilisez
logger(require("logger");logger.dbg/info/warn/err) plutôt queprint; la sortie va dans stdout dans l'émulateur et danscrash.logsur l'appareil. - Protégez les dépendances optionnelles (
self.ui.statistics,self.ui.document,Device:hasFrontlight()) : le même plugin peut se charger dans le gestionnaire de fichiers ou sur des appareils sans cette fonctionnalité.
Rapport
Indiquez ce que vous avez créé/modifié, la commande exacte du validateur et son résultat, et ce qui reste non vérifié (par ex., « pas exécuté dans l'émulateur »). Si le validateur ou luacheck n'était pas disponible, dites-le plutôt que de sous-entendre une réussite parfaite.