Files
vps-manager/scripts/check_renewals.py
T
gouki abd2fa8206 feat(renewal): 提醒前核对平台真实状态,消除手工数据导致的误报
容错策略:核对失败/无可用通道时标记「未核对」并继续按本地数据提醒,
避免因核对链路故障漏掉真正到期的资产;已停止资产仅在 auto_renew=true
时提醒(停止实例不等于停止扣费)。新增 WHMCS 面板只读适配器,
用于 TotHost 这类没有开放 API 的平台。
2026-10-25 23:05:39 +00:00

71 lines
2.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""定时检查续费并发送提醒(供 systemd timer 调用)
用法:
python scripts/check_renewals.py [threshold_days] [--dry-run] [--no-verify]
默认发送通知;--dry-run 只打印、不发送,便于核对平台真实状态。
依赖 .env 中的通知渠道配置与账号登录凭据(TELEGRAM_*/SMTP_*、credentials)。
"""
import argparse
import sys
from pathlib import Path
# 确保能导入 app 包(脚本位于 scripts/ 子目录)
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from sqlmodel import Session
from app.database import assets_engine, init_db
from app.services import renewal_service
def main() -> int:
parser = argparse.ArgumentParser(description="检查资产续费并发送提醒")
parser.add_argument("threshold_days", nargs="?", type=int, default=None)
parser.add_argument("--dry-run", action="store_true", help="只打印,不发送通知")
parser.add_argument("--no-verify", action="store_true", help="跳过平台真实状态核对")
args = parser.parse_args()
init_db()
with Session(assets_engine) as session:
result = renewal_service.check_renewals(
session,
threshold_days=args.threshold_days,
send=not args.dry_run,
verify=not args.no_verify,
)
# days 可为负(已过期),汇总行拆开统计,单项文案复用 day_text(与提醒消息一致)
overdue = sum(1 for item in result["expiring"] if item["days"] < 0)
summary = (
f"[续费检查] 阈值 {result['threshold_days']} 天 | 已过期 {overdue} 项 | "
f"即将到期 {result['expiring_count'] - overdue} 项"
)
verification = result.get("verification")
if verification:
summary += (
f" | 已核对 {verification['verified']} 项"
f" | 未核对 {verification['unverified']} 项"
)
print(summary)
for item in result["expiring"]:
print(
f" - {item['name']} [{item['provider']}] "
f"{renewal_service.day_text(item['days'])}(到期 {item['expiry_date']})"
)
if item.get("verified") is True:
print(f" 已核对[{item['verify_source']}]: {item['verify_detail']}")
elif item.get("verified") is False:
print(f" 未核对: {item['verify_detail']}")
for n in result.get("notifications", []):
status = "成功" if n["ok"] else f"失败:{n['message']}"
print(f" 通知[{n['channel']}]: {status}")
return 0
if __name__ == "__main__":
sys.exit(main())