Here is my version with a couple of added fields and some formatting improvements.
- solved the “Author” array problem using the author_block variable and parsing the separate authors one by one outside of the template.
- added quotes “” to all the metadata entries to be handled as strings, more reliable to handle string data.
- removed the ID-field , because it was already part of the URL, so no duplicate entries.
- adds a short cleaned up description of the book to the metadata
- adds the book cover (L-Size) to the metadata.
-- ==============================
-- Open Library → SilverBullet
-- ==============================
local cfg = config.get("bookmanager") or ""
local pagePrefix = cfg.prefix or "MediaDB/Books/"
local filenameTemplate = cfg.filenameTemplate or [==[${title} (${first_publish_year})]==]
local pageTemplate = cfg.pageTemplate or [==[---
type: "book"
title: "${title}"
year: "${first_publish_year}"
author:
${author_block}
isbn: "${isbn}"
url: "https://openlibrary.org${olid}"
tags: "mediaDB/book"
dataSource: "OpenLibraryAPI"
cover: "${cover_image_url}"
description: "${description}"
---
# ${title} - ${author_name[1]}
]==]
function searchBook(queryString)
-- Search by title, author, or ISBN
local url = "https://openlibrary.org/search.json?q=" ..
string.gsub(queryString, " ", "+")
local resp = net.proxyFetch(url, {
method = "GET",
headers = { Accept = "application/json" }
})
if not resp.ok then
error("Failed to fetch from Open Library: " .. resp.status)
end
local data = resp.body
if #data.docs == 0 then
editor.flashNotification("No book found for: " .. queryString)
return nil
end
options = {}
-- show prompt to select docs
for i, doc in ipairs(data.docs) do
options[i] = {
name=doc.title,
value=i,
description=string.format("By %s, published %s", doc.author_name, doc.first_publish_year)
}
end
local result = editor.filterBox("Select:", options)
if not result then
editor.flashNotification("Cancelled book search")
return nil
end
return data.docs[result.value] -- return first match
end
command.define {
name = "Open Library: Add Book to Knowledge Base",
run = function()
local queryString = editor.prompt("Search for book (title, author, or ISBN):")
if not queryString then
return
end
print("Searching for book " .. queryString)
local book = searchBook(queryString)
if not book then
return
end
local titleTemplate = template.new(filenameTemplate)
local safeBook = {}
for k in {'title','first_publish_year','author_name', 'isbn'} do
safeBook[k] = book[k] or "Unknown"
end
safeBook['olid'] = book['key']
if book.cover_i then
safeBook['cover_image_url'] = "https://covers.openlibrary.org/b/id/" .. book.cover_i .. "-L.jpg"
else
safeBook['cover_image_url'] = ""
end
-- build dynamic author block
local author_lines = {}
if type(book.author_name) == "table" then
for _, name in ipairs(book.author_name) do
table.insert(author_lines, ' - "' .. name .. '"')
end
end
safeBook['author_block'] = table.concat(author_lines, "\n")
-- determine ISBN robustly (search fields, then edition JSON)
local function extract_first_isbn(b)
if not b then return nil end
if type(b.isbn) == "table" and #b.isbn > 0 then
return b.isbn[1]
elseif type(b.isbn) == "string" then
return b.isbn
end
if type(b.isbn_13) == "table" and #b.isbn_13 > 0 then
return b.isbn_13[1]
end
if type(b.isbn_10) == "table" and #b.isbn_10 > 0 then
return b.isbn_10[1]
end
local edition = b.cover_edition_key or (type(b.edition_key) == "table" and b.edition_key[1]) or nil
if edition then
local ed_url = "https://openlibrary.org/books/" .. edition .. ".json"
local ed_resp = net.proxyFetch(ed_url, {
method = "GET",
headers = { Accept = "application/json" }
})
if ed_resp.ok then
local ed = ed_resp.body
if type(ed.isbn_13) == "table" and #ed.isbn_13 > 0 then
return ed.isbn_13[1]
end
if type(ed.isbn_10) == "table" and #ed.isbn_10 > 0 then
return ed.isbn_10[1]
end
end
end
return nil
end
local found_isbn = extract_first_isbn(book)
if found_isbn and found_isbn ~= "" then
safeBook['isbn'] = found_isbn
else
safeBook['isbn'] = "Unknown"
end
-- fetch and clean description/plot from the work JSON
local description_text = ""
if book.key then
local work_url = "https://openlibrary.org" .. book.key .. ".json"
local work_resp = net.proxyFetch(work_url, {
method = "GET",
headers = { Accept = "application/json" }
})
if work_resp.ok then
local w = work_resp.body
if w.description then
if type(w.description) == "table" then
description_text = w.description.value or ""
elseif type(w.description) == "string" then
description_text = w.description
end
elseif w.first_sentence then
if type(w.first_sentence) == "table" then
description_text = w.first_sentence.value or ""
elseif type(w.first_sentence) == "string" then
description_text = w.first_sentence
end
end
end
end
if description_text and description_text ~= "" then
-- truncate at earliest marker indicating references/footnotes/extra sections
local function cut_at_first_marker(text)
local lower = string.lower(text)
local markers = {
"([source", -- "([source" pattern
"[source", -- "[source" pattern
"----------", -- separator line
"preceded by:", -- preceded by
"see also:", -- see also
"followed by:", -- followed by
"contains:" -- contains
}
local first_pos = nil
for _, m in ipairs(markers) do
local s = string.find(lower, m, 1, true)
if s and (not first_pos or s < first_pos) then
first_pos = s
end
end
if first_pos then
return string.sub(text, 1, first_pos - 1)
end
return text
end
description_text = cut_at_first_marker(description_text)
-- remove markdown links [text](url)
description_text = string.gsub(description_text, "%b[]%b()", "")
-- remove any remaining bracketed reference markers like [1], [2], etc.
description_text = string.gsub(description_text, "%[%d+%]", "")
-- remove reference-style links and footnotes like "[1]: http..."
description_text = string.gsub(description_text, "%[%d+%]:%s*https?://%S+", "")
-- remove explicit [source] or [Source] occurrences if anything left
description_text = string.gsub(description_text, "%[source%]", "")
description_text = string.gsub(description_text, "%[Source%]", "")
-- remove raw URLs
description_text = string.gsub(description_text, "https?://%S+", "")
-- remove separators and excessive whitespace/newlines
description_text = string.gsub(description_text, "[\r\n]+", " ")
description_text = string.gsub(description_text, "%s+", " ")
-- escape double quotes for frontmatter
description_text = string.gsub(description_text, '"', '”')
description_text = string.gsub(description_text, "^%s*(.-)%s*$", "%1")
safeBook['description'] = description_text
else
safeBook['description'] = ""
end
local rawTitle = titleTemplate(safeBook)
local safeTitle = string.gsub(rawTitle, "[/%\\:'\"%*%?%<%>%|]", "")
safeTitle = string.gsub(safeTitle, "%s+", " ")
local pageName = pagePrefix .. safeTitle
if not space.pageExists(pageName) then
local pageTemplate = template.new(pageTemplate)
local content = pageTemplate(safeBook)
-- Create the page
space.writePage(pageName, content)
editor.flashNotification("Book added: " .. safeTitle)
else
editor.flashNotification("Book already in collection: " .. safeTitle)
end
editor.navigate(pageName)
end
}
All these modifications where necessary so I can use it in my “MediaGallery” which I’m currently working on, which gathers books, movies, tv-series, games, or basically any object or page with a title, subtitle, year, score, etc. and most importantly an image URL in it’s metadata and displays it in a beautiful Grid Gallery.
Here is a first sneak peak of the Media Gallery displaying the Book Gallery with the added books from OpenLibrary