feat: 支持在 Telegram 和飞书下请求 LLM 前预表态功能 (#2737)
* ✨ feat(platform): 为 Telegram 和飞书添加消息表情回应功能 支持在收到命令时自动添加表情回应,提升用户交互体验 新增平台特异配置项,允许自定义启用状态和表情列表 * Update astrbot/core/platform/astr_message_event.py Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * style: ruff format * fix: 优化平台特异配置的预回应表情处理逻辑 --------- Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Co-authored-by: Soulter <905617992@qq.com>
This commit is contained in:
@@ -116,6 +116,15 @@ DEFAULT_CONFIG = {
|
||||
"port": 6185,
|
||||
},
|
||||
"platform": [],
|
||||
"platform_specific": {
|
||||
# 平台特异配置:按平台分类,平台下按功能分组
|
||||
"lark": {
|
||||
"pre_ack_emoji": {"enable": False, "emojis": ["Typing"]},
|
||||
},
|
||||
"telegram": {
|
||||
"pre_ack_emoji": {"enable": False, "emojis": ["✍️"]},
|
||||
},
|
||||
},
|
||||
"wake_prefix": ["/"],
|
||||
"log_level": "INFO",
|
||||
"pip_install_arg": "",
|
||||
@@ -2295,6 +2304,32 @@ CONFIG_METADATA_3 = {
|
||||
"description": "用户权限不足时是否回复",
|
||||
"type": "bool",
|
||||
},
|
||||
"platform_specific.lark.pre_ack_emoji.enable": {
|
||||
"description": "[飞书] 启用预回应表情",
|
||||
"type": "bool",
|
||||
},
|
||||
"platform_specific.lark.pre_ack_emoji.emojis": {
|
||||
"description": "表情列表(飞书表情枚举名)",
|
||||
"type": "list",
|
||||
"items": {"type": "string"},
|
||||
"hint": "表情枚举名参考:https://open.feishu.cn/document/server-docs/im-v1/message-reaction/emojis-introduce",
|
||||
"condition": {
|
||||
"platform_specific.lark.pre_ack_emoji.enable": True,
|
||||
},
|
||||
},
|
||||
"platform_specific.telegram.pre_ack_emoji.enable": {
|
||||
"description": "[Telegram] 启用预回应表情",
|
||||
"type": "bool",
|
||||
},
|
||||
"platform_specific.telegram.pre_ack_emoji.emojis": {
|
||||
"description": "表情列表(Unicode)",
|
||||
"type": "list",
|
||||
"items": {"type": "string"},
|
||||
"hint": "Telegram 仅支持固定反应集合,参考:https://gist.github.com/Soulter/3f22c8e5f9c7e152e967e8bc28c97fc9",
|
||||
"condition": {
|
||||
"platform_specific.telegram.pre_ack_emoji.enable": True,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import traceback
|
||||
import asyncio
|
||||
import random
|
||||
from typing import Union, AsyncGenerator
|
||||
from ..stage import Stage, register_stage
|
||||
from ..context import PipelineContext
|
||||
@@ -22,6 +23,26 @@ class PreProcessStage(Stage):
|
||||
self, event: AstrMessageEvent
|
||||
) -> Union[None, AsyncGenerator[None, None]]:
|
||||
"""在处理事件之前的预处理"""
|
||||
# 平台特异配置:platform_specific.<platform>.pre_ack_emoji
|
||||
supported = {"telegram", "lark"}
|
||||
platform = event.get_platform_name()
|
||||
cfg = (
|
||||
self.config.get("platform_specific", {})
|
||||
.get(platform, {})
|
||||
.get("pre_ack_emoji", {})
|
||||
) or {}
|
||||
emojis = cfg.get("emojis") or []
|
||||
if (
|
||||
cfg.get("enable", False)
|
||||
and platform in supported
|
||||
and emojis
|
||||
and event.is_at_or_wake_command
|
||||
):
|
||||
try:
|
||||
await event.react(random.choice(emojis))
|
||||
except Exception as e:
|
||||
logger.warning(f"{platform} 预回应表情发送失败: {e}")
|
||||
|
||||
# 路径映射
|
||||
if mappings := self.platform_settings.get("path_mapping", []):
|
||||
# 支持 Record,Image 消息段的路径映射。
|
||||
|
||||
@@ -416,6 +416,16 @@ class AstrMessageEvent(abc.ABC):
|
||||
)
|
||||
self._has_send_oper = True
|
||||
|
||||
async def react(self, emoji: str):
|
||||
"""
|
||||
对消息添加表情回应。
|
||||
|
||||
默认实现为发送一条包含该表情的消息。
|
||||
注意:此实现并不一定符合所有平台的原生“表情回应”行为。
|
||||
如需支持平台原生的消息反应功能,请在对应平台的子类中重写本方法。
|
||||
"""
|
||||
await self.send(MessageChain([Plain(emoji)]))
|
||||
|
||||
async def get_group(self, group_id: str = None, **kwargs) -> Optional[Group]:
|
||||
"""获取一个群聊的数据, 如果不填写 group_id: 如果是私聊消息,返回 None。如果是群聊消息,返回当前群聊的数据。
|
||||
|
||||
|
||||
@@ -107,6 +107,22 @@ class LarkMessageEvent(AstrMessageEvent):
|
||||
|
||||
await super().send(message)
|
||||
|
||||
async def react(self, emoji: str):
|
||||
request = (
|
||||
CreateMessageReactionRequest.builder()
|
||||
.message_id(self.message_obj.message_id)
|
||||
.request_body(
|
||||
CreateMessageReactionRequestBody.builder()
|
||||
.reaction_type(Emoji.builder().emoji_type(emoji).build())
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
)
|
||||
response = await self.bot.im.v1.message_reaction.acreate(request)
|
||||
if not response.success():
|
||||
logger.error(f"发送飞书表情回应失败({response.code}): {response.msg}")
|
||||
return None
|
||||
|
||||
async def send_streaming(self, generator, use_fallback: bool = False):
|
||||
buffer = None
|
||||
async for chain in generator:
|
||||
|
||||
@@ -16,6 +16,7 @@ from telegram.ext import ExtBot
|
||||
from astrbot.core.utils.io import download_file
|
||||
from astrbot import logger
|
||||
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
|
||||
from telegram import ReactionTypeEmoji, ReactionTypeCustomEmoji
|
||||
|
||||
|
||||
class TelegramPlatformEvent(AstrMessageEvent):
|
||||
@@ -135,6 +136,39 @@ class TelegramPlatformEvent(AstrMessageEvent):
|
||||
await self.send_with_client(self.client, message, self.get_sender_id())
|
||||
await super().send(message)
|
||||
|
||||
async def react(self, emoji: str | None, big: bool = False):
|
||||
"""
|
||||
给原消息添加 Telegram 反应:
|
||||
- 普通 emoji:传入 '👍'、'😂' 等
|
||||
- 自定义表情:传入其 custom_emoji_id(纯数字字符串)
|
||||
- 取消本机器人的反应:传入 None 或空字符串
|
||||
"""
|
||||
try:
|
||||
# 解析 chat_id(去掉超级群的 "#<thread_id>" 片段)
|
||||
if self.get_message_type() == MessageType.GROUP_MESSAGE:
|
||||
chat_id = (self.message_obj.group_id or "").split("#")[0]
|
||||
else:
|
||||
chat_id = self.get_sender_id()
|
||||
|
||||
message_id = int(self.message_obj.message_id)
|
||||
|
||||
# 组装 reaction 参数(必须是 ReactionType 的列表)
|
||||
if not emoji: # 清空本 bot 的反应
|
||||
reaction_param = [] # 空列表表示移除本 bot 的反应
|
||||
elif emoji.isdigit(): # 自定义表情:传 custom_emoji_id
|
||||
reaction_param = [ReactionTypeCustomEmoji(emoji)]
|
||||
else: # 普通 emoji
|
||||
reaction_param = [ReactionTypeEmoji(emoji)]
|
||||
|
||||
await self.client.set_message_reaction(
|
||||
chat_id=chat_id,
|
||||
message_id=message_id,
|
||||
reaction=reaction_param, # 注意是列表
|
||||
is_big=big, # 可选:大动画
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"[Telegram] 添加反应失败: {e}")
|
||||
|
||||
async def send_streaming(self, generator, use_fallback: bool = False):
|
||||
message_thread_id = None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user