Spaces:
Sleeping
Sleeping
Update pages/monitor_playback.py
Browse files- pages/monitor_playback.py +28 -30
pages/monitor_playback.py
CHANGED
|
@@ -23,7 +23,7 @@ load_dotenv()
|
|
| 23 |
|
| 24 |
PUSHOVER_TOKEN = os.getenv("PUSHOVER_TOKEN", "")
|
| 25 |
PUSHOVER_USER = os.getenv("PUSHOVER_USER", "")
|
| 26 |
-
#
|
| 27 |
NTFY_TOPIC = os.getenv("ntfy_code", "gzhdhdtz")
|
| 28 |
|
| 29 |
CINEMA_ID = os.getenv("CINEMA_ID", "44001291")
|
|
@@ -48,7 +48,6 @@ class DailyStats:
|
|
| 48 |
"""每日运行数据统计"""
|
| 49 |
def __init__(self):
|
| 50 |
self.reset()
|
| 51 |
-
# 用于保存前一天的数据以便发送报告
|
| 52 |
self.yesterday_stats = {
|
| 53 |
'zero_sessions': 0,
|
| 54 |
'triggers': 0,
|
|
@@ -103,21 +102,17 @@ def extract_hall_number_raw(hall_name):
|
|
| 103 |
return match.group(1) if match else str(hall_name)
|
| 104 |
|
| 105 |
def format_hall_name(hall_name):
|
| 106 |
-
"""
|
| 107 |
-
格式化影厅名称:
|
| 108 |
-
【和成天下1号厅】 -> 1号厅
|
| 109 |
-
"""
|
| 110 |
match = re.search(r'(\d+)号', str(hall_name))
|
| 111 |
if match:
|
| 112 |
return f"{match.group(1)}号厅"
|
| 113 |
return str(hall_name)
|
| 114 |
|
| 115 |
|
| 116 |
-
# --- 3. 通知系统
|
| 117 |
class NotificationManager:
|
| 118 |
def __init__(self):
|
| 119 |
self.ntfy_queue = queue.Queue()
|
| 120 |
-
# 启动 NTFY 发送线程
|
| 121 |
self.worker_thread = threading.Thread(target=self._ntfy_worker, daemon=True)
|
| 122 |
self.worker_thread.start()
|
| 123 |
|
|
@@ -139,10 +134,8 @@ class NotificationManager:
|
|
| 139 |
daily_stats.notify_fails += 1
|
| 140 |
|
| 141 |
def _ntfy_worker(self):
|
| 142 |
-
"""NTFY 后台发送线程"""
|
| 143 |
while True:
|
| 144 |
try:
|
| 145 |
-
# 获取任务: (title, message, priority)
|
| 146 |
item = self.ntfy_queue.get()
|
| 147 |
if item is None: break
|
| 148 |
|
|
@@ -150,14 +143,10 @@ class NotificationManager:
|
|
| 150 |
|
| 151 |
if NTFY_TOPIC:
|
| 152 |
url = f"https://ntfy.sh/{NTFY_TOPIC}"
|
| 153 |
-
|
| 154 |
-
# 转换优先级: Pushover(0/1) -> NTFY headers
|
| 155 |
headers = {
|
| 156 |
"Title": title.encode('utf-8'),
|
| 157 |
"Priority": "high" if priority >= 1 else "default",
|
| 158 |
-
# "Tags": "warning" if priority >= 1 else "white_check_mark"
|
| 159 |
}
|
| 160 |
-
|
| 161 |
try:
|
| 162 |
requests.post(url, data=message.encode('utf-8'), headers=headers, timeout=10)
|
| 163 |
except Exception as e:
|
|
@@ -171,13 +160,9 @@ class NotificationManager:
|
|
| 171 |
time.sleep(5)
|
| 172 |
|
| 173 |
def send_alert(self, title, message, priority=0):
|
| 174 |
-
"""统一发送接口
|
| 175 |
daily_stats.triggers += 1
|
| 176 |
-
|
| 177 |
-
# 1. Pushover
|
| 178 |
self.send_pushover(title, message, priority)
|
| 179 |
-
|
| 180 |
-
# 2. NTFY (入队)
|
| 181 |
self.ntfy_queue.put((title, message, priority))
|
| 182 |
|
| 183 |
def send_daily_report(self, today_date):
|
|
@@ -195,9 +180,7 @@ class NotificationManager:
|
|
| 195 |
f"(包括排片数据API和获取TMS数据API)"
|
| 196 |
)
|
| 197 |
|
| 198 |
-
# Pushover
|
| 199 |
self.send_pushover(title, msg, priority=0)
|
| 200 |
-
# NTFY
|
| 201 |
self.ntfy_queue.put((title, msg, 0))
|
| 202 |
|
| 203 |
|
|
@@ -401,7 +384,7 @@ class PlaybackMonitor:
|
|
| 401 |
return check_points
|
| 402 |
|
| 403 |
def _run_loop(self):
|
| 404 |
-
self.log("🚀 智能防空转监控已启动 (
|
| 405 |
|
| 406 |
while True:
|
| 407 |
try:
|
|
@@ -418,7 +401,6 @@ class PlaybackMonitor:
|
|
| 418 |
if now.time() >= dt_time(9, 0):
|
| 419 |
need_refresh = True
|
| 420 |
|
| 421 |
-
# 日期变更结算
|
| 422 |
daily_stats.snapshot_as_yesterday(self.current_business_date)
|
| 423 |
self.cleared_sessions.clear()
|
| 424 |
self.processed_checks.clear()
|
|
@@ -431,7 +413,6 @@ class PlaybackMonitor:
|
|
| 431 |
self.current_business_date = biz_date
|
| 432 |
self.log(f"✅ 排片已更新,共 {len(schedule)} 场。")
|
| 433 |
|
| 434 |
-
# 发送每日报告
|
| 435 |
try:
|
| 436 |
d_obj = datetime.strptime(biz_date, "%Y-%m-%d")
|
| 437 |
date_cn = f"{d_obj.year}年{d_obj.month}月{d_obj.day}日"
|
|
@@ -464,20 +445,39 @@ class PlaybackMonitor:
|
|
| 464 |
|
| 465 |
if next_target:
|
| 466 |
target_time = next_target['time']
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 467 |
sleep_seconds = max(0, (target_time - now).total_seconds())
|
| 468 |
|
| 469 |
self.status_text = f"💤 休眠中 (等待: {target_time.strftime('%H:%M')})"
|
| 470 |
self.next_wakeup_str = target_time.strftime('%H:%M:%S')
|
| 471 |
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
|
|
|
|
|
|
|
|
|
| 475 |
|
| 476 |
self.log(f"💤 智能休眠 {int(sleep_seconds)}秒,将在 {self.next_wakeup_str} 唤醒...")
|
| 477 |
|
| 478 |
time.sleep(sleep_seconds)
|
| 479 |
|
| 480 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 481 |
self.status_text = "🔥 正在执行检查..."
|
| 482 |
self.log("⏰ 唤醒!正在同步最新票务数据...")
|
| 483 |
|
|
@@ -577,7 +577,6 @@ class PlaybackMonitor:
|
|
| 577 |
self.processed_checks.add(dedup_key)
|
| 578 |
continue
|
| 579 |
|
| 580 |
-
# 格式化
|
| 581 |
hall_name_clean = format_hall_name(hall_name_raw)
|
| 582 |
status_desc = f"{hall_name_clean} {ticket_start_str} (第{check_idx+1}/3次)"
|
| 583 |
|
|
@@ -596,7 +595,6 @@ class PlaybackMonitor:
|
|
| 596 |
status_desc += " ⚠️ [空转! TMS未撤]"
|
| 597 |
self.log(f"🚨 发现空转: {hall_name_clean}《{movie_name}》")
|
| 598 |
|
| 599 |
-
# --- 文案定制 (完全一致) ---
|
| 600 |
title = f"💸 发现{hall_name_clean}空场空转!"
|
| 601 |
msg = (
|
| 602 |
f"{hall_name_clean} {ticket_start_str}《{movie_name}》\n"
|
|
|
|
| 23 |
|
| 24 |
PUSHOVER_TOKEN = os.getenv("PUSHOVER_TOKEN", "")
|
| 25 |
PUSHOVER_USER = os.getenv("PUSHOVER_USER", "")
|
| 26 |
+
# 使用 NTFY 配置
|
| 27 |
NTFY_TOPIC = os.getenv("ntfy_code", "gzhdhdtz")
|
| 28 |
|
| 29 |
CINEMA_ID = os.getenv("CINEMA_ID", "44001291")
|
|
|
|
| 48 |
"""每日运行数据统计"""
|
| 49 |
def __init__(self):
|
| 50 |
self.reset()
|
|
|
|
| 51 |
self.yesterday_stats = {
|
| 52 |
'zero_sessions': 0,
|
| 53 |
'triggers': 0,
|
|
|
|
| 102 |
return match.group(1) if match else str(hall_name)
|
| 103 |
|
| 104 |
def format_hall_name(hall_name):
|
| 105 |
+
"""格式化影厅名称:【和成天下1号厅】 -> 1号厅"""
|
|
|
|
|
|
|
|
|
|
| 106 |
match = re.search(r'(\d+)号', str(hall_name))
|
| 107 |
if match:
|
| 108 |
return f"{match.group(1)}号厅"
|
| 109 |
return str(hall_name)
|
| 110 |
|
| 111 |
|
| 112 |
+
# --- 3. 通知系统 ---
|
| 113 |
class NotificationManager:
|
| 114 |
def __init__(self):
|
| 115 |
self.ntfy_queue = queue.Queue()
|
|
|
|
| 116 |
self.worker_thread = threading.Thread(target=self._ntfy_worker, daemon=True)
|
| 117 |
self.worker_thread.start()
|
| 118 |
|
|
|
|
| 134 |
daily_stats.notify_fails += 1
|
| 135 |
|
| 136 |
def _ntfy_worker(self):
|
|
|
|
| 137 |
while True:
|
| 138 |
try:
|
|
|
|
| 139 |
item = self.ntfy_queue.get()
|
| 140 |
if item is None: break
|
| 141 |
|
|
|
|
| 143 |
|
| 144 |
if NTFY_TOPIC:
|
| 145 |
url = f"https://ntfy.sh/{NTFY_TOPIC}"
|
|
|
|
|
|
|
| 146 |
headers = {
|
| 147 |
"Title": title.encode('utf-8'),
|
| 148 |
"Priority": "high" if priority >= 1 else "default",
|
|
|
|
| 149 |
}
|
|
|
|
| 150 |
try:
|
| 151 |
requests.post(url, data=message.encode('utf-8'), headers=headers, timeout=10)
|
| 152 |
except Exception as e:
|
|
|
|
| 160 |
time.sleep(5)
|
| 161 |
|
| 162 |
def send_alert(self, title, message, priority=0):
|
| 163 |
+
"""统一发送接口"""
|
| 164 |
daily_stats.triggers += 1
|
|
|
|
|
|
|
| 165 |
self.send_pushover(title, message, priority)
|
|
|
|
|
|
|
| 166 |
self.ntfy_queue.put((title, message, priority))
|
| 167 |
|
| 168 |
def send_daily_report(self, today_date):
|
|
|
|
| 180 |
f"(包括排片数据API和获取TMS数据API)"
|
| 181 |
)
|
| 182 |
|
|
|
|
| 183 |
self.send_pushover(title, msg, priority=0)
|
|
|
|
| 184 |
self.ntfy_queue.put((title, msg, 0))
|
| 185 |
|
| 186 |
|
|
|
|
| 384 |
return check_points
|
| 385 |
|
| 386 |
def _run_loop(self):
|
| 387 |
+
self.log("🚀 智能防空转监控已启动 (09:00唤醒修复版)")
|
| 388 |
|
| 389 |
while True:
|
| 390 |
try:
|
|
|
|
| 401 |
if now.time() >= dt_time(9, 0):
|
| 402 |
need_refresh = True
|
| 403 |
|
|
|
|
| 404 |
daily_stats.snapshot_as_yesterday(self.current_business_date)
|
| 405 |
self.cleared_sessions.clear()
|
| 406 |
self.processed_checks.clear()
|
|
|
|
| 413 |
self.current_business_date = biz_date
|
| 414 |
self.log(f"✅ 排片已更新,共 {len(schedule)} 场。")
|
| 415 |
|
|
|
|
| 416 |
try:
|
| 417 |
d_obj = datetime.strptime(biz_date, "%Y-%m-%d")
|
| 418 |
date_cn = f"{d_obj.year}年{d_obj.month}月{d_obj.day}日"
|
|
|
|
| 445 |
|
| 446 |
if next_target:
|
| 447 |
target_time = next_target['time']
|
| 448 |
+
|
| 449 |
+
# === 核心修复:09:00 拦截器 ===
|
| 450 |
+
# 如果当前时间早于 09:00,且下一场电影晚于 09:00,
|
| 451 |
+
# 必须在 09:00 醒来处理新的一天初始化,不能一觉睡过头。
|
| 452 |
+
nine_am_today = datetime.combine(now.date(), dt_time(9, 0))
|
| 453 |
+
|
| 454 |
+
force_wake_for_daily_reset = False
|
| 455 |
+
if now < nine_am_today and target_time > nine_am_today:
|
| 456 |
+
target_time = nine_am_today
|
| 457 |
+
force_wake_for_daily_reset = True
|
| 458 |
+
|
| 459 |
sleep_seconds = max(0, (target_time - now).total_seconds())
|
| 460 |
|
| 461 |
self.status_text = f"💤 休眠中 (等待: {target_time.strftime('%H:%M')})"
|
| 462 |
self.next_wakeup_str = target_time.strftime('%H:%M:%S')
|
| 463 |
|
| 464 |
+
if force_wake_for_daily_reset:
|
| 465 |
+
self.active_monitors = ["等待 09:00 日报刷新..."]
|
| 466 |
+
else:
|
| 467 |
+
raw_hall = next_target['data']['hallName']
|
| 468 |
+
clean_hall = format_hall_name(raw_hall)
|
| 469 |
+
self.active_monitors = [f"下个任务: {clean_hall} {next_target['data']['showStartTime']} (第{next_target['check_index']+1}次检查)"]
|
| 470 |
|
| 471 |
self.log(f"💤 智能休眠 {int(sleep_seconds)}秒,将在 {self.next_wakeup_str} 唤醒...")
|
| 472 |
|
| 473 |
time.sleep(sleep_seconds)
|
| 474 |
|
| 475 |
+
# 如果是被强制唤醒来做日报刷新的,直接跳过后续的检查逻辑,
|
| 476 |
+
# continue 会回到循环头部,触发 if now >= 9:00 的初始化逻辑。
|
| 477 |
+
if force_wake_for_daily_reset:
|
| 478 |
+
continue
|
| 479 |
+
|
| 480 |
+
# --- 正常唤醒检查 ---
|
| 481 |
self.status_text = "🔥 正在执行检查..."
|
| 482 |
self.log("⏰ 唤醒!正在同步最新票务数据...")
|
| 483 |
|
|
|
|
| 577 |
self.processed_checks.add(dedup_key)
|
| 578 |
continue
|
| 579 |
|
|
|
|
| 580 |
hall_name_clean = format_hall_name(hall_name_raw)
|
| 581 |
status_desc = f"{hall_name_clean} {ticket_start_str} (第{check_idx+1}/3次)"
|
| 582 |
|
|
|
|
| 595 |
status_desc += " ⚠️ [空转! TMS未撤]"
|
| 596 |
self.log(f"🚨 发现空转: {hall_name_clean}《{movie_name}》")
|
| 597 |
|
|
|
|
| 598 |
title = f"💸 发现{hall_name_clean}空场空转!"
|
| 599 |
msg = (
|
| 600 |
f"{hall_name_clean} {ticket_start_str}《{movie_name}》\n"
|