diff --git a/daily-wallpaper/README.md b/daily-wallpaper/README.md index d8097eda..53060306 100644 --- a/daily-wallpaper/README.md +++ b/daily-wallpaper/README.md @@ -1,14 +1,19 @@ # Daily Wallpaper -Daily Wallpaper fetches Bing's image of the day or NASA's image of the day and -applies it through Noctalia's wallpaper API. +Daily Wallpaper fetches Bing's image of the day or NASA's image of the day, +applies it through Noctalia's wallpaper API, and shows the caption/credit that +came with it — on the bar, in a panel, and on the desktop. ## Plugin | Field | Value | | --- | --- | | ID | `nzlov/daily-wallpaper` | -| Entry | Service: `service` | +| Entries | Service: `service`; bar widget: `widget`; panel: `panel`; desktop widget: `desktop` | + +## Requirements + +Install `xdg-utils` on `PATH` (for the panel's "Learn more" button, via `xdg-open`). ## Usage @@ -19,12 +24,27 @@ per source and Bing locale each day. Choose Bing or NASA under the plugin's settings. Bing accepts a market locale such as `en-US`, `de-DE`, or `fr-FR`; NASA ignores the locale setting. +Add the `widget` bar entry and/or the `desktop` desktop widget from Settings +to see today's caption. Clicking the bar glyph (or running +`noctalia msg panel-toggle nzlov/daily-wallpaper:panel`) opens the full panel +with the caption, credit (Bing only), and a "Learn more" link — Bing's quiz +page, or NASA's full article about the image. + ## Settings | Setting | Type | Default | Description | | --- | --- | --- | --- | | `source` | `select` | `bing` | Selects Bing or NASA as the daily image source. | | `locale` | `string` | *(automatic)* | Bing market locale; an empty value uses the service default. | +| `widget.glyph` | `glyph` | `photo` | Bar icon. | +| `widget.show_text` | `bool` | `true` | Show a truncated caption next to the bar glyph. | +| `widget.max_chars` | `int` | `28` | Max caption length shown on the bar before truncating with an ellipsis. | + +## IPC + +```sh +noctalia msg plugin nzlov/daily-wallpaper:service all refresh +``` ## Notes @@ -32,3 +52,11 @@ The service contacts the selected provider and downloads images into a dedicated `daily-wallpaper` cache directory. It removes cached images older than five days. Repeated failures are logged, but error notifications are limited to once per day. + +Neither source gives a wallpaper-application step any reason to keep the +caption text that comes with the image, so it is captured separately at the +point each source resolves and published as plugin state for the bar widget, +panel, and desktop widget to display. Bing's `copyright` field is one caption +line, e.g. "Runners at the base of Victoria Falls, Zimbabwe (© Byron.../Getty +Images)", which is split into a title and a credit; NASA's gallery-item +caption is a single sentence with no separate credit line. diff --git a/daily-wallpaper/daily_wallpaper.luau b/daily-wallpaper/daily_wallpaper.luau index dce80ede..c0a3f411 100644 --- a/daily-wallpaper/daily_wallpaper.luau +++ b/daily-wallpaper/daily_wallpaper.luau @@ -1,7 +1,8 @@ --!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. +-- the native Noctalia wallpaper API. Also captures each source's caption and +-- publishes it as "today" state for widget.luau/panel.luau/desktop_widget.luau. local CHECK_INTERVAL_MS = 10 * 60 * 1000 local RETENTION_SECONDS = 5 * 24 * 60 * 60 @@ -49,10 +50,40 @@ end local function decodeHtml(value) local decoded = value:gsub("&", "&") decoded = decoded:gsub(""", '"') - decoded = decoded:gsub("'", "'") + -- Numeric entities, e.g. ' or '. + decoded = decoded:gsub("&#(%d+);", function(n) + local code = tonumber(n) + return (code and code < 256) and string.char(code) or "" + end) return decoded end +-- Bing's `copyright` field is "Caption text (© Photographer/Source)"; split +-- off the trailing credit so title and credit can be shown separately. +local function splitCopyright(copyright) + local body, credit = copyright:match("^(.-)%s*%(([^()]*)%)%s*$") + if body and credit then + return decodeHtml(body), decodeHtml(credit) + end + return decodeHtml(copyright), "" +end + +-- Publishes today's caption/credit/link for the bar widget, panel, and +-- desktop widget to read via noctalia.state. +local function publishToday(selectedSource, date, info) + if type(info) ~= "table" then + return + end + noctalia.state.set("today", { + date = date, + source = selectedSource, + title = info.title or "", + credit = info.credit or "", + link = info.link or "", + }) + noctalia.log(`Daily Wallpaper caption: {info.title or ""}`) +end + local function absoluteNasaUrl(url) if url == nil or url == "" then return "" @@ -170,7 +201,12 @@ local function resolveBing(locale, done) return end - done(`bing-{locale}`, `https://www.bing.com{urlBase}_UHD.jpg`, `https://www.bing.com{urlBase}_1920x1080.jpg`) + local title, credit = splitCopyright(first.copyright or "") + local link = (type(first.copyrightlink) == "string" and first.copyrightlink ~= "") + and first.copyrightlink or "" + + done(`bing-{locale}`, `https://www.bing.com{urlBase}_UHD.jpg`, `https://www.bing.com{urlBase}_1920x1080.jpg`, + { title = title, credit = credit, link = link }) end) if not started then @@ -178,6 +214,16 @@ local function resolveBing(locale, done) end end +-- A small window of markup around the first gallery item, so later matches +-- run against a bounded string instead of the whole page. +local function firstNasaGalleryItem(page) + local s = page:find("hds-gallery-item-single hds-gallery-image", 1, true) + if not s then + return nil + end + return page:sub(s, s + 4000) +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 @@ -186,8 +232,17 @@ local function resolveNasa(done) end local page = res.body - local primary = page:match('class="[^"]-hds%-gallery%-image[^"]-"[^>]->.-]-src="([^"]+)"') - or page:match(']-class="[^"]-hds%-gallery%-image[^"]-"[^>]-src="([^"]+)"') + local item = firstNasaGalleryItem(page) + + local primary, caption, link + if item then + primary = item:match(']-src="([^"]+)"') + -- NASA has no separate credit line (unlike Bing), so the whole + -- caption becomes the title and credit stays empty. + caption = item:match('class="hds%-gallery%-item%-caption[^"]*">([^<]*)') + link = item:match('class="hds%-gallery%-item%-link[^"]*"%s+href="([^"]+)"') + end + local fallback = page:match('/dev/null 2>&1") + end +end + +noctalia.state.watch("today", render) +noctalia.state.watch("status", render) diff --git a/daily-wallpaper/plugin.toml b/daily-wallpaper/plugin.toml index 56ddde89..4bbecf3e 100644 --- a/daily-wallpaper/plugin.toml +++ b/daily-wallpaper/plugin.toml @@ -1,13 +1,13 @@ id = "nzlov/daily-wallpaper" name = "Daily Wallpaper" -version = "1.0.0" +version = "1.1.0" plugin_api = 3 author = "nzlov" license = "MIT" -dependencies = [] -tags = ["wallpaper", "desktop"] +dependencies = ["xdg-open"] +tags = ["wallpaper", "desktop", "bar", "panel", "service"] icon = "photo" -description = "Fetches a new daily wallpaper from Bing or NASA." +description = "Fetches a new daily wallpaper from Bing or NASA, and shows its caption on the bar, in a panel, and on the desktop." [[setting]] key = "source" @@ -29,3 +29,39 @@ default = "" [[service]] id = "service" entry = "daily_wallpaper.luau" + +[[widget]] +id = "widget" +entry = "widget.luau" + + [[widget.setting]] + key = "glyph" + type = "glyph" + label_key = "settings.widget.glyph.label" + default = "photo" + + [[widget.setting]] + key = "show_text" + type = "bool" + label_key = "settings.widget.show_text.label" + default = true + + [[widget.setting]] + key = "max_chars" + type = "int" + label_key = "settings.widget.max_chars.label" + default = 28 + min = 8 + max = 80 + +[[panel]] +id = "panel" +entry = "panel.luau" +width = 420 +height = 250 +placement = "attached" +open_near_click = true + +[[desktop_widget]] +id = "desktop" +entry = "desktop_widget.luau" diff --git a/daily-wallpaper/translations/en.json b/daily-wallpaper/translations/en.json index b6e215f2..92ea385e 100644 --- a/daily-wallpaper/translations/en.json +++ b/daily-wallpaper/translations/en.json @@ -14,6 +14,31 @@ "bing": "Bing", "nasa": "NASA" } + }, + "widget": { + "glyph": { + "label": "Glyph" + }, + "show_text": { + "label": "Show caption text next to glyph" + }, + "max_chars": { + "label": "Max caption characters on the bar" + } } + }, + "widget": { + "title": "Daily Wallpaper", + "loading_short": "Loading…", + "tooltip_loading": "Fetching today's wallpaper caption…", + "tooltip_error": "Could not fetch today's wallpaper caption.", + "panel_launch_failed": "Could not open the Daily Wallpaper panel." + }, + "panel": { + "title": "Daily Wallpaper", + "loading": "Fetching today's wallpaper caption…", + "error": "Could not fetch today's wallpaper caption.", + "date": "{source} · {date}", + "learn_more": "Learn more" } } diff --git a/daily-wallpaper/widget.luau b/daily-wallpaper/widget.luau new file mode 100644 index 00000000..c236dc02 --- /dev/null +++ b/daily-wallpaper/widget.luau @@ -0,0 +1,52 @@ +--!nonstrict +-- Bar widget: a glyph (+ optional short caption text) reflecting today's +-- wallpaper. Pure view over daily_wallpaper.luau's "today"/"status" state. +-- Click opens the panel with the full caption, credit, and "learn more" link. + +local function tr(key, args) return noctalia.tr(key, args) end + +local glyph = noctalia.getConfig("glyph") or "photo" +local showText = noctalia.getConfig("show_text") +local maxChars = tonumber(noctalia.getConfig("max_chars")) or 28 + +local today = noctalia.state.get("today") +local status = noctalia.state.get("status") + +local function truncate(text) + if #text <= maxChars then + return text + end + return text:sub(1, maxChars - 1) .. "…" +end + +local function render() + barWidget.setGlyph(glyph) + + if type(today) == "table" and type(today.title) == "string" and today.title ~= "" then + barWidget.setText(showText and truncate(today.title) or "") + barWidget.setTooltip(today.title) + else + barWidget.setText(showText and tr("widget.loading_short") or "") + local msg = (type(status) == "table" and status.state == "error") + and (status.message or tr("widget.tooltip_error")) + or tr("widget.tooltip_loading") + barWidget.setTooltip(msg) + end +end + +noctalia.state.watch("today", function(value) + today = value + render() +end) +noctalia.state.watch("status", function(value) + status = value + render() +end) + +function onClick() + if not noctalia.runAsync("noctalia msg panel-toggle 'nzlov/daily-wallpaper:panel'") then + noctalia.notifyError(tr("widget.title"), tr("widget.panel_launch_failed")) + end +end + +render()