diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index 700a345ccc..770b5a20b6 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -455,6 +455,7 @@ "telegram_command_auto_refresh": True, "telegram_command_register_interval": 300, "telegram_polling_restart_delay": 5.0, + "telegram_reply_to_message": "off", }, "Discord": { "id": "discord", @@ -774,6 +775,13 @@ "type": "float", "hint": "当轮询意外结束尝试自动重启时的延迟时间,理论上越短恢复越快,但过短(<0.1s)可能导致死循环针对 API 服务器的请求阻断。单位为秒。默认为 5s。", }, + "telegram_reply_to_message": { + "description": "Telegram 回复时引用消息", + "type": "string", + "options": ["off", "private", "group", "all"], + "labels": ["关闭", "仅私聊", "仅群聊", "私聊和群聊"], + "hint": "机器人回复时是否引用(reply to)触发该回复的原消息。off:关闭;private:仅私聊;group:仅群聊;all:私聊和群聊都引用。默认 off。", + }, "id": { "description": "机器人名称", "type": "string", diff --git a/astrbot/core/platform/sources/telegram/tg_adapter.py b/astrbot/core/platform/sources/telegram/tg_adapter.py index 8e6722ccff..0e1d84698a 100644 --- a/astrbot/core/platform/sources/telegram/tg_adapter.py +++ b/astrbot/core/platform/sources/telegram/tg_adapter.py @@ -79,6 +79,8 @@ def __init__( True, ) self.last_command_hash = None + # 机器人回复时引用原消息的范围:off / private / group / all + self.reply_to_message = self.config.get("telegram_reply_to_message", "off") self.scheduler = AsyncIOScheduler() self.scheduler.add_listener( @@ -484,9 +486,17 @@ def _apply_caption() -> None: if not _from_user: logger.warning("[Telegram] Received a message without a from_user.") return None + # 同时读取显示名称和用户名,都不存在时回退为 "Unknown" + display_name = " ".join( + part for part in (_from_user.first_name, _from_user.last_name) if part + ).strip() + if display_name and _from_user.username: + nickname = f"{display_name} (@{_from_user.username})" + else: + nickname = display_name or _from_user.username or "Unknown" message.sender = MessageMember( str(_from_user.id), - _from_user.username or "Unknown", + nickname, ) message.self_id = str(context.bot.username) message.raw_message = update @@ -751,6 +761,7 @@ def create_event(self, message: AstrBotMessage) -> TelegramPlatformEvent: platform_meta=self.meta(), session_id=message.session_id, client=self.client, + reply_to_message=self.reply_to_message, ) async def handle_msg(self, message: AstrBotMessage) -> None: diff --git a/astrbot/core/platform/sources/telegram/tg_event.py b/astrbot/core/platform/sources/telegram/tg_event.py index 8445a8ea1e..1ae3c855c3 100644 --- a/astrbot/core/platform/sources/telegram/tg_event.py +++ b/astrbot/core/platform/sources/telegram/tg_event.py @@ -76,9 +76,12 @@ def __init__( platform_meta: PlatformMetadata, session_id: str, client: ExtBot, + reply_to_message: str = "off", ) -> None: super().__init__(message_str, message_obj, platform_meta, session_id) self.client = client + # 机器人回复时引用原消息的范围:off / private / group / all + self.reply_to_message = reply_to_message @classmethod def _split_message(cls, text: str) -> list[str]: @@ -271,6 +274,7 @@ async def send_with_client( client: ExtBot, message: MessageChain, user_name: str, + default_reply_to_message_id: str | None = None, ) -> None: image_path = None @@ -284,6 +288,11 @@ async def send_with_client( if isinstance(i, At): at_user_id = i.name + # 消息链未显式包含引用时,按配置默认引用触发该回复的原消息 + if not has_reply and default_reply_to_message_id: + has_reply = True + reply_message_id = default_reply_to_message_id + at_flag = False message_thread_id = None if "#" in user_name: @@ -341,10 +350,32 @@ async def send_with_client( ) async def send(self, message: MessageChain) -> None: - if self.get_message_type() == MessageType.GROUP_MESSAGE: - await self.send_with_client(self.client, message, self.message_obj.group_id) + is_group = self.get_message_type() == MessageType.GROUP_MESSAGE + # 根据配置范围决定是否默认引用触发该回复的原消息。 + # 当配置为 off 时,清除消息链中已有的 Reply 组件(来自全局管道 + # 「回复时引用发送人消息」等设置),确保 Telegram 平台完全不引用。 + should_reply = self.reply_to_message == "all" or self.reply_to_message == ( + "group" if is_group else "private" + ) + if not should_reply: + message.chain = [c for c in message.chain if not isinstance(c, Reply)] + default_reply_to_message_id = ( + self.message_obj.message_id if should_reply else None + ) + if is_group: + await self.send_with_client( + self.client, + message, + self.message_obj.group_id, + default_reply_to_message_id, + ) else: - await self.send_with_client(self.client, message, self.get_sender_id()) + await self.send_with_client( + self.client, + message, + self.get_sender_id(), + default_reply_to_message_id, + ) await super().send(message) async def react(self, emoji: str | None, big: bool = False) -> None: diff --git a/dashboard/src/i18n/locales/en-US/features/config-metadata.json b/dashboard/src/i18n/locales/en-US/features/config-metadata.json index 27c24af179..2d6bac0e36 100644 --- a/dashboard/src/i18n/locales/en-US/features/config-metadata.json +++ b/dashboard/src/i18n/locales/en-US/features/config-metadata.json @@ -600,6 +600,16 @@ "description": "Telegram Polling Restart Delay", "hint": "Waiting time in seconds when the polling loop needs to restart after unexpected exits. Defaults to 5s." }, + "telegram_reply_to_message": { + "description": "Telegram Reply To Message", + "labels": [ + "Disabled", + "Direct messages only", + "Group chats only", + "Direct and group chats" + ], + "hint": "Whether the bot quotes (replies to) the message that triggered its response. off: disabled; private: direct messages only; group: group chats only; all: both direct messages and group chats. Defaults to off." + }, "telegram_token": { "description": "Bot Token", "hint": "If you are in mainland China, set a proxy or change api_base in Other Settings." diff --git a/dashboard/src/i18n/locales/ru-RU/features/config-metadata.json b/dashboard/src/i18n/locales/ru-RU/features/config-metadata.json index 2c59e76261..cb16b7306a 100644 --- a/dashboard/src/i18n/locales/ru-RU/features/config-metadata.json +++ b/dashboard/src/i18n/locales/ru-RU/features/config-metadata.json @@ -596,6 +596,16 @@ "description": "Интервал автообновления команд Telegram", "hint": "Интервал автоматического обновления команд Telegram в секундах." }, + "telegram_reply_to_message": { + "description": "Цитировать сообщение при ответе в Telegram", + "labels": [ + "Отключено", + "Только личные сообщения", + "Только групповые чаты", + "Личные и групповые чаты" + ], + "hint": "Цитирует ли бот (отвечает на) сообщение, вызвавшее его ответ. off: отключено; private: только личные сообщения; group: только групповые чаты; all: и личные сообщения, и групповые чаты. По умолчанию off." + }, "telegram_token": { "description": "Токен бота", "hint": "Если вы находитесь в материковом Китае, установите прокси или измените api_base в разделе «Другие настройки»." diff --git a/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json b/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json index ca7fa48f7f..82c10670b3 100644 --- a/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json +++ b/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json @@ -602,6 +602,16 @@ "description": "Telegram 轮询重启延迟", "hint": "当轮询意外结束尝试自动重启时的延迟时间,单位为秒。默认为 5s。" }, + "telegram_reply_to_message": { + "description": "Telegram 回复时引用消息", + "labels": [ + "关闭", + "仅私聊", + "仅群聊", + "私聊和群聊" + ], + "hint": "机器人回复时是否引用触发该回复的原消息。off:关闭;private:仅私聊;group:仅群聊;all:私聊和群聊都引用。默认 off。" + }, "telegram_token": { "description": "Bot Token", "hint": "如果你的网络环境为中国大陆,请在 `其他配置` 处设置代理或更改 api_base。"