"""Notification helpers: email, pushover, mattermost, signal and dispatcher.""" from typing import Optional import http.client import urllib.parse import subprocess import smtplib import time import traceback DEFAULT_PUSHPROVIDERS = ["all", "pushover", "mattermost", "signal"] # module-level configuration set via setup() _config = {} def setup(cfg: dict): """Initialize notifier defaults from a configuration dict.""" global _config _config = dict(cfg) def send_email(aemail, smtpserver, sender, subject, body, debug=0): """Send a plain email via SMTP. Returns True on success.""" try: server = smtplib.SMTP(smtpserver) if debug > 0: server.set_debuglevel(1) server.sendmail(sender, aemail, body) except Exception as e: if debug: print("email send failed:", e) try: server.quit() except Exception: pass return False try: server.quit() except Exception: pass return True def email(subject: str, msg: str, debug: int = 0) -> bool: """Convenience wrapper exposed to the rest of the application. Uses module-level configuration to supply recipient list, smtp server and sender address. """ toaddrs = _config.get("AEMAIL") or _config.get("aemail") or _config.get("email_to") or [] fromemail = _config.get("fromemail") or _config.get("sender") or f"aew.heartbeat@{_config.get('domain','local') }" smtpserver = _config.get("SMTPSERVER") or _config.get("smtpserver") or _config.get("SMTPSERVER", "localhost") date = time.strftime("%a, %d %b %Y %H:%M:%S %z", time.localtime()) body = "To: %s\nFrom: %s\nSubject: %s\nDate: %s\n\n%s" % ( toaddrs[0] if toaddrs else "", fromemail, subject, date, msg, ) return send_email(toaddrs, smtpserver, fromemail, subject, body, debug=debug) def pushover(token: str, user: str, msg: str, debug: int = 0) -> bool: """Send message via Pushover API.""" conn = http.client.HTTPSConnection("api.pushover.net:443") try: conn.request( "POST", "/1/messages.json", urllib.parse.urlencode({"token": token, "user": user, "message": msg}), {"Content-type": "application/x-www-form-urlencoded"}, ) r = conn.getresponse() if debug: print("pushover response:", r.status, r.reason) return r.status == 200 except Exception as e: if debug: print("pushover error:", e) return False def pushmattermost(host: str, token: str, channel: str, msg: str, username: str = "hbd", icon: Optional[str] = None, debug: int = 0) -> bool: """Send a message to Mattermost via simple webhook driver if available. This helper tries to import mattermostdriver.Driver and uses webhooks if present. If the import fails it returns False. """ try: from mattermostdriver import Driver except Exception: return False ses = {"url": host, "scheme": "http", "basepath": "/api/v4", "port": 8065} mm = Driver(ses) payload = {"text": msg, "channel": channel, "username": username} if icon: payload["icon_url"] = icon try: rc = mm.webhooks.call_webhook(token, payload) if debug: print("mattermost rc:", rc) return bool(rc is None or rc == "") except Exception as e: if debug: print("mattermost error:", e) return False def pushsignal(signal_cli_bin: str, user: str, recipient: str, msg: str, debug: int = 0) -> bool: """Send a message via signal-cli (requires local installation). Uses subprocess to call signal-cli. Returns True if the command succeeded. """ CLI = [signal_cli_bin, "-u", user, "send", "-m", msg, recipient] if debug: print("signal cli: ", CLI) try: res = subprocess.run(CLI, capture_output=True) if res.returncode != 0: if debug: print("signal failed:", res.stderr.decode()) return False if debug: print("signal sent:", res.stdout.decode()) return True except Exception as e: if debug: print("signal exception:", e) return False def pushmsg(cfg: dict, msg: str, debug: int = 0): """Dispatch push notifications according to `cfg['pushsrv']`. cfg is expected to contain keys for different services when needed, e.g. - cfg['pushsrv'] : one of 'all', 'pushover', 'mattermost', 'signal' - cfg['pushover_token'], cfg['pushover_user'] - cfg['matter_host'], cfg['matter_token'], cfg['matter_channel'] - cfg['signal_cli'], cfg['signal_user'], cfg['signal_recipient'] Returns a dict of results per provider. """ results = {} p = cfg.get("pushsrv", "pushover") if p in ("all", "pushover"): ok = pushover(cfg.get("pushover_token", ""), cfg.get("pushover_user", ""), msg, debug=debug) results["pushover"] = ok if p in ("all", "mattermost"): ok = pushmattermost(cfg.get("matter_host", ""), cfg.get("matter_token", ""), cfg.get("matter_channel", ""), msg, username=cfg.get("matter_username", "hbd"), icon=cfg.get("matter_icon"), debug=debug) results["mattermost"] = ok if p in ("all", "signal"): ok = pushsignal(cfg.get("signal_cli", "/usr/local/bin/signal-cli"), cfg.get("signal_user", ""), cfg.get("signal_recipient", ""), msg, debug=debug) results["signal"] = ok if debug: print("push results:", results) return results def pushmsg_from_config(msg: str, debug: int = 0) -> dict: """Use the module-level configuration dict to dispatch a push message.""" return pushmsg(_config, msg, debug=debug)