Community编程与开发github.com

aknahin/koplugin-skill

构建、审查、调试和验证KOReader插件(*.koplugin,Lua)用于电子阅读器(Kobo、Kindle、PocketBook、Android)和桌面模拟器。

koplugin-skill 是什么?

该技能专门用于开发KOReader插件,覆盖了从创建、实现到验证的完整工作流程。针对KOReader基于LuaJIT的小部件工具包环境,它提供了清晰的脚手架工具、丰富的参考文档和静态验证器,确保插件结构正确、语法无误并遵循项目约定。支持的平台包括Kobo、Kindle、PocketBook、Android以及桌面模拟器。技能内置了两个关键脚本:new_plugin.py用于从模板快速生成插件目录,自动配置菜单项、Dispatcher动作和设置持久化;validate_plugin.py则对插件进行结构、元数据、菜单连接、gettext使用和LuaJIT语法的静态检查。参考文档涵盖插件解剖结构、UI部件使用、事件与生命周期管理、设置存储、测试验证以及常见陷阱,帮助开发者避免常见错误并高效构建高质量的KOReader插件。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/aknahin/koplugin-skill/tree/HEAD/skills/koplugin-skill

Installed? Explore more 编程与开发 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

KOReader 插件开发

KOReader 的前端是纯 Lua (LuaJIT),运行在一个小型小部件工具包上。插件是一个名为 <名称>.koplugin/ 的目录,其中包含 main.lua(代码)和 _meta.lua(显示信息)。没有构建步骤,因此最快的方法是遵循以下经过验证的约定,并使用捆绑脚本进行验证,而不是猜测 API。

官方文档:https://koreader.rocks/doc/(API 参考,从源代码生成)以及 https://github.com/koreader/koreader 上的源代码(plugins/ 包含约 40 个实际插件;plugins/hello.koplugin 是最简化的插件)。当不确定签名时,请查看模块的文档页面或阅读源代码——KOReader API 在不同版本之间会发生变化,文档站点是比记忆更权威的来源。

工作流程

  1. 脚手架(新插件):python3 <skill>/scripts/new_plugin.py <id> "<全名>" [--dir <路径>] 从模板创建 <id>.koplugin/,包含菜单项、Dispatcher 动作、设置持久化和生命周期钩子。在此基础上编辑,而不是从头编写。
  2. 实现:使用此文件中的模式;仅当需要时才打开参考文件:
    • references/plugin-anatomy.md——文件布局、加载器规则、is_doc_only、初始化顺序、菜单 sorting_hint 值
    • references/ui-widgets.md——InfoMessage、ConfirmBox、InputDialog、MultiInputDialog、Menu、ButtonDialog、SpinWidget、Notification,附带可工作代码片段
    • references/events-and-lifecycle.md——onXxx 处理程序、广播、调度、Dispatcher、阅读器与文件管理器上下文
    • references/settings-and-storage.md——LuaSettings、G_reader_settings、每本书的文档设置、DataStorage 路径
    • references/testing-and-verification.md——模拟器(./kodev run)、luacheck、busted 规范、设备安装和日志读取
    • references/pitfalls.md——导致 KOReader 崩溃或静默无反应的错误;完成前通读一遍
  3. 验证(始终在确认生效之前进行):python3 <skill>/scripts/validate_plugin.py <路径/到/x.koplugin>。它会检查结构、_meta.lua、菜单连接、gettext 使用、LuaJIT 语法(luajit -bl)以及存在这些工具时的 luacheck。修复每个 ERROR;阅读每个 WARN。然后诚实地说出哪些内容已验证,哪些未验证——验证器无法证明运行时行为;只有模拟器或设备才能做到(请参阅测试参考)。

最小可行插件

-- _meta.lua
local _ = require("gettext")
return {
    fullname = _("我的插件"),
    description = _([[在插件管理中显示的一句话。]]),
}
-- 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",        -- 唯一ID,与目录前缀匹配
    is_doc_only = false,      -- true = 仅在打开书籍时加载
}

function MyPlugin:onDispatcherRegisterActions()
    Dispatcher:registerAction("myplugin_hello", {
        category = "none", event = "MyPluginHello",
        title = _("我的插件:你好"), general = true,
    })
end

function MyPlugin:init()
    self:onDispatcherRegisterActions()
    self.ui.menu:registerToMainMenu(self)
end

function MyPlugin:addToMainMenu(menu_items)
    menu_items.myplugin = {
        text = _("我的插件"),
        sorting_hint = "more_tools",
        callback = function() self:onMyPluginHello() end,
    }
end

function MyPlugin:onMyPluginHello()
    UIManager:show(InfoMessage:new{ text = _("来自我的插件的问候") })
    return true  -- 事件已消费
end

return MyPlugin

重要的规则(及原因)

  • 将所有用户可见的字符串包裹在 _() 中——KOReader 通过 gettext 进行翻译;未包裹的字符串永远无法本地化。使用 T = require("ffi/util").template 进行插值(T(_("第 %1 页"), n)),而不是 .. 连接,因为不同语言的词序不同。
  • 事件是名为 on<事件名称> 的方法;只有当你消费了事件时才返回 true,否则其他小部件(包括核心 KOReader)将停止接收该事件。
  • 切勿阻塞 UI 线程。 网络调用、大文件扫描和 os.execute 会冻结单线程的电纸书设备。对于长时间工作,请使用 NetworkMgr:runWhenOnline、UIManager:scheduleIn、Trapper:wrap(子进程+进度条)。
  • 使用 LuaSettings 持久化,并有意刷新。 设置文件位于 DataStorage:getSettingsDir() 中;在更改后(或在 onFlushSettings 中)调用 :flush()——否则崩溃或电池耗尽会导致数据丢失。
  • 清理 定时器(UIManager:unschedule(fn))和在 onCloseWidget/onCloseDocument 中打开的部件;泄漏的调度回调会持续唤醒设备并消耗电池。
  • 电纸书感知的 UI:优先使用带 timeout 的 InfoMessage,避免动画,保持刷新最少(仅在更改的部件上使用 UIManager:setDirty(widget, "ui"))。
  • 使用 logger(require("logger");logger.dbg/info/warn/err)而不是 print;输出会发送到模拟器中的 stdout 和设备上的 crash.log。
  • 保护可选依赖项(self.ui.statistics、self.ui.document、Device:hasFrontlight()):同一个插件可能会在文件管理器或没有该功能的设备上加载。

反馈

说明你创建/更改的内容、准确的验证器命令及其结果,以及什么内容仍未验证(例如“未在模拟器中运行”)。如果验证器或 luacheck 不可用,请说明,而不是暗示检测通过。

相关技能