From 539bebd58bf5e6f4149b8b21b3c16e61e2ea2299 Mon Sep 17 00:00:00 2001 From: liuwei Date: Thu, 7 May 2026 12:16:11 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E9=BD=90864=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E9=80=82=E9=85=8D=E5=B9=B6=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E8=81=94=E8=B0=83=E5=88=86=E5=B1=82=E7=BB=93=E8=AE=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修正 send_voice 到真实 SendVoice 路由 - 为 864 补上名片发送与视频发送的初版适配入口 - 更新路线图,记录消息接口在未建立连接对象时返回该链接不存在的联调结论 --- docs/wechat_ipad多版本Server适配路线图.md | 1 + wechat_ipad/providers/server_864/message.py | 63 +++++++++++++++++++-- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/docs/wechat_ipad多版本Server适配路线图.md b/docs/wechat_ipad多版本Server适配路线图.md index 51967d7..004806b 100644 --- a/docs/wechat_ipad多版本Server适配路线图.md +++ b/docs/wechat_ipad多版本Server适配路线图.md @@ -32,6 +32,7 @@ - 已实现 864 第一版登录、初始化等待、HTTP 消息轮询、联系人、群信息、资料与朋友圈基础接口 - 已在 [robot.py](/d:/learn/abot/robot.py:1) 中为 864 增加登录态硬隔离,默认不再回读 855 的历史 `config.toml` 与动态字段 - 已基于真实 864 服务联调修正首批路由差异:二维码返回结构、联系人详情路由、群公告路由、二维码有效期倒计时同步 +- 已确认 864 的消息发送类接口在“尚未建立连接对象”时会优先返回“该链接不存在”,这与登录态接口返回“需要重新登录”属于不同阶段 当前尚未完成的关键项: diff --git a/wechat_ipad/providers/server_864/message.py b/wechat_ipad/providers/server_864/message.py index 8d47931..cf1d7e9 100644 --- a/wechat_ipad/providers/server_864/message.py +++ b/wechat_ipad/providers/server_864/message.py @@ -161,7 +161,11 @@ class MessageMixin(Server864APIClientBase): voice_base64 = await self._read_base64_payload(voice) data = await self._request_data( "post", - "/message/UploadVoiceRequest", + # 864 的语音发送真实路由是 `/message/SendVoice`: + # 1. controller 方法名虽然叫 `UploadVoiceRequestApi`; + # 2. 但 router.go 暴露给外部的路径是 `SendVoice`; + # 3. 这里按真实注册路由适配,避免后续一接登录态就踩 404。 + "/message/SendVoice", json_body={ "ToUserName": wxid, "VoiceData": voice_base64, @@ -233,13 +237,60 @@ class MessageMixin(Server864APIClientBase): return True async def send_video_message(self, wxid: str, video: Union[str, bytes, os.PathLike], image=None): - """864 首版暂未做视频主动发送,先明确抛错避免静默失败。""" - del wxid, video, image - raise NotImplementedError("server_864 第一版暂未实现 send_video_message,可后续补 CDN 视频发送适配") + """发送视频消息。 + + 当前实现说明: + 1. 864 走的是 `CdnUploadVideo` 路由,而不是 855 的 `SendVideo`; + 2. body 里的 `VideoData` 在 JSON 中要传整数数组,因此这里会把视频字节展开成 `list[int]`; + 3. 返回结构和旧项目三元组并不完全一致,所以仍复用本地兜底消息回执。 + """ + return await self._queue_message(self._send_video_message, wxid, video, image) + + async def _send_video_message(self, wxid: str, video: Union[str, bytes, os.PathLike], image=None): + if isinstance(video, str): + video_bytes = base64.b64decode(video.split(",", 1)[1] if "," in video else video) + elif isinstance(video, bytes): + video_bytes = video + elif isinstance(video, os.PathLike): + async with aiofiles.open(video, "rb") as f: + video_bytes = await f.read() + else: + raise ValueError("video should be str, bytes, or path") + + thumb_base64 = "" + if image is not None: + thumb_base64 = await self._read_base64_payload(image) + + data = await self._request_data( + "post", + "/message/CdnUploadVideo", + json_body={ + "ToUserName": wxid, + "VideoData": list(video_bytes), + "ThumbData": thumb_base64, + }, + timeout=120, + ) + return self._extract_send_message_result(data, fallback_target=wxid) async def send_card_message(self, wxid: str, card_wxid: str, card_nickname: str, card_alias: str = ""): - del wxid, card_wxid, card_nickname, card_alias - raise NotImplementedError("server_864 第一版暂未实现 send_card_message") + """发送名片消息。""" + return await self._queue_message(self._send_card_message, wxid, card_wxid, card_nickname, card_alias) + + async def _send_card_message(self, wxid: str, card_wxid: str, card_nickname: str, card_alias: str = ""): + data = await self._request_data( + "post", + "/message/ShareCardMessage", + json_body={ + "ToUserName": wxid, + "CardWxId": card_wxid, + "CardNickName": card_nickname, + "CardAlias": card_alias, + "CardFlag": 0, + }, + timeout=20, + ) + return self._extract_send_message_result(data, fallback_target=wxid) async def send_app_message(self, wxid: str, xml: str, type: int): data = await self._request_data(