NovaBot
GitHub

附录 B 进阶玩法

正文按「在界面上点」写;这一本附录收那些界面之外、偶尔用得上的东西。

处理器类名

第 6 章的通知开关在界面上点就行,用不到类名。 要用自定义插件提供的处理器、或直接手写配置文件 datasource.json 时,才需要 下表的全文类名——文件里要写全限定名,照抄即可:

处理器全限定名界面上的名字触发时机可用占位符
org.frostnova.nova.bilibili.handler.BilibiliLiveOnPushHandler开播通知开播{uname} {title} {cover} {url} {at} {next} {at=all}
org.frostnova.nova.bilibili.handler.BilibiliLiveOffPushHandler下播通知下播{uname} {time} {url} {next} {at=all}
org.frostnova.nova.report.handler.BilibiliDynamicPushHandler动态通知动态更新{uname} {action} {url} {picture} {at} {next} {at=all}
org.frostnova.nova.report.handler.BilibiliLiveReportPushHandler下播报告下播只发图,旧文字模板不再生效;画图失败时发文字版报告
org.frostnova.nova.report.handler.BilibiliOfflineRewardDigestPushHandler下播打赏播报不在播时有人上舰或送礼够格,攒完一阵(见第 6 章){uname} {list} {url} {picture} {next} {at=all}

推送页的「试发」已改为「发送上一周的报告」,下播报告新增「发送上一份下播报告」。两者都只发往当前通道,确认后真实发送,不影响后续定时周报或下播推送。

前缀不止一种:动态通知、下播报告与下播打赏播报住在报告插件里(org.frostnova.nova.report.handler.), 其余两个在哔哩哔哩插件里。{next} 的意思是「从这里分成两条消息」——在界面的模板 编辑器里不用写它,多建一张卡就是多一条(见第 6 章)。

说明从 starbot 时代升上来的老配置不用急着改:旧全名(com.starlwr.bot.
开头的)仍被认,日志里会提醒一句「这个处理器现在叫什么、建议改过来」。
其中动态通知与下播报告原先住在哔哩哔哩插件包下,照上表改过来即可,
改之前推送照常。下播打赏播报没有 starbot 时代的名字,但它的旧全名
(org.frostnova.nova.bilibili.handler. 开头的那一份)同样仍被认。

每场留下的事件流水

每场直播的明细落在数据目录的 details/<平台>-<uid>-<开播时刻>/ 下。其中 events.jsonl 逐条记下礼物、盲盒、上舰、关注、PK 的开打与结算,一行一条 JSON, 以时刻 at(毫秒)与类型 t 打头,其余是短键:

t含义字段
gift付费礼物uid 观众、un 昵称、gid 礼物 id、gn 礼物名、n 数量、val 到手价值、pay 实扣、bag 是否背包
box盲盒uid、un、bn 投入盒名、bp 实扣、gid/gn/n 开出物、val 开出面值、pft 盈亏
guard上舰uid、un、lv 等级(舰长 3/提督 2/总督 1)、ot 开通(1)/续费(2)/未知(-1)、n 数量、u 单位、days 陪伴天数、val 价值、pay 实扣
follow关注uid、un
pk_openPK 开打pk 场次号、tp PK 类型、nc 参加家数、ps/pe 计划开始与结束(秒)、op 对手一串(r 房间号、u 主播 uid)
pk_settlePK 结算pk、tp、nc、er 提前结算(1/0)、en 实际结束(秒)、mv 本房票数、mr 本房名次、rs 本房结果(1 胜/0 负/2 平)、op 对手一串(r、u、v 票数、k 名次)

几点读法:

  • 上舰的 ot 记的是稳定码而不是显示字:1 开通、2 续费、-1 解析不出,旧档里没有这一字段的行按未知读
  • 上舰的 pay 是实扣;平台未给实付时按 val 记
  • PK 的票数只是这场的拉票结果,不算收入,所以它叫 mv/v 而不是 val/pay
  • 弹幕与醒目留言的原文在同目录的 danmu.jsonl;进场与点赞只计数,不进事件流水
  • 进场次数另按分钟记成时序,与其它曲线一起归档在每场的 detail.json 里
  • 一场最多留 50 万行流水,到了会在日志里说明一声,本场后面的不再收录

同一数据目录下的 image-cache/ 存着下播报告用过的礼物图标和大航海标志,程序重启后不用重新下载。三十天没再被读到的会自动删掉。整个目录删掉也行:正在出的报告会重新下载;回看以前的场次不联网,缺的那几张画成占位。

旁边的 report-render-cache/ 存着下播报告推送时的成品原图,每张通常几百 KB 到数 MB,随报告内容和版式而变;每位主播只留最近一场的各套外观,三十天没再被读到会自动删掉。删掉目录后仍能「发送上一份下播报告」,但会从明细重画,封面和头像用占位图,也不画粉丝增减。

让事件去触发一个脚本

开播时启动录播、上舰时写一行记录、被切流时给自己发条提醒——这类内置功能覆盖不到的用法, 可以挂一个外部程序:

