Skip to content

feat(dandan): 弹弹play弹幕替代机制改用 NipaPlay 中转弹弹play服务端获取弹幕与弹弹302关联链接(需登录) - #487

Merged
huangxd- merged 8 commits into
huangxd-:mainfrom
wan0ge:feat/nipaplay-gateway
Sep 26, 2026
Merged

huangxd- merged 8 commits into
huangxd-:mainfrom
wan0ge:feat/nipaplay-gateway

Conversation

@wan0ge

@wan0ge wan0ge commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Summary

为 dandan 源新增一种账号化的弹幕获取路径:以弹弹play 账号登录换取令牌,经 NipaPlay 中转弹弹play服务端获取弹幕。它与既有的「经弹幕代理获取缓存弹幕」路径并存——账号与密码均已填写时启用前者,未填写时维持后者,两种路径由是否配置凭据自动切换。

覆盖五个维度:获取链路、弹幕组合、配置形态、线路可用性、配置读取。另修复四处与本改动同源的既有缺陷。


变更背景

dandan 源原本依靠源码内嵌的 nipaplay 应用密钥直连 api.dandanplay.net 截获 302 关联链接做跨平台兜底。这套机制依赖的密钥已被上游轮换:

  • NipaPlay 因被滥用原因将应用密钥收归服务端网关保管,客户端不再持有;除登录/注册外,全部上游接口都要求真实的弹弹play 账号令牌(匿名 401)。
  • 轮换后的旧密钥签名被弹弹play 服务端判为无效应用认证,导致内嵌旧密钥的请求返回 403,既有兜底链路中断。
  • 服务端网关模式是上游长期计划状态。

因此本分支和 NipaPlay 官方沟通确认后决定迁移过去改为以自有弹弹play 账号登录 NipaPlay 中转服务端、由网关代为持有密钥并签名上游请求:我们只需取令牌、请求网关、截获其回传的 302,数据与既往完全一致(解析与分发沿用既有实现,零改动)。

上游原版行为 → 本分支行为

1. dandan 源弹幕获取

  • 上游:getEpisodeDanmu 经弹幕代理获取缓存弹幕;弹幕为空时借源码内嵌的 nipaplayv1 应用密钥直连 api.dandanplay.net 截获 302 关联链接做跨平台兜底,NIPAPLAY_REPLACE_DANDAN 开启时以关联弹幕替代原生弹幕。
  • 本分支:删除内嵌密钥、AES 派生与 X-AppId/X-Timestamp/X-Signature 本地签名。账号已配置时,提交 {userName, password} 换取令牌后由中转服务端代为请求:一次请求同时得到原生弹幕与弹弹302关联链接;关联链接按平台分发给已接入的对应源实时拉取(同平台串行、间隔 1 秒防风控),两部分沿用既有多源去重合并。账号未配置或中转服务端不可用 → 回退既有路径。

2. 令牌与线路

  • 令牌缓存 + 临近到期续期(提前 24h,有效期约 21 天);返回 401 时清除令牌重登并只重试一次;并发请求共用同一次登录;令牌只绑定账号,换账号才重新登录(改密码不触发)。
  • 香港域名为主线路、国内 IP 为备用线路,主线路连续失败达阈值后由备用线路临时接替,到期恢复。

3. 弹幕组合与标签

  • 每条实时拉取的弹幕标记 realTimeSource,经 convertToDanmakuJson 组装为 [来源&平台](如 [dandan&bilibili1]),触发既有多源去重并按来源降噪。
  • alignSourceTimelines 仅以 dandan 列表作「文本 → 最早时间」基准;实时拉取条目已按 302 的 shift 校正到同一时间轴,基准不被污染。

4. 配置形态

  • 移除 NIPAPLAY_REPLACE_DANDAN 开关及其 globals 取值,新增 DANDANPLAY_ACCOUNT / DANDANPLAY_PASSWORD(同时填写即启用,无额外开关,预览以掩码呈现)。
  • 密码配置页提供连通性测试,新增 POST /api/nipaplay/verify(对齐既有 handleAiVerify:成功 ok:true,失败以 200 返回 ok:false),不写入运行期令牌缓存。
  • 连通性测试随请求提交配置值(弹弹play 账号与密码、AI 密钥与地址/模型),取值自设置页已加载的配置——云部署保存变量需重新部署才会刷新运行期配置,故检测不依赖运行期配置即可进行;掩码值(当前访问无权限读取明文)视为未知,交由服务端回退到已保存配置。
  • 凭据类变量与文本类变量同样绕过 dotenv 的 # 截断(RAW_ENV_KEYS 扩展到凭据类变量并按掩码写入预览集合);Node 端写/删 .env 后同步刷新原始解析缓存。

5. 与合并源的兼容性

  • getEpisodeDanmu(id, mergedSources) 接收按 $$$ 拆分的合并段(形如 <源名>:<真实ID>),据此跳过已参与合并的平台以免重复拉取。dandan 作主源或被合并方时行为一致:dandan 自身的原生弹幕来自 fetchNipaplayDanmaku(id),不受跳过逻辑影响。

