style: restrict line-length

This commit is contained in:
Junyan Qin
2025-05-10 18:04:58 +08:00
parent b30016ed08
commit 055b389353
134 changed files with 1096 additions and 2595 deletions

View File

@@ -25,9 +25,7 @@ class DingTalkClient:
self.secret = client_secret
# 在 DingTalkClient 中传入自己作为参数,避免循环导入
self.EchoTextHandler = EchoTextHandler(self)
self.client.register_callback_handler(
dingtalk_stream.chatbot.ChatbotMessage.TOPIC, self.EchoTextHandler
)
self.client.register_callback_handler(dingtalk_stream.chatbot.ChatbotMessage.TOPIC, self.EchoTextHandler)
self._message_handlers = {
'example': [],
}
@@ -86,9 +84,7 @@ class DingTalkClient:
if response.status_code == 200:
file_bytes = response.content
base64_str = base64.b64encode(file_bytes).decode(
'utf-8'
) # 返回字符串格式
base64_str = base64.b64encode(file_bytes).decode('utf-8') # 返回字符串格式
return base64_str
else:
raise Exception('获取文件失败')
@@ -151,9 +147,7 @@ class DingTalkClient:
for handler in self._message_handlers[msg_type]:
await handler(event)
async def get_message(
self, incoming_message: dingtalk_stream.chatbot.ChatbotMessage
):
async def get_message(self, incoming_message: dingtalk_stream.chatbot.ChatbotMessage):
try:
# print(json.dumps(incoming_message.to_dict(), indent=4, ensure_ascii=False))
message_data = {
@@ -170,9 +164,7 @@ class DingTalkClient:
if 'text' in item:
message_data['Content'] = item['text']
if incoming_message.get_image_list()[0]:
message_data['Picture'] = await self.download_image(
incoming_message.get_image_list()[0]
)
message_data['Picture'] = await self.download_image(incoming_message.get_image_list()[0])
message_data['Type'] = 'text'
elif incoming_message.message_type == 'text':
@@ -180,15 +172,11 @@ class DingTalkClient:
message_data['Type'] = 'text'
elif incoming_message.message_type == 'picture':
message_data['Picture'] = await self.download_image(
incoming_message.get_image_list()[0]
)
message_data['Picture'] = await self.download_image(incoming_message.get_image_list()[0])
message_data['Type'] = 'image'
elif incoming_message.message_type == 'audio':
message_data['Audio'] = await self.get_audio_url(
incoming_message.to_dict()['content']['downloadCode']
)
message_data['Audio'] = await self.get_audio_url(incoming_message.to_dict()['content']['downloadCode'])
message_data['Type'] = 'audio'

View File

@@ -68,9 +68,7 @@ class OAClient:
elif request.method == 'POST':
encryt_msg = await request.data
wxcpt = WXBizMsgCrypt(self.token, self.aes, self.appid)
ret, xml_msg = wxcpt.DecryptMsg(
encryt_msg, msg_signature, timestamp, nonce
)
ret, xml_msg = wxcpt.DecryptMsg(encryt_msg, msg_signature, timestamp, nonce)
xml_msg = xml_msg.decode('utf-8')
if ret != 0:
@@ -112,9 +110,7 @@ class OAClient:
# create_time=int(time.time()),
# content = "请求失效暂不支持公众号超过15秒的请求如有需求请联系 LangBot 团队。"
# )
print(
'请求失效暂不支持公众号超过15秒的请求如有需求请联系 LangBot 团队。'
)
print('请求失效暂不支持公众号超过15秒的请求如有需求请联系 LangBot 团队。')
return ''
except Exception:
@@ -128,12 +124,8 @@ class OAClient:
'FromUserName': root.find('FromUserName').text,
'CreateTime': int(root.find('CreateTime').text),
'MsgType': root.find('MsgType').text,
'Content': root.find('Content').text
if root.find('Content') is not None
else None,
'MsgId': int(root.find('MsgId').text)
if root.find('MsgId') is not None
else None,
'Content': root.find('Content').text if root.find('Content') is not None else None,
'MsgId': int(root.find('MsgId').text) if root.find('MsgId') is not None else None,
}
return message_data
@@ -225,9 +217,7 @@ class OAClientForLongerResponse:
elif request.method == 'POST':
encryt_msg = await request.data
wxcpt = WXBizMsgCrypt(self.token, self.aes, self.appid)
ret, xml_msg = wxcpt.DecryptMsg(
encryt_msg, msg_signature, timestamp, nonce
)
ret, xml_msg = wxcpt.DecryptMsg(encryt_msg, msg_signature, timestamp, nonce)
xml_msg = xml_msg.decode('utf-8')
if ret != 0:
@@ -238,18 +228,12 @@ class OAClientForLongerResponse:
from_user = root.find('FromUserName').text
to_user = root.find('ToUserName').text
if (
self.msg_queue.get(from_user)
and self.msg_queue[from_user][0]['content']
):
if self.msg_queue.get(from_user) and self.msg_queue[from_user][0]['content']:
queue_top = self.msg_queue[from_user].pop(0)
queue_content = queue_top['content']
# 弹出用户消息
if (
self.user_msg_queue.get(from_user)
and self.user_msg_queue[from_user]
):
if self.user_msg_queue.get(from_user) and self.user_msg_queue[from_user]:
self.user_msg_queue[from_user].pop(0)
response_xml = xml_template.format(
@@ -268,10 +252,7 @@ class OAClientForLongerResponse:
content=self.loading_message,
)
if (
self.user_msg_queue.get(from_user)
and self.user_msg_queue[from_user][0]['content']
):
if self.user_msg_queue.get(from_user) and self.user_msg_queue[from_user][0]['content']:
return response_xml
else:
message_data = await self.get_message(xml_msg)
@@ -299,12 +280,8 @@ class OAClientForLongerResponse:
'FromUserName': root.find('FromUserName').text,
'CreateTime': int(root.find('CreateTime').text),
'MsgType': root.find('MsgType').text,
'Content': root.find('Content').text
if root.find('Content') is not None
else None,
'MsgId': int(root.find('MsgId').text)
if root.find('MsgId') is not None
else None,
'Content': root.find('Content').text if root.find('Content') is not None else None,
'MsgId': int(root.find('MsgId').text) if root.find('MsgId') is not None else None,
}
return message_data

