--!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('