v4: 改用本地IndexTTS2 + 新增飞书推送

- TTS从小爱音箱云API改为本地IndexTTS2模型
- 新增飞书群Webhook推送
- 新增notify_all统一通知(企业微信+飞书)
- 新增local_tts.py本地TTS封装模块
- 新增run.bat一键启动脚本
- 新增README.md项目说明
- 新增.gitignore排除敏感文件
- 更新CLAUDE.md文档
This commit is contained in:
2026-05-12 17:06:49 +08:00
commit f0bfd9dbbe
10 changed files with 912 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
# Sensitive
cookies.json
xiaomi_config.json
xiaomi_raw_cookies.json
order_data.json
# Python
__pycache__/
*.pyc
# IDE
.claude/
+82
View File
@@ -0,0 +1,82 @@
# 丰享订单监控项目
## 项目概述
自动监控丰享商家端(fs.szfx.com)的新订单,同时推送到企业微信、飞书群和本地TTS语音播报**。
## 文件说明
| 文件 | 用途 |
|------|------|
| `order_monitor.py` | 主程序:监控订单 + 企业微信 + 飞书 + 本地TTS播报 |
| `push_latest_order.py` | 单次获取最新订单并推送企业微信 |
| `fetch_orders.py` | 复用 Cookie 获取订单数据(命令行) |
| `fengxiang_scraper.py` | 首次登录获取 CookiePlaywright 半自动) |
| `setup_xiaomi.py` | 小米账号登录获取 serviceTokenPlaywright |
| `cookies.json` | 丰享平台的登录 Cookie |
| `xiaomi_config.json` | 小米账号和音箱配置 |
| `order_data.json` | 订单数据样例 |
## 已逆向的 API
### 丰享平台
- **登录**: `POST https://fspass.szfx.com/api/login`
- 参数: `uname`(手机号), `upass`(密码Base64反转), `ticket`, `randstr`(腾讯验证码)
- 登录后通过 `https://fs.szfx.com/saasmerchant/setstoken` 设置 Cookie
- Cookie 保存在浏览器中,主要: `USS`, `PTOKEN`, `CPTOKEN`, `STOKEN`
- **订单列表**: `POST https://fs.szfx.com/saasmerchant/pcweb/order/quickpayorder/list`
- Content-Type: application/json
- Body: `{"shopId": "20434543575189", "page": 1, "pageSize": 20}`
- 需要登录 Cookie,未登录返回 `errno: 110003`
- 返回: `{errno: 0, data: {total, list: [...], count}}`
- **关键字段**:
- `shopId`: `20434543575189` (益选便利店)
- `orderId`: 订单号
- `amountPayable`: 实付金额(单位:分)
- `allPrice`: 订单金额(单位:分)
- `shopName`, `city`, `companyName`, `workplaceName`: 业务信息
- `createTime`, `payTime`, `finishTime`: 时间戳(秒)
### 本地 TTSIndexTTS2
- 使用本地部署的 IndexTTS2 模型,无需联网
- 模型路径: `E:\2025Code\python\IndexTT\index-tts\`
- 语音样本: `examples/voice_01.wav`
- 封装模块: `local_tts.py`
- 调用方式: `_local_speak(text)` → 合成 WAV → 本地播放
### 企业微信 Webhook
- URL: `https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=644ab6d9-3b66-4166-88e9-5a8a89e3731d`
- 只能发消息,不能收消息
## order_monitor.py 核心逻辑
1. Playwright 打开浏览器,用户手动登录丰享平台
2. 登录成功后,通过浏览器页面的 `fetch()` 调用订单 API(复用浏览器会话,最自然)
3. 每次拉取最近 N 条订单,和上次最新订单号对比,找出所有新订单
4. 新订单同时推送企业微信 + 飞书 + 本地TTS播报
5. 高峰期(11-13点、17-19点)10-20秒轮询,闲时 30-60 秒
6. 每 5 分钟刷新页面防 Session 过期
7. Session 过期自动检测 → 通知企业微信 → 等待用户重新登录
## 消息格式
- 企业微信: `【丰享丰食】订单收款成功,收款24.00元`
- 小爱音箱: `收到新订单,收款24元`(口语化,整数不带小数点)
## 已安装的依赖
```
pip install playwright requests
playwright install chromium
```
## 注意事项
- 丰享登录有**腾讯验证码 TCaptcha**,无法纯接口绕过,必须用 Playwright 浏览器登录
- 小米 passToken 会过期,过期后需重新运行 `setup_xiaomi.py` 登录
- Windows 终端输出中文会乱码,但实际发送的内容是正确的 UTF-8
- MiService 的 `miaccount.py` 第 68 行已修改:`self.password.encode()` 加了 None 检查(passToken 登录时 password 为 None
+64
View File
@@ -0,0 +1,64 @@
# 丰享订单监控
自动监控丰享商家端(fs.szfx.com)新订单,同时推送到**企业微信**、**飞书群**并进行**本地TTS语音播报**。
## 快速开始
双击 `run.bat` 启动。首次运行会自动打开浏览器登录丰享平台,后续复用 Cookie 无需反复登录。
## 文件说明
| 文件 | 用途 |
|------|------|
| `order_monitor.py` | 主程序:监控订单 + 企业微信/飞书推送 + 本地TTS播报 |
| `local_tts.py` | 本地 TTS 封装模块(基于 IndexTTS2 |
| `run.bat` | 一键启动脚本 |
| `push_latest_order.py` | 单次获取最新订单并推送 |
| `fetch_orders.py` | 复用 Cookie 获取订单数据(命令行) |
| `fengxiang_scraper.py` | 首次登录获取 CookiePlaywright 半自动) |
| `setup_xiaomi.py` | 小米账号登录(已弃用,TTS 改为本地) |
| `cookies.json` | 丰享平台登录 Cookie(自动生成) |
## 运行环境
- Python 虚拟环境: `E:\2025Code\python\IndexTT\index-tts\.venv`
- 依赖: `playwright``requests`
- 浏览器: ChromiumPlaywright 自动管理)
- TTS 模型: IndexTTS2(本地部署,首次加载需 1-2 分钟)
## 通知渠道
| 渠道 | 说明 |
|------|------|
| 企业微信 | Webhook 推送消息 |
| 飞书群 | Webhook 推送消息 |
| 本地 TTS | IndexTTS2 合成语音 + 扬声器播放 |
## 轮询策略
| 时段 | 间隔 | 拉取数量 |
|------|------|---------|
| 高峰期 11:00-13:00, 17:00-19:00 | 5-10秒 | 20条 |
| 闲时 | 5-10秒 | 5条 |
| 夜间 21:00-07:40 | 自动暂停 | — |
## 消息格式
- 推送: `【丰享丰食】订单收款成功,收款24.00元`
- TTS: 同上文案语音播报
## 已逆向 API
### 丰享平台
- **登录**: `POST https://fspass.szfx.com/api/login`
- 腾讯验证码 TCaptcha,必须浏览器手动登录
- **订单列表**: `POST https://fs.szfx.com/saasmerchant/pcweb/order/quickpayorder/list`
- Body: `{"shopId": "20434543575189", "page": 1, "pageSize": 20}`
- 返回: `{errno: 0, data: {total, list: [...], count}}`
## 注意事项
- Cookie 过期时自动弹浏览器提示重新登录,并推送通知
- 每 5 分钟自动保活防 Session 过期
- 无需小米账号或小爱音箱,TTS 完全本地运行
+114
View File
@@ -0,0 +1,114 @@
"""
丰享商家端数据抓取脚本
使用 Playwright 半自动登录 + requests 调用数据接口
"""
import json
import time
import requests
from playwright.sync_api import sync_playwright
# ============ 配置 ============
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"
MMS_BASE = "https://fs.szfx.com/MMS"
SHOP_ID = "20434543575189"
# ==============================
def login_and_get_cookies():
"""用 Playwright 打开登录页,等待用户手动登录,然后提取 Cookie"""
print("[1/3] 启动浏览器,请手动完成登录(包括验证码)...")
with sync_playwright() as p:
browser = p.chromium.launch(headless=False, args=["--start-maximized"])
context = browser.new_context(no_viewport=True)
page = context.new_page()
page.goto(LOGIN_URL)
# 等待用户登录成功(检测页面跳转到 MMS 首页)
print("[2/3] 等待登录完成...(请在浏览器中输入账号密码并完成验证码)")
try:
page.wait_for_url("**/MMS**", timeout=300_000)
print(" 登录成功!正在提取 Cookie...")
except Exception:
print(" 等待超时(5分钟),请重新运行脚本。")
browser.close()
return None
# 确保访问过目标页面(触发必要的 Cookie 设置)
page.goto(MMS_BASE)
page.wait_for_load_state("networkidle")
time.sleep(2)
# 提取所有 Cookie
playwright_cookies = context.cookies()
cookies = {c["name"]: c["value"] for c in playwright_cookies}
print(f" 获取到 {len(cookies)} 个 Cookie")
browser.close()
return cookies
def fetch_order_list(cookies, page=1, page_size=20):
"""调用快捷买单订单列表接口"""
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": page,
"pageSize": page_size,
}
print(f"[3/3] 正在请求订单数据(第{page}页)...")
resp = requests.post(DATA_API, json=payload, headers=headers, cookies=cookies, timeout=30)
data = resp.json()
if data.get("errno") == 0:
print(" 请求成功!")
return data.get("data")
else:
print(f" 请求失败: errno={data.get('errno')}, errmsg={data.get('errmsg')}")
return None
def save_data(data, filename="order_data.json"):
"""保存数据到文件"""
with open(filename, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
print(f" 数据已保存到 {filename}")
def main():
# 第一步:登录获取 Cookie
cookies = login_and_get_cookies()
if not cookies:
return
# 保存 Cookie 以便下次复用
with open("cookies.json", "w", encoding="utf-8") as f:
json.dump(cookies, f, ensure_ascii=False, indent=2)
print(" Cookie 已保存到 cookies.json(下次可直接使用)\n")
# 第二步:获取订单数据
result = fetch_order_list(cookies)
if result:
save_data(result)
# 打印摘要
if isinstance(result, dict):
print(f"\n 数据摘要:")
for k, v in result.items():
if not isinstance(v, (list, dict)):
print(f" {k}: {v}")
elif isinstance(v, list):
print(f" {k}: 共 {len(v)} 条记录")
if __name__ == "__main__":
main()
+52
View File
@@ -0,0 +1,52 @@
"""
丰享商家端数据抓取 - Cookie 复用模式
已有 cookies.json 时直接调用接口,Cookie 过期再运行主脚本重新登录
"""
import json
import sys
import requests
DATA_API = "https://fs.szfx.com/saasmerchant/pcweb/order/quickpayorder/list"
SHOP_ID = "20434543575189"
def load_cookies():
try:
with open("cookies.json", "r", encoding="utf-8") as f:
return json.load(f)
except FileNotFoundError:
print("未找到 cookies.json,请先运行 fengxiang_scraper.py 完成登录")
sys.exit(1)
def fetch_order_list(cookies, page=1, page_size=20):
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": page, "pageSize": page_size}
resp = requests.post(DATA_API, json=payload, headers=headers, cookies=cookies, timeout=30)
return resp.json()
def main():
cookies = load_cookies()
print(f"已加载 {len(cookies)} 个 Cookie")
page = int(sys.argv[1]) if len(sys.argv) > 1 else 1
result = fetch_order_list(cookies, page=page)
if result.get("errno") == 0:
data = result.get("data")
print(json.dumps(data, ensure_ascii=False, indent=2))
elif result.get("errno") == 110003:
print("Cookie 已过期,请重新运行 fengxiang_scraper.py 完成登录")
else:
print(f"请求失败: {result}")
if __name__ == "__main__":
main()
+67
View File
@@ -0,0 +1,67 @@
"""
本地TTS语音播报模块 - 基于IndexTTS2
"""
import os
import sys
import time
import tempfile
import winsound
# IndexTTS2 路径配置
_INDEXTTS_HOME = r"E:\2025Code\python\IndexTT\index-tts"
_INDEXTTS_MODEL_DIR = os.path.join(_INDEXTTS_HOME, "checkpoints")
_INDEXTTS_CFG = os.path.join(_INDEXTTS_MODEL_DIR, "config.yaml")
_INDEXTTS_VENV_PYTHON = os.path.join(_INDEXTTS_HOME, ".venv", "Scripts", "python.exe")
# 默认语音样本
_DEFAULT_VOICE_PROMPT = os.path.join(_INDEXTTS_HOME, "examples", "voice_01.wav")
# 全局 TTS 实例(延迟初始化)
_tts_instance = None
def _get_tts():
"""延迟初始化 IndexTTS2 实例"""
global _tts_instance
if _tts_instance is None:
sys.path.insert(0, _INDEXTTS_HOME)
from indextts.infer_v2 import IndexTTS2
print("[TTS] 正在加载 IndexTTS2 模型(首次加载较慢,请耐心等待)...")
_tts_instance = IndexTTS2(
cfg_path=_INDEXTTS_CFG,
model_dir=_INDEXTTS_MODEL_DIR,
use_fp16=True,
use_cuda_kernel=False,
use_deepspeed=False
)
print("[TTS] IndexTTS2 模型加载完成")
return _tts_instance
def speak(text, voice_prompt=None):
"""使用本地 IndexTTS2 合成语音并播放
Args:
text: 要播报的文本
voice_prompt: 语音样本路径,默认使用 voice_01.wav
"""
if voice_prompt is None:
voice_prompt = _DEFAULT_VOICE_PROMPT
tts = _get_tts()
output_path = os.path.join(tempfile.gettempdir(), f"fx_tts_{int(time.time() * 1000)}.wav")
tts.infer(
spk_audio_prompt=voice_prompt,
text=text,
output_path=output_path,
verbose=False
)
# 播放生成的音频
winsound.PlaySound(output_path, winsound.SND_FILENAME)
return output_path
if __name__ == "__main__":
speak("本地语音播报测试成功")
+307
View File
@@ -0,0 +1,307 @@
"""
丰享订单监控 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()
+91
View File
@@ -0,0 +1,91 @@
"""获取最新一条订单并推送到企业微信群"""
import json
import datetime
import requests
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"
STATUS_MAP = {1: "已完成", 3: "已退款", 4: "退款中", 5: "已关闭"}
def fmt_ts(ts):
return datetime.datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M:%S") if ts else "-"
def fmt_money(cents):
return f"{cents / 100:.2f}" if cents else "0.00元"
def load_cookies():
with open("cookies.json", "r", encoding="utf-8") as f:
return json.load(f)
def fetch_latest_order():
cookies = load_cookies()
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": 1}
resp = requests.post(DATA_API, json=payload, headers=headers, cookies=cookies, timeout=30)
data = resp.json()
if data.get("errno") != 0:
print(f"请求失败: {data}")
return None
return data["data"]["list"][0]
def format_order(order):
status = STATUS_MAP.get(order["status"], str(order["status"]))
return (
f"【新订单通知】\n"
f"订单号:{order['orderId']}\n"
f"店铺:{order['shopName']}\n"
f"城市:{order['city']}\n"
f"大区:{order['largeAreaName']}\n"
f"企业:{order['companyName']}\n"
f"用餐地点:{order['workplaceName']}\n"
f"下单时间:{fmt_ts(order['createTime'])}\n"
f"支付时间:{fmt_ts(order['payTime'])}\n"
f"完成时间:{fmt_ts(order['finishTime'])}\n"
f"订单金额:{fmt_money(order['allPrice'])}\n"
f"实付金额:{fmt_money(order['amountPayable'])}\n"
f"支付方式:{order['payTypeName']}\n"
f"订单来源:{order['orderSourceName']}\n"
f"状态:{status}"
)
def send_to_wecom(text):
payload = {
"msgtype": "text",
"text": {"content": text}
}
resp = requests.post(WEBHOOK_URL, json=payload, timeout=10)
result = resp.json()
if result.get("errcode") == 0:
print("推送成功!")
else:
print(f"推送失败: {result}")
return result
def main():
order = fetch_latest_order()
if not order:
return
msg = format_order(order)
print(msg)
print("\n--- 正在推送到企业微信群 ---\n")
send_to_wecom(msg)
if __name__ == "__main__":
main()
+40
View File
@@ -0,0 +1,40 @@
@echo off
chcp 65001 >nul
title 丰享订单监控
echo ============================================
echo 丰享订单监控系统 v4
echo 推送: 企业微信 + 本地TTS语音播报
echo ============================================
echo.
cd /d "%~dp0"
:: Python路径(IndexTTS虚拟环境)
set PYTHON=E:\2025Code\python\IndexTT\index-tts\.venv\Scripts\python.exe
:: 检查Python是否存在
if not exist "%PYTHON%" (
echo [错误] 未找到Python: %PYTHON%
echo 请确认IndexTTS已正确安装
pause
exit /b 1
)
:: 检查cookie文件
if not exist "cookies.json" (
echo [提示] 未检测到登录Cookie,首次运行将自动打开浏览器
echo 请在浏览器中手动完成登录(含验证码)
echo.
)
echo [启动] 正在启动订单监控...
echo.
"%PYTHON%" order_monitor.py
if %errorlevel% neq 0 (
echo.
echo [异常] 监控程序异常退出,错误码: %errorlevel%
pause
)
+83
View File
@@ -0,0 +1,83 @@
"""
小米账号登录 - 通过浏览器获取 serviceToken
浏览器打开后请手动登录,脚本会自动检测登录成功
"""
import json
import time
from playwright.sync_api import sync_playwright
XIAOI_CONFIG = "xiaomi_config.json"
def login_and_get_tokens():
print("启动浏览器,请登录小米账号...")
with sync_playwright() as p:
browser = p.chromium.launch(headless=False, args=["--start-maximized"])
context = browser.new_context(no_viewport=True)
page = context.new_page()
page.goto("https://account.xiaomi.com/pass/serviceLogin?sid=micoapi&_json=true&callback=https%3A%2F%2Fapi2.mina.mi.com%2Fsts")
page.wait_for_load_state("networkidle")
print("请在浏览器中完成登录(输入手机号、密码、验证码)")
print("等待登录完成(最长5分钟)...")
# 等待页面离开登录页(说明登录成功)
try:
page.wait_for_url(lambda url: "account.xiaomi.com/pass" not in url, timeout=300_000)
print("检测到登录成功!")
except Exception:
print("登录超时或失败")
browser.close()
return None
time.sleep(3)
# 获取 cookies
cookies = context.cookies()
cookie_dict = {c["name"]: c["value"] for c in cookies}
service_token = cookie_dict.get("serviceToken", "")
user_id = cookie_dict.get("userId", "")
print(f"获取到 {len(cookies)} 个 cookies")
print(f" userId: {user_id}")
print(f" serviceToken: {'' if service_token else ''}")
# 通过浏览器调用 device_list API
print("\n获取设备列表...")
result = page.evaluate("""
async () => {
const resp = await fetch('https://api2.mina.mi.com/admin/v2/device_list?master=0&requestId=app_ios_' + Math.random().toString(36).substr(2, 30));
return await resp.json();
}
""")
devices = []
if result.get("code") == 0:
devices = result.get("data", [])
print(f"找到 {len(devices)} 个设备:")
for d in devices:
print(f" 名称: {d.get('name')} | 型号: {d.get('hardware')} | DID: {d.get('deviceID')}")
else:
print(f"获取设备失败: {result}")
# 保存配置
config = {
"userId": user_id,
"serviceToken": service_token,
"devices": devices,
"allCookies": cookie_dict,
}
with open(XIAOI_CONFIG, "w", encoding="utf-8") as f:
json.dump(config, f, ensure_ascii=False, indent=2)
print(f"\n配置已保存到 {XIAOI_CONFIG}")
browser.close()
return config
if __name__ == "__main__":
login_and_get_tokens()