Resolve saved stickers by resource ID or MD5, support group and private destinations with optional replies, and preserve presentation in upload and message metadata.
Fixes#468
Normalize the observed AI SILK container marker at the shared audio transcode boundary before invoking the bundled decoder. Preserve caller-owned bytes and leave ordinary SILK, AMR, and unrelated inputs unchanged.
Add unit and packaged-addon regression coverage for MP3 conversion, payload preservation, and input immutability.
Fixes#268
Replace the feature-specific MP3 verification command with a workspace-wide native test entry point.
Keep native regression cases in package-owned test suites so future checks can expand without adding workflow steps.
Use the dedicated audio conversion path for record output so mono QQ voice records produce valid MP3 data.
Add release-target smoke coverage and preserve conversion and cleanup failures for diagnostics.
Fixes#239
#165: get_record gains an optional `out_format` (mp3/amr/wma/m4a/spx/ogg/
wav/flac, mirroring NapCat). When set, the QQ voice (SILK/AMR) is
transcoded server-side and the result is attached as `data.base64`; the
original info (file/url/file_size) is preserved. Absent ⇒ behaviour
unchanged. This spares ASR consumers from shipping a SILK decoder.
- protocol: convertAudioBytes(bytes, format, deps?) drives the bundled
SILK-capable ffmpeg addon's convertFile through temp files (always
cleaned up), with a format allowlist and an output-size cap. Pure/
injectable, unit-tested with a fake addon (6 cases).
- core: ExtrasApi.convertRecord(source, format) = loadBinarySource (64 MiB
cap) → convertAudioBytes.
- onebot: get_record wires out_format; transcode failure (incl. an addon
lacking SILK decode) degrades to ACTION_FAILED, never crashes.
Adversarially reviewed: no path traversal (format allowlisted), no SSRF
(source is the cached CDN url, not user input), cleanup covers every path,
graceful degradation confirmed. Catalog regenerated. Real-machine e2e
(does the shipped .node actually decode SILK?) is Phase 2.
detectImageFormat read the VP8X (extended WebP) canvas width/height with a
big-endian readBE24, but the WebP spec stores them as 24-bit LITTLE-endian
(Minus-One). A 1920×1080 image was reported as 8324865×3605505, so the
fileInfo sent to highway was garbage and QQ cropped the thumbnail. VP8
(readLE16) and VP8L (readLE32) were already correct.
- Replace readBE24 with readLE24 in the VP8X branch; readBE24 had no other
caller, so it's deleted (no dead code left).
- Add the previously-absent detectImageFormat regression suite (12 cases):
PNG/GIF/BMP/VP8/VP8L/VP8X/JPEG + JPEG DHT-skip + truncated/unknown
fallbacks. The VP8X cases pin little-endian byte order, including the
exact bytes from the issue and an asymmetric 256×1 case that a BE misread
would blow up to 16711681.
Verified TDD red→green: the 3 VP8X cases failed before the one-helper swap,
all 12 pass after.