Commit Graph
9 Commits
Author SHA1 Message Date
DrSmoothl 7c0519f75f lint 2026-10-12 15:43:34 +08:00
DrSmoothl 2f5065545a fix(onebot): percent-decode file URL basenames on upload (#354)
send_group_msg / send_private_msg with a file segment that only has
file:// (no name/filename) used url.split('/').pop() as the upload
name, so percent-encoded CJK paths uploaded as
%E6%B5%8B%E8%AF%95%E6%96%87%E4%BB%B6.xlsx. Derive the fallback through
guessFileNameFromUrl, which decodeURIComponent's the path basename
(invalid encodings are left as-is). An explicit name still wins.

Fixes #354
2026-08-13 11:02:02 +08:00
DrSmoothl b4f5e9b71b fix(media): support base64 data URLs for uploads
Parse RFC 2397 base64 data URLs through the shared binary source classifier so inline media cannot fall through to local file handling.

Reuse the same parsing path for buffered and disk-staged uploads, reject malformed or oversized inputs explicitly, and add regression coverage for both paths.

Refs #236
2026-07-16 10:05:56 +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
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 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`.
2026-05-21 08:41:34 +08:00
手瓜一十雪 aea2676c31 Add TypeScript ESLint configs and lint scripts
Add a repository .editorconfig and a root eslint.config.ts to standardize formatting and linting. Migrate packages/webui ESLint config to TypeScript (eslint.config.ts) with project-specific rules and tsconfigRootDir handling, and remove the old JS config. Update package.json to run ESLint directly (add lint and lint:fix) and add required ESLint/dev dependencies; also add jiti to packages/webui/package.json. Include a small formatting tweak to tsconfig.base.json.
2026-05-20 19:08:45 +08:00
时瑾 b40cee18cc fix(bridge): close two binary-size cap gaps flagged by review
Follow-up on the previous highway commit, addressing two real holes the
Copilot review caught:

* loadBinarySource HTTP path was buffering the entire response via
  `await resp.arrayBuffer()` BEFORE length-checking. A server that omits
  or understates `Content-Length` (chunked transfer, malicious upstream,
  proxy stripping headers) bypassed the cap entirely. Switch to a
  streaming reader: accumulate chunks, abort + reject as soon as the
  running total exceeds `maxBytes`. The Content-Length precheck still
  short-circuits the obvious case before we read a byte.
* video-upload `stageVideoSource` has its own local-path fast-path that
  never went through `loadBinarySource`, so the 100 MB MAX_VIDEO_SIZE
  guard ran AFTER `fs.readFileSync` had already allocated the entire
  file. A 10 GiB local-file pointer would OOM the bot before the check
  fired. Add an `fs.statSync` precheck on the local branch, and pass
  `MAX_VIDEO_SIZE` explicitly when the source falls through to
  `loadBinarySource` so the HTTP / base64 cap matches the on-disk one.

Update the DEFAULT_MAX_BINARY_SIZE comment to stop claiming video paths
are bounded by it — they're bounded by the smaller MAX_VIDEO_SIZE,
enforced inside video-upload.

Adds five regression tests:
  - chunked streamed download (no Content-Length) over cap → throws
  - Content-Length over cap → throws before reading body
  - streamed download inside cap → returns full bytes
  - local file over cap (statSync precheck) → throws
  - base64 over cap → throws
2026-05-16 21:31:54 +08:00
DrSmoothl 6f506ff52c fix onebot video file sending 2026-05-11 23:26:45 +08:00