20 Commits
Author SHA1 Message Date
DrSmoothl 7c0519f75f lint 2026-10-12 15:43:34 +08:00
DrSmoothl 93b5c1b4bf feat(messages): support animated face results
Allow selecting animated face results and preserve received results in messages and history. Keep the default selection when no result is supplied.

Fixes #487
2026-09-27 13:44:48 +08:00
DrSmoothl 805665a6d6 feat(onebot): support choosing animated system faces
Send available face animations by default and accept large=false for the small representation. Preserve the sticker business type when encoding animated faces.

Fixes #483
2026-09-26 16:06:52 +08:00
DrSmoothl 4457dd4b4a fix(onebot): send merged forwards through send_msg
Exclusive node lists on send_msg, send_group_msg, and send_private_msg
now take the same forward path as send_forward_msg.

Fixes #415
2026-08-26 23:13:03 +08:00
DrSmoothl 8583afaf2c fix(onebot): resolve signed-int32 reply ids
Reply segments treated negative message_id hashes as invalid, so quotes against those events were dropped.
2026-08-16 13:20:35 +08:00
DrSmoothl 129b091081 fix(onebot): skip unusable reply segments
Keep delivering valid message content when a reply target cannot be resolved, while retaining strict validation for all other segments.

Fixes #339
2026-08-09 17:25:48 +08:00
DrSmoothl 65683bb44d fix(onebot): accept compatible JSON card payloads (#325) 2026-08-08 10:14:39 +08:00
DrSmoothl 005154077f fix(onebot): accept id-less custom music cards (#319) 2026-08-07 21:50:28 +08:00
DrSmoothl b884a1c479 fix(onebot): unify message segment type normalization
Keep empty-placeholder detection aligned with strict segment parsing.
2026-07-23 12:18:02 +08:00
DrSmoothl a7b9ba14fb fix(onebot): accept empty text placeholders in arrays
Fixes #273.
2026-07-23 12:08:17 +08:00
DrSmoothl ff60221330 feat(onebot): support private window shake messages
Encode poke message segments as private window shake messages while keeping the dedicated send_poke action unchanged.

Restrict window shakes to a single ordinary private-chat segment. Reject group, temporary-session, mixed-message, and forwarded usage before identity lookup, media upload, or packet transmission.

Add full outbound request coverage, OneBot action regressions, and live private-chat verification for issue #259.

Closes #259
2026-07-21 19:28:17 +08:00
DrSmoothl 01688c7567 fix(protocol): enforce message element contracts end to end
Replace the open message bag with a validated discriminated union and preflight complete messages before uploads or sends. Return BAD_REQUEST for invalid or unsendable segments instead of dropping fields, sending partial content, or silently changing private messages.
2026-07-10 13:47:06 +08:00
DrSmoothl 053137669a fix(protocol): drop the ineffective video force-full-on-fast-path retry (#145)
forceFullOnFastPath re-issued the NTV2 upload with tryFastUploadCompleted:
false to try to refresh a possibly-expired video resource. Real-machine
testing on a live account + RE of the QQ NT kernel (wrapper.node) proved it
never works:
- the server's fast-path is driven by md5-metadata existence and ignores
  the flag (proton omits a false bool anyway); the retry re-fast-paths
- the kernel itself does no validity check — HandleRspUploadV3 trusts the
  response's fileExist and goes straight to SendUploadSuccFTN

So an expired-but-still-indexed video resource is a platform-level
limitation, not something the upload flow can refresh. Removing the retry
drops two wasted OIDB round-trips per cached-video re-send. The underlying
#145 (receiver '资源已过期' for a server-expired resource) remains a
platform issue, tracked separately.

Tests updated to assert the single-request behaviour.
2026-06-29 15:32:57 +08:00
DrSmoothl 219d60410a fix(onebot): prefer real url over unresolvable file id in media segments
When a bot framework echoes a received image/voice/video, it resends the
original `file=<md5>.ext` (a QQ-internal media id) together with the
download `url`. The parser collapsed sources as `file ?? url ?? ...`, so
`file` won and the send path statSync'd the id as a local path → ENOENT
(retcode 1200). Reported in #155 for images via Yunzai复读.

Add pickMediaSource(): keep `file` when it is directly loadable (inline
base64/data bytes, a remote url, or a path with a separator); otherwise
fall back to a sibling http(s) `url`. Applied to image/record/video.

Tests cover the #155 echo case plus the path/base64-wins guards.
2026-06-28 01:33:07 +08:00
DrSmoothl ecea72df40 test(onebot): expect forced full video upload retry on stale fast-path
The NTV2 fast-upload video test still asserted a single sendRawPacket
call, but #145 (forceFullOnFastPath) re-issues the OIDB request with
fast-upload disabled when a flagged main file carrying real bytes is
fast-pathed by the server. The stale assertion was never caught because
the dev push workflow doesn't run the suite — only the PR gate (ci.yml)
does, so it first surfaced on #149.

Assert two packets go out and that the second round drops
tryFastUploadCompleted, locking #145's behavior at the onebot layer.
2026-06-26 17:09:18 +08:00
DrSmoothl 8dc4ff48f8 feat(onebot): unify market faces to image segments + add send support
Market faces (商城表情) now decode to an OneBot `image` segment carrying
NapCat-style markers (emoji_id/emoji_package_id/key/summary) plus a gxh
gif URL, so clients that don't special-case `mface` still render the
sticker while aware clients can reproduce it.

Adds the previously-missing send path: a dedicated `mface` segment OR an
`image` segment carrying `emoji_id` rebuilds the wire `marketFace`
(faceId=hex(emoji_id), itemType 6 / subType 3 / 300×300 /
pbReserve.field8=1) — byte-matched against NapCat and Lagrange.Core.

- proto-defs: MarketFace.key bytes→string; +MarketFacePbReserve{field8@8}
- protocol: decode faceId→emojiId(hex)/tabId/key; makeMarketFaceElem +
  buildSendElems 'mface' case; format preview keeps the sticker name
- onebot: to-segment mface→image; message-parser mface + image(emoji_id)
- tests: wire decode + full proton round-trip; parse→build; segment shape

Reply/forward round-trips preserved (element keeps type 'mface' + text).
2026-06-14 20:47:50 +08:00
游手好闲的言柒 d3092a1806 fix(onebot): support inline file url/path in send_group/private_msg (#80)
file 段现接受 file/url/path(无需先 upload_group_file/upload_private_file);群走 groupFile.upload()、私聊走 uploadPrivate()(内部已 publish/sendC2c),file_id 老路径不变。含 parser 与群/私聊文件发送测试。
2026-06-03 20:00:58 +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 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
DrSmoothl 9a30df9907 refactor(onebot): extract OneBot protocol translation into @snowluma/onebot
Phase 3 of the 4-package split (RoadMap #3). Moves the entire
`packages/core/src/onebot/` subtree (43 source files + 20 test files
spanning network adapters, action handlers, event converters, message
parser, message store, media store, instance + manager) into the new
`@snowluma/onebot` workspace package. The remaining `@snowluma/core`
now hosts only the api-layer (Bridge class + 13 Api classes), the
hook tooling, the WebUI server, and the runnable `index.ts`.

Why
---
- The OneBot adapter is the public-facing OneBot11 protocol surface;
  it has its own version cadence and its own consumers (third-party
  bots talking OneBot WS / HTTP). Splitting it lets that cadence move
  independently of the Api-on-ctx layer.
- The two were sharing the `packages/core/` build pipeline only
  because they happened to live in the same directory. Nothing in
  the api-layer reaches into OneBot internals.
- Mirrors NapCat / Lagrange.OneBot's repo split (their OneBot adapter
  is a separate assembly from the Core protocol library).

Layout
------
Moved via `git mv` (history preserved):
  packages/core/src/onebot/*  → @snowluma/onebot/src/<x>
  packages/core/tests/onebot/* (6 files)   → @snowluma/onebot/tests/
  packages/core/tests/{message-parser,message-store,event-pipeline,
    media-store,media-url-resolver,event-converter,
    http-server-hot-reload,http-server-content-type,
    api-handler-logging,message-id,group-msg-emoji-like,
    config,media-indexer,network-manager}.test.ts (14 files)
                                              → @snowluma/onebot/tests/

What stays in @snowluma/core
----------------------------
- `bridge/{bridge,bridge-interface,bridge-context,manager}.ts`
- `bridge/apis/*` (the 13 typed Api classes)
- `hook/*` (debugger/injector — tooling for the core protocol layer)
- `webui/*` (admin UI server — application wiring, not the OneBot
  adapter itself)
- `index.ts` (application entry that wires BridgeManager +
  OneBotManager + HookManager + WebUI together)

Changes
-------
- New package `packages/onebot/` (package.json with subpath exports
  mirroring legacy `core/src/onebot/<x>` 1:1, tsconfig.json,
  vitest.config.ts with `SNOWLUMA_LOG_LEVEL=debug` env so the action-
  dispatch logging tests see debug-level subscribers — copied verbatim
  from core's vitest.config.ts).
- @snowluma/core grows new subpath exports for the api-layer files
  OneBot's type-only imports need: `./bridge`, `./bridge-interface`,
  `./bridge-context`, `./manager`, `./apis`.
- `@snowluma/onebot` depends on `@snowluma/bridge`, `@snowluma/common`,
  `@snowluma/core`, `@snowluma/proto-defs`, `@snowluma/proton`,
  `@snowluma/websocket`.
- `@snowluma/core` depends on `@snowluma/onebot` (for index.ts +
  webui/server.ts wiring).
- 100+ import sites rewritten via sed:
  * OneBot → core: `'../bridge/bridge'` → `'@snowluma/core/bridge'`,
    `'../bridge/manager'` → `'@snowluma/core/manager'`,
    `'../bridge/bridge-interface'` → `'@snowluma/core/bridge-interface'`,
    `'@/bridge/bridge'` (vite path alias) → `'@snowluma/core/bridge'`.
  * Core → OneBot: `'./onebot/manager'` → `'@snowluma/onebot/manager'`,
    `'../onebot/config'` → `'@snowluma/onebot/config'`, etc.
  * Test relative paths: `'../../src/onebot/<x>'` → `'../src/<x>'`
    after the test file move (one level shallower).
- New `packages/onebot/src/env.d.ts` declaring `__APP_VERSION__` and
  `__BUILD_WEBUI__` (the vite-injected globals `info.ts` reads).

Workspace cycle
---------------
core ↔ onebot is allowed by pnpm workspaces. The runtime direction
is one-way: core → onebot (index.ts constructs OneBotManager,
webui/server reads OneBot config). OneBot only reaches into core for
TYPES (Bridge, BridgeInterface, BridgeManager, ClientKeyInfo,
DownloadRKeyInfo); those imports use `import type` so they compile
out at runtime and don't introduce a real cycle.

Verification
------------
- Full workspace `pnpm -r typecheck`: all 11 projects clean.
- `@snowluma/core` test suite: 302/302 pass (33 test files).
- `@snowluma/onebot` test suite: 250/250 pass (20 test files;
  pre-existing `network-manager.test.ts` native-addon failure
  unchanged — same baseline as before the split).
- Combined: 552/552 — identical pass count to the pre-Phase-3
  baseline.
- Vite build with stubbed native binary: clean, 2.84 MB bundle /
  224 KB gzip (identical envelope).
2026-05-21 08:52:00 +08:00