Desenvolvimento de plugins KOReader
A interface do KOReader é Lua pura (LuaJIT) executando em um pequeno toolkit de widgets. Um plugin é um diretório chamado <nome>.koplugin/ com um main.lua (código) e _meta.lua (informações de exibição). Não há etapa de compilação, então a maneira mais rápida de estar correto é seguir as convenções comprovadas abaixo e verificar com os scripts fornecidos, em vez de adivinhar as APIs.
Documentação oficial: https://koreader.rocks/doc/ (referência da API, gerada a partir do código-fonte) e o código-fonte em https://github.com/koreader/koreader (plugins/ tem cerca de 40 plugins reais; plugins/hello.koplugin é o mínimo). Quando estiver em dúvida sobre uma assinatura, consulte a página de documentação do módulo ou leia o código-fonte — as APIs do KOReader mudam entre versões, e o site de documentação é a autoridade sobre a memória.
Fluxo de trabalho
- Gerar esqueleto (novo plugin):
python3 <skill>/scripts/new_plugin.py <id> "<Nome Completo>" [--dir <onde>]cria<id>.koplugin/a partir do modelo com entrada de menu, ação Dispatcher, persistência de configurações e hooks de ciclo de vida configurados. Edite a partir daí em vez de escrever do zero. - Implementar usando os padrões deste arquivo; abra os arquivos de referência apenas quando necessário:
references/plugin-anatomy.md— layout de arquivos, regras do carregador,is_doc_only, ordem de inicialização, valoressorting_hintdo menureferences/ui-widgets.md— InfoMessage, ConfirmBox, InputDialog, MultiInputDialog, Menu, ButtonDialog, SpinWidget, Notification, com trechos funcionaisreferences/events-and-lifecycle.md— manipuladoresonXxx, transmissão, agendamento, Dispatcher, contexto de leitor vs gerenciador de arquivosreferences/settings-and-storage.md— LuaSettings, G_reader_settings, configurações de documento por livro, caminhos do DataStoragereferences/testing-and-verification.md— emulador (./kodev run), luacheck, especificações busted, instalação no dispositivo e leitura de logreferences/pitfalls.md— erros que travam o KOReader ou não fazem nada silenciosamente; leia antes de finalizar
- Verificar (sempre, antes de dizer que funciona):
python3 <skill>/scripts/validate_plugin.py <caminho/para/x.koplugin>. Ele verifica estrutura,_meta.lua, configuração de menu, uso de gettext, sintaxe LuaJIT (luajit -bl) eluacheckquando essas ferramentas existem. Corrija cada ERRO; leia cada AVISO. Depois, diga honestamente o que foi e o que não foi verificado — o validador não pode provar o comportamento em tempo de execução; apenas o emulador ou um dispositivo pode (consulte a referência de teste).
O plugin mínimo viável
-- _meta.lua
local _ = require("gettext")
return {
fullname = _("Meu Plugin"),
description = _([[Uma frase mostrada no Gerenciamento de 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 MyPlugin = WidgetContainer:extend{
name = "myplugin", -- id único, corresponde ao prefixo do diretório
is_doc_only = false, -- true = carregado apenas enquanto um livro está aberto
}
function MyPlugin:onDispatcherRegisterActions()
Dispatcher:registerAction("myplugin_hello", {
category = "none", event = "MyPluginHello",
title = _("Meu Plugin: olá"), general = true,
})
end
function MyPlugin:init()
self:onDispatcherRegisterActions()
self.ui.menu:registerToMainMenu(self)
end
function MyPlugin:addToMainMenu(menu_items)
menu_items.myplugin = {
text = _("Meu Plugin"),
sorting_hint = "more_tools",
callback = function() self:onMyPluginHello() end,
}
end
function MyPlugin:onMyPluginHello()
UIManager:show(InfoMessage:new{ text = _("Olá do meu plugin") })
return true -- evento consumido
end
return MyPlugin
Regras que importam (e por quê)
- Coloque toda string visível ao usuário em
_()— O KOReader é traduzido via gettext; strings não encapsuladas nunca podem ser localizadas. UseT = require("ffi/util").templatepara interpolação (T(_("Página %1"), n)), não concatenação.., porque a ordem das palavras difere por idioma. - Eventos são métodos nomeados
on<NomeDoEvento>; retornetrueapenas quando você consumiu o evento, caso contrário outros widgets (incluindo o núcleo do KOReader) param de recebê-lo. - Nunca bloqueie a thread da interface. Chamadas de rede, varreduras de arquivos grandes e
os.executecongelam um dispositivo de tinta eletrônica de thread única. UseNetworkMgr:runWhenOnline,UIManager:scheduleIn,Trapper:wrap(subprocesso + progresso) para trabalhos longos. - Persista com
LuaSettings, descarregue deliberadamente. Os arquivos de configuração ficam emDataStorage:getSettingsDir(); chame:flush()após alterações (ou emonFlushSettings) — uma falha ou descarga de bateria, caso contrário, os perde. - Limpe temporizadores (
UIManager:unschedule(fn)) e widgets que você abriu emonCloseWidget/onCloseDocument; callbacks agendados vazados continuam acordando o dispositivo e drenam a bateria. - Interface ciente de tinta eletrônica: prefira
InfoMessagecomtimeout, evite animações, mantenha as atualizações mínimas (UIManager:setDirty(widget, "ui")apenas no widget que mudou). - Use
logger(require("logger");logger.dbg/info/warn/err) em vez deprint; a saída vai para stdout no emulador ecrash.logno dispositivo. - Proteja dependências opcionais (
self.ui.statistics,self.ui.document,Device:hasFrontlight()): o mesmo plugin pode carregar no gerenciador de arquivos ou em dispositivos sem o recurso.
Relatando de volta
Informe o que você criou/alterou, o comando exato do validador e seu resultado, e o que permanece não verificado (por exemplo, "não executado no emulador"). Se o validador ou luacheck não estavam disponíveis, diga isso em vez de insinuar uma aprovação limpa.