--!nonstrict
-- Daily Wallpaper service for Noctalia 5.
-- Fetches one Bing or NASA image per day, caches it locally, and applies it via
-- the native Noctalia wallpaper API.
local CHECK_INTERVAL_MS = 10 * 60 * 1000
local RETENTION_SECONDS = 5 * 24 * 60 * 60
local PLUGIN_CACHE_DIR = "daily-wallpaper"
local checking = false
local lastAppliedPath = ""
local lastCheckedKey = ""
local lastErrorNotificationDate = ""
noctalia.setUpdateInterval(CHECK_INTERVAL_MS)
local function todayString()
return os.date("%Y-%m-%d")
end
local function normalizeLocale(value)
if type(value) ~= "string" then
return "en-us"
end
local locale = noctalia.string.trim(value):lower():gsub("_", "-")
if locale == "" or locale:find("[^%w%-]") then
return "en-us"
end
return locale
end
local function source()
local configured = noctalia.getConfig("source")
if configured == "nasa" then
return "nasa"
end
return "bing"
end
local function downloadDir()
return noctalia.expandPath(`{noctalia.wallpaperDirectory()}/{PLUGIN_CACHE_DIR}`)
end
local function cachePath(prefix, date)
return `{downloadDir()}/{prefix}-{date}.jpg`
end
local function decodeHtml(value)
local decoded = value:gsub("&", "&")
decoded = decoded:gsub(""", '"')
decoded = decoded:gsub("'", "'")
return decoded
end
local function absoluteNasaUrl(url)
if url == nil or url == "" then
return ""
end
url = decodeHtml(url)
if url:match("^https?://") then
return url
end
if url:match("^//") then
return `https:{url}`
end
if url:sub(1, 1) == "/" then
return `https://www.nasa.gov{url}`
end
return `https://www.nasa.gov/{url}`
end
local function applyWallpaper(path, downloaded)
noctalia.setWallpaper(path)
lastAppliedPath = path
lastCheckedKey = `{source()}:{todayString()}`
noctalia.state.set("status", {
state = "applied",
path = path,
downloaded = downloaded,
})
noctalia.log(`Daily Wallpaper applied: {path}`)
end
local function cleanupOldWallpapers(dir)
local entries = noctalia.listDir(dir)
if type(entries) ~= "table" then
return
end
local cutoff = os.time() - RETENTION_SECONDS
for _, name in ipairs(entries) do
if name:match("^bing%-.+%-%d%d%d%d%-%d%d%-%d%d%.jpg$") or name:match("^nasa%-%d%d%d%d%-%d%d%-%d%d%.jpg$") then
local path = `{dir}/{name}`
local info = noctalia.fileInfo(path)
if type(info) == "table" and type(info.mtime) == "number" and info.mtime < cutoff then
noctalia.removeFile(path)
end
end
end
end
local function fail(message)
checking = false
noctalia.state.set("status", {
state = "error",
message = message,
})
local date = todayString()
if lastErrorNotificationDate ~= date then
lastErrorNotificationDate = date
noctalia.notifyError(noctalia.tr("notify.failed"), message)
end
noctalia.log(`Daily Wallpaper failed: {message}`)
end
local function downloadAndApply(primaryUrl, fallbackUrl, dest)
local function tryDownload(url, fallback)
if type(url) ~= "string" or url == "" then
if fallback ~= "" then
tryDownload(fallback, "")
else
fail("No wallpaper URL found")
end
return
end
local started = noctalia.download(url, dest, function(ok)
if ok then
checking = false
cleanupOldWallpapers(downloadDir())
applyWallpaper(dest, true)
noctalia.notify(noctalia.tr("notify.applied"), dest)
return
end
if fallback ~= "" then
tryDownload(fallback, "")
else
fail("Download failed")
end
end)
if not started then
fail("Could not start wallpaper download")
end
end
tryDownload(primaryUrl, fallbackUrl or "")
end
local function resolveBing(locale, done)
local url = `https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1&mkt={noctalia.string.urlEncode(locale)}`
local started = noctalia.http({ url = url }, function(res)
if not res.ok or res.status < 200 or res.status >= 300 then
fail(`Bing request failed: HTTP {res.status}`)
return
end
local parsed, err = noctalia.json.decode(res.body)
if parsed == nil then
fail(`Bing response parse failed: {err or "invalid JSON"}`)
return
end
local first = parsed.images and parsed.images[1]
local urlBase = first and first.urlbase
if type(urlBase) ~= "string" or urlBase == "" then
fail("Bing response did not include an image URL")
return
end
done(`bing-{locale}`, `https://www.bing.com{urlBase}_UHD.jpg`, `https://www.bing.com{urlBase}_1920x1080.jpg`)
end)
if not started then
fail("Could not start Bing request")
end
end
local function resolveNasa(done)
local started = noctalia.http({ url = "https://www.nasa.gov/image-of-the-day/" }, function(res)
if not res.ok or res.status < 200 or res.status >= 300 then
fail(`NASA request failed: HTTP {res.status}`)
return
end
local page = res.body
local primary = page:match('class="[^"]-hds%-gallery%-image[^"]-"[^>]->.-
]-src="([^"]+)"')
or page:match('
]-class="[^"]-hds%-gallery%-image[^"]-"[^>]-src="([^"]+)"')
local fallback = page:match('