Skip to content

fix(napcat): encode bridged images as base64 - #191

Merged
BegoniaHe merged 3 commits into
masterfrom
fix/napcat-bridged-image-base64
Sep 14, 2026
Merged

BegoniaHe merged 3 commits into
masterfrom
fix/napcat-bridged-image-base64

Conversation

@BegoniaHe

@BegoniaHe BegoniaHe commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

/session watch and /session connect forwarded Telegram photos into NapCatQQ as unusable file:// URIs. NapCat now encodes readable local Image and Record copies as base64://, so the OneBot process does not need AstrBot's filesystem. HTTP, existing base64://, and NapCat cache names still pass through.

Related issue

Fixes #190

Root cause

Session bridge materializes deferred Telegram media to a temp file:// copy. NapCat _append_media_outbound_segment copied file/path into the OneBot image segment. A NapCat process in another container or on another host cannot read the AstrBot path.

The first patch encoded every image and record. That dropped native NapCat cache names, downloaded HTTP URLs AstrBot may not be able to fetch, and swallowed encoding failures without a log.

Reproduction

  1. Watch a Telegram session from NapCat with /session watch <telegram_umo>.
  2. Send a photo in Telegram.
  3. NapCat receives the source header but not the picture, or the send fails.

Unit: a local Image.fromFileSystem or delivery-style Image(file=uri, path=uri) through _build_outbound_message previously emitted file:///.../data/temp/bridge-*/0.jpg.

Implementation notes

Local and file:// copies that AstrBot can read are encoded as base64://. HTTP, existing base64://, and bare NapCat cache names pass through, including original url/path on the segment. Unreadable filesystem refs and encoding failures omit the media, log a redacted warning, and insert a generic [Image]/[Record] text placeholder instead of forwarding a path. Video and File stay pass-through, same as aiocqhttp. Telegram download failure still degrades to the existing unavailable placeholder.

Validation

uv run ruff format astrbot/core/platform/sources/napcat/napcat_platform_adapter.py tests/unit/platform/test_napcat_outbound.py
uv run ruff check astrbot/core/platform/sources/napcat/napcat_platform_adapter.py tests/unit/platform/test_napcat_outbound.py
uv run pytest tests/unit/platform/test_napcat_outbound.py tests/unit/test_message_protocol.py
node node_modules/prettier/bin/prettier.cjs --check --ignore-path .gitignore docs/zh/dev/plugin-platform-adapter.md docs/en/dev/plugin-platform-adapter.md
./node_modules/.bin/markdownlint-cli2 docs/zh/dev/plugin-platform-adapter.md docs/en/dev/plugin-platform-adapter.md

The ruff, pytest (106 passed), and Prettier commands passed in this session. markdownlint reported no issues on the two docs pages. make check and make test-blocking were not run.

Compatibility and risk

Public API unchanged. Large local images still inflate OneBot WebSocket frames. HTTP images are no longer downloaded by AstrBot. Video/File are unchanged and can still fail across hosts.

Checklist

  • The change is focused and does not include unrelated refactoring.
  • I added or updated a regression test, or explained why a test is not practical.
  • I ran the relevant formatting, lint, build, and test commands.
  • User-visible behavior updates both docs/zh/ and docs/en/ when needed.
  • OpenAPI, generated client, docs/public/openapi.json, and tests change together when routes or schemas change.
  • No secrets committed. Runtime Python deps update pyproject.toml, requirements.txt, and uv.lock together.
  • I did not restore legacy shims, Python <3.14 fallbacks, or upstream publish/docs URLs as fork artifacts.
  • Breaking API or behavior changes use ! and a BREAKING CHANGE: footer.
  • I will not merge this PR myself. Merge needs a human maintainer review plus a separate AI-assisted review (AI_POLICY.md).
  • AI use follows AI_POLICY.md. Keep exactly one author note below. Do not fabricate the other.

Agent note

Goal: make /session Telegram photos arrive on NapCatQQ without regressing native NapCat echo. Review of PR #191 found unconditional base64 encoding and silent failure swallows. Touched napcat_platform_adapter.py (_portable_media_file, _append_media_outbound_segment), tests/unit/platform/test_napcat_outbound.py, and the NapCat rows in docs/zh/dev/plugin-platform-adapter.md and docs/en/dev/plugin-platform-adapter.md. Residual risk: Video/File still use pass-through URIs; Telegram get_file download failure is a separate placeholder path. Tools: OpenCode / grok-4.6, ruff, pytest, Prettier.

