Files
koreader-mirror/plugins/exporter.koplugin/target/xmnote.lua
2025-11-18 08:38:36 +02:00

204 lines
7.0 KiB
Lua

local BD = require("ui/bidi")
local DataStorage = require("datastorage")
local DocSettings = require("docsettings")
local InputDialog = require("ui/widget/inputdialog")
local InfoMessage = require("ui/widget/infomessage")
local SQ3 = require("lua-ljsqlite3/init")
local UIManager = require("ui/uimanager")
local datetime = require("datetime")
local ffiUtil = require("ffi/util")
local logger = require("logger")
local util = require("util")
local _ = require("gettext")
local T = ffiUtil.template
local db_location = DataStorage:getSettingsDir() .. "/statistics.sqlite3"
-- xmnote exporter
local XMNoteExporter = require("base"):new {
name = "xmnote",
is_remote = true,
server_port = 8080
}
function XMNoteExporter:getMenuTable()
return {
text = _("XMNote"),
checked_func = function() return self:isEnabled() end,
sub_item_table = {
{
text = _("Set XMNote IP"),
keep_menu_open = true,
callback = function()
local url_dialog
url_dialog = InputDialog:new {
title = _("Set XMNote IP"),
input = self.settings.ip,
buttons = {
{
{
text = _("Cancel"),
callback = function()
UIManager:close(url_dialog)
end
},
{
text = _("Set IP"),
callback = function()
local ip = url_dialog:getInputText()
self.settings.ip = ip
self:saveSettings()
UIManager:close(url_dialog)
end
}
}
}
}
UIManager:show(url_dialog)
url_dialog:onShowKeyboard()
end
} ,
{
text = _("Export to XMNote"),
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(_([[Before starting the export process, please make sure that your mobile and KOReader are connected to the same local network. Open XMNote and go to "My" - "Import Highlights" - "Import via API". At the bottom of the interface, you will find the IP address of your mobile device. Enter this IP address into KOReader to complete the configuration.]])
, BD.dirpath(DataStorage:getDataDir()))
})
end
}
}
}
end
function XMNoteExporter:getBookReadingDurationsByDay(title, md5)
if util.fileExists(db_location) then
local conn = SQ3.open(db_location)
local sql_query_book_id = [[SELECT id FROM book WHERE title = "%s" and md5 = "%s" LIMIT 1]]
local sql_query_durations = [[
SELECT date(start_time, 'unixepoch', 'localtime') AS date,
max(page) AS last_page,
sum(duration) AS total_duration,
min(start_time) AS first_start_time
FROM page_stat
WHERE id_book = %d
GROUP BY Date(start_time, 'unixepoch', 'localtime')
ORDER BY date DESC;
]]
local result_book_id = conn:exec(string.format(sql_query_book_id, title, md5))
if not (result_book_id and result_book_id[1] and result_book_id[1][1]) then
return {}
end
local book_id = tonumber(result_book_id[1][1])
local result_durations = conn:exec(string.format(sql_query_durations, book_id))
conn:close()
if not result_durations then
return {}
end
local durations = {}
for i = 1, #result_durations.date do
local entry = {
date = tonumber(result_durations[4][i]) * 1000,
durationSeconds = tonumber(result_durations[3][i]),
position = tonumber(result_durations[2][i]),
}
table.insert(durations, entry)
end
return durations
else
return {}
end
end
function XMNoteExporter:createRequestBody(booknotes)
local doc_settings = DocSettings:open(booknotes.file)
local summary = doc_settings:readSetting("summary") or {}
local md5 = doc_settings:readSetting("partial_md5_checksum")
local reading_status_map = {
reading = 2,
complete = 3,
abandoned = 5,
}
local reading_status_changed_date
if summary.modified and summary.modified ~= "" then
reading_status_changed_date = datetime.stringToSeconds(summary.modified)
else
reading_status_changed_date = 0
end
local book = {
title = booknotes.title or "",
author = booknotes.author or "",
type = 1,
locationUnit = 1,
readingStatus = reading_status_map[summary.status] or reading_status_map.reading,
readingStatusChangedDate = reading_status_changed_date,
source = "KOReader"
}
local entries = {}
for _, chapter in ipairs(booknotes) do
for _, clipping in ipairs(chapter) do
local entry = {
text = clipping.text,
note = clipping.note or "",
chapter = clipping.chapter,
time = clipping.time
}
local page = tonumber(clipping.page)
if page ~= nil then
entry.page = page
end
table.insert(entries, entry)
end
end
book.entries = entries
book.fuzzyReadingDurations = self:getBookReadingDurationsByDay(book.title, md5)
return book
end
function XMNoteExporter:createHighlights(booknotes)
local body = self:createRequestBody(booknotes)
local url = "http://".. self.settings.ip .. ":" .. self.server_port .. "/send"
local result, err = self:makeJsonRequest(url, "POST", body)
if not result then
logger.warn("error creating highlights", err)
return false
end
logger.dbg("createHighlights result", result)
return true
end
function XMNoteExporter:isReadyToExport()
if self.settings.ip then return true end
return false
end
function XMNoteExporter:export(t)
if not self:isReadyToExport() then return false end
for _, booknotes in ipairs(t) do
if booknotes.file then
local ok = self:createHighlights(booknotes)
if not ok then return false end
end
end
return true
end
return XMNoteExporter