269 lines
7.6 KiB
Lua
269 lines
7.6 KiB
Lua
--[[--
|
|
KOReader plugin adding a "Upload to slenpaste" action to the File Manager long-press menu.
|
|
|
|
@module koplugin.slenpaste
|
|
--]]--
|
|
|
|
local http = require("socket.http")
|
|
local ltn12 = require("ltn12")
|
|
local Device = require("device")
|
|
local InfoMessage = require("ui/widget/infomessage")
|
|
local QRMessage = require("ui/widget/qrmessage")
|
|
local RadioButtonWidget = require("ui/widget/radiobuttonwidget")
|
|
local ButtonDialog = require("ui/widget/buttondialog")
|
|
local Font = require("ui/font")
|
|
local TextBoxWidget = require("ui/widget/textboxwidget")
|
|
local UIManager = require("ui/uimanager")
|
|
local NetworkMgr = require("ui/network/manager")
|
|
local WidgetContainer = require("ui/widget/container/widgetcontainer")
|
|
local util = require("util")
|
|
local _ = require("gettext")
|
|
local T = require("ffi/util").template
|
|
|
|
local SlenPaste = WidgetContainer:extend{
|
|
name = "slenpaste",
|
|
is_doc_only = false,
|
|
upload_url = "https://paste.slendi.dev/",
|
|
settings_key = "slenpaste",
|
|
}
|
|
|
|
SlenPaste.expiry_options = {
|
|
{ label = _("5 minutes"), value = "5m" },
|
|
{ label = _("1 hour"), value = "1h" },
|
|
{ label = _("1 day"), value = "1d" },
|
|
{ label = _("7 days"), value = "7d" },
|
|
{ label = _("30 days"), value = "30d" },
|
|
{ label = _("Expires on first view"), value = "view" },
|
|
{ label = _("Never"), value = "0" },
|
|
}
|
|
|
|
function SlenPaste:init()
|
|
self.settings = G_reader_settings:readSetting(self.settings_key, {})
|
|
if not self.ui.document then
|
|
self:registerFileManagerActions()
|
|
end
|
|
end
|
|
|
|
function SlenPaste:registerFileManagerActions()
|
|
local file_manager = self.ui
|
|
if not file_manager or not file_manager.addFileDialogButtons then
|
|
return
|
|
end
|
|
local plugin = self
|
|
file_manager:addFileDialogButtons("slenpaste_upload", function(file, is_file)
|
|
if not is_file then
|
|
return nil
|
|
end
|
|
return {
|
|
{
|
|
text = _("Upload to Slenpaste"),
|
|
callback = function()
|
|
if file_manager.file_dialog then
|
|
UIManager:close(file_manager.file_dialog)
|
|
end
|
|
plugin:promptExpiryAndUpload(file)
|
|
end,
|
|
},
|
|
}
|
|
end)
|
|
end
|
|
|
|
function SlenPaste:getDefaultExpiry()
|
|
local remembered = self.settings and self.settings.last_expiry
|
|
if remembered then
|
|
return remembered
|
|
end
|
|
return "1d"
|
|
end
|
|
|
|
function SlenPaste:promptExpiryAndUpload(file_path)
|
|
local radio_buttons = {}
|
|
local default_expiry = self:getDefaultExpiry()
|
|
for index, option in ipairs(self.expiry_options) do
|
|
radio_buttons[index] = {
|
|
{
|
|
text = option.label,
|
|
provider = option.value,
|
|
checked = option.value == default_expiry,
|
|
},
|
|
}
|
|
end
|
|
|
|
UIManager:show(RadioButtonWidget:new{
|
|
title_text = _("Slenpaste expiry"),
|
|
ok_text = _("Upload"),
|
|
cancel_text = _("Cancel"),
|
|
radio_buttons = radio_buttons,
|
|
callback = function(widget)
|
|
local expiry = widget.provider
|
|
self.settings.last_expiry = expiry
|
|
G_reader_settings:saveSetting(self.settings_key, self.settings)
|
|
NetworkMgr:runWhenConnected(function()
|
|
self:startUpload(file_path, expiry)
|
|
end)
|
|
end,
|
|
})
|
|
end
|
|
|
|
function SlenPaste:startUpload(file_path, expiry)
|
|
local uploading_message = InfoMessage:new{
|
|
text = _("Uploading to slenpaste…"),
|
|
}
|
|
UIManager:show(uploading_message)
|
|
|
|
local ok, result = self:performUpload(file_path, expiry)
|
|
|
|
UIManager:close(uploading_message)
|
|
|
|
if ok then
|
|
self:showSuccessDialog(result)
|
|
else
|
|
self:showError(result)
|
|
end
|
|
end
|
|
|
|
function SlenPaste:performUpload(file_path, expiry)
|
|
local file, err = io.open(file_path, "rb")
|
|
if not file then
|
|
return false, err or _("Unable to open file")
|
|
end
|
|
|
|
local filename = file_path:match("([^/]+)$") or "upload"
|
|
filename = filename:gsub('["\r\n]', "_")
|
|
|
|
local boundary = "----KOReaderSlenpaste" .. tostring(os.time()) .. tostring(math.random(1000, 9999))
|
|
local CRLF = "\r\n"
|
|
|
|
local prefix = table.concat({
|
|
"--", boundary, CRLF,
|
|
"Content-Disposition: form-data; name=\"expiry\"", CRLF,
|
|
CRLF,
|
|
tostring(expiry), CRLF,
|
|
"--", boundary, CRLF,
|
|
string.format("Content-Disposition: form-data; name=\"file\"; filename=\"%s\"", filename), CRLF,
|
|
"Content-Type: application/octet-stream", CRLF,
|
|
CRLF,
|
|
})
|
|
local suffix = CRLF .. "--" .. boundary .. "--" .. CRLF
|
|
|
|
local file_size = file:seek("end")
|
|
file:seek("set", 0)
|
|
|
|
local response_body = {}
|
|
local file_closed = false
|
|
local function close_file()
|
|
if not file_closed then
|
|
file_closed = true
|
|
file:close()
|
|
end
|
|
end
|
|
local function file_source()
|
|
if file_closed then
|
|
return nil
|
|
end
|
|
local chunk = file:read(8192)
|
|
if chunk then
|
|
return chunk
|
|
end
|
|
close_file()
|
|
return nil
|
|
end
|
|
|
|
local request_body = ltn12.source.cat(
|
|
ltn12.source.string(prefix),
|
|
file_source,
|
|
ltn12.source.string(suffix)
|
|
)
|
|
|
|
local ok, status_code, headers, status_line = http.request{
|
|
url = self.upload_url,
|
|
method = "POST",
|
|
headers = {
|
|
["Content-Type"] = "multipart/form-data; boundary=" .. boundary,
|
|
["Content-Length"] = tostring(#prefix + file_size + #suffix),
|
|
},
|
|
source = request_body,
|
|
sink = ltn12.sink.table(response_body),
|
|
}
|
|
|
|
close_file()
|
|
|
|
if not ok then
|
|
return false, status_code or _("HTTP request failed")
|
|
end
|
|
|
|
local code = tonumber(status_code) or 0
|
|
if code < 200 or code >= 300 then
|
|
local body_text = util.trim(table.concat(response_body))
|
|
if body_text ~= "" then
|
|
return false, string.format("%s (%s)", status_line or "HTTP error", body_text)
|
|
end
|
|
return false, status_line or string.format("HTTP status %d", code)
|
|
end
|
|
|
|
local url = util.trim(table.concat(response_body))
|
|
if url == "" then
|
|
return false, _("Empty response from server")
|
|
end
|
|
|
|
return true, url
|
|
end
|
|
|
|
function SlenPaste:showSuccessDialog(url)
|
|
local buttons
|
|
local dialog
|
|
buttons = {
|
|
{
|
|
{
|
|
text = _("Copy link"),
|
|
enabled = Device:hasClipboard(),
|
|
callback = function()
|
|
Device.input.setClipboardText(url)
|
|
UIManager:show(InfoMessage:new{
|
|
text = _("Link copied to clipboard"),
|
|
})
|
|
end,
|
|
},
|
|
{
|
|
text = _("Show QR code"),
|
|
callback = function()
|
|
UIManager:show(QRMessage:new{
|
|
text = url,
|
|
width = Device.screen:getWidth(),
|
|
height = Device.screen:getHeight(),
|
|
})
|
|
end,
|
|
},
|
|
},
|
|
{
|
|
{
|
|
text = _("Close"),
|
|
callback = function()
|
|
UIManager:close(dialog)
|
|
end,
|
|
},
|
|
},
|
|
}
|
|
|
|
dialog = ButtonDialog:new{
|
|
title = _("Slenpaste upload complete"),
|
|
buttons = buttons,
|
|
}
|
|
|
|
dialog:addWidget(TextBoxWidget:new{
|
|
text = url,
|
|
width = dialog:getAddedWidgetAvailableWidth(),
|
|
face = Font:getFace("infofont"),
|
|
})
|
|
|
|
UIManager:show(dialog)
|
|
end
|
|
|
|
function SlenPaste:showError(message)
|
|
UIManager:show(InfoMessage:new{
|
|
text = T(_("Upload failed:\n%s"), message),
|
|
})
|
|
end
|
|
|
|
return SlenPaste
|