View File

@@ -144,15 +144,9 @@ class QQOfficialClient:
'group_openid': msg.get('d', {}).get('group_openid', {}),
}
attachments = msg.get('d', {}).get('attachments', [])
image_attachments = [
attachment['url']
for attachment in attachments
if await self.is_image(attachment)
]
image_attachments = [attachment['url'] for attachment in attachments if await self.is_image(attachment)]
image_attachments_type = [
attachment['content_type']
for attachment in attachments
if await self.is_image(attachment)
attachment['content_type'] for attachment in attachments if await self.is_image(attachment)
]
if image_attachments:
message_data['image_attachments'] = image_attachments[0]
@@ -211,9 +205,7 @@ class QQOfficialClient:
else:
raise Exception(response.read().decode())
async def send_channle_group_text_msg(
self, channel_id: str, content: str, msg_id: str
):
async def send_channle_group_text_msg(self, channel_id: str, content: str, msg_id: str):
"""发送频道群聊消息"""
if not await self.check_access_token():
await self.get_access_token()
@@ -235,9 +227,7 @@ class QQOfficialClient:
else:
raise Exception(response)
async def send_channle_private_text_msg(
self, guild_id: str, content: str, msg_id: str
):
async def send_channle_private_text_msg(self, guild_id: str, content: str, msg_id: str):
"""发送频道私聊消息"""
if not await self.check_access_token():
await self.get_access_token()

View File

