Private (c2c) file sends uploaded the bytes (apply 0xE37_1700 + Highway)
and PbSendMsg returned ok, but the receiver saw "文件传输失败": the message
carried only FileExtra.file (metadata) and not the server-issued download
routing. Group files use a different element, hence c2c-only.
Port NapCat's upload→finalize→send sequence:
- add 0xE37_800 (FinalizeOfflineFile) OIDB service, run after the Highway
PUT to fetch the download metadata (field30)
- extend FileExtra with field6 (PrivateFileExtra.field2) and populate it
from that metadata in sendC2cFile; covers both the url-upload and the
file_id-resend send paths (both funnel through sendC2cFile)
Tests: byte-oracle locks the 0xE37_800 request + FileExtra.field6 tags;
core routing test now asserts finalize runs and field6 is populated.
Needs real-machine verification (send a private file, confirm download).
First slice of personal QQ-Zone (个人空间) support, an exclusive feature
vs NapCat/SL (both only do group albums). Read-only to prove out the
auth+parse chain before write ops (publish/like/comment).
- protocol/web/qzone.ts: getQzoneMsgList over taotao.qzone.qq.com's
emotion_cgi_msglist_v6 (h5.qzone proxy), reusing the same cookie/g_tk
plumbing as group-album. JSONP-tolerant parse; throws on auth failure
rather than swallowing to [] (mirrors group-signin contract).
- core: QzoneApi bridge class wired into ApiHub.
- onebot: get_qzone_msg_list action (target_uin/pos/num), regenerated
MCP catalog + new 空间 doc category.
- HTTP-layer tests lock URL/params, JSONP stripping, field mapping, and
the throw-on-auth-failure paths (9 cases, all green).
Exact CGI params/response fields to be re-confirmed against a live
capture when extending (same maintenance posture as group-album).
Switch from open-source to a NapCat-style source-available, NON-commercial
license so the free software cannot be resold / paid-deployed — which an OSI
license (AGPL included) cannot prevent. Source stays public for study and
non-commercial self-hosting; commercial use and public release of modifications
require written permission; the native addon (snowluma-*.node) is carved out as
proprietary. Explicitly NOT OSI open source.
- LICENSE: SnowLuma Source-Available Non-Commercial License v1.0 (bilingual)
- all 14 package.json: "license": "SEE LICENSE IN LICENSE"
- README license section; CONTRIBUTING contribution-licensing note + a grant
letting the project relicense contributions under commercial terms
(contributors keep copyright)
set_doubt_friends_add_request with approve:false now rejects/declines via
delDoubtBuddyReq instead of erroring. RE'd from QQNT wrapper.linux.node
(DoubtBuddyDelWorker ctor sub_3F3F320 → OIDB 0xd69_0; EncodeRequest
sub_3F3E860 body {1: varint const 3 (op discriminator; get uses 1),
3:{1: string uid}}, uid from kernel attr 21001). uin-form (reserved=1).
Adversarially IDA-verified; byte-oracle locks the tags. Split out as its
own commit because the approve-only 0xd69_0 work was already pushed.
All recovered from QQNT wrapper.linux.node via IDA — NapCat ships these as
kernel-only with no portable packet wire, so each cmd + protobuf was traced
from the binary (worker ctor cmd/subcmd via BaseOidbWorker::DoSendOidbReq;
field tags from the per-feature EncodeRequest). All uin-form OIDB (envelope
reserved=1, matching the NT worker flag=1). One commit for easy rollback.
New OIDB services + byte-oracle tests (fixed-wire, mutation-resistant):
- 0x9130_0 getBuddyRecommendContactArkJson → share_peer/send_ark_share (user)
- 0x8b7_5 getGroupRecommendContactArkJson → share_peer/send_ark_share (group),
share_group_ex/send_group_ark_share (NapCat's getArkJsonGroupShare
was NOT RE'd; routed to this confident equivalent — documented)
- 0xd69_0 getDoubtBuddyReq → get_doubt_friends_add_request
- 0xd69_0 approvalDoubtBuddyReq → set_doubt_friends_add_request (approve only)
- 0xf00_3 modifyGroupExtInfoV2 → set_group_robot_add_option (EXTInfo tags
30/31; GroupExtFilter is client-side gating, not on the wire)
NOT shipped (kernel-.so-side, no static cmd recoverable from this binary —
honest, no guessed packets): get_robot_uin_range (trpc method-routed),
create_collection/get_collection_list (Weiyun multi-step), trans_group_file.
Doubt-list string field NAMES (nick/source/msg) are medium-confidence
(generic serializer); uid+reqTime are high. READ path, so a mislabel is
cosmetic, never a wire/ban risk. Wired through bridge friend/contacts/
groupAdmin apis; proto-defs + per-file subpath exports registered.
Group sign-in (打卡) list is NapCat's one kernel-free item here: it's a
plain HTTP POST to qun.qq.com/v2/signin/trpc/GetDaySignedList with g_tk
from p_skey, not OIDB. SnowLuma already has the cookie/bkn infra (same as
group notice/essence/honor), so this is a straight port — no RE.
- protocol/web/group-signin.ts: getDaySignedListWebAPI + pure
mapDaySignedList (de-skews signInRank via (r-1)/2+1, [] when no page).
- core WebApi.getSignedList(groupId) wires the qun.qq.com cookie jar.
- onebot get_group_signed_list groupAction (readOnly), [] on no sign-ins.
- Tests pin the rank/field transform and the action wiring.
Server-side image OCR ported from Lagrange.Core ([Service]
"OidbSvcTrpcTcp.0xe07_0") cross-checked with NapCat's Oidb.0xE07 proto
(NapCat's transformer cmd 0xEB7 is a typo; the proto name + Lagrange + the
wire name all say 0xE07_0). New ImageOcr service + proto types, misc.ocrImage
api, and the ocr_image/.ocr_image action. The cmd takes an image URL, so the
action accepts an http(s) url or a cached image file_id (resolved via
getImageInfo); base64/local input is unsupported (NapCat's path is a
Windows-only kernel OCR, not portable). Wire round-trip + response-parse
tests lock the pb tags.
Add the file-rename slot to the group-file OIDB envelope (req/resp field 5),
a RenameGroupFile service mirroring MoveGroupFile, a groupFile.rename() api
method, and the rename_group_file action (params group_id/file_id/
current_parent_directory/new_name, NapCat-aligned), replacing the stub.
Wire fields (busId=102, parentFolder=current dir, newFileName) verified
against NapCat's Oidb.0x6D6 transformer; subCommand 4 cross-checked against
the move op (sub 5 on both sides). Serialize + envelope round-trip test locks
the pb tags.
A group invite push carries no group name (confirmed across SnowLuma / Lagrange
/ NapCat — all three invite protos only have groupUin + invitorUid), and
get_group_info only resolved joined groups (fetchGroupList → roster), so an
invited group rendered "未知群名". Add a by-id server lookup so the name
resolves even when the bot isn't a member.
- proto-defs: 0x88D_0 request (field1 + config.uin + a request-mask `flags`
block) + response (groupInfo.results: name / member counts / owner / …),
field tags transcribed from
dev/Lagrange.Core/.../OidbSvcTrpcTcp0x88D_0{,Response}.cs.
- protocol: FetchGroupDetail oidb-service. Envelope reserved=0 (NO uinForm) —
Lagrange's GetGroupInfoService builds it with isUid=false.
- core: ContactsApi.fetchGroupDetail(gid) → QQGroupInfo | null; deliberately
NOT remembered into the joined-groups roster (mustn't pollute get_group_list).
- onebot: get_group_info falls back to fetchGroupDetail for non-member groups,
behind a 5-min per-id TTL cache so a burst of invites for one group can't
hammer 0x88D_0 (rate-limit / kick risk). noCache bypasses it.
- tests: request/envelope round-trip (reserved=0, uin, command), response
decode; + non-member resolve / cache-hit / noCache-bypass at the onebot layer.
Live note: byte-identical to Lagrange's 0x88D_0, but not verified against a real
account on this host.
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.
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.