From c57f5fd9f45a905901937fb9946f88a7c091ea96 Mon Sep 17 00:00:00 2001 From: minecraft1024a Date: Wed, 13 Aug 2025 19:21:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E4=B8=BAMaiBot=E6=B7=BB=E5=8A=A0=E4=BA=86?= =?UTF-8?q?=E8=B4=B4=E8=A1=A8=E6=83=85=E6=8F=92=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../built_in/set_emoji_like/__init__.py | 0 .../built_in/set_emoji_like/_manifest.json | 40 +++ src/plugins/built_in/set_emoji_like/plugin.py | 201 ++++++++++++++ .../built_in/set_emoji_like/qq_emoji_list.py | 252 ++++++++++++++++++ 4 files changed, 493 insertions(+) create mode 100644 src/plugins/built_in/set_emoji_like/__init__.py create mode 100644 src/plugins/built_in/set_emoji_like/_manifest.json create mode 100644 src/plugins/built_in/set_emoji_like/plugin.py create mode 100644 src/plugins/built_in/set_emoji_like/qq_emoji_list.py diff --git a/src/plugins/built_in/set_emoji_like/__init__.py b/src/plugins/built_in/set_emoji_like/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/plugins/built_in/set_emoji_like/_manifest.json b/src/plugins/built_in/set_emoji_like/_manifest.json new file mode 100644 index 000000000..938c31439 --- /dev/null +++ b/src/plugins/built_in/set_emoji_like/_manifest.json @@ -0,0 +1,40 @@ +{ + "manifest_version": 1, + "name": "消息表情回应 (Set Message Emoji Like)", + "version": "1.0.0", + "description": "通过大模型判断或指令,为特定的聊天消息设置QQ表情回应。需要NapCat服务支持。", + "author": { + "name": "yishan", + "url": "https://github.com/MaiM-with-u" + }, + "license": "GPL-v3.0-or-later", + + "host_application": { + "min_version": "0.8.0" + }, + "homepage_url": "https://github.com/MaiM-with-u/maibot", + "repository_url": "https://github.com/MaiM-with-u/maibot", + "keywords": ["emoji", "reaction", "like", "表情", "回应", "点赞"], + "categories": ["Chat", "Integration"], + + "default_locale": "zh-CN", + "locales_path": "_locales", + + "plugin_info": { + "is_built_in": false, + "plugin_type": "functional", + "components": [ + { + "type": "action", + "name": "set_emoji_like", + "description": "为消息设置表情回应" + } + ], + "features": [ + "通过LLM理解并为消息添加表情回应", + "支持通过表情名称或ID指定表情", + "需要连接到NapCat服务", + "可配置NapCat服务器地址" + ] + } +} diff --git a/src/plugins/built_in/set_emoji_like/plugin.py b/src/plugins/built_in/set_emoji_like/plugin.py new file mode 100644 index 000000000..0bb9e2ca2 --- /dev/null +++ b/src/plugins/built_in/set_emoji_like/plugin.py @@ -0,0 +1,201 @@ +import re +from typing import List, Tuple, Type +import difflib + +from src.plugin_system import ( + BasePlugin, + register_plugin, + BaseAction, + ComponentInfo, + ActionActivationType, + ConfigField, + database_api, +) +from src.common.database.sqlalchemy_models import Messages, PersonInfo +from src.common.logger import get_logger +from src.plugin_system.apis import send_api +from .qq_emoji_list import qq_face + +logger = get_logger("set_emoji_like_plugin") + + +def get_emoji_id(emoji_input: str) -> str | None: + """根据输入获取表情ID""" + # 如果输入本身就是数字ID,直接返回 + if emoji_input.isdigit() or (isinstance(emoji_input, str) and emoji_input.startswith("😊")): + if emoji_input in qq_face: + return emoji_input + + # 尝试从 "[表情:xxx]" 格式中提取 + match = re.search(r"\[表情:(.+?)\]", emoji_input) + if match: + emoji_name = match.group(1).strip() + else: + emoji_name = emoji_input.strip() + + # 遍历查找 + for key, value in qq_face.items(): + # value 的格式是 "[表情:xxx]" + if f"[表情:{emoji_name}]" == value: + return key + + return None + + +# ===== Action组件 ===== +class SetEmojiLikeAction(BaseAction): + """设置消息表情回应""" + + # === 基本信息(必须填写)=== + action_name = "set_emoji_like" + action_description = "为消息设置表情回应/贴表情" + activation_type = ActionActivationType.ALWAYS # 消息接收时激活(?) + parallel_action = True + + # === 功能描述(必须填写)=== + # 从 qq_face 字典中提取所有表情名称用于提示 + emoji_options = [] + for name in qq_face.values(): + match = re.search(r"\[表情:(.+?)\]", name) + if match: + emoji_options.append(match.group(1)) + + action_parameters = { + "emoji": f"要回应的表情,必须从以下表情中选择: {', '.join(emoji_options)}", + "set": "是否设置回应 (True/False)", + } + action_require = ["当需要对消息贴表情时使用","当你想回应某条消息但又不想发文字时使用","不要连续发送,如果你已经贴表情包,就不要选择此动作","当你想用贴表情回应某条消息时使用"] + llm_judge_prompt = """ + 判定是否需要使用贴表情动作的条件: + 1. 用户明确要求使用贴表情包 + 2. 这是一个适合表达强烈情绪的场合 + 3. 不要发送太多表情包,如果你已经发送过多个表情包则回答"否" + + 请回答"是"或"否"。 + """ + associated_types = ["text"] + + async def execute(self) -> Tuple[bool, str]: + """执行设置表情回应的动作""" + message_id = None + if self.has_action_message: + logger.debug(str(self.action_message)) + if isinstance(self.action_message, dict): + message_id = self.action_message.get("message_id") + logger.info(f"获取到的消息ID: {message_id}") + else: + logger.error("未提供消息ID") + await self.store_action_info( + action_build_into_prompt=True, + action_prompt_display=f"执行了set_emoji_like动作:{self.action_name},失败: 未提供消息ID", + action_done=False + ) + return False, "未提供消息ID" + + emoji_input = self.action_data.get("emoji") + set_like = self.action_data.get("set", True) + + if not emoji_input: + logger.error("未提供表情") + return False, "未提供表情" + logger.info(f"设置表情回应: {emoji_input}, 是否设置: {set_like}") + + emoji_id = get_emoji_id(emoji_input) + if not emoji_id: + logger.error(f"找不到表情: '{emoji_input}'。请从可用列表中选择。") + await self.store_action_info( + action_build_into_prompt=True, + action_prompt_display=f"执行了set_emoji_like动作:{self.action_name},失败: 找不到表情: '{emoji_input}'", + action_done=False + ) + return False, f"找不到表情: '{emoji_input}'。请从可用列表中选择。" + + # 4. 使用适配器API发送命令 + if not message_id: + logger.error("未提供消息ID") + await self.store_action_info( + action_build_into_prompt=True, + action_prompt_display=f"执行了set_emoji_like动作:{self.action_name},失败: 未提供消息ID", + action_done=False + ) + return False, "未提供消息ID" + + try: + # 使用适配器API发送贴表情命令 + response = await send_api.adapter_command_to_stream( + action="set_msg_emoji_like", + params={ + "message_id": message_id, + "emoji_id": emoji_id, + "set": set_like + }, + stream_id=self.chat_stream.stream_id if self.chat_stream else None, + timeout=30.0, + storage_message=False + ) + + if response["status"] == "ok": + logger.info(f"设置表情回应成功: {response}") + await self.store_action_info( + action_build_into_prompt=True, + action_prompt_display=f"执行了set_emoji_like动作,{emoji_input},设置表情回应: {emoji_id}, 是否设置: {set_like}", + action_done=True + ) + return True, f"成功设置表情回应: {response.get('message', '成功')}" + else: + error_msg = response.get('message', '未知错误') + logger.error(f"设置表情回应失败: {error_msg}") + await self.store_action_info( + action_build_into_prompt=True, + action_prompt_display=f"执行了set_emoji_like动作:{self.action_name},失败: {error_msg}", + action_done=False + ) + return False, f"设置表情回应失败: {error_msg}" + + except Exception as e: + logger.error(f"设置表情回应失败: {e}") + await self.store_action_info( + action_build_into_prompt=True, + action_prompt_display=f"执行了set_emoji_like动作:{self.action_name},失败: {e}", + action_done=False + ) + return False, f"设置表情回应失败: {e}" + + +# ===== 插件注册 ===== +@register_plugin +class SetEmojiLikePlugin(BasePlugin): + """设置消息表情回应插件""" + + # 插件基本信息 + plugin_name: str = "set_emoji_like" # 内部标识符 + enable_plugin: bool = True + dependencies: List[str] = [] # 插件依赖列表 + python_dependencies: List[str] = [] # Python包依赖列表,现在使用内置API + config_file_name: str = "config.toml" # 配置文件名 + + # 配置节描述 + config_section_descriptions = { + "plugin": "插件基本信息", + "components": "插件组件" + } + + # 配置Schema定义 + config_schema: dict = { + "plugin": { + "name": ConfigField(type=str, default="set_emoji_like", description="插件名称"), + "version": ConfigField(type=str, default="1.0.0", description="插件版本"), + "enabled": ConfigField(type=bool, default=True, description="是否启用插件"), + "config_version": ConfigField(type=str, default="1.1", description="配置版本"), + }, + "components": { + "action_set_emoji_like": ConfigField(type=bool, default=True, description="是否启用设置表情回应功能"), + } + } + + def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]: + if self.get_config("components.action_set_emoji_like"): + return [ + (SetEmojiLikeAction.get_action_info(), SetEmojiLikeAction), + ] + return [] diff --git a/src/plugins/built_in/set_emoji_like/qq_emoji_list.py b/src/plugins/built_in/set_emoji_like/qq_emoji_list.py new file mode 100644 index 000000000..3f4f22480 --- /dev/null +++ b/src/plugins/built_in/set_emoji_like/qq_emoji_list.py @@ -0,0 +1,252 @@ +qq_face: dict = { + "0": "[表情:惊讶]", + "1": "[表情:撇嘴]", + "2": "[表情:色]", + "3": "[表情:发呆]", + "4": "[表情:得意]", + "5": "[表情:流泪]", + "6": "[表情:害羞]", + "7": "[表情:闭嘴]", + "8": "[表情:睡]", + "9": "[表情:大哭]", + "10": "[表情:尴尬]", + "11": "[表情:发怒]", + "12": "[表情:调皮]", + "13": "[表情:呲牙]", + "14": "[表情:微笑]", + "15": "[表情:难过]", + "16": "[表情:酷]", + "18": "[表情:抓狂]", + "19": "[表情:吐]", + "20": "[表情:偷笑]", + "21": "[表情:可爱]", + "22": "[表情:白眼]", + "23": "[表情:傲慢]", + "24": "[表情:饥饿]", + "25": "[表情:困]", + "26": "[表情:惊恐]", + "27": "[表情:流汗]", + "28": "[表情:憨笑]", + "29": "[表情:悠闲]", + "30": "[表情:奋斗]", + "31": "[表情:咒骂]", + "32": "[表情:疑问]", + "33": "[表情: 嘘]", + "34": "[表情:晕]", + "35": "[表情:折磨]", + "36": "[表情:衰]", + "37": "[表情:骷髅]", + "38": "[表情:敲打]", + "39": "[表情:再见]", + "41": "[表情:发抖]", + "42": "[表情:爱情]", + "43": "[表情:跳跳]", + "46": "[表情:猪头]", + "49": "[表情:拥抱]", + "53": "[表情:蛋糕]", + "56": "[表情:刀]", + "59": "[表情:便便]", + "60": "[表情:咖啡]", + "63": "[表情:玫瑰]", + "64": "[表情:凋谢]", + "66": "[表情:爱心]", + "67": "[表情:心碎]", + "74": "[表情:太阳]", + "75": "[表情:月亮]", + "76": "[表情:赞]", + "77": "[表情:踩]", + "78": "[表情:握手]", + "79": "[表情:胜利]", + "85": "[表情:飞吻]", + "86": "[表情:怄火]", + "89": "[表情:西瓜]", + "96": "[表情:冷汗]", + "97": "[表情:擦汗]", + "98": "[表情:抠鼻]", + "99": "[表情:鼓掌]", + "100": "[表情:糗大了]", + "101": "[表情:坏笑]", + "102": "[表情:左哼哼]", + "103": "[表情:右哼哼]", + "104": "[表情:哈欠]", + "105": "[表情:鄙视]", + "106": "[表情:委屈]", + "107": "[表情:快哭了]", + "108": "[表情:阴险]", + "109": "[表情:左亲亲]", + "110": "[表情:吓]", + "111": "[表情:可怜]", + "112": "[表情:菜刀]", + "114": "[表情:篮球]", + "116": "[表情:示爱]", + "118": "[表情:抱拳]", + "119": "[表情:勾引]", + "120": "[表情:拳头]", + "121": "[表情:差劲]", + "123": "[表情:NO]", + "124": "[表情:OK]", + "125": "[表情:转圈]", + "129": "[表情:挥手]", + "137": "[表情:鞭炮]", + "144": "[表情:喝彩]", + "146": "[表情:爆筋]", + "147": "[表情:棒棒糖]", + "169": "[表情:手枪]", + "171": "[表情:茶]", + "172": "[表情:眨眼睛]", + "173": "[表情:泪奔]", + "174": "[表情:无奈]", + "175": "[表情:卖萌]", + "176": "[表情:小纠结]", + "177": "[表情:喷血]", + "178": "[表情:斜眼笑]", + "179": "[表情:doge]", + "181": "[表情:戳一戳]", + "182": "[表情:笑哭]", + "183": "[表情:我最美]", + "185": "[表情:羊驼]", + "187": "[表情:幽灵]", + "201": "[表情:点赞]", + "212": "[表情:托腮]", + "262": "[表情:脑阔疼]", + "263": "[表情:沧桑]", + "264": "[表情:捂脸]", + "265": "[表情:辣眼睛]", + "266": "[表情:哦哟]", + "267": "[表情:头秃]", + "268": "[表情:问号脸]", + "269": "[表情:暗中观察]", + "270": "[表情:emm]", + "271": "[表情:吃 瓜]", + "272": "[表情:呵呵哒]", + "273": "[表情:我酸了]", + "277": "[表情:汪汪]", + "281": "[表情:无眼笑]", + "282": "[表情:敬礼]", + "283": "[表情:狂笑]", + "284": "[表情:面无表情]", + "285": "[表情:摸鱼]", + "286": "[表情:魔鬼笑]", + "287": "[表情:哦]", + "289": "[表情:睁眼]", + "293": "[表情:摸锦鲤]", + "294": "[表情:期待]", + "295": "[表情:拿到红包]", + "297": "[表情:拜谢]", + "298": "[表情:元宝]", + "299": "[表情:牛啊]", + "300": "[表情:胖三斤]", + "302": "[表情:左拜年]", + "303": "[表情:右拜年]", + "305": "[表情:右亲亲]", + "306": "[表情:牛气冲天]", + "307": "[表情:喵喵]", + "311": "[表情:打call]", + "312": "[表情:变形]", + "314": "[表情:仔细分析]", + "317": "[表情:菜汪]", + "318": "[表情:崇拜]", + "319": "[表情: 比心]", + "320": "[表情:庆祝]", + "323": "[表情:嫌弃]", + "324": "[表情:吃糖]", + "325": "[表情:惊吓]", + "326": "[表情:生气]", + "332": "[表情:举牌牌]", + "333": "[表情:烟花]", + "334": "[表情:虎虎生威]", + "336": "[表情:豹富]", + "337": "[表情:花朵脸]", + "338": "[表情:我想开了]", + "339": "[表情:舔屏]", + "341": "[表情:打招呼]", + "342": "[表情:酸Q]", + "343": "[表情:我方了]", + "344": "[表情:大怨种]", + "345": "[表情:红包多多]", + "346": "[表情:你真棒棒]", + "347": "[表情:大展宏兔]", + "349": "[表情:坚强]", + "350": "[表情:贴贴]", + "351": "[表情:敲敲]", + "352": "[表情:咦]", + "353": "[表情:拜托]", + "354": "[表情:尊嘟假嘟]", + "355": "[表情:耶]", + "356": "[表情:666]", + "357": "[表情:裂开]", + "392": "[表情:龙年 快乐]", + "393": "[表情:新年中龙]", + "394": "[表情:新年大龙]", + "395": "[表情:略略略]", + "396": "[表情:龙年快乐]", + "424":" [表情:按钮]", + "😊": "[表情:嘿嘿]", + "😌": "[表情:羞涩]", + "😚": "[ 表情:亲亲]", + "😓": "[表情:汗]", + "😰": "[表情:紧张]", + "😝": "[表情:吐舌]", + "😁": "[表情:呲牙]", + "😜": "[表情:淘气]", + "☺": "[表情:可爱]", + "😍": "[表情:花痴]", + "😔": "[表情:失落]", + "😄": "[表情:高兴]", + "😏": "[表情:哼哼]", + "😒": "[表情:不屑]", + "😳": "[表情:瞪眼]", + "😘": "[表情:飞吻]", + "😭": "[表情:大哭]", + "😱": "[表情:害怕]", + "😂": "[表情:激动]", + "💪": "[表情:肌肉]", + "👊": "[表情:拳头]", + "👍": "[表情 :厉害]", + "👏": "[表情:鼓掌]", + "👎": "[表情:鄙视]", + "🙏": "[表情:合十]", + "👌": "[表情:好的]", + "👆": "[表情:向上]", + "👀": "[表情:眼睛]", + "🍜": "[表情:拉面]", + "🍧": "[表情:刨冰]", + "🍞": "[表情:面包]", + "🍺": "[表情:啤酒]", + "🍻": "[表情:干杯]", + "☕": "[表情:咖啡]", + "🍎": "[表情:苹果]", + "🍓": "[表情:草莓]", + "🍉": "[表情:西瓜]", + "🚬": "[表情:吸烟]", + "🌹": "[表情:玫瑰]", + "🎉": "[表情:庆祝]", + "💝": "[表情:礼物]", + "💣": "[表情:炸弹]", + "✨": "[表情:闪光]", + "💨": "[表情:吹气]", + "💦": "[表情:水]", + "🔥": "[表情:火]", + "💤": "[表情:睡觉]", + "💩": "[表情:便便]", + "💉": "[表情:打针]", + "📫": "[表情:邮箱]", + "🐎": "[表情:骑马]", + "👧": "[表情:女孩]", + "👦": "[表情:男孩]", + "🐵": "[表情:猴]", + "🐷": "[表情:猪]", + "🐮": "[表情:牛]", + "🐔": "[表情:公鸡]", + "🐸": "[表情:青蛙]", + "👻": "[表情:幽灵]", + "🐛": "[表情:虫]", + "🐶": "[表情:狗]", + "🐳": "[表情:鲸鱼]", + "👢": "[表情:靴子]", + "☀": "[表情:晴天]", + "❔": "[表情:问号]", + "🔫": "[表情:手枪]", + "💓": "[表情:爱 心]", + "🏪": "[表情:便利店]", +}