Loading bookloresync.koplugin/booklore_api_client.lua +196 −0 Original line number Diff line number Diff line Loading @@ -918,4 +918,200 @@ function APIClient:submitRating(book_id, rating, username, password) end end --[[-- Submit a highlight (annotation) to Booklore. Endpoint: POST /api/v1/annotations Required body fields: bookId, cfi, text Optional: color (hex), style, note, chapterTitle @param book_id number @param cfi string epubcfi(pos0,pos1) @param text string highlighted text @param opts table { color, style, note, chapter_title } @param username string @param password string @return boolean success @return number|string server annotation id on success, error message on failure --]] function APIClient:submitHighlight(book_id, cfi, text, opts, username, password) opts = opts or {} if not book_id or not cfi or not text or text == "" then return false, "Missing required highlight fields" end local token_ok, token = self:getOrRefreshBearerToken(username, password, false) if not token_ok then return false, token or "Failed to obtain auth token" end local body_tbl = { bookId = tonumber(book_id), cfi = cfi, text = text, } if opts.color and opts.color ~= "" then body_tbl.color = opts.color end if opts.style and opts.style ~= "" then body_tbl.style = opts.style end if opts.note and opts.note ~= "" then body_tbl.note = opts.note end if opts.chapter_title and opts.chapter_title ~= "" then body_tbl.chapterTitle = opts.chapter_title end local body = json.encode(body_tbl) local headers = { ["Authorization"] = "Bearer " .. token, ["Content-Type"] = "application/json", } local success, code, response = self:request("POST", "/api/v1/annotations", body, headers) if not success and (code == 401 or code == 403) then if self.db then self.db:deleteBearerToken(username) end local ref_ok, new_token = self:getOrRefreshBearerToken(username, password, true) if ref_ok then headers["Authorization"] = "Bearer " .. new_token success, code, response = self:request("POST", "/api/v1/annotations", body, headers) else return false, new_token or "Authentication failed after refresh" end end if success and code == 200 then local server_id = type(response) == "table" and response.id or nil self:logInfo("BookloreSync API: Highlight submitted, server id:", server_id) return true, server_id else local err = (type(response) == "string" and response ~= "") and response or ("HTTP " .. tostring(code)) self:logWarn("BookloreSync API: Failed to submit highlight:", err) return false, err end end --[[-- Submit an in-book note (v2) to Booklore. Endpoint: POST /api/v2/book-notes Required body fields: bookId, cfi, noteContent Optional: selectedText, color (hex), chapterTitle @param book_id number @param cfi string epubcfi(pos0,pos1) @param note_content string the note text @param opts table { selected_text, color, chapter_title } @param username string @param password string @return boolean success @return number|string server note id on success, error message on failure --]] function APIClient:submitInBookNote(book_id, cfi, note_content, opts, username, password) opts = opts or {} if not book_id or not cfi or not note_content or note_content == "" then return false, "Missing required in-book note fields" end local token_ok, token = self:getOrRefreshBearerToken(username, password, false) if not token_ok then return false, token or "Failed to obtain auth token" end local body_tbl = { bookId = tonumber(book_id), cfi = cfi, noteContent = note_content, } if opts.selected_text and opts.selected_text ~= "" then body_tbl.selectedText = opts.selected_text end if opts.color and opts.color ~= "" then body_tbl.color = opts.color end if opts.chapter_title and opts.chapter_title ~= "" then body_tbl.chapterTitle = opts.chapter_title end local body = json.encode(body_tbl) local headers = { ["Authorization"] = "Bearer " .. token, ["Content-Type"] = "application/json", } local success, code, response = self:request("POST", "/api/v2/book-notes", body, headers) if not success and (code == 401 or code == 403) then if self.db then self.db:deleteBearerToken(username) end local ref_ok, new_token = self:getOrRefreshBearerToken(username, password, true) if ref_ok then headers["Authorization"] = "Bearer " .. new_token success, code, response = self:request("POST", "/api/v2/book-notes", body, headers) else return false, new_token or "Authentication failed after refresh" end end if success and code == 200 then local server_id = type(response) == "table" and response.id or nil self:logInfo("BookloreSync API: In-book note submitted, server id:", server_id) return true, server_id else local err = (type(response) == "string" and response ~= "") and response or ("HTTP " .. tostring(code)) self:logWarn("BookloreSync API: Failed to submit in-book note:", err) return false, err end end --[[-- Submit a Booklore (web-UI) note to Booklore. Endpoint: POST /api/v1/book-notes Required body fields: bookId, content Optional: title @param book_id number @param content string the note text @param title string|nil chapter title used as note title @param username string @param password string @return boolean success @return number|string server note id on success, error message on failure --]] function APIClient:submitBookloreNote(book_id, content, title, username, password) if not book_id or not content or content == "" then return false, "Missing required Booklore note fields" end local token_ok, token = self:getOrRefreshBearerToken(username, password, false) if not token_ok then return false, token or "Failed to obtain auth token" end local body_tbl = { bookId = tonumber(book_id), content = content, } if title and title ~= "" then body_tbl.title = title end local body = json.encode(body_tbl) local headers = { ["Authorization"] = "Bearer " .. token, ["Content-Type"] = "application/json", } local success, code, response = self:request("POST", "/api/v1/book-notes", body, headers) if not success and (code == 401 or code == 403) then if self.db then self.db:deleteBearerToken(username) end local ref_ok, new_token = self:getOrRefreshBearerToken(username, password, true) if ref_ok then headers["Authorization"] = "Bearer " .. new_token success, code, response = self:request("POST", "/api/v1/book-notes", body, headers) else return false, new_token or "Authentication failed after refresh" end end if success and code == 200 then local server_id = type(response) == "table" and response.id or nil self:logInfo("BookloreSync API: Booklore note submitted, server id:", server_id) return true, server_id else local err = (type(response) == "string" and response ~= "") and response or ("HTTP " .. tostring(code)) self:logWarn("BookloreSync API: Failed to submit Booklore note:", err) return false, err end end return APIClient Loading
bookloresync.koplugin/booklore_api_client.lua +196 −0 Original line number Diff line number Diff line Loading @@ -918,4 +918,200 @@ function APIClient:submitRating(book_id, rating, username, password) end end --[[-- Submit a highlight (annotation) to Booklore. Endpoint: POST /api/v1/annotations Required body fields: bookId, cfi, text Optional: color (hex), style, note, chapterTitle @param book_id number @param cfi string epubcfi(pos0,pos1) @param text string highlighted text @param opts table { color, style, note, chapter_title } @param username string @param password string @return boolean success @return number|string server annotation id on success, error message on failure --]] function APIClient:submitHighlight(book_id, cfi, text, opts, username, password) opts = opts or {} if not book_id or not cfi or not text or text == "" then return false, "Missing required highlight fields" end local token_ok, token = self:getOrRefreshBearerToken(username, password, false) if not token_ok then return false, token or "Failed to obtain auth token" end local body_tbl = { bookId = tonumber(book_id), cfi = cfi, text = text, } if opts.color and opts.color ~= "" then body_tbl.color = opts.color end if opts.style and opts.style ~= "" then body_tbl.style = opts.style end if opts.note and opts.note ~= "" then body_tbl.note = opts.note end if opts.chapter_title and opts.chapter_title ~= "" then body_tbl.chapterTitle = opts.chapter_title end local body = json.encode(body_tbl) local headers = { ["Authorization"] = "Bearer " .. token, ["Content-Type"] = "application/json", } local success, code, response = self:request("POST", "/api/v1/annotations", body, headers) if not success and (code == 401 or code == 403) then if self.db then self.db:deleteBearerToken(username) end local ref_ok, new_token = self:getOrRefreshBearerToken(username, password, true) if ref_ok then headers["Authorization"] = "Bearer " .. new_token success, code, response = self:request("POST", "/api/v1/annotations", body, headers) else return false, new_token or "Authentication failed after refresh" end end if success and code == 200 then local server_id = type(response) == "table" and response.id or nil self:logInfo("BookloreSync API: Highlight submitted, server id:", server_id) return true, server_id else local err = (type(response) == "string" and response ~= "") and response or ("HTTP " .. tostring(code)) self:logWarn("BookloreSync API: Failed to submit highlight:", err) return false, err end end --[[-- Submit an in-book note (v2) to Booklore. Endpoint: POST /api/v2/book-notes Required body fields: bookId, cfi, noteContent Optional: selectedText, color (hex), chapterTitle @param book_id number @param cfi string epubcfi(pos0,pos1) @param note_content string the note text @param opts table { selected_text, color, chapter_title } @param username string @param password string @return boolean success @return number|string server note id on success, error message on failure --]] function APIClient:submitInBookNote(book_id, cfi, note_content, opts, username, password) opts = opts or {} if not book_id or not cfi or not note_content or note_content == "" then return false, "Missing required in-book note fields" end local token_ok, token = self:getOrRefreshBearerToken(username, password, false) if not token_ok then return false, token or "Failed to obtain auth token" end local body_tbl = { bookId = tonumber(book_id), cfi = cfi, noteContent = note_content, } if opts.selected_text and opts.selected_text ~= "" then body_tbl.selectedText = opts.selected_text end if opts.color and opts.color ~= "" then body_tbl.color = opts.color end if opts.chapter_title and opts.chapter_title ~= "" then body_tbl.chapterTitle = opts.chapter_title end local body = json.encode(body_tbl) local headers = { ["Authorization"] = "Bearer " .. token, ["Content-Type"] = "application/json", } local success, code, response = self:request("POST", "/api/v2/book-notes", body, headers) if not success and (code == 401 or code == 403) then if self.db then self.db:deleteBearerToken(username) end local ref_ok, new_token = self:getOrRefreshBearerToken(username, password, true) if ref_ok then headers["Authorization"] = "Bearer " .. new_token success, code, response = self:request("POST", "/api/v2/book-notes", body, headers) else return false, new_token or "Authentication failed after refresh" end end if success and code == 200 then local server_id = type(response) == "table" and response.id or nil self:logInfo("BookloreSync API: In-book note submitted, server id:", server_id) return true, server_id else local err = (type(response) == "string" and response ~= "") and response or ("HTTP " .. tostring(code)) self:logWarn("BookloreSync API: Failed to submit in-book note:", err) return false, err end end --[[-- Submit a Booklore (web-UI) note to Booklore. Endpoint: POST /api/v1/book-notes Required body fields: bookId, content Optional: title @param book_id number @param content string the note text @param title string|nil chapter title used as note title @param username string @param password string @return boolean success @return number|string server note id on success, error message on failure --]] function APIClient:submitBookloreNote(book_id, content, title, username, password) if not book_id or not content or content == "" then return false, "Missing required Booklore note fields" end local token_ok, token = self:getOrRefreshBearerToken(username, password, false) if not token_ok then return false, token or "Failed to obtain auth token" end local body_tbl = { bookId = tonumber(book_id), content = content, } if title and title ~= "" then body_tbl.title = title end local body = json.encode(body_tbl) local headers = { ["Authorization"] = "Bearer " .. token, ["Content-Type"] = "application/json", } local success, code, response = self:request("POST", "/api/v1/book-notes", body, headers) if not success and (code == 401 or code == 403) then if self.db then self.db:deleteBearerToken(username) end local ref_ok, new_token = self:getOrRefreshBearerToken(username, password, true) if ref_ok then headers["Authorization"] = "Bearer " .. new_token success, code, response = self:request("POST", "/api/v1/book-notes", body, headers) else return false, new_token or "Authentication failed after refresh" end end if success and code == 200 then local server_id = type(response) == "table" and response.id or nil self:logInfo("BookloreSync API: Booklore note submitted, server id:", server_id) return true, server_id else local err = (type(response) == "string" and response ~= "") and response or ("HTTP " .. tostring(code)) self:logWarn("BookloreSync API: Failed to submit Booklore note:", err) return false, err end end return APIClient