@@ -1,59 +1,57 @@
import json
from quart import Quart, jsonify,request
from quart import Quart, jsonify, request
from slack_sdk.web.async_client import AsyncWebClient
from .slackevent import SlackEvent
from typing import Callable, Dict, Any
from pkg.platform.types import events as platform_events, message as platform_message
class SlackClient():
def __init__(self,bot_token:str,signing_secret:str):
self.bot_token = bot_token
self.signing_secret = signing_secret
self.app = Quart(__name__)
self.client = AsyncWebClient(self.bot_token)
self.app.add_url_rule('/callback/command', 'handle_callback', self.handle_callback_request, methods=['GET', 'POST'])
self._message_handlers = {
"example":[],
}
self.bot_user_id = None # 避免机器人回复自己的消息
async def handle_callback_request(self):
try:
body = await request.get_data()
data = json.loads(body)
if 'type' in data:
if data['type'] == 'url_verification':
return data['challenge']
bot_user_id = data.get("event",{}).get("bot_id","")
if self.bot_user_id and bot_user_id == self.bot_user_id:
return jsonify({'status': 'ok'})
# 处理私信
if data and data.get("event", {}).get("channel_type") in ["im"]:
event = SlackEvent.from_payload(data)
await self._handle_message(event)
return jsonify({'status': 'ok'})
#处理群聊
if data.get("event",{}).get("type") == 'app_mention':
data.setdefault("event", {})["channel_type"] = "channel"
event = SlackEvent.from_payload(data)
await self._handle_message(event)
return jsonify({'status':'ok'})
return jsonify({'status': 'ok'})
except Exception as e:
raise(e)
from typing import Callable
from pkg.platform.types import events as platform_events
async def _handle_message(self, event: SlackEvent):
class SlackClient:
def __init__(self, bot_token: str, signing_secret: str):
self.bot_token = bot_token
self.signing_secret = signing_secret
self.app = Quart(__name__)
self.client = AsyncWebClient(self.bot_token)
self.app.add_url_rule(
'/callback/command', 'handle_callback', self.handle_callback_request, methods=['GET', 'POST']
)
self._message_handlers = {
'example': [],
}
self.bot_user_id = None # 避免机器人回复自己的消息
async def handle_callback_request(self):
try:
body = await request.get_data()
data = json.loads(body)
if 'type' in data:
if data['type'] == 'url_verification':
return data['challenge']
bot_user_id = data.get('event', {}).get('bot_id', '')
if self.bot_user_id and bot_user_id == self.bot_user_id:
return jsonify({'status': 'ok'})
# 处理私信
if data and data.get('event', {}).get('channel_type') in ['im']:
event = SlackEvent.from_payload(data)
await self._handle_message(event)
return jsonify({'status': 'ok'})
# 处理群聊
if data.get('event', {}).get('type') == 'app_mention':
data.setdefault('event', {})['channel_type'] = 'channel'
event = SlackEvent.from_payload(data)
await self._handle_message(event)
return jsonify({'status': 'ok'})
return jsonify({'status': 'ok'})
except Exception as e:
raise (e)
async def _handle_message(self, event: SlackEvent):
"""
处理消息事件。
"""
@@ -62,50 +60,38 @@ class SlackClient():
for handler in self._message_handlers[msg_type]:
await handler(event)
def on_message(self, msg_type: str):
def on_message(self, msg_type: str):
"""注册消息类型处理器"""
def decorator(func: Callable[[platform_events.Event], None]):
if msg_type not in self._message_handlers:
self._message_handlers[msg_type] = []
self._message_handlers[msg_type].append(func)
return func
return decorator
async def send_message_to_channel(self,text:str,channel_id:str):
try:
response = await self.client.chat_postMessage(
channel=channel_id,
text=text
)
if self.bot_user_id is None and response.get("ok"):
self.bot_user_id = response["message"]["bot_id"]
return
except Exception as e:
raise e
async def send_message_to_channel(self, text: str, channel_id: str):
try:
response = await self.client.chat_postMessage(channel=channel_id, text=text)
if self.bot_user_id is None and response.get('ok'):
self.bot_user_id = response['message']['bot_id']
return
except Exception as e:
raise e
async def send_message_to_one(self,text:str,user_id:str):
try:
response = await self.client.chat_postMessage(
channel = '@'+user_id,
text= text
)
if self.bot_user_id is None and response.get("ok"):
self.bot_user_id = response["message"]["bot_id"]
return
except Exception as e:
raise e
async def run_task(self, host: str, port: int, *args, **kwargs):
"""
启动 Quart 应用。
"""
await self.app.run_task(host=host, port=port, *args, **kwargs)
async def send_message_to_one(self, text: str, user_id: str):
try:
response = await self.client.chat_postMessage(channel='@' + user_id, text=text)
if self.bot_user_id is None and response.get('ok'):
self.bot_user_id = response['message']['bot_id']
return
except Exception as e:
raise e
async def run_task(self, host: str, port: int, *args, **kwargs):
"""
启动 Quart 应用。
"""
await self.app.run_task(host=host, port=port, *args, **kwargs)

View File

