"""阈值提醒状态机:低于阈值提醒一次,恢复后重新武装。 状态(accounts.alert_armed): - 1 = 已武装,低于阈值时可提醒 - 0 = 已提醒,等待余额恢复到阈值以上 发送成功才切换状态;Telegram 失败则保持原状态,下次检查再试。 """ from __future__ import annotations import logging from datetime import datetime from app import db from app.config import Config from app.telegram import send_message logger = logging.getLogger("monitor.alert") def _fmt(v: float | None) -> str: return "—" if v is None else f"{v:g}" def build_alert_message(kind: str, account: dict, platform: dict, balance: float | None) -> str: pname = platform["name"] aname = account["name"] cur = platform["currency"] th = account["threshold"] now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") if kind == "below": return (f"⚠️ 余额不足提醒\n" f"平台:{pname}\n账号:{aname}\n" f"当前余额:{_fmt(balance)} {cur}\n阈值:{_fmt(th)} {cur}\n" f"时间:{now}") if kind == "recovered": return (f"✅ 余额已恢复\n" f"平台:{pname}\n账号:{aname}\n" f"当前余额:{_fmt(balance)} {cur}\n阈值:{_fmt(th)} {cur}\n" f"时间:{now}") if kind == "disabled": return (f"🚫 账号已自动禁用\n" f"平台:{pname}\n账号:{aname}\n" f"原因:{account.get('last_error') or '认证失败'}") return "" def evaluate(account: dict, platform: dict, balance: float, cfg: Config) -> None: """拉取成功后调用:按状态机决定是否发送提醒,发送成功才更新武装状态。""" threshold = account["threshold"] armed = bool(account["alert_armed"]) below = balance < threshold if below and armed: kind = "below" logger.info("触发提醒: %s / %s 余额 %s < 阈值 %s(%s)", platform["name"], account["name"], _fmt(balance), _fmt(threshold), platform["currency"]) elif not below and not armed: kind = "recovered" logger.info("余额恢复: %s / %s 余额 %s >= 阈值 %s(%s)", platform["name"], account["name"], _fmt(balance), _fmt(threshold), platform["currency"]) else: return message = build_alert_message(kind, account, platform, balance) if send_message(cfg.telegram_bot_token, cfg.telegram_chat_id, message): new_armed = 0 if kind == "below" else 1 with db.get_conn() as conn: conn.execute("UPDATE accounts SET alert_armed = ? WHERE id = ?", (new_armed, account["id"])) conn.execute( "INSERT INTO alert_log (account_id, type, message) VALUES (?, ?, ?)", (account["id"], kind, message), ) logger.info("已发送%s提醒: account=%s", kind, account["id"]) else: logger.warning("提醒发送失败,保持原状态稍后重试: account=%s kind=%s", account["id"], kind) def notify_disabled(account: dict, platform: dict, cfg: Config) -> None: """认证失败禁用账号后通知用户(不参与状态机)。""" message = build_alert_message("disabled", account, platform, None) if send_message(cfg.telegram_bot_token, cfg.telegram_chat_id, message): with db.get_conn() as conn: conn.execute( "INSERT INTO alert_log (account_id, type, message) VALUES (?, ?, ?)", (account["id"], "disabled", message), )