From 2599c39d429da5fb60e9038272a45884eec7a9a2 Mon Sep 17 00:00:00 2001
From: robert00s
Date: Fri, 18 Nov 2016 18:09:30 +0100
Subject: [PATCH] GoodReads Plugin (#2346)
GoodReads Plugin
---
.../goodreads.koplugin/doublekeyvaluepage.lua | 352 ++++++++++++++++++
plugins/goodreads.koplugin/goodreaderbook.lua | 259 +++++++++++++
plugins/goodreads.koplugin/goodreadsapi.lua | 212 +++++++++++
plugins/goodreads.koplugin/main.lua | 224 +++++++++++
resources/goodreadsnophoto.png | Bin 0 -> 599 bytes
5 files changed, 1047 insertions(+)
create mode 100644 plugins/goodreads.koplugin/doublekeyvaluepage.lua
create mode 100644 plugins/goodreads.koplugin/goodreaderbook.lua
create mode 100644 plugins/goodreads.koplugin/goodreadsapi.lua
create mode 100644 plugins/goodreads.koplugin/main.lua
create mode 100644 resources/goodreadsnophoto.png
diff --git a/plugins/goodreads.koplugin/doublekeyvaluepage.lua b/plugins/goodreads.koplugin/doublekeyvaluepage.lua
new file mode 100644
index 000000000..1d920eeae
--- /dev/null
+++ b/plugins/goodreads.koplugin/doublekeyvaluepage.lua
@@ -0,0 +1,352 @@
+local InputContainer = require("ui/widget/container/inputcontainer")
+local FrameContainer = require("ui/widget/container/framecontainer")
+local VerticalGroup = require("ui/widget/verticalgroup")
+local VerticalSpan = require("ui/widget/verticalspan")
+local OverlapGroup = require("ui/widget/overlapgroup")
+local LeftContainer = require("ui/widget/container/topcontainer")
+local HorizontalSpan = require("ui/widget/horizontalspan")
+local LineWidget = require("ui/widget/linewidget")
+local Blitbuffer = require("ffi/blitbuffer")
+local CloseButton = require("ui/widget/closebutton")
+local UIManager = require("ui/uimanager")
+local TextWidget = require("ui/widget/textwidget")
+local GestureRange = require("ui/gesturerange")
+local RenderText = require("ui/rendertext")
+local Geom = require("ui/geometry")
+local Font = require("ui/font")
+local Device = require("device")
+local Screen = Device.screen
+local GoodReadersApi = require("goodreadsapi")
+local LuaSettings = require("luasettings")
+local DataStorage = require("datastorage")
+local _ = require("gettext")
+local InfoMessage = require("ui/widget/infomessage")
+
+local DoubleKeyValueTitle = VerticalGroup:new{
+ kv_page = nil,
+ title = "",
+ tface = Font:getFace("tfont"),
+ align = "left",
+}
+
+function DoubleKeyValueTitle:init()
+ self.close_button = CloseButton:new{ window = self }
+ local btn_width = self.close_button:getSize().w
+ local title_txt_width = RenderText:sizeUtf8Text(
+ 0, self.width, self.tface, self.title).x
+ local show_title_txt
+ if self.width < (title_txt_width + btn_width) then
+ show_title_txt = RenderText:truncateTextByWidth(
+ self.title, self.tface, self.width - btn_width)
+ else
+ show_title_txt = self.title
+ end
+ -- title and close button
+ table.insert(self, OverlapGroup:new{
+ dimen = { w = self.width },
+ TextWidget:new{
+ text = show_title_txt,
+ face = self.tface,
+ },
+ self.close_button,
+ })
+ -- page count and separation line
+ self.page_cnt = FrameContainer:new{
+ padding = 4,
+ margin = 0,
+ bordersize = 0,
+ background = Blitbuffer.COLOR_WHITE,
+ -- overlap offset x will be updated in setPageCount method
+ overlap_offset = {0, -15},
+ TextWidget:new{
+ text = "", -- page count
+ fgcolor = Blitbuffer.COLOR_GREY,
+ face = Font:getFace("ffont", 20),
+ },
+ }
+ self.title_bottom = OverlapGroup:new{
+ dimen = { w = self.width, h = Screen:scaleBySize(2) },
+ LineWidget:new{
+ dimen = Geom:new{ w = self.width, h = Screen:scaleBySize(2) },
+ background = Blitbuffer.COLOR_GREY,
+ style = "solid",
+ },
+ self.page_cnt,
+ }
+ table.insert(self, self.title_bottom)
+ table.insert(self, VerticalSpan:new{ width = Screen:scaleBySize(5) })
+end
+
+function DoubleKeyValueTitle:setPageCount(curr, total)
+ if total == 1 then
+ -- remove page count if there is only one page
+ table.remove(self.title_bottom, 2)
+ return
+ end
+ self.page_cnt[1]:setText(curr .. "/" .. total)
+ self.page_cnt.overlap_offset[1] = (self.width - self.page_cnt:getSize().w - 10)
+ self.title_bottom[2] = self.page_cnt
+end
+
+function DoubleKeyValueTitle:onClose()
+ self.kv_page:onClose()
+ return true
+end
+
+local DoubleKeyValueItem = InputContainer:new{
+ key = nil,
+ value = nil,
+ cface_up = Font:getFace("cfont", 22),
+ cface_down = Font:getFace("cfont", 18),
+ width = nil,
+ height = nil,
+ align = "left",
+}
+
+function DoubleKeyValueItem:init()
+ self.dimen = Geom:new{align = "left", w = self.width, h = self.height}
+ if self.callback and Device:isTouchDevice() then
+ self.ges_events.Tap = {
+ GestureRange:new{
+ ges = "tap",
+ range = self.dimen,
+ }
+ }
+ end
+ local key_w = RenderText:sizeUtf8Text(0, self.width, self.cface_down, self.key).x
+ local value_w = RenderText:sizeUtf8Text(0, self.width, self.cface_up, self.value).x
+ if key_w > self.width then
+ self.show_key = RenderText:truncateTextByWidth(self.key, self.cface_down, self.width)
+ else
+ self.show_key = self.key
+ end
+ if value_w > self.width then
+ self.show_value = RenderText:truncateTextByWidth(self.value, self.cface_up, self.width)
+ else
+ self.show_value = self.value
+ end
+ local h = self.dimen.h / 2
+ local w = self.dimen.w
+ self[1] = FrameContainer:new{
+ padding = 10,
+ bordersize = 0,
+ VerticalGroup:new{
+ dimen = Geom:new{ h = h, w = w },
+ padding = 10,
+ LeftContainer:new{
+ padding = 10,
+ dimen = Geom:new{ h = h, w = w },
+ TextWidget:new{
+ text = self.show_value,
+ padding = 10,
+ face = self.cface_up,
+ }
+ },
+ LeftContainer:new{
+ padding = 10,
+ dimen = Geom:new{ h = h / 5 , w = w },
+ HorizontalSpan:new{ width = Screen:scaleBySize(15), height = 3 }
+ },
+ LeftContainer:new{
+ padding = 10,
+ dimen = Geom:new{ h = h, w = w },
+ TextWidget:new{
+ text = self.show_key,
+ padding = 10,
+ face = self.cface_down,
+ }
+ }
+ }
+ }
+end
+
+function DoubleKeyValueItem:onTap()
+ local info = InfoMessage:new{text = _("Please wait...")}
+ UIManager:show(info)
+ UIManager:forceRePaint()
+ self.callback()
+ UIManager:close(info)
+ return true
+end
+
+local DoubleKeyValuePage = InputContainer:new{
+ title = "",
+ width = nil,
+ height = nil,
+ show_page = 1,
+ text_input = "",
+ pages = 1,
+ goodreadersKey = "",
+}
+
+function DoubleKeyValuePage:readGRSettings()
+ self.gr_settings = LuaSettings:open(DataStorage:getSettingsDir().."/goodreadssettings.lua")
+ return self.gr_settings
+end
+
+function DoubleKeyValuePage:saveGRSettings(setting)
+ if not self.gr_settings then self:readGRSettings() end
+ self.gr_settings:saveSetting("goodreads", setting)
+ self.gr_settings:flush()
+end
+
+function DoubleKeyValuePage:init()
+ self.screen_width = Screen:getSize().w
+ self.screen_height = Screen:getSize().h
+ local gr_sett = self:readGRSettings().data
+ if gr_sett.goodreads then
+ self.goodreadersKey = gr_sett.goodreads.key
+ self.goodreadersSecret = gr_sett.goodreads.secret
+ end
+ self.kv_pairs = GoodReadersApi:showData(self.text_input, self.search_type, 1, self.goodreadersKey)
+ self.total_res = GoodReadersApi:getTotalResults()
+ if self.total_res == nil then
+ self.total_res = 0
+ end
+ self.total_res = tonumber(self.total_res)
+ if self.kv_pairs == nil then
+ self.kv_pairs = {}
+ end
+ self.dimen = Geom:new{
+ w = self.width or self.screen_width,
+ h = self.height or self.screen_height,
+ }
+ if Device:isTouchDevice() then
+ self.ges_events.Swipe = {
+ GestureRange:new{
+ ges = "swipe",
+ range = self.dimen,
+ }
+ }
+ end
+ local padding = Screen:scaleBySize(10)
+ self.item_width = self.dimen.w - 2 * padding
+ self.item_height = Screen:scaleBySize(45)
+ -- setup title bar
+ self.title_bar = DoubleKeyValueTitle:new{
+ title = self.title,
+ width = self.item_width,
+ height = self.item_height,
+ kv_page = self,
+ }
+ -- setup main content
+ self.item_margin = self.item_height / 4
+ local line_height = self.item_height + 2 * self.item_margin
+ local content_height = self.dimen.h - self.title_bar:getSize().h
+ self.max_loaded_pages = 1
+ self.items_per_page = math.floor(content_height / line_height)
+ self.pages = math.ceil(self.total_res / self.items_per_page)
+ self.main_content = VerticalGroup:new{}
+ self:_populateItems()
+ -- assemble page
+ self[1] = FrameContainer:new{
+ height = self.dimen.h,
+ padding = padding,
+ bordersize = 0,
+ background = Blitbuffer.COLOR_WHITE,
+ VerticalGroup:new{
+ self.title_bar,
+ self.main_content,
+ },
+ }
+end
+
+function DoubleKeyValuePage:nextPage()
+ local new_page = math.min(self.show_page + 1, self.pages)
+ if (new_page * self.items_per_page > #self.kv_pairs) and (self.max_loaded_pages < new_page)
+ and #self.kv_pairs < self.total_res then
+ local api_page = math.floor(new_page * self.items_per_page / 20 ) + 1
+ -- load new portion of data
+ local new_pair = GoodReadersApi:showData(self.text_input, self.search_type, api_page, self.goodreadersKey )
+ if new_pair == nil then return end
+ for _, v in pairs(new_pair) do
+ table.insert(self.kv_pairs, v)
+ end
+ self.refresh = true
+ end
+ if new_page > self.show_page then
+ if self.max_loaded_pages == self.show_page then
+ self.max_loaded_pages = self.max_loaded_pages + 1
+ end
+ self.show_page = new_page
+ self:_populateItems()
+ end
+end
+
+function DoubleKeyValuePage:prevPage()
+ local new_page = math.max(self.show_page - 1, 1)
+ if new_page < self.show_page then
+ self.show_page = new_page
+ self:_populateItems()
+ end
+end
+
+-- make sure self.item_margin and self.item_height are set before calling this
+function DoubleKeyValuePage:_populateItems()
+ self.main_content:clear()
+ local idx_offset = (self.show_page - 1) * self.items_per_page
+ for idx = 1, self.items_per_page do
+ local entry = self.kv_pairs[idx_offset + idx]
+ if entry == nil then break end
+ table.insert(self.main_content,
+ VerticalSpan:new{ align = "left", width = self.item_margin })
+ if type(entry) == "table" then
+ table.insert(
+ self.main_content,
+ DoubleKeyValueItem:new{
+ height = self.item_height,
+ width = self.item_width,
+ key = entry[1],
+ value = entry[2],
+ align = "left",
+ callback = entry.callback,
+ }
+ )
+ elseif type(entry) == "string" then
+ local c = string.sub(entry, 1, 1)
+ if c == "-" then
+ table.insert(self.main_content, LineWidget:new{
+ background = Blitbuffer.COLOR_LIGHT_GREY,
+ dimen = Geom:new{
+ w = self.item_width,
+ h = Screen:scaleBySize(2)
+ },
+ style = "solid",
+ })
+ end
+ end
+ table.insert(self.main_content,
+ VerticalSpan:new{ width = self.item_margin })
+ end
+ self.title_bar:setPageCount(self.show_page, self.pages)
+ UIManager:setDirty(self, function()
+ return "ui", self.dimen
+ end)
+end
+
+function DoubleKeyValuePage:onSwipe(arg, ges_ev)
+ if ges_ev.direction == "west" then
+ local new_page = math.min(self.show_page + 1, self.pages)
+ if (new_page * self.items_per_page > #self.kv_pairs) and (self.max_loaded_pages < new_page)
+ and #self.kv_pairs < self.total_res then
+ local info = InfoMessage:new{text = _("Please wait...")}
+ UIManager:show(info)
+ UIManager:forceRePaint()
+ self:nextPage()
+ UIManager:close(info)
+ else
+ self:nextPage()
+ end
+ return true
+ elseif ges_ev.direction == "east" then
+ self:prevPage()
+ return true
+ end
+end
+
+function DoubleKeyValuePage:onClose()
+ UIManager:close(self)
+ return true
+end
+
+return DoubleKeyValuePage
diff --git a/plugins/goodreads.koplugin/goodreaderbook.lua b/plugins/goodreads.koplugin/goodreaderbook.lua
new file mode 100644
index 000000000..6d2871825
--- /dev/null
+++ b/plugins/goodreads.koplugin/goodreaderbook.lua
@@ -0,0 +1,259 @@
+local InputContainer = require("ui/widget/container/inputcontainer")
+local FrameContainer = require("ui/widget/container/framecontainer")
+local CenterContainer = require("ui/widget/container/centercontainer")
+local LeftContainer = require("ui/widget/container/leftcontainer")
+local HorizontalGroup = require("ui/widget/horizontalgroup")
+local OverlapGroup = require("ui/widget/overlapgroup")
+local VerticalGroup = require("ui/widget/verticalgroup")
+local HorizontalSpan = require("ui/widget/horizontalspan")
+local VerticalSpan = require("ui/widget/verticalspan")
+local LineWidget = require("ui/widget/linewidget")
+local TextWidget = require("ui/widget/textwidget")
+local ScrollTextWidget = require("ui/widget/scrolltextwidget")
+local ImageWidget = require("ui/widget/imagewidget")
+local TextBoxWidget = require("ui/widget/textboxwidget")
+local CloseButton = require("ui/widget/closebutton")
+local UIManager = require("ui/uimanager")
+local Geom = require("ui/geometry")
+local Blitbuffer = require("ffi/blitbuffer")
+local Screen = require("device").screen
+local Font = require("ui/font")
+local _ = require("gettext")
+local T = require("ffi/util").template
+local Pic = require("ffi/pic")
+
+local GoodReaderBook = InputContainer:new{
+ padding = Screen:scaleBySize(15),
+}
+
+function GoodReaderBook:init()
+ self.small_font_face = Font:getFace("ffont", 16)
+ self.medium_font_face = Font:getFace("ffont", 18)
+ self.large_font_face = Font:getFace("ffont", 22)
+ self.screen_width = Screen:getSize().w
+ self.screen_height = Screen:getSize().h
+ UIManager:setDirty(self, function()
+ return "ui", self.dimen
+ end)
+ self[1] = FrameContainer:new{
+ width = self.screen_width,
+ height = self.screen_height,
+ background = Blitbuffer.COLOR_WHITE,
+ bordersize = 0,
+ padding = 0,
+ self:getStatusContent(self.screen_width),
+ }
+end
+
+function GoodReaderBook:getStatusContent(width)
+ return VerticalGroup:new{
+ align = "left",
+ OverlapGroup:new{
+ dimen = Geom:new{ w = width, h = Screen:scaleBySize(30) },
+ CloseButton:new{ window = self },
+ },
+ self:genHeader(_("Book info")),
+ self:genBookInfoGroup(),
+ self:genHeader(_("Review")),
+ self:bookReview(),
+ }
+end
+
+function GoodReaderBook:genHeader(title)
+ local header_title = TextWidget:new{
+ text = title,
+ face = self.medium_font_face,
+ fgcolor = Blitbuffer.gray(0.4),
+ }
+ local padding_span = HorizontalSpan:new{ width = self.padding}
+ local line_width = (self.screen_width - header_title:getSize().w) / 2 - self.padding * 2
+ local line_container = LeftContainer:new{
+ dimen = Geom:new{ w = line_width, h = self.screen_height / 25 },
+ LineWidget:new{
+ background = Blitbuffer.gray(0.2),
+ dimen = Geom:new{
+ w = line_width,
+ h = 2,
+ }
+ }
+ }
+
+ return VerticalGroup:new{
+ VerticalSpan:new{ width = Screen:scaleBySize(5) },
+ HorizontalGroup:new{
+ align = "center",
+ padding_span,
+ line_container,
+ padding_span,
+ header_title,
+ padding_span,
+ line_container,
+ padding_span,
+ },
+ VerticalSpan:new{ width = Screen:scaleBySize(5) },
+ }
+end
+
+function GoodReaderBook:genBookInfoGroup()
+ local split_span_width = self.screen_width * 0.05
+ local img_width, img_height
+ if Screen:getScreenMode() == "landscape" then
+ img_width = Screen:scaleBySize(132)
+ img_height = Screen:scaleBySize(184)
+ else
+ img_width = Screen:scaleBySize(132 * 1.5)
+ img_height = Screen:scaleBySize(184 * 1.5)
+ end
+ local height = img_height
+ local width = self.screen_width - 1.5 * split_span_width - img_width
+ -- title
+ local book_meta_info_group = VerticalGroup:new{
+ align = "center",
+ TextBoxWidget:new{
+ text = self.dates.title,
+ face = self.medium_font_face,
+ padding = 2,
+ alignment = "center",
+ width = width,
+ },
+ }
+ -- author
+ local text_author = TextBoxWidget:new{
+ text = self.dates.author,
+ width = width,
+ face = self.large_font_face,
+ alignment = "center",
+ }
+ table.insert(book_meta_info_group,
+ CenterContainer:new{
+ dimen = Geom:new{ w = width, h = text_author:getSize().h },
+ text_author
+ }
+ )
+ --span
+ local span_author = VerticalSpan:new{ width = height * 0.1 }
+ table.insert(book_meta_info_group,
+ CenterContainer:new{
+ dimen = Geom:new{ w = width, h = Screen:scaleBySize(10) },
+ span_author
+ }
+ )
+ -- series
+ local text_series = TextWidget:new{
+ text = T(_("Series: %1"), self.dates.series),
+ face = self.small_font_face,
+ padding = 2,
+ }
+ table.insert(book_meta_info_group,
+ CenterContainer:new{
+ dimen = Geom:new{ w = width, h = text_series:getSize().h },
+ text_series
+ }
+ )
+ -- rating
+ local text_rating = TextWidget:new{
+ text = T(_("Rating: %1"), self.dates.rating),
+ face = self.small_font_face,
+ padding = 2,
+ }
+ table.insert(book_meta_info_group,
+ CenterContainer:new{
+ dimen = Geom:new{ w = width, h = text_rating:getSize().h },
+ text_rating
+ }
+ )
+ -- pages
+ local text_pages = TextWidget:new{
+ text = T(_("Pages: %1"), self.dates.pages),
+ face = self.small_font_face,
+ padding = 2,
+ }
+ table.insert(book_meta_info_group,
+ CenterContainer:new{
+ dimen = Geom:new{ w = width, h = text_pages:getSize().h },
+ text_pages
+ }
+ )
+ -- relesse date
+ local text_release = TextWidget:new{
+ text = T(_("Release date: %1"), self.dates.release),
+ face = self.small_font_face,
+ padding = 2,
+ }
+ table.insert(book_meta_info_group,
+ CenterContainer:new{
+ dimen = Geom:new{ w = width, h = text_release:getSize().h },
+ text_release
+ }
+ )
+ local book_info_group = HorizontalGroup:new{
+ align = "top",
+ HorizontalSpan:new{ width = split_span_width }
+ }
+ --thumbnail
+ local http = require("socket.http")
+ local body = http.request(self.dates.image)
+ local image = false
+ if body then image = Pic.openJPGDocumentFromMem(body) end
+ if image then
+ table.insert(book_info_group, ImageWidget:new{
+ image = image.image_bb,
+ width = img_width,
+ height = img_height,
+ autoscale = false,
+ })
+ else
+ table.insert(book_info_group, ImageWidget:new{
+ file = "resources/goodreadsnophoto.png",
+ width = img_width,
+ height = img_height,
+ autoscale = false,
+ })
+ end
+
+ local book_info_group_span = HorizontalGroup:new{
+ align = "top",
+ HorizontalSpan:new{ width = split_span_width / 2 }
+ }
+ table.insert(book_info_group, book_info_group_span)
+ table.insert(book_info_group, CenterContainer:new{
+ dimen = Geom:new{ w = width , h = height },
+ book_meta_info_group,
+ })
+ return CenterContainer:new{
+ dimen = Geom:new{ w = self.screen_width, h = self.screen_height * 0.35 },
+ book_info_group,
+ }
+end
+
+function GoodReaderBook:bookReview()
+ local book_meta_info_group = VerticalGroup:new{
+ align = "center",
+ padding = 0,
+ bordersize = 0,
+ ScrollTextWidget:new{
+ text = self.dates.description,
+ face = self.medium_font_face,
+ padding = 0,
+ width = self.screen_width * 0.9,
+ height = self.screen_height * 0.48,
+ dialog = self,
+ }
+ }
+ return CenterContainer:new{
+ dimen = Geom:new{ w = self.screen_width, h = self.screen_height * 0.50 },
+ book_meta_info_group,
+ }
+end
+
+function GoodReaderBook:onAnyKeyPressed()
+ return self:onClose()
+end
+
+function GoodReaderBook:onClose()
+ UIManager:setDirty("all")
+ UIManager:close(self)
+ return true
+end
+
+return GoodReaderBook
diff --git a/plugins/goodreads.koplugin/goodreadsapi.lua b/plugins/goodreads.koplugin/goodreadsapi.lua
new file mode 100644
index 000000000..ffec436e4
--- /dev/null
+++ b/plugins/goodreads.koplugin/goodreadsapi.lua
@@ -0,0 +1,212 @@
+local InputContainer = require("ui/widget/container/inputcontainer")
+local GoodReaderBook = require("goodreaderbook")
+local InfoMessage = require("ui/widget/infomessage")
+local UIManager = require("ui/uimanager")
+local url = require('socket.url')
+local socket = require('socket')
+local http = require('socket.http')
+local https = require('ssl.https')
+local ltn12 = require('ltn12')
+local _ = require("gettext")
+
+local GoodReadsApi = InputContainer:new {
+ goodreaders_key = "",
+ goodreaders_secret = "",
+ total_result = 0,
+}
+
+function GoodReadsApi:init()
+end
+
+local function genSearchURL(text_search, userApi, search_type, npage)
+ if (text_search) then
+ text_search = string.gsub (text_search, "\n", "\r\n")
+ text_search = string.gsub (text_search, "([^%w %-%_%.%~])",
+ function (c) return string.format ("%%%02X", string.byte(c)) end)
+ text_search = string.gsub (text_search, " ", "+")
+ end
+ return (string.format(
+ "http://www.goodreads.com/search?q=%s&search[field]=%s&format=xml&key=%s&page=%s",
+ text_search,
+ search_type,
+ userApi,
+ npage
+ ))
+end
+
+local function genIdUrl(id, userApi)
+ return (string.format(
+ "https://www.goodreads.com/book/show/%s?format=xml&key=%s",
+ id,
+ userApi
+ ))
+end
+
+function GoodReadsApi:fetchXml(s_url)
+ local request, sink = {}, {}
+ local parsed = url.parse(s_url)
+ request['url'] = s_url
+ request['method'] = 'GET'
+ request['sink'] = ltn12.sink.table(sink)
+ http.TIMEOUT = 5
+ https.TIMEOUT = 5
+ local httpRequest = parsed.scheme == 'http' and http.request or https.request
+ local headers = socket.skip(1, httpRequest(request))
+ if headers == nil then
+ return nil
+ end
+ local xml = table.concat(sink)
+ if xml ~= "" then
+ return xml
+ end
+end
+
+function GoodReadsApi:showSearchTable(data)
+ local books = {}
+ if data == nil then
+ UIManager:show(InfoMessage:new{text =_("Network problem.\nCheck connection.")})
+ return books
+ end
+ self.total_result = data:match("(.*)")
+
+ for work in data:gmatch("(.-)") do
+ local book = work:match("]+>(.*)")
+ local id = book:match("]+>([^<]+)")
+ local title = book:match("([^<]+)"):gsub(" %(.*#%d+%)$", "")
+ local author = book:match("([^<]+)")
+ table.insert(books, {
+ author = author,
+ title = title,
+ id = id,
+ })
+ end
+ if #books == 0 then
+ UIManager:show(InfoMessage:new{text =_("Search not found!")})
+ end
+ return books
+end
+
+function GoodReadsApi:getTotalResults()
+ return self.total_result
+end
+
+local function cleanHTMLTags(str_html)
+ local cleaner = {
+ { "&", "&" },
+ { "", "-" },
+ { "", "'" },
+ { " ", " " },
+ { "", "%1" },
+ { "
", "\n" },
+ { "%-%-", "%-" },
+ { "
", "\n" },
+ { "(%b<>)", "" },
+ { "\n\n*", "\n" },
+ { "\n*$", "" },
+ { "^\n*", "" },
+ }
+ for i=1, #cleaner do
+ local cleans = cleaner[i]
+ str_html = string.gsub(str_html, cleans[1], cleans[2])
+ end
+ return str_html
+end
+
+local function showIdTable(data)
+ if data == nil then
+ UIManager:show(InfoMessage:new{text =_("Network problem.\nCheck connection.")})
+ return {}
+ end
+ local data1 = data:match("(.*)")
+ local title_all = data1:match("(.*)"):gsub("$", "")
+ local title = title_all:gsub(" %(.*#%d+%)$", "")
+ local average_rating = data1:match("([^<]+)")
+ local series = title_all:match("%(.*#%d+%)$")
+ if series ~= nil then
+ series = series:match("[(](.*)[)]")
+ else
+ series = _("N/A")
+ end
+ local num_pages = data1:match("(.*)"):gsub("$", "")
+ if num_pages == nil or num_pages =="" then
+ num_pages = _("N/A")
+ end
+ local id = data1:match("([^<]+)"):gsub("$", "")
+ local author = data1:match("([^<]+)")
+ local description = data1:match("(.*)")
+ description = cleanHTMLTags(description)
+ --change format from medium to large
+ local image = data1:match("([^<]+)"):gsub("([0-9]+)m/", "%1l/")
+ local day = data1:match("]+>([^<]+)")
+ local month = data1:match("]+>([^<]+)")
+ local year = data1:match("]+>([^<]+)")
+
+ local release = {}
+ if (year) then
+ table.insert(release, year)
+ end
+ if (month) then
+ table.insert(release, string.format("%02d", month))
+ end
+ if (day) then
+ table.insert(release, string.format("%02d", day))
+ end
+ release = table.concat(release, "-")
+ if release == "" then
+ release = _("N/A")
+ end
+ local book_info = {
+ title = title,
+ author = author,
+ series = series,
+ rating = average_rating,
+ pages = num_pages,
+ release = release,
+ description = description,
+ image = image,
+ id = id,
+ }
+ if id == nil then
+ UIManager:show(InfoMessage:new{text = _("Search not found!")})
+ end
+ return book_info
+end
+
+-- search_type = all - search all
+-- search_type = author - serch book by author
+-- search_type = title - search book by title
+function GoodReadsApi:showData(search_text, search_type, page, goodreaders_key)
+ local stats = {}
+ local gen_url = genSearchURL(search_text, goodreaders_key, search_type, page)
+ local gen_xml = self:fetchXml(gen_url)
+ local tbl = self:showSearchTable(gen_xml)
+ if #tbl == 0 then
+ return nil
+ end
+ for _, v in pairs(tbl) do
+ local author = v.author
+ local title = v.title
+ local id = v.id
+ table.insert(stats, { author,
+ title,
+ callback = function()
+ local dates = self:showIdData(id, goodreaders_key)
+ if dates.id ~= nil then
+ UIManager:show(GoodReaderBook:new{
+ dates = dates,
+ })
+ end
+ end,
+ })
+ end
+ return stats
+end
+
+function GoodReadsApi:showIdData(id, goodreaders_key)
+ local gen_url = genIdUrl(id, goodreaders_key)
+ local gen_xml = self:fetchXml(gen_url)
+ local tbl = showIdTable(gen_xml)
+ return tbl
+end
+
+return GoodReadsApi
diff --git a/plugins/goodreads.koplugin/main.lua b/plugins/goodreads.koplugin/main.lua
new file mode 100644
index 000000000..ff67a16f0
--- /dev/null
+++ b/plugins/goodreads.koplugin/main.lua
@@ -0,0 +1,224 @@
+local InputContainer = require("ui/widget/container/inputcontainer")
+local InputDialog = require("ui/widget/inputdialog")
+local DoubleKeyValuePage = require("doublekeyvaluepage")
+local MultiInputDialog = require("ui/widget/multiinputdialog")
+local InfoMessage = require("ui/widget/infomessage")
+local UIManager = require("ui/uimanager")
+local Screen = require("device").screen
+local _ = require("gettext")
+
+local GoodReads = InputContainer:new {
+ goodreaders_key = "",
+ goodreaders_secret = "",
+}
+
+function GoodReads:init()
+ local gr_sett = DoubleKeyValuePage:readGRSettings().data
+ if gr_sett.goodreads then
+ self.goodreaders_key = gr_sett.goodreads.key
+ self.goodreaders_secret = gr_sett.goodreads.secret
+ end
+ self.ui.menu:registerToMainMenu(self)
+end
+
+function GoodReads:addToMainMenu(tab_item_table)
+ table.insert(tab_item_table.plugins, {
+ text = _("GoodReads"),
+ sub_item_table = {
+ {
+ text = _("Settings"),
+ callback = function() self:updateSettings() end,
+ },
+ {
+ text = _("Search book all"),
+ callback = function()
+ if self.goodreaders_key ~= "" then
+ self:search("all")
+ else
+ UIManager:show(InfoMessage:new{
+ text = _("Please set up GoodReads key in settings"),
+ })
+ end
+ end,
+ },
+ {
+ text = _("Search book by title"),
+ callback = function()
+ if self.goodreaders_key ~= "" then
+ self:search("title")
+ else
+ UIManager:show(InfoMessage:new{
+ text = _("Please set up GoodReads key in settings"),
+ })
+ end
+ end,
+ },
+ {
+ text = _("Search book by author"),
+ callback = function()
+ if self.goodreaders_key ~= "" then
+ self:search("author")
+
+ else
+ UIManager:show(InfoMessage:new{
+ text = _("Please set up GoodReads key in settings"),
+ })
+ end
+ end,
+ },
+ },
+ })
+end
+
+function GoodReads:updateSettings()
+ local hint_top
+ local text_top
+ local hint_bottom
+ local text_bottom
+ local text_info = "How to generate key and secret key:\n"..
+ "1. Go to https://www.goodreads.com/user/sign_up and create account\n" ..
+ "2. Register development key on page: https://www.goodreads.com/user/sign_in?rd=true\n" ..
+ "3. Your key and secret key are on https://www.goodreads.com/api/keys\n" ..
+ "4. Enter your generated key and secret key in settings (Login to GoodReads window)"
+ if self.goodreaders_key == "" then
+ hint_top = _("GoodReaders Key Not Set")
+ text_top = ""
+ else
+ hint_top = ""
+ text_top = self.goodreaders_key
+ end
+
+ if self.goodreaders_secret == "" then
+ hint_bottom = _("GoodReaders Secret Key Not Set (optional)")
+ text_bottom = ""
+ else
+ hint_bottom = ""
+ text_bottom = self.goodreaders_key
+ end
+ self.settings_dialog = MultiInputDialog:new {
+ title = _("Login to GoodReads"),
+ fields = {
+ {
+ text = text_top,
+ input_type = "string",
+ hint = hint_top ,
+ },
+ {
+ text = text_bottom,
+ input_type = "string",
+ hint = hint_bottom,
+ },
+ },
+ buttons = {
+ {
+ {
+ text = _("Cancel"),
+ callback = function()
+ self.settings_dialog:onClose()
+ UIManager:close(self.settings_dialog)
+ end
+ },
+ {
+ text = _("Info"),
+ callback = function()
+ UIManager:show(InfoMessage:new{text = text_info })
+ end
+ },
+ {
+ text = _("Apply"),
+ callback = function()
+ self:saveSettings(MultiInputDialog:getFields())
+ self.settings_dialog:onClose()
+ UIManager:close(self.settings_dialog)
+ end
+ },
+ },
+ },
+ width = Screen:getWidth() * 0.95,
+ height = Screen:getHeight() * 0.2,
+ input_type = "text",
+ }
+ self.settings_dialog:onShowKeyboard()
+ UIManager:show(self.settings_dialog)
+end
+
+function GoodReads:saveSettings(fields)
+ if fields then
+ self.goodreaders_key = fields[1]
+ self.goodreaders_secret = fields[2]
+ end
+ local settings = {
+ key = self.goodreaders_key,
+ secret = self.goodreaders_secret,
+ }
+ DoubleKeyValuePage:saveGRSettings(settings)
+end
+
+-- search_type = all - search all
+-- search_type = author - serch book by author
+-- search_type = title - search book by title
+function GoodReads:search(search_type)
+ local title_header
+ local hint
+ local search_input
+ local text_input
+ local info
+ local result
+ if search_type == "all" then
+ title_header = _("Search book all in GoodReads")
+ hint = _("Title, author or ISBN")
+ elseif search_type == "author" then
+ title_header = _("Search book by author in GoodReads")
+ hint = _("Author")
+ elseif search_type == "title" then
+ title_header = _("Search book by title in GoodReads")
+ hint = _("Title")
+ end
+
+ search_input = InputDialog:new{
+ title = title_header,
+ input = "",
+ input_hint = hint,
+ input_type = "string",
+ buttons = {
+ {
+ {
+ text = _("Cancel"),
+ callback = function()
+ UIManager:close(search_input)
+ end,
+ },
+ {
+ text = _("Find"),
+ is_enter_default = true,
+ callback = function()
+ text_input = search_input:getInputText()
+ if text_input ~= nil and text_input ~= "" then
+ info = InfoMessage:new{text = _("Please wait..."), timeout = 0.0}
+ UIManager:show(info)
+ UIManager:nextTick(function()
+ result = DoubleKeyValuePage:new{
+ title = _("Select book"),
+ text_input = text_input,
+ search_type = search_type,
+ }
+ if #result.kv_pairs > 0 then
+ UIManager:show(result)
+ end
+ end)
+ UIManager:close(search_input)
+ else
+ UIManager:show(InfoMessage:new{
+ text =_("Please input text"),
+ })
+ end
+ end,
+ },
+ }
+ },
+ }
+ search_input:onShowKeyboard()
+ UIManager:show(search_input)
+end
+
+return GoodReads
diff --git a/resources/goodreadsnophoto.png b/resources/goodreadsnophoto.png
new file mode 100644
index 0000000000000000000000000000000000000000..4db7258b60393433a63338e9ba995a29f41b3ccd
GIT binary patch
literal 599
zcmV-d0;v6oP)*(3$;n3dJ!|m+d-PXhC=GE%w*Q(6Kvj6}A!AV3xRCr$P*lCK~Kny_Pr&_&l
zy-bq(A2c2qXl(b7Oc50f=w0B$#wv^AKWAB%Wm%SGS(as4R@iCm>b&;Ch&eijZ^0BTbzOpCgz0W-J%rkG9??9YZCwoob
z_nO|C_=>oS$O!0pF7pZoWv@v9!_tT}TLuPI&jlcK@>QQ;)bu7GxT8PldHsnKX(H%(
z8R+DYSM(a-Ca02~Hv;|HoAta5QOyLt9R4%`m6V-{623#
z_j@@7Ojj>z&ihBrd7pvq>^=0n<=uJxq31~gntJ~u8G&%%x~S<%0&ecW#aYqoIP6PH
z1h2XeZU9W{PWJw;z7L9YvFg4n;>MR47g6_iaT{`kFl|ouCROiRhE&r_kSlr(Fza|v
zV01b{U6jfm?X2_QH{
zPX