6. 顺带修复的既有缺陷(与上列改动同源,非本功能引入)

位置 问题 → 处理
node-handler.js setEnv 日志明文记录凭据 → 按 Envs.sensitiveKeys(Envs.get 在 encrypt 为真时登记)脱敏
http-util.js httpRequestMethod GET/POST 支持 allow_redirects 而 PATCH/PUT/DELETE 未实现、JSDoc 失真 → 统一实现并修正 JSDoc
systemsettings.js 保存接口 未防护掩码值 *** → 未改动直接保存时跳过写入,避免覆盖服务端已存凭据
nipaplay-util.js applyShiftToDanmu 手写偏移缺非负钳制,负偏移产出负时间戳 → 改用 offset-util.applyOffset,与其余偏移路径一致

影响范围

场景 行为
未配置 DANDANPLAY_ACCOUNT / DANDANPLAY_PASSWORD 维持经弹幕代理获取缓存弹幕
账号与密码均已配置 经中转服务端获取原生弹幕 + 关联链接分发自采源
账号已配置且中转服务端返回空(无原生弹幕且无关联链接) 以中转结果为准,不回退经弹幕代理获取的缓存弹幕
关联链接指向的平台已参与合并 跳过该平台,避免重复拉取
关联链接含未接入的 host 跳过并记录日志
原生地址请求失败 记录错误并保留关联链接,仍分发自采源
中转服务端不可用(登录/请求失败) 回退既有路径
令牌临近到期 / 返回 401 自动续期;失效时重登并只重试一次
主线路连续失败达阈值 备用线路接替,到期恢复主线路
密码含 # 等字符 无需转义,按原文完整读取
加密变量在配置预览中 仍为掩码
合并链接中的 dandan(主源或被合并方) 两种位置均跳过重复平台,时间轴基准不受影响
连通性测试(/api/nipaplay/verify) 不写入运行期令牌缓存
日志中的凭据类变量 脱敏为 ***;非凭据类保留明文以便排障

已知限制

  • 关联链接指向的平台视频若已下架,无法经自有链路补取;关联含巴哈姆特平台时需确保能够连通巴哈。
  • 关联链接的获取依赖用户在弹弹play客户端绑定对应平台;未绑定则不产生关联。
  • NipaPlay 备用线路为国内 IP 直连(无 TLS),仅在主线路连续失败后临时使用。
  • 单源(非合并)请求下不感知客户端另行选择的其它源,可能与其自身结果重复,去重由客户端或合并链接承担。

测试覆盖

层面 内容
项目测试 node --test danmu_api/worker.test.js 150/150 通过(上游基线 144/144);新增关联链接偏移钳制、httpPatch 重定向一致性、dandan 按实时拉取标记区分处理、设置页连通性测试请求体字段、弹弹play 与 AI 验证接口以请求体为准等用例
上游 / 分支差分 解析层三函数与上游逐字对照;Envs 加密变量读取契约对照上游
常规情况 账号配置后取中转弹幕并分发关联链接;合并去重;真实账号端到端(中转 387 条 + bilibili×4,叠加自有链路后合并去重为 398 条)
边缘情况 账号未配置 / 仅账号 / 仅密码;含 # 不加引号;错误凭据不误报成功;关联链接含未支持平台;302 无 Location;持续 401 只重登一次;主线路连续失败切备用
合并兼容 dandan 为主源 / 被合并方 / 合并伙伴无交集 / 单源,四种场景均已验证

涉及文件

文件 改动
utils/nipaplay-util.js 改为中转服务端客户端:登录、令牌缓存/续期/失效重试、线路切换;删除内嵌密钥、AES 派生与签名;保留解析层三函数;日志标签按源与服务模块标注为 [dandan] [nipaplay]
sources/dandan.js 取中转原生弹幕并把关联链接分发给自采源;按 mergedSources 跳过已合并平台;实时拉取弹幕标记 realTimeSource;模块级常量与辅助函数下移到导出类之后,分节注释与其它源一致地紧随导入
configs/envs.js 移除 NIPAPLAY_REPLACE_DANDAN;新增 DANDANPLAY_ACCOUNT / DANDANPLAY_PASSWORD;getRawEnv 支持掩码;RAW_ENV_KEYS 扩展到凭据类变量;新增 sensitiveKeys 凭据登记
configs/handlers/node-handler.js 写/删 .env 后同步原始解析缓存;setEnv 日志按 sensitiveKeys 脱敏
apis/env-api.js 新增 handleDandanplayVerify
worker.js 注册 POST /api/nipaplay/verify;路径归一化放行 /api/nipaplay
ui/js/systemsettings.js 密码配置页专用编辑界面与连通性测试;检测随请求提交账号/密码/密钥/地址/模型;保存接口对掩码值 *** 跳过写入
utils/http-util.js httpRequestMethod 统一支持 allow_redirects,并修正失真 JSDoc
ui/js/preview.js 配置预览分组新增「NipaPlay 中转弹弹play服务端」
ui/css/components.css.js 将 AI API Key 与 B站 Cookie 各自独立的编辑器样式合并为通用凭证编辑器样式,供 AI API Key / 弹弹play密码 / B站 Cookie 共用
worker.test.js 工具函数用例转为活跃并补充账号缺失、偏移钳制、连通性检测请求体取值等场景
config/.env.example 移除旧开关说明,新增两个账号配置说明
README.md 环境变量表、结构树、致谢段同步

