MSG 审核发送逻辑

坂道46 MSG 平台消息从提交到粉丝收到的完整流程

整体流程图
┌─────────────────────────────────────────────────────────────────────┐ MSG 消息生命周期 └─────────────────────────────────────────────────────────────────────┘ 成员 (MSG App) 写消息 + 提交 系统分配 message_id (递增整数) 记录 created_at ┌──────────────────┐ Staff 审核 审核通过/驳回 └────────┬─────────┘ ┌──────┴──────┐ ┌─────────────┐ ┌──────────────────┐ 手动发布 (手打) 定时发布 (定时) staff手点发送 成员设定 HH:MM 秒数=随机值 系统在HH:MM:XX发 ID降=0 XX=指纹秒(固定) 立即推送 ID降=提前量 └──────┬──────┘ └────────┬─────────┘ └────────┬─────────┘ 粉丝收到消息 (published_at) msg-pusher 轮询API (getTimeline) ┌────────────────┐ 推送到QQ/TG/Discord 媒体下载+文件名记录 └────────────────┘
分步说明
STEP 1 — 成员提交消息
成员在 MSG App 写消息并提交
系统分配 message_id(递增整数,全局唯一),记录 created_at(提交时间)。此时消息还未发布,粉丝看不到。
STEP 2 — Staff 审核
运营 staff 审核消息内容
审核通过后消息进入发布队列。审核延迟 = published_at - created_at。大部分秒审(ID降=0),偶尔延迟几分钟到几小时。
注意:D1 API 的 created_at 和 published_at 78%相同,说明 API 不暴露真实审核延迟,只记录最终发布时间。
STEP 3a — 手动发布(手打)
staff 审核通过后手点发送按钮
消息立即发布。published_at 的秒数是随机的(取决于 staff 手点的时刻)。
特征:秒数随机、ID降=0(提交后立即发布,没有其他消息插队)。
STEP 3b — 定时发布(定时)
成员设定 HH:MM,系统到点自动发布
系统在 HH:MM:XX 发布,其中 XX系统级固定偏移(指纹秒)。成员看不到也改不了这个秒数。
特征:秒数=指纹秒、ID降>0(提交后等了一段时间才发布,期间其他消息先发了)。
STEP 4 — msg-pusher 轮询
msg-pusher 轮询 MSG 平台 API
msg-pusher 每 15 秒轮询 /v2/groups/{groupId}/timeline,获取最新消息的 published_at(UTC时间)。
下载媒体文件,文件名格式:成员名_YYYYMMDD_HH-mm-ss_msgId.ext(时间取自 published_at 的 UTC 值)。
推送到 QQ群(NapCat)、Telegram、Discord Webhook。
ID下降原理

定时消息提交时就分配了 ID,但发布时间在设定的时间点。期间其他成员的消息先发布,导致定时消息的 ID 比发布时相邻消息的 ID 小。这个差值叫「ID下降」,提前量越大,ID下降越大。

时间轴 ──────────────────────────────────────────▶ 手打消息A 手打消息B 定时消息X 手打消息C ID=100 ID=101 ID=098 ID=103 ID降=0 ID降=0 ID降=3 即时发布 即时发布 定时发布 提交顺序: A(100) → X(098) → B(101) → C(103) 发布顺序: A(100) → B(101) → X(098) → C(103) X的ID比B小 因为X提交时B还没提交 但X定时在B之后才发 → ID降 = 101-098 = 3 提前量越大 → 等待发布的时间越长 → ID降越大
实际例子
浅井恋乃未设定 22:53 定时发送 → 消息在 22:53:09 发布(指纹秒09)
但她提交时 ID=355000,而 22:53 时其他成员的最新 ID 已经到 355080
→ ID降 = 355080 - 355000 = 80
→ 说明她提前了约 80 条消息的时间(约几小时)提交
指纹秒原理

指纹秒 = 定时发布器的系统级固定偏移。成员设定 "22:54 发送",系统在 22:54:XX 发布,XX 是系统级固定值。系统升级或维护后指纹秒会变更。

成员设定: 22:54 发送 系统执行: 22:54:09 发布 (樱坂46 指纹秒=09) ┌──────────────────────────────────────────────────┐ 秒数分布 (樱坂46 全部消息) :00 :01 :02 ... :08 ::09 :10 :11 ... :58 :59 ▁ ▁ ▁ ▁ ▆▆▆▆ ▁ ▁ ▁ ▁ 788条 集中在 :09 其他秒数只有几十条 → 只有定时发布器能产生这种集中 └──────────────────────────────────────────────────┘ 手打消息: 秒数随机 (staff手点时刻) 定时消息: 秒数=指纹秒 (系统固定偏移) 随机基线: 1/60 = 1.67% 的手打消息碰巧落在指纹秒
指纹秒变化历史

三团都在 2026年3~5月期间频繁换指纹秒,5月之后各自稳定。v9 分析按周动态匹配指纹秒,修正了切换前的定时消息漏判。

樱坂46 — 换了4次

2025年12月 :37
2026年3月 :15
2026年4月 :44
2026年5月起 :09

日向坂46 — 换了3次

2026年3月 :28
2026年4月 :18
2026年5月起 :37

乃木坂46 — 换了3次

2026年4月初 :48
2026年4月末 :07
2026年5月起 :45
定时率判定公式

总定时% = 指纹秒定时% + 非指纹秒额外定时%

指纹秒定时% = 指纹秒占比 - 1.67%(随机基线)
非指纹秒额外定时% = (非指纹秒ID降≥50率 - 手打基线) × 非指纹秒占比

指纹秒: 秒数 == 当周指纹秒 → 定时发布器发的
非指纹秒ID降≥50: 秒数不是指纹秒,但 ID 比发布时相邻消息小50+ → 走 staff 手动通道的定时
真实% = 100% - 总定时%

判定逻辑
为什么需要两层判定?
1. 指纹秒法能抓住大部分定时消息,但有两类漏网:
  • 成员设定的时间恰好落在指纹秒(极少数)
  • staff 手动操作定时发布(不走自动发布器,秒数随机)
2. 第二类用 ID降≥50 补抓:手打消息 ID降=0(秒审秒发),定时消息 ID降>0(提交后等了才发)
3. 手打基线 = 纯手打成员的 ID降≥50 率(排除审核延迟导致的误判)
数据来源与时区

媒体文件名时间:msg-pusher 轮询 MSG 平台 API(getTimeline)获取 published_at(UTC),写入文件名(toISOString())。
QQ群推送时间:NapCat 记录的推送时间,北京时间(CST, UTC+8)。
本页所有时间已统一转换为日本时间(JST, UTC+9)——即粉丝在日本收到消息的时间。

不使用 D1 归档数据的时间字段:D1 的 created_atpublished_at 78%完全相同,不暴露真实审核延迟,不可靠。D1 仅用于成员→团映射。