在本地将 Niconico(ニコニコ動画)弹幕 抓取并转换为 弹弹play(dandanplay)兼容格式 的 API 服务,可作为 danmaku-anywhere(弹幕任何地方)扩展的本地 Niconico 弹幕源,也可供其他 dandanplay 兼容的播放程序调用。
docker compose up -d --buildDocker 方式下服务默认映射到宿主机 http://localhost:3012(容器内监听 3000,见下)。也可以不使用 Docker 直接运行:
npm install
npm run build
npm start # 开发模式: npm run devdanmaku-anywhere 内置了弹弹play 弹幕源(manifest),支持将 API 根地址指向自建服务。接入步骤:
- 启动本服务(
docker compose up -d); - 打开 danmaku-anywhere 扩展弹窗 → 弹幕源(Danmaku Providers)页面;
- 点击添加 弹弹play 兼容源(Add DanDanPlay Compatible Provider);
- 将 基础地址(Base URL)设置为
http://localhost:3012,保存; - 播放视频时触发弹幕搜索/匹配,即可从 Niconico 获取弹幕。
说明:扩展请求本地地址需要「允许访问私有/回环主机」的选项,danmaku-anywhere 的 manifest 运行选项已默认开启(
allowPrivateHosts: true),且扩展的host_permissions覆盖http://*/*,无需额外配置。
每个 Niconico 官方频道(一般即一部动画)在弹幕源中表现为一个「作品」,内含全部分集:
搜索/匹配 ──▶ GET /api/v2/search/anime?keyword=关键词 (SnapShot 搜索,按频道合并)
选择作品 ──▶ GET /api/v2/bangumi/ch{频道ID} (频道分集列表)
获取弹幕 ──▶ GET /api/v2/comment/{数字编码episodeId} (单集弹幕)
搜索时输入动漫名/频道名(如「とある魔術の禁書目録」)即可命中对应作品。
所有端点与 dandanplay API v2 兼容。bangumiId 使用频道 ID(ch{频道ID})或 Niconico 视频 ID,episodeId 使用数字编码 ID(见下文)。
路径前缀:所有接口同时挂载于
/api/v2/...与/v2/...两种路径。新版 danmaku-anywhere(manifest 引擎)使用/api/v2/,旧版扩展使用/v2/,两种均可正常使用。
GET /api/v2/comment/:episodeId
curl "http://localhost:3012/api/v2/comment/sm9"{
"count": 938,
"comments": [
{
"p": "0.00,4,65535,d2d9baa9eda7c175ff53537fa734c765",
"m": "ニコニコに入り浸って15年という自分に震える"
}
],
"related": []
}p 字段为逗号分隔的 4 项(dandanplay 兼容生态的标准格式):
| # | 字段 | 含义 | 转换自 |
|---|---|---|---|
| 0 | time |
弹幕出现时间(秒,两位小数) | vposMs / 1000 |
| 1 | mode |
1=滚动,4=底部,5=顶部 | mail 命令 ue/shita |
| 2 | color |
十进制 RGB 颜色 | mail 命令命名色 / 高级会员 #rrggbb |
| 3 | uid |
用户 ID 的 md5 哈希(可省略) | userId |
说明:
- 弹幕集合与 Niconico Web 端一致:弹幕线程接口按视频时间轴返回各时间段的"直近弹幕"(约数百至上千条),不支持历史全量分页。
- 弹幕 ID 超过 JS 安全整数范围(Niconico 使用 64 位 ID),因此不输出
cid字段(dandanplay 格式中该字段可选)。 - 结果在服务端缓存(默认 1 小时,
CACHE_TTL_SECONDS可调)。
GET /api/v2/search/anime?keyword=<关键词>&page=<页码>
底层使用 Niconico SnapShot Search API,仅按标题匹配并按播放量排序。
搜索结果会自动筛选并合并:
- 只保留动漫番剧:按内容 ID 前缀过滤,仅保留
so(官方频道番剧正片)/na/niconico开头的视频,排除sm/nm/ss开头的普通用户投稿(MAD、剪辑、音乐、CM 等杂音); - 去除 dアニメストア 重复版本:同一集若同时存在 dアニメストア 频道(默认
2632720「dアニメストア ニコニコ支店」)与其他频道的版本,只保留主站版本;仅在 dアニメストア 存在的作品仍会保留; - 按频道合并为作品:同一官方频道的多个分集合并为一个选项(如搜索一部番只出现一个结果),选项名为频道名(一般即动画名),点进去后分集按集数排序、分集名为具体视频标题。
合并后的作品通过 /api/v2/bangumi/ch{频道ID}(如 ch2650196)获取分集列表,分集直接抓取 Niconico 频道视频页面(准确、完整,不受搜索噪声影响),抓取失败时自动降级为按频道名搜索。
curl "http://localhost:3012/api/v2/search/anime?keyword=%E3%81%A8%E3%81%82%E3%82%8B"{
"success": true,
"errorCode": 0,
"errorMessage": "",
"animes": [
{
"animeId": 641,
"bangumiId": "ch641",
"animeTitle": "とある魔術の禁書目録<インデックス>",
"type": "other",
"typeDescription": "Niconico",
"imageUrl": "",
"startDate": "",
"episodeCount": 1,
"rating": 0,
"isFavorited": false
}
]
}合并作品的 animeId 即频道 ID,bangumiId 为 ch{频道ID};搜索页显示的 episodeCount 为当页搜到的集数,完整分集以 /api/v2/bangumi/ch{频道ID} 返回为准。
GET /api/v2/bangumi/:bangumiId
bangumiId 支持两种形式:
- 频道 ID(
ch{频道ID},如ch2650196):返回该频道(一般即一部动画)的所有分集,作品名为频道名,分集按集数排序; - 单个 niconico 视频 ID(如
sm9):返回单视频作品(含单集)。
episodes[].episodeId为数字编码 ID(dandanplay 格式要求 episodeId 为数字):由视频 ID 确定性编码、可无状态反解(如so46525903→2000046525903),弹幕接口可直接用该数字请求。
频道模式示例(/api/v2/bangumi/ch641):
{
"success": true,
"errorCode": 0,
"errorMessage": "",
"bangumi": {
"animeId": 641,
"bangumiId": "ch641",
"animeTitle": "とある魔術の禁書目録<インデックス>",
"episodes": [
{ "episodeId": 10000064101, "episodeTitle": "とある魔術の禁書目録 第1話「学園都市」", "episodeNumber": "1" },
{ "episodeId": 10000064102, "episodeTitle": "とある魔術の禁書目録 第2話「魔女狩りの王」", "episodeNumber": "2" }
]
}
}(10000064101 等为演示用的数字编码 ID,实际以接口返回为准。)
单视频模式(/api/v2/bangumi/sm9)返回含单集的同名作品,用于直接按视频 ID 调用。
POST /api/v2/match
从请求体 fileName 中提取 Niconico 视频 ID(把本地视频文件命名为 sm12345678.mp4 即可命中),也可通过 nicoId 字段直接指定:
{ "fileName": "sm15630734.mp4", "fileHash": "...", "fileSize": 123456, "videoDuration": 300 }GET /healthz
| 变量 | 默认值 | 说明 |
|---|---|---|
PORT |
3000 |
监听端口 |
HOST |
0.0.0.0 |
监听地址 |
CACHE_TTL_SECONDS |
3600 |
弹幕结果缓存时间(秒) |
NICO_HTTP_TIMEOUT_MS |
15000 |
请求 Niconico 的超时(毫秒) |
NICO_USER_SESSION |
空 | Niconico 登录态 user_session cookie,用于抓取需要登录才能观看的视频 |
SEARCH_LIMIT |
20 |
搜索每页返回条数 |
DANIME_CHANNEL_IDS |
2632720 |
dアニメストア 在 niconico 的频道 ID(逗号分隔)。搜索到同一标题时,若同时存在 dアニメストア 版本与其他版本,删除 dアニメストア 版本 |
部分视频(会员限定、部分频道视频)需要登录态。在浏览器中登录 Niconico 后,从 Cookie 中取出 user_session 的值注入环境变量:
docker run -d -p 3012:3000 -e NICO_USER_SESSION="你的user_session" nico2dandan:latest或在 docker-compose.yml 中设置 NICO_USER_SESSION。该 cookie 有有效期,过期后需更新。
项目包含三个测试,均使用扩展真实使用的引擎/格式对本服务做端到端验证:
test/integration.mjs:使用 danmaku-anywhere 同款引擎(@mr-quin/dango的ManifestRunner)+ 真实弹弹play manifest,走 搜索 → 作品详情 → 弹幕 流水线;test/legacy-provider.mjs:模拟旧版扩展(manifest 引擎之前的@danmaku-anywhere/danmaku-provider),用其 zod schema 原样校验/v2/...路径的 搜索 → 作品 → 弹幕 → 关联 流程;test/filter.test.mjs:搜索筛选与合并逻辑(动漫前缀过滤 + dアニメストア 去重 + 频道合并/集数提取/排序)单元测试。
npm run build && npm start & # 先启动服务
node test/integration.mjs # 默认 http://localhost:3012
node test/legacy-provider.mjs # 默认 http://localhost:3012
BASE_URL=http://localhost:3001 node test/integration.mjs # 指定服务地址弹幕获取分两步(与 Niconico 官方 Web 端一致):
GET https://www.nicovideo.jp/watch/{videoId}?responseType=json获取视频信息(含nvComment弹幕配置);POST {nvComment.server}/v1/threads,携带threadKey与params.targets,请求头使用x-client-os-type: others、x-frontend-id: 6、x-frontend-version: 0获取弹幕线程数据。
获取到的弹幕(vposMs 毫秒时间戳 + commands mail 命令 + 颜色/字号/位置)随后转换为 dandanplay 的 4 字段 p 格式。
GPL-3.0(见 LICENSE)。