Add Nextcloud Notes to exporter plugin (#12301)

This commit is contained in:
Nikola Kotur
2024-11-08 19:29:36 +01:00
committed by GitHub
parent 5fb9216efb
commit 7c166a294e
2 changed files with 228 additions and 1 deletions

View File

@@ -42,7 +42,8 @@ local _ = require("gettext")
-- migrate settings from old "evernote.koplugin" or from previous (monolithic) "exporter.koplugin"
local function migrateSettings()
local formats = { "flomo", "html", "joplin", "json", "memos", "my_clippings", "readwise", "text", "xmnote" }
-- these are for legacy formats. Don't add new targets here.
local formats = { "html", "joplin", "json", "readwise", "text" }
local settings = G_reader_settings:readSetting("exporter")
if not settings then
@@ -108,6 +109,7 @@ local Exporter = WidgetContainer:extend{
markdown = require("target/markdown"),
memos = require("target/memos"),
my_clippings = require("target/my_clippings"),
nextcloud = require("target/nextcloud"),
readwise = require("target/readwise"),
text = require("target/text"),
xmnote = require("target/xmnote"),

View File

@@ -0,0 +1,225 @@
local InfoMessage = require("ui/widget/infomessage")
local MultiInputDialog = require("ui/widget/multiinputdialog")
local UIManager = require("ui/uimanager")
local mime = require("mime")
local md = require("template/md")
local json = require("json")
local http = require("socket.http")
local ltn12 = require("ltn12")
local socket = require("socket")
local socketutil = require("socketutil")
local logger = require("logger")
local T = require("ffi/util").template
local _ = require("gettext")
-- nextcloud notes exporter
local NextcloudExporter = require("base"):new {
name = "nextcloud_notes",
category = _("KOReader"),
is_remote = true,
}
-- fetching all notes from Nextcloud is costly, so we keep a copy here
-- while we determine wether to update existing or create a new note
local notes_cache
local function makeRequest(url, auth, method, request_body)
local sink = {}
local request_body_json = json.encode(request_body)
local source = ltn12.source.string(request_body_json)
local request = {
url = url,
method = method,
sink = ltn12.sink.table(sink),
source = source,
headers = {
["Content-Length"] = #request_body_json,
["Content-Type"] = "application/json",
["Authorization"] = "Basic " .. auth,
["OCS-APIRequest"] = "true",
},
}
socketutil:set_timeout(socketutil.LARGE_BLOCK_TIMEOUT, socketutil.LARGE_TOTAL_TIMEOUT)
local code, headers, status = socket.skip(1, http.request(request))
socketutil:reset_timeout()
if code ~= 200 then
logger.warn("Nextcloud: HTTP response code <> 200. Response status:", status or code or "network unreachable")
logger.dbg("Response headers:", headers)
return nil, status
end
if not sink[1] then
return nil, "No response from Nextcloud"
end
local response = json.decode(sink[1])
return response
end
function NextcloudExporter:isReadyToExport()
return self.settings.host and self.settings.username and self.settings.password
end
function NextcloudExporter:getMenuTable()
local dialog_title = _("Setup Nextcloud Notes plugin")
return {
text = _("Nextcloud Notes"),
checked_func = function() return self:isEnabled() end,
sub_item_table = {
{
text = dialog_title,
keep_menu_open = true,
callback = function()
local url_dialog
url_dialog = MultiInputDialog:new {
title = dialog_title,
fields = {
{
description = _("Nextcloud URL"),
hint = "https://yournextcloud.com",
text = self.settings.host,
input_type = "string"
},
{
description = _("Username"),
hint = _("Username"),
text = self.settings.username,
input_type = "string"
},
{
description = _("App password"),
hint = _("Security -> Devices & sessions"),
text = self.settings.password,
input_type = "string"
}
},
buttons = {
{
{
text = _("Cancel"),
callback = function()
UIManager:close(url_dialog)
end
},
{
text = _("OK"),
callback = function()
local fields = url_dialog:getFields()
local host = fields[1]
local username = fields[2]
local password = fields[3]
if host ~= "" then
self.settings.host = host
self:saveSettings()
end
if username ~= "" then
self.settings.username = username
self:saveSettings()
end
if password ~= "" then
self.settings.password = password
self:saveSettings()
end
UIManager:close(url_dialog)
end
}
}
}
}
UIManager:show(url_dialog)
url_dialog:onShowKeyboard()
end
},
{
text = _("Export to Nextcloud Notes"),
checked_func = function() return self:isEnabled() end,
callback = function() self:toggleEnabled() end,
},
{
text = _("Help"),
keep_menu_open = true,
callback = function()
UIManager:show(InfoMessage:new {
text = T(_([[For Nextcloud Notes setup instructions, see %1
Markdown formatting can be configured in:
Export highlights > Choose formats and services > Markdown.]]), "https://github.com/koreader/koreader/wiki/Nextcloud-notes")
})
end
}
}
}
end
function NextcloudExporter:export(t)
if not self:isReadyToExport() then
return false
end
-- determine if markdown export is set
local plugin_settings = G_reader_settings:readSetting("exporter") or {}
local markdown_settings = plugin_settings.markdown
-- setup Nextcloud variables
local url_base = string.format("%s/index.php/apps/notes/api/v1/", self.settings.host)
local auth = mime.b64(self.settings.username .. ":" .. self.settings.password)
local note_id
local verb
local request_body
local response
local err
-- fetch existing notes from Nextcloud
local url = url_base .. "notes?category=" .. self.category
notes_cache, err = makeRequest(url, auth, "GET")
if not notes_cache then
logger.warn("Error fetching existing notes from Nextcloud", err)
return false
end
-- export each note
for _, booknotes in pairs(t) do
local note = md.prepareBookContent(booknotes, markdown_settings.formatting_options, markdown_settings.highlight_formatting)
local note_title = string.format("%s - %s", booknotes.author, booknotes.title)
-- search for existing note, and in that case use its ID for update
note_id = nil
if notes_cache then
for i, note_cached in ipairs(notes_cache) do
if note_cached.title == note_title then
note_id = note_cached.id
break
end
end
end
-- note body is the same for create and update
request_body = {
title = note_title,
content = table.concat(note, "\n"),
category = self.category,
}
-- set up create or update specific parameters
if note_id then
verb = "PUT"
url = string.format("%snotes/%s", url_base, note_id)
else
verb = "POST"
url = url_base.."notes"
end
-- save note in Nextcloud
response, err = makeRequest(url, auth, verb, request_body)
if not response then
logger.warn("Error saving note in Nextcloud", err)
return false
end
end
return true
end
return NextcloudExporter