30 Commits

Author SHA1 Message Date
Xubin Ren
4db50f2e32 fix(channels): reject unauthorized inbound before side effects
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-05 23:16:36 +08:00
Jack Lu
d9800ecdd2 refactor: replace try-except blocks with contextlib.suppress for cleaner error handling across multiple files 2026-05-01 19:30:11 +08:00
bahtya
f879d81b28 fix(channels/qq): propagate network errors in send() instead of swallowing
The catch-all except Exception in QQ send() was swallowing
aiohttp.ClientError and OSError that _send_media correctly
re-raises. Add explicit catch for network errors before the
generic handler.
2026-04-13 00:30:45 +08:00
bahtya
fa98524944 fix(channels): prevent retry amplification and silent message loss across channels
Audited all channel implementations for overly broad exception handling
that causes retry amplification or silent message loss during network
errors. This is the same class of bug as #3050 (Telegram _send_text).

Fixes by channel:

Telegram (send_delta):
- _stream_end path used except Exception for HTML edit fallback
- Network errors (TimedOut, NetworkError) triggered redundant plain
  text edit, doubling connection demand during pool exhaustion
- Changed to except BadRequest, matching the _send_text fix

Discord:
- send() caught all exceptions without re-raising
- ChannelManager._send_with_retry() saw successful return, never retried
- Messages silently dropped on any send failure
- Added raise after error logging

DingTalk:
- _send_batch_message() returned False on all exceptions including
  network errors — no retry, fallback text sent unnecessarily
- _read_media_bytes() and _upload_media() swallowed transport errors,
  causing _send_media_ref() to cascade through doomed fallback attempts
- Added except httpx.TransportError handlers that re-raise immediately

WeChat:
- Media send failure triggered text fallback even for network errors
- During network issues: 3×(media + text) = 6 API calls per message
- Added specific catches: TimeoutException/TransportError re-raise,
  5xx HTTPStatusError re-raises, 4xx falls back to text

QQ:
- _send_media() returned False on all exceptions
- Network errors triggered fallback text instead of retry
- Added except (aiohttp.ClientError, OSError) that re-raises

Tests: 331 passed (283 existing + 48 new across 5 channel test files)

