第三方弹幕接入
第三方自己的直播间/聊天系统里的弹幕,怎么让 AI 主播看见、回应,再把回应拿回去显示。
0. 先对号入座
| 你的情况 | 用哪条路 | 你会得到什么 |
|---|---|---|
| A. 只有聊天/IM(App 内聊天、客服群) | POST /v1/anchors/{id}/ask | 纯文本回答,你自己渲染(不发声) |
| B. 有直播间和弹幕(B 站/抖音/自有直播) | 弹幕推进来 + 听我们房间的声音 | 主播回应你们的观众;回复文本用 danmaku.replied 取回 |
| C. 观众就在我们页面 | 不用接,现有弹幕通道 | 照常 |
用哪把钥匙:弹幕注入需要danmaku:ingest。推荐用运营在后台「API 接入」里给这个主播生成的 专用 Key(只能操作这一个主播,泄露也只影响这一个);平台直推则用 ingest 源自己的 secret, 连主 Key 都不用给。另外该主播的 API 开关必须开着,否则会返回403 api_disabled。
1. 三条上行通道
① 单条注入(事件驱动,最简单)
curl -X POST https://szr.dzhan.cc/v1/anchors/a_1f2c3d4e/danmaku \
-H "Authorization: Bearer vt_live_…" -H 'Content-Type: application/json' \
-H "Idempotency-Key: bili-887766" \
-d '{
"source": "bilibili",
"source_msg_id": "887766",
"user": {"id": "u_1", "name": "观众A", "level": 12},
"text": "主播这个功能怎么用?",
"kind": "chat",
"ts": 1730000000
}'
→ {"ok":true,"accepted":1,"results":[{"source_msg_id":"887766","status":"accepted"}],"note":"是否回应由门控判定"}
kind:chat|gift|follow|enter。礼物/关注优先级最高(会绕过判定直接答谢)。
② 批量注入(自己攒批)
curl -X POST https://szr.dzhan.cc/v1/anchors/a_1f2c3d4e/danmaku/batch \
-H "Authorization: Bearer …" -H 'Content-Type: application/json' \
-d '{"items":[
{"source":"bilibili","id":"1","user":"观众A","text":"这个多少钱"},
{"source":"bilibili","id":"2","user":"观众B","text":"哈哈哈哈"},
{"source":"bilibili","id":"3","user":"观众C","text":"送了个小心心","kind":"gift"}
]}'
限制:一次 ≤ 50 条、≤ 8KB;返回逐条 accepted / duplicate / filtered / dropped 便于对账。
③ 平台直推(推荐给"有回调能力"的直播平台)
不要把主 API Key 填进第三方平台的后台。先建一个 ingest 源,它有自己的 secret:
curl -X POST https://szr.dzhan.cc/v1/anchors/a_1f2c3d4e/ingest-sources \
-H "Authorization: Bearer …" -H 'Content-Type: application/json' \
-d '{"platform":"generic",
"fieldmap":{"items_path":"$.data.list",
"text":"$.content",
"user.name":"$.nick",
"id":"$.msg_id",
"kind":"const:chat"}}'
{"ok":true,"id":"ig_9f2c…","ingest_url":"https://szr.dzhan.cc/v1/ingest/ig_9f2c…",
"secret":"…(只显示一次)","platform":"generic","fieldmap":{…}}
然后把平台回调地址填成 ingest_url,请求体用平台原始 JSON(不用改造)。签名:
X-VTB-Timestamp: 1730000000
X-VTB-Signature: sha256=HMAC_SHA256(secret, "1730000000." + raw_body)
调试时也可以直接带 X-VTB-Secret: <secret>(不建议生产用)。
fieldmap 是"字段路径映射",支持 $.a.b、a.b、const:xxx,不用改代码:
| 键 | 含义 |
|---|---|
items_path | 弹幕数组在 JSON 里的位置(为空则整个 body 当一条) |
text | 弹幕文本 |
user.name | 用户名 |
id | 平台消息 id(用于去重,强烈建议给) |
kind | 类型;const:chat 表示固定为聊天 |
ts | 时间戳(可选) |
内置平台预设:generic / bilibili / douyin / vtb,选平台名即可,再用 fieldmap 覆盖个别字段。
2. 内部怎么处理(你会感受到的行为)
去重 → 过滤 → 排优先级 → 门控判定 → 插播/继续播 → 回传事件
- 去重:
(source, source_msg_id)10 分钟窗口。平台重试不会让主播说三遍。 - 过滤:>120 字截断、纯表情/纯标点丢弃、URL 脱敏、可按主播配黑名单。
- 优先级:
gift/follow/enter>提问(含"吗/怎么/为什么/多少钱"等)>普通。队列满时先丢普通。 - 门控:不是每条都回应。判定为"值得回"才插播,否则继续讲自己的内容——这是刻意的,否则会变成复读机。
- 洪峰聚合:当待处理弹幕 > 6 条时,进入聚合模式,每 30 秒合成一条:
「现在弹幕在刷:观众A:这个多少钱、观众B:有优惠吗(共 47 条)」 主播回应的是全场气氛,而不是逐条。送礼和提问会被单独拎出来不参与聚合。
3. 回复怎么拿回来
AI 只有声音和文本,不能直接往你的平台发弹幕。回传靠事件:
| 事件 | 关键字段 | 用途 |
|---|---|---|
danmaku.replied | source source_msg_id reply_text duration_ms | 主用:把 AI 这句贴回你的聊天区 |
speech.finished | kind text duration_ms | 记录全部播报(含主动讲解、笑话) |
speech.started | kind | 想"一开口就显示气泡"用这个 |
anchor.started/stopped | room | 生命周期 |
{"event":"danmaku.replied","anchor_id":"a_1f2c3d4e","ts":1793616197.2,
"data":{"source":"bilibili","source_msg_id":"887766",
"reply_text":"这位朋友问怎么用,点右上角新建就行,第一次会送你一个体验额度。",
"duration_ms":7043}}
两种取法:
- Webhook(推荐,服务端收):
POST /v1/webhooks {"url":"https://your.app/hook","events":["danmaku.replied"]} - SSE(前端收):
GET /v1/events?anchor=a_1f2c3d4e&access_token=<key>
也可以给 ingest 源配 reply_webhook:我们会直接把 {source_msg_id, reply_text} POST 到你给的地址。
时序说明:danmaku.replied在这句话播完时发(文本才稳定)。 想更早显示,用speech.started先占位、再用speech.finished补全文。
4. 联调清单
| 检查 | 期望 |
|---|---|
同一条 source_msg_id 连发 3 次 | 只有 1 次 accepted,其余 duplicate |
发一条 "哈哈哈哈" | 大概率不回应(门控不拦但判定为低价值) |
发一条 "送了个小心心" + kind:gift | 一定被答谢 |
| 1 秒灌 200 条 | 不崩、不丢礼物、每 30 秒出现一条聚合摘要 |
| 停掉主播再注入 | 409 anchor_not_live(先把主播开起来) |
| 用别人的 anchor_id | 404(按租户隔离,不暴露存在性) |
本地想看点效果可以跑 python scripts/demo_danmaku.py,或用 scripts/webhook_sink.py 起一个接收器看事件。