""" 丰享订单监控 v4 - 优先使用 Cookie 文件请求 API,无需每次都打开浏览器 - Cookie 过期时自动打开浏览器提示重新登录 - 晚上 21:00 到早上 7:40 自动暂停 - 高峰期缩短间隔,闲时拉长间隔 """ import json import random import time import datetime import requests from pathlib import Path from playwright.sync_api import sync_playwright from local_tts import speak as _local_speak # ============ 配置 ============ LOGIN_URL = "https://fspass.szfx.com/ucenter/userlogin?platform=saas&redirect_url=https%3A%2F%2Ffs.szfx.com%2FMMS%23%2F&return_url=https%3A%2F%2Ffs.szfx.com%2Fsaasmerchant%2Fsetstoken" DATA_API = "https://fs.szfx.com/saasmerchant/pcweb/order/quickpayorder/list" SHOP_ID = "20434543575189" WEBHOOK_URL = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=644ab6d9-3b66-4166-88e9-5a8a89e3731d" FEISHU_WEBHOOK_URL = "https://open.feishu.cn/open-apis/bot/v2/hook/oc_d5ef8b1abf66842c28ef57e1658dc096" COOKIES_FILE = "cookies.json" # 本地 TTS 配置(使用 IndexTTS2,无需额外配置) # 高峰时段配置 PEAK_HOURS = [(11, 13), (17, 19)] POLL_INTERVAL_MIN = 5 POLL_INTERVAL_MAX = 10 PEAK_PAGE_SIZE = 20 IDLE_PAGE_SIZE = 5 # 夜间暂停:21:00 ~ 07:40 不轮询 NIGHT_START_H = 21 NIGHT_END_H = 7 NIGHT_END_M = 40 KEEPALIVE_INTERVAL = 300 # 每5分钟保活一次 # ============================== def fmt_money(cents): return f"{cents / 100:.2f}元" if cents else "0.00元" def log(msg): now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") print(f"[{now}] {msg}") def is_peak_hour(): hour = datetime.datetime.now().hour return any(start <= hour < end for start, end in PEAK_HOURS) def is_night_time(): """是否在夜间暂停时段 (21:00 ~ 07:40)""" now = datetime.datetime.now() minutes = now.hour * 60 + now.minute return minutes >= NIGHT_START_H * 60 or minutes < NIGHT_END_H * 60 + NIGHT_END_M def seconds_until_morning(): """距离明天早上 07:40 还有多少秒""" now = datetime.datetime.now() target = now.replace(hour=NIGHT_END_H, minute=NIGHT_END_M, second=0, microsecond=0) if now.hour >= NIGHT_START_H: target += datetime.timedelta(days=1) return max(1, int((target - now).total_seconds())) def get_poll_config(): if is_peak_hour(): return POLL_INTERVAL_MIN, POLL_INTERVAL_MAX, PEAK_PAGE_SIZE return POLL_INTERVAL_MIN, POLL_INTERVAL_MAX, IDLE_PAGE_SIZE def format_msg(order): return f"【丰享丰食】订单收款成功,收款{fmt_money(order['amountPayable'])}" def notify_all(text): """统一发送通知到企业微信 + 飞书""" send_to_wecom(text) send_to_feishu(text) def send_to_wecom(text): payload = {"msgtype": "text", "text": {"content": text}} try: resp = requests.post(WEBHOOK_URL, json=payload, timeout=10) result = resp.json() if result.get("errcode") == 0: log("企业微信推送成功") else: log(f"企业微信推送失败: {result}") except Exception as e: log(f"企业微信推送异常: {e}") def send_to_feishu(text): payload = {"msg_type": "text", "content": {"text": text}} try: resp = requests.post(FEISHU_WEBHOOK_URL, json=payload, timeout=10) result = resp.json() if result.get("code") == 0: log("飞书推送成功") else: log(f"飞书推送失败: {result}") except Exception as e: log(f"飞书推送异常: {e}") def speak(text): """使用本地 IndexTTS2 语音播报""" try: _local_speak(text) log("语音播报成功") except Exception as e: log(f"语音播报异常: {e}") def load_cookies(): """加载本地 Cookie 文件""" cookie_path = Path(__file__).parent / COOKIES_FILE if cookie_path.exists(): with open(cookie_path, "r", encoding="utf-8") as f: return json.load(f) return None def save_cookies(cookiejar): """把浏览器 Cookie 列表保存为 dict 到文件""" cookie_dict = {c["name"]: c["value"] for c in cookiejar} cookie_path = Path(__file__).parent / COOKIES_FILE with open(cookie_path, "w", encoding="utf-8") as f: json.dump(cookie_dict, f, ensure_ascii=False, indent=2) log("Cookie 已保存到 cookies.json") def fetch_orders_via_requests(cookies, page_size=5): """通过 requests + Cookie 拉取订单列表,返回 (订单列表 / SESSION_EXPIRED / None)""" headers = { "Content-Type": "application/json", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Referer": "https://fs.szfx.com/MMS", "Origin": "https://fs.szfx.com", } payload = {"shopId": SHOP_ID, "page": 1, "pageSize": page_size} try: resp = requests.post(DATA_API, json=payload, headers=headers, cookies=cookies, timeout=30) data = resp.json() if data.get("errno") == 0: return data["data"]["list"] elif data.get("errno") in (110003, 10009): return "SESSION_EXPIRED" else: log(f"API 异常: errno={data.get('errno')}, errmsg={data.get('errmsg')}") return None except Exception as e: log(f"请求异常: {e}") return None def browser_login(): """打开浏览器让用户手动登录,返回 Cookie dict""" log("启动浏览器,请手动登录...") pw = sync_playwright().start() browser = pw.chromium.launch(headless=False, args=["--start-maximized"]) context = browser.new_context(no_viewport=True) page = context.new_page() page.goto(LOGIN_URL) log("等待登录完成(10分钟超时)...") try: page.wait_for_url("**/MMS**", timeout=600_000) log("登录成功!提取 Cookie...") except Exception: log("登录超时") browser.close() pw.stop() return None time.sleep(2) cookiejar = context.cookies() save_cookies(cookiejar) browser.close() pw.stop() log("浏览器已关闭") return {c["name"]: c["value"] for c in cookiejar} def main(): # 1. 尝试加载已有 Cookie cookies = load_cookies() if cookies: log("检测到已有 Cookie,验证有效性...") test_result = fetch_orders_via_requests(cookies, page_size=1) if test_result == "SESSION_EXPIRED" or test_result is None: log("Cookie 已过期,需要重新登录") cookies = None else: log("Cookie 有效,无需打开浏览器") # 2. 没有有效 Cookie → 打开浏览器登录 if not cookies: notify_all("【监控通知】需要登录丰享平台,请查看电脑浏览器完成登录") cookies = browser_login() if not cookies: log("登录失败,退出") return # 3. 获取初始最新订单号 log("获取初始订单...") _, _, page_size = get_poll_config() last_order_id = None orders = fetch_orders_via_requests(cookies, page_size) if orders and orders != "SESSION_EXPIRED" and len(orders) > 0: last_order_id = orders[0]["orderId"] log(f"当前最新订单号: {last_order_id}") last_keepalive = time.time() mode = "高峰期" if is_peak_hour() else ("夜间暂停" if is_night_time() else "闲时") log(f"开始监控(当前模式: {mode})") try: while True: # 夜间暂停:到点就睡,天亮再起 if is_night_time(): sleep_secs = seconds_until_morning() until = datetime.datetime.now() + datetime.timedelta(seconds=sleep_secs) log(f"夜间模式,暂停监控至 {until.strftime('%H:%M:%S')}(约 {sleep_secs // 60} 分钟)") time.sleep(sleep_secs) log("恢复监控...") # 验证 Cookie 是否还活着 test = fetch_orders_via_requests(cookies, page_size=1) if test == "SESSION_EXPIRED": log("Cookie 在夜间过期,需要重新登录") notify_all("【监控通知】Cookie 过期,请查看电脑浏览器完成登录") cookies = browser_login() if not cookies: log("登录失败,退出") return last_keepalive = time.time() continue # 轮询 interval_min, interval_max, page_size = get_poll_config() wait = random.randint(interval_min, interval_max) mode = "高峰期" if is_peak_hour() else "闲时" log(f"[{mode}] 等待 {wait} 秒,拉取 {page_size} 条...") time.sleep(wait) # 定期保活(一次 API 请求即可维持 Cookie) if time.time() - last_keepalive > KEEPALIVE_INTERVAL: fetch_orders_via_requests(cookies, page_size=1) last_keepalive = time.time() # 拉取订单 orders = fetch_orders_via_requests(cookies, page_size) # Cookie 过期 → 浏览器重新登录 if orders == "SESSION_EXPIRED": log("Cookie 已过期!") notify_all("【监控异常】Cookie 已过期,请查看电脑浏览器完成登录") cookies = browser_login() if not cookies: notify_all("登录超时,监控已退出。请手动重新运行。") return send_to_wecom("重新登录成功,监控已恢复") last_keepalive = time.time() orders = fetch_orders_via_requests(cookies, page_size) if orders and orders != "SESSION_EXPIRED" and len(orders) > 0: last_order_id = orders[0]["orderId"] continue if not orders or orders == "SESSION_EXPIRED": continue # 找出所有新订单(orderId > last_order_id) new_orders = [] for o in orders: if o["orderId"] == last_order_id: break new_orders.append(o) if new_orders: log(f"发现 {len(new_orders)} 条新订单!") # 从旧到新依次推送 for o in reversed(new_orders): msg = format_msg(o) notify_all(msg) speak(msg) time.sleep(0.5) last_order_id = new_orders[0]["orderId"] else: log(f"暂无新订单(最新: {last_order_id})") except KeyboardInterrupt: log("监控已停止") if __name__ == "__main__": main()