dev2
6
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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. |
||
|
|
cd9403f27a |
fix(forward): align resid+uniseq for nested forwards
Multi-layer 合并转发 was uploading correctly but the outer's
LongMsgResult piggyback was *unreachable* by the receiver because
two things were missing:
1. The forward preview element was emitted as the old XML
`richMsg serviceID=35 m_resid=…` shape, which carries the
inner resId but has NO uniseq field.
2. The piggyback `actionCommand: <uuid>` slot generated a fresh
UUID that was never reflected into the preview either.
Result: QQ-NT receivers saw the inner preview's resid, scanned the
outer's actions[] for a matching uniseq, found none, and either
fell back to a fresh server fetch per layer or dropped the layer
entirely.
Cross-checked against:
- dev/Lagrange.Core/Lagrange.Core/Message/Entity/MultiMsgEntity.cs:43-115
- dev/NapCatQQ/packages/napcat-core/helper/forward-msg-builder.ts:52-122
- dev/NapCatQQ/packages/napcat-onebot/action/msg/SendMsg.ts:208-347
Both upstream impls use the modern `com.tencent.multimsg` LightApp
JSON whose `meta.detail.uniseq` is the round-trip key that links
the inner preview to the matching `actionCommand` in the outer's
LongMsgResult — no separate fetch needed.
Changes:
- `element-builder.makeForwardElem` now emits a deflated
`com.tencent.multimsg` LightApp JSON (LightAppElem, field 51)
instead of the XML RichMsg (field 12). `forwardUuid` on the
MessageElement is baked into `meta.detail.uniseq` and
`extra.filename`. Auto-generates a UUID when absent.
- `MessageElement.forwardUuid?: string` added for the round-trip.
- `forward.uploadRecursive` threads `inner.uuid` into the outer's
preview element AND uses the same uuid for the outer's
piggyback actionCommand — closing the receiver-side lookup loop.
- `forward.fetch` now walks ALL actions (not just `MultiMsg`),
indexes piggybacks by uuid, and pre-populates the cache for
each inner resId discovered in the outer's preview bubbles.
Follow-up `fetch(innerResId)` hits the cache instead of the wire.
- `rich-body-decoder` parses LightApp JSON; when `app ===
com.tencent.multimsg`, emits `{type: 'forward', resId,
forwardUuid}` so the OneBot layer can walk in. Legacy XML
serviceID=35 path stays as a fallback for mobile QQ senders.
Wire-byte tests cover:
- send: lightApp.data is 0x01-prefixed deflated JSON with the
right meta + extra fields
- send/nested: outer LongMsgResult carries `MultiMsg` AND a
uuid-keyed piggyback; the piggyback uuid matches the
preview's `meta.detail.uniseq` baked into the same outer
msgBody
- send/3-deep: outermost action[] has 1 + 2 entries (own
MultiMsg + 2 piggybacks hoisted from deeper levels)
- recv: lightApp `com.tencent.multimsg` → forward element with
both resId and forwardUuid; non-multimsg LightApp falls
through to {type:'json'}; legacy XML path still emits
{type:'forward'}.
641 tests pass (was 628 + 13 new); typecheck clean; lint clean.
|
||
|
|
f7a03ca05a |
refactor(bridge): extract protocol layer into @snowluma/bridge package
Phase 2 of the 4-package split (RoadMap #3). Moves ~35 files — events / event-bus / identity-service / packet-pipeline / element- builder / bridge-oidb / qq-info / msg-push/* / highway/* / web/* + the bridge-side formatters — out of `packages/core/src/bridge/` into the new `@snowluma/bridge` workspace package. Everything that translates QQ wire bytes ↔ typed events/elements now lives there. Why --- - `packages/core/src/bridge/` was actually two layers: a low-level "protocol decoder + I/O" set (msg-push, highway, packet-pipeline, identity, element-builder) AND the Api-class host (bridge.ts + apis/*). The Api layer can't move without the protocol layer moving first, but the two had grown into one directory. - Bridge-internal consumers (highway uploaders, OIDB envelope helper, element-builder, msg-push parsers) only ever needed `bridge.identity` + `bridge.sendRawPacket` + the upload-metadata cache. Splitting them out lets that minimal surface live in the bridge package as `BridgeContext` while the api-layer's extended context (with `apis: ApiHub`) stays in @snowluma/core. What stays in @snowluma/core (apis layer): - bridge.ts ── the god-class that composes the protocol building blocks + mounts apis - bridge-interface.ts ── public façade for OneBot consumers - manager.ts ── per-uin BridgeManager hub - apis/* ── the 13 typed Api classes - bridge-context.ts (NEW) ── extends @snowluma/bridge's slim BridgeContext with `apis: ApiHub` so the 13 Api classes keep their typed `this.ctx.apis.X.method()` access. Layout ------ Moved via `git mv` (history preserved): events / event-bus / identity-service / packet-pipeline / element-builder / bridge-oidb / qq-info / msg-push/* / highway/* / web/* → @snowluma/bridge/<sub> utils/event-format.ts → @snowluma/bridge/format.ts (the last one renamed because the destination directory only has bridge-related files; `format.ts` reads cleaner than `event-format`. Listed as a rename in git's `RM` output — content unchanged.) Layering fixes (functionality preserved, just type signatures) -------------------------------------------------------------- - `element-builder.SendContext.bridge` → typed as `BridgeContext` instead of `Bridge`. Reads only `bridge.identity` and the upload-cache (both on BridgeContext); the concrete `Bridge` instance still passes via free upcast. - `bridge-oidb.runOidb` signature widened from `Bridge` to `BridgeContext` — same reason. - `highway/{highway-client,pipeline,image-upload,ptt-upload, video-upload}.ts` — same. - `format.ts::formatReply` parameter typed as a new local `ReplyEventLookup` structural type (`{ findEvent(id): JsonObject | null }`) instead of `MessageStore` from @snowluma/onebot. The OneBot `MessageStore` is a structural superset; consumers pass it through unchanged. - JSON value types moved to a new `@snowluma/common/json` subpath (consumed by format.ts) — `onebot/types.ts` re-exports them so external `import { JsonValue } from '<…>/onebot/types'` paths keep working. - `UploadedFileMeta` interface moved from `core/src/bridge/bridge.ts` into `@snowluma/bridge/bridge-context.ts` — element-builder + the forward-builder both read the upload cache through BridgeContext, so the type has to live in @snowluma/bridge. `bridge.ts` keeps its re-export for back-compat. Changes ------- - New package `packages/bridge/` (package.json with per-file subpath exports mirroring legacy `core/src/bridge/<x>` layout 1:1, tsconfig.json, typecheck script). - 35 `.ts` files moved via `git mv` (3 files counted by git as RM — rename + modify because Bridge → BridgeContext type swap). - 150+ import sites rewritten across `packages/core/src` and `packages/core/tests`: `'../bridge/<x>'`, `'../../bridge/<x>'`, `'../src/bridge/<x>'`, `'@/bridge/web/<x>'`, and vitest `vi.mock(...)` literals all collapse to `@snowluma/bridge/<x>`. - `@snowluma/core` depends on `@snowluma/bridge` (workspace:*). - `@snowluma/bridge` depends on `@snowluma/common`, `@snowluma/proto-defs`, `@snowluma/proton`. - `packages/core/src/utils/` directory removed (empty). Verification ------------ - Full workspace `pnpm -r typecheck`: all 10 projects clean. - core test suite: 552 passed / 552 total (pre-existing websocket-darwin-arm64.node addon failure unchanged — same baseline as the previous refactor commits). - Vite build (with stubbed native binary): clean — 143 modules, 2.84 MB bundle / 224 KB gzip, identical envelope to the pre-split build. Phase 3 (next): OneBot + WebUI move into @snowluma/onebot. The runnable `index.ts` stays in @snowluma/core as the application entry, importing `OneBotManager` from `@snowluma/onebot`. |
||
|
|
c83c333e73 |
fix(file): route group publish through OIDB 0x6d9_4 and cache upload meta for c2c resend
The chat-publish step after upload_group_file used to ship a PbSendMsg
with a transElem(elemType=24) — but that wire shape is RECEIVE-side
only, and current QQ-NT rejects it on send with `result=79`. Lagrange.
Core V2 uses a dedicated `OidbSvcTrpcTcp.0x6d9_4` for the publish
(`GroupSendFileService.cs`); mirror that. Send_group_msg with a
`{type:'file', file_id}` segment now routes through the same path
after the OneBot layer splits file segments out (matching the c2c
file split pattern).
Separately, send_private_msg with just `{file_id}` was shipping a 0 B
file because the wire packet for c2c file send needs fileSize/fileMd5/
fileName/fileHash but the OneBot caller only echoes the file_id from
upload_private_file. Add a bounded (1024-entry, FIFO) bridge cache
populated at upload time so the send-message path can rehydrate the
full tuple; inline segment fields still take precedence so callers
that track the metadata themselves can bypass the cache. Parser now
also accepts `file_hash`/`fileHash` on file segments.
Also: file segments no longer get a `[空消息]` log line — the format
string now shows `[文件:<name-or-id>]`.
|
||
|
|
8f839f4f30 |
fix(group-file): correct GroupFileExtra wire shape — server was rejecting with result=79
Two off-by-one bugs in the group-file chat-post element that combined to make the QQ-NT server reject every group file send with result=79 (visible as "group file uploaded but chat post failed" in the logs): 1. Inner GroupFileInfo fields were shifted one tag left: - fileSha was field 5, should be field 6 - extInfo was field 6, should be field 7 - fileMd5 was field 7, should be field 8 Field 5 is actually a uint32 placeholder (`field5` in both NapCat and acidify). With the bug, our fileSha bytes were landing where the server expected a varint and the rest of the message got skipped as garbage. 2. Outer GroupFileExtra was missing the magic field1=6 + fileName slots that NapCat's encoder writes (`packet/message/element.ts:589`). Acidify's schema also models them as required even though it serialises with defaults. The current server build rejects sends without these slots populated. Cross-referenced: - NapCat `GroupFileExtraInfo` (component.ts:146-155) — matches our new field numbers exactly - acidify `GroupFileExtra.Inner.Info` (proto/message/extra/GroupFileExtra.kt) — same field numbers; their schema uses generic `field5`/`field7` names but the wire layout is identical Changes: - Proton + legacy schemas: shift inner fields, add outer field1/ fileName/display - `makeGroupFileElem`: write field1=6 + fileName on the outer wrapper - Regression test asserts each inner tag lands at 50/58/66 (not the old 42/50/58) and verifies the new outer field1 + fileName bytes are present |
||
|
|
29f28d611f |
fix(send): use c2c businessType for private video / record
Private-chat video and voice sends were getting bounced by the QQ NT server with `PbSendMsg result=79`. Highway upload logs said success, but the final SendMsg the bot constructed used the group-scene tag inside the outgoing commonElem, and the c2c routing path doesn't recognise it. The convention — verified against the receive-side decoder at msg-push/rich-body-decoder.ts:261-330 which explicitly branches on the pairs — is: image : commonElem.businessType c2c=10 group=20 video : commonElem.businessType c2c=11 group=21 record : commonElem.businessType c2c=12 group=22 makeImageElem already honoured the split (`isGroup ? 20 : 10`). makeVideoElem and makePttElem hardcoded the group value for both scenes. Fix them to follow the same pattern. Also fix a symmetric asymmetry inside the highway upload itself: uploadVideoMsgInfo hardcoded `compatQmsgSceneType: 2` while uploadImageMsgInfo and uploadPttMsgInfo both use `isGroup ? 2 : 1`. Same root cause — group value leaking into the c2c path. Regression test (tests/element-builder.test.ts) pins all six businessType pairs by mocking the highway upload functions and inspecting the proto element the builder returns; would have caught the bug had it existed at the time element-builder.ts was written. |