Fixes: #3054
Related: #3050, #3053
2026-04-13 00:30:45 +08:00
gem12
48f6bbd256 feat(channels): Add full media support for QQ and WeCom channels
QQ channel improvements (on top of nightly):
- Add top-level try/except in _on_message and send() for resilience
- Use defensive getattr() for attachment attributes (botpy version compat)
- Skip file_name for image uploads to avoid QQ rendering as file attachment
- Extract only file_info from upload response to avoid extra fields
- Handle protocol-relative URLs (//...) in attachment downloads

WeCom channel improvements:
- Add _upload_media_ws() for WebSocket 3-step media upload protocol
- Send media files (image/video/voice/file) via WeCom rich media API
- Support progress messages (plain reply) vs final response (streaming)
- Support proactive send when no frame available (cron push)
- Pass media_paths to message bus for downstream processing
2026-04-11 21:47:19 +08:00
daliu858
06989fd65b feat(qq): add configurable instant acknowledgment message (#2561)
Add ack_message config field to QQConfig (default: Processing...). When non-empty, sends an instant text reply before agent processing begins, filling the silence gap for users. Uses existing _send_text_only method; failure is logged but never blocks normal message handling.

Made-with: Cursor
2026-04-04 01:52:39 +08:00
chengyongru
e4137736f6 fix(qq): handle file:// URI on Windows in _read_media_bytes
urlparse on Windows puts the path in netloc, not path. Use
(parsed.path or parsed.netloc) to get the correct raw path.
2026-03-23 15:48:31 +08:00
Chen Junda
2db2cc18f1 fix(qq): fix local file outbound and add svg as image type (#2294)
- Fix _read_media_bytes treating local paths as URLs: local file
  handling code was dead code placed after an early return inside the
  HTTP try/except block. Restructure to check for local paths (plain
  path or file:// URI) before URL validation, so files like
  /home/.../.nanobot/workspace/generated_image.svg can be read and
  sent correctly.
- Add .svg to _IMAGE_EXTS so SVG files are uploaded as file_type=1
  (image) instead of file_type=4 (file).
- Add tests for local path, file:// URI, and missing file cases.

Fixes: https://github.com/HKUDS/nanobot/pull/1667#issuecomment-4096400955

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 15:48:21 +08:00
Chen Junda
d7373db419 feat(qq): bot can send and receive images and files (#1667)
Implement file upload and sending for QQ C2C messages

Reference: https://github.com/tencent-connect/botpy/blob/master/examples/demo_c2c_reply_file.py

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: chengyongru <chengyongru.ai@gmail.com>
2026-03-23 15:47:59 +08:00
Xubin Ren
af65145bc8 fix(qq): add configurable message format and onboard backfill 2026-03-14 08:25:44 +00:00
Xubin Ren
dbdb43faff feat: channel plugin architecture with decoupled configs
- Add plugin discovery via Python entry_points (group: nanobot.channels)
- Move 11 channel Config classes from schema.py into their own channel modules
- ChannelsConfig now only keeps send_progress + send_tool_hints (extra=allow)
- Each built-in channel parses dict->Pydantic in __init__, zero internal changes
- All channels implement default_config() for onboard auto-population
- nanobot onboard injects defaults for all discovered channels (built-in + plugins)
- Add nanobot plugins list CLI command
- Add Channel Plugin Guide (docs/CHANNEL_PLUGIN_GUIDE.md)
- Fully backward compatible: existing config.json and sessions work as-is
- 340 tests pass, zero regressions
2026-03-14 16:13:38 +08:00
Frank
a09245e919 fix(qq): restore plain text replies for legacy clients 2026-03-12 12:48:25 -07:00
Re-bin
254cfd48ba refactor: auto-discover channels via pkgutil, eliminate hardcoded registry 2026-03-11 14:23:19 +00:00
TheAutomatic
1421ac501c feat(qq): send messages using markdown payload 2026-03-08 07:04:06 -07:00
Re-bin
3e9c5aa34a merge main into pr-532 and keep qq msg_seq/startup behavior while adding group @message support with regression tests 2026-03-07 16:22:41 +00:00
Liwx
20bec3bc26
Update qq.py 2026-03-04 14:06:19 +08:00
Liwx
d0a48ed23c
Update qq.py 2026-03-04 14:00:40 +08:00
Re-bin
c34e1053f0 fix(qq): disable botpy file log to fix read-only filesystem error 2026-02-28 16:45:06 +00:00
zerone0x
cfe33ff7cd fix(qq): disable botpy file log to fix read-only filesystem error
When nanobot is run as a systemd service with ProtectSystem=strict,
the process cwd defaults to the read-only root filesystem (/). botpy's
default Client configuration includes a TimedRotatingFileHandler that
writes 'botpy.log' to os.getcwd(), which raises [Errno 30] Read-only
file system.

Pass ext_handlers=False when constructing the botpy Client subclass to
suppress the file handler. nanobot already routes all log output through
loguru, so botpy's file handler is redundant.

Fixes #1343

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-28 17:35:07 +01:00
GabrielWithTina
8842fb2b4d fix: pass msg_id in QQ C2C reply to avoid proactive message permission error
QQ's bot API requires a msg_id (original inbound message ID) to send a
passive reply. Without it the request is treated as a proactive message
and fails with error 40034102 (无权限). The message_id was already stored
in InboundMessage.metadata and forwarded to OutboundMessage, but was never
read in send().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 09:44:28 +08:00
Re-bin
b53c3d39ed fix(qq): remove dead _bot_task field and fix stop() to close client 2026-02-22 17:35:53 +00:00
andienguyen-ecoligo
8c55b40b9f fix(qq): make start() long-running per base channel contract
QQ channel's start() created a background task and returned immediately,
violating the base Channel contract which specifies start() should be
"a long-running async task". This caused the gateway to exit prematurely
when QQ was the only enabled channel.

Now directly awaits _run_bot() to stay alive like other channels.

Fixes #894
2026-02-21 12:38:24 -05:00
Re-bin
37252a4226 fix: complete loguru native formatting migration across all files 2026-02-20 07:55:34 +00:00
Nikolas de Hor
53b83a38e2 fix: use loguru native formatting to prevent KeyError on messages containing curly braces
Closes #857
2026-02-19 17:19:36 -03:00
zhengliyuan
cedde62201 Merge branch 'main' into feature/qq-groupmessage 2026-02-12 10:48:02 +08:00
zhengliyuan
039ab717fa update: Enable listening to both private and group messages. 2026-02-12 10:44:26 +08:00
Re-bin
b429bf9381 fix: improve long-running stability for various channels 2026-02-12 01:20:57 +00:00
Re-bin
4f928e9d2a feat: improve QQ channel setup guide and fix botpy intent flags 2026-02-09 16:17:35 +00:00
Re-bin
a63a44fa79 fix: align QQ channel with BaseChannel conventions, simplify implementation 2026-02-09 12:04:34 +00:00
yinwm
34dc933fce feat: add QQ channel integration with botpy SDK
Add official QQ platform support using botpy SDK with WebSocket connection.

Features:
- C2C (private message) support via QQ Open Platform
- WebSocket-based bot connection (no public IP required)
- Message deduplication with efficient deque-based LRU cache
- User whitelist support via allow_from configuration
- Clean async architecture using single event loop

Changes:
- Add QQChannel implementation in nanobot/channels/qq.py
- Add QQConfig schema with appId and secret fields
- Register QQ channel in ChannelManager
- Update README with QQ setup instructions
- Add qq-botpy dependency to pyproject.toml
- Add botpy.log to .gitignore

Setup:
1. Get AppID and Secret from q.qq.com
2. Configure in ~/.nanobot/config.json:
   {
     "channels": {
       "qq": {
         "enabled": true,
         "appId": "YOUR_APP_ID",
         "secret": "YOUR_APP_SECRET",
         "allowFrom": []
       }
     }
   }
3. Run: nanobot gateway

Note: Group chat support will be added in future updates.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-09 15:54:14 +08:00