Files
Hermes-Skills/okx-auto-position/references/v4.5.114-2026-09-24-tg-forwarder-chat-id-extract-bug-and-channel-swap.md

94 lines
5.4 KiB
Markdown

# v4.5.114 (2026-09-24) — TG Forwarder chat_id 提取 bug + 频道切换 (shipan_jiankong)
## 事故背景
用户问"现在用的是哪个频道 @shipan_jiankong 还是 @jiankongshipan"——切到新频道后,
发现**新频道有信号(通过 telethon 直接读 shipan_jiankong 最近10条消息,9/23 11:39 ~ 9/24 13:48 有真实交易信号)**,但 forwarder 转发器**完全没转发**。
## 根因 (双层 bug 叠加)
### Bug 1: chat_id 提取错 (`/app/message_listener.py` line 76)
```python
chat = await event.get_chat()
chat_id = abs(chat.id) # ❌ 错误: 拿掉 -100 前缀
```
- `event.chat.id` (Channel 对象) = **正数** `2651333064`
- `event.chat_id` (telethon 完整 channel id) = **`-1002651333064`**
- DB 存的 `telegram_chat_id` 字段 = `'-1002651333064'` (字符串)
`abs(chat.id)` = `2651333064` → 查询 `Chat.telegram_chat_id == '2651333064'` → **永远不匹配** DB 里的 `'-1002651333064'`。
后续 line 80 的 `int(f"100{chat_id}")` = `1002651333064` 也仍然不对,应是 `-1002651333064`。
但**那段代码只在 `state_manager.check_state()` 为真时触发** (用户主动操作),正常转发流程不走那段,所以更靠前的 `abs()` 才是真正转发问题的根因。
### Bug 2: 频道切换生效但漏了修复
2026-09-24 把 forward_rule source 从 chats.id=1 (jiankongshipan) 切到 chats.id=4 (shipan_jiankong)。
切换前: source_chat_id=1, channel id=3805472665 (个人号? 不是 -100 前缀), `abs(chat.id)=3805472665` 居然能匹配 → 之前能跑是因为旧频道不是 -100 格式频道。
切换后: source_chat_id=4, channel id=-1002651333064 (新格式), `abs(chat.id)=2651333064` 不匹配 → 立即暴露 Bug 1。
## 修复
```python
# message_listener.py line 75-76 (user handler + bot handler 两处)
old:
chat = await event.get_chat()
chat_id = abs(chat.id)
new:
chat = await event.get_chat()
# v4.5.114: 用 event.chat_id (完整 TG channel id -100xxxxxxxx) 而不是 abs(chat.id) (剥掉了-100前缀)
chat_id = event.chat_id
```
patch 后必须 `docker restart telegram-forwarder`。
## 双层防御 (也改 Chat 查询兼容性)
即便修了 chat_id 提取,DB 查询兼容性再加一层 — 同时匹配:
```python
_chat_id_str = str(chat_id)
if abs(chat_id) > 1e10 and not _chat_id_str.startswith('-100'):
_alt_id_str = f'-100{abs(chat_id) - 1e12}' if abs(chat_id) > 1e12 else f'-100{_chat_id_str}'
else:
_alt_id_str = f'100{_chat_id_str.lstrip("-")}'
source_chat = session.query(Chat).filter(
Chat.telegram_chat_id.in_([_chat_id_str, _alt_id_str, f'-100{_chat_id_str.lstrip("100")}'])
).first()
```
防御性编程: 即使将来又有人改 chat_id 提取方式,query 也能匹配。
## 诊断流程 (可复用)
当用户说"信号没转发"时:
1. `docker logs telegram-forwarder --tail 100` → 看有无 [处理规则|新消息|转发|命中黑名单]
3. 若只有 `Got difference for channel XXX updates` (telethon) 但**没有 [新消息|处理规则]** 日志:
→ channel 已收到 update 但 message handler **没匹配 source chat**
4. 验证频道真有消息: `cp /app/sessions/user.session /tmp/u.session` 然后 telethon 直连 + `iter_messages('shipan_jiankong', limit=10)`
5. 验证 DB 配置: `docker exec telegram-forwarder python3 -c "import sqlite3; conn=sqlite3.connect('/app/db/forward.db'); ..."` 读 chats/forward_rules/keywords 表
6. 验证 chat_id 提取: 容器内 python 跑 telethon `event.chat.id` vs `event.chat_id`
## 容器内诊断关键
- 容器内无 sqlite3 CLI, 用 `python3 -c "import sqlite3; ..."` 读 forward.db
- session 文件 `main.session/user.session/bot.session` 被主进程锁 → 必须 `cp` 到 `/tmp/xx.session` 再 `TelegramClient('/tmp/xx', ...)` 连接
- 容器内 telethon 连 TG **必带** `proxy=('socks5','127.0.0.1',7891), request_retries=1` + 短 timeout (15s)
- 容器内 python 默认没装 ccxt/socks 模块,不能直接 pip install(共享 docker venv)
## 双频道最终状态 (2026-09-24)
| chats.id | telegram_chat_id | 名称 | 当前用途 |
|---|---|---|---|
| **4** | -1002651333064 | **币圈实盘监控 \| 熬鹰 风寻** | ✅ **source**, 当前转发 |
| 2 | -1003966251111 | 交易信号(Telegram消息转发) | ✅ target, 你给我发消息的群 |
| 1 | 3805472665 | 实盘监控 (jiankongshipan) | ❌ 停用,记录保留 |
| 3 | 656685714 | Mike 黑六网 | ❌ 不参与 |
forward_rule id=1: source=4 → target=2, enable=1, mode=WHITELIST_THEN_BLACKLIST, delay=5s。
## 用户偏好 (本session)
> "换到新频道,其他的配置不动。"
→ 切换 = 只改 forward_rule.source_chat_id, 不要碰白名单/黑名单/关键词/transit delay 配置。
## 相关
- v4.5.113 — classify 严格模式(同 session 部署)
- v4.5.111 — 1% min 减仓过滤 (同 session 部署)
- v4.5.110 — ZAMA 蚂蚁减仓双层过滤
- v4.5.83 — 4 熬鹰号白名单
- v4.5.77 — close 也闸
## 教训
- **通道型 bug 必须端到端测**: 只改 forward.db 的 source_chat_id 不够, 必须用 `docker logs` 看 [新消息|处理规则] 日志确认触发。如果没 log,说明 message_listener 在更早的阶段就 return 了(通常是 chat_id 提取错或 source chat 查不到)。
- **session 主进程锁的处理模式可复用**: 容器内任何 `*.session` 文件被主转发器占着, 必须 `cp` 出来再用 telethon 直连查实体。
- **telethon `event.chat_id` vs `event.chat.id`**: 前者是 TG 完整 ID(含 -100 前缀),后者是 Channel 对象的内部 ID(已剥前缀)。**Channel 类型用前者**。