855 协议版本-调整完毕内容

This commit is contained in:
liuwei
2025-04-30 13:22:33 +08:00
parent 869bce8a18
commit 454d084715
88 changed files with 1565 additions and 7816 deletions

View File

@@ -8,7 +8,7 @@ from pathlib import Path
from typing import Union
import aiohttp
import logging
from loguru import logger
from pymediainfo import MediaInfo
import pysilk
@@ -24,6 +24,7 @@ class MessageMixin(WechatAPIClientBase):
super().__init__(ip, port)
self._message_queue = Queue()
self._is_processing = False
self.logging = logger
async def _process_message_queue(self):
"""
@@ -62,7 +63,13 @@ class MessageMixin(WechatAPIClientBase):
async def revoke_message(self, wxid: str, client_msg_id: int, create_time: int, new_msg_id: int) -> bool:
"""撤回消息。
{
"ClientMsgId": 0,
"CreateTime": 0,
"NewMsgId": 0,
"ToUserName": "string",
"Wxid": "string"
}
Args:
wxid (str): 接收人wxid
client_msg_id (int): 发送消息的返回值
@@ -81,16 +88,17 @@ class MessageMixin(WechatAPIClientBase):
raise UserLoggedOut("请先登录")
async with aiohttp.ClientSession() as session:
json_param = {"Wxid": self.wxid, "ToWxid": wxid, "ClientMsgId": client_msg_id, "CreateTime": create_time,
json_param = {"Wxid": self.wxid, "ToUserName": wxid, "ClientMsgId": client_msg_id,
"CreateTime": create_time,
"NewMsgId": new_msg_id}
response = await session.post(f'http://{self.ip}:{self.port}/RevokeMsg', json=json_param)
response = await session.post(f'http://{self.ip}:{self.port}/api/Msg/Revoke', json=json_param)
json_resp = await response.json()
if json_resp.get("Success"):
logging.info("消息撤回成功: 对方wxid:{} ClientMsgId:{} CreateTime:{} NewMsgId:{}",
wxid,
client_msg_id,
new_msg_id)
self.logging.info("消息撤回成功: 对方wxid:{} ClientMsgId:{} CreateTime:{} NewMsgId:{}",
wxid,
client_msg_id,
new_msg_id)
return True
else:
self.error_handler(json_resp)
@@ -131,10 +139,10 @@ class MessageMixin(WechatAPIClientBase):
async with aiohttp.ClientSession() as session:
json_param = {"Wxid": self.wxid, "ToWxid": wxid, "Content": content, "Type": 1, "At": at_str}
response = await session.post(f'http://{self.ip}:{self.port}/SendTextMsg', json=json_param)
response = await session.post(f'http://{self.ip}:{self.port}/api/Msg/SendTxt', json=json_param)
json_resp = await response.json()
if json_resp.get("Success"):
logging.info("发送文字消息: 对方wxid:{} at:{} 内容:{}", wxid, at, content)
self.logging.info("发送文字消息: 对方wxid:{} at:{} 内容:{}", wxid, at, content)
data = json_resp.get("Data")
return data.get("List")[0].get("ClientMsgid"), data.get("List")[0].get("Createtime"), data.get("List")[
0].get("NewMsgId")
@@ -163,7 +171,6 @@ class MessageMixin(WechatAPIClientBase):
int, int, int]:
if not self.wxid:
raise UserLoggedOut("请先登录")
if isinstance(image, str):
pass
@@ -177,12 +184,12 @@ class MessageMixin(WechatAPIClientBase):
async with aiohttp.ClientSession() as session:
json_param = {"Wxid": self.wxid, "ToWxid": wxid, "Base64": image}
response = await session.post(f'http://{self.ip}:{self.port}/SendImageMsg', json=json_param)
response = await session.post(f'http://{self.ip}:{self.port}/api/Msg/UploadImg', json=json_param)
json_resp = await response.json()
if json_resp.get("Success"):
json_param.pop('Base64')
logging.info("发送图片消息: 对方wxid:{} 图片base64略", wxid)
self.logging.info("发送图片消息: 对方wxid:{} 图片base64略", wxid)
data = json_resp.get("Data")
return data.get("ClientImgId").get("string"), data.get("CreateTime"), data.get("Newmsgid")
else:
@@ -240,18 +247,18 @@ class MessageMixin(WechatAPIClientBase):
# 打印预估时间300KB/s
predict_time = int(file_len / 1024 / 300)
logging.info("开始发送视频: 对方wxid:{} 视频base64略 图片base64略 预计耗时:{}", wxid, predict_time)
self.logging.info("开始发送视频: 对方wxid:{} 视频base64略 图片base64略 预计耗时:{}", wxid, predict_time)
async with aiohttp.ClientSession() as session:
json_param = {"Wxid": self.wxid, "ToWxid": wxid, "Base64": vid_base64, "ImageBase64": image_base64,
"PlayLength": duration}
async with session.post(f'http://{self.ip}:{self.port}/SendVideoMsg', json=json_param) as resp:
async with session.post(f'http://{self.ip}:{self.port}/api/Msg/SendVideo', json=json_param) as resp:
json_resp = await resp.json()
if json_resp.get("Success"):
json_param.pop('Base64')
json_param.pop('ImageBase64')
logging.info("发送视频成功: 对方wxid:{} 时长:{} 视频base64略 图片base64略", wxid, duration)
self.logging.info("发送视频成功: 对方wxid:{} 时长:{} 视频base64略 图片base64略", wxid, duration)
data = json_resp.get("Data")
return data.get("clientMsgId"), data.get("newMsgId")
else:
@@ -277,12 +284,12 @@ class MessageMixin(WechatAPIClientBase):
"""
return await self._queue_message(self._send_voice_message, wxid, voice, format)
async def _send_voice_message(self, wxid: str, voice: Union[str, bytes, os.PathLike], format: str = "amr") -> \
async def _send_voice_message(self, wxid: str, voice: Union[str, bytes, os.PathLike], format: str = "AMR") -> \
tuple[int, int, int]:
if not self.wxid:
raise UserLoggedOut("请先登录")
elif format not in ["amr", "wav", "mp3"]:
elif format not in ["AMR", "WAVE", "MP3", "SILK", "SPEEX"]:
raise ValueError("format must be one of amr, wav, mp3")
# read voice to byte
@@ -297,15 +304,15 @@ class MessageMixin(WechatAPIClientBase):
raise ValueError("voice should be str, bytes, or path")
# get voice duration and b64
if format.lower() == "amr":
if format.lower() == "AMR":
audio = AudioSegment.from_file(BytesIO(voice_byte), format="amr")
voice_base64 = base64.b64encode(voice_byte).decode()
elif format.lower() == "wav":
elif format.lower() == "WAVE":
audio = AudioSegment.from_file(BytesIO(voice_byte), format="wav").set_channels(1)
audio = audio.set_frame_rate(self._get_closest_frame_rate(audio.frame_rate))
voice_base64 = base64.b64encode(
await pysilk.async_encode(audio.raw_data, sample_rate=audio.frame_rate)).decode()
elif format.lower() == "mp3":
elif format.lower() == "MP3":
audio = AudioSegment.from_file(BytesIO(voice_byte), format="mp3").set_channels(1)
audio = audio.set_frame_rate(self._get_closest_frame_rate(audio.frame_rate))
voice_base64 = base64.b64encode(
@@ -314,18 +321,24 @@ class MessageMixin(WechatAPIClientBase):
raise ValueError("format must be one of amr, wav, mp3")
duration = len(audio)
format_dict = {"amr": 0, "wav": 4, "mp3": 4}
# Type AMR = 0, MP3 = 2, SILK = 4, SPEEX = 1, WAVE = 3 VoiceTime :音频长度 1000为一秒
format_dict = {"AMR": 0, "WAVE": 3, "MP3": 2, "SILK": 4, "SPEEX": 1}
# {
# "Base64": "string",
# "ToWxid": "string",
# "Type": 0,
# "VoiceTime": 0,
# "Wxid": "string"
# }
async with aiohttp.ClientSession() as session:
json_param = {"Wxid": self.wxid, "ToWxid": wxid, "Base64": voice_base64, "VoiceTime": duration,
"Type": format_dict[format]}
response = await session.post(f'http://{self.ip}:{self.port}/SendVoiceMsg', json=json_param)
response = await session.post(f'http://{self.ip}:{self.port}/api/Msg/SendVoice', json=json_param)
json_resp = await response.json()
if json_resp.get("Success"):
json_param.pop('Base64')
logging.info("发送语音消息: 对方wxid:{} 时长:{} 格式:{} 音频base64略", wxid, duration, format)
self.logging.info("发送语音消息: 对方wxid:{} 时长:{} 格式:{} 音频base64略", wxid, duration, format)
data = json_resp.get("Data")
return int(data.get("ClientMsgId")), data.get("CreateTime"), data.get("NewMsgId")
else:
@@ -344,10 +357,38 @@ class MessageMixin(WechatAPIClientBase):
return closest_rate
async def send_link_xml_message(self, xml: str, towxid: str) -> tuple[str, int, int]:
"""发送链接消息。
{
"ToWxid": "string",
"Type": 0,
"Wxid": "string",
"Xml": "string"
}
Args:
xml (str): 发送的内容
towxid (str):接收人
Returns:
tuple[str, int, int]: 返回(ClientMsgid, CreateTime, NewMsgId)
Raises:
UserLoggedOut: 未登录时调用
BanProtection: 登录新设备后4小时内操作
根据error_handler处理错误
"""
return await self._queue_message(self._send_link_xml_message, xml, towxid)
async def send_link_message(self, wxid: str, url: str, title: str = "", description: str = "",
thumb_url: str = "") -> tuple[str, int, int]:
"""发送链接消息。
{
"ToWxid": "string",
"Type": 0,
"Wxid": "string",
"Xml": "string"
}
Args:
wxid (str): 接收人wxid
url (str): 跳转链接
@@ -363,8 +404,25 @@ class MessageMixin(WechatAPIClientBase):
BanProtection: 登录新设备后4小时内操作
根据error_handler处理错误
"""
return await self._queue_message(self._send_link_message, wxid, url, title, description, thumb_url)
async def _send_link_xml_message(self, xml: str, towxid: str) -> tuple[int, int, int]:
if not self.wxid:
raise UserLoggedOut("请先登录")
async with aiohttp.ClientSession() as session:
json_param = {"Wxid": self.wxid, "ToWxid": towxid, "Xml": xml, "Type": 0}
response = await session.post(f'http://{self.ip}:{self.port}/api/Msg/ShareLink', json=json_param)
json_resp = await response.json()
logger.info(f"_send_link_xml_message resp:{json_resp}")
if json_resp.get("Success"):
data = json_resp.get("Data")
return data.get("clientMsgId"), data.get("createTime"), data.get("newMsgId")
else:
self.error_handler(json_resp)
async def _send_link_message(self, wxid: str, url: str, title: str = "", description: str = "",
thumb_url: str = "") -> tuple[int, int, int]:
if not self.wxid:
@@ -373,16 +431,17 @@ class MessageMixin(WechatAPIClientBase):
async with aiohttp.ClientSession() as session:
json_param = {"Wxid": self.wxid, "ToWxid": wxid, "Url": url, "Title": title, "Desc": description,
"ThumbUrl": thumb_url}
response = await session.post(f'http://{self.ip}:{self.port}/SendShareLink', json=json_param)
response = await session.post(f'http://{self.ip}:{self.port}/api/Msg/ShareLink', json=json_param)
json_resp = await response.json()
if json_resp.get("Success"):
logging.info("发送链接消息: 对方wxid:{} 链接:{} 标题:{} 描述:{} 缩略图链接:{}",
wxid,
url,
title,
description,
thumb_url)
self.logging.info("发送链接消息: 对方wxid:{} 链接:{} 标题:{} 描述:{} 缩略图链接:{}",
wxid,
url,
title,
description,
thumb_url)
data = json_resp.get("Data")
return data.get("clientMsgId"), data.get("createTime"), data.get("newMsgId")
else:
@@ -409,7 +468,6 @@ class MessageMixin(WechatAPIClientBase):
async def _send_emoji_message(self, wxid: str, md5: str, total_length: int) -> tuple[int, int, int]:
if not self.wxid:
raise UserLoggedOut("请先登录")
async with aiohttp.ClientSession() as session:
json_param = {"Wxid": self.wxid, "ToWxid": wxid, "Md5": md5, "TotalLen": total_length}
@@ -417,7 +475,7 @@ class MessageMixin(WechatAPIClientBase):
json_resp = await response.json()
if json_resp.get("Success"):
logging.info("发送表情消息: 对方wxid:{} md5:{} 总长度:{}", wxid, md5, total_length)
self.logging.info("发送表情消息: 对方wxid:{} md5:{} 总长度:{}", wxid, md5, total_length)
return json_resp.get("Data").get("emojiItem")
else:
self.error_handler(json_resp)
@@ -447,7 +505,6 @@ class MessageMixin(WechatAPIClientBase):
if not self.wxid:
raise UserLoggedOut("请先登录")
async with aiohttp.ClientSession() as session:
json_param = {"Wxid": self.wxid, "ToWxid": wxid, "CardWxid": card_wxid, "CardAlias": card_alias,
"CardNickname": card_nickname}
@@ -455,10 +512,10 @@ class MessageMixin(WechatAPIClientBase):
json_resp = await response.json()
if json_resp.get("Success"):
logging.info("发送名片消息: 对方wxid:{} 名片wxid:{} 名片备注:{} 名片昵称:{}", wxid,
card_wxid,
card_alias,
card_nickname)
self.logging.info("发送名片消息: 对方wxid:{} 名片wxid:{} 名片备注:{} 名片昵称:{}", wxid,
card_wxid,
card_alias,
card_nickname)
data = json_resp.get("Data")
return data.get("List")[0].get("ClientMsgid"), data.get("List")[0].get("Createtime"), data.get("List")[
0].get("NewMsgId")
@@ -486,16 +543,20 @@ class MessageMixin(WechatAPIClientBase):
async def _send_app_message(self, wxid: str, xml: str, type: int) -> tuple[int, int, int]:
if not self.wxid:
raise UserLoggedOut("请先登录")
# {
# "ToWxid": "string",
# "Type": 0,
# "Wxid": "string",
# "Xml": "string"
# }
async with aiohttp.ClientSession() as session:
json_param = {"Wxid": self.wxid, "ToWxid": wxid, "Xml": xml, "Type": type}
response = await session.post(f'http://{self.ip}:{self.port}/SendAppMsg', json=json_param)
response = await session.post(f'http://{self.ip}:{self.port}/api/Msg/SendApp', json=json_param)
json_resp = await response.json()
logger.info(f"json_resp: {json_resp}")
if json_resp.get("Success"):
json_param["Xml"] = json_param["Xml"].replace("\n", "")
logging.info("发送app消息: 对方wxid:{} 类型:{} xml:{}", wxid, type, json_param["Xml"])
self.logging.info("发送app消息: 对方wxid:{} 类型:{} xml:{}", wxid, type, json_param["Xml"])
return json_resp.get("Data").get("clientMsgId"), json_resp.get("Data").get(
"createTime"), json_resp.get("Data").get("newMsgId")
else:
@@ -522,14 +583,13 @@ class MessageMixin(WechatAPIClientBase):
if not self.wxid:
raise UserLoggedOut("请先登录")
async with aiohttp.ClientSession() as session:
json_param = {"Wxid": self.wxid, "ToWxid": wxid, "Content": xml}
response = await session.post(f'http://{self.ip}:{self.port}/SendCDNFileMsg', json=json_param)
json_resp = await response.json()
if json_resp.get("Success"):
logging.info("转发文件消息: 对方wxid:{} xml:{}", wxid, xml)
self.logging.info("转发文件消息: 对方wxid:{} xml:{}", wxid, xml)
data = json_resp.get("Data")
return data.get("clientMsgId"), data.get("createTime"), data.get("newMsgId")
else:
@@ -556,14 +616,13 @@ class MessageMixin(WechatAPIClientBase):
if not self.wxid:
raise UserLoggedOut("请先登录")
async with aiohttp.ClientSession() as session:
json_param = {"Wxid": self.wxid, "ToWxid": wxid, "Content": xml}
response = await session.post(f'http://{self.ip}:{self.port}/SendCDNImgMsg', json=json_param)
json_resp = await response.json()
if json_resp.get("Success"):
logging.info("转发图片消息: 对方wxid:{} xml:{}", wxid, xml)
self.logging.info("转发图片消息: 对方wxid:{} xml:{}", wxid, xml)
data = json_resp.get("Data")
return data.get("ClientImgId").get("string"), data.get("CreateTime"), data.get("Newmsgid")
else:
@@ -590,14 +649,13 @@ class MessageMixin(WechatAPIClientBase):
if not self.wxid:
raise UserLoggedOut("请先登录")
async with aiohttp.ClientSession() as session:
json_param = {"Wxid": self.wxid, "ToWxid": wxid, "Content": xml}
response = await session.post(f'http://{self.ip}:{self.port}/SendCDNVideoMsg', json=json_param)
json_resp = await response.json()
if json_resp.get("Success"):
logging.info("转发视频消息: 对方wxid:{} xml:{}", wxid, xml)
self.logging.info("转发视频消息: 对方wxid:{} xml:{}", wxid, xml)
data = json_resp.get("Data")
return data.get("clientMsgId"), data.get("newMsgId")
else: