diff --git a/config_loader.py b/config_loader.py index f4ed70b..71480e0 100644 --- a/config_loader.py +++ b/config_loader.py @@ -185,7 +185,8 @@ def list_notification_urls() -> list[dict]: if url: events_str = cfg.get("notifications", f"events_{idx}", fallback="finished,failed,cancelled") events = [e.strip() for e in events_str.split(",") if e.strip()] - result.append({"url": url, "events": events}) + include_image = cfg.get("notifications", f"image_{idx}", fallback="false").strip().lower() in ("1", "true", "yes") + result.append({"url": url, "events": events, "include_image": include_image}) idx += 1 return result diff --git a/kobrax_moonraker_bridge.py b/kobrax_moonraker_bridge.py index 9878386..79cd195 100644 --- a/kobrax_moonraker_bridge.py +++ b/kobrax_moonraker_bridge.py @@ -913,8 +913,8 @@ class KobraXBridge: # ------------------------------------------------------------------------- def _notify(self, event: str, filename: str = ""): - urls = [e["url"] for e in self._notification_urls if event in e.get("events", [])] - if not urls: + matching = [e for e in self._notification_urls if event in e.get("events", [])] + if not matching: return printer_name = self._state.get("printer_name", "KX-Bridge") titles = { @@ -927,10 +927,10 @@ class KobraXBridge: } title = titles.get(event, "KX-Bridge") if event == "progress": - pct = int(self._state.get("progress", 0) * 100) - curr = self._state.get("curr_layer", 0) + pct = int(self._state.get("progress", 0) * 100) + curr = self._state.get("curr_layer", 0) total = self._state.get("total_layers", 0) - rem = self._state.get("remain_time", 0) + rem = self._state.get("remain_time", 0) rem_str = f"{rem // 3600}h {(rem % 3600) // 60}m" if rem else "" parts = [f"{pct}%"] if total: @@ -945,14 +945,37 @@ class KobraXBridge: if filename and printer_name: body = f"{filename}\n{printer_name}" + urls_plain = [e["url"] for e in matching if not e.get("include_image")] + urls_image = [e["url"] for e in matching if e.get("include_image")] + jpeg_bytes = self.camera_cache.latest_jpeg if urls_image else b"" + def _send(): + import apprise, os, tempfile try: - import apprise - ap = apprise.Apprise() - for url in urls: - ap.add(url) - ap.notify(title=title, body=body) - log.info(f"Benachrichtigung '{event}' gesendet ({len(urls)} URL(s))") + if urls_plain: + ap = apprise.Apprise() + for u in urls_plain: + ap.add(u) + ap.notify(title=title, body=body) + if urls_image: + ap2 = apprise.Apprise() + for u in urls_image: + ap2.add(u) + attach = None + tmppath = None + if jpeg_bytes: + with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as f: + f.write(jpeg_bytes) + tmppath = f.name + attach = apprise.AppriseAttachment() + attach.add(tmppath) + ap2.notify(title=title, body=body, attach=attach) + if tmppath: + try: + os.unlink(tmppath) + except Exception: + pass + log.info(f"Benachrichtigung '{event}' gesendet ({len(matching)} URL(s))") except Exception as e: log.warning(f"Benachrichtigung fehlgeschlagen: {e}") @@ -3905,11 +3928,13 @@ class KobraXBridge: if not url: continue events = [e for e in entry.get("events", []) if e in valid_events] - entries.append({"url": url, "events": events}) + include_image = bool(entry.get("include_image", False)) + entries.append({"url": url, "events": events, "include_image": include_image}) cfg.add_section("notifications") for i, entry in enumerate(entries, 1): cfg.set("notifications", f"url_{i}", entry["url"]) cfg.set("notifications", f"events_{i}", ",".join(entry["events"])) + cfg.set("notifications", f"image_{i}", "true" if entry["include_image"] else "false") self._notification_urls = entries elif not cfg.has_section("notifications"): cfg.add_section("notifications") diff --git a/web/themes/default/app.js b/web/themes/default/app.js index cff6d42..6723a6c 100644 --- a/web/themes/default/app.js +++ b/web/themes/default/app.js @@ -894,7 +894,7 @@ function drawChart(id,_,series){ var _notifRows=[]; var _NOTIF_EVENTS=['started','finished','failed','cancelled','paused','progress']; function notifRenderList(entries){ - _notifRows=entries.map(function(e){return {url:e.url||'',events:e.events||[]};}); + _notifRows=entries.map(function(e){return {url:e.url||'',events:e.events||[],include_image:!!e.include_image};}); notifRefreshDOM(); } function notifRefreshDOM(){ @@ -912,6 +912,9 @@ function notifRefreshDOM(){ +' '+lbl+''; }).join(' '); + var imgCheck=''; return '