Compare commits

...

2 Commits

Author SHA1 Message Date
houhuan 69046d1ccc merge: 保留本地最新版本 2026-05-12 17:07:12 +08:00
houhuan f0bfd9dbbe v4: 改用本地IndexTTS2 + 新增飞书推送
- TTS从小爱音箱云API改为本地IndexTTS2模型
- 新增飞书群Webhook推送
- 新增notify_all统一通知(企业微信+飞书)
- 新增local_tts.py本地TTS封装模块
- 新增run.bat一键启动脚本
- 新增README.md项目说明
- 新增.gitignore排除敏感文件
- 更新CLAUDE.md文档
2026-05-12 17:06:49 +08:00
6 changed files with 260 additions and 144 deletions
+2 -13
View File
@@ -1,23 +1,12 @@
# Sensitive data - NEVER commit
CLAUDE.md
# Sensitive
cookies.json
xiaomi_raw_cookies.json
xiaomi_config.json
xiaomi_raw_cookies.json
order_data.json
*.mi.token
# Python
__pycache__/
*.pyc
*.pyo
# IDE
.vscode/
.idea/
# OS
Thumbs.db
Desktop.ini
# Claude
.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
+39 -81
View File
@@ -1,106 +1,64 @@
# 丰享订单监控
自动监控丰享商家端(fs.szfx.com新订单,同时推送到**企业微信群**和**小爱音箱语音播报**。
## 功能特性
- **自动轮询**:高峰期(11-13点、17-19点)10-20秒/次,闲时30-60秒/次
- **双通道推送**:新订单同时推送企业微信消息 + 小爱音箱 TTS 语音播报
- **Cookie 复用**:首次登录后保存 Cookie,后续启动无需重复登录
- **夜间暂停**21:00 ~ 07:40 自动暂停轮询,节省资源
- **Session 保活**:每5分钟自动发请求维持 Cookie 活性
- **过期自动恢复**:Cookie 过期时弹浏览器提示重新登录,并推送企业微信通知
- **开机自启**:支持 Windows 计划任务,登录系统后自动运行
自动监控丰享商家端(fs.szfx.com)新订单,同时推送到**企业微信**、**飞书群**并进行**本地TTS语音播报**。
## 快速开始
### 1. 安装依赖
```bash
pip install playwright requests miservice_fork aiohttp
playwright install chromium
```
### 2. 配置小米音箱(可选)
首次使用需要获取小米 passToken:
```bash
python setup_xiaomi.py
```
浏览器打开后手动登录小米账号,Token 自动保存到 `~/.mi.token`
### 3. 运行监控
```bash
python order_monitor.py
```
首次运行会打开浏览器,手动完成丰享平台登录(有腾讯验证码,需人工操作)。登录成功后 Cookie 自动保存,之后重启无需再登录。
### 4. 测试推送
```bash
python test_push.py
```
拉取最新一条订单,测试企业微信和小爱音箱是否正常。
双击 `run.bat` 启动。首次运行会自动打开浏览器登录丰享平台,后续复用 Cookie 无需反复登录。
## 文件说明
| 文件 | 用途 |
|------|------|
| `order_monitor.py` | 主监控程序,轮询订单 + 推送 + 播报 |
| `test_push.py` | 测试脚本,验证企业微信和音箱通道 |
| `push_latest_order.py` | 单次获取最新订单并推送企业微信 |
| `fetch_orders.py` | 命令行获取订单数据(JSON 输出) |
| `fengxiang_scraper.py` | Playwright 半自动登录获取 Cookie |
| `setup_xiaomi.py` | 小米账号登录获取 serviceToken |
| `run_monitor.bat` | Windows 启动批处理 |
| `setup_autostart.ps1` | Windows 开机自启配置脚本 |
| `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(自动生成) |
## 已逆向的 API
## 运行环境
### 丰享平台
- Python 虚拟环境: `E:\2025Code\python\IndexTT\index-tts\.venv`
- 依赖: `playwright``requests`
- 浏览器: ChromiumPlaywright 自动管理)
- TTS 模型: IndexTTS2(本地部署,首次加载需 1-2 分钟)
- **登录**`POST https://fspass.szfx.com/api/login`
- 参数:`uname`(手机号)、`upass`(密码 Base64 反转)、`ticket``randstr`(腾讯验证码)
- Cookie`USS``PTOKEN``CPTOKEN``STOKEN`
## 通知渠道
- **订单列表**`POST https://fs.szfx.com/saasmerchant/pcweb/order/quickpayorder/list`
- Content-Type: application/json
- Body: `{"shopId": "...", "page": 1, "pageSize": 20}`
- 返回:`{errno: 0, data: {total, list: [...], count}}`
| 渠道 | 说明 |
|------|------|
| 企业微信 | Webhook 推送消息 |
| 飞书群 | Webhook 推送消息 |
| 本地 TTS | IndexTTS2 合成语音 + 扬声器播放 |
### 小米/小爱音箱
## 轮询策略
- 使用 [miservice_fork](https://github.com/nicepkg/miservice_fork) Python 库
- TTS 播报:`MiNAService.text_to_speech(deviceId, text)`
- Token 保存在 `~/.mi.token`
| 时段 | 间隔 | 拉取数量 |
|------|------|---------|
| 高峰期 11:00-13:00, 17:00-19:00 | 5-10秒 | 20条 |
| 闲时 | 5-10秒 | 5条 |
| 夜间 21:00-07:40 | 自动暂停 | — |
## 消息格式
企业微信和小爱音箱使用统一格式:
- 推送: `【丰享丰食】订单收款成功,收款24.00元`
- TTS: 同上文案语音播报
> 【丰享丰食】订单收款成功,收款24.00元
## 已逆向 API
## 开机自启(Windows
### 丰享平台
已配置计划任务 `FXOrderMonitor`,用户登录时自动运行。
```powershell
# 手动配置
.\setup_autostart.ps1
```
- **登录**: `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}}`
## 注意事项
- 丰享登录有腾讯验证码 TCaptcha,无法纯接口绕过,首次必须用浏览器登录
- Cookie 有效期较长,持续使用可维持;过期会自动弹窗提示重新登录
- 小米 passToken 会过期,过期后需重新运行 `setup_xiaomi.py`
- 配置文件(cookies.json、xiaomi_config.json)含敏感信息,已加入 .gitignore
## License
MIT
- Cookie 过期时自动弹浏览器提示重新登录,并推送通知
- 每 5 分钟自动保活防 Session 过期
- 无需小米账号或小爱音箱,TTS 完全本地运行
+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("本地语音播报测试成功")
+30 -50
View File
@@ -10,25 +10,20 @@ import json
import random
import time
import datetime
import asyncio
import threading
import requests
from pathlib import Path
from aiohttp import ClientSession
from miservice import MiAccount, MiNAService
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"
# 小爱音箱配置
XIAOMI_USER_ID = "1136458602"
XIAOMI_TOKEN_PATH = str(Path.home() / ".mi.token")
XIAOMI_SPEAKER_DID = "3ba2c1e8-d8cb-45c5-b88a-15624e7a02f3"
# 本地 TTS 配置(使用 IndexTTS2,无需额外配置)
# 高峰时段配置
PEAK_HOURS = [(11, 13), (17, 19)]
@@ -86,6 +81,12 @@ 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:
@@ -94,53 +95,32 @@ def send_to_wecom(text):
if result.get("errcode") == 0:
log("企业微信推送成功")
else:
log(f"推送失败: {result}")
log(f"企业微信推送失败: {result}")
except Exception as e:
log(f"推送异常: {e}")
log(f"企业微信推送异常: {e}")
def _run_async_in_thread(coro):
"""在独立线程中运行协程,避免与 Playwright 事件循环冲突"""
result = None
error = None
def _target():
nonlocal result, error
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
result = loop.run_until_complete(coro)
except Exception as e:
error = e
finally:
loop.close()
t = threading.Thread(target=_target)
t.start()
t.join(timeout=15)
if error:
raise error
return result
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:
async def _tts():
async with ClientSession() as session:
account = MiAccount(session, XIAOMI_USER_ID, None, XIAOMI_TOKEN_PATH)
mina = MiNAService(account)
return await mina.text_to_speech(XIAOMI_SPEAKER_DID, text)
result = _run_async_in_thread(_tts())
if result and result.get("code") == 0:
log("语音播报成功")
else:
log(f"语音播报失败: {result}")
_local_speak(text)
log("语音播报成功")
except Exception as e:
log(f"语音播报异常: {e}")
def load_cookies():
"""加载本地 Cookie 文件"""
cookie_path = Path(__file__).parent / COOKIES_FILE
@@ -227,7 +207,7 @@ def main():
# 2. 没有有效 Cookie → 打开浏览器登录
if not cookies:
send_to_wecom("【监控通知】需要登录丰享平台,请查看电脑浏览器完成登录")
notify_all("【监控通知】需要登录丰享平台,请查看电脑浏览器完成登录")
cookies = browser_login()
if not cookies:
log("登录失败,退出")
@@ -259,7 +239,7 @@ def main():
test = fetch_orders_via_requests(cookies, page_size=1)
if test == "SESSION_EXPIRED":
log("Cookie 在夜间过期,需要重新登录")
send_to_wecom("【监控通知】Cookie 过期,请查看电脑浏览器完成登录")
notify_all("【监控通知】Cookie 过期,请查看电脑浏览器完成登录")
cookies = browser_login()
if not cookies:
log("登录失败,退出")
@@ -285,10 +265,10 @@ def main():
# Cookie 过期 → 浏览器重新登录
if orders == "SESSION_EXPIRED":
log("Cookie 已过期!")
send_to_wecom("【监控异常】Cookie 已过期,请查看电脑浏览器完成登录")
notify_all("【监控异常】Cookie 已过期,请查看电脑浏览器完成登录")
cookies = browser_login()
if not cookies:
send_to_wecom("登录超时,监控已退出。请手动重新运行。")
notify_all("登录超时,监控已退出。请手动重新运行。")
return
send_to_wecom("重新登录成功,监控已恢复")
last_keepalive = time.time()
@@ -312,7 +292,7 @@ def main():
# 从旧到新依次推送
for o in reversed(new_orders):
msg = format_msg(o)
send_to_wecom(msg)
notify_all(msg)
speak(msg)
time.sleep(0.5)
last_order_id = new_orders[0]["orderId"]
+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
)