biliup → AstrBot(QQ) 录播完成通知 修复
1648 字
8 分钟
biliup → AstrBot(QQ) 录播完成通知 修复
1. 原本设想的链路
biliup 录制 → 上传 B 站 → 后处理(notify) → push_lite(:9966) → AstrBot → QQ 群- biliup:ghcr.io/biliup/caution(录+传一体),配置在 WebUI,落库到 sqlite
data.sqlite3 - push_lite:AstrBot 插件
astrbot_plugin_push_litev0.2.2,监听0.0.0.0:9966- 路由
POST /send,Body 需content+umo,HeaderAuthorization: Bearer <token>
- 路由
- AstrBot:soulter/AstrBot v4.26.7,QQ 后端走 NapCat(aiocqhttp 适配器,platform_id =
ai)
2. 为什么一直「没收到 webhook」(两个独立根因)
根因 A:biliup 的 run 后处理根本不执行
biliup-cli 1.2.1 的「后置任务(run)」并不会在上传完成后去调用外部脚本。
证据:
- 每天的实际上传日志里只有
mv步骤,从没出现run; - push_lite 在真实上传时间点(如 02<24>24>)完全没有任何请求记录;
- 也就是说
notify.py从头到尾没被调用过 → 自然一条通知都发不出。
结论: 依赖 biliup 后处理去触发通知这条路,从根上就走不通,必须绕开。
根因 B:umo 会话格式写错,即使触发也会被 AstrBot 拒绝
最开始写的 umo 是 group_485476610,AstrBot 侧直接报错:
| 写的格式 | AstrBot 报错 |
|---|---|
group_485476610 | not enough values to unpack (expected 3, got 1) |
ai:group:485476610 | 'group' is not a valid MessageType |
查 AstrBot 源码 message_type.py 确认:会话标识是三段式
platform_id : message_type_value : session_id- 中间段必须是
MessageType枚举的「值」,首字母大写:GroupMessage/FriendMessage - QQ 适配器的 platform_id 是
ai
正确写法:ai:GroupMessage:485476610(这一步即便根因 A 修好,格式错也照样发不出去)。
3. 最终修复方案(绕过后处理,改用目录监听)
放弃 biliup 后处理,改为监听备份目录的脚本:
| 项目 | 内容 |
|---|---|
| 脚本路径 | /opt/notify/watch_backup.py(zero 用户运行) |
| 保活 | zero 的 crontab 每分钟拉起;flock 单实例锁防多开 |
| 监听目录 | /opt/biliup/backup(biliup 的 mv 后处理把录播移到这里) |
| 轮询周期 | 每 30 秒扫一次 |
| 触发条件 | 发现新媒体文件,且文件修改时间稳定 ≥ 5 秒 |
| 媒体扩展名 | .flv .mp4 .mkv .ts .mov .avi .m4v .mp3 .m4a .m4s(其余如 .log 会被过滤,避免误推) |
| 推送目标 | POST http://127.0.0.1:9966/send |
| 认证 | Authorization: Bearer cUoGKubE8IJvNu8nMwpK_Z1K-TbcvfurPzug10UWak0 |
| Body | {"content":"录播已上传完成\n文件:<文件名>","umo":"ai:GroupMessage:485476610"} |
| 去重 | 已通知的文件名写入 /opt/notify/watched_state.json;仅当 HTTP 200 成功才记录 → 失败自动重试,不漏不重 |
配套修正:
- biliup 的
postprocessor已用 sudo 还原为[{"mv":"/opt/backup"}](去掉run,避免将来双通知); - 脚本加了
MEDIA扩展名过滤,修掉了早期版本把download.log当媒体推送的 bug。
脚本核心逻辑(节选):
st = notify(key)if st == 200: # 只有成功才记 state state[key] = int(now) save_state(state) print("notified:", key)# 失败则不记录 → 下一轮轮询会重试4. 已验证可用的证据(2026-07-20 诊断)
- ✅ watcher 进程存活(PID 3409022,父进程 1,
STAT S= 正常 30s 轮询中睡觉,CPU 时间 0 属正常,不是卡死); - ✅
curl用脚本同款 payload 直发 push_lite → HTTP 200;端口 9966 监听0.0.0.0; - ✅
watch.log已显示两个真实录制被notified::【2026-07-19】咪茉Mimo直播回放.flv【2026-07-20】咪茉Mimo直播回放.flv→ 生产链路已经实际工作过。
关于「TEST_NOTIFY.flv 没触发」的假阴性说明
早前在排查时放过一个 TEST_NOTIFY.flv 测试文件,但它没被通知。这不是逻辑 bug,而是假阴性:
- 测试文件没有在「watcher 正常运行期间」持续留在
/opt/biliup/backup里(要么放的时候 watcher 被重启/杀掉,要么文件太新未过 5s 稳定期就被清理/挪走); - 当前 state 文件里也没有
TEST_NOTIFY.flv这条记录,说明它从没被成功扫到过。
换句话说:真实录播文件能被正常通知(见上方 ✅ 证据),测试文件只是没赶上好时机。
5. 手动端到端验证(确认 QQ 群真能收到)
# 1) 直接在 backup 目录生成测试文件(不经过 /tmp)echo test | sudo tee /opt/biliup/backup/TEST_NOTIFY.flv > /dev/null
# 2) 盯日志,等 ~40 秒(30s 轮询 + 5s 稳定判定)tail -f /opt/notify/watch.log# 应出现:notified: TEST_NOTIFY.flv# 同时 QQ 群 485476610 收到「录播已上传完成 / 文件:TEST_NOTIFY.flv」
# 3) 确认后清理(state 里的记录无害,不会重复推送)sudo rm -f /opt/biliup/backup/TEST_NOTIFY.flv注意第 2 步要真等够 ~40 秒,前 30 多秒没反应是正常的。
若等满 40 秒仍无 notified:,把 tail -f 的输出贴回来,再看是否 watcher 被 cron 重启打断(sudo pkill -f watch_backup.py 后让它重新拉起即可)。
6. 监听脚本完整代码
密钥全部走环境变量,不要把 token 写死进仓库。服务器实际部署版只是把下列变量硬编码成了真实值,逻辑完全一致。
#!/usr/bin/env python3"""watch_backup.py — 录播备份目录监听通知
当 biliup 等录制工具把完成的媒体文件 mv 到监控目录后,本脚本会检测新文件(稳定 N 秒后)并推送到 AstrBot 的 push_lite 插件,进而发到指定 QQ 群 / 私聊。
配置全部通过环境变量注入(不要把 token 写死进仓库): PUSH_LITE_URL 推送地址,默认 http://127.0.0.1:9966/send PUSH_LITE_TOKEN push_lite 的 Bearer token(必填) PUSH_LITE_UMO 目标会话,如 ai:GroupMessage:123456 WATCH_DIR 监控目录,如 /opt/biliup/backup STATE_FILE 去重状态文件,默认 <脚本目录>/watched_state.json LOCK_FILE 单实例锁文件,默认 <脚本目录>/watch.lock POLL 轮询间隔秒,默认 30 STABLE_SECS 文件需稳定多少秒才认为写完,默认 5
依赖:仅 Python 3 标准库,无需 pip install。"""import osimport sysimport jsonimport timeimport globimport fcntlimport urllib.request
URL = os.environ.get("PUSH_LITE_URL", "http://127.0.0.1:9966/send")TOKEN = os.environ.get("PUSH_LITE_TOKEN", "")UMO = os.environ.get("PUSH_LITE_UMO", "ai:GroupMessage:000000")WATCH_DIR = os.environ.get("WATCH_DIR", "/opt/biliup/backup")_HERE = os.path.dirname(os.path.abspath(__file__))STATE = os.environ.get("STATE_FILE", os.path.join(_HERE, "watched_state.json"))LOCK = os.environ.get("LOCK_FILE", os.path.join(_HERE, "watch.lock"))POLL = int(os.environ.get("POLL", "30"))STABLE_SECS = int(os.environ.get("STABLE_SECS", "5"))
# 只推送这些媒体扩展名,避免把 download.log 之类当媒体误推MEDIA = {".flv", ".mp4", ".mkv", ".ts", ".mov", ".avi", ".m4v", ".mp3", ".m4a", ".m4s"}
_lf = open(LOCK, "w")try: fcntl.flock(_lf, fcntl.LOCK_EX | fcntl.LOCK_NB)except OSError: print("another instance running, exit", flush=True) sys.exit(0)
def load_state(): try: return json.load(open(STATE)) except Exception: return {}
def save_state(s): tmp = STATE + ".tmp" json.dump(s, open(tmp, "w"), ensure_ascii=False) os.replace(tmp, STATE)
def notify(fname): content = "录播已上传完成\n文件:" + fname payload = json.dumps({"content": content, "umo": UMO}).encode("utf-8") req = urllib.request.Request(URL, data=payload, method="POST") req.add_header("Content-Type", "application/json") req.add_header("Authorization", "Bearer " + TOKEN) try: with urllib.request.urlopen(req, timeout=15) as r: return r.status except Exception as e: print("notify failed:", e, flush=True) return None
def main(): state = load_state() print("watcher started, watch_dir=%s" % WATCH_DIR, flush=True) while True: try: files = [f for f in glob.glob(os.path.join(WATCH_DIR, "*")) if os.path.isfile(f)] now = time.time() for f in files: if os.path.splitext(f)[1].lower() not in MEDIA: continue if now - os.path.getmtime(f) < STABLE_SECS: continue key = os.path.basename(f) if key in state: continue st = notify(key) if st == 200: state[key] = int(now) save_state(state) print("notified:", key, flush=True) except Exception as e: print("loop err:", e, flush=True) time.sleep(POLL)
if __name__ == "__main__": main()跑法示例:
export PUSH_LITE_URL="http://127.0.0.1:9966/send"export PUSH_LITE_TOKEN="你的push_lite_token"export PUSH_LITE_UMO="ai:GroupMessage:485476610"export WATCH_DIR="/opt/biliup/backup"python3 watch_backup.pycron 保活(零依赖用户级):
* * * * * pgrep -f /opt/notify/watch_backup.py > /dev/null || (PUSH_LITE_TOKEN=xxx PUSH_LITE_UMO=ai:GroupMessage:485476610 python3 /opt/notify/watch_backup.py >> /opt/notify/watch.log 2>&1 &)附:关键常量速查
服务器 IP : 192.168.7.102push_lite 端口 : 9966 (0.0.0.0)token : cUoGKubE8IJvNu8nMwpK_Z1K-TbcvfurPzug10UWak0目标群 umo : ai:GroupMessage:485476610监听脚本 : /opt/notify/watch_backup.py监听目录 : /opt/biliup/backup状态文件 : /opt/notify/watched_state.json日志 : /opt/notify/watch.log文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!
biliup → AstrBot(QQ) 录播完成通知 修复
https://vtdd.vip/posts/biliup--astrbotqq-录播完成通知-修复/相关文章智能推荐
1
我的第二个QQ机器人AstrBot
服务器2025-07-21
2
Rustdesk服务部署
服务器因为工作需要,所以在服务器上部署Rustdesk服务
3
本地化Ai大模型Agent部署
服务器最近AI的那么火token又那么贵,所以我想本地化部署一个Ai大模型顺便部署Agent,这样就能无限使用了,感谢老东家的给我提供设备
4
k8s集群部署
服务器2025-08-04
5
300l_pro推算卡驱动固件部署
服务器因为工作需要,所以在服务器上部署300l_pro推算卡的驱动和固件
随机文章随机推荐