@@ -1,86 +1,82 @@
from typing import Dict, Any, Optional
class SlackEvent(dict):
@staticmethod
def from_payload(payload: Dict[str, Any]) -> Optional["SlackEvent"]:
def from_payload(payload: Dict[str, Any]) -> Optional['SlackEvent']:
try:
event = SlackEvent(payload)
return event
except KeyError:
return None
@property
def text(self) -> str:
if self.get("event", {}).get("channel_type") == "im":
blocks = self.get("event", {}).get("blocks", [])
if not blocks:
return ""
if self.get('event', {}).get('channel_type') == 'im':
blocks = self.get('event', {}).get('blocks', [])
if not blocks:
return ''
elements = blocks[0].get("elements", [])
if not elements:
return ""
elements = blocks[0].get('elements', [])
if not elements:
return ''
elements = elements[0].get("elements", [])
text = ""
elements = elements[0].get('elements', [])
text = ''
for el in elements:
if el.get("type") == "text":
text += el.get("text", "")
elif el.get("type") == "link":
text += el.get("url", "")
for el in elements:
if el.get('type') == 'text':
text += el.get('text', '')
elif el.get('type') == 'link':
text += el.get('url', '')
return text
return text
if self.get("event",{}).get("channel_type") == 'channel':
message_text = ""
for block in self.get("event", {}).get("blocks", []):
if block.get("type") == "rich_text":
for element in block.get("elements", []):
if element.get("type") == "rich_text_section":
if self.get('event', {}).get('channel_type') == 'channel':
message_text = ''
for block in self.get('event', {}).get('blocks', []):
if block.get('type') == 'rich_text':
for element in block.get('elements', []):
if element.get('type') == 'rich_text_section':
parts = []
for el in element.get("elements", []):
if el.get("type") == "text":
parts.append(el["text"])
elif el.get("type") == "link":
parts.append(el["url"])
message_text = "".join(parts)
for el in element.get('elements', []):
if el.get('type') == 'text':
parts.append(el['text'])
elif el.get('type') == 'link':
parts.append(el['url'])
message_text = ''.join(parts)
return message_text
@property
def user_id(self) -> Optional[str]:
return self.get("event", {}).get("user","")
return self.get('event', {}).get('user', '')
@property
def channel_id(self) -> Optional[str]:
return self.get("event", {}).get("channel","")
return self.get('event', {}).get('channel', '')
@property
def type(self) -> str:
""" message对应私聊app_mention对应频道at """
return self.get("event", {}).get("channel_type", "")
"""message对应私聊app_mention对应频道at"""
return self.get('event', {}).get('channel_type', '')
@property
def message_id(self) -> str:
return self.get("event_id","")
return self.get('event_id', '')
@property
def pic_url(self) -> str:
"""提取 Slack 事件中的图片 URL"""
files = self.get("event", {}).get("files", [])
files = self.get('event', {}).get('files', [])
if files:
return files[0].get("url_private", "")
return files[0].get('url_private', '')
return None
@property
def sender_name(self) -> str:
return self.get("event", {}).get("user","")
return self.get('event', {}).get('user', '')
def __getattr__(self, key: str) -> Optional[Any]:
return self.get(key)
@@ -88,4 +84,4 @@ class SlackEvent(dict):
self[key] = value
def __repr__(self) -> str:
return f"<SlackEvent {super().__repr__()}>"
return f'<SlackEvent {super().__repr__()}>'

View File

