Rename to hkt.sh
This commit is contained in:
531
projects/yt-stock-bot/yt_stock_bot.py
Normal file
531
projects/yt-stock-bot/yt_stock_bot.py
Normal file
@@ -0,0 +1,531 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
BASE_API = "https://server.cloud.yt.net/api/v1"
|
||||
DEFAULT_CONFIG = {
|
||||
"telegram_token": "",
|
||||
"allowed_chat_ids": [165067365],
|
||||
"yt_email": "",
|
||||
"yt_password": "",
|
||||
"monitor_enabled": True,
|
||||
"monitor_interval": 5,
|
||||
"monitored_categories": ["sz-bgp"],
|
||||
"auto_order_plan_ids": [53, 54, 55],
|
||||
"auto_order": True,
|
||||
"stop_on_success": True,
|
||||
"last_update_id": 0,
|
||||
"last_seen_stock": {},
|
||||
"last_order_attempt": {},
|
||||
}
|
||||
|
||||
|
||||
class Bot:
|
||||
def __init__(self, config_path: str):
|
||||
self.config_path = Path(config_path)
|
||||
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
self.config = self._load_config()
|
||||
|
||||
def _load_config(self) -> Dict[str, Any]:
|
||||
if self.config_path.exists():
|
||||
with self.config_path.open("r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
else:
|
||||
data = {}
|
||||
merged = dict(DEFAULT_CONFIG)
|
||||
merged.update(data)
|
||||
self._save_config(merged)
|
||||
return merged
|
||||
|
||||
def _save_config(self, data: Optional[Dict[str, Any]] = None):
|
||||
if data is not None:
|
||||
self.config = data
|
||||
tmp = self.config_path.with_suffix(".tmp")
|
||||
with tmp.open("w", encoding="utf-8") as f:
|
||||
json.dump(self.config, f, ensure_ascii=False, indent=2)
|
||||
os.replace(tmp, self.config_path)
|
||||
try:
|
||||
os.chmod(self.config_path, 0o600)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def api_json(self, url: str, method: str = "GET", data: Optional[dict] = None, headers: Optional[dict] = None, timeout: int = 20) -> dict:
|
||||
req_headers = {"Content-Type": "application/json"}
|
||||
if headers:
|
||||
req_headers.update(headers)
|
||||
body = None
|
||||
if data is not None:
|
||||
body = json.dumps(data).encode()
|
||||
req = urllib.request.Request(url, data=body, headers=req_headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
return json.loads(resp.read().decode("utf-8"))
|
||||
|
||||
def tg_api(self, method: str, data: Optional[dict] = None, timeout: int = 20) -> dict:
|
||||
token = self.config["telegram_token"]
|
||||
if not token:
|
||||
raise RuntimeError("telegram_token 未配置")
|
||||
url = f"https://api.telegram.org/bot{token}/{method}"
|
||||
return self.api_json(url, method="POST", data=data or {}, timeout=timeout)
|
||||
|
||||
def answer_callback(self, callback_id: str, text: str = "已执行"):
|
||||
try:
|
||||
self.tg_api("answerCallbackQuery", {"callback_query_id": callback_id, "text": text})
|
||||
except Exception as e:
|
||||
print(f"answer_callback failed: {e}", file=sys.stderr)
|
||||
|
||||
def command_keyboard(self) -> dict:
|
||||
return {
|
||||
"inline_keyboard": [
|
||||
[
|
||||
{"text": "状态", "callback_data": "/status"},
|
||||
{"text": "立即检查", "callback_data": "/check"},
|
||||
],
|
||||
[
|
||||
{"text": "分类列表", "callback_data": "/categories"},
|
||||
{"text": "当前产品", "callback_data": "/plans"},
|
||||
],
|
||||
[
|
||||
{"text": "开始监控", "callback_data": "/startmon"},
|
||||
{"text": "停止监控", "callback_data": "/stopmon"},
|
||||
],
|
||||
[
|
||||
{"text": "自动下单开", "callback_data": "/autoon"},
|
||||
{"text": "自动下单关", "callback_data": "/autooff"},
|
||||
],
|
||||
]
|
||||
}
|
||||
|
||||
def send_message(self, chat_id: int, text: str, reply_markup: Optional[dict] = None):
|
||||
try:
|
||||
payload = {"chat_id": chat_id, "text": text}
|
||||
if reply_markup is not None:
|
||||
payload["reply_markup"] = reply_markup
|
||||
self.tg_api("sendMessage", payload)
|
||||
except Exception as e:
|
||||
print(f"send_message failed: {e}", file=sys.stderr)
|
||||
|
||||
def edit_message(self, chat_id: int, message_id: int, text: str, reply_markup: Optional[dict] = None):
|
||||
try:
|
||||
payload = {"chat_id": chat_id, "message_id": message_id, "text": text}
|
||||
if reply_markup is not None:
|
||||
payload["reply_markup"] = reply_markup
|
||||
self.tg_api("editMessageText", payload)
|
||||
except Exception as e:
|
||||
print(f"edit_message failed: {e}", file=sys.stderr)
|
||||
|
||||
def send_panel(self, chat_id: int, text: str):
|
||||
self.send_message(chat_id, text, reply_markup=self.command_keyboard())
|
||||
|
||||
def send_plans_panel(self, chat_id: int, slug: Optional[str] = None):
|
||||
self.send_message(chat_id, self.plans_text(slug), reply_markup=self.plans_keyboard(slug))
|
||||
|
||||
def broadcast(self, text: str):
|
||||
for chat_id in self.config.get("allowed_chat_ids", []):
|
||||
self.send_panel(chat_id, text)
|
||||
|
||||
def login(self) -> str:
|
||||
email = self.config.get("yt_email", "")
|
||||
password = self.config.get("yt_password", "")
|
||||
if not email or not password:
|
||||
raise RuntimeError("YT 账号未配置")
|
||||
resp = self.api_json(
|
||||
f"{BASE_API}/auth/login",
|
||||
method="POST",
|
||||
data={"email": email, "password": password},
|
||||
)
|
||||
if not resp.get("success"):
|
||||
raise RuntimeError(resp.get("message", "登录失败"))
|
||||
return resp["data"]["token"]
|
||||
|
||||
def get_all_plans(self) -> Tuple[List[dict], Dict[str, dict]]:
|
||||
resp = self.api_json(f"{BASE_API}/auth/plans")
|
||||
if not resp.get("success"):
|
||||
raise RuntimeError(resp.get("message", "获取套餐失败"))
|
||||
data = resp.get("data", {})
|
||||
cages = {c["slug"]: c for c in data.get("cages", [])}
|
||||
cage_by_id = {c["id"]: c for c in data.get("cages", [])}
|
||||
plans = []
|
||||
for p in data.get("plans", []):
|
||||
p = dict(p)
|
||||
cage = cage_by_id.get(p.get("cage"), {})
|
||||
p["cage_info"] = cage
|
||||
p["slug"] = cage.get("slug", "")
|
||||
plans.append(p)
|
||||
return plans, cages
|
||||
|
||||
def get_monitored_plans(self) -> List[dict]:
|
||||
plans, _ = self.get_all_plans()
|
||||
monitored = set(self.config.get("monitored_categories", []))
|
||||
return [p for p in plans if p.get("slug") in monitored]
|
||||
|
||||
def parse_yt_link(self, text: str) -> Optional[str]:
|
||||
m = re.search(r"cloud\.yt\.net/server/([a-z0-9-]+)", text, re.I)
|
||||
if not m:
|
||||
return None
|
||||
return m.group(1).lower()
|
||||
|
||||
def add_link(self, text: str) -> Tuple[str, List[dict]]:
|
||||
slug = self.parse_yt_link(text)
|
||||
if not slug:
|
||||
raise RuntimeError("链接格式不对,发 cloud.yt.net/server/xxx 这种")
|
||||
plans, cages = self.get_all_plans()
|
||||
cage = cages.get(slug)
|
||||
matched = [p for p in plans if p.get("slug") == slug]
|
||||
if not cage or not matched:
|
||||
raise RuntimeError(f"没识别到分类:{slug}")
|
||||
arr = set(self.config.get("monitored_categories", []))
|
||||
arr.add(slug)
|
||||
self.config["monitored_categories"] = sorted(arr)
|
||||
self._save_config()
|
||||
matched.sort(key=lambda x: x.get("id", 0))
|
||||
return slug, matched
|
||||
|
||||
def get_templates(self, token: str, plan_id: int) -> List[dict]:
|
||||
resp = self.api_json(
|
||||
f"{BASE_API}/vps/templates/plan/{plan_id}",
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
)
|
||||
if not resp.get("success"):
|
||||
raise RuntimeError(resp.get("message", "获取模板失败"))
|
||||
return resp.get("data", [])
|
||||
|
||||
def order_plan(self, token: str, plan_id: int, template_id: int) -> dict:
|
||||
return self.api_json(
|
||||
f"{BASE_API}/vps/order/{plan_id}",
|
||||
method="POST",
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
data={"Template": template_id},
|
||||
)
|
||||
|
||||
def categories_text(self) -> str:
|
||||
plans, cages = self.get_all_plans()
|
||||
counts: Dict[str, int] = {}
|
||||
monitored = set(self.config.get("monitored_categories", []))
|
||||
for p in plans:
|
||||
slug = p.get("slug") or "-"
|
||||
counts[slug] = counts.get(slug, 0) + 1
|
||||
lines = ["YT 分类列表:"]
|
||||
for slug, cage in sorted(cages.items()):
|
||||
mark = "✅" if slug in monitored else "▫️"
|
||||
lines.append(f"{mark} {slug} | {cage.get('label', cage.get('name', slug))} | {counts.get(slug, 0)} 个产品")
|
||||
lines += ["", "命令:/watch 分类slug", "命令:/unwatch 分类slug"]
|
||||
return "\n".join(lines)
|
||||
|
||||
def get_display_plans(self, slug: Optional[str] = None) -> List[dict]:
|
||||
plans, _ = self.get_all_plans()
|
||||
monitored = set(self.config.get("monitored_categories", []))
|
||||
if slug:
|
||||
plans = [p for p in plans if p.get("slug") == slug]
|
||||
else:
|
||||
plans = [p for p in plans if p.get("slug") in monitored]
|
||||
plans.sort(key=lambda x: (x.get("slug", ""), x.get("id", 0)))
|
||||
return plans
|
||||
|
||||
def plans_text(self, slug: Optional[str] = None) -> str:
|
||||
plans = self.get_display_plans(slug)
|
||||
auto_ids = set(int(x) for x in self.config.get("auto_order_plan_ids", []))
|
||||
lines = [f"产品列表{'(' + slug + ')' if slug else '(当前监控分类)'}:"]
|
||||
for p in plans:
|
||||
auto = "✅" if int(p.get("id", 0)) in auto_ids else "▫️"
|
||||
lines.append(
|
||||
f"{auto} ID={p['id']} | {p['name']} | {p.get('slug','-')} | stock={p.get('stock',0)} | ¥{p.get('price','?')}"
|
||||
)
|
||||
lines += ["", "点下面按钮可直接开关自动下单"]
|
||||
return "\n".join(lines)
|
||||
|
||||
def plans_keyboard(self, slug: Optional[str] = None) -> dict:
|
||||
plans = self.get_display_plans(slug)
|
||||
auto_ids = set(int(x) for x in self.config.get("auto_order_plan_ids", []))
|
||||
rows = []
|
||||
for p in plans:
|
||||
pid = int(p["id"])
|
||||
mark = "✅" if pid in auto_ids else "▫️"
|
||||
rows.append([
|
||||
{
|
||||
"text": f"{mark} {p['name']}",
|
||||
"callback_data": f"toggle_auto:{pid}:{slug or ''}",
|
||||
}
|
||||
])
|
||||
rows.append([
|
||||
{"text": "刷新产品", "callback_data": f"show_plans:{slug or ''}"},
|
||||
{"text": "状态", "callback_data": "/status"},
|
||||
])
|
||||
return {"inline_keyboard": rows}
|
||||
|
||||
def status_text(self) -> str:
|
||||
plans = self.get_monitored_plans()
|
||||
auto_ids = set(int(x) for x in self.config.get("auto_order_plan_ids", []))
|
||||
lines = [
|
||||
"YT 补货 Bot 状态:",
|
||||
f"监控开关:{'开' if self.config.get('monitor_enabled') else '关'}",
|
||||
f"监控间隔:{self.config.get('monitor_interval')} 秒",
|
||||
f"监控分类:{', '.join(self.config.get('monitored_categories', [])) or '-'}",
|
||||
f"自动下单总开关:{'开' if self.config.get('auto_order') else '关'}",
|
||||
f"自动下单产品数:{len(auto_ids)}",
|
||||
"",
|
||||
"当前监控产品:",
|
||||
]
|
||||
for p in plans:
|
||||
auto = "[自动]" if int(p.get("id", 0)) in auto_ids else "[仅监控]"
|
||||
lines.append(f"- {p['name']} {auto} stock={p.get('stock', 0)} price=¥{p.get('price', '?')}")
|
||||
return "\n".join(lines)
|
||||
|
||||
def toggle_auto_plan(self, plan_id: int) -> bool:
|
||||
arr = set(int(x) for x in self.config.get("auto_order_plan_ids", []))
|
||||
enabled = plan_id not in arr
|
||||
if enabled:
|
||||
arr.add(plan_id)
|
||||
else:
|
||||
arr.discard(plan_id)
|
||||
self.config["auto_order_plan_ids"] = sorted(arr)
|
||||
self._save_config()
|
||||
return enabled
|
||||
|
||||
def handle_command(self, chat_id: int, text: str):
|
||||
parts = text.strip().split(maxsplit=1)
|
||||
cmd = parts[0].lower()
|
||||
arg = parts[1].strip() if len(parts) > 1 else ""
|
||||
|
||||
if cmd == "/start":
|
||||
self.send_panel(chat_id, "YT 补货 Bot 在线。")
|
||||
return
|
||||
if cmd == "/status":
|
||||
self.send_panel(chat_id, self.status_text())
|
||||
return
|
||||
if cmd == "/categories":
|
||||
self.send_panel(chat_id, self.categories_text())
|
||||
return
|
||||
if cmd == "/plans":
|
||||
self.send_plans_panel(chat_id, arg or None)
|
||||
return
|
||||
if cmd == "/addlink":
|
||||
if not arg:
|
||||
self.send_panel(chat_id, "用法:/addlink https://cloud.yt.net/server/sz-bgp")
|
||||
return
|
||||
slug, matched = self.add_link(arg)
|
||||
self.send_message(chat_id, f"已加入监控链接:{slug}\n识别到 {len(matched)} 个产品,点下面按钮选择自动下单。", reply_markup=self.plans_keyboard(slug))
|
||||
return
|
||||
if cmd == "/listlinks":
|
||||
arr = self.config.get("monitored_categories", [])
|
||||
self.send_panel(chat_id, "当前监控链接:\n" + "\n".join(f"- https://cloud.yt.net/server/{x}" for x in arr))
|
||||
return
|
||||
if cmd == "/check":
|
||||
msg = self.check_once(manual=True)
|
||||
self.send_panel(chat_id, msg or "当前没有新变化")
|
||||
return
|
||||
if cmd == "/startmon":
|
||||
self.config["monitor_enabled"] = True
|
||||
self._save_config()
|
||||
self.send_panel(chat_id, "监控已开启")
|
||||
return
|
||||
if cmd == "/stopmon":
|
||||
self.config["monitor_enabled"] = False
|
||||
self._save_config()
|
||||
self.send_panel(chat_id, "监控已停止")
|
||||
return
|
||||
if cmd == "/autoon":
|
||||
self.config["auto_order"] = True
|
||||
self._save_config()
|
||||
self.send_panel(chat_id, "自动下单总开关已开启")
|
||||
return
|
||||
if cmd == "/autooff":
|
||||
self.config["auto_order"] = False
|
||||
self._save_config()
|
||||
self.send_panel(chat_id, "自动下单总开关已关闭")
|
||||
return
|
||||
if cmd == "/setinterval":
|
||||
if not arg.isdigit():
|
||||
self.send_panel(chat_id, "用法:/setinterval 5")
|
||||
return
|
||||
val = max(3, int(arg))
|
||||
self.config["monitor_interval"] = val
|
||||
self._save_config()
|
||||
self.send_panel(chat_id, f"监控间隔已设为 {val} 秒")
|
||||
return
|
||||
if cmd == "/watch":
|
||||
if not arg:
|
||||
self.send_panel(chat_id, "用法:/watch 分类slug")
|
||||
return
|
||||
arr = set(self.config.get("monitored_categories", []))
|
||||
arr.add(arg)
|
||||
self.config["monitored_categories"] = sorted(arr)
|
||||
self._save_config()
|
||||
self.send_panel(chat_id, f"已加入监控分类:{arg}")
|
||||
return
|
||||
if cmd == "/unwatch":
|
||||
if not arg:
|
||||
self.send_panel(chat_id, "用法:/unwatch 分类slug")
|
||||
return
|
||||
arr = set(self.config.get("monitored_categories", []))
|
||||
arr.discard(arg)
|
||||
self.config["monitored_categories"] = sorted(arr)
|
||||
self._save_config()
|
||||
self.send_panel(chat_id, f"已移除监控分类:{arg}")
|
||||
return
|
||||
if cmd == "/autoadd":
|
||||
if not arg.isdigit():
|
||||
self.send_panel(chat_id, "用法:/autoadd 产品ID")
|
||||
return
|
||||
arr = set(int(x) for x in self.config.get("auto_order_plan_ids", []))
|
||||
arr.add(int(arg))
|
||||
self.config["auto_order_plan_ids"] = sorted(arr)
|
||||
self._save_config()
|
||||
self.send_panel(chat_id, f"已加入自动下单:ID={arg}")
|
||||
return
|
||||
if cmd == "/autodel":
|
||||
if not arg.isdigit():
|
||||
self.send_panel(chat_id, "用法:/autodel 产品ID")
|
||||
return
|
||||
arr = set(int(x) for x in self.config.get("auto_order_plan_ids", []))
|
||||
arr.discard(int(arg))
|
||||
self.config["auto_order_plan_ids"] = sorted(arr)
|
||||
self._save_config()
|
||||
self.send_panel(chat_id, f"已移除自动下单:ID={arg}")
|
||||
return
|
||||
self.send_panel(chat_id, "未知命令")
|
||||
|
||||
def poll_updates(self):
|
||||
offset = int(self.config.get("last_update_id", 0)) + 1
|
||||
resp = self.tg_api("getUpdates", {"offset": offset, "timeout": 5, "allowed_updates": ["message", "callback_query"]}, timeout=15)
|
||||
for item in resp.get("result", []):
|
||||
self.config["last_update_id"] = item["update_id"]
|
||||
self._save_config()
|
||||
|
||||
callback = item.get("callback_query") or {}
|
||||
if callback:
|
||||
msg = callback.get("message") or {}
|
||||
chat_id = ((msg.get("chat") or {}).get("id"))
|
||||
message_id = msg.get("message_id")
|
||||
if chat_id in self.config.get("allowed_chat_ids", []):
|
||||
data = (callback.get("data") or "").strip()
|
||||
if data.startswith("toggle_auto:"):
|
||||
_, pid, slug = data.split(":", 2)
|
||||
enabled = self.toggle_auto_plan(int(pid))
|
||||
self.edit_message(chat_id, message_id, self.plans_text(slug or None), self.plans_keyboard(slug or None))
|
||||
self.answer_callback(callback.get("id"), "已加入自动下单" if enabled else "已取消自动下单")
|
||||
elif data.startswith("show_plans:"):
|
||||
_, slug = data.split(":", 1)
|
||||
self.edit_message(chat_id, message_id, self.plans_text(slug or None), self.plans_keyboard(slug or None))
|
||||
self.answer_callback(callback.get("id"), "已刷新")
|
||||
else:
|
||||
self.answer_callback(callback.get("id"), "已执行")
|
||||
if data.startswith("/"):
|
||||
self.handle_command(chat_id, data)
|
||||
continue
|
||||
|
||||
msg = item.get("message") or {}
|
||||
chat = msg.get("chat") or {}
|
||||
chat_id = chat.get("id")
|
||||
if chat_id not in self.config.get("allowed_chat_ids", []):
|
||||
continue
|
||||
text = (msg.get("text") or "").strip()
|
||||
if text.startswith("/"):
|
||||
self.handle_command(chat_id, text)
|
||||
|
||||
def check_once(self, manual: bool = False) -> Optional[str]:
|
||||
plans = self.get_monitored_plans()
|
||||
plans.sort(key=lambda x: (x.get("slug", ""), x.get("id", 0)))
|
||||
last_seen = self.config.setdefault("last_seen_stock", {})
|
||||
newly_available = []
|
||||
currently_available = []
|
||||
|
||||
for p in plans:
|
||||
pid = str(p["id"])
|
||||
stock = int(p.get("stock", 0) or 0)
|
||||
prev = int(last_seen.get(pid, 0) or 0)
|
||||
if stock > 0:
|
||||
currently_available.append(p)
|
||||
if stock > 0 and prev <= 0:
|
||||
newly_available.append(p)
|
||||
last_seen[pid] = stock
|
||||
|
||||
self._save_config()
|
||||
|
||||
if manual:
|
||||
if currently_available:
|
||||
lines = ["当前有货:"]
|
||||
for p in currently_available:
|
||||
auto = "[自动]" if int(p["id"]) in set(int(x) for x in self.config.get("auto_order_plan_ids", [])) else "[仅监控]"
|
||||
lines.append(f"- ID={p['id']} {p['name']} {auto} stock={p['stock']} price=¥{p.get('price','?')}")
|
||||
return "\n".join(lines)
|
||||
return "当前无货"
|
||||
|
||||
if not newly_available:
|
||||
return None
|
||||
|
||||
auto_ids = set(int(x) for x in self.config.get("auto_order_plan_ids", []))
|
||||
lines = ["发现新补货:"]
|
||||
for p in newly_available:
|
||||
auto = "[自动下单]" if int(p["id"]) in auto_ids and self.config.get("auto_order") else "[仅通知]"
|
||||
lines.append(f"- ID={p['id']} {p['name']} {auto} stock={p['stock']} price=¥{p.get('price','?')}")
|
||||
|
||||
order_results = []
|
||||
success_count = 0
|
||||
if self.config.get("auto_order"):
|
||||
token = self.login()
|
||||
now = int(time.time())
|
||||
attempts = self.config.setdefault("last_order_attempt", {})
|
||||
for p in newly_available:
|
||||
plan_id = int(p["id"])
|
||||
if plan_id not in auto_ids:
|
||||
continue
|
||||
key = str(plan_id)
|
||||
if now - int(attempts.get(key, 0)) < 20:
|
||||
order_results.append(f"- {p['name']}: 跳过(20秒内已尝试过)")
|
||||
continue
|
||||
attempts[key] = now
|
||||
template_id = 1
|
||||
try:
|
||||
templates = self.get_templates(token, plan_id)
|
||||
if templates:
|
||||
template_id = int(templates[0]["id"])
|
||||
resp = self.order_plan(token, plan_id, template_id)
|
||||
if resp.get("success"):
|
||||
success_count += 1
|
||||
order_results.append(f"- {p['name']}: 下单成功")
|
||||
else:
|
||||
order_results.append(f"- {p['name']}: 下单失败 -> {resp.get('message', '未知错误')}")
|
||||
except Exception as e:
|
||||
order_results.append(f"- {p['name']}: 下单异常 -> {e}")
|
||||
|
||||
if success_count > 0 and self.config.get("stop_on_success", True):
|
||||
self.config["monitor_enabled"] = False
|
||||
self._save_config()
|
||||
order_results.append("已自动关闭监控(下单成功后停机)")
|
||||
|
||||
if order_results:
|
||||
lines += [""] + order_results
|
||||
return "\n".join(lines)
|
||||
|
||||
def run(self):
|
||||
next_check = 0.0
|
||||
while True:
|
||||
try:
|
||||
self.poll_updates()
|
||||
except Exception as e:
|
||||
print(f"poll_updates error: {e}", file=sys.stderr)
|
||||
time.sleep(3)
|
||||
|
||||
try:
|
||||
interval = max(3, int(self.config.get("monitor_interval", 5)))
|
||||
if self.config.get("monitor_enabled") and time.time() >= next_check:
|
||||
msg = self.check_once(manual=False)
|
||||
if msg:
|
||||
self.broadcast(msg)
|
||||
next_check = time.time() + interval
|
||||
except Exception as e:
|
||||
print(f"监控异常: {e}", file=sys.stderr)
|
||||
traceback.print_exc()
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
config_path = sys.argv[1] if len(sys.argv) > 1 else "/opt/yt-stock-bot/config.json"
|
||||
Bot(config_path).run()
|
||||
Reference in New Issue
Block a user