Commit Graph
12 Commits
Author SHA1 Message Date
DrSmoothl 7c0519f75f lint 2026-10-12 15:43:34 +08:00
DrSmoothl f95d247e73 feat(runtime): override deploy settings from the environment
SNOWLUMA_ variables can set the factory OneBot listen ports, WebUI TLS,
packet and hook timeouts, the stream directory, file download timeout,
and notification HTTP timeout. The values apply in memory and are not
written back to saved config.
2026-10-06 00:48:34 +08:00
DrSmoothl 3c97e71650 fix(onebot): strengthen endpoint defaults
Use safer defaults for newly created service endpoints and guide operators toward suitable access credentials without disrupting existing deployments.

Apply the same policy at the save boundary, retain deliberate local-only setups and external client compatibility, and add regression coverage for editing, recovery, and trusted access paths.
2026-08-01 18:12:04 +08:00
DrSmoothl dc2ec1235a feat(onebot): backfill cloud history after login
Add an opt-in per-account login history backfill that waits for roster warmup and a 30-second grace period before silently filling local message history. The setting defaults to disabled and applies on the next account lifecycle.

Run one account at a time, prioritize known local gaps, and process at most three groups plus three friends with one 20-message page per session. Failures are logged and never retried during the same login.

Persist reconstructed history without dispatching OneBot events or changing QQ read state. Keep private metadata and event bodies atomic, honor recall tombstones, suppress live-only history side effects, avoid extra media requests, and stop queued, in-flight, or converting work when the account closes.

Share the persistence path with manual history actions and add lifecycle, cancellation, corruption, partial-failure, peer-identity, and storage-atomicity regression coverage. Refs #280.
2026-07-25 23:44:28 +08:00
DrSmoothl aff6ad9179 refactor(onebot): make config persistence explicit
Persist user saves as authoritative per-account snapshots while retaining legacy overlay compatibility. Validate deterministic topology errors before writing and replace configuration files atomically.
2026-07-10 13:46:09 +08:00
DrSmoothl ea1c5ff9eb feat(onebot): migrate musicSignUrl from per-UIN to global config
musicSignUrl is a deployment-wide signing-service endpoint (same for every
account), so it belongs in the global config/snowluma.json next to rkey, not in
per-UIN onebot_<uin>.json.

- GlobalSettings gains musicSignUrl (http(s)-validated, '' = built-in default);
  consumption switches to globalSettings.musicSignUrl and hot-reloads via
  applyGlobalSettings.
- One-shot copy-up at boot (migrateGlobalSettings, called first in main()):
  lifts the first VALID legacy value from any config/onebot*.json into the
  global store. Idempotent via a key-PRESENCE guard, so an intentional
  clear-to-'' is never reverted by a stale legacy value.
- Remove musicSignUrl from per-UIN OneBotConfig (type + parse/serialize).

Phase 2 of the global-settings split. (WebUI form follows in Phase 3.)
2026-06-28 21:38:32 +08:00
DrSmoothl 659970077a refactor(onebot): move rkey fallback from per-UIN to a global config store
The rkey fallback endpoint is deployment infra (one self-hosted server serves
all accounts; fallback rkeys are fungible), so it doesn't belong in per-UIN
onebot_<uin>.json. Introduce config/snowluma.json — a generic home for small
all-accounts SnowLuma settings — and move rkey.fallbackServers there.

- New onebot/global-config.ts: load/save/normalize with section-merge + atomic
  write; http(s)+host URL validation; default [] (off). Lives in onebot (not
  core) because the onebot manager constructs instances and must not depend on
  core.
- OneBotInstance takes globalSettings; manager loads it on session start and
  exposes reloadGlobalSettings() to hot-push edits to every live instance via
  applyGlobalSettings → RKeyCache.setFallbackServers.
- Remove rkey from per-UIN OneBotConfig + the WebUI per-UIN round-trip (never
  released, so no migration). Register snowluma.json in the backup allowlist.

Reworks the per-UIN rkey from 12b0190. Phase 1 of the global-settings split.
2026-06-28 20:52:07 +08:00
DrSmoothl 12b01907bc feat(onebot): opt-in remote rkey fallback server (#156)
On accounts where the native OIDB 0x9067_202 rkey fetch persistently returns
nothing, every image was served as a bare multimedia.nt.qq.com.cn URL the CDN
rejects with invalid rkey — SnowLuma had no fallback, unlike NapCat.

Add config.rkey.fallbackServers (default []: OFF; no third-party server is
contacted unless the operator configures one). When the native fetch leaves the
cache without a usable image rkey, each configured HTTP endpoint is tried in
turn for { group_rkey, private_rkey, expired_time } (NapCat rkey-server format;
an OneBot { retcode, data } wrapper is also accepted), populating the group/
private image rkey types. Values keep any leading &rkey=/?rkey= since
resolveImageUrl strips before re-appending.

WebUI has no form yet but round-trips rkey.fallbackServers verbatim so a hand-
edited value survives an unrelated config save. Part B of #156 (cooldown is A).
2026-06-28 01:37:58 +08:00
stla 41bad7f24c feat: make #sl status command trigger configurable (exact match, max 32 char) (#138)
* feat: make #sl status command trigger configurable (exact match, max 32 char)

The trigger word for the built-in #sl status command is now configurable
via statusCommand.trigger, with exact case-insensitive matching preserved.

* chore: reject control chars in trigger + add sync assertion
2026-06-25 01:02:17 +08:00
DrSmoothl 34d05f41f5 feat(notifications): P1 config layer — global channel store + per-UIN opt-in
Global channel store at config/notifications.json, mirroring ui-config's
total-normalize + atomic-write + cached-load conventions: NotificationsConfig
(version, debounceSeconds clamp [0,3600] default 30, channels[]), a total
normalizeNotificationsConfig (drops malformed/non-http channels, dedupes by
id, strips unknown fields), and the pure renderTemplate ({key} substitution;
missing keys left as-is; no re-expansion of substituted values).

Per-UIN opt-in: OneBotConfig.notifications.channelIds (validated slugs,
last-write-wins merge across global+per-uin, deduped) in the onebot package.

Tests: 19 (core notifications-config) + 12 (onebot config), all green.
Adversarial review: 0 blockers / 0 should-fix.
2026-06-16 19:02:01 +08:00
DrSmoothl 003e8c2deb feat(onebot): built-in #sl status command (toggleable, non-plugin)
A hardcoded `#sl` status command — deliberately NOT a plugin system.
Matched at the instance dispatch choke point: a pure-text message equal
to `#sl` (exact, trimmed, case-insensitive — no startsWith) replies with
SnowLuma version / platform / uptime in the same conversation.

- Anyone can trigger; the per-UIN `statusCommand.enabled` toggle is the
  only gate (default on). Per-conversation cooldown (default 5s,
  configurable) prevents flood/风控 from repeated triggers.
- `swallow` (default false / pass-through): when set, a matched `#sl` is
  not forwarded to downstream adapters — still cached, logged, replied.
- Config on OneBotConfig.statusCommand (per-UIN): defaulted + clamped in
  config.ts, mirrored/normalized in the WebUI; edits hot-reload via the
  shared instance context.
- WebUI: a "内置命令" section in the 通用设置 tab (enable / swallow /
  cooldown). Node enable toggle extracted into a shared ui/ToggleSwitch.

Pure matcher / cooldown / uptime / text helpers live in
modules/status-command with unit tests; config defaults + clamp covered.
2026-06-01 14:58:13 +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