@@ -147,12 +147,7 @@ class Prpcrypt(object):
"""
# 16位随机字符串添加到明文开头
text = text.encode()
text = (
self.get_random_str()
+ struct.pack('I', socket.htonl(len(text)))
+ text
+ receiveid.encode()
)
text = self.get_random_str() + struct.pack('I', socket.htonl(len(text))) + text + receiveid.encode()
# 使用自定义的填充方式对明文进行补位填充
pkcs7 = PKCS7Encoder()

View File

@@ -45,9 +45,7 @@ class WecomClient:
return bool(self.access_token and self.access_token.strip())
async def check_access_token_for_contacts(self):
return bool(
self.access_token_for_contacts and self.access_token_for_contacts.strip()
)
return bool(self.access_token_for_contacts and self.access_token_for_contacts.strip())
async def get_access_token(self, secret):
url = f'https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={self.corpid}&corpsecret={secret}'
@@ -61,15 +59,9 @@ class WecomClient:
async def get_users(self):
if not self.check_access_token_for_contacts():
self.access_token_for_contacts = await self.get_access_token(
self.secret_for_contacts
)
self.access_token_for_contacts = await self.get_access_token(self.secret_for_contacts)
url = (
self.base_url
+ '/user/list_id?access_token='
+ self.access_token_for_contacts
)
url = self.base_url + '/user/list_id?access_token=' + self.access_token_for_contacts
async with httpx.AsyncClient() as client:
params = {
'cursor': '',
@@ -88,15 +80,9 @@ class WecomClient:
async def send_to_all(self, content: str, agent_id: int):
if not self.check_access_token_for_contacts():
self.access_token_for_contacts = await self.get_access_token(
self.secret_for_contacts
)
self.access_token_for_contacts = await self.get_access_token(self.secret_for_contacts)
url = (
self.base_url
+ '/message/send?access_token='
+ self.access_token_for_contacts
)
url = self.base_url + '/message/send?access_token=' + self.access_token_for_contacts
user_ids = await self.get_users()
user_ids_string = '|'.join(user_ids)
async with httpx.AsyncClient() as client:
@@ -187,27 +173,21 @@ class WecomClient:
if request.method == 'GET':
echostr = request.args.get('echostr')
ret, reply_echo_str = self.wxcpt.VerifyURL(
msg_signature, timestamp, nonce, echostr
)
ret, reply_echo_str = self.wxcpt.VerifyURL(msg_signature, timestamp, nonce, echostr)
if ret != 0:
raise Exception(f'验证失败,错误码: {ret}')
return reply_echo_str
elif request.method == 'POST':
encrypt_msg = await request.data
ret, xml_msg = self.wxcpt.DecryptMsg(
encrypt_msg, msg_signature, timestamp, nonce
)
ret, xml_msg = self.wxcpt.DecryptMsg(encrypt_msg, msg_signature, timestamp, nonce)
if ret != 0:
raise Exception(f'消息解密失败,错误码: {ret}')
# 解析消息并处理
message_data = await self.get_message(xml_msg)
if message_data:
event = WecomEvent.from_payload(
message_data
) # 转换为 WecomEvent 对象
event = WecomEvent.from_payload(message_data) # 转换为 WecomEvent 对象
if event:
await self._handle_message(event)
@@ -253,23 +233,13 @@ class WecomClient:
'FromUserName': root.find('FromUserName').text,
'CreateTime': int(root.find('CreateTime').text),
'MsgType': root.find('MsgType').text,
'Content': root.find('Content').text
if root.find('Content') is not None
else None,
'MsgId': int(root.find('MsgId').text)
if root.find('MsgId') is not None
else None,
'AgentID': int(root.find('AgentID').text)
if root.find('AgentID') is not None
else None,
'Content': root.find('Content').text if root.find('Content') is not None else None,
'MsgId': int(root.find('MsgId').text) if root.find('MsgId') is not None else None,
'AgentID': int(root.find('AgentID').text) if root.find('AgentID') is not None else None,
}
if message_data['MsgType'] == 'image':
message_data['MediaId'] = (
root.find('MediaId').text if root.find('MediaId') is not None else None
)
message_data['PicUrl'] = (
root.find('PicUrl').text if root.find('PicUrl') is not None else None
)
message_data['MediaId'] = root.find('MediaId').text if root.find('MediaId') is not None else None
message_data['PicUrl'] = root.find('PicUrl').text if root.find('PicUrl') is not None else None
return message_data
@@ -298,12 +268,7 @@ class WecomClient:
if not await self.check_access_token():
self.access_token = await self.get_access_token(self.secret)
url = (
self.base_url
+ '/media/upload?access_token='
+ self.access_token
+ '&type=file'
)
url = self.base_url + '/media/upload?access_token=' + self.access_token + '&type=file'
file_bytes = None
file_name = 'uploaded_file.txt'

View File

@@ -6,60 +6,61 @@ import httpx
import traceback
from quart import Quart
import xml.etree.ElementTree as ET
from typing import Callable, Dict, Any
from typing import Callable
from .wecomcsevent import WecomCSEvent
from pkg.platform.types import events as platform_events, message as platform_message
from pkg.platform.types import message as platform_message
import aiofiles
class WecomCSClient():
def __init__(self,corpid:str,secret:str,token:str,EncodingAESKey:str):
class WecomCSClient:
def __init__(self, corpid: str, secret: str, token: str, EncodingAESKey: str):
self.corpid = corpid
self.secret = secret
self.access_token_for_contacts =''
self.access_token_for_contacts = ''
self.token = token
self.aes = EncodingAESKey
self.base_url = 'https://qyapi.weixin.qq.com/cgi-bin'
self.access_token = ''
self.app = Quart(__name__)
self.wxcpt = WXBizMsgCrypt(self.token, self.aes, self.corpid)
self.app.add_url_rule('/callback/command', 'handle_callback', self.handle_callback_request, methods=['GET', 'POST'])
self.app.add_url_rule(
'/callback/command', 'handle_callback', self.handle_callback_request, methods=['GET', 'POST']
)
self._message_handlers = {
"example":[],
'example': [],
}
async def get_pic_url(self, media_id: str):
if not await self.check_access_token():
self.access_token = await self.get_access_token(self.secret)
url = f"{self.base_url}/media/get?access_token={self.access_token}&media_id={media_id}"
url = f'{self.base_url}/media/get?access_token={self.access_token}&media_id={media_id}'
async with httpx.AsyncClient() as client:
response = await client.get(url)
if response.headers.get("Content-Type", "").startswith("application/json"):
if response.headers.get('Content-Type', '').startswith('application/json'):
data = response.json()
if data.get('errcode') in [40014, 42001]:
self.access_token = await self.get_access_token(self.secret)
return await self.get_pic_url(media_id)
else:
raise Exception("Failed to get image: " + str(data))
raise Exception('Failed to get image: ' + str(data))
# 否则是图片,转成 base64
image_bytes = response.content
content_type = response.headers.get("Content-Type", "")
base64_str = base64.b64encode(image_bytes).decode("utf-8")
base64_str = f"data:{content_type};base64,{base64_str}"
content_type = response.headers.get('Content-Type', '')
base64_str = base64.b64encode(image_bytes).decode('utf-8')
base64_str = f'data:{content_type};base64,{base64_str}'
return base64_str
#access——token操作
# access——token操作
async def check_access_token(self):
return bool(self.access_token and self.access_token.strip())
async def check_access_token_for_contacts(self):
return bool(self.access_token_for_contacts and self.access_token_for_contacts.strip())
async def get_access_token(self,secret):
async def get_access_token(self, secret):
url = f'https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={self.corpid}&corpsecret={secret}'
async with httpx.AsyncClient() as client:
response = await client.get(url)
@@ -67,118 +68,115 @@ class WecomCSClient():
if 'access_token' in data:
return data['access_token']
else:
raise Exception(f"未获取access token: {data}")
async def get_detailed_message_list(self,xml_msg:str):
raise Exception(f'未获取access token: {data}')
async def get_detailed_message_list(self, xml_msg: str):
# 在本方法中解析消息,并且获得消息的具体内容
if isinstance(xml_msg, bytes):
xml_msg = xml_msg.decode('utf-8')
root = ET.fromstring(xml_msg)
token = root.find("Token").text
open_kfid = root.find("OpenKfId").text
token = root.find('Token').text
open_kfid = root.find('OpenKfId').text
# if open_kfid in self.openkfid_list:
# return None
# else:
# self.openkfid_list.append(open_kfid)
if not await self.check_access_token():
self.access_token = await self.get_access_token(self.secret)
url = self.base_url+'/kf/sync_msg?access_token='+ self.access_token
url = self.base_url + '/kf/sync_msg?access_token=' + self.access_token
async with httpx.AsyncClient() as client:
params = {
"token": token,
"voice_format": 0,
"open_kfid": open_kfid,
'token': token,
'voice_format': 0,
'open_kfid': open_kfid,
}
response = await client.post(url,json=params)
response = await client.post(url, json=params)
data = response.json()
if data['errcode'] == 40014 or data['errcode'] == 42001:
self.access_token = await self.get_access_token(self.secret)
return await self.get_detailed_message_list(xml_msg)
if data['errcode'] != 0:
raise Exception("Failed to get message")
raise Exception('Failed to get message')
last_msg_data = data['msg_list'][-1]
open_kfid = last_msg_data.get("open_kfid")
open_kfid = last_msg_data.get('open_kfid')
# 进行获取图片操作
if last_msg_data.get("msgtype") == "image":
media_id = last_msg_data.get("image").get("media_id")
if last_msg_data.get('msgtype') == 'image':
media_id = last_msg_data.get('image').get('media_id')
picurl = await self.get_pic_url(media_id)
last_msg_data["picurl"] = picurl
last_msg_data['picurl'] = picurl
# await self.change_service_status(userid=external_userid,openkfid=open_kfid,servicer=servicer)
return last_msg_data
async def change_service_status(self,userid:str,openkfid:str,servicer:str):
async def change_service_status(self, userid: str, openkfid: str, servicer: str):
if not await self.check_access_token():
self.access_token = await self.get_access_token(self.secret)
url = self.base_url+"/kf/service_state/get?access_token="+self.access_token
url = self.base_url + '/kf/service_state/get?access_token=' + self.access_token
async with httpx.AsyncClient() as client:
params = {
"open_kfid" : openkfid,
"external_userid" : userid,
"service_state" : 1,
"servicer_userid" : servicer,
'open_kfid': openkfid,
'external_userid': userid,
'service_state': 1,
'servicer_userid': servicer,
}
response = await client.post(url,json=params)
response = await client.post(url, json=params)
data = response.json()
if data['errcode'] == 40014 or data['errcode'] == 42001:
self.access_token = await self.get_access_token(self.secret)
return await self.change_service_status(userid,openkfid)
return await self.change_service_status(userid, openkfid)
if data['errcode'] != 0:
raise Exception("Failed to change service status: "+str(data))
raise Exception('Failed to change service status: ' + str(data))
async def send_image(self,user_id:str,agent_id:int,media_id:str):
async def send_image(self, user_id: str, agent_id: int, media_id: str):
if not await self.check_access_token():
self.access_token = await self.get_access_token(self.secret)
url = self.base_url+'/media/upload?access_token='+self.access_token
url = self.base_url + '/media/upload?access_token=' + self.access_token
async with httpx.AsyncClient() as client:
params = {
"touser" : user_id,
"toparty" : "",
"totag":"",
"agentid" : agent_id,
"msgtype" : "image",
"image" : {
"media_id" : media_id,
'touser': user_id,
'toparty': '',
'totag': '',
'agentid': agent_id,
'msgtype': 'image',
'image': {
'media_id': media_id,
},
"safe":0,
"enable_id_trans": 0,
"enable_duplicate_check": 0,
"duplicate_check_interval": 1800
'safe': 0,
'enable_id_trans': 0,
'enable_duplicate_check': 0,
'duplicate_check_interval': 1800,
}
try:
response = await client.post(url,json=params)
response = await client.post(url, json=params)
data = response.json()
except Exception as e:
raise Exception("Failed to send image: "+str(e))
raise Exception('Failed to send image: ' + str(e))
# 企业微信错误码40014和42001代表accesstoken问题
if data['errcode'] == 40014 or data['errcode'] == 42001:
self.access_token = await self.get_access_token(self.secret)
return await self.send_image(user_id,agent_id,media_id)
return await self.send_image(user_id, agent_id, media_id)
if data['errcode'] != 0:
raise Exception("Failed to send image: "+str(data))
raise Exception('Failed to send image: ' + str(data))
async def send_text_msg(self, open_kfid: str, external_userid: str, msgid: str,content:str):
async def send_text_msg(self, open_kfid: str, external_userid: str, msgid: str, content: str):
if not await self.check_access_token():
self.access_token = await self.get_access_token(self.secret)
url = f"https://qyapi.weixin.qq.com/cgi-bin/kf/send_msg?access_token={self.access_token}"
url = f'https://qyapi.weixin.qq.com/cgi-bin/kf/send_msg?access_token={self.access_token}'
payload = {
"touser": external_userid,
"open_kfid": open_kfid,
"msgid": msgid,
"msgtype": "text",
"text": {
"content": content,
}
'touser': external_userid,
'open_kfid': open_kfid,
'msgid': msgid,
'msgtype': 'text',
'text': {
'content': content,
},
}
async with httpx.AsyncClient() as client:
@@ -187,46 +185,44 @@ class WecomCSClient():
data = response.json()
if data['errcode'] == 40014 or data['errcode'] == 42001:
self.access_token = await self.get_access_token(self.secret)
return await self.send_text_msg(open_kfid,external_userid,msgid,content)
return await self.send_text_msg(open_kfid, external_userid, msgid, content)
if data['errcode'] != 0:
raise Exception("Failed to send message")
raise Exception('Failed to send message')
return data
async def handle_callback_request(self):
"""
处理回调请求,包括 GET 验证和 POST 消息接收。
"""
try:
msg_signature = request.args.get('msg_signature')
timestamp = request.args.get('timestamp')
nonce = request.args.get('nonce')
msg_signature = request.args.get("msg_signature")
timestamp = request.args.get("timestamp")
nonce = request.args.get("nonce")
if request.method == "GET":
echostr = request.args.get("echostr")
if request.method == 'GET':
echostr = request.args.get('echostr')
ret, reply_echo_str = self.wxcpt.VerifyURL(msg_signature, timestamp, nonce, echostr)
if ret != 0:
raise Exception(f"验证失败,错误码: {ret}")
raise Exception(f'验证失败,错误码: {ret}')
return reply_echo_str
elif request.method == "POST":
elif request.method == 'POST':
encrypt_msg = await request.data
ret, xml_msg = self.wxcpt.DecryptMsg(encrypt_msg, msg_signature, timestamp, nonce)
if ret != 0:
raise Exception(f"消息解密失败,错误码: {ret}")
raise Exception(f'消息解密失败,错误码: {ret}')
# 解析消息并处理
message_data = await self.get_detailed_message_list(xml_msg)
if message_data is not None:
event = WecomCSEvent.from_payload(message_data)
event = WecomCSEvent.from_payload(message_data)
if event:
await self._handle_message(event)
return "success"
return 'success'
except Exception as e:
traceback.print_exc()
return f"Error processing request: {str(e)}", 400
return f'Error processing request: {str(e)}', 400
async def run_task(self, host: str, port: int, *args, **kwargs):
"""
@@ -238,11 +234,13 @@ class WecomCSClient():
"""
注册消息类型处理器。
"""
def decorator(func: Callable[[WecomCSEvent], None]):
if msg_type not in self._message_handlers:
self._message_handlers[msg_type] = []
self._message_handlers[msg_type].append(func)
return func
return decorator
async def _handle_message(self, event: WecomCSEvent):
@@ -254,25 +252,23 @@ class WecomCSClient():
for handler in self._message_handlers[msg_type]:
await handler(event)
@staticmethod
async def get_image_type(image_bytes: bytes) -> str:
"""
通过图片的magic numbers判断图片类型
"""
magic_numbers = {
b'\xFF\xD8\xFF': 'jpg',
b'\x89\x50\x4E\x47': 'png',
b'\xff\xd8\xff': 'jpg',
b'\x89\x50\x4e\x47': 'png',
b'\x47\x49\x46': 'gif',
b'\x42\x4D': 'bmp',
b'\x00\x00\x01\x00': 'ico'
b'\x42\x4d': 'bmp',
b'\x00\x00\x01\x00': 'ico',
}
for magic, ext in magic_numbers.items():
if image_bytes.startswith(magic):
return ext
return 'jpg' # 默认返回jpg
async def upload_to_work(self, image: platform_message.Image):
"""
@@ -283,7 +279,7 @@ class WecomCSClient():
url = self.base_url + '/media/upload?access_token=' + self.access_token + '&type=file'
file_bytes = None
file_name = "uploaded_file.txt"
file_name = 'uploaded_file.txt'
# 获取文件的二进制数据
if image.path:
@@ -302,20 +298,22 @@ class WecomCSClient():
padded_base64 = base64_data + '=' * padding
file_bytes = base64.b64decode(padded_base64)
except binascii.Error as e:
raise ValueError(f"Invalid base64 string: {str(e)}")
raise ValueError(f'Invalid base64 string: {str(e)}')
else:
raise ValueError("image对象出错")
raise ValueError('image对象出错')
# 设置 multipart/form-data 格式的文件
boundary = "-------------------------acebdf13572468"
headers = {
'Content-Type': f'multipart/form-data; boundary={boundary}'
}
boundary = '-------------------------acebdf13572468'
headers = {'Content-Type': f'multipart/form-data; boundary={boundary}'}
body = (
f"--{boundary}\r\n"
f"Content-Disposition: form-data; name=\"media\"; filename=\"{file_name}\"; filelength={len(file_bytes)}\r\n"
f"Content-Type: application/octet-stream\r\n\r\n"
).encode('utf-8') + file_bytes + f"\r\n--{boundary}--\r\n".encode('utf-8')
(
f'--{boundary}\r\n'
f'Content-Disposition: form-data; name="media"; filename="{file_name}"; filelength={len(file_bytes)}\r\n'
f'Content-Type: application/octet-stream\r\n\r\n'
).encode('utf-8')
+ file_bytes
+ f'\r\n--{boundary}--\r\n'.encode('utf-8')
)
# 上传文件
async with httpx.AsyncClient() as client:
@@ -325,19 +323,18 @@ class WecomCSClient():
self.access_token = await self.get_access_token(self.secret)
media_id = await self.upload_to_work(image)
if data.get('errcode', 0) != 0:
raise Exception("failed to upload file")
raise Exception('failed to upload file')
media_id = data.get('media_id')
return media_id
async def download_image_to_bytes(self,url:str) -> bytes:
async def download_image_to_bytes(self, url: str) -> bytes:
async with httpx.AsyncClient() as client:
response = await client.get(url)
response.raise_for_status()
return response.content
#进行media_id的获取
# 进行media_id的获取
async def get_media_id(self, image: platform_message.Image):
media_id = await self.upload_to_work(image=image)
return media_id

View File

@@ -9,7 +9,7 @@ class WecomCSEvent(dict):
"""
@staticmethod
def from_payload(payload: Dict[str, Any]) -> Optional["WecomCSEvent"]:
def from_payload(payload: Dict[str, Any]) -> Optional['WecomCSEvent']:
"""
从企业微信(客服会话)事件数据构造 `WecomEvent` 对象。
@@ -21,7 +21,7 @@ class WecomCSEvent(dict):
"""
try:
event = WecomCSEvent(payload)
_ = event.type,
_ = (event.type,)
return event
except KeyError:
return None
@@ -34,8 +34,8 @@ class WecomCSEvent(dict):
Returns:
str: 事件类型。
"""
return self.get("msgtype", "")
return self.get('msgtype', '')
@property
def user_id(self) -> Optional[str]:
"""
@@ -44,7 +44,7 @@ class WecomCSEvent(dict):
Returns:
Optional[str]: 用户 ID。
"""
return self.get("external_userid")
return self.get('external_userid')
@property
def receiver_id(self) -> Optional[str]:
@@ -54,8 +54,8 @@ class WecomCSEvent(dict):
Returns:
Optional[str]: 接收者 ID。
"""
return self.get("open_kfid","")
return self.get('open_kfid', '')
@property
def picurl(self) -> Optional[str]:
"""
@@ -65,7 +65,7 @@ class WecomCSEvent(dict):
Optional[str]: 图片 URL。
"""
return self.get("picurl","")
return self.get('picurl', '')
@property
def message_id(self) -> Optional[str]:
@@ -75,7 +75,7 @@ class WecomCSEvent(dict):
Returns:
Optional[str]: 消息 ID。
"""
return self.get("msgid")
return self.get('msgid')
@property
def message(self) -> Optional[str]:
@@ -85,12 +85,11 @@ class WecomCSEvent(dict):
Returns:
Optional[str]: 消息内容。
"""
if self.get("msgtype") == 'text':
return self.get("text").get("content","")
if self.get('msgtype') == 'text':
return self.get('text').get('content', '')
else:
return None
@property
def timestamp(self) -> Optional[int]:
"""
@@ -99,8 +98,7 @@ class WecomCSEvent(dict):
Returns:
Optional[int]: 时间戳。
"""
return self.get("send_time")
return self.get('send_time')
def __getattr__(self, key: str) -> Optional[Any]:
"""
@@ -131,4 +129,4 @@ class WecomCSEvent(dict):
Returns:
str: 字符串表示。
"""
return f"<WecomEvent {super().__repr__()}>"
return f'<WecomEvent {super().__repr__()}>'