So I want to build a plugin (or two) for koreader. Specifically, I'd like to be able to download files using ssh / scp. I think this is the laziest way to share documents to your ereader since if you're running koreader, you probably have a server running ssh you can use to stash files.
I also want to make an S3 plugin. There's lots of S3 compatible hosts (eg: min.io or backblaze) and storing your books there seems pretty easy and cheap.
I thought I'd blog about the process of making a plugin as I go.
Here's the most barebones basic plugin you can write
```lua
local InfoMessage = require("ui/widget/infomessage")
local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local _ = require("gettext")
local SCP = WidgetContainer:extend{
name = "scp",
is_doc_only = false,
}
function SCP:init()
self.ui.menu:registerToMainMenu(self)
end
function SCP:addToMainMenu(menu_items)
menu_items.hello_world = {
text = _("SCP"),
callback = function()
UIManager:show(InfoMessage:new{
text = _("yooo"),
})
end,
}
end
return SCP
```

Clicking on the item shows a model that says "yooo".
I'm not sure what `is_doc_only = False` means, maybe that makes the plugin only active in documents rather than the main menu?
I'm also not sure how `self.ui.menu:registerToMainMenu` ends up calling `addToMainMenu` but it makes sense that it does.
The `_` is for translations I think. Basically anything a user can see should use it instead of a direct string.
The other plugins I looked at call `Dispatcher::registerAction`. I think this is to register global "actions" which can be trigger by shortcuts or gestures, but I'm going to leave it out for now until I understand it better.
Next up, let's make a more complex menu:
```lua
function SCP:addToMainMenu(menu_items)
menu_items.hello_world = {
text = _("SCP"),
sub_item_table = {
{
text = _("First"),
keep_menu_open = true,
callback = function()
UIManager:show(InfoMessage:new{
text = _("yooo"),
})
end,
}
}
}
end
```
Pretty straightforward.
Next up, let's do some settings:
```lua
local DataStorage = require("datastorage")
local InfoMessage = require("ui/widget/infomessage")
local LuaSettings = require("frontend/luasettings")
local MultiInputDialog = require("ui/widget/multiinputdialog")
local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local logger = require("logger")
local _ = require("gettext")
local SCP = WidgetContainer:extend{
name = "scp",
is_doc_only = false,
}
function SCP:init()
self.ui.menu:registerToMainMenu(self)
self.scp_settings = self.readSettings()
self.host = self.scp_settings.data.scp.host
self.port = self.scp_settings.data.scp.port
self.user = self.scp_settings.data.scp.username
self.password = self.scp_settings.data.scp.password
self.path = self.scp_settings.data.scp.path
end
function SCP:addToMainMenu(menu_items)
menu_items.hello_world = {
text = _("SCP"),
sub_item_table = {
{
text = _("Settings"),
keep_menu_open = true,
callback = function()
self:editServerSettings()
end,
}
}
}
end
function SCP:readSettings()
local scp_settings = LuaSettings:open(DataStorage:getSettingsDir().."/scp.lua")
scp_settings:readSetting("scp", {})
return scp_settings
end
function SCP:saveSettings()
local tempsettings = {
host = self.host,
port = self.port,
user = self.user,
password = self.password,
path = self.path,
}
self.scp_settings:saveSetting("scp", tempsettings)
self.scp_settings:flush()
end
function SCP:editServerSettings()
local text_info = _("Some useful help text")
self.settings_dialog = MultiInputDialog:new {
title = _("SCP settings"),
fields = {
{
text = self.host,
hint = _("SSH Host")
},
{
text = self.port,
input_type = "number",
hint = _("SSH Port")
},
{
text = self.user,
hint = _("User")
},
{
text = self.password,
text_type = "password",
hint = _("Password")
},
{
text = self.path,
hint = _("Path")
},
},
buttons = {
{
{
text = _("Cancel"),
id = "close",
callback = function()
UIManager:close(self.settings_dialog)
end
},
{
text = _("Info"),
callback = function()
UIManager:show(InfoMessage:new{ text = text_info })
end
},
{
text = _("Apply"),
callback = function()
local myfields = self.settings_dialog:getFields()
self.host = myfields[1]
self.port = myfields[2]
self.user = myfields[3]
self.password = myfields[4]
self.path = myfields[5]
self:saveSettings()
UIManager:close(self.settings_dialog)
end
},
},
},
}
UIManager:show(self.settings_dialog)
self.settings_dialog:onShowKeyboard()
end
return SCP
```
There's some stuff here about the settings API that seems a bit redundant