novabot:
  core:
    exec:
      enabled: true          # ⚠️ 默认关闭
      timeout: 30            # 单条命令最长执行时间,单位:秒
      max-concurrent: 4      # 同时执行的命令数上限,超出的直接丢弃
      rules:
        - event: LiveOnEvent
          command:
            - /opt/scripts/record.sh
            - "{room_id}"
            - "--name={uname}"

event 填事件类名,简名或全限定名都行,沿继承链匹配——填 LiveOnEvent 就能同时命中各平台的具体开播事件。可用占位符: {event} {platform} {uid} {uname} {room_id} {timestamp} {json}。

注意command 是参数数组,不是一整行命令。第一项是可执行文件,填写固定的程序名或路径,
不使用占位符;其余各项各自是一个独立参数。想用管道、重定向之类的 shell 特性,
请自己写一个脚本文件再在这里填它的路径。占位符替换后的内容无论含空格、引号还是换行,
都仍是原来那一个参数。

用解释器运行脚本时,脚本路径也填写固定值。未列出的占位符按原文保留,空参数也保留,null 项跳过。

第一项之外,{event} {platform} {uid} {room_id} {timestamp} {json} 可以单独成一项, 也可以和固定文字写在同一项里。程序的选项名写固定文字;{uname} 用作选项值时, 目标程序支持哪一种写法,就选哪一种:

  • 长选项合写成一个参数:--选项名= 后接占位符,例如 "--name={uname}"。
  • 短选项合写成一个参数:- 加选项字母后直接接占位符,例如 "-n{uname}",用于目标程序允许把值紧跟在短选项后面的情形。
  • 程序支持 -- 时,先列固定选项,再单列一个固定的 --,后面填写事件参数。
  • 脚本自行读取事件内容时,在这条规则上加 event-environment: true,从环境变量 NOVABOT_EVENT_JSON 读取事件 JSON,command 可以全部写固定值。

{uname} 写在固定文字之后(例如 "./{uname}")也照常替换。

例如,目标程序支持 -- 时可以这样写:

        - event: LiveOnEvent
          command:
            - /opt/bin/recorder
            - --quiet
            - --
            - "{room_id}"
            - "{uname}"

固定的 -- 之前,某一项替换后若以 - 开头(单个 - 除外),这一项开头须是上面两种固定的 选项写法,否则跳过本次命令,日志里写明是哪一条规则;同一条规则每次运行只记一次。固定参数沿用原文。-- 是否有分隔作用由 目标程序决定,请按目标程序的用法填写,程序不会自动添加它。

event-environment 默认是 false,打开后仅对这次启动的子进程设置 NOVABOT_EVENT_JSON, 子进程的其他环境变量照常继承。它与 {json} 使用同一份格式:超过 8000 个字符或无法 生成时传空字符串。标准输入仍为空。JSON 中的事件字段可按自己的脚本需要读取; 不要把读取出的内容再拼成一整行命令。

启动时会逐条检查规则;运行中有规则因参数写法被跳过时,也会补入提醒。同一条规则本次运行只提醒一次。需要调整的规则,日志里会有一句提示,管理员打开控制台时, 页面顶部也会列出是哪几条(按 novabot.core.exec.rules[序号] 指明,序号从 0 数起)。按上面的写法修改这几条的 command;第一项使用了占位符的,改填固定程序或脚本路径。修改后重启,提醒随之不再出现。 控制台按实际生效的规则更新清单,只有管理员能看到;保存在配置文件里、尚未重启的改动不改变当前清单。 没有提示且照常执行的规则不用改。

超时只结束命令本身,它拉起的其他程序(若有)会接着跑——要长时间运行的程序(例如录播), 请在脚本里把它放到后台、让脚本自己尽快退出,例如在脚本里写这样一行:

nohup 录播程序 ... >/dev/null 2>&1 &

并发超过上限的那次直接丢弃并记日志,而不是排队—— 等到十分钟后才执行的「开播启动录播」没有任何意义。

把事件推给别的程序

把直播间事件按「事件输出协议」实时推给本机的其他程序,用于自建面板、独立统计 这类内置功能覆盖不到的场景。它只输出,不接受任何指令:

novabot:
  core:
    event-stream:
      enabled: true               # 默认关闭
      path: /nova/events
      buffer-size: 2000
      allowed-origins: []          # 浏览器面板填写页面来源,如 https://panel.example

打开后地址是 ws://127.0.0.1:<端口>/nova/events,与配置界面共用端口。客户端先发送只读口令, 成功后服务端发一条 hello(带会话标识与能力集),随后是实时事件;每条事件有一个会话内严格递增的 seq, 断线重连时用 resume 就能把缺的那段补回来。协议的每一帧长什么样、时限是多少,见 事件源协议。

说明这一段以前写在 starbot.bilibili 下。事件输出由核心提供,与接的是哪个平台无关,
配置也就跟着挪到了 novabot.core。5.4 起旧键不再识别;写在旧位置的设置启动时日志会用
WARN 说明没被读取、该挪到哪,照着挪完重启。
注意只接受来自本机回环地址的连接,且这一点与 server.address 无关。
即使你为了对外提供推送接口把监听地址改成了 0.0.0.0,事件流也只认本机。
需要从其他机器读取请自行建立 SSH 隧道(ssh -L 7828:127.0.0.1:7827 你的服务器)。
直接暴露到公网风险自负——这条流里有观众的昵称、uid 与消费金额。

