上一主题 下一主题
ScriptCat,新一代的脚本管理器脚本站,与全世界分享你的用户脚本油猴脚本开发指南教程目录
返回列表 发新帖

简易脚本开发过程:自动查询B站弹幕点赞数并显示在弹幕旁

[复制链接]
  • TA的每日心情
    郁闷
    2025-12-21 21:58
  • 签到天数: 66 天

    [LV.6]常住居民II

    4

    主题

    9

    回帖

    55

    积分

    初级工程师

    积分
    55

    新人报道油中3周年

    发表于 4 天前 | 显示全部楼层 | 阅读模式

    本帖最后由 啦la啦 于 2026-1-4 02:07 编辑

    此笔记记录脚本B站弹幕显示发送次数与点赞数 开发过程

    B站弹幕DOM探索

    F12 → 元素 → 查找 搜索弹幕文本,找到弹幕元素

    <!--典型弹幕元素-->
    <div aria-live="polite" role="comment" 
      class="bili-danmaku-x-dm bili-danmaku-x-roll bili-danmaku-x-show" 
      style="--opacity: 0.8; --fontSize: 25px; --fontFamily: SimHei, 'Microsoft JhengHei', Arial, Helvetica, sans-serif; --fontWeight: bold; --color: #ffffff; --textShadow: 1px 0 1px #000000,0 1px 1px #000000,0 -1px 1px #000000,-1px 0 1px #000000; --offset: 730.5388359130914px; --translateX: -861px; --duration: 8.21s; --top: 33.125px;"
      >
      哈哈哈
    </div>

    经研究得到以下结论:

    1. 所有弹幕都有bili-danmaku-x-dm
    2. 显示中的弹幕有bili-danmaku-x-show类,显示结束后不会把弹幕元素从DOM移除,而是移除bili-danmaku-x-show类,等待复用
    3. 外层弹幕容器的类名为bpx-player-row-dm-wrap

    B站弹幕加工探索

    由以上DOM探索可知弹幕DOM元素并不含弹幕的id等信息,得找到弹幕是如何加工成DOM元素的。

    在DevTools(F12)中右键弹幕容器(bpx-player-row-dm-wrap) → 中断于 → 子树修改

    播放视频,结果中断于ndp.223.xxxxxx.js文件的一个名为render方法中(有时中断于名为init的方法,F8继续运行一下),此方法根据弹幕信息,为弹幕DOM元素添加各种style属性。

    e.prototype.render = function(t, e) {
      var n, r, o, i = this.textData, a = i.color, s = i.border, c = i.borderColor, u = i.colorfulImg, l = i.isHighLike, f = i.likes, p = this.config.setting, h = p.bold, d = p.fontFamily, m = p.fontBorder, y = p.opacity, g = Ft(this.outlineColor(a)), v = "".concat(H, "-dm ").concat(H, "-roll"), b = "";
      s && "transparent" !== c && !(null === (n = this.modeInfo) || void 0 === n ? void 0 : n.isUpSlogan) ? (b += "border: 1px solid ".concat(Ft(c), ";"),
      this.width += 4,
      this.height += 4) : (null === (r = this.modeInfo) || void 0 === r ? void 0 : r.isUpSlogan) && (v += " ".concat(H, "-upslogan"),
      this.width += 12),
      this.font = (h ? "bold" : "normal") + " " + 2 * this.fontSize + "px " + d,
      b += "--opacity: ".concat(Math.max(y, .1) + "", ";"),
      b += "--fontSize: ".concat(this.fontSize, "px;"),
      b += "--fontFamily: ".concat(d, ", Arial, Helvetica, sans-serif;"),
      b += "--fontWeight: ".concat(h ? "bold" : "normal", ";"),
      b += "--color: ".concat(Ft(a), ";"),
      this.element.setAttribute("aria-live", "polite"),
      this.element.setAttribute("role", "comment"),
      u || (b += "--textShadow: ".concat(this.getShadow(g, m), ";")),
      b += this.renderRealTimeOptimization(null === (o = this.textData) || void 0 === o ? void 0 : o.isRealTime),
      l || f || u ? this.renderSpecialType(e || v, b) : (this.hasEmoji || this.textData.prefix || this.textData.suffix ? this.element.innerHTML = lt(this.text) : this.element.textContent = lt(this.text),
      this.element.className = e || v,
      this.element.style.cssText = b),
      this.textData.prefix && this.textData.prefix instanceof HTMLElement && (this.textData.prefix.style.height = "".concat(1.2 * this.fontSize, "px"),
      this.textData.prefix.style.width = "auto",
      this.element.insertBefore(this.textData.prefix, this.element.firstChild)),
      this.textData.suffix && this.textData.suffix instanceof HTMLElement && (this.textData.suffix.style.height = "".concat(1.2 * this.fontSize, "px"),
      this.textData.suffix.style.width = "auto",
      this.element.appendChild(this.textData.suffix))
    }

    this.textData包含各种弹幕信息。具体含义参考 protobuf弹幕 DanmakuElem

    {
        "stime": 884.647,   // 发送时进度条时间
        "mode": 5,
        "size": 25,
        "color": 16646914,
        "uhash": "6a3439b",
        "text": "哈哈哈",
        "date": 1766993886,
        "weight": 10,
        "dmid": "2012449591689219328",  // 弹幕唯一id(查询点赞数需要)
        "attr": 1048576,
        "oid": 35045576155, // 视频唯一chatid(查询点赞数需要)
        "dmFrom": 1,
        "likes": undefine,
        "rawMode": 5,
        "modeInfo": {
            "cid": 35045576155
        },
        "pool": 0,
        "uname": "",
        "id_str": "2012449591689219328",    // 弹幕id通常超出Number范围
        "border": false,
        "borderColor": 6750207,
        "isHighLike": false,
        "isMine": false,
        "prefix": null,
        "suffix": null,
        "on": true
    }

    到此为止就很简单了,交给AI即可。hook render 函数,把需要的弹幕信息塞入弹幕DOM元素的dataset里;然后MutationObserver监控弹幕的DOM元素,有新弹幕出现就从dataset里获取oid、dmid查询点赞数,显示在弹幕旁。

    其他


    点赞数查询APIbilibili-API-collect 弹幕点赞查询


    角标使用的SVG

    1. 发送图标:iconfont找了一个
    2. 点赞图标:B站自带点赞弹幕图标(悬浮于弹幕上,右键点赞图标 → 检查 → svg复制下来)

    角标按热度增亮公式

    高亮角标即角标添加背景色,亮度即背景色不透明度(0~1),选用公式 $1-e^{-kx}$

    由于合并数与点赞数不在一个数量级,热度为合并数与点赞数的加权和:

    $hot=(sendCount-1)\cdot sendMul+likes\cdot likeMul$

    发送次数为1相当于合并数为0,因此sendCount需减1

    热度为0时也要有一定的亮度,设为b0,公式修正为:

    $brightness=1-(1-b0)e^{-k \cdot hot}$

    还剩参数k决定曲线上升速率,设参数h90表示亮度达到90%时的热度值,计算k:

    $$
    \begin{aligned}
    0.9 &= 1-(1-b_0)e^{-k \cdot h_{90}} \
    e^{-k \cdot h_{90}} &= \frac{0.1}{1-b_0} \
    k &= -\frac{1}{h_{90}}\ln\left(\frac{0.1}{1-b_0}\right) \
    k &= \frac{1}{h_{90}}\ln(10-10\cdot b_0)
    \end{aligned}
    $$

    最终四个参数决定亮度曲线:

    参数 含义 默认
    b0 热度为0时的亮度 0.2
    h90 亮度为90%时的热度 500
    sendMul 合并数倍率 5
    likeMul 点赞数倍率 1

    这几个参数没给UI,可通过控制台 __DM_ADAPT__.setBadgeHighlightCurve(0.2, 500, 5, 1) 控制。

    已有1人评分好评 油猫币 理由
    王一之 + 1 + 4 赞一个!

    查看全部评分 总评分:好评 +1  油猫币 +4 

    发表回复

    本版积分规则

    快速回复 返回顶部 返回列表