Convert outbound Image and Record components to base64:// so session
bridge file:// copies reach NapCat without sharing AstrBot's filesystem.

Fixes #190
AI-Generated: true
Generated-At: 2026-09-14T15:07:58Z
@BegoniaHe BegoniaHe self-assigned this Sep 14, 2026
Prefer HTTP, existing base64, and NapCat cache names so native echo
still works. Encode readable local copies for session bridge, log
failures, and omit unreadable filesystem refs.

Fixes #190
AI-Generated: true
Generated-At: 2026-09-14T16:03:00Z
@BegoniaHe

Copy link
Copy Markdown
Collaborator Author

AI-assisted review

Cannot use GitHub “request changes” on this PR (same author). Treat this as the separate AI review required by AI_POLICY.md.

Verdict

Not ready to merge. The Telegram → NapCat file:// bridge path is covered, but the unreadable-filesystem omit branch regresses native NapCat media that AstrBot cannot read and NapCat can.

What works

  • Root cause for [bug] session bridge cannot deliver Telegram images to NapCat #190 is right: session-bridge local copies must not be forwarded as AstrBot file:// paths.
  • Second commit correctly prefers http(s):// / existing base64://, and encodes only files AstrBot can actually read.
  • Encoding failures are logged with safe_error and replaced by a placeholder instead of swallowing.
  • Tests cover bridged file://, HTTP pass-through, cache names, and encode failure.
  • CI on this head is green.

Blocking

_portable_media_file treats any unreadable file:// or absolute path as an AstrBot path and drops it:

for value in candidates:
    if _is_raw_filesystem_ref(value):
        logger.warning("[NapCat] Omitting unreadable outbound %s", ...)
        return None

Inbound NapCat records already look like this (tests/unit/platform/test_napcat_inbound_parse.py):

  • file=napcat-record.amr
  • url=file:///C:/NapCat/cache/napcat-record.amr
  • path=C:/NapCat/cache/napcat-record.amr

On a Linux AstrBot host those paths exists() as false, _is_raw_filesystem_ref is true, and a native resend/echo becomes [Record]. Images with only a NapCat cache file:// and no HTTPS URL would hit the same path. #190 is “AstrBot could read it and still sent a useless path”. If AstrBot cannot read it, pass the original ref through — that is NapCat’s filesystem, not ours.

Suggested policy:

  1. http(s):// / base64:// → pass through (keep).
  2. Readable local / file:// / data:base64:// (keep); on encode failure, omit and log (keep).
  3. Otherwise → candidates[0] pass-through (delete the omit loop).

Add a regression test with the inbound Record shape above (and an image whose only URL is file:///C:/NapCat/cache/...) asserting the original file/url is forwarded, not [Record]/[Image].

Non-blocking

  • Video and File still pass file://. Fine as a follow-up; same class of bug if someone watches a Telegram video.
  • Record.convert_to_base64() transcodes to WAV. Local bridged voice may not play as QQ silk. Same as aiocqhttp; worth a note, not this PR’s job unless you encode Record differently.
  • Image.convert_to_base64() uses url or file, not path. Path-only components that exists() still fail closed to [Image]. Bridge and fromFileSystem set file, so [bug] session bridge cannot deliver Telegram images to NapCat #190 is fine.
  • HTTP pass-through still copies component.path into the segment. Harmless for native NapCat cache paths; do not copy an AstrBot temp path if file is already HTTPS.
  • Two commits are a useful review story; squash on merge if you want a single fix commit.

Human maintainer review is still required before merge.

Encode only AstrBot-readable local copies as base64. Unreadable
file:// and cache paths belong to NapCat and stay pass-through.

Fixes #190
AI-Generated: true
Generated-At: 2026-09-14T22:08:23Z
@BegoniaHe
BegoniaHe merged commit a1527bc into master Sep 14, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug] session bridge cannot deliver Telegram images to NapCat

1 participant