几条口径上的提醒,连上后 hello 里的 notes 字段也会重复一遍:

  • 点赞未聚合,每次点赞单独一条、count 恒为 1
  • live_state 没有中途接入的快照,只在开播、下播、标题变更时下发
  • source_state 的 disconnected 只在主播被移出数据源时下发,还在监听列表里时一律是 reconnecting
  • 房管标志只有弹幕消息带,其余消息恒为 false,含义是「这条消息没说」而不是「不是房管」
  • emoji 只在「整条弹幕就是一张表情图」时非空,判断「这条带表情」要读 inlineEmojis
  • 每条事件附带平台原始报文(rawJson),字段随平台改版而变,不要当契约用

一条连接承载全部房间,每条信封上都有 room,下游必须按它过滤——不滤的后果不只是 「消息多了」:曾有下游把别的主播的礼物一起算进了当前主播的本场累计,主播看到一个错的数字 而界面上没有任何提示。seq 也是整条连接共用的,所以过滤时只能丢消息,不能丢序号: 按 room 筛掉几条之后,本房间的下一条 seq 自然不连续,把它当跳号处理就会凭空报出一次 不存在的断档。判断有没有真丢包,要看整条连接的 seq 连不连续。

缓冲是全部房间共用的一条流。房间多或弹幕密时要把 buffer-size 调大,否则客户端重连时会 被告知补不上而必须清空重来;每条约数 KB,调大前先掂量内存。

事件输出始终要求只读口令,客户端要先开口。 require-token 默认 true,填写 false 也按 true 运行。口令只在连接建立后的第一帧出示:

{"kind":"auth","token":"<口令>"}

认证成功之前服务端什么都不发,连 hello 也不发——所以别等 hello 再发口令,会一直等 到 10 秒后被断开。口令在控制台「连接」页的「外部面板」卡上签发(只读,见第 12 章),明文只在签发那一刻显示一次; 签发时要填「令牌名称」,否则日后无法单独吊销。吊销会立即断开用该口令认证的所有已有连接, 其他口令的连接不受影响。口令只填在首帧里;握手不要填写 Authorization、 nova.token. 开头的子协议或口令查询参数(HTTP 400),普通子协议及其他查询参数照常使用。 完整的参数名约定见事件源协议 §1。

浏览器面板还需把页面来源填入 novabot.core.event-stream.allowed-origins,修改后重启。 填写页面地址的协议、主机及可选端口,如 https://panel.example 或 http://localhost:7830, 不带路径、末尾斜线或通配符;默认空表。列表以外的页面来源返回 HTTP 403。 页面来源按浏览器实际值填写,默认端口 80/443 会自动去掉;IPv6 主机须保留方括号,域名使用 ASCII 写法(国际化域名先转为 Punycode),不加末尾斜线。 保存时会检查写法;启动时若配置格式不合,事件输出暂停,程序和控制台照常启动,可在设置中修改后重启。 不带 Origin 的本机程序照常连接,并在首帧出示只读口令。

事件输出的连接额度与等待时间

以下键位于 novabot.core.event-stream,省略时使用默认值;填写正整数,修改后重启。 填写零或负数时使用默认值,日志会提示调整方法;填写文字、空值或超出整数范围的数时程序无法启动,日志会指出是哪个键,改正后重启。

配置键默认值用法
max-connections256全部连接的总额度,包含握手、等待首帧与收尾中的连接
max-pending-connections128等待首帧的连接额度,包含握手中的连接;首帧通过后归还
max-connections-per-source128每个对端地址的连接额度;同一代理或隧道转入的连接共用额度
max-concurrent-handshakes64同时进行的 WebSocket 握手数
handshake-timeout-ms10000从握手开始到连接建立的等待毫秒数
io-timeout-ms10000内置服务端单次写入与关闭等待的毫秒数

达到额度时,新的握手返回 HTTP 503,并带 Retry-After: 5,客户端至少等待 5 秒后再试。 已建立的连接继续工作;日志会指出可调整的配置键,按实际面板数量增大对应额度后重启即可。 连接收尾完成后归还额度;握手等待到期的连接不进入事件流,客户端稍后重新连接。 写入等待到期时结束该连接,客户端按原有 sessionId、seq 约定重新连接与回补。 每条连接可接收全部房间,无需按房间另建连接。现有配置可直接使用,无需增加部署步骤。

手动编辑配置文件

application.yml 与 datasource.json 都可以直接编辑,改完重启即可。 datasource.json 的完整示例见发行包里的 datasource.example.json。

推送目标的 type:1 为群聊,0 为私聊,num 是对应的群号或 QQ 号。填其他数字会被当作 「未知类型」在运行期丢弃,因此界面在保存时就会拦下;手写 JSON 时没有这道拦,写错了不报错, 只是那条推送悄悄不生效。