84 lines
3.5 KiB
Python
84 lines
3.5 KiB
Python
"""阈值提醒状态机:低于阈值提醒一次,恢复后重新武装。
|
||
|
||
状态(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),
|
||
)
|