From 38e503c7f02a8de3a72af4712690866bd1f2a9ad Mon Sep 17 00:00:00 2001 From: inferzhang Date: Thu, 27 Nov 2025 15:15:20 +0800 Subject: [PATCH] =?UTF-8?q?feat(platform):=20=E6=96=B0=E5=A2=9E=E4=BC=81?= =?UTF-8?q?=E4=B8=9A=E5=BE=AE=E4=BF=A1=E6=B6=88=E6=81=AF=E6=8E=A8=E9=80=81?= =?UTF-8?q?=E6=9C=BA=E5=99=A8=E4=BA=BA=EF=BC=88=E5=8E=9F=E7=BE=A4=E6=9C=BA?= =?UTF-8?q?=E5=99=A8=E4=BA=BA=EF=BC=89=E9=80=82=E9=85=8D=E5=99=A8=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/config/default.py | 14 ++ astrbot/core/platform/manager.py | 4 + .../sources/wecom_group_bot/__init__.py | 5 + .../wecom_group_bot_adapter.py | 223 ++++++++++++++++++ .../wecom_group_bot/wecom_group_bot_client.py | 76 ++++++ .../wecom_group_bot/wecom_group_bot_event.py | 89 +++++++ .../wecom_group_bot/wecom_group_bot_parser.py | 92 ++++++++ .../wecom_group_bot/wecom_group_bot_server.py | 131 ++++++++++ dashboard/src/utils/platformUtils.js | 2 +- 9 files changed, 635 insertions(+), 1 deletion(-) create mode 100644 astrbot/core/platform/sources/wecom_group_bot/__init__.py create mode 100644 astrbot/core/platform/sources/wecom_group_bot/wecom_group_bot_adapter.py create mode 100644 astrbot/core/platform/sources/wecom_group_bot/wecom_group_bot_client.py create mode 100644 astrbot/core/platform/sources/wecom_group_bot/wecom_group_bot_event.py create mode 100644 astrbot/core/platform/sources/wecom_group_bot/wecom_group_bot_parser.py create mode 100644 astrbot/core/platform/sources/wecom_group_bot/wecom_group_bot_server.py diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index 5a23a8b6a..2d002d306 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -228,6 +228,20 @@ "callback_server_host": "0.0.0.0", "port": 6198, }, + "企业微信消息推送机器人(原群机器人)": { + "id": "wecom_group_bot", + "type": "wecom_group_bot", + "enable": False, + "token": "", + "encoding_aes_key": "", + "receive_id": "", + "callback_server_host": "0.0.0.0", + "port": 6200, + "callback_path": "/webhook/wecom-group-bot", + "callback_format": "xml", + "wecomaibot_init_respond_text": "💭 思考中...", + "wecomaibot_friend_message_welcome_text": "", + }, "飞书(Lark)": { "id": "lark", "type": "lark", diff --git a/astrbot/core/platform/manager.py b/astrbot/core/platform/manager.py index 9ff892025..c3529952d 100644 --- a/astrbot/core/platform/manager.py +++ b/astrbot/core/platform/manager.py @@ -87,6 +87,10 @@ async def load_platform(self, platform_config: dict): from .sources.wecom_ai_bot.wecomai_adapter import ( WecomAIBotAdapter, # noqa: F401 ) + case "wecom_group_bot": + from .sources.wecom_group_bot.wecom_group_bot_adapter import ( + WecomGroupBotAdapter, # noqa: F401 + ) case "weixin_official_account": from .sources.weixin_official_account.weixin_offacc_adapter import ( WeixinOfficialAccountPlatformAdapter, # noqa: F401 diff --git a/astrbot/core/platform/sources/wecom_group_bot/__init__.py b/astrbot/core/platform/sources/wecom_group_bot/__init__.py new file mode 100644 index 000000000..f9534bbbb --- /dev/null +++ b/astrbot/core/platform/sources/wecom_group_bot/__init__.py @@ -0,0 +1,5 @@ +"""WeCom 群机器人适配模块""" + +from .wecom_group_bot_adapter import WecomGroupBotAdapter + +__all__ = ["WecomGroupBotAdapter"] diff --git a/astrbot/core/platform/sources/wecom_group_bot/wecom_group_bot_adapter.py b/astrbot/core/platform/sources/wecom_group_bot/wecom_group_bot_adapter.py new file mode 100644 index 000000000..2b74d25de --- /dev/null +++ b/astrbot/core/platform/sources/wecom_group_bot/wecom_group_bot_adapter.py @@ -0,0 +1,223 @@ +"""企业微信消息推送机器人(原群机器人)适配器""" + +from __future__ import annotations + +import asyncio +import time +import uuid +from typing import Any + +from astrbot.api import logger +from astrbot.api.event import MessageChain +from astrbot.api.message_components import Image, Plain +from astrbot.api.platform import ( + AstrBotMessage, + MessageMember, + MessageType, + Platform, + PlatformMetadata, +) +from astrbot.core.platform.astr_message_event import MessageSesion + +from ...register import register_platform_adapter +from .wecom_group_bot_client import WecomGroupBotClient +from .wecom_group_bot_event import WecomGroupBotEvent +from .wecom_group_bot_parser import WecomGroupBotParser +from .wecom_group_bot_server import WecomGroupBotServer + + +@register_platform_adapter( + "wecom_group_bot", + "企业微信消息推送机器人(原群机器人)适配器", + default_config_tmpl={ + "token": "your_token", + "encoding_aes_key": "your_encoding_aes_key", + "port": 6200, + "callback_server_host": "0.0.0.0", + "callback_path": "/webhook/wecom-group-bot", + "callback_format": "xml", + "receive_id": "", + "wecomaibot_init_respond_text": "💭 思考中...", + "wecomaibot_friend_message_welcome_text": "", + }, +) +class WecomGroupBotAdapter(Platform): + """将企业微信消息推送机器人接入 AstrBot""" + + def __init__(self, platform_config: dict, platform_settings: dict, event_queue: asyncio.Queue) -> None: + super().__init__(event_queue) + self.config = platform_config + self.settings = platform_settings + + self.token = self.config["token"] + self.encoding_aes_key = self.config["encoding_aes_key"] + self.host = self.config.get("callback_server_host", "0.0.0.0") + self.port = int(self.config.get("port", 0) or 0) + self.callback_path = self.config.get("callback_path", "/webhook/wecom-group-bot") + self.receive_id = self.config.get("receive_id", "") + self.callback_format = self.config.get("callback_format", "xml") + self.initial_respond_text = self.config.get( + "wecomaibot_init_respond_text", + "💭 思考中...", + ) + self.friend_message_welcome_text = self.config.get( + "wecomaibot_friend_message_welcome_text", + "", + ) + + self.metadata = PlatformMetadata( + name="wecom_group_bot", + description="企业微信消息推送机器人(原群机器人)适配器", + id=self.config.get("id", "wecom_group_bot"), + ) + + self.parser = WecomGroupBotParser(self.callback_format) + self.client = WecomGroupBotClient() + self.server = WecomGroupBotServer( + host=self.host, + port=self.port, + token=self.token, + encoding_aes_key=self.encoding_aes_key, + receive_id=self.receive_id, + parser=self.parser, + message_handler=self._handle_incoming_message, + callback_path=self.callback_path, + ) + + async def _handle_incoming_message(self, message_data: dict[str, Any], metadata: dict[str, str]): + if not message_data: + logger.warning("收到空的企业微信消息推送机器人(原群机器人)消息,忽略") + return + + await self._maybe_send_auto_reply(message_data) + + abm = await self.convert_message(message_data, metadata) + if abm: + await self.handle_msg(abm) + + async def convert_message(self, payload: dict[str, Any], metadata: dict[str, str]) -> AstrBotMessage | None: + msgtype = str(payload.get("msgtype") or payload.get("msg_type") or "").lower() + if not msgtype: + logger.warning("无法识别的企业微信消息推送机器人(原群机器人)消息: %s", payload) + return None + + sender_data = payload.get("from", {}) or {} + user_id = sender_data.get("userid") or sender_data.get("user_id") or "unknown" + nickname = sender_data.get("name") or sender_data.get("alias") or user_id + chat_id = payload.get("chatid") or payload.get("chat_id") or user_id + message_id = payload.get("msgid") or uuid.uuid4().hex + + abm = AstrBotMessage() + abm.self_id = payload.get("webhook_url", self.metadata.id) + abm.sender = MessageMember(user_id=user_id, nickname=nickname) + abm.session_id = chat_id + abm.message_id = message_id + abm.timestamp = int(time.time()) + abm.raw_message = {"payload": payload, "metadata": metadata} + abm.type = ( + MessageType.GROUP_MESSAGE + if str(payload.get("chattype") or "").lower() == "group" + else MessageType.FRIEND_MESSAGE + ) + + message_components, message_str = await self._build_message_components(msgtype, payload) + abm.message = message_components or [Plain(message_str or "")] + abm.message_str = message_str or "" + + return abm + + async def _build_message_components( + self, + msgtype: str, + payload: dict[str, Any], + ) -> tuple[list, str]: + components: list = [] + message_str = "" + + if msgtype == "text": + content = str(payload.get("text", {}).get("content", "")).strip() + message_str = content + components.append(Plain(content)) + elif msgtype == "image": + image_url = payload.get("image", {}).get("image_url") or payload.get("image", {}).get("url") + message_str = "[图片]" + if image_url: + components.append(Image(file=image_url, url=image_url)) + elif msgtype == "mixed": + items = payload.get("mixed_message", {}).get("msg_item", []) + texts: list[str] = [] + for item in items: + item_type = str(item.get("msg_type", "")).lower() + if item_type == "text": + text_content = item.get("text", {}).get("content", "") + texts.append(text_content) + components.append(Plain(text_content)) + elif item_type == "image": + image_url = item.get("image", {}).get("image_url") + if image_url: + components.append(Image(file=image_url, url=image_url)) + message_str = " ".join(texts) + elif msgtype == "event": + event_type = payload.get("event", {}).get("event_type") + message_str = f"[事件] {event_type}" if event_type else "[事件]" + components.append(Plain(message_str)) + elif msgtype == "attachment": + callback_id = payload.get("attachment", {}).get("callback_id") + message_str = f"[按钮回调] {callback_id or ''}".strip() + components.append(Plain(message_str)) + else: + message_str = f"[{msgtype}]" + components.append(Plain(message_str)) + + return components, message_str + + async def send_by_session(self, session: MessageSesion, message_chain: MessageChain): + logger.info("WeCom 群机器人 send_by_session: %s -> %s", session.session_id, message_chain) + await super().send_by_session(session, message_chain) + + async def _maybe_send_auto_reply(self, payload: dict[str, Any]) -> None: + webhook_url = payload.get("webhook_url") or payload.get("response_url") + chat_id = payload.get("chatid") or payload.get("chat_id") + msgtype = str(payload.get("msgtype") or payload.get("msg_type") or "").lower() + + if not webhook_url or not chat_id: + return + + try: + if msgtype in {"text", "image", "mixed"} and self.initial_respond_text: + await self.client.send_plain_message( + webhook_url, + chat_id, + Plain(self.initial_respond_text), + ) + elif ( + msgtype == "event" + and (payload.get("event") or {}).get("event_type") == "enter_chat" + and self.friend_message_welcome_text + ): + await self.client.send_plain_message( + webhook_url, + chat_id, + Plain(self.friend_message_welcome_text), + ) + except Exception as exc: # pragma: no cover - defensive log + logger.error("企业微信消息推送机器人(原群机器人)自动回复失败: %s", exc) + + def meta(self) -> PlatformMetadata: + return self.metadata + + async def run(self): + await self.server.start() + + async def terminate(self): + await self.server.stop() + + async def handle_msg(self, message: AstrBotMessage): + event = WecomGroupBotEvent( + message_str=message.message_str, + message_obj=message, + platform_meta=self.meta(), + session_id=message.session_id, + client=self.client, + ) + self.commit_event(event) diff --git a/astrbot/core/platform/sources/wecom_group_bot/wecom_group_bot_client.py b/astrbot/core/platform/sources/wecom_group_bot/wecom_group_bot_client.py new file mode 100644 index 000000000..1ca0de97c --- /dev/null +++ b/astrbot/core/platform/sources/wecom_group_bot/wecom_group_bot_client.py @@ -0,0 +1,76 @@ +"""企业微信消息推送机器人(原群机器人)主动发送客户端""" + +from __future__ import annotations + +import base64 +import hashlib +import json +from typing import Any + +import aiohttp + +from astrbot.api import logger +from astrbot.api.message_components import Image, Plain + + +class WecomGroupBotClient: + """封装 webhook 主动推送能力""" + + async def send_plain_message( + self, + webhook_url: str, + chat_id: str | None, + plain: Plain, + mentioned_list: list[str] | None = None, + ) -> None: + content = plain.text.strip() + if not content: + return + payload = { + "msgtype": "text", + "text": {"content": content}, + } + if chat_id: + payload["chatid"] = chat_id + if mentioned_list: + payload["text"]["mentioned_list"] = mentioned_list + await self._post(webhook_url, payload) + + async def send_image_message(self, webhook_url: str, chat_id: str | None, image: Image) -> None: + base64_data = await image.convert_to_base64() + if not base64_data: + logger.warning("无法获取图片 base64 数据,跳过发送") + return + image_bytes = base64.b64decode(base64_data) + payload = { + "msgtype": "image", + "image": { + "base64": base64_data, + "md5": hashlib.md5(image_bytes).hexdigest(), + }, + } + if chat_id: + payload["chatid"] = chat_id + await self._post(webhook_url, payload) + + async def _post(self, webhook_url: str, payload: dict[str, Any]) -> None: + if not webhook_url: + logger.error("未提供 webhook_url,无法发送企业微信消息推送机器人(原群机器人)消息") + return + + try: + async with aiohttp.ClientSession() as session: + async with session.post(webhook_url, json=payload, timeout=10) as response: + text = await response.text() + if response.status != 200: + logger.error( + "发送企业微信消息推送机器人(原群机器人)消息失败,状态码=%s,响应=%s", + response.status, + text, + ) + return + data = json.loads(text) if text else {} + if data.get("errcode") not in (None, 0): + logger.error("企业微信返回错误: %s", data) + except Exception as exc: # pragma: no cover - defensive log + logger.error("发送企业微信消息推送机器人(原群机器人)消息失败: %s", exc) diff --git a/astrbot/core/platform/sources/wecom_group_bot/wecom_group_bot_event.py b/astrbot/core/platform/sources/wecom_group_bot/wecom_group_bot_event.py new file mode 100644 index 000000000..3e1dcba8f --- /dev/null +++ b/astrbot/core/platform/sources/wecom_group_bot/wecom_group_bot_event.py @@ -0,0 +1,89 @@ +"""企业微信消息推送机器人(原群机器人)事件""" + +from __future__ import annotations + +from typing import Any + +from astrbot.api import logger +from astrbot.api.event import AstrMessageEvent, MessageChain +from astrbot.api.message_components import At, Image, Plain + +from .wecom_group_bot_client import WecomGroupBotClient + + +class WecomGroupBotEvent(AstrMessageEvent): + """封装消息推送机器人的回复能力""" + + def __init__( + self, + message_str: str, + message_obj, + platform_meta, + session_id: str, + client: WecomGroupBotClient, + ) -> None: + super().__init__(message_str, message_obj, platform_meta, session_id) + self.client = client + + async def send(self, message: MessageChain): + webhook_url = self._extract_webhook() + if not webhook_url: + logger.warning("缺少 webhook_url,无法向企业微信消息推送机器人(原群机器人)发送消息") + return + + chat_id = self._extract_chat_id() + mentioned_list: list[str] = [] + plain_segments: list[str] = [] + image_components: list[Image] = [] + + for comp in message.chain: + if isinstance(comp, Plain): + plain_segments.append(comp.text) + elif isinstance(comp, At): + target = str(comp.qq) + plain_segments.append(f"<@{target}>") + mentioned_list.append(target) + elif isinstance(comp, Image): + image_components.append(comp) + else: + logger.warning("暂未支持的消息组件类型: %s", comp.type) + + if plain_segments: + await self.client.send_plain_message( + webhook_url, + chat_id, + Plain("\n".join(segment.strip() for segment in plain_segments if segment)), + mentioned_list or None, + ) + + for image in image_components: + await self.client.send_image_message(webhook_url, chat_id, image) + + await super().send(message) + + async def send_streaming(self, generator, use_fallback: bool = False): + buffer = None + async for chain in generator: + if not buffer: + buffer = chain + else: + buffer.chain.extend(chain.chain) + if buffer: + await self.send(buffer) + return await super().send_streaming(generator, use_fallback) + + def _extract_chat_id(self) -> str: + raw = self.message_obj.raw_message or {} + if isinstance(raw, dict): + payload: dict[str, Any] = raw.get("payload") or raw + chat_id = payload.get("chatid") or payload.get("chat_id") + if chat_id: + return str(chat_id) + return self.message_obj.session_id or self.session_id or "" + + def _extract_webhook(self) -> str: + raw = self.message_obj.raw_message or {} + if isinstance(raw, dict): + payload: dict[str, Any] = raw.get("payload") or raw + return payload.get("webhook_url", "") or payload.get("response_url", "") + return "" diff --git a/astrbot/core/platform/sources/wecom_group_bot/wecom_group_bot_parser.py b/astrbot/core/platform/sources/wecom_group_bot/wecom_group_bot_parser.py new file mode 100644 index 000000000..1aa40a157 --- /dev/null +++ b/astrbot/core/platform/sources/wecom_group_bot/wecom_group_bot_parser.py @@ -0,0 +1,92 @@ +"""消息推送机器人回调解析工具""" + +from __future__ import annotations + +import json +import re +import xml.etree.ElementTree as ET +from typing import Any + +from astrbot.api import logger + + +_CAMEL_CASE_PATTERN = re.compile(r"(? str: + if not value: + return value + return _CAMEL_CASE_PATTERN.sub("_", value).lower() + + +def _element_to_data(element: ET.Element) -> Any: + children = list(element) + if not children: + return (element.text or "").strip() + + data: dict[str, Any] = {} + for child in children: + key = _camel_to_snake(child.tag) + value = _element_to_data(child) + if key in data: + existing = data[key] + if isinstance(existing, list): + existing.append(value) + else: + data[key] = [existing, value] + else: + data[key] = value + return data + + +class WecomGroupBotParser: + """根据配置解析企业微信消息推送回调""" + + def __init__(self, prefer_format: str = "xml") -> None: + self.prefer_format = prefer_format.lower() + + def parse(self, payload: str) -> dict[str, Any]: + text = (payload or "").strip() + if not text: + return {} + + try: + if self._should_parse_as_json(text): + return self._parse_json(text) + return self._parse_xml(text) + except Exception as exc: # pragma: no cover - defensive log + logger.error("解析企业微信消息推送机器人(原群机器人)回调失败: %s", exc) + return {} + + def _should_parse_as_json(self, payload: str) -> bool: + if self.prefer_format == "json": + return True + if self.prefer_format == "xml": + return payload.startswith("<") is False + return payload.lstrip().startswith("{") + + def _parse_json(self, payload: str) -> dict[str, Any]: + data = json.loads(payload) + return self._normalize_keys(data) + + def _parse_xml(self, payload: str) -> dict[str, Any]: + root = ET.fromstring(payload) + parsed: dict[str, Any] = {} + for child in root: + parsed[_camel_to_snake(child.tag)] = _element_to_data(child) + return parsed + + def _normalize_keys(self, data: dict[str, Any]) -> dict[str, Any]: + normalized: dict[str, Any] = {} + for key, value in data.items(): + new_key = _camel_to_snake(str(key)) + if isinstance(value, dict): + normalized[new_key] = self._normalize_keys(value) + elif isinstance(value, list): + normalized[new_key] = [ + self._normalize_keys(item) if isinstance(item, dict) else item + for item in value + ] + else: + normalized[new_key] = value + return normalized diff --git a/astrbot/core/platform/sources/wecom_group_bot/wecom_group_bot_server.py b/astrbot/core/platform/sources/wecom_group_bot/wecom_group_bot_server.py new file mode 100644 index 000000000..acac95a22 --- /dev/null +++ b/astrbot/core/platform/sources/wecom_group_bot/wecom_group_bot_server.py @@ -0,0 +1,131 @@ +"""企业微信消息推送机器人(原群机器人) HTTP Server""" + +from __future__ import annotations + +import asyncio +import json +from typing import Any, Awaitable, Callable + +import quart +from wechatpy.enterprise.crypto import WeChatCrypto + +from astrbot.api import logger + +from .wecom_group_bot_parser import WecomGroupBotParser + +MessageHandler = Callable[[dict[str, Any], dict[str, str]], Awaitable[None]] + + +class WecomGroupBotServer: + def __init__( + self, + host: str, + port: int, + token: str, + encoding_aes_key: str, + receive_id: str, + parser: WecomGroupBotParser, + message_handler: MessageHandler | None = None, + callback_path: str = "/webhook/wecom-group-bot", + ) -> None: + self.host = host + self.port = port + self.callback_path = callback_path + self.message_handler = message_handler + self.parser = parser + + self.app = quart.Quart(__name__) + self._setup_routes() + + self.crypto = WeChatCrypto(token.strip(), encoding_aes_key.strip(), receive_id.strip()) + self.shutdown_event = asyncio.Event() + + def _setup_routes(self) -> None: + self.app.add_url_rule(self.callback_path, view_func=self.verify_url, methods=["GET"]) + self.app.add_url_rule(self.callback_path, view_func=self.handle_message, methods=["POST"]) + + @staticmethod + def _make_payload_preview(payload: dict[str, Any], limit: int = 800) -> str: + try: + serialized = json.dumps(payload, ensure_ascii=False) + except Exception: + serialized = str(payload) + serialized = serialized.strip() + if len(serialized) > limit: + return f"{serialized[:limit]}..." + return serialized + + async def verify_url(self): + args = quart.request.args + msg_signature = args.get("msg_signature") + timestamp = args.get("timestamp") + nonce = args.get("nonce") + echostr = args.get("echostr") + + if not all([msg_signature, timestamp, nonce, echostr]): + logger.error("企业微信消息推送机器人(原群机器人) URL 验证参数缺失") + return "verify fail", 400 + + try: + echo_result = self.crypto.check_signature(msg_signature, timestamp, nonce, echostr) + logger.info("企业微信消息推送机器人(原群机器人) URL 验证通过") + return echo_result, 200 + except Exception as exc: # pragma: no cover - defensive log + logger.error("企业微信消息推送机器人(原群机器人) URL 验证失败: %s", exc) + return "verify fail", 400 + + async def handle_message(self): + args = quart.request.args + msg_signature = args.get("msg_signature") + timestamp = args.get("timestamp") + nonce = args.get("nonce") + + if not all([msg_signature, timestamp, nonce]): + logger.error("企业微信消息推送机器人(原群机器人)消息参数缺失") + return "缺少必要参数", 400 + + payload_bytes = await quart.request.get_data() + try: + decrypted_text = self.crypto.decrypt_message(payload_bytes, msg_signature, timestamp, nonce) + except Exception as exc: + logger.error("企业微信消息推送机器人(原群机器人)消息解密失败: %s", exc) + return "解密失败", 400 + + message_data = self.parser.parse(decrypted_text) + metadata = {"msg_signature": msg_signature, "timestamp": timestamp, "nonce": nonce} + + if message_data: + logger.info( + ( + "企业微信消息推送机器人(原群机器人)收到消息: chat_id=%s, msgtype=%s, sender=%s, payload=%s" + ), + message_data.get("chatid") or message_data.get("chat_id"), + message_data.get("msgtype") or message_data.get("msg_type"), + (message_data.get("from") or {}).get("userid"), + self._make_payload_preview(message_data), + ) + else: + logger.warning("企业微信消息推送机器人(原群机器人)收到空消息,metadata=%s", metadata) + + if self.message_handler: + try: + await self.message_handler(message_data, metadata) + except Exception as exc: # pragma: no cover - defensive log + logger.error("企业微信消息推送机器人(原群机器人)消息处理失败: %s", exc) + return "处理失败", 500 + + return "success", 200 + + async def start(self): + logger.info("启动企业微信消息推送机器人(原群机器人)服务器,监听 %s:%s", self.host, self.port) + await self.app.run_task( + host=self.host, + port=self.port, + shutdown_trigger=self._shutdown_trigger, + ) + + async def stop(self): + self.shutdown_event.set() + + async def _shutdown_trigger(self): + await self.shutdown_event.wait() diff --git a/dashboard/src/utils/platformUtils.js b/dashboard/src/utils/platformUtils.js index 7ddbad7ae..b01918cc9 100644 --- a/dashboard/src/utils/platformUtils.js +++ b/dashboard/src/utils/platformUtils.js @@ -10,7 +10,7 @@ export function getPlatformIcon(name) { if (name === 'aiocqhttp' || name === 'qq_official' || name === 'qq_official_webhook') { return new URL('@/assets/images/platform_logos/qq.png', import.meta.url).href - } else if (name === 'wecom' || name === 'wecom_ai_bot') { + } else if (name === 'wecom' || name === 'wecom_ai_bot' || name === 'wecom_group_bot') { return new URL('@/assets/images/platform_logos/wecom.png', import.meta.url).href } else if (name === 'wechatpadpro' || name === 'weixin_official_account' || name === 'wechat') { return new URL('@/assets/images/platform_logos/wechat.png', import.meta.url).href