@vercel

vercel Bot commented Sep 21, 2026

Copy link
Copy Markdown

@wan0ge is attempting to deploy a commit to the huangxd's projects Team on Vercel.

A member of the Team first needs to authorize it.

dandan 源原经弹幕代理获取弹弹缓存弹幕,仅在弹幕为空时借弹弹302关联链接拉取跨平台弹幕。现改为:配置弹弹play账号后,dandan 源经 NipaPlay 中转弹弹play服务端获取弹幕,并把同一请求下发的弹弹302关联链接分发给已接入的对应平台源实时拉取,两部分沿用既有多源去重合并。

- 源配置新增 DANDANPLAY_ACCOUNT 与 DANDANPLAY_PASSWORD,账号与密码同时填写后自动开启,移除 NIPAPLAY_REPLACE_DANDAN 开关
- 密码配置页提供连通性测试,新增 POST /api/nipaplay/verify
- NipaPlay 中转弹弹play服务端以香港线路为主、国内线路为备,主线路连续失败达到阈值后临时切换备用线路
- 原生弹幕地址请求失败时保留已解析的弹弹302关联链接,仍交由自采源拉取
- 以内嵌应用密钥与签名换取关联链接的做法改为账号令牌登录,令牌临近过期自动续期
- 凭据类变量与文本类变量同样绕过 dotenv 注释截断,含 # 时无需转义即可完整读取,写入 .env 后即时生效
- AI API Key 与 B站 Cookie 各自独立的编辑器样式合并为通用凭证编辑器样式,供 AI API Key、弹弹play密码与 B站 Cookie 共用
- nipaplay-util.js 日志标签按源与服务模块标注为 [dandan] [nipaplay]
- README 与 .env.example 新增两个账号变量的配置项与说明,配置预览新增「NipaPlay 中转弹弹play服务端」分组
httpGet/httpPost 已实现 allow_redirects,httpRequestMethod(PATCH/PUT/DELETE 委托)此前未实现,其 JSDoc 标注「暂未实现」失真。现于 httpRequestMethod 统一实现:默认跟随重定向,false 时禁止以截获 3xx Location,与 GET/POST 行为一致;同步修正 JSDoc,并移除与 JSDoc 语义重复的那行注释。
- setEnv 日志对凭据类变量以 *** 脱敏,避免令牌/密码/Cookie/带令牌 URL 明文进入运行日志;判据为 Envs.sensitiveKeys(由 Envs.get 在 encrypt 为真时登记),非凭据变量保留明文以便排障
- 保存接口对掩码值 *** 跳过写入,避免凭据编辑框未改动直接保存时用星号覆盖服务端已存的真实值
- 补全 Envs.get 缺失的 encrypt 参数说明
原手写实现直接对 p 首字段与 t 做加减,缺少通用偏移工具的非负钳制,负偏移且绝对值大于弹幕时间时会产出负时间戳。
现改为调用 offset-util 的 applyOffset,与手动 @偏移、DANMU_OFFSET 等其余偏移路径行为一致,并保留 isRealTimePulled 标记供 dandan 源 formatComments 跳过专属转换。
worker.test.js 补充负偏移钳制用例。
worker.test.js 补充两处行为的回归用例:httpRequestMethod(经 httpPatch)的 allow_redirects 是否与 GET/POST 一致,以及 dandan 源 formatComments 是否按 isRealTimePulled 区分原生弹幕与实时拉取弹幕。两者均为可稳定回归的纯逻辑,纳入项目测试以免后续回归。
将 DandanUserAgent、SOURCE_TO_PLATFORM、getRelatedDanmuViaNipaplay、fetchDandanComments 下移到导出类之后,使「获取弹弹play弹幕」分节注释与其它源一致地紧随导入与源实例之后。
上述标识符均在函数体或方法体内使用,不在模块加载期求值,故下移不影响运行;已验证导出类方法仍可正常调用下移后的函数。
@wan0ge
wan0ge force-pushed the feat/nipaplay-gateway branch from de01969 to de4213d Compare September 22, 2026 07:11
wan0ge and others added 2 commits September 22, 2026 16:10
B站 Cookie 检测以请求体中的值向 B 站实时校验,弹弹play 与 AI 的检测则只能取到服务端运行期配置:弹弹play 的账号、AI 的地址与模型都不随检测请求提交。云部署(vercel 等)保存变量需重新部署才会刷新运行期配置,因此这两处检测在重新部署前一直失败。

现改为账号、密码、密钥、地址、模型一律随检测请求提交,取值自设置页已加载的配置;掩码值(当前访问无权限读取明文)视为未知,交由服务端回退到已保存配置。

worker.test.js 补充设置页请求体字段与弹弹play/AI 验证接口以请求体为准的用例。
@huangxd-
huangxd- merged commit c42877c into huangxd-:main Sep 26, 2026
1 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants