aknahin/koplugin-skill

Développez, examinez, déboguez et vérifiez des plugins KOReader (*.koplugin, Lua) pour liseuses (Kobo, Kindle, PocketBook, Android) et l'émulateur de bureau.

Qu'est-ce que koplugin-skill ?

Cette compétence assiste les développeurs dans la création, la révision et le débogage de plugins pour KOReader, un logiciel de lecture open-source pour liseuses électroniques. Elle fournit des outils pour générer la structure d'un plugin (scaffolding), valider sa syntaxe et sa conformité avec les conventions de KOReader via des scripts Python, et vérifier la qualité du code avec luacheck. La compétence s'appuie sur une documentation de référence détaillée (anatomie du plugin, widgets UI, événements, stockage, tests) et insiste sur les bonnes pratiques : utiliser gettext pour la traduction, ne pas bloquer le thread UI, persister les paramètres avec LuaSettings. Elle permet également de signaler honnêtement ce qui a et n'a pas été vérifié, car la validation automatisée ne remplace pas un test sur l'émulateur ou un appareil réel.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/aknahin/koplugin-skill/tree/HEAD/skills/koplugin-skill

Installed? Explore more Développement et programmation skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

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

  1. 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.
  2. 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_hint du menu
    • references/ui-widgets.md — InfoMessage, ConfirmBox, InputDialog, MultiInputDialog, Menu, ButtonDialog, SpinWidget, Notification, avec extraits fonctionnels
    • references/events-and-lifecycle.md — gestionnaires onXxx, diffusion, planification, Dispatcher, contexte lecteur vs gestionnaire de fichiers
    • references/settings-and-storage.md — LuaSettings, G_reader_settings, paramètres de document par livre, chemins DataStorage
    • references/testing-and-verification.md — émulateur (./kodev run), luacheck, tests busted, installation sur appareil et lecture des logs
    • references/pitfalls.md — erreurs qui plantent KOReader ou ne font rien silencieusement ; lisez avant de terminer
  3. 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) et luacheck quand 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. Utilisez T = require("ffi/util").template pour 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 retournez true que 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.execute bloquent un appareil à encre électronique monothread. Utilisez NetworkMgr: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 dans DataStorage:getSettingsDir() ; appelez :flush() après les modifications (ou dans onFlushSettings) — un plantage ou une batterie déchargée les perdrait autrement.
  • Nettoyez les minuteurs (UIManager:unschedule(fn)) et les widgets que vous avez ouverts dans onCloseWidget/onCloseDocument ; les rappels programmés qui fuient réveillent l'appareil et épuisent la batterie.
  • UI adaptée à l'encre électronique : préférez InfoMessage avec timeout, é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 que print ; la sortie va dans stdout dans l'émulateur et dans crash.log sur 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.

Skills associés