314 lines
13 KiB
Lua
314 lines
13 KiB
Lua
-- name = "Bible Search"
|
|
-- type = "search"
|
|
-- author = "Evon Smith"
|
|
-- description = "Type in Bible book and chapter to open the YouVersion Bible App to that location right from the search window"
|
|
-- version = "2.0"
|
|
|
|
------------------------------------------------------------
|
|
-- Complete Bible books list with aliases and chapters
|
|
------------------------------------------------------------
|
|
local BOOKS = {
|
|
{ name = "Genesis", aliases = {"genesis","gen","ge"}, chapters = 50, osis = "GEN" },
|
|
{ name = "Exodus", aliases = {"exodus","exo","ex"}, chapters = 40, osis = "EXO" },
|
|
{ name = "Leviticus", aliases = {"leviticus","lev","le"}, chapters = 27, osis = "LEV" },
|
|
{ name = "Numbers", aliases = {"numbers","num","nu","nm"}, chapters = 36, osis = "NUM" },
|
|
{ name = "Deuteronomy", aliases = {"deuteronomy","deut","dt"}, chapters = 34, osis = "DEU" },
|
|
{ name = "Joshua", aliases = {"joshua","josh","jos"}, chapters = 24, osis = "JOS" },
|
|
{ name = "Judges", aliases = {"judges","judg","jdg"}, chapters = 21, osis = "JDG" },
|
|
{ name = "Ruth", aliases = {"ruth","ru"}, chapters = 4, osis = "RUT" },
|
|
{ name = "1 Samuel", aliases = {"1samuel","1sam","1sa","i samuel","1 sam"}, chapters = 31, osis = "1SA" },
|
|
{ name = "2 Samuel", aliases = {"2samuel","2sam","2sa","ii samuel","2 sam"}, chapters = 24, osis = "2SA" },
|
|
{ name = "1 Kings", aliases = {"1kings","1ki","i kings","1 ki"}, chapters = 22, osis = "1KI" },
|
|
{ name = "2 Kings", aliases = {"2kings","2ki","ii kings","2 ki"}, chapters = 25, osis = "2KI" },
|
|
{ name = "1 Chronicles", aliases = {"1chronicles","1chr","i chronicles","1 chr"}, chapters = 29, osis = "1CH" },
|
|
{ name = "2 Chronicles", aliases = {"2chronicles","2chr","ii chronicles","2 chr"}, chapters = 36, osis = "2CH" },
|
|
{ name = "Ezra", aliases = {"ezra","ezr"}, chapters = 10, osis = "EZR" },
|
|
{ name = "Nehemiah", aliases = {"nehemiah","neh","ne"}, chapters = 13, osis = "NEH" },
|
|
{ name = "Esther", aliases = {"esther","est","es"}, chapters = 10, osis = "EST" },
|
|
{ name = "Job", aliases = {"job","jb"}, chapters = 42, osis = "JOB" },
|
|
{ name = "Psalms", aliases = {"psalms","psalm","ps","psa"}, chapters = 150, osis = "PSA" },
|
|
{ name = "Proverbs", aliases = {"proverbs","prov","prv","pr"}, chapters = 31, osis = "PRO" },
|
|
{ name = "Ecclesiastes", aliases = {"ecclesiastes","eccl","ecc"}, chapters = 12, osis = "ECC" },
|
|
{ name = "Song of Solomon", aliases = {"songofsolomon","song","so","ss","song of songs","songs"}, chapters = 8, osis = "SNG" },
|
|
{ name = "Isaiah", aliases = {"isaiah","isa","is"}, chapters = 66, osis = "ISA" },
|
|
{ name = "Jeremiah", aliases = {"jeremiah","jer","je"}, chapters = 52, osis = "JER" },
|
|
{ name = "Lamentations", aliases = {"lamentations","lam","la"}, chapters = 5, osis = "LAM" },
|
|
{ name = "Ezekiel", aliases = {"ezekiel","ezek","eze"}, chapters = 48, osis = "EZK" },
|
|
{ name = "Daniel", aliases = {"daniel","dan","da"}, chapters = 12, osis = "DAN" },
|
|
{ name = "Hosea", aliases = {"hosea","hos","ho"}, chapters = 14, osis = "HOS" },
|
|
{ name = "Joel", aliases = {"joel","jl"}, chapters = 3, osis = "JOL" },
|
|
{ name = "Amos", aliases = {"amos","am"}, chapters = 9, osis = "AMO" },
|
|
{ name = "Obadiah", aliases = {"obadiah","obad","ob"}, chapters = 1, osis = "OBA" },
|
|
{ name = "Jonah", aliases = {"jonah","jon","jh"}, chapters = 4, osis = "JON" },
|
|
{ name = "Micah", aliases = {"micah","mic","mc"}, chapters = 7, osis = "MIC" },
|
|
{ name = "Nahum", aliases = {"nahum","nah","na"}, chapters = 3, osis = "NAM" },
|
|
{ name = "Habakkuk", aliases = {"habakkuk","hab","hb"}, chapters = 3, osis = "HAB" },
|
|
{ name = "Zephaniah", aliases = {"zephaniah","zeph","zep"}, chapters = 3, osis = "ZEP" },
|
|
{ name = "Haggai", aliases = {"haggai","hag","hg"}, chapters = 2, osis = "HAG" },
|
|
{ name = "Zechariah", aliases = {"zechariah","zech","zec"}, chapters = 14, osis = "ZEC" },
|
|
{ name = "Malachi", aliases = {"malachi","mal","ml"}, chapters = 4, osis = "MAL" },
|
|
{ name = "Matthew", aliases = {"matthew","matt","mt"}, chapters = 28, osis = "MAT" },
|
|
{ name = "Mark", aliases = {"mark","mk","mrk"}, chapters = 16, osis = "MRK" },
|
|
{ name = "Luke", aliases = {"luke","lk","luk"}, chapters = 24, osis = "LUK" },
|
|
{ name = "John", aliases = {"john","jn","jhn"}, chapters = 21, osis = "JHN" },
|
|
{ name = "Acts", aliases = {"acts","ac","acts of the apostles"}, chapters = 28, osis = "ACT" },
|
|
{ name = "Romans", aliases = {"romans","rom","ro"}, chapters = 16, osis = "ROM" },
|
|
{ name = "1 Corinthians", aliases = {"1corinthians","1cor","1co","i corinthians","1 cor"}, chapters = 16, osis = "1CO" },
|
|
{ name = "2 Corinthians", aliases = {"2corinthians","2cor","2co","ii corinthians","2 cor"}, chapters = 13, osis = "2CO" },
|
|
{ name = "Galatians", aliases = {"galatians","gal","ga"}, chapters = 6, osis = "GAL" },
|
|
{ name = "Ephesians", aliases = {"ephesians","eph","ep"}, chapters = 6, osis = "EPH" },
|
|
{ name = "Philippians", aliases = {"philippians","phil","php"}, chapters = 4, osis = "PHP" },
|
|
{ name = "Colossians", aliases = {"colossians","col","co"}, chapters = 4, osis = "COL" },
|
|
{ name = "1 Thessalonians", aliases = {"1thessalonians","1thess","1th","i thessalonians","1 thess"}, chapters = 5, osis = "1TH" },
|
|
{ name = "2 Thessalonians", aliases = {"2thessalonians","2thess","2th","ii thessalonians","2 thess"}, chapters = 3, osis = "2TH" },
|
|
{ name = "1 Timothy", aliases = {"1timothy","1tim","1ti","i timothy","1 tim"}, chapters = 6, osis = "1TI" },
|
|
{ name = "2 Timothy", aliases = {"2timothy","2tim","2ti","ii timothy","2 tim"}, chapters = 4, osis = "2TI" },
|
|
{ name = "Titus", aliases = {"titus","tit","ti"}, chapters = 3, osis = "TIT" },
|
|
{ name = "Philemon", aliases = {"philemon","philem","phm"}, chapters = 1, osis = "PHM" },
|
|
{ name = "Hebrews", aliases = {"hebrews","heb","he"}, chapters = 13, osis = "HEB" },
|
|
{ name = "James", aliases = {"james","jas","jm"}, chapters = 5, osis = "JAS" },
|
|
{ name = "1 Peter", aliases = {"1peter","1pet","1pe","i peter","1 pet"}, chapters = 5, osis = "1PE" },
|
|
{ name = "2 Peter", aliases = {"2peter","2pet","2pe","ii peter","2 pet"}, chapters = 3, osis = "2PE" },
|
|
{ name = "1 John", aliases = {"1john","1jn","i john","1 jn"}, chapters = 5, osis = "1JN" },
|
|
{ name = "2 John", aliases = {"2john","2jn","ii john","2 jn"}, chapters = 1, osis = "2JN" },
|
|
{ name = "3 John", aliases = {"3john","3jn","iii john","3 jn"}, chapters = 1, osis = "3JN" },
|
|
{ name = "Jude", aliases = {"jude","jud"}, chapters = 1, osis = "JUD" },
|
|
{ name = "Revelation", aliases = {"revelation","rev","re","revelations"}, chapters = 22, osis = "REV" },
|
|
}
|
|
|
|
-- Build alias lookup table
|
|
local ALIAS = {}
|
|
local function _norm(s) return (s or ""):lower():gsub("[%s%p]+","") end
|
|
for _, b in ipairs(BOOKS) do
|
|
ALIAS[_norm(b.name)] = b
|
|
for _, a in ipairs(b.aliases) do ALIAS[_norm(a)] = b end
|
|
end
|
|
|
|
------------------------------------------------------------
|
|
-- State management
|
|
------------------------------------------------------------
|
|
local suggestions = {} -- Store all clickable suggestions with their URLs
|
|
local currentQuery = ""
|
|
|
|
------------------------------------------------------------
|
|
-- Helper functions
|
|
------------------------------------------------------------
|
|
local function trim(s) return (s or ""):match("^%s*(.-)%s*$") end
|
|
|
|
-- Enhanced parser that handles multi-word book names better
|
|
local function parse_book_chapter(q)
|
|
q = trim(q or "")
|
|
if q == "" then return nil, nil end
|
|
|
|
-- Try to match book name with optional chapter
|
|
-- This pattern allows for multi-word book names
|
|
local book_part, chapter_part = q:match("^(.-)%s+(%d+)$")
|
|
|
|
if book_part and chapter_part then
|
|
-- Found both book and chapter
|
|
return trim(book_part), tonumber(chapter_part)
|
|
else
|
|
-- No chapter number found, treat entire query as book name
|
|
return q, nil
|
|
end
|
|
end
|
|
|
|
-- Enhanced book resolver for better multi-word matching
|
|
local function resolve_book(letters)
|
|
if not letters then return nil end
|
|
|
|
local key = _norm(letters)
|
|
|
|
-- Exact match first
|
|
if ALIAS[key] then return ALIAS[key] end
|
|
|
|
-- Try partial matching for better multi-word support
|
|
local matches = {}
|
|
for ak, b in pairs(ALIAS) do
|
|
if ak:find("^" .. key) then
|
|
table.insert(matches, {alias = ak, book = b, priority = 1})
|
|
elseif ak:find(key, 1, true) then
|
|
table.insert(matches, {alias = ak, book = b, priority = 2})
|
|
end
|
|
end
|
|
|
|
-- Return best match if found
|
|
if #matches > 0 then
|
|
table.sort(matches, function(a, b)
|
|
if a.priority == b.priority then
|
|
return #a.alias < #b.alias -- Prefer shorter matches
|
|
end
|
|
return a.priority < b.priority
|
|
end)
|
|
return matches[1].book
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
local function build_youversion_url(osis, chapter)
|
|
return string.format("youversion://bible?reference=%s.%d", osis, chapter)
|
|
end
|
|
|
|
-- Get matching books based on query
|
|
local function get_matching_books(query)
|
|
if not query or query == "" then
|
|
return BOOKS -- Return all books if no query
|
|
end
|
|
|
|
local matches = {}
|
|
local key = _norm(query)
|
|
|
|
for _, book in ipairs(BOOKS) do
|
|
local book_key = _norm(book.name)
|
|
local is_match = false
|
|
|
|
-- Check if book name starts with query
|
|
if book_key:find("^" .. key) then
|
|
is_match = true
|
|
else
|
|
-- Check aliases
|
|
for _, alias in ipairs(book.aliases) do
|
|
if _norm(alias):find("^" .. key) then
|
|
is_match = true
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
if is_match then
|
|
table.insert(matches, book)
|
|
end
|
|
end
|
|
|
|
-- If no matches found with prefix matching, try substring matching
|
|
if #matches == 0 then
|
|
for _, book in ipairs(BOOKS) do
|
|
local book_key = _norm(book.name)
|
|
if book_key:find(key, 1, true) then
|
|
table.insert(matches, book)
|
|
end
|
|
end
|
|
end
|
|
|
|
return #matches > 0 and matches or BOOKS
|
|
end
|
|
|
|
------------------------------------------------------------
|
|
-- Main search function
|
|
------------------------------------------------------------
|
|
function on_search(query)
|
|
currentQuery = query
|
|
suggestions = {}
|
|
|
|
local book_part, chapter = parse_book_chapter(query)
|
|
|
|
if book_part then
|
|
local specific_book = resolve_book(book_part)
|
|
|
|
if specific_book and chapter then
|
|
-- Specific book and chapter requested
|
|
if chapter >= 1 and chapter <= specific_book.chapters then
|
|
local url = build_youversion_url(specific_book.osis, chapter)
|
|
suggestions[1] = {
|
|
text = string.format("Open %s %d in YouVersion", specific_book.name, chapter),
|
|
url = url
|
|
}
|
|
search:show_buttons({suggestions[1].text})
|
|
return
|
|
else
|
|
search:show_lines({
|
|
string.format("%s only has %d chapter%s",
|
|
specific_book.name,
|
|
specific_book.chapters,
|
|
specific_book.chapters == 1 and "" or "s")
|
|
})
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Show book/chapter suggestions
|
|
local matching_books = get_matching_books(book_part)
|
|
local button_texts = {}
|
|
local max_suggestions = 10 -- Limit number of suggestions shown
|
|
local suggestion_count = 0
|
|
|
|
for _, book in ipairs(matching_books) do
|
|
if suggestion_count >= max_suggestions then break end
|
|
|
|
-- If book has few chapters, show all in one line
|
|
if book.chapters <= 5 then
|
|
local chapters_text = ""
|
|
for ch = 1, book.chapters do
|
|
if ch > 1 then chapters_text = chapters_text .. ", " end
|
|
chapters_text = chapters_text .. ch
|
|
end
|
|
suggestion_count = suggestion_count + 1
|
|
local idx = #suggestions + 1
|
|
suggestions[idx] = {
|
|
text = string.format("%s (Ch: %s)", book.name, chapters_text),
|
|
book = book,
|
|
all_chapters = true
|
|
}
|
|
table.insert(button_texts, suggestions[idx].text)
|
|
else
|
|
-- For books with many chapters, show range
|
|
suggestion_count = suggestion_count + 1
|
|
local idx = #suggestions + 1
|
|
suggestions[idx] = {
|
|
text = string.format("%s (1-%d chapters)", book.name, book.chapters),
|
|
book = book,
|
|
all_chapters = true
|
|
}
|
|
table.insert(button_texts, suggestions[idx].text)
|
|
end
|
|
end
|
|
|
|
if #button_texts == 0 then
|
|
search:show_lines({"No matching books found. Try: Genesis, John, Psalms..."})
|
|
else
|
|
search:show_buttons(button_texts)
|
|
end
|
|
end
|
|
|
|
------------------------------------------------------------
|
|
-- Handle button clicks
|
|
------------------------------------------------------------
|
|
function on_click(index)
|
|
if not index then index = 1 end
|
|
|
|
local suggestion = suggestions[index]
|
|
if not suggestion then return false end
|
|
|
|
-- If it's a direct chapter link, open it
|
|
if suggestion.url then
|
|
system:open_browser(suggestion.url)
|
|
return true
|
|
end
|
|
|
|
-- If it's a book suggestion, show its chapters
|
|
if suggestion.book and suggestion.all_chapters then
|
|
local book = suggestion.book
|
|
suggestions = {}
|
|
local button_texts = {}
|
|
|
|
-- Create buttons for each chapter
|
|
for ch = 1, book.chapters do
|
|
local idx = #suggestions + 1
|
|
local url = build_youversion_url(book.osis, ch)
|
|
suggestions[idx] = {
|
|
text = string.format("%s %d", book.name, ch),
|
|
url = url
|
|
}
|
|
table.insert(button_texts, suggestions[idx].text)
|
|
end
|
|
|
|
search:show_buttons(button_texts)
|
|
return false -- Don't close search, allow chapter selection
|
|
end
|
|
|
|
return false
|
|
end
|
|
|