Move channel-specific login logic from CLI into each channel class via a
new `login(force=False)` method on BaseChannel. The `channels login <name>`
command now dynamically loads the channel and calls its login() method.
- WeixinChannel.login(): calls existing _qr_login(), with force to clear saved token
- WhatsAppChannel.login(): sets up bridge and spawns npm process for QR login
- CLI no longer contains duplicate login logic per channel
- Update CHANNEL_PLUGIN_GUIDE to document the login() hook
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When allow_from is not configured, block all access by default
instead of allowing everyone. This prevents unauthorized access
when channels are enabled without explicit allow lists.
- Remove trailing whitespace and normalize blank lines
- Unify string quotes and line breaks for long lines
- Sort imports alphabetically across modules
Each Slack thread now gets its own conversation session instead of
sharing one session per channel. DM sessions are unchanged.
Added as a generic feature to also support if Feishu threads support
is added in the future.
Follow-up to #864. Three f-string logger calls in base.py and dingtalk.py
were missed in the original sweep. These can cause KeyError if interpolated
values contain curly braces, since loguru interprets them as format placeholders.
- Update sender ID construction to prioritize user ID while maintaining username for allowlist compatibility.
- Improve allowlist checking in BaseChannel to support sender IDs with multiple parts separated by '|'.