mirror of
https://github.com/countbot-ai/CountBot.git
synced 2026-09-14 20:46:47 +08:00
14d4d4e2a0
功能(核心): 会话级配置系统与前端体验全面升级 核心功能:- 会话级配置系统,轻松打造你的AI团队(每个会话独立 API、模型、提示词) - 新增微博、企业微信、小智 AI 三个 IM 渠道 - 实现企业微信、飞书两个IM渠道的消息流式输出 - 全面优化多智能体协作系统 - 全面优化项目代码兼容性 - 新增 /help 命令(通过 IM 渠道输入即可体验) - 集成 Mermaid 图表渲染引擎 - 全面兼容 OpenClaw Skills 技能生态 - Heartbeat 主动问候系统重构 - WEB聊天页面支持自定义用户头像 - 支持自定义输出语言,比如中文、英文、西语等 - 修复大量已知问题
225 lines
7.6 KiB
Python
225 lines
7.6 KiB
Python
"""Telegram 频道模块
|
||
|
||
使用 python-telegram-bot 库通过长轮询模式接收消息,无需 webhook 或公网 IP。
|
||
python-telegram-bot 内置自动重连机制,无需额外处理。
|
||
"""
|
||
|
||
import asyncio
|
||
from typing import Any, Dict
|
||
|
||
from loguru import logger
|
||
|
||
from backend.modules.channels.base import BaseChannel, OutboundMessage
|
||
|
||
|
||
class TelegramChannel(BaseChannel):
|
||
"""Telegram 频道
|
||
|
||
通过长轮询模式收发消息,支持私聊和群聊。
|
||
python-telegram-bot 内置连接管理和自动重连。
|
||
"""
|
||
|
||
name = "telegram"
|
||
|
||
def __init__(self, config: Any):
|
||
super().__init__(config)
|
||
self._app = None
|
||
self._chat_ids: Dict[str, int] = {}
|
||
|
||
# ------------------------------------------------------------------
|
||
# 生命周期
|
||
# ------------------------------------------------------------------
|
||
|
||
async def start(self) -> None:
|
||
"""启动 Telegram 机器人。"""
|
||
if not self.config.token:
|
||
logger.error("Telegram bot token not configured")
|
||
return
|
||
|
||
try:
|
||
from telegram import BotCommand, Update
|
||
from telegram.ext import (
|
||
Application,
|
||
CommandHandler,
|
||
MessageHandler,
|
||
filters,
|
||
ContextTypes,
|
||
)
|
||
except ImportError:
|
||
logger.error("python-telegram-bot not installed. Run: pip install python-telegram-bot")
|
||
return
|
||
|
||
self._running = True
|
||
|
||
builder = Application.builder().token(self.config.token)
|
||
if hasattr(self.config, "proxy") and self.config.proxy:
|
||
builder = builder.proxy(self.config.proxy).get_updates_proxy(self.config.proxy)
|
||
|
||
self._app = builder.build()
|
||
self._app.add_handler(CommandHandler("start", self._on_start))
|
||
self._app.add_handler(CommandHandler("help", self._on_help))
|
||
self._app.add_handler(
|
||
MessageHandler(filters.TEXT & ~filters.COMMAND, self._on_message)
|
||
)
|
||
|
||
logger.info("Starting Telegram bot (polling mode)...")
|
||
await self._app.initialize()
|
||
await self._app.start()
|
||
|
||
bot_info = await self._app.bot.get_me()
|
||
logger.info(f"Telegram bot @{bot_info.username} connected")
|
||
|
||
try:
|
||
commands = [
|
||
BotCommand("start", "Start the bot"),
|
||
BotCommand("help", "Show help message"),
|
||
]
|
||
await self._app.bot.set_my_commands(commands)
|
||
logger.debug("Bot commands registered")
|
||
except Exception as e:
|
||
logger.warning(f"Failed to register bot commands: {e}")
|
||
|
||
await self._app.updater.start_polling(
|
||
allowed_updates=["message"], drop_pending_updates=True
|
||
)
|
||
|
||
while self._running:
|
||
await asyncio.sleep(1)
|
||
|
||
async def stop(self) -> None:
|
||
"""停止 Telegram 机器人。"""
|
||
self._running = False
|
||
if self._app:
|
||
logger.info("Stopping Telegram bot...")
|
||
await self._app.updater.stop()
|
||
await self._app.stop()
|
||
await self._app.shutdown()
|
||
self._app = None
|
||
|
||
# ------------------------------------------------------------------
|
||
# 出站消息发送
|
||
# ------------------------------------------------------------------
|
||
|
||
async def send(self, msg: OutboundMessage) -> None:
|
||
"""发送消息到 Telegram,markdown 失败自动降级为纯文本。"""
|
||
if not self._app:
|
||
logger.warning("Telegram bot not running")
|
||
return
|
||
|
||
try:
|
||
chat_id = int(msg.chat_id)
|
||
await self._app.bot.send_message(
|
||
chat_id=chat_id, text=msg.content, parse_mode="Markdown"
|
||
)
|
||
except ValueError:
|
||
logger.error(f"Invalid chat_id: {msg.chat_id}")
|
||
except Exception as e:
|
||
logger.error(f"Error sending Telegram message: {e}")
|
||
try:
|
||
await self._app.bot.send_message(chat_id=int(msg.chat_id), text=msg.content)
|
||
except Exception as e2:
|
||
logger.error(f"Failed to send plain text: {e2}")
|
||
|
||
# ------------------------------------------------------------------
|
||
# 入站消息处理
|
||
# ------------------------------------------------------------------
|
||
|
||
async def _on_start(self, update, context) -> None:
|
||
"""处理 /start 命令。"""
|
||
if not update.message or not update.effective_user:
|
||
return
|
||
|
||
user = update.effective_user
|
||
await update.message.reply_text(
|
||
f"Hi {user.first_name}! I'm your AI assistant.\n\n"
|
||
"Send me a message and I'll respond!\n"
|
||
"Type /help to see available commands."
|
||
)
|
||
|
||
async def _on_help(self, update, context) -> None:
|
||
"""处理 /help 命令。"""
|
||
if not update.message:
|
||
return
|
||
|
||
await update.message.reply_text(
|
||
"*Available Commands*\n\n"
|
||
"/start — Start the bot\n"
|
||
"/help — Show this help message\n\n"
|
||
"Just send me a text message to chat!",
|
||
parse_mode="Markdown",
|
||
)
|
||
|
||
async def _on_message(self, update, context) -> None:
|
||
"""处理入站文本消息。"""
|
||
if not update.message or not update.effective_user:
|
||
return
|
||
|
||
message = update.message
|
||
user = update.effective_user
|
||
chat_id = message.chat_id
|
||
|
||
sender_id = str(user.id)
|
||
if user.username:
|
||
sender_id = f"{sender_id}|{user.username}"
|
||
|
||
self._chat_ids[sender_id] = chat_id
|
||
content = message.text or "[empty message]"
|
||
|
||
logger.debug(f"Telegram message from {sender_id}: {content[:50]}...")
|
||
|
||
await self._handle_message(
|
||
sender_id=sender_id,
|
||
chat_id=str(chat_id),
|
||
content=content,
|
||
metadata={
|
||
"message_id": message.message_id,
|
||
"user_id": user.id,
|
||
"username": user.username,
|
||
"first_name": user.first_name,
|
||
"is_group": message.chat.type != "private",
|
||
},
|
||
)
|
||
|
||
# ------------------------------------------------------------------
|
||
# 连接测试
|
||
# ------------------------------------------------------------------
|
||
|
||
async def test_connection(self) -> Dict[str, Any]:
|
||
"""测试 Telegram 连接(验证 bot token)。"""
|
||
if not self.config.token:
|
||
return {"success": False, "message": "Token not configured"}
|
||
|
||
try:
|
||
from telegram import Bot
|
||
from telegram.request import HTTPXRequest
|
||
|
||
kwargs = {}
|
||
if hasattr(self.config, "proxy") and self.config.proxy:
|
||
kwargs["request"] = HTTPXRequest(proxy=self.config.proxy)
|
||
|
||
bot = Bot(token=self.config.token, **kwargs)
|
||
bot_info = await bot.get_me()
|
||
await bot.close()
|
||
|
||
return {
|
||
"success": True,
|
||
"message": f"Connected to @{bot_info.username}",
|
||
"bot_info": {
|
||
"username": bot_info.username,
|
||
"first_name": bot_info.first_name,
|
||
"id": bot_info.id,
|
||
},
|
||
}
|
||
except ImportError:
|
||
return {"success": False, "message": "python-telegram-bot not installed"}
|
||
except Exception as e:
|
||
return {"success": False, "message": f"Connection failed: {e}"}
|
||
|
||
# ------------------------------------------------------------------
|
||
# 属性
|
||
# ------------------------------------------------------------------
|
||
|
||
@property
|
||
def display_name(self) -> str:
|
||
return "Telegram"
|