Instantly share code, notes, and snippets.
Last active
September 24, 2025 16:24
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
-
Save mallomar/56c524d17bcbc026288ffadc9ff8d174 to your computer and use it in GitHub Desktop.
Adds a Menu Item in Tools Showing the Book's Partial Sync Hash
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --[[ | |
| KOReader Patch: Add Book Info with Both Hashes to Menu | |
| Adds a menu item to display the book's sync hash and Readest meta hash | |
| --]] | |
| -- Function to find the KOReader sync hash | |
| local function findSyncHash(file_path) | |
| if not file_path then return nil end | |
| local ok_ds, DocSettings = pcall(require, "docsettings") | |
| if ok_ds then | |
| local doc_settings = DocSettings:open(file_path) | |
| if doc_settings and doc_settings.data and doc_settings.data.partial_md5_checksum then | |
| return doc_settings.data.partial_md5_checksum | |
| end | |
| end | |
| return nil | |
| end | |
| -- Function to get Readest meta hash (using same logic as the plugin) | |
| local function getReadestMetaHash(file_path) | |
| if not file_path then return nil end | |
| local ok_ds, DocSettings = pcall(require, "docsettings") | |
| if not ok_ds then return nil end | |
| local doc_settings = DocSettings:open(file_path) | |
| if not doc_settings then return nil end | |
| -- Check if meta_hash is already cached | |
| local readest_sync = doc_settings:readSetting("readest_sync") or {} | |
| if readest_sync.meta_hash then | |
| return readest_sync.meta_hash | |
| end | |
| -- Calculate meta_hash using the same logic as Readest plugin | |
| local util = require("util") | |
| local sha2 = require("ffi/sha2") | |
| local doc_props = doc_settings:readSetting("doc_props") or {} | |
| local title = doc_props.title or '' | |
| if title == '' then | |
| local doc_path = doc_settings:readSetting("doc_path") or '' | |
| local doc_path_part, filename = util.splitFilePathName(doc_path) | |
| local basename, suffix = util.splitFileNameSuffix(filename) | |
| title = basename or '' | |
| end | |
| local function normalizeAuthor(author) | |
| return author:gsub("^%s*(.-)%s*$", "%1") | |
| end | |
| local function normalizeIdentifier(identifier) | |
| if identifier:match("urn:") then | |
| return identifier:match("([^:]+)$") | |
| elseif identifier:match(":") then | |
| return identifier:match("^[^:]+:(.+)$") | |
| end | |
| return identifier | |
| end | |
| local authors = doc_props.authors or '' | |
| if authors:find("\n") then | |
| local author_array = util.splitToArray(authors, "\n") | |
| for i, author in ipairs(author_array) do | |
| author_array[i] = normalizeAuthor(author) | |
| end | |
| authors = table.concat(author_array, ",") | |
| else | |
| authors = normalizeAuthor(authors) | |
| end | |
| local identifiers = doc_props.identifiers or '' | |
| if identifiers:find("\n") then | |
| local id_array = util.splitToArray(identifiers, "\n") | |
| for i, id in ipairs(id_array) do | |
| id_array[i] = normalizeIdentifier(id) | |
| end | |
| identifiers = table.concat(id_array, ",") | |
| else | |
| identifiers = normalizeIdentifier(identifiers) | |
| end | |
| local doc_meta = title .. "|" .. authors .. "|" .. identifiers | |
| local meta_hash = sha2.md5(doc_meta) | |
| return meta_hash | |
| end | |
| -- Function to find book metadata | |
| local function findBookMetadata(file_path) | |
| if not file_path then return nil, nil, nil end | |
| local title, author, isbn = nil, nil, nil | |
| -- Try to get metadata from DocSettings first (most reliable) | |
| local ok_ds, DocSettings = pcall(require, "docsettings") | |
| if ok_ds then | |
| local doc_settings = DocSettings:open(file_path) | |
| if doc_settings and doc_settings.data and doc_settings.data.doc_props then | |
| local props = doc_settings.data.doc_props | |
| title = props.title | |
| author = props.authors | |
| isbn = props.identifiers | |
| end | |
| end | |
| -- Try to get metadata from live document if available | |
| local ok_reader, ReaderUI = pcall(require, "apps/reader/readerui") | |
| if ok_reader and ReaderUI.instance and ReaderUI.instance.document then | |
| local document = ReaderUI.instance.document | |
| if document.getMetadata then | |
| local ok, metadata = pcall(document.getMetadata, document) | |
| if ok and metadata then | |
| title = title or metadata.title | |
| author = author or metadata.author or metadata.creator | |
| isbn = isbn or metadata.identifier or metadata.isbn or metadata.ISBN | |
| end | |
| end | |
| if document.getProps then | |
| local ok, props = pcall(document.getProps, document) | |
| if ok and props then | |
| title = title or props.title | |
| author = author or props.author or props.creator | |
| isbn = isbn or props.identifier or props.isbn or props.ISBN | |
| end | |
| end | |
| end | |
| return title, author, isbn | |
| end | |
| -- Function to show comprehensive book info dialog | |
| local function showBookInfoDialog() | |
| local file_path = nil | |
| local ok_reader, ReaderUI = pcall(require, "apps/reader/readerui") | |
| if ok_reader and ReaderUI.instance and ReaderUI.instance.document then | |
| file_path = ReaderUI.instance.document.file | |
| end | |
| local sync_hash = findSyncHash(file_path) | |
| local meta_hash = getReadestMetaHash(file_path) | |
| local title, author, isbn = findBookMetadata(file_path) | |
| local sync_hash_text = sync_hash or "Sync hash not found" | |
| local meta_hash_text = meta_hash or "Meta hash not available" | |
| local isbn_text = isbn or "ISBN not found" | |
| local title_text = title or "Title not found" | |
| local author_text = author or "Author not found" | |
| local filename = file_path and string.match(file_path, "[^/]+$") or "unknown" | |
| local InfoMessage = require("ui/widget/infomessage") | |
| local UIManager = require("ui/uimanager") | |
| local dialog_text = string.format( | |
| "Book Information:\n\n" .. | |
| "KOReader Sync Hash:\n%s\n\n" .. | |
| "Readest Meta Hash:\n%s\n\n" .. | |
| "Title: %s\n" .. | |
| "Author: %s\n" .. | |
| "ISBN: %s\n\n" .. | |
| "The sync hash is KOReader's book identifier.\n" .. | |
| "The meta hash is calculated from title|author|identifiers.\n\n" .. | |
| "File: %s", | |
| sync_hash_text, meta_hash_text, title_text, author_text, isbn_text, filename | |
| ) | |
| local info = InfoMessage:new{ | |
| text = dialog_text, | |
| -- Remove timeout to make dialog persistent | |
| } | |
| UIManager:show(info) | |
| end | |
| -- Create the menu item | |
| local function createBookInfoMenuItem() | |
| return { | |
| text = "Show Book Info & Hashes", | |
| callback = function() | |
| showBookInfoDialog() | |
| end, | |
| } | |
| end | |
| -- Add menu item to the specified section | |
| local function patchMenu(menu, order) | |
| if order and order.more_tools then | |
| table.insert(order.more_tools, "book_info_hashes") | |
| menu.menu_items.book_info_hashes = createBookInfoMenuItem() | |
| elseif order and order.tools then | |
| table.insert(order.tools, "book_info_hashes") | |
| menu.menu_items.book_info_hashes = createBookInfoMenuItem() | |
| else | |
| menu.menu_items.book_info_hashes = createBookInfoMenuItem() | |
| end | |
| end | |
| -- Hook into ReaderMenu | |
| local ok_reader_menu, ReaderMenu = pcall(require, "apps/reader/modules/readermenu") | |
| if ok_reader_menu then | |
| local orig_ReaderMenu_setUpdateItemTable = ReaderMenu.setUpdateItemTable | |
| ReaderMenu.setUpdateItemTable = function(self) | |
| local ok_order, reader_order = pcall(require, "ui/elements/reader_menu_order") | |
| if ok_order then | |
| patchMenu(self, reader_order) | |
| end | |
| orig_ReaderMenu_setUpdateItemTable(self) | |
| end | |
| end | |
| -- Hook into FileManagerMenu | |
| local ok_fm_menu, FileManagerMenu = pcall(require, "apps/filemanager/filemanagermenu") | |
| if ok_fm_menu then | |
| local orig_FileManagerMenu_setUpdateItemTable = FileManagerMenu.setUpdateItemTable | |
| FileManagerMenu.setUpdateItemTable = function(self) | |
| local ok_order, fm_order = pcall(require, "ui/elements/filemanager_menu_order") | |
| if ok_order then | |
| patchMenu(self, fm_order) | |
| end | |
| orig_FileManagerMenu_setUpdateItemTable(self) | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment