feat: add send image option to notifs

This commit is contained in:
2026-06-03 07:21:07 -05:00
parent cedab6d3b2
commit 080fda0f1a
7 changed files with 57 additions and 21 deletions

View File

@@ -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

View File

@@ -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")

View File

@@ -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(){
+'<input type="checkbox" data-notif-idx="'+idx+'" data-notif-ev="'+ev+'" '+checked
+' onchange="notifToggleEvent('+idx+',\''+ev+'\')" style="width:auto;margin:0"> '+lbl+'</label>';
}).join(' ');
var imgCheck='<label style="display:inline-flex;align-items:center;gap:3px;font-size:11px;cursor:pointer;margin-left:4px;padding-left:8px;border-left:1px solid var(--border)" title="'+(T.settings_notif_send_image||'Send image')+'">'
+'<input type="checkbox" '+(row.include_image?'checked':'')+' onchange="notifToggleImage('+idx+')" style="width:auto;margin:0"> '
+'📷 '+(T.settings_notif_send_image||'Image')+'</label>';
return '<div style="background:var(--raised);border-radius:6px;padding:8px;margin-bottom:6px">'
+'<div style="display:flex;gap:6px;align-items:center;margin-bottom:6px">'
+'<input type="text" value="'+_escHtml(row.url)+'" placeholder="discord://… telegram://… gotify://…"'
@@ -919,14 +922,14 @@ function notifRefreshDOM(){
+'<button onclick="notifTest('+idx+')" style="background:var(--raised2,var(--raised));border:1px solid var(--border);color:var(--txt);border-radius:4px;cursor:pointer;padding:2px 8px;font-size:11px" id="notif-test-btn-'+idx+'">'+(T.settings_notif_test||'Test')+'</button>'
+'<button onclick="notifRemoveRow('+idx+')" style="background:none;border:none;color:var(--err);cursor:pointer;font-size:16px;line-height:1" title="remove">✕</button>'
+'</div>'
+'<div style="display:flex;flex-wrap:wrap;gap:8px">'+evChecks+'</div>'
+'<div style="display:flex;flex-wrap:wrap;gap:8px;align-items:center">'+evChecks+imgCheck+'</div>'
+'<div id="notif-test-status-'+idx+'" style="font-size:11px;margin-top:4px"></div>'
+'</div>';
}).join('');
}
function _escHtml(s){return (s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');}
function notifAddRow(){
_notifRows.push({url:'',events:['finished','failed']});
_notifRows.push({url:'',events:['finished','failed'],include_image:false});
notifRefreshDOM();
}
function notifRemoveRow(idx){
@@ -942,9 +945,12 @@ function notifToggleEvent(idx,ev){
var pos=evs.indexOf(ev);
if(pos>=0)evs.splice(pos,1);else evs.push(ev);
}
function notifToggleImage(idx){
if(_notifRows[idx])_notifRows[idx].include_image=!_notifRows[idx].include_image;
}
function notifCollect(){
return _notifRows.filter(function(r){return r.url.trim();}).map(function(r){
return {url:r.url.trim(),events:r.events};
return {url:r.url.trim(),events:r.events,include_image:!!r.include_image};
});
}
function notifTest(idx){

View File

@@ -260,5 +260,6 @@
"settings_notif_interval_lbl": "Wiederholungsintervall",
"settings_notif_min_unit": "Min.",
"settings_notif_layers_unit": "Layer",
"settings_notif_zero_off": "(0 = aus)"
"settings_notif_zero_off": "(0 = aus)",
"settings_notif_send_image": "Bild"
}

View File

@@ -260,5 +260,6 @@
"settings_notif_interval_lbl": "Repeat interval",
"settings_notif_min_unit": "min",
"settings_notif_layers_unit": "layers",
"settings_notif_zero_off": "(0 = off)"
"settings_notif_zero_off": "(0 = off)",
"settings_notif_send_image": "Image"
}

View File

@@ -260,5 +260,6 @@
"settings_notif_interval_lbl": "Intervalo de repetición",
"settings_notif_min_unit": "min",
"settings_notif_layers_unit": "capas",
"settings_notif_zero_off": "(0 = off)"
"settings_notif_zero_off": "(0 = off)",
"settings_notif_send_image": "Imagen"
}

View File

@@ -260,5 +260,6 @@
"settings_notif_interval_lbl": "重复间隔",
"settings_notif_min_unit": "分钟",
"settings_notif_layers_unit": "层",
"settings_notif_zero_off": "(0 = 关闭)"
"settings_notif_zero_off": "(0 = 关闭)",
"settings_notif_send_image": "图片"
}