Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Source/BibleVerse.spoon/config.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--- Default configuration for BibleVerse Spoon.
return {
translation = "UBIO",
refresh_interval = 3600,
background = { color = { red = 0.1, green = 0.1, blue = 0.15 }, alpha = 0.9, corner_radius = 12 },
font = { name = nil, size = 14, color = { white = 0.95 }, reference_size = 12, reference_color = { red = 0.6, green = 0.7, blue = 0.9 } },
width = 400,
height = 165,
position = { default = { x = -410, y = -175 } }
}
7 changes: 7 additions & 0 deletions Source/BibleVerse.spoon/docs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "BibleVerse",
"version": "2.0",
"description": "Desktop widget displaying random New Testament verses in Ukrainian or English",
"author": "Oleksandr",
"license": "MIT"
}
47 changes: 47 additions & 0 deletions Source/BibleVerse.spoon/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
--- BibleVerse Spoon - displays random Bible verses as desktop widget.
local obj = {}
obj.__index = obj
obj.name = "BibleVerse"
obj.version = "2.0"
obj.author = "Oleksandr"
obj.license = "MIT - https://opensource.org/licenses/MIT"
obj.homepage = "https://github.com/auraz/BibleVerseSpoon"

local verse = dofile(hs.spoons.resourcePath("verse.lua"))
local widget = dofile(hs.spoons.resourcePath("widget.lua"))
local default_config = dofile(hs.spoons.resourcePath("config.lua"))

obj.config = {}
for k, v in pairs(default_config) do obj.config[k] = v end

local state = { canvas = nil, timer = nil, watcher = nil }

function obj:refresh()
verse.fetch(self.config.translation, function(data)
if not data then return end
local text = verse.clean_text(data.text)
local ref = verse.format_reference(data, self.config.translation)
state.canvas = widget.render(state, text, ref, self.config, data)
end)
return self
end

function obj:start()
self:refresh()
state.timer = hs.timer.doEvery(self.config.refresh_interval, function() self:refresh() end)
state.watcher = hs.caffeinate.watcher.new(function(e)
if e == hs.caffeinate.watcher.systemDidWake then self:refresh() end
end)
state.watcher:start()
return self
end

function obj:stop()
if state.timer then state.timer:stop() end
if state.watcher then state.watcher:stop() end
widget.destroy(state.canvas)
state = { canvas = nil, timer = nil, watcher = nil }
return self
end

return obj
19 changes: 19 additions & 0 deletions Source/BibleVerse.spoon/translations.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--- Book name translations for New Testament (books 40-66).
return {
UBIO = {
[40] = "Матвія", [41] = "Марка", [42] = "Луки", [43] = "Івана", [44] = "Дії",
[45] = "Римлян", [46] = "1 Коринтян", [47] = "2 Коринтян", [48] = "Галатів", [49] = "Ефесян",
[50] = "Филип'ян", [51] = "Колосян", [52] = "1 Солунян", [53] = "2 Солунян", [54] = "1 Тимофія",
[55] = "2 Тимофія", [56] = "Тита", [57] = "Филимона", [58] = "Євреїв", [59] = "Якова",
[60] = "1 Петра", [61] = "2 Петра", [62] = "1 Івана", [63] = "2 Івана", [64] = "3 Івана",
[65] = "Юди", [66] = "Об'явлення"
},
KJV = {
[40] = "Matthew", [41] = "Mark", [42] = "Luke", [43] = "John", [44] = "Acts",
[45] = "Romans", [46] = "1 Corinthians", [47] = "2 Corinthians", [48] = "Galatians", [49] = "Ephesians",
[50] = "Philippians", [51] = "Colossians", [52] = "1 Thessalonians", [53] = "2 Thessalonians", [54] = "1 Timothy",
[55] = "2 Timothy", [56] = "Titus", [57] = "Philemon", [58] = "Hebrews", [59] = "James",
[60] = "1 Peter", [61] = "2 Peter", [62] = "1 John", [63] = "2 John", [64] = "3 John",
[65] = "Jude", [66] = "Revelation"
}
}
32 changes: 32 additions & 0 deletions Source/BibleVerse.spoon/verse.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
--- Verse fetching and parsing functions.
local translations = dofile(hs.spoons.resourcePath("translations.lua"))

local M = {}
local API_BASE = "https://bolls.life/get-random-verse/"

function M.clean_text(raw)
return raw:gsub("<[^>]+>", ""):gsub("&nbsp;", " "):gsub("%s+", " "):match("^%s*(.-)%s*$")
end

function M.get_book_name(book_num, translation)
local books = translations[translation]
return books and books[book_num] or ("Book " .. book_num)
end

function M.format_reference(data, translation)
return M.get_book_name(data.book, translation) .. " " .. data.chapter .. ":" .. data.verse
end

function M.fetch(translation, callback)
local function try_fetch()
hs.http.asyncGet(API_BASE .. translation .. "/", nil, function(status, body)
if status ~= 200 then callback(nil) return end
local data = hs.json.decode(body)
if data.book < 40 or data.book > 66 then try_fetch() return end
callback(data)
end)
end
try_fetch()
end

return M
46 changes: 46 additions & 0 deletions Source/BibleVerse.spoon/widget.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
--- Widget rendering functions.
local M = {}

function M.calculate_position(screen_frame, config, screen_name)
local pos = config.position[screen_name] or config.position.default
local x = pos.x >= 0 and pos.x or (screen_frame.w + pos.x - config.width)
local y = pos.y >= 0 and pos.y or (screen_frame.h + pos.y - config.height)
return { x = screen_frame.x + x, y = screen_frame.y + y }
end

function M.create_elements(text, reference, config)
local bg = config.background
local font = config.font
return {
{ id = "bg", type = "rectangle", action = "fill", trackMouseUp = true, roundedRectRadii = { xRadius = bg.corner_radius, yRadius = bg.corner_radius }, fillColor = { red = bg.color.red, green = bg.color.green, blue = bg.color.blue, alpha = bg.alpha } },
{ type = "rectangle", action = "stroke", roundedRectRadii = { xRadius = bg.corner_radius, yRadius = bg.corner_radius }, strokeColor = { red = 0.3, green = 0.4, blue = 0.5, alpha = 0.5 }, strokeWidth = 1 },
{ type = "text", text = text, textColor = font.color, textSize = font.size, textFont = font.name, textAlignment = "left", frame = { x = "5%", y = "8%", w = "90%", h = "68%" } },
{ type = "text", text = "— " .. reference, textColor = font.reference_color, textSize = font.reference_size, textFont = font.name, textAlignment = "right", frame = { x = "5%", y = "78%", w = "90%", h = "18%" } }
}
end

function M.render(state, text, reference, config, verse_data)
if state.canvas then state.canvas:delete() end
local screen = hs.screen.mainScreen()
local frame = screen:frame()
local pos = M.calculate_position(frame, config, screen:name())
local canvas = hs.canvas.new({ x = pos.x, y = pos.y, w = config.width, h = config.height })
canvas:appendElements(M.create_elements(text, reference, config))
canvas:level(hs.canvas.windowLevels.floating)
canvas:behavior(hs.canvas.windowBehaviors.canJoinAllSpaces)
canvas:mouseCallback(function(c, msg, id, x, y)
if msg == "mouseUp" and verse_data then
local url = "https://bolls.life/" .. verse_data.translation .. "/" .. verse_data.book .. "/" .. verse_data.chapter .. "/"
hs.urlevent.openURL(url)
end
end)
canvas:canvasMouseEvents(false, true, false, false)
canvas:show()
return canvas
end

function M.destroy(canvas)
if canvas then canvas:delete() end
end

return M