Commit Graph
316 Commits
Author SHA1 Message Date
DrSmoothl 4fcaba2f3e chore(release): v1.9.5 2026-06-03 21:55:37 +08:00
DrSmoothl fc21d87a9b feat(onebot): fetch_ptt_text via async 0x210/61 result push (#79)
Live testing showed the voice-to-text text is delivered by an async push
(Event 0x210 subType 61), not the request response — so the previous
poll-by-resend never got it. Captured a real push and decoded its protobuf:
outer { f1, f2:item }; the item echoes the request msgId at field 1 (the
correlation key) and the recognised text at field 8.

- proto-defs: PttTransPush / PttTransPushItem (live-verified).
- protocol: Event0x210SubType.PttTransResult = 61 + decoder → internal
  `ptt_trans_result` event { msgId, text } (replaces the temp hex dump).
- core: translatePttToText now sends ONE trigger and returns the inline text
  only if present (already-transcribed); empty = pending.
- onebot: ptt-trans-waiter (keyed by selfUin:msgId), event-pipeline subscribes
  `ptt_trans_result` to resolve it; fetch_ptt_text registers the waiter,
  fires the trigger, and awaits the push (20s timeout).

Decoded one real push as text "12311123。" end to end.
2026-06-02 22:01:07 +08:00
DrSmoothl e036e70acc chore(protocol): temp-dump 0x210 subType-61 ptt-trans push body (#79)
Live testing showed the voice-to-text text is delivered via an async push
Event 0x210 subType 61 (0x3D), not the sync pttTrans.TransC2CPttReq response
(which is a fixed ~137B ack). That push has no static sys_msg_0x210_0x3d
handler — its protobuf is registered dynamically by the ptt-trans subsystem,
so RE through the std::function callback chain is impractical.

Dump the raw subType-61 body (fromUin + len + hex) once so the wire format
can be decoded from ground truth, then implement the real decoder +
request/await correlation. Temporary — to be replaced by the actual decoder.
2026-06-02 21:28:44 +08:00
DrSmoothl 15e4e261c5 feat(log): memory-only trace level + request correlation + chokepoint tracing
Debugging from user-supplied logs was impossible: a successful action left
one terse, over-summarized debug line and nothing about what it sent QQ or
what QQ returned. Add full-chain tracing that stays off disk so it scales to
hundreds of groups.

common:
- New `trace` level (below debug). Never written to file — it lives only in
  an in-memory ring + the WebUI stream, so the high-volume full chain costs no
  disk. `emit` early-returns for trace before format/render (and before any
  lazy producer) when trace isn't active → ~free at the default level.
- Dedicated O(1) circular ring for trace (default 5000, SNOWLUMA_TRACE_BUFFER),
  separate from the normal 1000-entry buffer (also switched off array .shift);
  getRecentLogs merges both by id.
- request-context.ts: AsyncLocalStorage request id. The logger auto-stamps
  `[req#N]` on every line in a request's async chain — across packages, zero
  signature threading — and records it on the entry.
- log-summary.renderParamsVerbose: deep render of params (nested structure,
  per-string cap, total budget, token/password/secret redaction).

chokepoints (auto-cover every action without touching 100+ handlers):
- onebot api-handler: wrap each action in a request id (only when trace is
  live); trace the full params on entry and status+elapsed on exit; the terse
  debug breadcrumb still persists to file, failures still warn.
- core bridge.sendRawPacket: trace on success (cmd, sizes, elapsed); warn on
  errorCode!=0 / no response (cmd, code, message) so disk keeps real problems.
- protocol highway: fold the bare console.log lines into the logger (trace).

webui: trace as the lowest filterable level; render the `[req#N]` tag and let
search match it. Server needs no change — LOG_LEVELS, getRecentLogs and the
SSE stream carry it through.
2026-06-02 21:01:19 +08:00
DrSmoothl a1f372bc9d chore(release): v1.9.4 2026-06-01 16:21:54 +08:00
tt-P607 118bcbe83c fix(protocol): one TCP connection per highway chunk
Highway 上传超过 1 MiB 的载荷(多 chunk 场景)会失败,OneBot 侧表现
为 retcode=1200 / `connection closed before response`。

根因:`uploadHighwayHttp` 用同一个 TCP socket,靠 HTTP keep-alive 把
每个 1 MiB chunk 依次 POST 出去。但 QQ highway 的边缘节点(以及链
路上的代理/NAT)经常在第一次响应之后立刻 FIN 关闭连接,第二个 chunk
的 POST 写到一个已被对端 close 的 socket 上,`readHttpResponseBody`
立刻收到 `close` 事件并抛出 `connection closed before response`。

为什么之前看起来像网络问题:≤1 MiB 的载荷(文本、PTT、小图)只发一
次 POST,永远不会触发;但凡跨过 `HIGHWAY_BLOCK_SIZE = 1 MiB`,第二
个 chunk 路径必然失败。改 Clash MTU、关 TUN、加直连规则都没用,因
为问题不在网络层。

改动:
- `uploadHighwayHttp`:每个 chunk 都新开一个 TCP 连接,`finally` 里
  `destroy()`。建连开销相对 1 MiB chunk 的传输时间可以忽略,行为也
  和 NapCat / Lagrange 在大文件上传时的连接生命周期一致。
- `readHttpResponseBody`:报错信息带上 `received=NB, headerSeen=
  true|false`,方便后续区分 “连接刚握手就被关” 和 “响应读到一半被
  截断” 两种情况。

测试:`packages/core/tests/highway/highway-client.test.ts` 三个用例
(0.5 MiB 单 chunk / 1.5 MiB 两 chunk —— 即本次 bug 复现 / 2.5 MiB
三 chunk),断言 `net.createConnection` 被调用的次数等于
`ceil(size / HIGHWAY_BLOCK_SIZE)`,并且每个用过的 socket 在下一次
建连之前都被 destroy。

线上验证:之前每次发 1 MB 以上图片必出 retcode=1200 的账号,打补丁
后通过 OneBot HTTP 已能正常发出图片。
2026-06-01 14:35:57 +08:00
DrSmoothl c3803e4ba8 feat(proto): implement deflated payload for JSON and XML elements in message builder 2026-05-30 22:52:15 +08:00
DrSmoothl ad081e2149 chore(release): v1.9.3 2026-05-30 20:53:45 +08:00
DrSmoothl 90f26ca4f4 fix(proto): emit state=0 on whole-ban unmute via new pb_optional marker
set_group_whole_ban false (unmute) failed with `OIDB error 1007` while
enabling worked (issue #70). OIDB (0x89A, 0) is shared by several commands
(SetSearch / SetAddOption / mute-all) disambiguated by body shape. Unmute
sends muteState.state=0, but proton omits zero scalars (proto3), leaving an
empty `muteState` the server can't classify. Enable sends 0xFFFFFFFF, which
is non-zero and survives — hence the asymmetry.

Add a `pb_optional<N, T>` marker (proto3 explicit presence): a scalar so
marked serialises even at its zero/default value. It is opt-in per field via
a NEW `explicitPresence` flag set ONLY by this marker — never reused from the
TS-`?`-derived `isOptional` (true for nearly every field). The encoder routes
explicit-presence scalars to a dedicated block; every `pb<>` / `pb_repeated<>`
field takes the untouched branches, so existing wire output is byte-identical
(proton's 99 existing tests, incl. byte-level round-trips, stay green).

Mark OidbMuteAllState.state `pb_optional<17, uint_32>` so unmute now puts
field 17 (=0) on the wire. New tests: proton pb_optional encode/round-trip
(incl. that sibling pb<> fields still omit zero), and a byte-level assertion
that mute-all unmute carries the state field.

Note: proton ships as a built Vite plugin (dist, gitignored); rebuild it
(`pnpm --filter @snowluma/proton build`) for the change to reach consumers.
2026-05-30 17:55:23 +08:00
DrSmoothl 0110b71dd3 fix(protocol): send browser UA + Referer retry on media downloads
loadBinarySource issued a header-less fetch(source) for remote media.
Anti-hotlink / anti-bot front-ends reset such requests, surfacing as
undici `TypeError: fetch failed` (not a clean 403) — so quoting a
command and sending an image/GIF whose source is a guarded URL bounced
with send_group_msg retcode 1200 "fetch failed".

Mirror NapCat's download path: send a browser User-Agent on every
request and retry once with a Referer pointing at the resource when the
first attempt is refused (network error or 403/4xx). Size-limit
rejections are tagged noRetry so an oversized body isn't re-downloaded.
Also route set_group_notice's image fetch through loadBinarySource so it
inherits the same hardening (plus http/file/base64 handling).
2026-05-29 17:37:10 +08:00
H3CoF6 00ca582f1a feat(protocol): implement OIDB 0xcde_2 (RequestDbKey) (#69) 2026-05-28 15:18:06 +08:00
手瓜一十雪 f2b7ef0056 Switch from @snowluma/sqlite to node:sqlite (sync)
Remove the in-tree @snowluma/sqlite wrapper and vendored better-sqlite3 prebuilts; migrate consumers to node:sqlite's synchronous API. Replaced imports and types (Database/Statement -> DatabaseSync/StatementSync), updated PRAGMA calls to use exec, and adjusted code in onebot (media/message/reaction stores) and protocol (identity service). Vite config no longer bundles sqlite native binaries and references to the sqlite workspace were removed. Also delete the packages/sqlite package and its native prebuild artifacts, and add a couple of unrelated AST improvements in proton: use a module-level shared wrapper cache and add an inheritance cache to avoid repeated scans. Minor import reordering/cleanup applied where needed.
2026-05-27 20:02:40 +08:00
DrSmoothl 5924346823 chore(release): v1.9.2 2026-05-24 18:01:50 +08:00
手瓜一十雪 456086599c Normalize imports, typings and formatting
Reorder and normalize imports, add missing type imports, and fix whitespace/formatting across multiple files. Notable updates: eslint.config.ts import ordering; core bridge APIs (group-album, misc, web) now import Json types; tests/highway pipeline import ordering and added type imports; protocol changes (element-builder, highway client/pipeline) reorganized imports and added proto type imports; many oidb service modules moved protobuf encode/decode imports for consistency; web group modules (album, essence, honor, notice, request-util) reordered imports, adjusted typings/indentation, and small whitespace fixes. These edits are stylistic and typing improvements and should not change runtime behavior.
2026-05-24 13:39:35 +08:00
手瓜一十雪 80ee12ecc2 Enforce no-explicit-any; allow in tests
Enable @typescript-eslint/no-explicit-any as an error in the ESLint config to tighten typing across the codebase, and add an override to disable this rule for test files. Also apply non-functional cleanup in packages/proton/src/runtime.ts: reorder/imports and adjust formatting/indentation and string alignment for readability — no behavioral changes.
2026-05-24 13:38:13 +08:00
DrSmoothl c16064d7e3 refactor(packages): rename @snowluma/bridge → @snowluma/protocol
The package previously named "bridge" houses the protocol-byte layer
(OIDB cmds, msg-push decoders, highway uploaders, element-builder,
event bus). The name "bridge" suggested transport/lifecycle, which it
isn't — that role lives in core/hook and core/bridge/manager. Rename
to match what's actually inside.

This is a pure rename: zero logic changes, all 73 import sites updated
mechanically and TS-verified. core/onebot dependencies key updated.
ESLint override path adjusted for the moved oidb-services directory.

Rename detection threshold: git log --find-renames=20% recommended
when following history across this commit — content-replacement
(@snowluma/bridge → @snowluma/protocol) dropped per-file similarity
below git's default 50% on import-heavy files.
2026-05-23 23:31:03 +08:00