mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-10 07:46:02 +00:00
style: restrict line-length
This commit is contained in:
@@ -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'
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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__()}>'
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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__()}>'
|
||||
|
||||
Reference in New Issue
Block a user