From ecd39b3b30de1fb1ef937ad5d9ad6ff828b1b461 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sun, 22 Mar 2026 12:31:08 +0800 Subject: [PATCH 1/8] feat: supports weixin personal account --- astrbot/core/config/default.py | 43 + astrbot/core/platform/manager.py | 4 + .../sources/weixin_oc/weixin_oc_adapter.py | 1099 +++++++++++++++++ .../sources/weixin_oc/weixin_oc_event.py | 87 ++ dashboard/package.json | 3 +- dashboard/pnpm-lock.yaml | 149 +++ .../mdi-subset/materialdesignicons-subset.css | 6 +- .../materialdesignicons-webfont-subset.woff | Bin 16172 -> 16276 bytes .../materialdesignicons-webfont-subset.woff2 | Bin 12996 -> 13060 bytes .../src/components/shared/QrCodeViewer.vue | 90 ++ .../en-US/features/config-metadata.json | 24 + .../i18n/locales/en-US/features/platform.json | 7 + .../ru-RU/features/config-metadata.json | 24 + .../i18n/locales/ru-RU/features/platform.json | 25 +- .../zh-CN/features/config-metadata.json | 24 + .../i18n/locales/zh-CN/features/platform.json | 7 + dashboard/src/utils/platformUtils.js | 2 + dashboard/src/views/PlatformPage.vue | 86 +- docs/.vitepress/config.mjs | 1 + docs/zh/platform/weixin_oc.md | 74 ++ 20 files changed, 1740 insertions(+), 15 deletions(-) create mode 100644 astrbot/core/platform/sources/weixin_oc/weixin_oc_adapter.py create mode 100644 astrbot/core/platform/sources/weixin_oc/weixin_oc_event.py create mode 100644 dashboard/src/components/shared/QrCodeViewer.vue create mode 100644 docs/zh/platform/weixin_oc.md diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index b0d4e3c351..2e8e646960 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -7,6 +7,38 @@ VERSION = "4.21.0" DB_PATH = os.path.join(get_astrbot_data_path(), "data_v4.db") +PERSONAL_WECHAT_CONFIG_METADATA = { + "weixin_oc_base_url": { + "description": "iLink API 地址", + "type": "string", + "hint": "默认值: https://ilinkai.weixin.qq.com", + }, + "weixin_oc_bot_type": { + "description": "扫码参数 bot_type", + "type": "string", + "hint": "默认值: 3", + }, + "weixin_oc_qr_poll_interval": { + "description": "二维码状态轮询间隔(秒)", + "type": "int", + "hint": "每隔多少秒轮询一次二维码状态。", + }, + "weixin_oc_long_poll_timeout_ms": { + "description": "getUpdates 长轮询超时时间(毫秒)", + "type": "int", + "hint": "会话消息拉取接口超时参数。", + }, + "weixin_oc_api_timeout_ms": { + "description": "HTTP 请求超时(毫秒)", + "type": "int", + "hint": "通用 API 请求超时参数。", + }, + "weixin_oc_token": { + "description": "登录后 token(可留空)", + "type": "string", + "hint": "扫码登录成功后会自动写入;高级场景可手动填写。", + }, +} WEBHOOK_SUPPORTED_PLATFORMS = [ "qq_official_webhook", @@ -364,6 +396,16 @@ class ChatProviderTemplate(TypedDict): "callback_server_host": "0.0.0.0", "port": 6198, }, + "个人微信": { + "id": "weixin_personal", + "type": "weixin_oc", + "enable": False, + "weixin_oc_base_url": "https://ilinkai.weixin.qq.com", + "weixin_oc_bot_type": "3", + "weixin_oc_qr_poll_interval": 1, + "weixin_oc_long_poll_timeout_ms": 35_000, + "weixin_oc_api_timeout_ms": 15_000, + }, "飞书(Lark)": { "id": "lark", "type": "lark", @@ -869,6 +911,7 @@ class ChatProviderTemplate(TypedDict): "type": "bool", "hint": "Webhook 模式下使用 AstrBot 统一 Webhook 入口,无需单独开启端口。回调地址为 /api/platform/webhook/{webhook_uuid}。", }, + **PERSONAL_WECHAT_CONFIG_METADATA, "webhook_uuid": { "invisible": True, "description": "Webhook UUID", diff --git a/astrbot/core/platform/manager.py b/astrbot/core/platform/manager.py index 68737b2bcf..15c04166dc 100644 --- a/astrbot/core/platform/manager.py +++ b/astrbot/core/platform/manager.py @@ -170,6 +170,10 @@ async def load_platform(self, platform_config: dict) -> None: from .sources.misskey.misskey_adapter import ( MisskeyPlatformAdapter, # noqa: F401 ) + case "weixin_oc": + from .sources.weixin_oc.weixin_oc_adapter import ( + WeixinOCAdapter, # noqa: F401 + ) case "slack": from .sources.slack.slack_adapter import SlackAdapter # noqa: F401 case "satori": diff --git a/astrbot/core/platform/sources/weixin_oc/weixin_oc_adapter.py b/astrbot/core/platform/sources/weixin_oc/weixin_oc_adapter.py new file mode 100644 index 0000000000..aaa38d9d17 --- /dev/null +++ b/astrbot/core/platform/sources/weixin_oc/weixin_oc_adapter.py @@ -0,0 +1,1099 @@ +from __future__ import annotations + +import asyncio +import base64 +import hashlib +import json +import random +import time +import uuid +from dataclasses import dataclass +from pathlib import Path +from typing import TYPE_CHECKING, Any, cast +from urllib.parse import quote + +import aiohttp +from Crypto.Cipher import AES + +from astrbot import logger +from astrbot.api.event import MessageChain +from astrbot.api.message_components import File, Image, Plain, Record, Video +from astrbot.api.platform import ( + AstrBotMessage, + MessageMember, + MessageType, + Platform, + PlatformMetadata, + register_platform_adapter, +) +from astrbot.core.platform.astr_message_event import MessageSesion +from astrbot.core.utils.astrbot_path import get_astrbot_data_path, get_astrbot_temp_path + +from .weixin_oc_event import WeixinOCMessageEvent + +if TYPE_CHECKING: # pragma: no cover - typing-only helper + pass + + +@dataclass +class OpenClawLoginSession: + session_key: str + qrcode: str + qrcode_img_content: str + started_at: float + status: str = "wait" + bot_token: str | None = None + account_id: str | None = None + base_url: str | None = None + user_id: str | None = None + error: str | None = None + + +@register_platform_adapter( + "weixin_oc", + "个人微信", + support_streaming_message=False, +) +class WeixinOCAdapter(Platform): + IMAGE_ITEM_TYPE = 2 + VOICE_ITEM_TYPE = 3 + FILE_ITEM_TYPE = 4 + VIDEO_ITEM_TYPE = 5 + IMAGE_UPLOAD_TYPE = 1 + VIDEO_UPLOAD_TYPE = 2 + FILE_UPLOAD_TYPE = 3 + + def __init__( + self, + platform_config: dict, + platform_settings: dict, + event_queue: asyncio.Queue, + ) -> None: + super().__init__(platform_config, event_queue) + + self.settings = platform_settings + self.base_url = str( + platform_config.get("weixin_oc_base_url", "https://ilinkai.weixin.qq.com") + ).rstrip("/") + self.bot_type = str(platform_config.get("weixin_oc_bot_type", "3")) + self.qr_poll_interval = max( + 1, + int(platform_config.get("weixin_oc_qr_poll_interval", 1)), + ) + self.long_poll_timeout_ms = int( + platform_config.get("weixin_oc_long_poll_timeout_ms", 35_000), + ) + self.api_timeout_ms = int( + platform_config.get("weixin_oc_api_timeout_ms", 15_000), + ) + self.cdn_base_url = str( + platform_config.get( + "weixin_oc_cdn_base_url", + "https://novac2c.cdn.weixin.qq.com/c2c", + ) + ).rstrip("/") + + self.metadata = PlatformMetadata( + name="weixin_oc", + description="个人微信", + id=cast(str, self.config.get("id", "weixin_oc")), + support_streaming_message=False, + ) + + self._shutdown_event = asyncio.Event() + self._http_session: aiohttp.ClientSession | None = None + self._login_session: OpenClawLoginSession | None = None + self._sync_buf = "" + self._qr_expired_count = 0 + self._context_tokens: dict[str, str] = {} + self._last_inbound_error = "" + + self.token = str(platform_config.get("weixin_oc_token", "")).strip() or None + self.account_id = str(platform_config.get("account_id", "")).strip() or None + self._account_file_path = self._resolve_account_file_path() + self._load_account_state() + + if self.token: + logger.info( + "weixin_oc adapter %s loaded with token from config/credential.", + self.meta().id, + ) + + def _resolve_account_file_path(self) -> Path: + root = Path(get_astrbot_data_path()) / "platform" / "weixin_oc" + root.mkdir(parents=True, exist_ok=True) + return root / f"{cast(str, self.config.get('id', 'weixin_oc'))}.json" + + def _load_account_state(self) -> None: + path = self._account_file_path + if not path.exists(): + return + try: + raw = json.loads(path.read_text(encoding="utf-8")) + if isinstance(raw, dict): + if not self.token: + token = str(raw.get("token", "")).strip() + if token: + self.token = token + if not self.account_id: + account_id = str(raw.get("account_id", "")).strip() + if account_id: + self.account_id = account_id + sync_buf = str(raw.get("sync_buf", "")).strip() + if sync_buf: + self._sync_buf = sync_buf + saved_base = str(raw.get("base_url", "")).strip() + if saved_base: + self.base_url = saved_base.rstrip("/") + except Exception as e: + logger.warning("weixin_oc: load credential failed: %s", e) + + async def _save_account_state(self) -> None: + payload = { + "token": self.token, + "account_id": self.account_id, + "sync_buf": self._sync_buf, + "base_url": self.base_url, + "updated_at": int(time.time()), + } + tmp = self._account_file_path.with_suffix(".tmp") + tmp.write_text( + json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8" + ) + tmp.replace(self._account_file_path) + + async def _ensure_http_session(self) -> None: + if self._http_session is None or self._http_session.closed: + timeout = aiohttp.ClientTimeout(total=self.api_timeout_ms / 1000) + self._http_session = aiohttp.ClientSession(timeout=timeout) + + async def _close_http_session(self) -> None: + if self._http_session is not None and not self._http_session.closed: + await self._http_session.close() + self._http_session = None + + def _is_login_session_valid( + self, login_session: OpenClawLoginSession | None + ) -> bool: + if not login_session: + return False + return (time.time() - login_session.started_at) * 1000 < 5 * 60_000 + + def _build_base_headers(self, token_required: bool = False) -> dict[str, str]: + headers = { + "Content-Type": "application/json", + "AuthorizationType": "ilink_bot_token", + "X-WECHAT-UIN": base64.b64encode( + str(random.getrandbits(32)).encode("utf-8") + ).decode("utf-8"), + } + if token_required and self.token: + headers["Authorization"] = f"Bearer {self.token}" + return headers + + def _resolve_url(self, endpoint: str) -> str: + return f"{self.base_url.rstrip('/')}/{endpoint.lstrip('/')}" + + def _build_cdn_upload_url(self, upload_param: str, file_key: str) -> str: + return ( + f"{self.cdn_base_url}/upload?" + f"encrypted_query_param={quote(upload_param)}&filekey={quote(file_key)}" + ) + + def _build_cdn_download_url(self, encrypted_query_param: str) -> str: + return ( + f"{self.cdn_base_url}/download?" + f"encrypted_query_param={quote(encrypted_query_param)}" + ) + + def _resolve_inbound_media_dir(self) -> Path: + media_dir = Path(get_astrbot_temp_path()) + media_dir.mkdir(parents=True, exist_ok=True) + return media_dir + + @staticmethod + def _aes_padded_size(size: int) -> int: + return size + (16 - (size % 16) or 16) + + @staticmethod + def _pkcs7_pad(data: bytes, block_size: int = 16) -> bytes: + pad_len = block_size - (len(data) % block_size) + if pad_len == 0: + pad_len = block_size + return data + bytes([pad_len]) * pad_len + + @staticmethod + def _pkcs7_unpad(data: bytes, block_size: int = 16) -> bytes: + if not data: + return data + pad_len = data[-1] + if pad_len <= 0 or pad_len > block_size: + return data + if data[-pad_len:] != bytes([pad_len]) * pad_len: + return data + return data[:-pad_len] + + @staticmethod + def _parse_media_aes_key(aes_key_value: str) -> bytes: + normalized = aes_key_value.strip() + if not normalized: + raise ValueError("empty media aes key") + padded = normalized + "=" * (-len(normalized) % 4) + decoded = base64.b64decode(padded) + if len(decoded) == 16: + return decoded + decoded_text = decoded.decode("ascii", errors="ignore") + if len(decoded) == 32 and all( + c in "0123456789abcdefABCDEF" for c in decoded_text + ): + return bytes.fromhex(decoded_text) + raise ValueError("unsupported media aes key format") + + @staticmethod + def _normalize_inbound_filename(file_name: str, fallback_name: str) -> str: + normalized = Path(file_name or "").name.strip() + return normalized or fallback_name + + def _save_inbound_media( + self, + content: bytes, + *, + prefix: str, + file_name: str, + fallback_suffix: str, + ) -> Path: + normalized_name = self._normalize_inbound_filename( + file_name, + f"{prefix}{fallback_suffix}", + ) + stem = Path(normalized_name).stem or prefix + suffix = Path(normalized_name).suffix or fallback_suffix + target = ( + self._resolve_inbound_media_dir() + / f"{prefix}_{uuid.uuid4().hex}_{stem}{suffix}" + ) + target.write_bytes(content) + return target + + @staticmethod + def _build_plain_text_item(text: str) -> dict[str, Any]: + return { + "type": 1, + "text_item": { + "text": text, + }, + } + + async def _upload_to_cdn( + self, + upload_param: str, + file_key: str, + aes_key_hex: str, + media_path: Path, + ) -> str: + raw_data = media_path.read_bytes() + logger.debug( + "weixin_oc(%s): prepare CDN upload file=%s size=%s md5=%s filekey=%s", + self.meta().id, + media_path.name, + len(raw_data), + hashlib.md5(raw_data).hexdigest(), + file_key, + ) + cipher = AES.new(bytes.fromhex(aes_key_hex), AES.MODE_ECB) + encrypted = cipher.encrypt(self._pkcs7_pad(raw_data)) + logger.debug( + "weixin_oc(%s): encrypt done aes_key_len=%s plain_size=%s cipher_size=%s", + self.meta().id, + len(bytes.fromhex(aes_key_hex)), + len(raw_data), + len(encrypted), + ) + + await self._ensure_http_session() + assert self._http_session is not None + timeout = aiohttp.ClientTimeout(total=self.api_timeout_ms / 1000) + cdn_url = self._build_cdn_upload_url(upload_param, file_key) + + async with self._http_session.post( + cdn_url, + data=encrypted, + headers={"Content-Type": "application/octet-stream"}, + timeout=timeout, + ) as resp: + detail = await resp.text() + logger.debug( + "weixin_oc(%s): CDN upload response status=%s url=%s x-error-message=%s x-encrypted-param=%s body=%s", + self.meta().id, + resp.status, + cdn_url, + resp.headers.get("x-error-message"), + resp.headers.get("x-encrypted-param"), + detail[:512], + ) + if resp.status >= 400 and resp.status < 500: + raise RuntimeError( + f"upload media to cdn failed: {resp.status} {detail}" + ) + if resp.status != 200: + raise RuntimeError( + f"upload media to cdn failed: {resp.status} {detail}" + ) + download_param = resp.headers.get("x-encrypted-param") + if not download_param: + raise RuntimeError( + "upload media to cdn failed: missing x-encrypted-param" + ) + return download_param + + async def _prepare_media_item( + self, + user_id: str, + media_path: Path, + upload_media_type: int, + item_type: int, + file_name: str, + ) -> dict[str, Any]: + raw_bytes = media_path.read_bytes() + raw_size = len(raw_bytes) + raw_md5 = hashlib.md5(raw_bytes).hexdigest() + file_key = uuid.uuid4().hex + aes_key_hex = uuid.uuid4().bytes.hex() + ciphertext_size = self._aes_padded_size(raw_size) + + payload = await self._request_json( + "POST", + "ilink/bot/getuploadurl", + payload={ + "filekey": file_key, + "media_type": upload_media_type, + "to_user_id": user_id, + "rawsize": raw_size, + "rawfilemd5": raw_md5, + "filesize": ciphertext_size, + "no_need_thumb": True, + "aeskey": aes_key_hex, + "base_info": { + "channel_version": "astrbot", + }, + }, + token_required=True, + timeout_ms=self.api_timeout_ms, + ) + logger.debug( + "weixin_oc(%s): getuploadurl response user=%s media_type=%s raw_size=%s raw_md5=%s filekey=%s file=%s upload_param_len=%s", + self.meta().id, + user_id, + upload_media_type, + raw_size, + raw_md5, + file_key, + media_path.name, + len(str(payload.get("upload_param", ""))), + ) + upload_param = str(payload.get("upload_param", "")).strip() + if not upload_param: + raise RuntimeError("getuploadurl returned empty upload_param") + + encrypted_query_param = await self._upload_to_cdn( + upload_param, + file_key, + aes_key_hex, + media_path, + ) + logger.debug( + "weixin_oc(%s): prepared media item type=%s file=%s user=%s mid_size=%s upload_param_len=%s query_len=%s", + self.meta().id, + item_type, + media_path.name, + user_id, + ciphertext_size, + len(upload_param), + len(encrypted_query_param), + ) + + aes_key_b64 = base64.b64encode(bytes.fromhex(aes_key_hex)).decode("utf-8") + media_payload = { + "media": { + "encrypt_query_param": encrypted_query_param, + "aes_key": aes_key_b64, + "encrypt_type": 1, + } + } + + if item_type == self.IMAGE_ITEM_TYPE: + return { + "type": self.IMAGE_ITEM_TYPE, + "image_item": { + **{"media": media_payload}, + "mid_size": ciphertext_size, + }, + } + if item_type == self.VIDEO_ITEM_TYPE: + return { + "type": self.VIDEO_ITEM_TYPE, + "video_item": { + **{"media": media_payload}, + "video_size": ciphertext_size, + }, + } + + file_len = str(raw_size) + return { + "type": self.FILE_ITEM_TYPE, + "file_item": { + "media": media_payload, + "file_name": file_name, + "len": file_len, + }, + } + + async def _download_cdn_bytes(self, encrypted_query_param: str) -> bytes: + await self._ensure_http_session() + assert self._http_session is not None + timeout = aiohttp.ClientTimeout(total=self.api_timeout_ms / 1000) + async with self._http_session.get( + self._build_cdn_download_url(encrypted_query_param), + timeout=timeout, + ) as resp: + if resp.status >= 400: + detail = await resp.text() + raise RuntimeError( + f"download media from cdn failed: {resp.status} {detail}" + ) + return await resp.read() + + async def _download_and_decrypt_media( + self, + encrypted_query_param: str, + aes_key_value: str, + ) -> bytes: + encrypted = await self._download_cdn_bytes(encrypted_query_param) + key = self._parse_media_aes_key(aes_key_value) + cipher = AES.new(key, AES.MODE_ECB) + return self._pkcs7_unpad(cipher.decrypt(encrypted)) + + async def _resolve_inbound_media_component( + self, + item: dict[str, Any], + ) -> Image | Video | File | Record | None: + item_type = int(item.get("type") or 0) + + if item_type == self.IMAGE_ITEM_TYPE: + image_item = cast(dict[str, Any], item.get("image_item", {}) or {}) + media = cast(dict[str, Any], image_item.get("media", {}) or {}) + encrypted_query_param = str(media.get("encrypt_query_param", "")).strip() + if not encrypted_query_param: + return None + image_aes_key = str(image_item.get("aeskey", "")).strip() + if image_aes_key: + aes_key_value = base64.b64encode(bytes.fromhex(image_aes_key)).decode( + "utf-8" + ) + else: + aes_key_value = str(media.get("aes_key", "")).strip() + if aes_key_value: + content = await self._download_and_decrypt_media( + encrypted_query_param, + aes_key_value, + ) + else: + content = await self._download_cdn_bytes(encrypted_query_param) + image_path = self._save_inbound_media( + content, + prefix="weixin_oc_img", + file_name="image.jpg", + fallback_suffix=".jpg", + ) + return Image.fromFileSystem(str(image_path)) + + if item_type == self.VIDEO_ITEM_TYPE: + video_item = cast(dict[str, Any], item.get("video_item", {}) or {}) + media = cast(dict[str, Any], video_item.get("media", {}) or {}) + encrypted_query_param = str(media.get("encrypt_query_param", "")).strip() + aes_key_value = str(media.get("aes_key", "")).strip() + if not encrypted_query_param or not aes_key_value: + return None + content = await self._download_and_decrypt_media( + encrypted_query_param, + aes_key_value, + ) + video_path = self._save_inbound_media( + content, + prefix="weixin_oc_video", + file_name="video.mp4", + fallback_suffix=".mp4", + ) + return Video.fromFileSystem(str(video_path)) + + if item_type == self.FILE_ITEM_TYPE: + file_item = cast(dict[str, Any], item.get("file_item", {}) or {}) + media = cast(dict[str, Any], file_item.get("media", {}) or {}) + encrypted_query_param = str(media.get("encrypt_query_param", "")).strip() + aes_key_value = str(media.get("aes_key", "")).strip() + if not encrypted_query_param or not aes_key_value: + return None + file_name = self._normalize_inbound_filename( + str(file_item.get("file_name", "")).strip(), + "file.bin", + ) + content = await self._download_and_decrypt_media( + encrypted_query_param, + aes_key_value, + ) + file_path = self._save_inbound_media( + content, + prefix="weixin_oc_file", + file_name=file_name, + fallback_suffix=".bin", + ) + return File(name=file_name, file=str(file_path)) + + if item_type == self.VOICE_ITEM_TYPE: + voice_item = cast(dict[str, Any], item.get("voice_item", {}) or {}) + media = cast(dict[str, Any], voice_item.get("media", {}) or {}) + encrypted_query_param = str(media.get("encrypt_query_param", "")).strip() + aes_key_value = str(media.get("aes_key", "")).strip() + if not encrypted_query_param or not aes_key_value: + return None + content = await self._download_and_decrypt_media( + encrypted_query_param, + aes_key_value, + ) + voice_path = self._save_inbound_media( + content, + prefix="weixin_oc_voice", + file_name="voice.silk", + fallback_suffix=".silk", + ) + return Record.fromFileSystem(str(voice_path)) + + return None + + async def _resolve_media_file_path( + self, segment: Image | Video | File + ) -> Path | None: + try: + if isinstance(segment, File): + path = await segment.get_file() + elif isinstance(segment, (Image, Video)): + path = await segment.convert_to_file_path() + else: + path = "" + except Exception as e: + logger.warning("weixin_oc(%s): media resolve failed: %s", self.meta().id, e) + return None + + if not path: + return None + media_path = Path(path) + if not media_path.exists() or not media_path.is_file(): + return None + return media_path + + async def _send_items_to_session( + self, + user_id: str, + item_list: list[dict[str, Any]], + ) -> bool: + if not self.token: + logger.warning("weixin_oc(%s): missing token, skip send", self.meta().id) + return False + if not item_list: + logger.warning( + "weixin_oc(%s): empty message payload is ignored", + self.meta().id, + ) + return False + context_token = self._context_tokens.get(user_id) + if not context_token: + logger.warning( + "weixin_oc(%s): context token missing for %s, skip send", + self.meta().id, + user_id, + ) + return False + await self._request_json( + "POST", + "ilink/bot/sendmessage", + payload={ + "base_info": { + "channel_version": "astrbot", + }, + "msg": { + "from_user_id": "", + "to_user_id": user_id, + "client_id": uuid.uuid4().hex, + "message_type": 2, + "message_state": 2, + "context_token": context_token, + "item_list": item_list, + }, + }, + token_required=True, + headers={}, + ) + return True + + async def _send_media_segment( + self, + user_id: str, + segment: Image | Video | File, + text: str | None = None, + ) -> bool: + if not self.token: + logger.warning( + "weixin_oc(%s): missing token, skip media send", self.meta().id + ) + return False + media_path = await self._resolve_media_file_path(segment) + if media_path is None: + logger.warning( + "weixin_oc(%s): skip media segment, media file not resolvable", + self.meta().id, + ) + return False + + item_type = self.IMAGE_ITEM_TYPE + upload_media_type = self.IMAGE_UPLOAD_TYPE + if isinstance(segment, Video): + item_type = self.VIDEO_ITEM_TYPE + upload_media_type = self.VIDEO_UPLOAD_TYPE + elif isinstance(segment, File): + item_type = self.FILE_ITEM_TYPE + upload_media_type = self.FILE_UPLOAD_TYPE + + file_name = ( + segment.name + if isinstance(segment, File) and segment.name + else media_path.name + ) + try: + media_item = await self._prepare_media_item( + user_id, + media_path, + upload_media_type, + item_type, + file_name, + ) + except Exception as e: + logger.error("weixin_oc(%s): prepare media failed: %s", self.meta().id, e) + return False + + if text: + await self._send_items_to_session( + user_id, + [self._build_plain_text_item(text)], + ) + return await self._send_items_to_session(user_id, [media_item]) + + async def _request_json( + self, + method: str, + endpoint: str, + *, + params: dict[str, Any] | None = None, + payload: dict[str, Any] | None = None, + token_required: bool = False, + timeout_ms: int | None = None, + headers: dict[str, str] | None = None, + ) -> dict[str, Any]: + await self._ensure_http_session() + assert self._http_session is not None + req_timeout = timeout_ms if timeout_ms is not None else self.api_timeout_ms + timeout = aiohttp.ClientTimeout(total=req_timeout / 1000) + merged_headers = self._build_base_headers(token_required=token_required) + if headers: + merged_headers.update(headers) + + async with self._http_session.request( + method, + self._resolve_url(endpoint), + params=params, + json=payload, + headers=merged_headers, + timeout=timeout, + ) as resp: + text = await resp.text() + if resp.status >= 400: + raise RuntimeError(f"{method} {endpoint} failed: {resp.status} {text}") + if not text: + return {} + return cast(dict[str, Any], json.loads(text)) + + async def _start_login_session(self) -> OpenClawLoginSession: + endpoint = "ilink/bot/get_bot_qrcode" + params = {"bot_type": self.bot_type} + logger.info("weixin_oc(%s): request QR code from %s", self.meta().id, endpoint) + data = await self._request_json( + "GET", + endpoint, + params=params, + token_required=False, + timeout_ms=15_000, + ) + qrcode = str(data.get("qrcode", "")).strip() + qrcode_url = str(data.get("qrcode_img_content", "")).strip() + if not qrcode or not qrcode_url: + raise RuntimeError("qrcode response missing qrcode or qrcode_img_content") + qr_console_url = ( + f"https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=" + f"{quote(qrcode_url)}" + ) + logger.info( + "weixin_oc(%s): QR session started, qr_link=%s", + self.meta().id, + qr_console_url, + ) + login_session = OpenClawLoginSession( + session_key=str(uuid.uuid4()), + qrcode=qrcode, + qrcode_img_content=qrcode_url, + started_at=time.time(), + ) + self._login_session = login_session + self._qr_expired_count = 0 + self._last_inbound_error = "" + return login_session + + async def _poll_qr_status(self, login_session: OpenClawLoginSession) -> None: + endpoint = "ilink/bot/get_qrcode_status" + logger.debug("weixin_oc(%s): poll qrcode status", self.meta().id) + data = await self._request_json( + "GET", + endpoint, + params={"qrcode": login_session.qrcode}, + token_required=False, + timeout_ms=self.long_poll_timeout_ms, + headers={"iLink-App-ClientVersion": "1"}, + ) + status = str(data.get("status", "wait")).strip() + login_session.status = status + if status == "expired": + self._qr_expired_count += 1 + if self._qr_expired_count > 3: + login_session.error = "二维码已过期,超过重试次数,等待下次重试" + self._login_session = None + return + logger.warning( + "weixin_oc(%s): qr expired, refreshing (%s/%s)", + self.meta().id, + self._qr_expired_count, + 3, + ) + new_session = await self._start_login_session() + self._login_session = new_session + return + + if status == "confirmed": + bot_token = data.get("bot_token") + account_id = data.get("ilink_bot_id") + base_url = data.get("baseurl") + user_id = data.get("ilink_user_id") + if not bot_token: + login_session.error = "登录返回成功但未返回 bot_token" + return + login_session.bot_token = str(bot_token) + login_session.account_id = str(account_id) if account_id else None + login_session.base_url = str(base_url) if base_url else self.base_url + login_session.user_id = str(user_id) if user_id else None + self.token = login_session.bot_token + self.account_id = login_session.account_id + if login_session.base_url: + self.base_url = login_session.base_url.rstrip("/") + await self._save_account_state() + + def _message_text_from_item_list( + self, item_list: list[dict[str, Any]] | None + ) -> str: + if not item_list: + return "" + text_parts: list[str] = [] + for item in item_list: + item_type = int(item.get("type") or 0) + if item_type == 1: + text = str(item.get("text_item", {}).get("text", "")).strip() + if text: + text_parts.append(text) + elif item_type == 2: + text_parts.append("[图片]") + elif item_type == 3: + voice_text = str(item.get("voice_item", {}).get("text", "")).strip() + if voice_text: + text_parts.append(voice_text) + else: + text_parts.append("[语音]") + elif item_type == 4: + text_parts.append("[文件]") + elif item_type == 5: + text_parts.append("[视频]") + else: + ref = item.get("ref_msg") + if isinstance(ref, dict): + ref_item = ref.get("message_item") + if isinstance(ref_item, dict): + ref_text = str(self._message_text_from_item_list([ref_item])) + if ref_text: + text_parts.append(f"[引用:{ref_text}]") + return "\n".join(text_parts).strip() + + async def _item_list_to_components( + self, item_list: list[dict[str, Any]] | None + ) -> list[Any]: + if not item_list: + return [] + parts: list[Any] = [] + for item in item_list: + item_type = int(item.get("type") or 0) + if item_type == 1: + text = str(item.get("text_item", {}).get("text", "")).strip() + if text: + parts.append(Plain(text)) + continue + try: + media_component = await self._resolve_inbound_media_component(item) + except Exception as e: + logger.warning( + "weixin_oc(%s): resolve inbound media failed: %s", + self.meta().id, + e, + ) + media_component = None + if media_component is not None: + parts.append(media_component) + return parts + + async def _handle_inbound_message(self, msg: dict[str, Any]) -> None: + from_user_id = str(msg.get("from_user_id", "")).strip() + if not from_user_id: + logger.debug("weixin_oc: skip message with empty from_user_id.") + return + + context_token = str(msg.get("context_token", "")).strip() + if context_token: + self._context_tokens[from_user_id] = context_token + + item_list = cast(list[dict[str, Any]], msg.get("item_list", [])) + components = await self._item_list_to_components(item_list) + text = self._message_text_from_item_list(item_list) + message_id = str(msg.get("message_id") or msg.get("msg_id") or uuid.uuid4().hex) + create_time = msg.get("create_time_ms") or msg.get("create_time") + if isinstance(create_time, (int, float)) and create_time > 1_000_000_000_000: + ts = int(float(create_time) / 1000) + elif isinstance(create_time, (int, float)): + ts = int(create_time) + else: + ts = int(time.time()) + + abm = AstrBotMessage() + abm.self_id = self.meta().id + abm.sender = MessageMember(user_id=from_user_id, nickname=from_user_id) + abm.type = MessageType.FRIEND_MESSAGE + abm.session_id = from_user_id + abm.message_id = message_id + abm.message = components + abm.message_str = text + abm.timestamp = ts + abm.raw_message = msg + + self.commit_event( + WeixinOCMessageEvent( + message_str=text, + message_obj=abm, + platform_meta=self.meta(), + session_id=abm.session_id, + platform=self, + ) + ) + + async def _poll_inbound_updates(self) -> None: + data = await self._request_json( + "POST", + "ilink/bot/getupdates", + payload={ + "base_info": { + "channel_version": "astrbot", + }, + "get_updates_buf": self._sync_buf, + }, + token_required=True, + timeout_ms=self.long_poll_timeout_ms, + ) + ret = int(data.get("ret") or 0) + errcode = data.get("errcode", 0) + if ret != 0 and ret is not None: + errmsg = str(data.get("errmsg", "")) + self._last_inbound_error = f"ret={ret}, errcode={errcode}, errmsg={errmsg}" + logger.warning( + "weixin_oc(%s): getupdates error: %s", + self.meta().id, + self._last_inbound_error, + ) + return + if errcode and int(errcode) != 0: + errmsg = str(data.get("errmsg", "")) + self._last_inbound_error = f"ret={ret}, errcode={errcode}, errmsg={errmsg}" + logger.warning( + "weixin_oc(%s): getupdates error: %s", + self.meta().id, + self._last_inbound_error, + ) + return + + if data.get("get_updates_buf"): + self._sync_buf = str(data.get("get_updates_buf")) + await self._save_account_state() + + for msg in data.get("msgs", []) if isinstance(data.get("msgs"), list) else []: + if self._shutdown_event.is_set(): + return + if not isinstance(msg, dict): + continue + await self._handle_inbound_message(msg) + + def _message_chain_to_text(self, message_chain: MessageChain) -> str: + text = "" + for segment in message_chain.chain: + if isinstance(segment, Plain): + text += segment.text + return text.strip() + + async def _send_to_session( + self, user_id: str, text: str, _components: list[Any] | None = None + ) -> bool: + if not text: + text = self._message_chain_to_text(MessageChain(_components or [])) + if not text: + logger.warning( + "weixin_oc(%s): message without plain text is ignored", + self.meta().id, + ) + return False + return await self._send_items_to_session( + user_id, + [self._build_plain_text_item(text)], + ) + + async def send_by_session( + self, + session: MessageSesion, + message_chain: MessageChain, + ) -> None: + target_user = session.session_id + pending_text = "" + has_supported_segment = False + for segment in message_chain.chain: + if isinstance(segment, Plain): + pending_text += segment.text + continue + + if isinstance(segment, (Image, Video, File)): + has_supported_segment = True + await self._send_media_segment( + target_user, + segment, + text=pending_text.strip() or None, + ) + pending_text = "" + continue + + logger.debug( + "weixin_oc(%s): unsupported outbound segment type %s", + self.meta().id, + type(segment).__name__, + ) + + if pending_text: + has_supported_segment = True + await self._send_to_session(target_user, pending_text.strip()) + + if not has_supported_segment: + logger.warning( + "weixin_oc(%s): outbound message ignored, no supported segments", + self.meta().id, + ) + await super().send_by_session(session, message_chain) + + def meta(self) -> PlatformMetadata: + return self.metadata + + async def run(self) -> None: + try: + while not self._shutdown_event.is_set(): + if not self.token: + if not self._is_login_session_valid(self._login_session): + try: + self._login_session = await self._start_login_session() + self._qr_expired_count = 0 + except Exception as e: + logger.error( + "weixin_oc(%s): start login failed: %s", + self.meta().id, + e, + ) + await asyncio.sleep(5) + continue + + current_login = self._login_session + if current_login is None: + continue + + try: + await self._poll_qr_status(current_login) + except asyncio.TimeoutError: + logger.debug( + "weixin_oc(%s): qr status long-poll timeout", + self.meta().id, + ) + except Exception as e: + logger.error( + "weixin_oc(%s): poll qr status failed: %s", + self.meta().id, + e, + ) + current_login.error = str(e) + await asyncio.sleep(2) + + if self.token: + logger.info( + "weixin_oc(%s): login confirmed, account=%s", + self.meta().id, + self.account_id or "", + ) + continue + + if current_login.error: + await asyncio.sleep(2) + else: + await asyncio.sleep(self.qr_poll_interval) + continue + + await self._poll_inbound_updates() + except asyncio.CancelledError: + raise + except Exception as e: + logger.exception("weixin_oc(%s): run failed: %s", self.meta().id, e) + finally: + await self._close_http_session() + + async def terminate(self) -> None: + self._shutdown_event.set() + + def get_stats(self) -> dict: + stat = super().get_stats() + login_session = self._login_session + stat["weixin_oc"] = { + "configured": bool(self.token), + "account_id": self.account_id, + "base_url": self.base_url, + "qr_session_key": login_session.session_key if login_session else None, + "qr_status": login_session.status if login_session else None, + "qrcode": login_session.qrcode if login_session else None, + "qrcode_img_content": login_session.qrcode_img_content + if login_session + else None, + "qr_error": login_session.error if login_session else None, + "sync_buf_len": len(self._sync_buf), + "last_error": self._last_inbound_error, + } + return stat diff --git a/astrbot/core/platform/sources/weixin_oc/weixin_oc_event.py b/astrbot/core/platform/sources/weixin_oc/weixin_oc_event.py new file mode 100644 index 0000000000..0bc886db85 --- /dev/null +++ b/astrbot/core/platform/sources/weixin_oc/weixin_oc_event.py @@ -0,0 +1,87 @@ +from __future__ import annotations + +import asyncio +from typing import TYPE_CHECKING + +from astrbot.api.event import AstrMessageEvent, MessageChain +from astrbot.api.message_components import ( + At, + BaseMessageComponent, + File, + Image, + Plain, + Record, + Video, +) + +if TYPE_CHECKING: # pragma: no cover - typing helper + from .weixin_oc_adapter import WeixinOCAdapter + + +class WeixinOCMessageEvent(AstrMessageEvent): + def __init__( + self, + message_str, + message_obj, + platform_meta, + session_id, + platform: WeixinOCAdapter, + ) -> None: + super().__init__(message_str, message_obj, platform_meta, session_id) + self.platform = platform + + @staticmethod + def _segment_to_text(segment: BaseMessageComponent) -> str: + if isinstance(segment, Plain): + return segment.text + if isinstance(segment, Image): + return "[图片]" + if isinstance(segment, File): + return f"[文件:{segment.name}]" + if isinstance(segment, Video): + return "[视频]" + if isinstance(segment, Record): + return "[音频]" + if isinstance(segment, At): + return f"@{segment.name or segment.qq}" + return "[消息]" + + @staticmethod + def _build_plain_text(message: MessageChain) -> str: + return "".join( + WeixinOCMessageEvent._segment_to_text(seg) for seg in message.chain + ) + + async def send(self, message: MessageChain) -> None: + text = self._build_plain_text(message) + if not text: + return + await self.platform._send_to_session(self.session_id, text, message.chain) + await super().send(message) + + async def send_streaming(self, generator, use_fallback: bool = False): + if not use_fallback: + buffer = None + async for chain in generator: + if not buffer: + buffer = chain + else: + buffer.chain.extend(chain.chain) + if not buffer: + return None + await self.send(buffer) + return await super().send_streaming(generator, use_fallback) + + buffer = "" + async for chain in generator: + if not isinstance(chain, MessageChain): + continue + for component in chain.chain: + if not isinstance(component, Plain): + await self.send(MessageChain(chain=[component])) + await asyncio.sleep(1.2) + continue + buffer += component.text + if buffer.strip(): + await self.send(MessageChain([Plain(buffer)])) + return await super().send_streaming(generator, use_fallback) diff --git a/dashboard/package.json b/dashboard/package.json index d8ce1c7af9..00224a0f58 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -14,6 +14,7 @@ "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" }, "dependencies": { + "qrcode": "^1.5.4", "@guolao/vue-monaco-editor": "^1.5.4", "@tiptap/starter-kit": "2.1.7", "@tiptap/vue-3": "2.1.7", @@ -76,4 +77,4 @@ "lodash-es": "4.17.23" } } -} \ No newline at end of file +} diff --git a/dashboard/pnpm-lock.yaml b/dashboard/pnpm-lock.yaml index a3926a9534..775b52a2fa 100644 --- a/dashboard/pnpm-lock.yaml +++ b/dashboard/pnpm-lock.yaml @@ -72,6 +72,9 @@ importers: pinyin-pro: specifier: ^3.26.0 version: 3.28.0 + qrcode: + specifier: ^1.5.4 + version: 1.5.4 shiki: specifier: ^3.20.0 version: 3.22.0 @@ -537,66 +540,79 @@ packages: resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.59.0': resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.59.0': resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.59.0': resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.59.0': resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.59.0': resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.59.0': resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.59.0': resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.59.0': resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.59.0': resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.59.0': resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.59.0': resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.59.0': resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.59.0': resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} @@ -1277,6 +1293,10 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + caniuse-lite@1.0.30001778: resolution: {integrity: sha512-PN7uxFL+ExFJO61aVmP1aIEG4i9whQd4eoSCebav62UwDyp5OHh06zN4jqKSMePVgxHifCw1QJxdRkA1Pisekg==} @@ -1316,6 +1336,9 @@ packages: resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==} engines: {node: '>= 10.0'} + cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -1549,6 +1572,10 @@ packages: decache@4.6.2: resolution: {integrity: sha512-2LPqkLeu8XWHU8qNCS3kcF6sCcb5zIzvWaAHYSvPfwhdd7mHuah29NssMzrTYyHN4F5oFy2ko9OBYxegtU0FEw==} + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -1566,6 +1593,9 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + dijkstrajs@1.0.3: + resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} + dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -1585,6 +1615,9 @@ packages: electron-to-chromium@1.5.307: resolution: {integrity: sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==} + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + emojis-list@3.0.0: resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==} engines: {node: '>= 4'} @@ -1805,6 +1838,10 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -1945,6 +1982,10 @@ packages: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -2317,6 +2358,10 @@ packages: pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + pngjs@5.0.0: + resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} + engines: {node: '>=10.13.0'} + points-on-curve@0.2.0: resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} @@ -2423,6 +2468,11 @@ packages: resolution: {integrity: sha512-tsSGN1x3h569ZSU1u6diwhltLyfUWDp3YbFHedapTmpBl0B3P6U3+Qptg7xu+v+1io1EwhdPyyRHYbEw0KN2FA==} engines: {node: '>=20'} + qrcode@1.5.4: + resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==} + engines: {node: '>=10.13.0'} + hasBin: true + querystring@0.2.1: resolution: {integrity: sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==} engines: {node: '>=0.4.x'} @@ -2448,10 +2498,17 @@ packages: regex@6.1.0: resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -2534,6 +2591,9 @@ packages: engines: {node: '>=10'} hasBin: true + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -2585,6 +2645,10 @@ packages: peerDependencies: monaco-editor: ^0.52.2 + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + stringify-entities@4.0.4: resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} @@ -2975,6 +3039,9 @@ packages: webpack-cli: optional: true + which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -2987,6 +3054,10 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -2994,6 +3065,17 @@ packages: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} engines: {node: '>=12'} + y18n@4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + + yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + + yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -4154,6 +4236,8 @@ snapshots: callsites@3.1.0: {} + camelcase@5.3.1: {} + caniuse-lite@1.0.30001778: {} ccount@2.0.1: {} @@ -4201,6 +4285,12 @@ snapshots: dependencies: source-map: 0.6.1 + cliui@6.0.0: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -4445,6 +4535,8 @@ snapshots: dependencies: callsite: 1.0.0 + decamelize@1.2.0: {} + deep-is@0.1.4: {} delaunator@5.0.1: @@ -4459,6 +4551,8 @@ snapshots: dependencies: dequal: 2.0.3 + dijkstrajs@1.0.3: {} + dir-glob@3.0.1: dependencies: path-type: 4.0.0 @@ -4479,6 +4573,8 @@ snapshots: electron-to-chromium@1.5.307: {} + emoji-regex@8.0.0: {} + emojis-list@3.0.0: {} enhanced-resolve@5.20.0: @@ -4739,6 +4835,8 @@ snapshots: function-bind@1.1.2: {} + get-caller-file@2.0.5: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -4881,6 +4979,8 @@ snapshots: is-extglob@2.1.1: {} + is-fullwidth-code-point@3.0.0: {} + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 @@ -5231,6 +5331,8 @@ snapshots: mlly: 1.8.0 pathe: 2.0.3 + pngjs@5.0.0: {} + points-on-curve@0.2.0: {} points-on-path@0.2.1: @@ -5374,6 +5476,12 @@ snapshots: dependencies: hookified: 1.15.1 + qrcode@1.5.4: + dependencies: + dijkstrajs: 1.0.3 + pngjs: 5.0.0 + yargs: 15.4.1 + querystring@0.2.1: {} queue-microtask@1.2.3: {} @@ -5396,8 +5504,12 @@ snapshots: dependencies: regex-utilities: 2.3.0 + require-directory@2.1.1: {} + require-from-string@2.0.2: {} + require-main-filename@2.0.0: {} + resolve-from@4.0.0: {} resolve@1.22.11: @@ -5492,6 +5604,8 @@ snapshots: semver@7.7.4: {} + set-blocking@2.0.0: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -5553,6 +5667,12 @@ snapshots: shiki: 3.22.0 optional: true + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + stringify-entities@4.0.4: dependencies: character-entities-html4: 2.1.0 @@ -5929,6 +6049,8 @@ snapshots: - esbuild - uglify-js + which-module@2.0.1: {} + which@2.0.2: dependencies: isexe: 2.0.0 @@ -5939,10 +6061,37 @@ snapshots: word-wrap@1.2.5: {} + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrappy@1.0.2: {} xml-name-validator@4.0.0: {} + y18n@4.0.3: {} + + yargs-parser@18.1.3: + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + + yargs@15.4.1: + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 18.1.3 + yocto-queue@0.1.0: {} yup@1.2.0: diff --git a/dashboard/src/assets/mdi-subset/materialdesignicons-subset.css b/dashboard/src/assets/mdi-subset/materialdesignicons-subset.css index 7f734b0498..6f6f319576 100644 --- a/dashboard/src/assets/mdi-subset/materialdesignicons-subset.css +++ b/dashboard/src/assets/mdi-subset/materialdesignicons-subset.css @@ -1,4 +1,4 @@ -/* Auto-generated MDI subset – 229 icons */ +/* Auto-generated MDI subset – 230 icons */ /* Do not edit manually. Run: pnpm run subset-icons */ @font-face { @@ -720,6 +720,10 @@ content: "\F0A66"; } +.mdi-qrcode::before { + content: "\F0432"; +} + .mdi-refresh::before { content: "\F0450"; } diff --git a/dashboard/src/assets/mdi-subset/materialdesignicons-webfont-subset.woff b/dashboard/src/assets/mdi-subset/materialdesignicons-webfont-subset.woff index 20cd8f5c89e20145b7b71d6d4945f994e33da535..47daa82b65f006759a324f5544545f6e4e64f97d 100644 GIT binary patch delta 15802 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 Eg>V*u?EnA( delta 15733 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 G00022_Fpgn diff --git a/dashboard/src/components/shared/QrCodeViewer.vue b/dashboard/src/components/shared/QrCodeViewer.vue new file mode 100644 index 0000000000..89c9a879ac --- /dev/null +++ b/dashboard/src/components/shared/QrCodeViewer.vue @@ -0,0 +1,90 @@ + + + + + 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 acbf70c8c0..92853803ab 100644 --- a/dashboard/src/i18n/locales/en-US/features/config-metadata.json +++ b/dashboard/src/i18n/locales/en-US/features/config-metadata.json @@ -618,6 +618,30 @@ "description": "Send Replies via Webhook Only", "hint": "When enabled, all WeCom AI Bot replies are sent through msg_push_webhook_url. The message push webhook supports more message types (such as images, files, etc.). If you do not need the typing effect, it is strongly recommended to use this option. " }, + "weixin_oc_base_url": { + "description": "iLink API Base URL", + "hint": "Default: https://ilinkai.weixin.qq.com" + }, + "weixin_oc_bot_type": { + "description": "bot_type (QR login parameter)", + "hint": "Default: 3" + }, + "weixin_oc_qr_poll_interval": { + "description": "QR status poll interval (seconds)", + "hint": "Polling interval in seconds for QR code status." + }, + "weixin_oc_long_poll_timeout_ms": { + "description": "getUpdates long-poll timeout (ms)", + "hint": "Timeout parameter for polling messages." + }, + "weixin_oc_api_timeout_ms": { + "description": "HTTP timeout (ms)", + "hint": "Generic API request timeout." + }, + "weixin_oc_token": { + "description": "Token after login (optional)", + "hint": "Automatically written after QR login; can be filled manually for advanced scenarios." + }, "kook_bot_token": { "description": "Bot Token", "type": "string", diff --git a/dashboard/src/i18n/locales/en-US/features/platform.json b/dashboard/src/i18n/locales/en-US/features/platform.json index 64448ca1ea..a62b2f6806 100644 --- a/dashboard/src/i18n/locales/en-US/features/platform.json +++ b/dashboard/src/i18n/locales/en-US/features/platform.json @@ -123,6 +123,13 @@ "unknown": "Unknown", "errors": "error(s)" }, + "platformQr": { + "title": "QR Login", + "show": "Show QR", + "status": "QR Status", + "waiting": "Waiting for QR", + "close": "Close" + }, "errorDialog": { "title": "Error Details", "platformId": "Platform ID", 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 6424a3f705..4cde9b932a 100644 --- a/dashboard/src/i18n/locales/ru-RU/features/config-metadata.json +++ b/dashboard/src/i18n/locales/ru-RU/features/config-metadata.json @@ -614,6 +614,30 @@ "description": "Отправлять ответы только через Webhook", "hint": "Все ответы WeCom AI Bot будут идти через вебхук пуш-сообщений. Поддерживает больше типов контента." }, + "weixin_oc_base_url": { + "description": "URL API iLink", + "hint": "Значение по умолчанию: https://ilinkai.weixin.qq.com" + }, + "weixin_oc_bot_type": { + "description": "bot_type (параметр входа по QR)", + "hint": "Значение по умолчанию: 3" + }, + "weixin_oc_qr_poll_interval": { + "description": "Интервал опроса статуса QR (сек)", + "hint": "Пауза между запросами статуса QR-кода." + }, + "weixin_oc_long_poll_timeout_ms": { + "description": "Таймаут long-poll getUpdates (мс)", + "hint": "Параметр таймаута запроса получения сообщений." + }, + "weixin_oc_api_timeout_ms": { + "description": "Таймаут HTTP запроса (мс)", + "hint": "Общий таймаут для HTTP запросов." + }, + "weixin_oc_token": { + "description": "Token после входа (опционально)", + "hint": "Автоматически сохраняется после QR логина; для сложных сценариев можно ввести вручную." + }, "kook_bot_token": { "description": "Токен бота", "type": "string", diff --git a/dashboard/src/i18n/locales/ru-RU/features/platform.json b/dashboard/src/i18n/locales/ru-RU/features/platform.json index 04c7613a93..42bcae42e6 100644 --- a/dashboard/src/i18n/locales/ru-RU/features/platform.json +++ b/dashboard/src/i18n/locales/ru-RU/features/platform.json @@ -116,14 +116,21 @@ "error": "Ошибка" }, "runtimeStatus": { - "running": "Работает", - "error": "Ошибка", - "pending": "Ожидание", - "stopped": "Остановлен", - "unknown": "Неизвестно", - "errors": "ошибок" - }, - "errorDialog": { + "running": "Работает", + "error": "Ошибка", + "pending": "Ожидание", + "stopped": "Остановлен", + "unknown": "Неизвестно", + "errors": "ошибок" + }, + "platformQr": { + "title": "QR вход", + "show": "Показать QR", + "status": "Статус QR", + "waiting": "Ожидание QR", + "close": "Закрыть" + }, + "errorDialog": { "title": "Детали ошибки", "platformId": "ID платформы", "errorCount": "Кол-во ошибок", @@ -132,4 +139,4 @@ "traceback": "Стек вызовов", "close": "Закрыть" } -} \ No newline at end of file +} 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 afca8e7fcb..6023f3eafb 100644 --- a/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json +++ b/dashboard/src/i18n/locales/zh-CN/features/config-metadata.json @@ -620,6 +620,30 @@ "description": "仅使用 Webhook 发送消息", "hint": "可选。启用后,企业微信智能机器人的所有回复都改为通过消息推送 Webhook 发送。消息推送 Webhook 支持更多的消息类型(如图片、文件等)。如果不需要打字机效果,强烈建议使用此选项。" }, + "weixin_oc_base_url": { + "description": "iLink API 地址", + "hint": "默认值: https://ilinkai.weixin.qq.com" + }, + "weixin_oc_bot_type": { + "description": "扫码参数 bot_type", + "hint": "默认值: 3" + }, + "weixin_oc_qr_poll_interval": { + "description": "二维码状态轮询间隔(秒)", + "hint": "每隔多少秒轮询一次二维码状态。" + }, + "weixin_oc_long_poll_timeout_ms": { + "description": "getUpdates 长轮询超时时间(毫秒)", + "hint": "会话消息拉取接口超时参数。" + }, + "weixin_oc_api_timeout_ms": { + "description": "HTTP 请求超时(毫秒)", + "hint": "通用 API 请求超时参数。" + }, + "weixin_oc_token": { + "description": "登录后 token(可留空)", + "hint": "扫码登录成功后会自动写入;高级场景可手动填写。" + }, "kook_bot_token": { "description": "机器人 Token", "type": "string", diff --git a/dashboard/src/i18n/locales/zh-CN/features/platform.json b/dashboard/src/i18n/locales/zh-CN/features/platform.json index f26d9507dd..75dc5a2a6f 100644 --- a/dashboard/src/i18n/locales/zh-CN/features/platform.json +++ b/dashboard/src/i18n/locales/zh-CN/features/platform.json @@ -123,6 +123,13 @@ "unknown": "未知", "errors": "个错误" }, + "platformQr": { + "title": "二维码登录", + "show": "查看二维码", + "status": "二维码状态", + "waiting": "等待二维码", + "close": "关闭" + }, "errorDialog": { "title": "错误详情", "platformId": "平台 ID", diff --git a/dashboard/src/utils/platformUtils.js b/dashboard/src/utils/platformUtils.js index 87523f3ad1..905270abce 100644 --- a/dashboard/src/utils/platformUtils.js +++ b/dashboard/src/utils/platformUtils.js @@ -12,6 +12,8 @@ export function getPlatformIcon(name) { return new URL('@/assets/images/platform_logos/onebot.png', import.meta.url).href } else if (name === 'qq_official' || name === 'qq_official_webhook') { return new URL('@/assets/images/platform_logos/qq.png', import.meta.url).href + } else if (name === 'weixin_oc' || name === 'weixin_oc') { + return new URL('@/assets/images/platform_logos/wechat.png', import.meta.url).href } else if (name === 'wecom' || name === 'wecom_ai_bot') { return new URL('@/assets/images/platform_logos/wecom.png', import.meta.url).href } else if (name === 'weixin_official_account') { diff --git a/dashboard/src/views/PlatformPage.vue b/dashboard/src/views/PlatformPage.vue index f50df95546..ebdc6bcd86 100644 --- a/dashboard/src/views/PlatformPage.vue +++ b/dashboard/src/views/PlatformPage.vue @@ -57,6 +57,21 @@ {{ getPlatformStat(item.id)?.error_count }} {{ tm('runtimeStatus.errors') }} +
+ + mdi-qrcode + {{ tm('platformQr.show') }} + +
+ + + + mdi-qrcode + {{ tm('platformQr.title') }} + + +
+ {{ tm('platformQr.status') }}: {{ getPlatformQrLoginStat(currentQrPlatformId)?.qr_status || tm('platformQr.waiting') }} +
+ +
+ + + + {{ tm('platformQr.close') }} + + +
+
+ @@ -194,9 +233,10 @@ import WaitingForRestart from '@/components/shared/WaitingForRestart.vue'; import ConsoleDisplayer from '@/components/shared/ConsoleDisplayer.vue'; import ItemCard from '@/components/shared/ItemCard.vue'; import AddNewPlatform from '@/components/platform/AddNewPlatform.vue'; +import QrCodeViewer from '@/components/shared/QrCodeViewer.vue'; import { useCommonStore } from '@/stores/common'; import { useI18n, useModuleI18n, mergeDynamicTranslations } from '@/i18n/composables'; -import { getPlatformIcon, getTutorialLink } from '@/utils/platformUtils'; +import { getPlatformIcon } from '@/utils/platformUtils'; import { askForConfirmation as askForConfirmationDialog, useConfirmDialog @@ -209,7 +249,8 @@ export default { WaitingForRestart, ConsoleDisplayer, ItemCard, - AddNewPlatform + AddNewPlatform, + QrCodeViewer, }, setup() { const { t } = useI18n(); @@ -248,6 +289,8 @@ export default { // 错误详情对话框 showErrorDialog: false, currentErrorPlatform: null, + showQrDialog: false, + currentQrPlatformId: "", store: useCommonStore() } @@ -326,8 +369,8 @@ export default { }); }, - getPlatformStats() { - axios.get('/api/platform/stats').then((res) => { + async getPlatformStats() { + await axios.get('/api/platform/stats').then((res) => { if (res.data.status === 'ok') { // 将数组转换为以 id 为 key 的对象,方便查找 const stats = {}; @@ -345,6 +388,31 @@ export default { return this.platformStats[platformId] || null; }, + hasQrPayload(platformId) { + const stat = this.getPlatformQrLoginStat(platformId); + return Boolean(stat?.qrcode_img_content || stat?.qrcode); + }, + + getPlatformQrLoginStat(platformId) { + const stat = this.getPlatformStat(platformId); + if (stat?.weixin_oc) { + return stat.weixin_oc; + } + if (stat && typeof stat === "object") { + for (const value of Object.values(stat)) { + if (value && typeof value === "object" && ("qrcode_img_content" in value || "qrcode" in value)) { + return value; + } + } + } + return null; + }, + + openPlatformQrDialog(platformId) { + this.currentQrPlatformId = platformId; + this.showQrDialog = true; + }, + getStatusColor(status) { switch (status) { case 'running': return 'success'; @@ -618,4 +686,14 @@ export default { max-height: 300px; overflow-y: auto; } + +.platform-qr-chip { + margin-top: 4px; +} + +.platform-qr-status { + font-size: 13px; + margin-bottom: 10px; + color: rgba(0, 0, 0, 0.7); +} diff --git a/docs/.vitepress/config.mjs b/docs/.vitepress/config.mjs index 9932d7a700..7150ebfae5 100644 --- a/docs/.vitepress/config.mjs +++ b/docs/.vitepress/config.mjs @@ -92,6 +92,7 @@ export default defineConfig({ { text: "企微应用", link: "/wecom" }, { text: "企微智能机器人", link: "/wecom_ai_bot" }, { text: "微信公众号", link: "/weixin-official-account" }, + { text: "个人微信", link: "/weixin_oc" }, { text: "飞书", link: "/lark" }, { text: "钉钉", link: "/dingtalk" }, { text: "Telegram", link: "/telegram" }, diff --git a/docs/zh/platform/weixin_oc.md b/docs/zh/platform/weixin_oc.md new file mode 100644 index 0000000000..0770b1cd15 --- /dev/null +++ b/docs/zh/platform/weixin_oc.md @@ -0,0 +1,74 @@ +# 接入个人微信 + +AstrBot 支持通过 `个人微信` 适配器接入微信个人号。该适配器基于**腾讯微信官方** `openclaw-weixin` 接口实现,使用扫码登录和长轮询收发消息,不需要配置 Webhook 回调地址。 + +## 支持的消息类型 + +> 版本要求:建议使用包含 `weixin_oc` 适配器的最新版本。 + +| 消息类型 | 是否支持接收 | 是否支持发送 | 备注 | +| --- | --- | --- | --- | +| 文本 | 是 | 是 | | +| 图片 | 是 | 是 | 接收时会下载并解密到本地临时目录 | +| 语音 | 是 | 是* | *微信云端会自动转录成文本,无需本地转录 | +| 视频 | 是 | 是 | 接收时会下载并解密到本地临时目录 | +| 文件 | 是 | 是 | 接收时会下载并解密到本地临时目录 | + +## 创建机器人 + +1. 进入 AstrBot WebUI。 +2. 点击左侧栏 `机器人`。 +3. 点击右上角 `+ 创建机器人`。 +4. 选择 `个人微信`。 + +## 配置项说明 + +通常只需要关注以下几个配置: + +- `ID(id)`:随意填写,用于区分不同的机器人实例。 +- `启用(enable)`:勾选。 +- `基础地址(weixin_oc_base_url)`:默认保持 `https://ilinkai.weixin.qq.com` 即可。 +- `CDN 基础地址(weixin_oc_cdn_base_url)`:默认保持 `https://novac2c.cdn.weixin.qq.com/c2c` 即可。 +- `Bot Type(weixin_oc_bot_type)`:默认保持 `3` 即可。 + +以下配置一般无需修改,除非您明确知道用途: + +- `二维码轮询间隔(weixin_oc_qr_poll_interval)` +- `长轮询超时(weixin_oc_long_poll_timeout_ms)` +- `API 超时(weixin_oc_api_timeout_ms)` + +> [!TIP] +> `token` 和 `account_id` 会在扫码登录成功后由 AstrBot 自动保存,通常不需要手动填写。 + +## 扫码登录 + +1. 填好配置后点击 `保存`。 +2. 返回机器人列表,AstrBot 会自动向微信接口申请登录二维码。 +3. 在机器人卡片中点击二维码入口查看二维码。 +4. 使用手机微信扫码,并在微信内确认登录。 + +登录成功后,AstrBot 会自动保存登录态。后续重启时,如果登录态仍有效,通常不需要再次扫码。 + +> [!NOTE] +> 如果二维码过期,AstrBot 会自动重新申请新的二维码。刷新后请使用新的二维码重新扫码。 + +## 验证 + +登录成功后,用微信给该个人微信会话发送一条消息,例如 `help` 或 `/help`。如果 AstrBot 能正常回复,说明接入成功。 + +也可以在 WebUI `控制台` 中观察日志,确认适配器已经完成登录并开始轮询消息。 + +## 多媒体文件保存位置 + +接收到的图片、视频、文件、语音会被 AstrBot 下载并解密后保存到本地临时目录: + +`data/temp` + +这些文件属于 AstrBot 的临时缓存文件,后续插件、Agent 或文件服务可以继续读取和处理。 + +## 已知说明 + +- 该适配器通过扫码登录个人微信,接入方式与微信公众号、企业微信不同。 +- 不需要配置公网回调地址,也不需要开启统一 Webhook 模式。 +- 如果发送图片、视频、文件异常,建议先检查 `weixin_oc_base_url` 与 `weixin_oc_cdn_base_url` 是否保持默认值,并查看控制台日志中的上传错误信息。 +- 语音消息当前仅实现接收,暂未实现个人微信语音主动发送。 From 038a884ebb91c7ea32345f63a6618b3992a2f8cf Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sun, 22 Mar 2026 12:39:54 +0800 Subject: [PATCH 2/8] feat(weixin): update documentation for personal WeChat integration and add QR code image --- docs/.vitepress/config.mjs | 1 + docs/en/platform/weixin_oc.md | 74 +++++++++++++++++++++++++++ docs/zh/platform/weixin_oc.md | 23 +++++---- docs/zh/platform/weixin_qr_entry.png | Bin 0 -> 46799 bytes 4 files changed, 88 insertions(+), 10 deletions(-) create mode 100644 docs/en/platform/weixin_oc.md create mode 100644 docs/zh/platform/weixin_qr_entry.png diff --git a/docs/.vitepress/config.mjs b/docs/.vitepress/config.mjs index 7150ebfae5..5b92e97d2f 100644 --- a/docs/.vitepress/config.mjs +++ b/docs/.vitepress/config.mjs @@ -327,6 +327,7 @@ export default defineConfig({ { text: "WeCom Application", link: "/wecom" }, { text: "WeCom AI Bot", link: "/wecom_ai_bot" }, { text: "WeChat Official Account", link: "/weixin-official-account" }, + { text: "Personal WeChat", link: "/weixin_oc" }, { text: "Lark", link: "/lark" }, { text: "DingTalk", link: "/dingtalk" }, { text: "Telegram", link: "/telegram" }, diff --git a/docs/en/platform/weixin_oc.md b/docs/en/platform/weixin_oc.md new file mode 100644 index 0000000000..8234e615a6 --- /dev/null +++ b/docs/en/platform/weixin_oc.md @@ -0,0 +1,74 @@ +# Connect Personal WeChat + +> Introduced in v4.22.0. + +AstrBot supports connecting a personal WeChat account through the `Personal WeChat` adapter. This adapter is implemented on top of Tencent's official `openclaw-weixin` interface, uses QR-code login plus long polling, and does not require a Webhook callback URL. + +> [!NOTE] +> Please upgrade your mobile WeChat to a recent version. +> +> **iOS**: >= 4.0.70 + +## Supported Message Types + +| Message Type | Receive | Send | Notes | +| --- | --- | --- | --- | +| Text | Yes | Yes | | +| Image | Yes | Yes | Downloaded and decrypted into the local temp directory on receive | +| Voice | Yes | Yes* | *WeChat cloud-side transcription is used, so no local transcription is required | +| Video | Yes | Yes | Downloaded and decrypted into the local temp directory on receive | +| File | Yes | Yes | Downloaded and decrypted into the local temp directory on receive | + +## Create the Bot + +1. Open AstrBot WebUI. +2. Click `Bots` in the left sidebar. +3. Click `+ Create Bot` in the upper-right corner. +4. Select `Personal WeChat`. + +## Configuration Notes + +In most cases, you only need to pay attention to these fields: + +- `ID(id)`: Any value you like, used to distinguish different bot instances. +- `Enable(enable)`: Turn it on. + +Leave the remaining options at their default values unless you explicitly know you need to change them: + +- `QR Poll Interval (weixin_oc_qr_poll_interval)` +- `Long Poll Timeout (weixin_oc_long_poll_timeout_ms)` +- `API Timeout (weixin_oc_api_timeout_ms)` + +> [!TIP] +> `token` and `account_id` are saved automatically by AstrBot after QR login succeeds. You normally do not need to fill them manually. + +## QR Login + +1. Fill in the configuration and click `Save`. +2. Return to the bot list. AstrBot will automatically request a login QR code from WeChat. +3. On the bot card, click `View QR Code` to open the QR dialog. +4. Scan it with WeChat on your phone, then confirm the login inside WeChat. + +After login succeeds, AstrBot will automatically persist the login state. On later restarts, if the session is still valid, you usually do not need to scan again. + +> [!NOTE] +> If the QR code expires, AstrBot will automatically request a new one. Please scan the refreshed QR code instead of the old one. + +## Verification + +After login succeeds, send a message from WeChat. If AstrBot replies normally, the integration is working. + +You can also watch the `Console` page in WebUI to confirm that the adapter has completed login and started polling messages. + +## Media File Storage + +Received images, videos, files, and voice messages are downloaded and decrypted into AstrBot's local temporary directory: + +`data/temp` + +These files are temporary cached files and can be further used by plugins, agents, or the file service. + +## Notes + +- This adapter logs in by scanning a QR code with a personal WeChat account, so its setup flow is different from WeChat Official Account and WeCom. +- No public callback URL is required, and Unified Webhook Mode is not needed. diff --git a/docs/zh/platform/weixin_oc.md b/docs/zh/platform/weixin_oc.md index 0770b1cd15..dfbc7f535e 100644 --- a/docs/zh/platform/weixin_oc.md +++ b/docs/zh/platform/weixin_oc.md @@ -1,10 +1,15 @@ # 接入个人微信 +> v4.22.0 引入。 + AstrBot 支持通过 `个人微信` 适配器接入微信个人号。该适配器基于**腾讯微信官方** `openclaw-weixin` 接口实现,使用扫码登录和长轮询收发消息,不需要配置 Webhook 回调地址。 -## 支持的消息类型 +> [!NOTE] +> 需要升级到最新的手机微信版本: +> +> **iOS**: >= 4.0.70 -> 版本要求:建议使用包含 `weixin_oc` 适配器的最新版本。 +## 支持的消息类型 | 消息类型 | 是否支持接收 | 是否支持发送 | 备注 | | --- | --- | --- | --- | @@ -27,11 +32,8 @@ AstrBot 支持通过 `个人微信` 适配器接入微信个人号。该适配 - `ID(id)`:随意填写,用于区分不同的机器人实例。 - `启用(enable)`:勾选。 -- `基础地址(weixin_oc_base_url)`:默认保持 `https://ilinkai.weixin.qq.com` 即可。 -- `CDN 基础地址(weixin_oc_cdn_base_url)`:默认保持 `https://novac2c.cdn.weixin.qq.com/c2c` 即可。 -- `Bot Type(weixin_oc_bot_type)`:默认保持 `3` 即可。 -以下配置一般无需修改,除非您明确知道用途: +其余配置**保持默认即可**,一般无需修改,除非您明确知道用途: - `二维码轮询间隔(weixin_oc_qr_poll_interval)` - `长轮询超时(weixin_oc_long_poll_timeout_ms)` @@ -44,9 +46,11 @@ AstrBot 支持通过 `个人微信` 适配器接入微信个人号。该适配 1. 填好配置后点击 `保存`。 2. 返回机器人列表,AstrBot 会自动向微信接口申请登录二维码。 -3. 在机器人卡片中点击二维码入口查看二维码。 +3. 在**机器人卡片**中点击 “查看二维码” 按钮,会弹出二维码对话框。 4. 使用手机微信扫码,并在微信内确认登录。 +![微信二维码入口](weixin_qr_entry.png) + 登录成功后,AstrBot 会自动保存登录态。后续重启时,如果登录态仍有效,通常不需要再次扫码。 > [!NOTE] @@ -54,7 +58,7 @@ AstrBot 支持通过 `个人微信` 适配器接入微信个人号。该适配 ## 验证 -登录成功后,用微信给该个人微信会话发送一条消息,例如 `help` 或 `/help`。如果 AstrBot 能正常回复,说明接入成功。 +登录成功后,用微信发送一条消息。如果 AstrBot 能正常回复,说明接入成功。 也可以在 WebUI `控制台` 中观察日志,确认适配器已经完成登录并开始轮询消息。 @@ -70,5 +74,4 @@ AstrBot 支持通过 `个人微信` 适配器接入微信个人号。该适配 - 该适配器通过扫码登录个人微信,接入方式与微信公众号、企业微信不同。 - 不需要配置公网回调地址,也不需要开启统一 Webhook 模式。 -- 如果发送图片、视频、文件异常,建议先检查 `weixin_oc_base_url` 与 `weixin_oc_cdn_base_url` 是否保持默认值,并查看控制台日志中的上传错误信息。 -- 语音消息当前仅实现接收,暂未实现个人微信语音主动发送。 + diff --git a/docs/zh/platform/weixin_qr_entry.png b/docs/zh/platform/weixin_qr_entry.png new file mode 100644 index 0000000000000000000000000000000000000000..b376bce488fd519ec087bf7b1f710eeb334f0c88 GIT binary patch literal 46799 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