mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-17 09:56:06 +00:00
style: restrict line-length
This commit is contained in:
@@ -1,3 +1,6 @@
|
|||||||
|
|
||||||
|
line-length = 120
|
||||||
|
|
||||||
[lint]
|
[lint]
|
||||||
|
|
||||||
ignore = [
|
ignore = [
|
||||||
|
|||||||
@@ -25,9 +25,7 @@ class DingTalkClient:
|
|||||||
self.secret = client_secret
|
self.secret = client_secret
|
||||||
# 在 DingTalkClient 中传入自己作为参数,避免循环导入
|
# 在 DingTalkClient 中传入自己作为参数,避免循环导入
|
||||||
self.EchoTextHandler = EchoTextHandler(self)
|
self.EchoTextHandler = EchoTextHandler(self)
|
||||||
self.client.register_callback_handler(
|
self.client.register_callback_handler(dingtalk_stream.chatbot.ChatbotMessage.TOPIC, self.EchoTextHandler)
|
||||||
dingtalk_stream.chatbot.ChatbotMessage.TOPIC, self.EchoTextHandler
|
|
||||||
)
|
|
||||||
self._message_handlers = {
|
self._message_handlers = {
|
||||||
'example': [],
|
'example': [],
|
||||||
}
|
}
|
||||||
@@ -86,9 +84,7 @@ class DingTalkClient:
|
|||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
file_bytes = response.content
|
file_bytes = response.content
|
||||||
base64_str = base64.b64encode(file_bytes).decode(
|
base64_str = base64.b64encode(file_bytes).decode('utf-8') # 返回字符串格式
|
||||||
'utf-8'
|
|
||||||
) # 返回字符串格式
|
|
||||||
return base64_str
|
return base64_str
|
||||||
else:
|
else:
|
||||||
raise Exception('获取文件失败')
|
raise Exception('获取文件失败')
|
||||||
@@ -151,9 +147,7 @@ class DingTalkClient:
|
|||||||
for handler in self._message_handlers[msg_type]:
|
for handler in self._message_handlers[msg_type]:
|
||||||
await handler(event)
|
await handler(event)
|
||||||
|
|
||||||
async def get_message(
|
async def get_message(self, incoming_message: dingtalk_stream.chatbot.ChatbotMessage):
|
||||||
self, incoming_message: dingtalk_stream.chatbot.ChatbotMessage
|
|
||||||
):
|
|
||||||
try:
|
try:
|
||||||
# print(json.dumps(incoming_message.to_dict(), indent=4, ensure_ascii=False))
|
# print(json.dumps(incoming_message.to_dict(), indent=4, ensure_ascii=False))
|
||||||
message_data = {
|
message_data = {
|
||||||
@@ -170,9 +164,7 @@ class DingTalkClient:
|
|||||||
if 'text' in item:
|
if 'text' in item:
|
||||||
message_data['Content'] = item['text']
|
message_data['Content'] = item['text']
|
||||||
if incoming_message.get_image_list()[0]:
|
if incoming_message.get_image_list()[0]:
|
||||||
message_data['Picture'] = await self.download_image(
|
message_data['Picture'] = await self.download_image(incoming_message.get_image_list()[0])
|
||||||
incoming_message.get_image_list()[0]
|
|
||||||
)
|
|
||||||
message_data['Type'] = 'text'
|
message_data['Type'] = 'text'
|
||||||
|
|
||||||
elif incoming_message.message_type == 'text':
|
elif incoming_message.message_type == 'text':
|
||||||
@@ -180,15 +172,11 @@ class DingTalkClient:
|
|||||||
|
|
||||||
message_data['Type'] = 'text'
|
message_data['Type'] = 'text'
|
||||||
elif incoming_message.message_type == 'picture':
|
elif incoming_message.message_type == 'picture':
|
||||||
message_data['Picture'] = await self.download_image(
|
message_data['Picture'] = await self.download_image(incoming_message.get_image_list()[0])
|
||||||
incoming_message.get_image_list()[0]
|
|
||||||
)
|
|
||||||
|
|
||||||
message_data['Type'] = 'image'
|
message_data['Type'] = 'image'
|
||||||
elif incoming_message.message_type == 'audio':
|
elif incoming_message.message_type == 'audio':
|
||||||
message_data['Audio'] = await self.get_audio_url(
|
message_data['Audio'] = await self.get_audio_url(incoming_message.to_dict()['content']['downloadCode'])
|
||||||
incoming_message.to_dict()['content']['downloadCode']
|
|
||||||
)
|
|
||||||
|
|
||||||
message_data['Type'] = 'audio'
|
message_data['Type'] = 'audio'
|
||||||
|
|
||||||
|
|||||||
@@ -68,9 +68,7 @@ class OAClient:
|
|||||||
elif request.method == 'POST':
|
elif request.method == 'POST':
|
||||||
encryt_msg = await request.data
|
encryt_msg = await request.data
|
||||||
wxcpt = WXBizMsgCrypt(self.token, self.aes, self.appid)
|
wxcpt = WXBizMsgCrypt(self.token, self.aes, self.appid)
|
||||||
ret, xml_msg = wxcpt.DecryptMsg(
|
ret, xml_msg = wxcpt.DecryptMsg(encryt_msg, msg_signature, timestamp, nonce)
|
||||||
encryt_msg, msg_signature, timestamp, nonce
|
|
||||||
)
|
|
||||||
xml_msg = xml_msg.decode('utf-8')
|
xml_msg = xml_msg.decode('utf-8')
|
||||||
|
|
||||||
if ret != 0:
|
if ret != 0:
|
||||||
@@ -112,9 +110,7 @@ class OAClient:
|
|||||||
# create_time=int(time.time()),
|
# create_time=int(time.time()),
|
||||||
# content = "请求失效:暂不支持公众号超过15秒的请求,如有需求,请联系 LangBot 团队。"
|
# content = "请求失效:暂不支持公众号超过15秒的请求,如有需求,请联系 LangBot 团队。"
|
||||||
# )
|
# )
|
||||||
print(
|
print('请求失效:暂不支持公众号超过15秒的请求,如有需求,请联系 LangBot 团队。')
|
||||||
'请求失效:暂不支持公众号超过15秒的请求,如有需求,请联系 LangBot 团队。'
|
|
||||||
)
|
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -128,12 +124,8 @@ class OAClient:
|
|||||||
'FromUserName': root.find('FromUserName').text,
|
'FromUserName': root.find('FromUserName').text,
|
||||||
'CreateTime': int(root.find('CreateTime').text),
|
'CreateTime': int(root.find('CreateTime').text),
|
||||||
'MsgType': root.find('MsgType').text,
|
'MsgType': root.find('MsgType').text,
|
||||||
'Content': root.find('Content').text
|
'Content': root.find('Content').text if root.find('Content') is not None else None,
|
||||||
if root.find('Content') is not None
|
'MsgId': int(root.find('MsgId').text) if root.find('MsgId') is not None else None,
|
||||||
else None,
|
|
||||||
'MsgId': int(root.find('MsgId').text)
|
|
||||||
if root.find('MsgId') is not None
|
|
||||||
else None,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return message_data
|
return message_data
|
||||||
@@ -225,9 +217,7 @@ class OAClientForLongerResponse:
|
|||||||
elif request.method == 'POST':
|
elif request.method == 'POST':
|
||||||
encryt_msg = await request.data
|
encryt_msg = await request.data
|
||||||
wxcpt = WXBizMsgCrypt(self.token, self.aes, self.appid)
|
wxcpt = WXBizMsgCrypt(self.token, self.aes, self.appid)
|
||||||
ret, xml_msg = wxcpt.DecryptMsg(
|
ret, xml_msg = wxcpt.DecryptMsg(encryt_msg, msg_signature, timestamp, nonce)
|
||||||
encryt_msg, msg_signature, timestamp, nonce
|
|
||||||
)
|
|
||||||
xml_msg = xml_msg.decode('utf-8')
|
xml_msg = xml_msg.decode('utf-8')
|
||||||
|
|
||||||
if ret != 0:
|
if ret != 0:
|
||||||
@@ -238,18 +228,12 @@ class OAClientForLongerResponse:
|
|||||||
from_user = root.find('FromUserName').text
|
from_user = root.find('FromUserName').text
|
||||||
to_user = root.find('ToUserName').text
|
to_user = root.find('ToUserName').text
|
||||||
|
|
||||||
if (
|
if self.msg_queue.get(from_user) and self.msg_queue[from_user][0]['content']:
|
||||||
self.msg_queue.get(from_user)
|
|
||||||
and self.msg_queue[from_user][0]['content']
|
|
||||||
):
|
|
||||||
queue_top = self.msg_queue[from_user].pop(0)
|
queue_top = self.msg_queue[from_user].pop(0)
|
||||||
queue_content = queue_top['content']
|
queue_content = queue_top['content']
|
||||||
|
|
||||||
# 弹出用户消息
|
# 弹出用户消息
|
||||||
if (
|
if self.user_msg_queue.get(from_user) and self.user_msg_queue[from_user]:
|
||||||
self.user_msg_queue.get(from_user)
|
|
||||||
and self.user_msg_queue[from_user]
|
|
||||||
):
|
|
||||||
self.user_msg_queue[from_user].pop(0)
|
self.user_msg_queue[from_user].pop(0)
|
||||||
|
|
||||||
response_xml = xml_template.format(
|
response_xml = xml_template.format(
|
||||||
@@ -268,10 +252,7 @@ class OAClientForLongerResponse:
|
|||||||
content=self.loading_message,
|
content=self.loading_message,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if self.user_msg_queue.get(from_user) and self.user_msg_queue[from_user][0]['content']:
|
||||||
self.user_msg_queue.get(from_user)
|
|
||||||
and self.user_msg_queue[from_user][0]['content']
|
|
||||||
):
|
|
||||||
return response_xml
|
return response_xml
|
||||||
else:
|
else:
|
||||||
message_data = await self.get_message(xml_msg)
|
message_data = await self.get_message(xml_msg)
|
||||||
@@ -299,12 +280,8 @@ class OAClientForLongerResponse:
|
|||||||
'FromUserName': root.find('FromUserName').text,
|
'FromUserName': root.find('FromUserName').text,
|
||||||
'CreateTime': int(root.find('CreateTime').text),
|
'CreateTime': int(root.find('CreateTime').text),
|
||||||
'MsgType': root.find('MsgType').text,
|
'MsgType': root.find('MsgType').text,
|
||||||
'Content': root.find('Content').text
|
'Content': root.find('Content').text if root.find('Content') is not None else None,
|
||||||
if root.find('Content') is not None
|
'MsgId': int(root.find('MsgId').text) if root.find('MsgId') is not None else None,
|
||||||
else None,
|
|
||||||
'MsgId': int(root.find('MsgId').text)
|
|
||||||
if root.find('MsgId') is not None
|
|
||||||
else None,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return message_data
|
return message_data
|
||||||
|
|||||||
@@ -144,15 +144,9 @@ class QQOfficialClient:
|
|||||||
'group_openid': msg.get('d', {}).get('group_openid', {}),
|
'group_openid': msg.get('d', {}).get('group_openid', {}),
|
||||||
}
|
}
|
||||||
attachments = msg.get('d', {}).get('attachments', [])
|
attachments = msg.get('d', {}).get('attachments', [])
|
||||||
image_attachments = [
|
image_attachments = [attachment['url'] for attachment in attachments if await self.is_image(attachment)]
|
||||||
attachment['url']
|
|
||||||
for attachment in attachments
|
|
||||||
if await self.is_image(attachment)
|
|
||||||
]
|
|
||||||
image_attachments_type = [
|
image_attachments_type = [
|
||||||
attachment['content_type']
|
attachment['content_type'] for attachment in attachments if await self.is_image(attachment)
|
||||||
for attachment in attachments
|
|
||||||
if await self.is_image(attachment)
|
|
||||||
]
|
]
|
||||||
if image_attachments:
|
if image_attachments:
|
||||||
message_data['image_attachments'] = image_attachments[0]
|
message_data['image_attachments'] = image_attachments[0]
|
||||||
@@ -211,9 +205,7 @@ class QQOfficialClient:
|
|||||||
else:
|
else:
|
||||||
raise Exception(response.read().decode())
|
raise Exception(response.read().decode())
|
||||||
|
|
||||||
async def send_channle_group_text_msg(
|
async def send_channle_group_text_msg(self, channel_id: str, content: str, msg_id: str):
|
||||||
self, channel_id: str, content: str, msg_id: str
|
|
||||||
):
|
|
||||||
"""发送频道群聊消息"""
|
"""发送频道群聊消息"""
|
||||||
if not await self.check_access_token():
|
if not await self.check_access_token():
|
||||||
await self.get_access_token()
|
await self.get_access_token()
|
||||||
@@ -235,9 +227,7 @@ class QQOfficialClient:
|
|||||||
else:
|
else:
|
||||||
raise Exception(response)
|
raise Exception(response)
|
||||||
|
|
||||||
async def send_channle_private_text_msg(
|
async def send_channle_private_text_msg(self, guild_id: str, content: str, msg_id: str):
|
||||||
self, guild_id: str, content: str, msg_id: str
|
|
||||||
):
|
|
||||||
"""发送频道私聊消息"""
|
"""发送频道私聊消息"""
|
||||||
if not await self.check_access_token():
|
if not await self.check_access_token():
|
||||||
await self.get_access_token()
|
await self.get_access_token()
|
||||||
|
|||||||
+73
-87
@@ -1,59 +1,57 @@
|
|||||||
import json
|
import json
|
||||||
from quart import Quart, jsonify,request
|
from quart import Quart, jsonify, request
|
||||||
from slack_sdk.web.async_client import AsyncWebClient
|
from slack_sdk.web.async_client import AsyncWebClient
|
||||||
from .slackevent import SlackEvent
|
from .slackevent import SlackEvent
|
||||||
from typing import Callable, Dict, Any
|
from typing import Callable
|
||||||
from pkg.platform.types import events as platform_events, message as platform_message
|
from pkg.platform.types import events as platform_events
|
||||||
|
|
||||||
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):
|
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]:
|
for handler in self._message_handlers[msg_type]:
|
||||||
await handler(event)
|
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]):
|
def decorator(func: Callable[[platform_events.Event], None]):
|
||||||
if msg_type not in self._message_handlers:
|
if msg_type not in self._message_handlers:
|
||||||
self._message_handlers[msg_type] = []
|
self._message_handlers[msg_type] = []
|
||||||
self._message_handlers[msg_type].append(func)
|
self._message_handlers[msg_type].append(func)
|
||||||
return func
|
return func
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
async def send_message_to_channel(self,text:str,channel_id:str):
|
async def send_message_to_channel(self, text: str, channel_id: str):
|
||||||
try:
|
try:
|
||||||
response = await self.client.chat_postMessage(
|
response = await self.client.chat_postMessage(channel=channel_id, text=text)
|
||||||
channel=channel_id,
|
if self.bot_user_id is None and response.get('ok'):
|
||||||
text=text
|
self.bot_user_id = response['message']['bot_id']
|
||||||
)
|
return
|
||||||
if self.bot_user_id is None and response.get("ok"):
|
except Exception as e:
|
||||||
self.bot_user_id = response["message"]["bot_id"]
|
raise e
|
||||||
return
|
|
||||||
except Exception as e:
|
|
||||||
raise e
|
|
||||||
|
|
||||||
async def send_message_to_one(self,text:str,user_id:str):
|
async def send_message_to_one(self, text: str, user_id: str):
|
||||||
try:
|
try:
|
||||||
response = await self.client.chat_postMessage(
|
response = await self.client.chat_postMessage(channel='@' + user_id, text=text)
|
||||||
channel = '@'+user_id,
|
if self.bot_user_id is None and response.get('ok'):
|
||||||
text= text
|
self.bot_user_id = response['message']['bot_id']
|
||||||
)
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
|
|
||||||
class SlackEvent(dict):
|
class SlackEvent(dict):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_payload(payload: Dict[str, Any]) -> Optional["SlackEvent"]:
|
def from_payload(payload: Dict[str, Any]) -> Optional['SlackEvent']:
|
||||||
try:
|
try:
|
||||||
event = SlackEvent(payload)
|
event = SlackEvent(payload)
|
||||||
return event
|
return event
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def text(self) -> str:
|
def text(self) -> str:
|
||||||
|
if self.get('event', {}).get('channel_type') == 'im':
|
||||||
if self.get("event", {}).get("channel_type") == "im":
|
blocks = self.get('event', {}).get('blocks', [])
|
||||||
blocks = self.get("event", {}).get("blocks", [])
|
if not blocks:
|
||||||
if not blocks:
|
return ''
|
||||||
return ""
|
|
||||||
|
|
||||||
elements = blocks[0].get("elements", [])
|
elements = blocks[0].get('elements', [])
|
||||||
if not elements:
|
if not elements:
|
||||||
return ""
|
return ''
|
||||||
|
|
||||||
elements = elements[0].get("elements", [])
|
elements = elements[0].get('elements', [])
|
||||||
text = ""
|
text = ''
|
||||||
|
|
||||||
for el in elements:
|
for el in elements:
|
||||||
if el.get("type") == "text":
|
if el.get('type') == 'text':
|
||||||
text += el.get("text", "")
|
text += el.get('text', '')
|
||||||
elif el.get("type") == "link":
|
elif el.get('type') == 'link':
|
||||||
text += el.get("url", "")
|
text += el.get('url', '')
|
||||||
|
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
if self.get('event', {}).get('channel_type') == 'channel':
|
||||||
if self.get("event",{}).get("channel_type") == 'channel':
|
message_text = ''
|
||||||
message_text = ""
|
for block in self.get('event', {}).get('blocks', []):
|
||||||
for block in self.get("event", {}).get("blocks", []):
|
if block.get('type') == 'rich_text':
|
||||||
if block.get("type") == "rich_text":
|
for element in block.get('elements', []):
|
||||||
for element in block.get("elements", []):
|
if element.get('type') == 'rich_text_section':
|
||||||
if element.get("type") == "rich_text_section":
|
|
||||||
parts = []
|
parts = []
|
||||||
for el in element.get("elements", []):
|
for el in element.get('elements', []):
|
||||||
if el.get("type") == "text":
|
if el.get('type') == 'text':
|
||||||
parts.append(el["text"])
|
parts.append(el['text'])
|
||||||
elif el.get("type") == "link":
|
elif el.get('type') == 'link':
|
||||||
parts.append(el["url"])
|
parts.append(el['url'])
|
||||||
message_text = "".join(parts)
|
message_text = ''.join(parts)
|
||||||
|
|
||||||
return message_text
|
return message_text
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def user_id(self) -> Optional[str]:
|
def user_id(self) -> Optional[str]:
|
||||||
return self.get("event", {}).get("user","")
|
return self.get('event', {}).get('user', '')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def channel_id(self) -> Optional[str]:
|
def channel_id(self) -> Optional[str]:
|
||||||
return self.get("event", {}).get("channel","")
|
return self.get('event', {}).get('channel', '')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type(self) -> str:
|
def type(self) -> str:
|
||||||
""" message对应私聊,app_mention对应频道at """
|
"""message对应私聊,app_mention对应频道at"""
|
||||||
return self.get("event", {}).get("channel_type", "")
|
return self.get('event', {}).get('channel_type', '')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def message_id(self) -> str:
|
def message_id(self) -> str:
|
||||||
return self.get("event_id","")
|
return self.get('event_id', '')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pic_url(self) -> str:
|
def pic_url(self) -> str:
|
||||||
"""提取 Slack 事件中的图片 URL"""
|
"""提取 Slack 事件中的图片 URL"""
|
||||||
files = self.get("event", {}).get("files", [])
|
files = self.get('event', {}).get('files', [])
|
||||||
if files:
|
if files:
|
||||||
return files[0].get("url_private", "")
|
return files[0].get('url_private', '')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sender_name(self) -> str:
|
def sender_name(self) -> str:
|
||||||
return self.get("event", {}).get("user","")
|
return self.get('event', {}).get('user', '')
|
||||||
|
|
||||||
def __getattr__(self, key: str) -> Optional[Any]:
|
def __getattr__(self, key: str) -> Optional[Any]:
|
||||||
return self.get(key)
|
return self.get(key)
|
||||||
|
|
||||||
@@ -88,4 +84,4 @@ class SlackEvent(dict):
|
|||||||
self[key] = value
|
self[key] = value
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<SlackEvent {super().__repr__()}>"
|
return f'<SlackEvent {super().__repr__()}>'
|
||||||
|
|||||||
@@ -147,12 +147,7 @@ class Prpcrypt(object):
|
|||||||
"""
|
"""
|
||||||
# 16位随机字符串添加到明文开头
|
# 16位随机字符串添加到明文开头
|
||||||
text = text.encode()
|
text = text.encode()
|
||||||
text = (
|
text = self.get_random_str() + struct.pack('I', socket.htonl(len(text))) + text + receiveid.encode()
|
||||||
self.get_random_str()
|
|
||||||
+ struct.pack('I', socket.htonl(len(text)))
|
|
||||||
+ text
|
|
||||||
+ receiveid.encode()
|
|
||||||
)
|
|
||||||
|
|
||||||
# 使用自定义的填充方式对明文进行补位填充
|
# 使用自定义的填充方式对明文进行补位填充
|
||||||
pkcs7 = PKCS7Encoder()
|
pkcs7 = PKCS7Encoder()
|
||||||
|
|||||||
+14
-49
@@ -45,9 +45,7 @@ class WecomClient:
|
|||||||
return bool(self.access_token and self.access_token.strip())
|
return bool(self.access_token and self.access_token.strip())
|
||||||
|
|
||||||
async def check_access_token_for_contacts(self):
|
async def check_access_token_for_contacts(self):
|
||||||
return bool(
|
return bool(self.access_token_for_contacts and self.access_token_for_contacts.strip())
|
||||||
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}'
|
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):
|
async def get_users(self):
|
||||||
if not self.check_access_token_for_contacts():
|
if not self.check_access_token_for_contacts():
|
||||||
self.access_token_for_contacts = await self.get_access_token(
|
self.access_token_for_contacts = await self.get_access_token(self.secret_for_contacts)
|
||||||
self.secret_for_contacts
|
|
||||||
)
|
|
||||||
|
|
||||||
url = (
|
url = self.base_url + '/user/list_id?access_token=' + self.access_token_for_contacts
|
||||||
self.base_url
|
|
||||||
+ '/user/list_id?access_token='
|
|
||||||
+ self.access_token_for_contacts
|
|
||||||
)
|
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
params = {
|
params = {
|
||||||
'cursor': '',
|
'cursor': '',
|
||||||
@@ -88,15 +80,9 @@ class WecomClient:
|
|||||||
|
|
||||||
async def send_to_all(self, content: str, agent_id: int):
|
async def send_to_all(self, content: str, agent_id: int):
|
||||||
if not self.check_access_token_for_contacts():
|
if not self.check_access_token_for_contacts():
|
||||||
self.access_token_for_contacts = await self.get_access_token(
|
self.access_token_for_contacts = await self.get_access_token(self.secret_for_contacts)
|
||||||
self.secret_for_contacts
|
|
||||||
)
|
|
||||||
|
|
||||||
url = (
|
url = self.base_url + '/message/send?access_token=' + self.access_token_for_contacts
|
||||||
self.base_url
|
|
||||||
+ '/message/send?access_token='
|
|
||||||
+ self.access_token_for_contacts
|
|
||||||
)
|
|
||||||
user_ids = await self.get_users()
|
user_ids = await self.get_users()
|
||||||
user_ids_string = '|'.join(user_ids)
|
user_ids_string = '|'.join(user_ids)
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
@@ -187,27 +173,21 @@ class WecomClient:
|
|||||||
|
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
echostr = request.args.get('echostr')
|
echostr = request.args.get('echostr')
|
||||||
ret, reply_echo_str = self.wxcpt.VerifyURL(
|
ret, reply_echo_str = self.wxcpt.VerifyURL(msg_signature, timestamp, nonce, echostr)
|
||||||
msg_signature, timestamp, nonce, echostr
|
|
||||||
)
|
|
||||||
if ret != 0:
|
if ret != 0:
|
||||||
raise Exception(f'验证失败,错误码: {ret}')
|
raise Exception(f'验证失败,错误码: {ret}')
|
||||||
return reply_echo_str
|
return reply_echo_str
|
||||||
|
|
||||||
elif request.method == 'POST':
|
elif request.method == 'POST':
|
||||||
encrypt_msg = await request.data
|
encrypt_msg = await request.data
|
||||||
ret, xml_msg = self.wxcpt.DecryptMsg(
|
ret, xml_msg = self.wxcpt.DecryptMsg(encrypt_msg, msg_signature, timestamp, nonce)
|
||||||
encrypt_msg, msg_signature, timestamp, nonce
|
|
||||||
)
|
|
||||||
if ret != 0:
|
if ret != 0:
|
||||||
raise Exception(f'消息解密失败,错误码: {ret}')
|
raise Exception(f'消息解密失败,错误码: {ret}')
|
||||||
|
|
||||||
# 解析消息并处理
|
# 解析消息并处理
|
||||||
message_data = await self.get_message(xml_msg)
|
message_data = await self.get_message(xml_msg)
|
||||||
if message_data:
|
if message_data:
|
||||||
event = WecomEvent.from_payload(
|
event = WecomEvent.from_payload(message_data) # 转换为 WecomEvent 对象
|
||||||
message_data
|
|
||||||
) # 转换为 WecomEvent 对象
|
|
||||||
if event:
|
if event:
|
||||||
await self._handle_message(event)
|
await self._handle_message(event)
|
||||||
|
|
||||||
@@ -253,23 +233,13 @@ class WecomClient:
|
|||||||
'FromUserName': root.find('FromUserName').text,
|
'FromUserName': root.find('FromUserName').text,
|
||||||
'CreateTime': int(root.find('CreateTime').text),
|
'CreateTime': int(root.find('CreateTime').text),
|
||||||
'MsgType': root.find('MsgType').text,
|
'MsgType': root.find('MsgType').text,
|
||||||
'Content': root.find('Content').text
|
'Content': root.find('Content').text if root.find('Content') is not None else None,
|
||||||
if root.find('Content') is not None
|
'MsgId': int(root.find('MsgId').text) if root.find('MsgId') is not None else None,
|
||||||
else None,
|
'AgentID': int(root.find('AgentID').text) if root.find('AgentID') 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':
|
if message_data['MsgType'] == 'image':
|
||||||
message_data['MediaId'] = (
|
message_data['MediaId'] = root.find('MediaId').text if root.find('MediaId') is not None else None
|
||||||
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['PicUrl'] = (
|
|
||||||
root.find('PicUrl').text if root.find('PicUrl') is not None else None
|
|
||||||
)
|
|
||||||
|
|
||||||
return message_data
|
return message_data
|
||||||
|
|
||||||
@@ -298,12 +268,7 @@ class WecomClient:
|
|||||||
if not await self.check_access_token():
|
if not await self.check_access_token():
|
||||||
self.access_token = await self.get_access_token(self.secret)
|
self.access_token = await self.get_access_token(self.secret)
|
||||||
|
|
||||||
url = (
|
url = self.base_url + '/media/upload?access_token=' + self.access_token + '&type=file'
|
||||||
self.base_url
|
|
||||||
+ '/media/upload?access_token='
|
|
||||||
+ self.access_token
|
|
||||||
+ '&type=file'
|
|
||||||
)
|
|
||||||
file_bytes = None
|
file_bytes = None
|
||||||
file_name = 'uploaded_file.txt'
|
file_name = 'uploaded_file.txt'
|
||||||
|
|
||||||
|
|||||||
@@ -6,60 +6,61 @@ import httpx
|
|||||||
import traceback
|
import traceback
|
||||||
from quart import Quart
|
from quart import Quart
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
from typing import Callable, Dict, Any
|
from typing import Callable
|
||||||
from .wecomcsevent import WecomCSEvent
|
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
|
import aiofiles
|
||||||
|
|
||||||
|
|
||||||
class WecomCSClient():
|
class WecomCSClient:
|
||||||
def __init__(self,corpid:str,secret:str,token:str,EncodingAESKey:str):
|
def __init__(self, corpid: str, secret: str, token: str, EncodingAESKey: str):
|
||||||
self.corpid = corpid
|
self.corpid = corpid
|
||||||
self.secret = secret
|
self.secret = secret
|
||||||
self.access_token_for_contacts =''
|
self.access_token_for_contacts = ''
|
||||||
self.token = token
|
self.token = token
|
||||||
self.aes = EncodingAESKey
|
self.aes = EncodingAESKey
|
||||||
self.base_url = 'https://qyapi.weixin.qq.com/cgi-bin'
|
self.base_url = 'https://qyapi.weixin.qq.com/cgi-bin'
|
||||||
self.access_token = ''
|
self.access_token = ''
|
||||||
self.app = Quart(__name__)
|
self.app = Quart(__name__)
|
||||||
self.wxcpt = WXBizMsgCrypt(self.token, self.aes, self.corpid)
|
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 = {
|
self._message_handlers = {
|
||||||
"example":[],
|
'example': [],
|
||||||
}
|
}
|
||||||
|
|
||||||
async def get_pic_url(self, media_id: str):
|
async def get_pic_url(self, media_id: str):
|
||||||
if not await self.check_access_token():
|
if not await self.check_access_token():
|
||||||
self.access_token = await self.get_access_token(self.secret)
|
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:
|
async with httpx.AsyncClient() as client:
|
||||||
response = await client.get(url)
|
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()
|
data = response.json()
|
||||||
if data.get('errcode') in [40014, 42001]:
|
if data.get('errcode') in [40014, 42001]:
|
||||||
self.access_token = await self.get_access_token(self.secret)
|
self.access_token = await self.get_access_token(self.secret)
|
||||||
return await self.get_pic_url(media_id)
|
return await self.get_pic_url(media_id)
|
||||||
else:
|
else:
|
||||||
raise Exception("Failed to get image: " + str(data))
|
raise Exception('Failed to get image: ' + str(data))
|
||||||
|
|
||||||
# 否则是图片,转成 base64
|
# 否则是图片,转成 base64
|
||||||
image_bytes = response.content
|
image_bytes = response.content
|
||||||
content_type = response.headers.get("Content-Type", "")
|
content_type = response.headers.get('Content-Type', '')
|
||||||
base64_str = base64.b64encode(image_bytes).decode("utf-8")
|
base64_str = base64.b64encode(image_bytes).decode('utf-8')
|
||||||
base64_str = f"data:{content_type};base64,{base64_str}"
|
base64_str = f'data:{content_type};base64,{base64_str}'
|
||||||
return base64_str
|
return base64_str
|
||||||
|
|
||||||
|
# access——token操作
|
||||||
#access——token操作
|
|
||||||
async def check_access_token(self):
|
async def check_access_token(self):
|
||||||
return bool(self.access_token and self.access_token.strip())
|
return bool(self.access_token and self.access_token.strip())
|
||||||
|
|
||||||
async def check_access_token_for_contacts(self):
|
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):
|
async def get_access_token(self, secret):
|
||||||
url = f'https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={self.corpid}&corpsecret={secret}'
|
url = f'https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={self.corpid}&corpsecret={secret}'
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
response = await client.get(url)
|
response = await client.get(url)
|
||||||
@@ -67,118 +68,115 @@ class WecomCSClient():
|
|||||||
if 'access_token' in data:
|
if 'access_token' in data:
|
||||||
return data['access_token']
|
return data['access_token']
|
||||||
else:
|
else:
|
||||||
raise Exception(f"未获取access token: {data}")
|
raise Exception(f'未获取access token: {data}')
|
||||||
|
|
||||||
async def get_detailed_message_list(self,xml_msg:str):
|
async def get_detailed_message_list(self, xml_msg: str):
|
||||||
# 在本方法中解析消息,并且获得消息的具体内容
|
# 在本方法中解析消息,并且获得消息的具体内容
|
||||||
if isinstance(xml_msg, bytes):
|
if isinstance(xml_msg, bytes):
|
||||||
xml_msg = xml_msg.decode('utf-8')
|
xml_msg = xml_msg.decode('utf-8')
|
||||||
root = ET.fromstring(xml_msg)
|
root = ET.fromstring(xml_msg)
|
||||||
token = root.find("Token").text
|
token = root.find('Token').text
|
||||||
open_kfid = root.find("OpenKfId").text
|
open_kfid = root.find('OpenKfId').text
|
||||||
|
|
||||||
# if open_kfid in self.openkfid_list:
|
# if open_kfid in self.openkfid_list:
|
||||||
# return None
|
# return None
|
||||||
# else:
|
# else:
|
||||||
# self.openkfid_list.append(open_kfid)
|
# self.openkfid_list.append(open_kfid)
|
||||||
|
|
||||||
if not await self.check_access_token():
|
if not await self.check_access_token():
|
||||||
self.access_token = await self.get_access_token(self.secret)
|
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:
|
async with httpx.AsyncClient() as client:
|
||||||
params = {
|
params = {
|
||||||
"token": token,
|
'token': token,
|
||||||
"voice_format": 0,
|
'voice_format': 0,
|
||||||
"open_kfid": open_kfid,
|
'open_kfid': open_kfid,
|
||||||
}
|
}
|
||||||
response = await client.post(url,json=params)
|
response = await client.post(url, json=params)
|
||||||
data = response.json()
|
data = response.json()
|
||||||
if data['errcode'] == 40014 or data['errcode'] == 42001:
|
if data['errcode'] == 40014 or data['errcode'] == 42001:
|
||||||
self.access_token = await self.get_access_token(self.secret)
|
self.access_token = await self.get_access_token(self.secret)
|
||||||
return await self.get_detailed_message_list(xml_msg)
|
return await self.get_detailed_message_list(xml_msg)
|
||||||
if data['errcode'] != 0:
|
if data['errcode'] != 0:
|
||||||
raise Exception("Failed to get message")
|
raise Exception('Failed to get message')
|
||||||
|
|
||||||
last_msg_data = data['msg_list'][-1]
|
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":
|
if last_msg_data.get('msgtype') == 'image':
|
||||||
media_id = last_msg_data.get("image").get("media_id")
|
media_id = last_msg_data.get('image').get('media_id')
|
||||||
picurl = await self.get_pic_url(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)
|
# await self.change_service_status(userid=external_userid,openkfid=open_kfid,servicer=servicer)
|
||||||
return last_msg_data
|
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():
|
if not await self.check_access_token():
|
||||||
self.access_token = await self.get_access_token(self.secret)
|
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:
|
async with httpx.AsyncClient() as client:
|
||||||
params = {
|
params = {
|
||||||
"open_kfid" : openkfid,
|
'open_kfid': openkfid,
|
||||||
"external_userid" : userid,
|
'external_userid': userid,
|
||||||
"service_state" : 1,
|
'service_state': 1,
|
||||||
"servicer_userid" : servicer,
|
'servicer_userid': servicer,
|
||||||
}
|
}
|
||||||
response = await client.post(url,json=params)
|
response = await client.post(url, json=params)
|
||||||
data = response.json()
|
data = response.json()
|
||||||
if data['errcode'] == 40014 or data['errcode'] == 42001:
|
if data['errcode'] == 40014 or data['errcode'] == 42001:
|
||||||
self.access_token = await self.get_access_token(self.secret)
|
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:
|
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():
|
if not await self.check_access_token():
|
||||||
self.access_token = await self.get_access_token(self.secret)
|
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:
|
async with httpx.AsyncClient() as client:
|
||||||
params = {
|
params = {
|
||||||
"touser" : user_id,
|
'touser': user_id,
|
||||||
"toparty" : "",
|
'toparty': '',
|
||||||
"totag":"",
|
'totag': '',
|
||||||
"agentid" : agent_id,
|
'agentid': agent_id,
|
||||||
"msgtype" : "image",
|
'msgtype': 'image',
|
||||||
"image" : {
|
'image': {
|
||||||
"media_id" : media_id,
|
'media_id': media_id,
|
||||||
},
|
},
|
||||||
"safe":0,
|
'safe': 0,
|
||||||
"enable_id_trans": 0,
|
'enable_id_trans': 0,
|
||||||
"enable_duplicate_check": 0,
|
'enable_duplicate_check': 0,
|
||||||
"duplicate_check_interval": 1800
|
'duplicate_check_interval': 1800,
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
response = await client.post(url,json=params)
|
response = await client.post(url, json=params)
|
||||||
data = response.json()
|
data = response.json()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception("Failed to send image: "+str(e))
|
raise Exception('Failed to send image: ' + str(e))
|
||||||
|
|
||||||
# 企业微信错误码40014和42001,代表accesstoken问题
|
# 企业微信错误码40014和42001,代表accesstoken问题
|
||||||
if data['errcode'] == 40014 or data['errcode'] == 42001:
|
if data['errcode'] == 40014 or data['errcode'] == 42001:
|
||||||
self.access_token = await self.get_access_token(self.secret)
|
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:
|
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():
|
if not await self.check_access_token():
|
||||||
self.access_token = await self.get_access_token(self.secret)
|
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 = {
|
payload = {
|
||||||
"touser": external_userid,
|
'touser': external_userid,
|
||||||
"open_kfid": open_kfid,
|
'open_kfid': open_kfid,
|
||||||
"msgid": msgid,
|
'msgid': msgid,
|
||||||
"msgtype": "text",
|
'msgtype': 'text',
|
||||||
"text": {
|
'text': {
|
||||||
"content": content,
|
'content': content,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
@@ -187,46 +185,44 @@ class WecomCSClient():
|
|||||||
data = response.json()
|
data = response.json()
|
||||||
if data['errcode'] == 40014 or data['errcode'] == 42001:
|
if data['errcode'] == 40014 or data['errcode'] == 42001:
|
||||||
self.access_token = await self.get_access_token(self.secret)
|
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:
|
if data['errcode'] != 0:
|
||||||
raise Exception("Failed to send message")
|
raise Exception('Failed to send message')
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
async def handle_callback_request(self):
|
async def handle_callback_request(self):
|
||||||
"""
|
"""
|
||||||
处理回调请求,包括 GET 验证和 POST 消息接收。
|
处理回调请求,包括 GET 验证和 POST 消息接收。
|
||||||
"""
|
"""
|
||||||
try:
|
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")
|
if request.method == 'GET':
|
||||||
timestamp = request.args.get("timestamp")
|
echostr = request.args.get('echostr')
|
||||||
nonce = request.args.get("nonce")
|
|
||||||
|
|
||||||
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:
|
if ret != 0:
|
||||||
raise Exception(f"验证失败,错误码: {ret}")
|
raise Exception(f'验证失败,错误码: {ret}')
|
||||||
return reply_echo_str
|
return reply_echo_str
|
||||||
|
|
||||||
elif request.method == "POST":
|
elif request.method == 'POST':
|
||||||
encrypt_msg = await request.data
|
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:
|
if ret != 0:
|
||||||
raise Exception(f"消息解密失败,错误码: {ret}")
|
raise Exception(f'消息解密失败,错误码: {ret}')
|
||||||
|
|
||||||
# 解析消息并处理
|
# 解析消息并处理
|
||||||
message_data = await self.get_detailed_message_list(xml_msg)
|
message_data = await self.get_detailed_message_list(xml_msg)
|
||||||
if message_data is not None:
|
if message_data is not None:
|
||||||
event = WecomCSEvent.from_payload(message_data)
|
event = WecomCSEvent.from_payload(message_data)
|
||||||
if event:
|
if event:
|
||||||
await self._handle_message(event)
|
await self._handle_message(event)
|
||||||
|
|
||||||
return "success"
|
return 'success'
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
traceback.print_exc()
|
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):
|
async def run_task(self, host: str, port: int, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
@@ -238,11 +234,13 @@ class WecomCSClient():
|
|||||||
"""
|
"""
|
||||||
注册消息类型处理器。
|
注册消息类型处理器。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(func: Callable[[WecomCSEvent], None]):
|
def decorator(func: Callable[[WecomCSEvent], None]):
|
||||||
if msg_type not in self._message_handlers:
|
if msg_type not in self._message_handlers:
|
||||||
self._message_handlers[msg_type] = []
|
self._message_handlers[msg_type] = []
|
||||||
self._message_handlers[msg_type].append(func)
|
self._message_handlers[msg_type].append(func)
|
||||||
return func
|
return func
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
async def _handle_message(self, event: WecomCSEvent):
|
async def _handle_message(self, event: WecomCSEvent):
|
||||||
@@ -254,25 +252,23 @@ class WecomCSClient():
|
|||||||
for handler in self._message_handlers[msg_type]:
|
for handler in self._message_handlers[msg_type]:
|
||||||
await handler(event)
|
await handler(event)
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def get_image_type(image_bytes: bytes) -> str:
|
async def get_image_type(image_bytes: bytes) -> str:
|
||||||
"""
|
"""
|
||||||
通过图片的magic numbers判断图片类型
|
通过图片的magic numbers判断图片类型
|
||||||
"""
|
"""
|
||||||
magic_numbers = {
|
magic_numbers = {
|
||||||
b'\xFF\xD8\xFF': 'jpg',
|
b'\xff\xd8\xff': 'jpg',
|
||||||
b'\x89\x50\x4E\x47': 'png',
|
b'\x89\x50\x4e\x47': 'png',
|
||||||
b'\x47\x49\x46': 'gif',
|
b'\x47\x49\x46': 'gif',
|
||||||
b'\x42\x4D': 'bmp',
|
b'\x42\x4d': 'bmp',
|
||||||
b'\x00\x00\x01\x00': 'ico'
|
b'\x00\x00\x01\x00': 'ico',
|
||||||
}
|
}
|
||||||
|
|
||||||
for magic, ext in magic_numbers.items():
|
for magic, ext in magic_numbers.items():
|
||||||
if image_bytes.startswith(magic):
|
if image_bytes.startswith(magic):
|
||||||
return ext
|
return ext
|
||||||
return 'jpg' # 默认返回jpg
|
return 'jpg' # 默认返回jpg
|
||||||
|
|
||||||
|
|
||||||
async def upload_to_work(self, image: platform_message.Image):
|
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'
|
url = self.base_url + '/media/upload?access_token=' + self.access_token + '&type=file'
|
||||||
file_bytes = None
|
file_bytes = None
|
||||||
file_name = "uploaded_file.txt"
|
file_name = 'uploaded_file.txt'
|
||||||
|
|
||||||
# 获取文件的二进制数据
|
# 获取文件的二进制数据
|
||||||
if image.path:
|
if image.path:
|
||||||
@@ -302,20 +298,22 @@ class WecomCSClient():
|
|||||||
padded_base64 = base64_data + '=' * padding
|
padded_base64 = base64_data + '=' * padding
|
||||||
file_bytes = base64.b64decode(padded_base64)
|
file_bytes = base64.b64decode(padded_base64)
|
||||||
except binascii.Error as e:
|
except binascii.Error as e:
|
||||||
raise ValueError(f"Invalid base64 string: {str(e)}")
|
raise ValueError(f'Invalid base64 string: {str(e)}')
|
||||||
else:
|
else:
|
||||||
raise ValueError("image对象出错")
|
raise ValueError('image对象出错')
|
||||||
|
|
||||||
# 设置 multipart/form-data 格式的文件
|
# 设置 multipart/form-data 格式的文件
|
||||||
boundary = "-------------------------acebdf13572468"
|
boundary = '-------------------------acebdf13572468'
|
||||||
headers = {
|
headers = {'Content-Type': f'multipart/form-data; boundary={boundary}'}
|
||||||
'Content-Type': f'multipart/form-data; boundary={boundary}'
|
|
||||||
}
|
|
||||||
body = (
|
body = (
|
||||||
f"--{boundary}\r\n"
|
(
|
||||||
f"Content-Disposition: form-data; name=\"media\"; filename=\"{file_name}\"; filelength={len(file_bytes)}\r\n"
|
f'--{boundary}\r\n'
|
||||||
f"Content-Type: application/octet-stream\r\n\r\n"
|
f'Content-Disposition: form-data; name="media"; filename="{file_name}"; filelength={len(file_bytes)}\r\n'
|
||||||
).encode('utf-8') + file_bytes + f"\r\n--{boundary}--\r\n".encode('utf-8')
|
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:
|
async with httpx.AsyncClient() as client:
|
||||||
@@ -325,19 +323,18 @@ class WecomCSClient():
|
|||||||
self.access_token = await self.get_access_token(self.secret)
|
self.access_token = await self.get_access_token(self.secret)
|
||||||
media_id = await self.upload_to_work(image)
|
media_id = await self.upload_to_work(image)
|
||||||
if data.get('errcode', 0) != 0:
|
if data.get('errcode', 0) != 0:
|
||||||
raise Exception("failed to upload file")
|
raise Exception('failed to upload file')
|
||||||
|
|
||||||
media_id = data.get('media_id')
|
media_id = data.get('media_id')
|
||||||
return 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:
|
async with httpx.AsyncClient() as client:
|
||||||
response = await client.get(url)
|
response = await client.get(url)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return response.content
|
return response.content
|
||||||
|
|
||||||
#进行media_id的获取
|
# 进行media_id的获取
|
||||||
async def get_media_id(self, image: platform_message.Image):
|
async def get_media_id(self, image: platform_message.Image):
|
||||||
|
|
||||||
media_id = await self.upload_to_work(image=image)
|
media_id = await self.upload_to_work(image=image)
|
||||||
return media_id
|
return media_id
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class WecomCSEvent(dict):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_payload(payload: Dict[str, Any]) -> Optional["WecomCSEvent"]:
|
def from_payload(payload: Dict[str, Any]) -> Optional['WecomCSEvent']:
|
||||||
"""
|
"""
|
||||||
从企业微信(客服会话)事件数据构造 `WecomEvent` 对象。
|
从企业微信(客服会话)事件数据构造 `WecomEvent` 对象。
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ class WecomCSEvent(dict):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
event = WecomCSEvent(payload)
|
event = WecomCSEvent(payload)
|
||||||
_ = event.type,
|
_ = (event.type,)
|
||||||
return event
|
return event
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
return None
|
||||||
@@ -34,8 +34,8 @@ class WecomCSEvent(dict):
|
|||||||
Returns:
|
Returns:
|
||||||
str: 事件类型。
|
str: 事件类型。
|
||||||
"""
|
"""
|
||||||
return self.get("msgtype", "")
|
return self.get('msgtype', '')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def user_id(self) -> Optional[str]:
|
def user_id(self) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
@@ -44,7 +44,7 @@ class WecomCSEvent(dict):
|
|||||||
Returns:
|
Returns:
|
||||||
Optional[str]: 用户 ID。
|
Optional[str]: 用户 ID。
|
||||||
"""
|
"""
|
||||||
return self.get("external_userid")
|
return self.get('external_userid')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def receiver_id(self) -> Optional[str]:
|
def receiver_id(self) -> Optional[str]:
|
||||||
@@ -54,8 +54,8 @@ class WecomCSEvent(dict):
|
|||||||
Returns:
|
Returns:
|
||||||
Optional[str]: 接收者 ID。
|
Optional[str]: 接收者 ID。
|
||||||
"""
|
"""
|
||||||
return self.get("open_kfid","")
|
return self.get('open_kfid', '')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def picurl(self) -> Optional[str]:
|
def picurl(self) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
@@ -65,7 +65,7 @@ class WecomCSEvent(dict):
|
|||||||
Optional[str]: 图片 URL。
|
Optional[str]: 图片 URL。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return self.get("picurl","")
|
return self.get('picurl', '')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def message_id(self) -> Optional[str]:
|
def message_id(self) -> Optional[str]:
|
||||||
@@ -75,7 +75,7 @@ class WecomCSEvent(dict):
|
|||||||
Returns:
|
Returns:
|
||||||
Optional[str]: 消息 ID。
|
Optional[str]: 消息 ID。
|
||||||
"""
|
"""
|
||||||
return self.get("msgid")
|
return self.get('msgid')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def message(self) -> Optional[str]:
|
def message(self) -> Optional[str]:
|
||||||
@@ -85,12 +85,11 @@ class WecomCSEvent(dict):
|
|||||||
Returns:
|
Returns:
|
||||||
Optional[str]: 消息内容。
|
Optional[str]: 消息内容。
|
||||||
"""
|
"""
|
||||||
if self.get("msgtype") == 'text':
|
if self.get('msgtype') == 'text':
|
||||||
return self.get("text").get("content","")
|
return self.get('text').get('content', '')
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def timestamp(self) -> Optional[int]:
|
def timestamp(self) -> Optional[int]:
|
||||||
"""
|
"""
|
||||||
@@ -99,8 +98,7 @@ class WecomCSEvent(dict):
|
|||||||
Returns:
|
Returns:
|
||||||
Optional[int]: 时间戳。
|
Optional[int]: 时间戳。
|
||||||
"""
|
"""
|
||||||
return self.get("send_time")
|
return self.get('send_time')
|
||||||
|
|
||||||
|
|
||||||
def __getattr__(self, key: str) -> Optional[Any]:
|
def __getattr__(self, key: str) -> Optional[Any]:
|
||||||
"""
|
"""
|
||||||
@@ -131,4 +129,4 @@ class WecomCSEvent(dict):
|
|||||||
Returns:
|
Returns:
|
||||||
str: 字符串表示。
|
str: 字符串表示。
|
||||||
"""
|
"""
|
||||||
return f"<WecomEvent {super().__repr__()}>"
|
return f'<WecomEvent {super().__repr__()}>'
|
||||||
|
|||||||
@@ -65,9 +65,7 @@ class RouterGroup(abc.ABC):
|
|||||||
async def handler_error(*args, **kwargs):
|
async def handler_error(*args, **kwargs):
|
||||||
if auth_type == AuthType.USER_TOKEN:
|
if auth_type == AuthType.USER_TOKEN:
|
||||||
# 从Authorization头中获取token
|
# 从Authorization头中获取token
|
||||||
token = quart.request.headers.get('Authorization', '').replace(
|
token = quart.request.headers.get('Authorization', '').replace('Bearer ', '')
|
||||||
'Bearer ', ''
|
|
||||||
)
|
|
||||||
|
|
||||||
if not token:
|
if not token:
|
||||||
return self.http_status(401, -1, '未提供有效的用户令牌')
|
return self.http_status(401, -1, '未提供有效的用户令牌')
|
||||||
|
|||||||
@@ -14,10 +14,8 @@ class LogsRouterGroup(group.RouterGroup):
|
|||||||
start_page_number = int(quart.request.args.get('start_page_number', 0))
|
start_page_number = int(quart.request.args.get('start_page_number', 0))
|
||||||
start_offset = int(quart.request.args.get('start_offset', 0))
|
start_offset = int(quart.request.args.get('start_offset', 0))
|
||||||
|
|
||||||
logs_str, end_page_number, end_offset = (
|
logs_str, end_page_number, end_offset = self.ap.log_cache.get_log_by_pointer(
|
||||||
self.ap.log_cache.get_log_by_pointer(
|
start_page_number=start_page_number, start_offset=start_offset
|
||||||
start_page_number=start_page_number, start_offset=start_offset
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return self.success(
|
return self.success(
|
||||||
|
|||||||
@@ -11,23 +11,17 @@ class PipelinesRouterGroup(group.RouterGroup):
|
|||||||
@self.route('', methods=['GET', 'POST'])
|
@self.route('', methods=['GET', 'POST'])
|
||||||
async def _() -> str:
|
async def _() -> str:
|
||||||
if quart.request.method == 'GET':
|
if quart.request.method == 'GET':
|
||||||
return self.success(
|
return self.success(data={'pipelines': await self.ap.pipeline_service.get_pipelines()})
|
||||||
data={'pipelines': await self.ap.pipeline_service.get_pipelines()}
|
|
||||||
)
|
|
||||||
elif quart.request.method == 'POST':
|
elif quart.request.method == 'POST':
|
||||||
json_data = await quart.request.json
|
json_data = await quart.request.json
|
||||||
|
|
||||||
pipeline_uuid = await self.ap.pipeline_service.create_pipeline(
|
pipeline_uuid = await self.ap.pipeline_service.create_pipeline(json_data)
|
||||||
json_data
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.success(data={'uuid': pipeline_uuid})
|
return self.success(data={'uuid': pipeline_uuid})
|
||||||
|
|
||||||
@self.route('/_/metadata', methods=['GET'])
|
@self.route('/_/metadata', methods=['GET'])
|
||||||
async def _() -> str:
|
async def _() -> str:
|
||||||
return self.success(
|
return self.success(data={'configs': await self.ap.pipeline_service.get_pipeline_metadata()})
|
||||||
data={'configs': await self.ap.pipeline_service.get_pipeline_metadata()}
|
|
||||||
)
|
|
||||||
|
|
||||||
@self.route('/<pipeline_uuid>', methods=['GET', 'PUT', 'DELETE'])
|
@self.route('/<pipeline_uuid>', methods=['GET', 'PUT', 'DELETE'])
|
||||||
async def _(pipeline_uuid: str) -> str:
|
async def _(pipeline_uuid: str) -> str:
|
||||||
|
|||||||
@@ -8,30 +8,20 @@ class AdaptersRouterGroup(group.RouterGroup):
|
|||||||
async def initialize(self) -> None:
|
async def initialize(self) -> None:
|
||||||
@self.route('', methods=['GET'])
|
@self.route('', methods=['GET'])
|
||||||
async def _() -> str:
|
async def _() -> str:
|
||||||
return self.success(
|
return self.success(data={'adapters': self.ap.platform_mgr.get_available_adapters_info()})
|
||||||
data={'adapters': self.ap.platform_mgr.get_available_adapters_info()}
|
|
||||||
)
|
|
||||||
|
|
||||||
@self.route('/<adapter_name>', methods=['GET'])
|
@self.route('/<adapter_name>', methods=['GET'])
|
||||||
async def _(adapter_name: str) -> str:
|
async def _(adapter_name: str) -> str:
|
||||||
adapter_info = self.ap.platform_mgr.get_available_adapter_info_by_name(
|
adapter_info = self.ap.platform_mgr.get_available_adapter_info_by_name(adapter_name)
|
||||||
adapter_name
|
|
||||||
)
|
|
||||||
|
|
||||||
if adapter_info is None:
|
if adapter_info is None:
|
||||||
return self.http_status(404, -1, 'adapter not found')
|
return self.http_status(404, -1, 'adapter not found')
|
||||||
|
|
||||||
return self.success(data={'adapter': adapter_info})
|
return self.success(data={'adapter': adapter_info})
|
||||||
|
|
||||||
@self.route(
|
@self.route('/<adapter_name>/icon', methods=['GET'], auth_type=group.AuthType.NONE)
|
||||||
'/<adapter_name>/icon', methods=['GET'], auth_type=group.AuthType.NONE
|
|
||||||
)
|
|
||||||
async def _(adapter_name: str) -> quart.Response:
|
async def _(adapter_name: str) -> quart.Response:
|
||||||
adapter_manifest = (
|
adapter_manifest = self.ap.platform_mgr.get_available_adapter_manifest_by_name(adapter_name)
|
||||||
self.ap.platform_mgr.get_available_adapter_manifest_by_name(
|
|
||||||
adapter_name
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if adapter_manifest is None:
|
if adapter_manifest is None:
|
||||||
return self.http_status(404, -1, 'adapter not found')
|
return self.http_status(404, -1, 'adapter not found')
|
||||||
|
|||||||
@@ -92,9 +92,7 @@ class PluginsRouterGroup(group.RouterGroup):
|
|||||||
await self.ap.plugin_mgr.reorder_plugins(data.get('plugins'))
|
await self.ap.plugin_mgr.reorder_plugins(data.get('plugins'))
|
||||||
return self.success()
|
return self.success()
|
||||||
|
|
||||||
@self.route(
|
@self.route('/install/github', methods=['POST'], auth_type=group.AuthType.USER_TOKEN)
|
||||||
'/install/github', methods=['POST'], auth_type=group.AuthType.USER_TOKEN
|
|
||||||
)
|
|
||||||
async def _() -> str:
|
async def _() -> str:
|
||||||
data = await quart.request.json
|
data = await quart.request.json
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,7 @@ class LLMModelsRouterGroup(group.RouterGroup):
|
|||||||
@self.route('', methods=['GET', 'POST'])
|
@self.route('', methods=['GET', 'POST'])
|
||||||
async def _() -> str:
|
async def _() -> str:
|
||||||
if quart.request.method == 'GET':
|
if quart.request.method == 'GET':
|
||||||
return self.success(
|
return self.success(data={'models': await self.ap.model_service.get_llm_models()})
|
||||||
data={'models': await self.ap.model_service.get_llm_models()}
|
|
||||||
)
|
|
||||||
elif quart.request.method == 'POST':
|
elif quart.request.method == 'POST':
|
||||||
json_data = await quart.request.json
|
json_data = await quart.request.json
|
||||||
|
|
||||||
|
|||||||
@@ -8,30 +8,20 @@ class RequestersRouterGroup(group.RouterGroup):
|
|||||||
async def initialize(self) -> None:
|
async def initialize(self) -> None:
|
||||||
@self.route('', methods=['GET'])
|
@self.route('', methods=['GET'])
|
||||||
async def _() -> quart.Response:
|
async def _() -> quart.Response:
|
||||||
return self.success(
|
return self.success(data={'requesters': self.ap.model_mgr.get_available_requesters_info()})
|
||||||
data={'requesters': self.ap.model_mgr.get_available_requesters_info()}
|
|
||||||
)
|
|
||||||
|
|
||||||
@self.route('/<requester_name>', methods=['GET'])
|
@self.route('/<requester_name>', methods=['GET'])
|
||||||
async def _(requester_name: str) -> quart.Response:
|
async def _(requester_name: str) -> quart.Response:
|
||||||
requester_info = self.ap.model_mgr.get_available_requester_info_by_name(
|
requester_info = self.ap.model_mgr.get_available_requester_info_by_name(requester_name)
|
||||||
requester_name
|
|
||||||
)
|
|
||||||
|
|
||||||
if requester_info is None:
|
if requester_info is None:
|
||||||
return self.http_status(404, -1, 'requester not found')
|
return self.http_status(404, -1, 'requester not found')
|
||||||
|
|
||||||
return self.success(data={'requester': requester_info})
|
return self.success(data={'requester': requester_info})
|
||||||
|
|
||||||
@self.route(
|
@self.route('/<requester_name>/icon', methods=['GET'], auth_type=group.AuthType.NONE)
|
||||||
'/<requester_name>/icon', methods=['GET'], auth_type=group.AuthType.NONE
|
|
||||||
)
|
|
||||||
async def _(requester_name: str) -> quart.Response:
|
async def _(requester_name: str) -> quart.Response:
|
||||||
requester_manifest = (
|
requester_manifest = self.ap.model_mgr.get_available_requester_manifest_by_name(requester_name)
|
||||||
self.ap.model_mgr.get_available_requester_manifest_by_name(
|
|
||||||
requester_name
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if requester_manifest is None:
|
if requester_manifest is None:
|
||||||
return self.http_status(404, -1, 'requester not found')
|
return self.http_status(404, -1, 'requester not found')
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ class StatsRouterGroup(group.RouterGroup):
|
|||||||
async def _() -> str:
|
async def _() -> str:
|
||||||
conv_count = 0
|
conv_count = 0
|
||||||
for session in self.ap.sess_mgr.session_list:
|
for session in self.ap.sess_mgr.session_list:
|
||||||
conv_count += len(
|
conv_count += len(session.conversations if session.conversations is not None else [])
|
||||||
session.conversations if session.conversations is not None else []
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.success(
|
return self.success(
|
||||||
data={
|
data={
|
||||||
|
|||||||
@@ -13,9 +13,7 @@ class SystemRouterGroup(group.RouterGroup):
|
|||||||
data={
|
data={
|
||||||
'version': constants.semantic_version,
|
'version': constants.semantic_version,
|
||||||
'debug': constants.debug_mode,
|
'debug': constants.debug_mode,
|
||||||
'enabled_platform_count': len(
|
'enabled_platform_count': len(self.ap.platform_mgr.get_running_adapters()),
|
||||||
self.ap.platform_mgr.get_running_adapters()
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,9 +26,7 @@ class SystemRouterGroup(group.RouterGroup):
|
|||||||
|
|
||||||
return self.success(data=self.ap.task_mgr.get_tasks_dict(task_type))
|
return self.success(data=self.ap.task_mgr.get_tasks_dict(task_type))
|
||||||
|
|
||||||
@self.route(
|
@self.route('/tasks/<task_id>', methods=['GET'], auth_type=group.AuthType.USER_TOKEN)
|
||||||
'/tasks/<task_id>', methods=['GET'], auth_type=group.AuthType.USER_TOKEN
|
|
||||||
)
|
|
||||||
async def _(task_id: str) -> str:
|
async def _(task_id: str) -> str:
|
||||||
task = self.ap.task_mgr.get_task_by_id(int(task_id))
|
task = self.ap.task_mgr.get_task_by_id(int(task_id))
|
||||||
|
|
||||||
@@ -48,9 +44,7 @@ class SystemRouterGroup(group.RouterGroup):
|
|||||||
await self.ap.reload(scope=scope)
|
await self.ap.reload(scope=scope)
|
||||||
return self.success()
|
return self.success()
|
||||||
|
|
||||||
@self.route(
|
@self.route('/_debug/exec', methods=['POST'], auth_type=group.AuthType.USER_TOKEN)
|
||||||
'/_debug/exec', methods=['POST'], auth_type=group.AuthType.USER_TOKEN
|
|
||||||
)
|
|
||||||
async def _() -> str:
|
async def _() -> str:
|
||||||
if not constants.debug_mode:
|
if not constants.debug_mode:
|
||||||
return self.http_status(403, 403, 'Forbidden')
|
return self.http_status(403, 403, 'Forbidden')
|
||||||
|
|||||||
@@ -10,9 +10,7 @@ class UserRouterGroup(group.RouterGroup):
|
|||||||
@self.route('/init', methods=['GET', 'POST'], auth_type=group.AuthType.NONE)
|
@self.route('/init', methods=['GET', 'POST'], auth_type=group.AuthType.NONE)
|
||||||
async def _() -> str:
|
async def _() -> str:
|
||||||
if quart.request.method == 'GET':
|
if quart.request.method == 'GET':
|
||||||
return self.success(
|
return self.success(data={'initialized': await self.ap.user_service.is_initialized()})
|
||||||
data={'initialized': await self.ap.user_service.is_initialized()}
|
|
||||||
)
|
|
||||||
|
|
||||||
if await self.ap.user_service.is_initialized():
|
if await self.ap.user_service.is_initialized():
|
||||||
return self.fail(1, '系统已初始化')
|
return self.fail(1, '系统已初始化')
|
||||||
@@ -31,17 +29,13 @@ class UserRouterGroup(group.RouterGroup):
|
|||||||
json_data = await quart.request.json
|
json_data = await quart.request.json
|
||||||
|
|
||||||
try:
|
try:
|
||||||
token = await self.ap.user_service.authenticate(
|
token = await self.ap.user_service.authenticate(json_data['user'], json_data['password'])
|
||||||
json_data['user'], json_data['password']
|
|
||||||
)
|
|
||||||
except argon2.exceptions.VerifyMismatchError:
|
except argon2.exceptions.VerifyMismatchError:
|
||||||
return self.fail(1, '用户名或密码错误')
|
return self.fail(1, '用户名或密码错误')
|
||||||
|
|
||||||
return self.success(data={'token': token})
|
return self.success(data={'token': token})
|
||||||
|
|
||||||
@self.route(
|
@self.route('/check-token', methods=['GET'], auth_type=group.AuthType.USER_TOKEN)
|
||||||
'/check-token', methods=['GET'], auth_type=group.AuthType.USER_TOKEN
|
|
||||||
)
|
|
||||||
async def _(user_email: str) -> str:
|
async def _(user_email: str) -> str:
|
||||||
token = await self.ap.user_service.generate_jwt_token(user_email)
|
token = await self.ap.user_service.generate_jwt_token(user_email)
|
||||||
|
|
||||||
|
|||||||
@@ -70,15 +70,12 @@ class HTTPController:
|
|||||||
|
|
||||||
@self.quart_app.route('/')
|
@self.quart_app.route('/')
|
||||||
async def index():
|
async def index():
|
||||||
return await quart.send_from_directory(
|
return await quart.send_from_directory(frontend_path, 'index.html', mimetype='text/html')
|
||||||
frontend_path, 'index.html', mimetype='text/html'
|
|
||||||
)
|
|
||||||
|
|
||||||
@self.quart_app.route('/<path:path>')
|
@self.quart_app.route('/<path:path>')
|
||||||
async def static_file(path: str):
|
async def static_file(path: str):
|
||||||
if not (
|
if not (
|
||||||
os.path.exists(os.path.join(frontend_path, path))
|
os.path.exists(os.path.join(frontend_path, path)) and os.path.isfile(os.path.join(frontend_path, path))
|
||||||
and os.path.isfile(os.path.join(frontend_path, path))
|
|
||||||
):
|
):
|
||||||
if os.path.exists(os.path.join(frontend_path, path + '.html')):
|
if os.path.exists(os.path.join(frontend_path, path + '.html')):
|
||||||
path += '.html'
|
path += '.html'
|
||||||
@@ -110,6 +107,4 @@ class HTTPController:
|
|||||||
elif path.endswith('.txt'):
|
elif path.endswith('.txt'):
|
||||||
mimetype = 'text/plain'
|
mimetype = 'text/plain'
|
||||||
|
|
||||||
return await quart.send_from_directory(
|
return await quart.send_from_directory(frontend_path, path, mimetype=mimetype)
|
||||||
frontend_path, path, mimetype=mimetype
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -18,23 +18,16 @@ class BotService:
|
|||||||
|
|
||||||
async def get_bots(self) -> list[dict]:
|
async def get_bots(self) -> list[dict]:
|
||||||
"""获取所有机器人"""
|
"""获取所有机器人"""
|
||||||
result = await self.ap.persistence_mgr.execute_async(
|
result = await self.ap.persistence_mgr.execute_async(sqlalchemy.select(persistence_bot.Bot))
|
||||||
sqlalchemy.select(persistence_bot.Bot)
|
|
||||||
)
|
|
||||||
|
|
||||||
bots = result.all()
|
bots = result.all()
|
||||||
|
|
||||||
return [
|
return [self.ap.persistence_mgr.serialize_model(persistence_bot.Bot, bot) for bot in bots]
|
||||||
self.ap.persistence_mgr.serialize_model(persistence_bot.Bot, bot)
|
|
||||||
for bot in bots
|
|
||||||
]
|
|
||||||
|
|
||||||
async def get_bot(self, bot_uuid: str) -> dict | None:
|
async def get_bot(self, bot_uuid: str) -> dict | None:
|
||||||
"""获取机器人"""
|
"""获取机器人"""
|
||||||
result = await self.ap.persistence_mgr.execute_async(
|
result = await self.ap.persistence_mgr.execute_async(
|
||||||
sqlalchemy.select(persistence_bot.Bot).where(
|
sqlalchemy.select(persistence_bot.Bot).where(persistence_bot.Bot.uuid == bot_uuid)
|
||||||
persistence_bot.Bot.uuid == bot_uuid
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
bot = result.first()
|
bot = result.first()
|
||||||
@@ -60,9 +53,7 @@ class BotService:
|
|||||||
bot_data['use_pipeline_uuid'] = pipeline.uuid
|
bot_data['use_pipeline_uuid'] = pipeline.uuid
|
||||||
bot_data['use_pipeline_name'] = pipeline.name
|
bot_data['use_pipeline_name'] = pipeline.name
|
||||||
|
|
||||||
await self.ap.persistence_mgr.execute_async(
|
await self.ap.persistence_mgr.execute_async(sqlalchemy.insert(persistence_bot.Bot).values(bot_data))
|
||||||
sqlalchemy.insert(persistence_bot.Bot).values(bot_data)
|
|
||||||
)
|
|
||||||
|
|
||||||
bot = await self.get_bot(bot_data['uuid'])
|
bot = await self.get_bot(bot_data['uuid'])
|
||||||
|
|
||||||
@@ -79,8 +70,7 @@ class BotService:
|
|||||||
if 'use_pipeline_uuid' in bot_data:
|
if 'use_pipeline_uuid' in bot_data:
|
||||||
result = await self.ap.persistence_mgr.execute_async(
|
result = await self.ap.persistence_mgr.execute_async(
|
||||||
sqlalchemy.select(persistence_pipeline.LegacyPipeline).where(
|
sqlalchemy.select(persistence_pipeline.LegacyPipeline).where(
|
||||||
persistence_pipeline.LegacyPipeline.uuid
|
persistence_pipeline.LegacyPipeline.uuid == bot_data['use_pipeline_uuid']
|
||||||
== bot_data['use_pipeline_uuid']
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
pipeline = result.first()
|
pipeline = result.first()
|
||||||
@@ -90,9 +80,7 @@ class BotService:
|
|||||||
raise Exception('Pipeline not found')
|
raise Exception('Pipeline not found')
|
||||||
|
|
||||||
await self.ap.persistence_mgr.execute_async(
|
await self.ap.persistence_mgr.execute_async(
|
||||||
sqlalchemy.update(persistence_bot.Bot)
|
sqlalchemy.update(persistence_bot.Bot).values(bot_data).where(persistence_bot.Bot.uuid == bot_uuid)
|
||||||
.values(bot_data)
|
|
||||||
.where(persistence_bot.Bot.uuid == bot_uuid)
|
|
||||||
)
|
)
|
||||||
await self.ap.platform_mgr.remove_bot(bot_uuid)
|
await self.ap.platform_mgr.remove_bot(bot_uuid)
|
||||||
|
|
||||||
@@ -108,7 +96,5 @@ class BotService:
|
|||||||
"""删除机器人"""
|
"""删除机器人"""
|
||||||
await self.ap.platform_mgr.remove_bot(bot_uuid)
|
await self.ap.platform_mgr.remove_bot(bot_uuid)
|
||||||
await self.ap.persistence_mgr.execute_async(
|
await self.ap.persistence_mgr.execute_async(
|
||||||
sqlalchemy.delete(persistence_bot.Bot).where(
|
sqlalchemy.delete(persistence_bot.Bot).where(persistence_bot.Bot.uuid == bot_uuid)
|
||||||
persistence_bot.Bot.uuid == bot_uuid
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,22 +15,15 @@ class ModelsService:
|
|||||||
self.ap = ap
|
self.ap = ap
|
||||||
|
|
||||||
async def get_llm_models(self) -> list[dict]:
|
async def get_llm_models(self) -> list[dict]:
|
||||||
result = await self.ap.persistence_mgr.execute_async(
|
result = await self.ap.persistence_mgr.execute_async(sqlalchemy.select(persistence_model.LLMModel))
|
||||||
sqlalchemy.select(persistence_model.LLMModel)
|
|
||||||
)
|
|
||||||
|
|
||||||
models = result.all()
|
models = result.all()
|
||||||
return [
|
return [self.ap.persistence_mgr.serialize_model(persistence_model.LLMModel, model) for model in models]
|
||||||
self.ap.persistence_mgr.serialize_model(persistence_model.LLMModel, model)
|
|
||||||
for model in models
|
|
||||||
]
|
|
||||||
|
|
||||||
async def create_llm_model(self, model_data: dict) -> str:
|
async def create_llm_model(self, model_data: dict) -> str:
|
||||||
model_data['uuid'] = str(uuid.uuid4())
|
model_data['uuid'] = str(uuid.uuid4())
|
||||||
|
|
||||||
await self.ap.persistence_mgr.execute_async(
|
await self.ap.persistence_mgr.execute_async(sqlalchemy.insert(persistence_model.LLMModel).values(**model_data))
|
||||||
sqlalchemy.insert(persistence_model.LLMModel).values(**model_data)
|
|
||||||
)
|
|
||||||
|
|
||||||
llm_model = await self.get_llm_model(model_data['uuid'])
|
llm_model = await self.get_llm_model(model_data['uuid'])
|
||||||
|
|
||||||
@@ -53,9 +46,7 @@ class ModelsService:
|
|||||||
|
|
||||||
async def get_llm_model(self, model_uuid: str) -> dict | None:
|
async def get_llm_model(self, model_uuid: str) -> dict | None:
|
||||||
result = await self.ap.persistence_mgr.execute_async(
|
result = await self.ap.persistence_mgr.execute_async(
|
||||||
sqlalchemy.select(persistence_model.LLMModel).where(
|
sqlalchemy.select(persistence_model.LLMModel).where(persistence_model.LLMModel.uuid == model_uuid)
|
||||||
persistence_model.LLMModel.uuid == model_uuid
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
model = result.first()
|
model = result.first()
|
||||||
@@ -63,9 +54,7 @@ class ModelsService:
|
|||||||
if model is None:
|
if model is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return self.ap.persistence_mgr.serialize_model(
|
return self.ap.persistence_mgr.serialize_model(persistence_model.LLMModel, model)
|
||||||
persistence_model.LLMModel, model
|
|
||||||
)
|
|
||||||
|
|
||||||
async def update_llm_model(self, model_uuid: str, model_data: dict) -> None:
|
async def update_llm_model(self, model_uuid: str, model_data: dict) -> None:
|
||||||
if 'uuid' in model_data:
|
if 'uuid' in model_data:
|
||||||
@@ -85,9 +74,7 @@ class ModelsService:
|
|||||||
|
|
||||||
async def delete_llm_model(self, model_uuid: str) -> None:
|
async def delete_llm_model(self, model_uuid: str) -> None:
|
||||||
await self.ap.persistence_mgr.execute_async(
|
await self.ap.persistence_mgr.execute_async(
|
||||||
sqlalchemy.delete(persistence_model.LLMModel).where(
|
sqlalchemy.delete(persistence_model.LLMModel).where(persistence_model.LLMModel.uuid == model_uuid)
|
||||||
persistence_model.LLMModel.uuid == model_uuid
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
await self.ap.model_mgr.remove_llm_model(model_uuid)
|
await self.ap.model_mgr.remove_llm_model(model_uuid)
|
||||||
|
|||||||
@@ -39,15 +39,11 @@ class PipelineService:
|
|||||||
]
|
]
|
||||||
|
|
||||||
async def get_pipelines(self) -> list[dict]:
|
async def get_pipelines(self) -> list[dict]:
|
||||||
result = await self.ap.persistence_mgr.execute_async(
|
result = await self.ap.persistence_mgr.execute_async(sqlalchemy.select(persistence_pipeline.LegacyPipeline))
|
||||||
sqlalchemy.select(persistence_pipeline.LegacyPipeline)
|
|
||||||
)
|
|
||||||
|
|
||||||
pipelines = result.all()
|
pipelines = result.all()
|
||||||
return [
|
return [
|
||||||
self.ap.persistence_mgr.serialize_model(
|
self.ap.persistence_mgr.serialize_model(persistence_pipeline.LegacyPipeline, pipeline)
|
||||||
persistence_pipeline.LegacyPipeline, pipeline
|
|
||||||
)
|
|
||||||
for pipeline in pipelines
|
for pipeline in pipelines
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -63,23 +59,17 @@ class PipelineService:
|
|||||||
if pipeline is None:
|
if pipeline is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return self.ap.persistence_mgr.serialize_model(
|
return self.ap.persistence_mgr.serialize_model(persistence_pipeline.LegacyPipeline, pipeline)
|
||||||
persistence_pipeline.LegacyPipeline, pipeline
|
|
||||||
)
|
|
||||||
|
|
||||||
async def create_pipeline(self, pipeline_data: dict, default: bool = False) -> str:
|
async def create_pipeline(self, pipeline_data: dict, default: bool = False) -> str:
|
||||||
pipeline_data['uuid'] = str(uuid.uuid4())
|
pipeline_data['uuid'] = str(uuid.uuid4())
|
||||||
pipeline_data['for_version'] = self.ap.ver_mgr.get_current_version()
|
pipeline_data['for_version'] = self.ap.ver_mgr.get_current_version()
|
||||||
pipeline_data['stages'] = default_stage_order.copy()
|
pipeline_data['stages'] = default_stage_order.copy()
|
||||||
pipeline_data['is_default'] = default
|
pipeline_data['is_default'] = default
|
||||||
pipeline_data['config'] = json.load(
|
pipeline_data['config'] = json.load(open('templates/default-pipeline-config.json', 'r', encoding='utf-8'))
|
||||||
open('templates/default-pipeline-config.json', 'r', encoding='utf-8')
|
|
||||||
)
|
|
||||||
|
|
||||||
await self.ap.persistence_mgr.execute_async(
|
await self.ap.persistence_mgr.execute_async(
|
||||||
sqlalchemy.insert(persistence_pipeline.LegacyPipeline).values(
|
sqlalchemy.insert(persistence_pipeline.LegacyPipeline).values(**pipeline_data)
|
||||||
**pipeline_data
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
pipeline = await self.get_pipeline(pipeline_data['uuid'])
|
pipeline = await self.get_pipeline(pipeline_data['uuid'])
|
||||||
|
|||||||
@@ -17,9 +17,7 @@ class UserService:
|
|||||||
self.ap = ap
|
self.ap = ap
|
||||||
|
|
||||||
async def is_initialized(self) -> bool:
|
async def is_initialized(self) -> bool:
|
||||||
result = await self.ap.persistence_mgr.execute_async(
|
result = await self.ap.persistence_mgr.execute_async(sqlalchemy.select(user.User).limit(1))
|
||||||
sqlalchemy.select(user.User).limit(1)
|
|
||||||
)
|
|
||||||
|
|
||||||
result_list = result.all()
|
result_list = result.all()
|
||||||
return result_list is not None and len(result_list) > 0
|
return result_list is not None and len(result_list) > 0
|
||||||
@@ -30,9 +28,7 @@ class UserService:
|
|||||||
hashed_password = ph.hash(password)
|
hashed_password = ph.hash(password)
|
||||||
|
|
||||||
await self.ap.persistence_mgr.execute_async(
|
await self.ap.persistence_mgr.execute_async(
|
||||||
sqlalchemy.insert(user.User).values(
|
sqlalchemy.insert(user.User).values(user=user_email, password=hashed_password)
|
||||||
user=user_email, password=hashed_password
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get_user_by_email(self, user_email: str) -> user.User | None:
|
async def get_user_by_email(self, user_email: str) -> user.User | None:
|
||||||
@@ -41,9 +37,7 @@ class UserService:
|
|||||||
)
|
)
|
||||||
|
|
||||||
result_list = result.all()
|
result_list = result.all()
|
||||||
return (
|
return result_list[0] if result_list is not None and len(result_list) > 0 else None
|
||||||
result_list[0] if result_list is not None and len(result_list) > 0 else None
|
|
||||||
)
|
|
||||||
|
|
||||||
async def authenticate(self, user_email: str, password: str) -> str | None:
|
async def authenticate(self, user_email: str, password: str) -> str | None:
|
||||||
result = await self.ap.persistence_mgr.execute_async(
|
result = await self.ap.persistence_mgr.execute_async(
|
||||||
|
|||||||
+6
-20
@@ -40,18 +40,14 @@ class CommandManager:
|
|||||||
# 应用命令权限配置
|
# 应用命令权限配置
|
||||||
for cls in operator.preregistered_operators:
|
for cls in operator.preregistered_operators:
|
||||||
if cls.path in self.ap.instance_config.data['command']['privilege']:
|
if cls.path in self.ap.instance_config.data['command']['privilege']:
|
||||||
cls.lowest_privilege = self.ap.instance_config.data['command'][
|
cls.lowest_privilege = self.ap.instance_config.data['command']['privilege'][cls.path]
|
||||||
'privilege'
|
|
||||||
][cls.path]
|
|
||||||
|
|
||||||
# 实例化所有类
|
# 实例化所有类
|
||||||
self.cmd_list = [cls(self.ap) for cls in operator.preregistered_operators]
|
self.cmd_list = [cls(self.ap) for cls in operator.preregistered_operators]
|
||||||
|
|
||||||
# 设置所有类的子节点
|
# 设置所有类的子节点
|
||||||
for cmd in self.cmd_list:
|
for cmd in self.cmd_list:
|
||||||
cmd.children = [
|
cmd.children = [child for child in self.cmd_list if child.parent_class == cmd.__class__]
|
||||||
child for child in self.cmd_list if child.parent_class == cmd.__class__
|
|
||||||
]
|
|
||||||
|
|
||||||
# 初始化所有类
|
# 初始化所有类
|
||||||
for cmd in self.cmd_list:
|
for cmd in self.cmd_list:
|
||||||
@@ -68,10 +64,7 @@ class CommandManager:
|
|||||||
found = False
|
found = False
|
||||||
if len(context.crt_params) > 0: # 查找下一个参数是否对应此节点的某个子节点名
|
if len(context.crt_params) > 0: # 查找下一个参数是否对应此节点的某个子节点名
|
||||||
for oper in operator_list:
|
for oper in operator_list:
|
||||||
if (
|
if (context.crt_params[0] == oper.name or context.crt_params[0] in oper.alias) and (
|
||||||
context.crt_params[0] == oper.name
|
|
||||||
or context.crt_params[0] in oper.alias
|
|
||||||
) and (
|
|
||||||
oper.parent_class is None or oper.parent_class == operator.__class__
|
oper.parent_class is None or oper.parent_class == operator.__class__
|
||||||
):
|
):
|
||||||
found = True
|
found = True
|
||||||
@@ -85,14 +78,10 @@ class CommandManager:
|
|||||||
|
|
||||||
if not found: # 如果下一个参数未在此节点的子节点中找到,则执行此节点或者报错
|
if not found: # 如果下一个参数未在此节点的子节点中找到,则执行此节点或者报错
|
||||||
if operator is None:
|
if operator is None:
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.CommandNotFoundError(context.crt_params[0]))
|
||||||
error=errors.CommandNotFoundError(context.crt_params[0])
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
if operator.lowest_privilege > context.privilege:
|
if operator.lowest_privilege > context.privilege:
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.CommandPrivilegeError(operator.name))
|
||||||
error=errors.CommandPrivilegeError(operator.name)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
async for ret in operator.execute(context):
|
async for ret in operator.execute(context):
|
||||||
yield ret
|
yield ret
|
||||||
@@ -107,10 +96,7 @@ class CommandManager:
|
|||||||
|
|
||||||
privilege = 1
|
privilege = 1
|
||||||
|
|
||||||
if (
|
if f'{query.launcher_type.value}_{query.launcher_id}' in self.ap.instance_config.data['admins']:
|
||||||
f'{query.launcher_type.value}_{query.launcher_id}'
|
|
||||||
in self.ap.instance_config.data['admins']
|
|
||||||
):
|
|
||||||
privilege = 2
|
privilege = 2
|
||||||
|
|
||||||
ctx = entities.ExecuteContext(
|
ctx = entities.ExecuteContext(
|
||||||
|
|||||||
@@ -95,9 +95,7 @@ class CommandOperator(metaclass=abc.ABCMeta):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
async def execute(
|
async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
||||||
self, context: entities.ExecuteContext
|
|
||||||
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
|
||||||
"""实现此方法以执行命令
|
"""实现此方法以执行命令
|
||||||
|
|
||||||
支持多次yield以返回多个结果。
|
支持多次yield以返回多个结果。
|
||||||
|
|||||||
@@ -9,9 +9,7 @@ from .. import operator, entities, errors
|
|||||||
class CmdOperator(operator.CommandOperator):
|
class CmdOperator(operator.CommandOperator):
|
||||||
"""命令列表"""
|
"""命令列表"""
|
||||||
|
|
||||||
async def execute(
|
async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
||||||
self, context: entities.ExecuteContext
|
|
||||||
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
|
||||||
"""执行"""
|
"""执行"""
|
||||||
if len(context.crt_params) == 0:
|
if len(context.crt_params) == 0:
|
||||||
reply_str = '当前所有命令: \n\n'
|
reply_str = '当前所有命令: \n\n'
|
||||||
@@ -30,16 +28,12 @@ class CmdOperator(operator.CommandOperator):
|
|||||||
cmd = None
|
cmd = None
|
||||||
|
|
||||||
for _cmd in self.ap.cmd_mgr.cmd_list:
|
for _cmd in self.ap.cmd_mgr.cmd_list:
|
||||||
if (cmd_name == _cmd.name or cmd_name in _cmd.alias) and (
|
if (cmd_name == _cmd.name or cmd_name in _cmd.alias) and (_cmd.parent_class is None):
|
||||||
_cmd.parent_class is None
|
|
||||||
):
|
|
||||||
cmd = _cmd
|
cmd = _cmd
|
||||||
break
|
break
|
||||||
|
|
||||||
if cmd is None:
|
if cmd is None:
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.CommandNotFoundError(cmd_name))
|
||||||
error=errors.CommandNotFoundError(cmd_name)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
reply_str = f'{cmd.name}: {cmd.help}\n\n'
|
reply_str = f'{cmd.name}: {cmd.help}\n\n'
|
||||||
reply_str += f'使用方法: \n{cmd.usage}'
|
reply_str += f'使用方法: \n{cmd.usage}'
|
||||||
|
|||||||
@@ -5,55 +5,38 @@ import typing
|
|||||||
from .. import operator, entities, errors
|
from .. import operator, entities, errors
|
||||||
|
|
||||||
|
|
||||||
@operator.operator_class(
|
@operator.operator_class(name='del', help='删除当前会话的历史记录', usage='!del <序号>\n!del all')
|
||||||
name='del', help='删除当前会话的历史记录', usage='!del <序号>\n!del all'
|
|
||||||
)
|
|
||||||
class DelOperator(operator.CommandOperator):
|
class DelOperator(operator.CommandOperator):
|
||||||
async def execute(
|
async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
||||||
self, context: entities.ExecuteContext
|
|
||||||
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
|
||||||
if context.session.conversations:
|
if context.session.conversations:
|
||||||
delete_index = 0
|
delete_index = 0
|
||||||
if len(context.crt_params) > 0:
|
if len(context.crt_params) > 0:
|
||||||
try:
|
try:
|
||||||
delete_index = int(context.crt_params[0])
|
delete_index = int(context.crt_params[0])
|
||||||
except Exception:
|
except Exception:
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.CommandOperationError('索引必须是整数'))
|
||||||
error=errors.CommandOperationError('索引必须是整数')
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if delete_index < 0 or delete_index >= len(context.session.conversations):
|
if delete_index < 0 or delete_index >= len(context.session.conversations):
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.CommandOperationError('索引超出范围'))
|
||||||
error=errors.CommandOperationError('索引超出范围')
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# 倒序
|
# 倒序
|
||||||
to_delete_index = len(context.session.conversations) - 1 - delete_index
|
to_delete_index = len(context.session.conversations) - 1 - delete_index
|
||||||
|
|
||||||
if (
|
if context.session.conversations[to_delete_index] == context.session.using_conversation:
|
||||||
context.session.conversations[to_delete_index]
|
|
||||||
== context.session.using_conversation
|
|
||||||
):
|
|
||||||
context.session.using_conversation = None
|
context.session.using_conversation = None
|
||||||
|
|
||||||
del context.session.conversations[to_delete_index]
|
del context.session.conversations[to_delete_index]
|
||||||
|
|
||||||
yield entities.CommandReturn(text=f'已删除对话: {delete_index}')
|
yield entities.CommandReturn(text=f'已删除对话: {delete_index}')
|
||||||
else:
|
else:
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.CommandOperationError('当前没有对话'))
|
||||||
error=errors.CommandOperationError('当前没有对话')
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@operator.operator_class(
|
@operator.operator_class(name='all', help='删除此会话的所有历史记录', parent_class=DelOperator)
|
||||||
name='all', help='删除此会话的所有历史记录', parent_class=DelOperator
|
|
||||||
)
|
|
||||||
class DelAllOperator(operator.CommandOperator):
|
class DelAllOperator(operator.CommandOperator):
|
||||||
async def execute(
|
async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
||||||
self, context: entities.ExecuteContext
|
|
||||||
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
|
||||||
context.session.conversations = []
|
context.session.conversations = []
|
||||||
context.session.using_conversation = None
|
context.session.using_conversation = None
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ from .. import operator, entities
|
|||||||
|
|
||||||
@operator.operator_class(name='func', help='查看所有已注册的内容函数', usage='!func')
|
@operator.operator_class(name='func', help='查看所有已注册的内容函数', usage='!func')
|
||||||
class FuncOperator(operator.CommandOperator):
|
class FuncOperator(operator.CommandOperator):
|
||||||
async def execute(
|
async def execute(self, context: entities.ExecuteContext) -> AsyncGenerator[entities.CommandReturn, None]:
|
||||||
self, context: entities.ExecuteContext
|
|
||||||
) -> AsyncGenerator[entities.CommandReturn, None]:
|
|
||||||
reply_str = '当前已启用的内容函数: \n\n'
|
reply_str = '当前已启用的内容函数: \n\n'
|
||||||
|
|
||||||
index = 1
|
index = 1
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ from .. import operator, entities
|
|||||||
|
|
||||||
@operator.operator_class(name='help', help='显示帮助', usage='!help\n!help <命令名称>')
|
@operator.operator_class(name='help', help='显示帮助', usage='!help\n!help <命令名称>')
|
||||||
class HelpOperator(operator.CommandOperator):
|
class HelpOperator(operator.CommandOperator):
|
||||||
async def execute(
|
async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
||||||
self, context: entities.ExecuteContext
|
|
||||||
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
|
||||||
help = 'LangBot - 大语言模型原生即时通信机器人平台\n链接:https://langbot.app'
|
help = 'LangBot - 大语言模型原生即时通信机器人平台\n链接:https://langbot.app'
|
||||||
|
|
||||||
help += '\n发送命令 !cmd 可查看命令列表'
|
help += '\n发送命令 !cmd 可查看命令列表'
|
||||||
|
|||||||
@@ -8,36 +8,21 @@ from .. import operator, entities, errors
|
|||||||
|
|
||||||
@operator.operator_class(name='last', help='切换到前一个对话', usage='!last')
|
@operator.operator_class(name='last', help='切换到前一个对话', usage='!last')
|
||||||
class LastOperator(operator.CommandOperator):
|
class LastOperator(operator.CommandOperator):
|
||||||
async def execute(
|
async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
||||||
self, context: entities.ExecuteContext
|
|
||||||
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
|
||||||
if context.session.conversations:
|
if context.session.conversations:
|
||||||
# 找到当前会话的上一个会话
|
# 找到当前会话的上一个会话
|
||||||
for index in range(len(context.session.conversations) - 1, -1, -1):
|
for index in range(len(context.session.conversations) - 1, -1, -1):
|
||||||
if (
|
if context.session.conversations[index] == context.session.using_conversation:
|
||||||
context.session.conversations[index]
|
|
||||||
== context.session.using_conversation
|
|
||||||
):
|
|
||||||
if index == 0:
|
if index == 0:
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.CommandOperationError('已经是第一个对话了'))
|
||||||
error=errors.CommandOperationError('已经是第一个对话了')
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
context.session.using_conversation = (
|
context.session.using_conversation = context.session.conversations[index - 1]
|
||||||
context.session.conversations[index - 1]
|
time_str = context.session.using_conversation.create_time.strftime('%Y-%m-%d %H:%M:%S')
|
||||||
)
|
|
||||||
time_str = (
|
|
||||||
context.session.using_conversation.create_time.strftime(
|
|
||||||
'%Y-%m-%d %H:%M:%S'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(
|
||||||
text=f'已切换到上一个对话: {index} {time_str}: {context.session.using_conversation.messages[0].readable_str()}'
|
text=f'已切换到上一个对话: {index} {time_str}: {context.session.using_conversation.messages[0].readable_str()}'
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.CommandOperationError('当前没有对话'))
|
||||||
error=errors.CommandOperationError('当前没有对话')
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -5,22 +5,16 @@ import typing
|
|||||||
from .. import operator, entities, errors
|
from .. import operator, entities, errors
|
||||||
|
|
||||||
|
|
||||||
@operator.operator_class(
|
@operator.operator_class(name='list', help='列出此会话中的所有历史对话', usage='!list\n!list <页码>')
|
||||||
name='list', help='列出此会话中的所有历史对话', usage='!list\n!list <页码>'
|
|
||||||
)
|
|
||||||
class ListOperator(operator.CommandOperator):
|
class ListOperator(operator.CommandOperator):
|
||||||
async def execute(
|
async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
||||||
self, context: entities.ExecuteContext
|
|
||||||
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
|
||||||
page = 0
|
page = 0
|
||||||
|
|
||||||
if len(context.crt_params) > 0:
|
if len(context.crt_params) > 0:
|
||||||
try:
|
try:
|
||||||
page = int(context.crt_params[0] - 1)
|
page = int(context.crt_params[0] - 1)
|
||||||
except Exception:
|
except Exception:
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.CommandOperationError('页码应为整数'))
|
||||||
error=errors.CommandOperationError('页码应为整数')
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
record_per_page = 10
|
record_per_page = 10
|
||||||
@@ -38,7 +32,9 @@ class ListOperator(operator.CommandOperator):
|
|||||||
using_conv_index = index
|
using_conv_index = index
|
||||||
|
|
||||||
if index >= page * record_per_page and index < (page + 1) * record_per_page:
|
if index >= page * record_per_page and index < (page + 1) * record_per_page:
|
||||||
content += f'{index} {time_str}: {conv.messages[0].readable_str() if len(conv.messages) > 0 else "无内容"}\n'
|
content += (
|
||||||
|
f'{index} {time_str}: {conv.messages[0].readable_str() if len(conv.messages) > 0 else "无内容"}\n'
|
||||||
|
)
|
||||||
index += 1
|
index += 1
|
||||||
|
|
||||||
if content == '':
|
if content == '':
|
||||||
|
|||||||
@@ -14,9 +14,7 @@ from .. import operator, entities, errors
|
|||||||
class ModelOperator(operator.CommandOperator):
|
class ModelOperator(operator.CommandOperator):
|
||||||
"""Model命令"""
|
"""Model命令"""
|
||||||
|
|
||||||
async def execute(
|
async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
||||||
self, context: entities.ExecuteContext
|
|
||||||
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
|
||||||
content = '模型列表:\n'
|
content = '模型列表:\n'
|
||||||
|
|
||||||
model_list = self.ap.model_mgr.model_list
|
model_list = self.ap.model_mgr.model_list
|
||||||
@@ -31,15 +29,11 @@ class ModelOperator(operator.CommandOperator):
|
|||||||
yield entities.CommandReturn(text=content.strip())
|
yield entities.CommandReturn(text=content.strip())
|
||||||
|
|
||||||
|
|
||||||
@operator.operator_class(
|
@operator.operator_class(name='show', help='显示模型详情', privilege=2, parent_class=ModelOperator)
|
||||||
name='show', help='显示模型详情', privilege=2, parent_class=ModelOperator
|
|
||||||
)
|
|
||||||
class ModelShowOperator(operator.CommandOperator):
|
class ModelShowOperator(operator.CommandOperator):
|
||||||
"""Model Show命令"""
|
"""Model Show命令"""
|
||||||
|
|
||||||
async def execute(
|
async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
||||||
self, context: entities.ExecuteContext
|
|
||||||
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
|
||||||
model_name = context.crt_params[0]
|
model_name = context.crt_params[0]
|
||||||
|
|
||||||
model = None
|
model = None
|
||||||
@@ -49,9 +43,7 @@ class ModelShowOperator(operator.CommandOperator):
|
|||||||
break
|
break
|
||||||
|
|
||||||
if model is None:
|
if model is None:
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.CommandError(f'未找到模型 {model_name}'))
|
||||||
error=errors.CommandError(f'未找到模型 {model_name}')
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
content = '模型详情\n'
|
content = '模型详情\n'
|
||||||
content += f'名称: {model.name}\n'
|
content += f'名称: {model.name}\n'
|
||||||
@@ -65,15 +57,11 @@ class ModelShowOperator(operator.CommandOperator):
|
|||||||
yield entities.CommandReturn(text=content.strip())
|
yield entities.CommandReturn(text=content.strip())
|
||||||
|
|
||||||
|
|
||||||
@operator.operator_class(
|
@operator.operator_class(name='set', help='设置默认使用模型', privilege=2, parent_class=ModelOperator)
|
||||||
name='set', help='设置默认使用模型', privilege=2, parent_class=ModelOperator
|
|
||||||
)
|
|
||||||
class ModelSetOperator(operator.CommandOperator):
|
class ModelSetOperator(operator.CommandOperator):
|
||||||
"""Model Set命令"""
|
"""Model Set命令"""
|
||||||
|
|
||||||
async def execute(
|
async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
||||||
self, context: entities.ExecuteContext
|
|
||||||
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
|
||||||
model_name = context.crt_params[0]
|
model_name = context.crt_params[0]
|
||||||
|
|
||||||
model = None
|
model = None
|
||||||
@@ -83,12 +71,8 @@ class ModelSetOperator(operator.CommandOperator):
|
|||||||
break
|
break
|
||||||
|
|
||||||
if model is None:
|
if model is None:
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.CommandError(f'未找到模型 {model_name}'))
|
||||||
error=errors.CommandError(f'未找到模型 {model_name}')
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
self.ap.provider_cfg.data['model'] = model_name
|
self.ap.provider_cfg.data['model'] = model_name
|
||||||
await self.ap.provider_cfg.dump_config()
|
await self.ap.provider_cfg.dump_config()
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(text=f'已设置当前使用模型为 {model_name},重置会话以生效')
|
||||||
text=f'已设置当前使用模型为 {model_name},重置会话以生效'
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -7,36 +7,21 @@ from .. import operator, entities, errors
|
|||||||
|
|
||||||
@operator.operator_class(name='next', help='切换到后一个对话', usage='!next')
|
@operator.operator_class(name='next', help='切换到后一个对话', usage='!next')
|
||||||
class NextOperator(operator.CommandOperator):
|
class NextOperator(operator.CommandOperator):
|
||||||
async def execute(
|
async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
||||||
self, context: entities.ExecuteContext
|
|
||||||
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
|
||||||
if context.session.conversations:
|
if context.session.conversations:
|
||||||
# 找到当前会话的下一个会话
|
# 找到当前会话的下一个会话
|
||||||
for index in range(len(context.session.conversations)):
|
for index in range(len(context.session.conversations)):
|
||||||
if (
|
if context.session.conversations[index] == context.session.using_conversation:
|
||||||
context.session.conversations[index]
|
|
||||||
== context.session.using_conversation
|
|
||||||
):
|
|
||||||
if index == len(context.session.conversations) - 1:
|
if index == len(context.session.conversations) - 1:
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.CommandOperationError('已经是最后一个对话了'))
|
||||||
error=errors.CommandOperationError('已经是最后一个对话了')
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
context.session.using_conversation = (
|
context.session.using_conversation = context.session.conversations[index + 1]
|
||||||
context.session.conversations[index + 1]
|
time_str = context.session.using_conversation.create_time.strftime('%Y-%m-%d %H:%M:%S')
|
||||||
)
|
|
||||||
time_str = (
|
|
||||||
context.session.using_conversation.create_time.strftime(
|
|
||||||
'%Y-%m-%d %H:%M:%S'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(
|
||||||
text=f'已切换到后一个对话: {index} {time_str}: {context.session.using_conversation.messages[0].content}'
|
text=f'已切换到后一个对话: {index} {time_str}: {context.session.using_conversation.messages[0].content}'
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.CommandOperationError('当前没有对话'))
|
||||||
error=errors.CommandOperationError('当前没有对话')
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -13,9 +13,7 @@ from .. import operator, entities, errors
|
|||||||
usage='!ollama\n!ollama show <模型名>\n!ollama pull <模型名>\n!ollama del <模型名>',
|
usage='!ollama\n!ollama show <模型名>\n!ollama pull <模型名>\n!ollama del <模型名>',
|
||||||
)
|
)
|
||||||
class OllamaOperator(operator.CommandOperator):
|
class OllamaOperator(operator.CommandOperator):
|
||||||
async def execute(
|
async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
||||||
self, context: entities.ExecuteContext
|
|
||||||
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
|
||||||
try:
|
try:
|
||||||
content: str = '模型列表:\n'
|
content: str = '模型列表:\n'
|
||||||
model_list: list = ollama.list().get('models', [])
|
model_list: list = ollama.list().get('models', [])
|
||||||
@@ -25,9 +23,7 @@ class OllamaOperator(operator.CommandOperator):
|
|||||||
content += f'大小: {bytes_to_mb(model["size"])}MB\n\n'
|
content += f'大小: {bytes_to_mb(model["size"])}MB\n\n'
|
||||||
yield entities.CommandReturn(text=f'{content.strip()}')
|
yield entities.CommandReturn(text=f'{content.strip()}')
|
||||||
except ollama.ResponseError:
|
except ollama.ResponseError:
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.CommandError('无法获取模型列表,请确认 Ollama 服务正常'))
|
||||||
error=errors.CommandError('无法获取模型列表,请确认 Ollama 服务正常')
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def bytes_to_mb(num_bytes):
|
def bytes_to_mb(num_bytes):
|
||||||
@@ -35,13 +31,9 @@ def bytes_to_mb(num_bytes):
|
|||||||
return format(mb, '.2f')
|
return format(mb, '.2f')
|
||||||
|
|
||||||
|
|
||||||
@operator.operator_class(
|
@operator.operator_class(name='show', help='ollama模型详情', privilege=2, parent_class=OllamaOperator)
|
||||||
name='show', help='ollama模型详情', privilege=2, parent_class=OllamaOperator
|
|
||||||
)
|
|
||||||
class OllamaShowOperator(operator.CommandOperator):
|
class OllamaShowOperator(operator.CommandOperator):
|
||||||
async def execute(
|
async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
||||||
self, context: entities.ExecuteContext
|
|
||||||
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
|
||||||
content: str = '模型详情:\n'
|
content: str = '模型详情:\n'
|
||||||
try:
|
try:
|
||||||
show: dict = ollama.show(model=context.crt_params[0])
|
show: dict = ollama.show(model=context.crt_params[0])
|
||||||
@@ -60,27 +52,19 @@ class OllamaShowOperator(operator.CommandOperator):
|
|||||||
content += json.dumps(show, indent=4)
|
content += json.dumps(show, indent=4)
|
||||||
yield entities.CommandReturn(text=content.strip())
|
yield entities.CommandReturn(text=content.strip())
|
||||||
except ollama.ResponseError:
|
except ollama.ResponseError:
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.CommandError('无法获取模型详情,请确认 Ollama 服务正常'))
|
||||||
error=errors.CommandError('无法获取模型详情,请确认 Ollama 服务正常')
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@operator.operator_class(
|
@operator.operator_class(name='pull', help='ollama模型拉取', privilege=2, parent_class=OllamaOperator)
|
||||||
name='pull', help='ollama模型拉取', privilege=2, parent_class=OllamaOperator
|
|
||||||
)
|
|
||||||
class OllamaPullOperator(operator.CommandOperator):
|
class OllamaPullOperator(operator.CommandOperator):
|
||||||
async def execute(
|
async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
||||||
self, context: entities.ExecuteContext
|
|
||||||
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
|
||||||
try:
|
try:
|
||||||
model_list: list = ollama.list().get('models', [])
|
model_list: list = ollama.list().get('models', [])
|
||||||
if context.crt_params[0] in [model['name'] for model in model_list]:
|
if context.crt_params[0] in [model['name'] for model in model_list]:
|
||||||
yield entities.CommandReturn(text='模型已存在')
|
yield entities.CommandReturn(text='模型已存在')
|
||||||
return
|
return
|
||||||
except ollama.ResponseError:
|
except ollama.ResponseError:
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.CommandError('无法获取模型列表,请确认 Ollama 服务正常'))
|
||||||
error=errors.CommandError('无法获取模型列表,请确认 Ollama 服务正常')
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
on_progress: bool = False
|
on_progress: bool = False
|
||||||
@@ -108,13 +92,9 @@ class OllamaPullOperator(operator.CommandOperator):
|
|||||||
yield entities.CommandReturn(text=f'拉取失败: {e.error}')
|
yield entities.CommandReturn(text=f'拉取失败: {e.error}')
|
||||||
|
|
||||||
|
|
||||||
@operator.operator_class(
|
@operator.operator_class(name='del', help='ollama模型删除', privilege=2, parent_class=OllamaOperator)
|
||||||
name='del', help='ollama模型删除', privilege=2, parent_class=OllamaOperator
|
|
||||||
)
|
|
||||||
class OllamaDelOperator(operator.CommandOperator):
|
class OllamaDelOperator(operator.CommandOperator):
|
||||||
async def execute(
|
async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
||||||
self, context: entities.ExecuteContext
|
|
||||||
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
|
||||||
try:
|
try:
|
||||||
ret: str = ollama.delete(model=context.crt_params[0])['status']
|
ret: str = ollama.delete(model=context.crt_params[0])['status']
|
||||||
except ollama.ResponseError as e:
|
except ollama.ResponseError as e:
|
||||||
|
|||||||
+34
-102
@@ -11,9 +11,7 @@ from .. import operator, entities, errors
|
|||||||
usage='!plugin\n!plugin get <插件仓库地址>\n!plugin update\n!plugin del <插件名>\n!plugin on <插件名>\n!plugin off <插件名>',
|
usage='!plugin\n!plugin get <插件仓库地址>\n!plugin update\n!plugin del <插件名>\n!plugin on <插件名>\n!plugin off <插件名>',
|
||||||
)
|
)
|
||||||
class PluginOperator(operator.CommandOperator):
|
class PluginOperator(operator.CommandOperator):
|
||||||
async def execute(
|
async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
||||||
self, context: entities.ExecuteContext
|
|
||||||
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
|
||||||
plugin_list = self.ap.plugin_mgr.plugins()
|
plugin_list = self.ap.plugin_mgr.plugins()
|
||||||
reply_str = '所有插件({}):\n'.format(len(plugin_list))
|
reply_str = '所有插件({}):\n'.format(len(plugin_list))
|
||||||
idx = 0
|
idx = 0
|
||||||
@@ -32,17 +30,11 @@ class PluginOperator(operator.CommandOperator):
|
|||||||
yield entities.CommandReturn(text=reply_str)
|
yield entities.CommandReturn(text=reply_str)
|
||||||
|
|
||||||
|
|
||||||
@operator.operator_class(
|
@operator.operator_class(name='get', help='安装插件', privilege=2, parent_class=PluginOperator)
|
||||||
name='get', help='安装插件', privilege=2, parent_class=PluginOperator
|
|
||||||
)
|
|
||||||
class PluginGetOperator(operator.CommandOperator):
|
class PluginGetOperator(operator.CommandOperator):
|
||||||
async def execute(
|
async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
||||||
self, context: entities.ExecuteContext
|
|
||||||
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
|
||||||
if len(context.crt_params) == 0:
|
if len(context.crt_params) == 0:
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.ParamNotEnoughError('请提供插件仓库地址'))
|
||||||
error=errors.ParamNotEnoughError('请提供插件仓库地址')
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
repo = context.crt_params[0]
|
repo = context.crt_params[0]
|
||||||
|
|
||||||
@@ -53,22 +45,14 @@ class PluginGetOperator(operator.CommandOperator):
|
|||||||
yield entities.CommandReturn(text='插件安装成功,请重启程序以加载插件')
|
yield entities.CommandReturn(text='插件安装成功,请重启程序以加载插件')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.CommandError('插件安装失败: ' + str(e)))
|
||||||
error=errors.CommandError('插件安装失败: ' + str(e))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@operator.operator_class(
|
@operator.operator_class(name='update', help='更新插件', privilege=2, parent_class=PluginOperator)
|
||||||
name='update', help='更新插件', privilege=2, parent_class=PluginOperator
|
|
||||||
)
|
|
||||||
class PluginUpdateOperator(operator.CommandOperator):
|
class PluginUpdateOperator(operator.CommandOperator):
|
||||||
async def execute(
|
async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
||||||
self, context: entities.ExecuteContext
|
|
||||||
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
|
||||||
if len(context.crt_params) == 0:
|
if len(context.crt_params) == 0:
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.ParamNotEnoughError('请提供插件名称'))
|
||||||
error=errors.ParamNotEnoughError('请提供插件名称')
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
plugin_name = context.crt_params[0]
|
plugin_name = context.crt_params[0]
|
||||||
|
|
||||||
@@ -78,27 +62,17 @@ class PluginUpdateOperator(operator.CommandOperator):
|
|||||||
if plugin_container is not None:
|
if plugin_container is not None:
|
||||||
yield entities.CommandReturn(text='正在更新插件...')
|
yield entities.CommandReturn(text='正在更新插件...')
|
||||||
await self.ap.plugin_mgr.update_plugin(plugin_name)
|
await self.ap.plugin_mgr.update_plugin(plugin_name)
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(text='插件更新成功,请重启程序以加载插件')
|
||||||
text='插件更新成功,请重启程序以加载插件'
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.CommandError('插件更新失败: 未找到插件'))
|
||||||
error=errors.CommandError('插件更新失败: 未找到插件')
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.CommandError('插件更新失败: ' + str(e)))
|
||||||
error=errors.CommandError('插件更新失败: ' + str(e))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@operator.operator_class(
|
@operator.operator_class(name='all', help='更新所有插件', privilege=2, parent_class=PluginUpdateOperator)
|
||||||
name='all', help='更新所有插件', privilege=2, parent_class=PluginUpdateOperator
|
|
||||||
)
|
|
||||||
class PluginUpdateAllOperator(operator.CommandOperator):
|
class PluginUpdateAllOperator(operator.CommandOperator):
|
||||||
async def execute(
|
async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
||||||
self, context: entities.ExecuteContext
|
|
||||||
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
|
||||||
try:
|
try:
|
||||||
plugins = [p.plugin_name for p in self.ap.plugin_mgr.plugins()]
|
plugins = [p.plugin_name for p in self.ap.plugin_mgr.plugins()]
|
||||||
|
|
||||||
@@ -111,32 +85,20 @@ class PluginUpdateAllOperator(operator.CommandOperator):
|
|||||||
updated.append(plugin_name)
|
updated.append(plugin_name)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.CommandError('插件更新失败: ' + str(e)))
|
||||||
error=errors.CommandError('插件更新失败: ' + str(e))
|
yield entities.CommandReturn(text='已更新插件: {}'.format(', '.join(updated)))
|
||||||
)
|
|
||||||
yield entities.CommandReturn(
|
|
||||||
text='已更新插件: {}'.format(', '.join(updated))
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
yield entities.CommandReturn(text='没有可更新的插件')
|
yield entities.CommandReturn(text='没有可更新的插件')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.CommandError('插件更新失败: ' + str(e)))
|
||||||
error=errors.CommandError('插件更新失败: ' + str(e))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@operator.operator_class(
|
@operator.operator_class(name='del', help='删除插件', privilege=2, parent_class=PluginOperator)
|
||||||
name='del', help='删除插件', privilege=2, parent_class=PluginOperator
|
|
||||||
)
|
|
||||||
class PluginDelOperator(operator.CommandOperator):
|
class PluginDelOperator(operator.CommandOperator):
|
||||||
async def execute(
|
async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
||||||
self, context: entities.ExecuteContext
|
|
||||||
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
|
||||||
if len(context.crt_params) == 0:
|
if len(context.crt_params) == 0:
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.ParamNotEnoughError('请提供插件名称'))
|
||||||
error=errors.ParamNotEnoughError('请提供插件名称')
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
plugin_name = context.crt_params[0]
|
plugin_name = context.crt_params[0]
|
||||||
|
|
||||||
@@ -146,79 +108,49 @@ class PluginDelOperator(operator.CommandOperator):
|
|||||||
if plugin_container is not None:
|
if plugin_container is not None:
|
||||||
yield entities.CommandReturn(text='正在删除插件...')
|
yield entities.CommandReturn(text='正在删除插件...')
|
||||||
await self.ap.plugin_mgr.uninstall_plugin(plugin_name)
|
await self.ap.plugin_mgr.uninstall_plugin(plugin_name)
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(text='插件删除成功,请重启程序以加载插件')
|
||||||
text='插件删除成功,请重启程序以加载插件'
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.CommandError('插件删除失败: 未找到插件'))
|
||||||
error=errors.CommandError('插件删除失败: 未找到插件')
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.CommandError('插件删除失败: ' + str(e)))
|
||||||
error=errors.CommandError('插件删除失败: ' + str(e))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@operator.operator_class(
|
@operator.operator_class(name='on', help='启用插件', privilege=2, parent_class=PluginOperator)
|
||||||
name='on', help='启用插件', privilege=2, parent_class=PluginOperator
|
|
||||||
)
|
|
||||||
class PluginEnableOperator(operator.CommandOperator):
|
class PluginEnableOperator(operator.CommandOperator):
|
||||||
async def execute(
|
async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
||||||
self, context: entities.ExecuteContext
|
|
||||||
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
|
||||||
if len(context.crt_params) == 0:
|
if len(context.crt_params) == 0:
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.ParamNotEnoughError('请提供插件名称'))
|
||||||
error=errors.ParamNotEnoughError('请提供插件名称')
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
plugin_name = context.crt_params[0]
|
plugin_name = context.crt_params[0]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if await self.ap.plugin_mgr.update_plugin_switch(plugin_name, True):
|
if await self.ap.plugin_mgr.update_plugin_switch(plugin_name, True):
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(text='已启用插件: {}'.format(plugin_name))
|
||||||
text='已启用插件: {}'.format(plugin_name)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(
|
||||||
error=errors.CommandError(
|
error=errors.CommandError('插件状态修改失败: 未找到插件 {}'.format(plugin_name))
|
||||||
'插件状态修改失败: 未找到插件 {}'.format(plugin_name)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.CommandError('插件状态修改失败: ' + str(e)))
|
||||||
error=errors.CommandError('插件状态修改失败: ' + str(e))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@operator.operator_class(
|
@operator.operator_class(name='off', help='禁用插件', privilege=2, parent_class=PluginOperator)
|
||||||
name='off', help='禁用插件', privilege=2, parent_class=PluginOperator
|
|
||||||
)
|
|
||||||
class PluginDisableOperator(operator.CommandOperator):
|
class PluginDisableOperator(operator.CommandOperator):
|
||||||
async def execute(
|
async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
||||||
self, context: entities.ExecuteContext
|
|
||||||
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
|
||||||
if len(context.crt_params) == 0:
|
if len(context.crt_params) == 0:
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.ParamNotEnoughError('请提供插件名称'))
|
||||||
error=errors.ParamNotEnoughError('请提供插件名称')
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
plugin_name = context.crt_params[0]
|
plugin_name = context.crt_params[0]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if await self.ap.plugin_mgr.update_plugin_switch(plugin_name, False):
|
if await self.ap.plugin_mgr.update_plugin_switch(plugin_name, False):
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(text='已禁用插件: {}'.format(plugin_name))
|
||||||
text='已禁用插件: {}'.format(plugin_name)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(
|
||||||
error=errors.CommandError(
|
error=errors.CommandError('插件状态修改失败: 未找到插件 {}'.format(plugin_name))
|
||||||
'插件状态修改失败: 未找到插件 {}'.format(plugin_name)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.CommandError('插件状态修改失败: ' + str(e)))
|
||||||
error=errors.CommandError('插件状态修改失败: ' + str(e))
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -7,14 +7,10 @@ from .. import operator, entities, errors
|
|||||||
|
|
||||||
@operator.operator_class(name='prompt', help='查看当前对话的前文', usage='!prompt')
|
@operator.operator_class(name='prompt', help='查看当前对话的前文', usage='!prompt')
|
||||||
class PromptOperator(operator.CommandOperator):
|
class PromptOperator(operator.CommandOperator):
|
||||||
async def execute(
|
async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
||||||
self, context: entities.ExecuteContext
|
|
||||||
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
|
||||||
"""执行"""
|
"""执行"""
|
||||||
if context.session.using_conversation is None:
|
if context.session.using_conversation is None:
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.CommandOperationError('当前没有对话'))
|
||||||
error=errors.CommandOperationError('当前没有对话')
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
reply_str = '当前对话所有内容:\n\n'
|
reply_str = '当前对话所有内容:\n\n'
|
||||||
|
|
||||||
|
|||||||
@@ -5,13 +5,9 @@ import typing
|
|||||||
from .. import operator, entities, errors
|
from .. import operator, entities, errors
|
||||||
|
|
||||||
|
|
||||||
@operator.operator_class(
|
@operator.operator_class(name='resend', help='重发当前会话的最后一条消息', usage='!resend')
|
||||||
name='resend', help='重发当前会话的最后一条消息', usage='!resend'
|
|
||||||
)
|
|
||||||
class ResendOperator(operator.CommandOperator):
|
class ResendOperator(operator.CommandOperator):
|
||||||
async def execute(
|
async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
||||||
self, context: entities.ExecuteContext
|
|
||||||
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
|
||||||
# 回滚到最后一条用户message前
|
# 回滚到最后一条用户message前
|
||||||
if context.session.using_conversation is None:
|
if context.session.using_conversation is None:
|
||||||
yield entities.CommandReturn(error=errors.CommandError('当前没有对话'))
|
yield entities.CommandReturn(error=errors.CommandError('当前没有对话'))
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ from .. import operator, entities
|
|||||||
|
|
||||||
@operator.operator_class(name='reset', help='重置当前会话', usage='!reset')
|
@operator.operator_class(name='reset', help='重置当前会话', usage='!reset')
|
||||||
class ResetOperator(operator.CommandOperator):
|
class ResetOperator(operator.CommandOperator):
|
||||||
async def execute(
|
async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
||||||
self, context: entities.ExecuteContext
|
|
||||||
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
|
||||||
"""执行"""
|
"""执行"""
|
||||||
context.session.using_conversation = None
|
context.session.using_conversation = None
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ from .. import operator, entities, errors
|
|||||||
|
|
||||||
@operator.operator_class(name='update', help='更新程序', usage='!update', privilege=2)
|
@operator.operator_class(name='update', help='更新程序', usage='!update', privilege=2)
|
||||||
class UpdateCommand(operator.CommandOperator):
|
class UpdateCommand(operator.CommandOperator):
|
||||||
async def execute(
|
async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
||||||
self, context: entities.ExecuteContext
|
|
||||||
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
|
||||||
try:
|
try:
|
||||||
yield entities.CommandReturn(text='正在进行更新...')
|
yield entities.CommandReturn(text='正在进行更新...')
|
||||||
if await self.ap.ver_mgr.update_all():
|
if await self.ap.ver_mgr.update_all():
|
||||||
@@ -19,6 +17,4 @@ class UpdateCommand(operator.CommandOperator):
|
|||||||
yield entities.CommandReturn(text='当前已是最新版本')
|
yield entities.CommandReturn(text='当前已是最新版本')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
yield entities.CommandReturn(
|
yield entities.CommandReturn(error=errors.CommandError('更新失败: ' + str(e)))
|
||||||
error=errors.CommandError('更新失败: ' + str(e))
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ from .. import operator, entities
|
|||||||
|
|
||||||
@operator.operator_class(name='version', help='显示版本信息', usage='!version')
|
@operator.operator_class(name='version', help='显示版本信息', usage='!version')
|
||||||
class VersionCommand(operator.CommandOperator):
|
class VersionCommand(operator.CommandOperator):
|
||||||
async def execute(
|
async def execute(self, context: entities.ExecuteContext) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
||||||
self, context: entities.ExecuteContext
|
|
||||||
) -> typing.AsyncGenerator[entities.CommandReturn, None]:
|
|
||||||
reply_str = f'当前版本: \n{self.ap.ver_mgr.get_current_version()}'
|
reply_str = f'当前版本: \n{self.ap.ver_mgr.get_current_version()}'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -41,9 +41,7 @@ class ConfigManager:
|
|||||||
self.file.save_sync(self.data)
|
self.file.save_sync(self.data)
|
||||||
|
|
||||||
|
|
||||||
async def load_python_module_config(
|
async def load_python_module_config(config_name: str, template_name: str, completion: bool = True) -> ConfigManager:
|
||||||
config_name: str, template_name: str, completion: bool = True
|
|
||||||
) -> ConfigManager:
|
|
||||||
"""加载Python模块配置文件
|
"""加载Python模块配置文件
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
+1
-3
@@ -160,9 +160,7 @@ class Application:
|
|||||||
"""打印访问 webui 的提示"""
|
"""打印访问 webui 的提示"""
|
||||||
|
|
||||||
if not os.path.exists(os.path.join('.', 'web/out')):
|
if not os.path.exists(os.path.join('.', 'web/out')):
|
||||||
self.logger.warning(
|
self.logger.warning('WebUI 文件缺失,请根据文档获取:https://docs.langbot.app/webui/intro.html')
|
||||||
'WebUI 文件缺失,请根据文档获取:https://docs.langbot.app/webui/intro.html'
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
host_ip = '127.0.0.1'
|
host_ip = '127.0.0.1'
|
||||||
|
|||||||
@@ -26,9 +26,7 @@ async def init_logging(extra_handlers: list[logging.Handler] = None) -> logging.
|
|||||||
if constants.debug_mode:
|
if constants.debug_mode:
|
||||||
level = logging.DEBUG
|
level = logging.DEBUG
|
||||||
|
|
||||||
log_file_name = 'data/logs/langbot-%s.log' % time.strftime(
|
log_file_name = 'data/logs/langbot-%s.log' % time.strftime('%Y-%m-%d', time.localtime())
|
||||||
'%Y-%m-%d', time.localtime()
|
|
||||||
)
|
|
||||||
|
|
||||||
qcg_logger = logging.getLogger('langbot')
|
qcg_logger = logging.getLogger('langbot')
|
||||||
|
|
||||||
@@ -43,9 +41,7 @@ async def init_logging(extra_handlers: list[logging.Handler] = None) -> logging.
|
|||||||
stream_handler = logging.StreamHandler(sys.stdout)
|
stream_handler = logging.StreamHandler(sys.stdout)
|
||||||
# stream_handler.setLevel(level)
|
# stream_handler.setLevel(level)
|
||||||
# stream_handler.setFormatter(color_formatter)
|
# stream_handler.setFormatter(color_formatter)
|
||||||
stream_handler.stream = open(
|
stream_handler.stream = open(sys.stdout.fileno(), mode='w', encoding='utf-8', buffering=1)
|
||||||
sys.stdout.fileno(), mode='w', encoding='utf-8', buffering=1
|
|
||||||
)
|
|
||||||
|
|
||||||
log_handlers: list[logging.Handler] = [
|
log_handlers: list[logging.Handler] = [
|
||||||
stream_handler,
|
stream_handler,
|
||||||
|
|||||||
+6
-17
@@ -87,8 +87,7 @@ class Query(pydantic.BaseModel):
|
|||||||
"""使用的函数,由前置处理器阶段设置"""
|
"""使用的函数,由前置处理器阶段设置"""
|
||||||
|
|
||||||
resp_messages: (
|
resp_messages: (
|
||||||
typing.Optional[list[llm_entities.Message]]
|
typing.Optional[list[llm_entities.Message]] | typing.Optional[list[platform_message.MessageChain]]
|
||||||
| typing.Optional[list[platform_message.MessageChain]]
|
|
||||||
) = []
|
) = []
|
||||||
"""由Process阶段生成的回复消息对象列表"""
|
"""由Process阶段生成的回复消息对象列表"""
|
||||||
|
|
||||||
@@ -130,13 +129,9 @@ class Conversation(pydantic.BaseModel):
|
|||||||
|
|
||||||
messages: list[llm_entities.Message]
|
messages: list[llm_entities.Message]
|
||||||
|
|
||||||
create_time: typing.Optional[datetime.datetime] = pydantic.Field(
|
create_time: typing.Optional[datetime.datetime] = pydantic.Field(default_factory=datetime.datetime.now)
|
||||||
default_factory=datetime.datetime.now
|
|
||||||
)
|
|
||||||
|
|
||||||
update_time: typing.Optional[datetime.datetime] = pydantic.Field(
|
update_time: typing.Optional[datetime.datetime] = pydantic.Field(default_factory=datetime.datetime.now)
|
||||||
default_factory=datetime.datetime.now
|
|
||||||
)
|
|
||||||
|
|
||||||
use_llm_model: requester.RuntimeLLMModel
|
use_llm_model: requester.RuntimeLLMModel
|
||||||
|
|
||||||
@@ -162,17 +157,11 @@ class Session(pydantic.BaseModel):
|
|||||||
|
|
||||||
using_conversation: typing.Optional[Conversation] = None
|
using_conversation: typing.Optional[Conversation] = None
|
||||||
|
|
||||||
conversations: typing.Optional[list[Conversation]] = pydantic.Field(
|
conversations: typing.Optional[list[Conversation]] = pydantic.Field(default_factory=list)
|
||||||
default_factory=list
|
|
||||||
)
|
|
||||||
|
|
||||||
create_time: typing.Optional[datetime.datetime] = pydantic.Field(
|
create_time: typing.Optional[datetime.datetime] = pydantic.Field(default_factory=datetime.datetime.now)
|
||||||
default_factory=datetime.datetime.now
|
|
||||||
)
|
|
||||||
|
|
||||||
update_time: typing.Optional[datetime.datetime] = pydantic.Field(
|
update_time: typing.Optional[datetime.datetime] = pydantic.Field(default_factory=datetime.datetime.now)
|
||||||
default_factory=datetime.datetime.now
|
|
||||||
)
|
|
||||||
|
|
||||||
semaphore: typing.Optional[asyncio.Semaphore] = None
|
semaphore: typing.Optional[asyncio.Semaphore] = None
|
||||||
"""当前会话的信号量,用于限制并发"""
|
"""当前会话的信号量,用于限制并发"""
|
||||||
|
|||||||
@@ -11,16 +11,14 @@ class SensitiveWordMigration(migration.Migration):
|
|||||||
|
|
||||||
async def need_migrate(self) -> bool:
|
async def need_migrate(self) -> bool:
|
||||||
"""判断当前环境是否需要运行此迁移"""
|
"""判断当前环境是否需要运行此迁移"""
|
||||||
return os.path.exists(
|
return os.path.exists('data/config/sensitive-words.json') and not os.path.exists(
|
||||||
'data/config/sensitive-words.json'
|
'data/metadata/sensitive-words.json'
|
||||||
) and not os.path.exists('data/metadata/sensitive-words.json')
|
)
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
"""执行迁移"""
|
"""执行迁移"""
|
||||||
# 移动文件
|
# 移动文件
|
||||||
os.rename(
|
os.rename('data/config/sensitive-words.json', 'data/metadata/sensitive-words.json')
|
||||||
'data/config/sensitive-words.json', 'data/metadata/sensitive-words.json'
|
|
||||||
)
|
|
||||||
|
|
||||||
# 重新加载配置
|
# 重新加载配置
|
||||||
await self.ap.sensitive_meta.load_config()
|
await self.ap.sensitive_meta.load_config()
|
||||||
|
|||||||
@@ -23,9 +23,7 @@ class OpenAIConfigMigration(migration.Migration):
|
|||||||
|
|
||||||
self.ap.provider_cfg.data['keys']['openai'] = old_openai_config['api-keys']
|
self.ap.provider_cfg.data['keys']['openai'] = old_openai_config['api-keys']
|
||||||
|
|
||||||
self.ap.provider_cfg.data['model'] = old_openai_config[
|
self.ap.provider_cfg.data['model'] = old_openai_config['chat-completions-params']['model']
|
||||||
'chat-completions-params'
|
|
||||||
]['model']
|
|
||||||
|
|
||||||
del old_openai_config['chat-completions-params']['model']
|
del old_openai_config['chat-completions-params']['model']
|
||||||
|
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ class QCGCenterURLConfigMigration(migration.Migration):
|
|||||||
"""执行迁移"""
|
"""执行迁移"""
|
||||||
|
|
||||||
if 'qcg-center-url' not in self.ap.system_cfg.data:
|
if 'qcg-center-url' not in self.ap.system_cfg.data:
|
||||||
self.ap.system_cfg.data['qcg-center-url'] = (
|
self.ap.system_cfg.data['qcg-center-url'] = 'https://api.qchatgpt.rockchin.top/api/v2'
|
||||||
'https://api.qchatgpt.rockchin.top/api/v2'
|
|
||||||
)
|
|
||||||
|
|
||||||
await self.ap.system_cfg.dump_config()
|
await self.ap.system_cfg.dump_config()
|
||||||
|
|||||||
@@ -9,9 +9,7 @@ class AdFixwinConfigMigration(migration.Migration):
|
|||||||
|
|
||||||
async def need_migrate(self) -> bool:
|
async def need_migrate(self) -> bool:
|
||||||
"""判断当前环境是否需要运行此迁移"""
|
"""判断当前环境是否需要运行此迁移"""
|
||||||
return isinstance(
|
return isinstance(self.ap.pipeline_cfg.data['rate-limit']['fixwin']['default'], int)
|
||||||
self.ap.pipeline_cfg.data['rate-limit']['fixwin']['default'], int
|
|
||||||
)
|
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
"""执行迁移"""
|
"""执行迁移"""
|
||||||
@@ -19,9 +17,7 @@ class AdFixwinConfigMigration(migration.Migration):
|
|||||||
for session_name in self.ap.pipeline_cfg.data['rate-limit']['fixwin']:
|
for session_name in self.ap.pipeline_cfg.data['rate-limit']['fixwin']:
|
||||||
temp_dict = {
|
temp_dict = {
|
||||||
'window-size': 60,
|
'window-size': 60,
|
||||||
'limit': self.ap.pipeline_cfg.data['rate-limit']['fixwin'][
|
'limit': self.ap.pipeline_cfg.data['rate-limit']['fixwin'][session_name],
|
||||||
session_name
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.ap.pipeline_cfg.data['rate-limit']['fixwin'][session_name] = temp_dict
|
self.ap.pipeline_cfg.data['rate-limit']['fixwin'][session_name] = temp_dict
|
||||||
|
|||||||
@@ -9,10 +9,7 @@ class HttpApiConfigMigration(migration.Migration):
|
|||||||
|
|
||||||
async def need_migrate(self) -> bool:
|
async def need_migrate(self) -> bool:
|
||||||
"""判断当前环境是否需要运行此迁移"""
|
"""判断当前环境是否需要运行此迁移"""
|
||||||
return (
|
return 'http-api' not in self.ap.system_cfg.data or 'persistence' not in self.ap.system_cfg.data
|
||||||
'http-api' not in self.ap.system_cfg.data
|
|
||||||
or 'persistence' not in self.ap.system_cfg.data
|
|
||||||
)
|
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
"""执行迁移"""
|
"""执行迁移"""
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ class DifyAPITimeoutParamsMigration(migration.Migration):
|
|||||||
"""判断当前环境是否需要运行此迁移"""
|
"""判断当前环境是否需要运行此迁移"""
|
||||||
return (
|
return (
|
||||||
'timeout' not in self.ap.provider_cfg.data['dify-service-api']['chat']
|
'timeout' not in self.ap.provider_cfg.data['dify-service-api']['chat']
|
||||||
or 'timeout'
|
or 'timeout' not in self.ap.provider_cfg.data['dify-service-api']['workflow']
|
||||||
not in self.ap.provider_cfg.data['dify-service-api']['workflow']
|
|
||||||
or 'agent' not in self.ap.provider_cfg.data['dify-service-api']
|
or 'agent' not in self.ap.provider_cfg.data['dify-service-api']
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,7 @@ class SiliconFlowConfigMigration(migration.Migration):
|
|||||||
async def need_migrate(self) -> bool:
|
async def need_migrate(self) -> bool:
|
||||||
"""判断当前环境是否需要运行此迁移"""
|
"""判断当前环境是否需要运行此迁移"""
|
||||||
|
|
||||||
return (
|
return 'siliconflow-chat-completions' not in self.ap.provider_cfg.data['requester']
|
||||||
'siliconflow-chat-completions' not in self.ap.provider_cfg.data['requester']
|
|
||||||
)
|
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
"""执行迁移"""
|
"""执行迁移"""
|
||||||
|
|||||||
@@ -13,17 +13,12 @@ class DifyThinkingConfigMigration(migration.Migration):
|
|||||||
if 'options' not in self.ap.provider_cfg.data['dify-service-api']:
|
if 'options' not in self.ap.provider_cfg.data['dify-service-api']:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if (
|
if 'convert-thinking-tips' not in self.ap.provider_cfg.data['dify-service-api']['options']:
|
||||||
'convert-thinking-tips'
|
|
||||||
not in self.ap.provider_cfg.data['dify-service-api']['options']
|
|
||||||
):
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
"""执行迁移"""
|
"""执行迁移"""
|
||||||
self.ap.provider_cfg.data['dify-service-api']['options'] = {
|
self.ap.provider_cfg.data['dify-service-api']['options'] = {'convert-thinking-tips': 'plain'}
|
||||||
'convert-thinking-tips': 'plain'
|
|
||||||
}
|
|
||||||
await self.ap.provider_cfg.dump_config()
|
await self.ap.provider_cfg.dump_config()
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ class GewechatFileUrlConfigMigration(migration.Migration):
|
|||||||
if adapter['adapter'] == 'gewechat':
|
if adapter['adapter'] == 'gewechat':
|
||||||
if 'gewechat_file_url' not in adapter:
|
if 'gewechat_file_url' not in adapter:
|
||||||
parsed_url = urlparse(adapter['gewechat_url'])
|
parsed_url = urlparse(adapter['gewechat_url'])
|
||||||
adapter['gewechat_file_url'] = (
|
adapter['gewechat_file_url'] = f'{parsed_url.scheme}://{parsed_url.hostname}:2532'
|
||||||
f'{parsed_url.scheme}://{parsed_url.hostname}:2532'
|
|
||||||
)
|
|
||||||
|
|
||||||
await self.ap.platform_cfg.dump_config()
|
await self.ap.platform_cfg.dump_config()
|
||||||
|
|||||||
@@ -3,24 +3,23 @@ from __future__ import annotations
|
|||||||
from .. import migration
|
from .. import migration
|
||||||
|
|
||||||
|
|
||||||
@migration.migration_class("tg-dingtalk-markdown", 38)
|
@migration.migration_class('tg-dingtalk-markdown', 38)
|
||||||
class TgDingtalkMarkdownMigration(migration.Migration):
|
class TgDingtalkMarkdownMigration(migration.Migration):
|
||||||
"""迁移"""
|
"""迁移"""
|
||||||
|
|
||||||
async def need_migrate(self) -> bool:
|
async def need_migrate(self) -> bool:
|
||||||
"""判断当前环境是否需要运行此迁移"""
|
"""判断当前环境是否需要运行此迁移"""
|
||||||
|
|
||||||
for adapter in self.ap.platform_cfg.data['platform-adapters']:
|
for adapter in self.ap.platform_cfg.data['platform-adapters']:
|
||||||
if adapter['adapter'] in ['dingtalk','telegram']:
|
if adapter['adapter'] in ['dingtalk', 'telegram']:
|
||||||
if 'markdown_card' not in adapter:
|
if 'markdown_card' not in adapter:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
"""执行迁移"""
|
"""执行迁移"""
|
||||||
for adapter in self.ap.platform_cfg.data['platform-adapters']:
|
for adapter in self.ap.platform_cfg.data['platform-adapters']:
|
||||||
if adapter['adapter'] in ['dingtalk','telegram']:
|
if adapter['adapter'] in ['dingtalk', 'telegram']:
|
||||||
if 'markdown_card' not in adapter:
|
if 'markdown_card' not in adapter:
|
||||||
adapter['markdown_card'] = False
|
adapter['markdown_card'] = False
|
||||||
await self.ap.platform_cfg.dump_config()
|
await self.ap.platform_cfg.dump_config()
|
||||||
|
|
||||||
@@ -3,20 +3,19 @@ from __future__ import annotations
|
|||||||
from .. import migration
|
from .. import migration
|
||||||
|
|
||||||
|
|
||||||
@migration.migration_class("modelscope-config-completion", 39)
|
@migration.migration_class('modelscope-config-completion', 39)
|
||||||
class ModelScopeConfigCompletionMigration(migration.Migration):
|
class ModelScopeConfigCompletionMigration(migration.Migration):
|
||||||
"""ModelScope配置迁移
|
"""ModelScope配置迁移"""
|
||||||
"""
|
|
||||||
|
|
||||||
async def need_migrate(self) -> bool:
|
async def need_migrate(self) -> bool:
|
||||||
"""判断当前环境是否需要运行此迁移
|
"""判断当前环境是否需要运行此迁移"""
|
||||||
"""
|
return (
|
||||||
return 'modelscope-chat-completions' not in self.ap.provider_cfg.data['requester'] \
|
'modelscope-chat-completions' not in self.ap.provider_cfg.data['requester']
|
||||||
or 'modelscope' not in self.ap.provider_cfg.data['keys']
|
or 'modelscope' not in self.ap.provider_cfg.data['keys']
|
||||||
|
)
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
"""执行迁移
|
"""执行迁移"""
|
||||||
"""
|
|
||||||
if 'modelscope-chat-completions' not in self.ap.provider_cfg.data['requester']:
|
if 'modelscope-chat-completions' not in self.ap.provider_cfg.data['requester']:
|
||||||
self.ap.provider_cfg.data['requester']['modelscope-chat-completions'] = {
|
self.ap.provider_cfg.data['requester']['modelscope-chat-completions'] = {
|
||||||
'base-url': 'https://api-inference.modelscope.cn/v1',
|
'base-url': 'https://api-inference.modelscope.cn/v1',
|
||||||
|
|||||||
@@ -3,20 +3,19 @@ from __future__ import annotations
|
|||||||
from .. import migration
|
from .. import migration
|
||||||
|
|
||||||
|
|
||||||
@migration.migration_class("ppio-config", 40)
|
@migration.migration_class('ppio-config', 40)
|
||||||
class PPIOConfigMigration(migration.Migration):
|
class PPIOConfigMigration(migration.Migration):
|
||||||
"""PPIO配置迁移
|
"""PPIO配置迁移"""
|
||||||
"""
|
|
||||||
|
|
||||||
async def need_migrate(self) -> bool:
|
async def need_migrate(self) -> bool:
|
||||||
"""判断当前环境是否需要运行此迁移
|
"""判断当前环境是否需要运行此迁移"""
|
||||||
"""
|
return (
|
||||||
return 'ppio-chat-completions' not in self.ap.provider_cfg.data['requester'] \
|
'ppio-chat-completions' not in self.ap.provider_cfg.data['requester']
|
||||||
or 'ppio' not in self.ap.provider_cfg.data['keys']
|
or 'ppio' not in self.ap.provider_cfg.data['keys']
|
||||||
|
)
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
"""执行迁移
|
"""执行迁移"""
|
||||||
"""
|
|
||||||
if 'ppio-chat-completions' not in self.ap.provider_cfg.data['requester']:
|
if 'ppio-chat-completions' not in self.ap.provider_cfg.data['requester']:
|
||||||
self.ap.provider_cfg.data['requester']['ppio-chat-completions'] = {
|
self.ap.provider_cfg.data['requester']['ppio-chat-completions'] = {
|
||||||
'base-url': 'https://api.ppinfra.com/v3/openai',
|
'base-url': 'https://api.ppinfra.com/v3/openai',
|
||||||
|
|||||||
+11
-25
@@ -35,9 +35,7 @@ class TaskContext:
|
|||||||
if action is not None:
|
if action is not None:
|
||||||
self.set_current_action(action)
|
self.set_current_action(action)
|
||||||
|
|
||||||
self._log(
|
self._log(f'{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")} | {self.current_action} | {msg}')
|
||||||
f'{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")} | {self.current_action} | {msg}'
|
|
||||||
)
|
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
def to_dict(self) -> dict:
|
||||||
return {'current_action': self.current_action, 'log': self.log}
|
return {'current_action': self.current_action, 'log': self.log}
|
||||||
@@ -104,9 +102,7 @@ class TaskWrapper:
|
|||||||
name: str = '',
|
name: str = '',
|
||||||
label: str = '',
|
label: str = '',
|
||||||
context: TaskContext = None,
|
context: TaskContext = None,
|
||||||
scopes: list[core_entities.LifecycleControlScope] = [
|
scopes: list[core_entities.LifecycleControlScope] = [core_entities.LifecycleControlScope.APPLICATION],
|
||||||
core_entities.LifecycleControlScope.APPLICATION
|
|
||||||
],
|
|
||||||
):
|
):
|
||||||
self.id = TaskWrapper._id_index
|
self.id = TaskWrapper._id_index
|
||||||
TaskWrapper._id_index += 1
|
TaskWrapper._id_index += 1
|
||||||
@@ -141,7 +137,9 @@ class TaskWrapper:
|
|||||||
exception_traceback = 'Traceback (most recent call last):\n'
|
exception_traceback = 'Traceback (most recent call last):\n'
|
||||||
|
|
||||||
for frame in self.task_stack:
|
for frame in self.task_stack:
|
||||||
exception_traceback += f' File "{frame.f_code.co_filename}", line {frame.f_lineno}, in {frame.f_code.co_name}\n'
|
exception_traceback += (
|
||||||
|
f' File "{frame.f_code.co_filename}", line {frame.f_lineno}, in {frame.f_code.co_name}\n'
|
||||||
|
)
|
||||||
|
|
||||||
exception_traceback += f' {self.assume_exception().__str__()}\n'
|
exception_traceback += f' {self.assume_exception().__str__()}\n'
|
||||||
|
|
||||||
@@ -156,13 +154,9 @@ class TaskWrapper:
|
|||||||
'runtime': {
|
'runtime': {
|
||||||
'done': self.task.done(),
|
'done': self.task.done(),
|
||||||
'state': self.task._state,
|
'state': self.task._state,
|
||||||
'exception': self.assume_exception().__str__()
|
'exception': self.assume_exception().__str__() if self.assume_exception() is not None else None,
|
||||||
if self.assume_exception() is not None
|
|
||||||
else None,
|
|
||||||
'exception_traceback': exception_traceback,
|
'exception_traceback': exception_traceback,
|
||||||
'result': self.assume_result().__str__()
|
'result': self.assume_result().__str__() if self.assume_result() is not None else None,
|
||||||
if self.assume_result() is not None
|
|
||||||
else None,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,13 +185,9 @@ class AsyncTaskManager:
|
|||||||
name: str = '',
|
name: str = '',
|
||||||
label: str = '',
|
label: str = '',
|
||||||
context: TaskContext = None,
|
context: TaskContext = None,
|
||||||
scopes: list[core_entities.LifecycleControlScope] = [
|
scopes: list[core_entities.LifecycleControlScope] = [core_entities.LifecycleControlScope.APPLICATION],
|
||||||
core_entities.LifecycleControlScope.APPLICATION
|
|
||||||
],
|
|
||||||
) -> TaskWrapper:
|
) -> TaskWrapper:
|
||||||
wrapper = TaskWrapper(
|
wrapper = TaskWrapper(self.ap, coro, task_type, kind, name, label, context, scopes)
|
||||||
self.ap, coro, task_type, kind, name, label, context, scopes
|
|
||||||
)
|
|
||||||
self.tasks.append(wrapper)
|
self.tasks.append(wrapper)
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
@@ -208,9 +198,7 @@ class AsyncTaskManager:
|
|||||||
name: str = '',
|
name: str = '',
|
||||||
label: str = '',
|
label: str = '',
|
||||||
context: TaskContext = None,
|
context: TaskContext = None,
|
||||||
scopes: list[core_entities.LifecycleControlScope] = [
|
scopes: list[core_entities.LifecycleControlScope] = [core_entities.LifecycleControlScope.APPLICATION],
|
||||||
core_entities.LifecycleControlScope.APPLICATION
|
|
||||||
],
|
|
||||||
) -> TaskWrapper:
|
) -> TaskWrapper:
|
||||||
return self.create_task(coro, 'user', kind, name, label, context, scopes)
|
return self.create_task(coro, 'user', kind, name, label, context, scopes)
|
||||||
|
|
||||||
@@ -225,9 +213,7 @@ class AsyncTaskManager:
|
|||||||
type: str = None,
|
type: str = None,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
return {
|
return {
|
||||||
'tasks': [
|
'tasks': [t.to_dict() for t in self.tasks if type is None or t.task_type == type],
|
||||||
t.to_dict() for t in self.tasks if type is None or t.task_type == type
|
|
||||||
],
|
|
||||||
'id_index': TaskWrapper._id_index,
|
'id_index': TaskWrapper._id_index,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-35
@@ -114,9 +114,7 @@ class Component(pydantic.BaseModel):
|
|||||||
_execution: Execution
|
_execution: Execution
|
||||||
"""组件执行"""
|
"""组件执行"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, owner: str, manifest: typing.Dict[str, typing.Any], rel_path: str):
|
||||||
self, owner: str, manifest: typing.Dict[str, typing.Any], rel_path: str
|
|
||||||
):
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
owner=owner,
|
owner=owner,
|
||||||
manifest=manifest,
|
manifest=manifest,
|
||||||
@@ -125,19 +123,12 @@ class Component(pydantic.BaseModel):
|
|||||||
)
|
)
|
||||||
self._metadata = Metadata(**manifest['metadata'])
|
self._metadata = Metadata(**manifest['metadata'])
|
||||||
self._spec = manifest['spec']
|
self._spec = manifest['spec']
|
||||||
self._execution = (
|
self._execution = Execution(**manifest['execution']) if 'execution' in manifest else None
|
||||||
Execution(**manifest['execution']) if 'execution' in manifest else None
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_component_manifest(cls, manifest: typing.Dict[str, typing.Any]) -> bool:
|
def is_component_manifest(cls, manifest: typing.Dict[str, typing.Any]) -> bool:
|
||||||
"""判断是否为组件清单"""
|
"""判断是否为组件清单"""
|
||||||
return (
|
return 'apiVersion' in manifest and 'kind' in manifest and 'metadata' in manifest and 'spec' in manifest
|
||||||
'apiVersion' in manifest
|
|
||||||
and 'kind' in manifest
|
|
||||||
and 'metadata' in manifest
|
|
||||||
and 'spec' in manifest
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def kind(self) -> str:
|
def kind(self) -> str:
|
||||||
@@ -200,9 +191,7 @@ class ComponentDiscoveryEngine:
|
|||||||
def __init__(self, ap: app.Application):
|
def __init__(self, ap: app.Application):
|
||||||
self.ap = ap
|
self.ap = ap
|
||||||
|
|
||||||
def load_component_manifest(
|
def load_component_manifest(self, path: str, owner: str = 'builtin', no_save: bool = False) -> Component | None:
|
||||||
self, path: str, owner: str = 'builtin', no_save: bool = False
|
|
||||||
) -> Component | None:
|
|
||||||
"""加载组件清单"""
|
"""加载组件清单"""
|
||||||
with open(path, 'r', encoding='utf-8') as f:
|
with open(path, 'r', encoding='utf-8') as f:
|
||||||
manifest = yaml.safe_load(f)
|
manifest = yaml.safe_load(f)
|
||||||
@@ -229,18 +218,12 @@ class ComponentDiscoveryEngine:
|
|||||||
if depth > max_depth:
|
if depth > max_depth:
|
||||||
return
|
return
|
||||||
for file in os.listdir(path):
|
for file in os.listdir(path):
|
||||||
if (not os.path.isdir(os.path.join(path, file))) and (
|
if (not os.path.isdir(os.path.join(path, file))) and (file.endswith('.yaml') or file.endswith('.yml')):
|
||||||
file.endswith('.yaml') or file.endswith('.yml')
|
comp = self.load_component_manifest(os.path.join(path, file), owner, no_save)
|
||||||
):
|
|
||||||
comp = self.load_component_manifest(
|
|
||||||
os.path.join(path, file), owner, no_save
|
|
||||||
)
|
|
||||||
if comp is not None:
|
if comp is not None:
|
||||||
components.append(comp)
|
components.append(comp)
|
||||||
elif os.path.isdir(os.path.join(path, file)):
|
elif os.path.isdir(os.path.join(path, file)):
|
||||||
recursive_load_component_manifests_in_dir(
|
recursive_load_component_manifests_in_dir(os.path.join(path, file), depth + 1)
|
||||||
os.path.join(path, file), depth + 1
|
|
||||||
)
|
|
||||||
|
|
||||||
recursive_load_component_manifests_in_dir(path)
|
recursive_load_component_manifests_in_dir(path)
|
||||||
return components
|
return components
|
||||||
@@ -259,18 +242,12 @@ class ComponentDiscoveryEngine:
|
|||||||
for dir in group['fromDirs']:
|
for dir in group['fromDirs']:
|
||||||
path = dir['path']
|
path = dir['path']
|
||||||
max_depth = dir['maxDepth'] if 'maxDepth' in dir else 1
|
max_depth = dir['maxDepth'] if 'maxDepth' in dir else 1
|
||||||
components.extend(
|
components.extend(self.load_component_manifests_in_dir(path, owner, no_save, max_depth))
|
||||||
self.load_component_manifests_in_dir(
|
|
||||||
path, owner, no_save, max_depth
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return components
|
return components
|
||||||
|
|
||||||
def discover_blueprint(self, blueprint_manifest_path: str, owner: str = 'builtin'):
|
def discover_blueprint(self, blueprint_manifest_path: str, owner: str = 'builtin'):
|
||||||
"""发现蓝图"""
|
"""发现蓝图"""
|
||||||
blueprint_manifest = self.load_component_manifest(
|
blueprint_manifest = self.load_component_manifest(blueprint_manifest_path, owner, no_save=True)
|
||||||
blueprint_manifest_path, owner, no_save=True
|
|
||||||
)
|
|
||||||
if blueprint_manifest is None:
|
if blueprint_manifest is None:
|
||||||
raise ValueError(f'Invalid blueprint manifest: {blueprint_manifest_path}')
|
raise ValueError(f'Invalid blueprint manifest: {blueprint_manifest_path}')
|
||||||
assert blueprint_manifest.kind == 'Blueprint', '`Kind` must be `Blueprint`'
|
assert blueprint_manifest.kind == 'Blueprint', '`Kind` must be `Blueprint`'
|
||||||
@@ -297,9 +274,7 @@ class ComponentDiscoveryEngine:
|
|||||||
return []
|
return []
|
||||||
return self.components[kind]
|
return self.components[kind]
|
||||||
|
|
||||||
def find_components(
|
def find_components(self, kind: str, component_list: typing.List[Component]) -> typing.List[Component]:
|
||||||
self, kind: str, component_list: typing.List[Component]
|
|
||||||
) -> typing.List[Component]:
|
|
||||||
"""查找组件"""
|
"""查找组件"""
|
||||||
result: typing.List[Component] = []
|
result: typing.List[Component] = []
|
||||||
for component in component_list:
|
for component in component_list:
|
||||||
|
|||||||
@@ -16,9 +16,7 @@ class Bot(Base):
|
|||||||
enable = sqlalchemy.Column(sqlalchemy.Boolean, nullable=False, default=False)
|
enable = sqlalchemy.Column(sqlalchemy.Boolean, nullable=False, default=False)
|
||||||
use_pipeline_name = sqlalchemy.Column(sqlalchemy.String(255), nullable=True)
|
use_pipeline_name = sqlalchemy.Column(sqlalchemy.String(255), nullable=True)
|
||||||
use_pipeline_uuid = sqlalchemy.Column(sqlalchemy.String(255), nullable=True)
|
use_pipeline_uuid = sqlalchemy.Column(sqlalchemy.String(255), nullable=True)
|
||||||
created_at = sqlalchemy.Column(
|
created_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now())
|
||||||
sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now()
|
|
||||||
)
|
|
||||||
updated_at = sqlalchemy.Column(
|
updated_at = sqlalchemy.Column(
|
||||||
sqlalchemy.DateTime,
|
sqlalchemy.DateTime,
|
||||||
nullable=False,
|
nullable=False,
|
||||||
|
|||||||
@@ -16,9 +16,7 @@ class LLMModel(Base):
|
|||||||
api_keys = sqlalchemy.Column(sqlalchemy.JSON, nullable=False)
|
api_keys = sqlalchemy.Column(sqlalchemy.JSON, nullable=False)
|
||||||
abilities = sqlalchemy.Column(sqlalchemy.JSON, nullable=False, default=[])
|
abilities = sqlalchemy.Column(sqlalchemy.JSON, nullable=False, default=[])
|
||||||
extra_args = sqlalchemy.Column(sqlalchemy.JSON, nullable=False, default={})
|
extra_args = sqlalchemy.Column(sqlalchemy.JSON, nullable=False, default={})
|
||||||
created_at = sqlalchemy.Column(
|
created_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now())
|
||||||
sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now()
|
|
||||||
)
|
|
||||||
updated_at = sqlalchemy.Column(
|
updated_at = sqlalchemy.Column(
|
||||||
sqlalchemy.DateTime,
|
sqlalchemy.DateTime,
|
||||||
nullable=False,
|
nullable=False,
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ class LegacyPipeline(Base):
|
|||||||
uuid = sqlalchemy.Column(sqlalchemy.String(255), primary_key=True, unique=True)
|
uuid = sqlalchemy.Column(sqlalchemy.String(255), primary_key=True, unique=True)
|
||||||
name = sqlalchemy.Column(sqlalchemy.String(255), nullable=False)
|
name = sqlalchemy.Column(sqlalchemy.String(255), nullable=False)
|
||||||
description = sqlalchemy.Column(sqlalchemy.String(255), nullable=False)
|
description = sqlalchemy.Column(sqlalchemy.String(255), nullable=False)
|
||||||
created_at = sqlalchemy.Column(
|
created_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now())
|
||||||
sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now()
|
|
||||||
)
|
|
||||||
updated_at = sqlalchemy.Column(
|
updated_at = sqlalchemy.Column(
|
||||||
sqlalchemy.DateTime,
|
sqlalchemy.DateTime,
|
||||||
nullable=False,
|
nullable=False,
|
||||||
@@ -35,9 +33,7 @@ class PipelineRunRecord(Base):
|
|||||||
uuid = sqlalchemy.Column(sqlalchemy.String(255), primary_key=True, unique=True)
|
uuid = sqlalchemy.Column(sqlalchemy.String(255), primary_key=True, unique=True)
|
||||||
pipeline_uuid = sqlalchemy.Column(sqlalchemy.String(255), nullable=False)
|
pipeline_uuid = sqlalchemy.Column(sqlalchemy.String(255), nullable=False)
|
||||||
status = sqlalchemy.Column(sqlalchemy.String(255), nullable=False)
|
status = sqlalchemy.Column(sqlalchemy.String(255), nullable=False)
|
||||||
created_at = sqlalchemy.Column(
|
created_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now())
|
||||||
sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now()
|
|
||||||
)
|
|
||||||
updated_at = sqlalchemy.Column(
|
updated_at = sqlalchemy.Column(
|
||||||
sqlalchemy.DateTime,
|
sqlalchemy.DateTime,
|
||||||
nullable=False,
|
nullable=False,
|
||||||
|
|||||||
@@ -13,9 +13,7 @@ class PluginSetting(Base):
|
|||||||
enabled = sqlalchemy.Column(sqlalchemy.Boolean, nullable=False, default=True)
|
enabled = sqlalchemy.Column(sqlalchemy.Boolean, nullable=False, default=True)
|
||||||
priority = sqlalchemy.Column(sqlalchemy.Integer, nullable=False, default=0)
|
priority = sqlalchemy.Column(sqlalchemy.Integer, nullable=False, default=0)
|
||||||
config = sqlalchemy.Column(sqlalchemy.JSON, nullable=False, default=dict)
|
config = sqlalchemy.Column(sqlalchemy.JSON, nullable=False, default=dict)
|
||||||
created_at = sqlalchemy.Column(
|
created_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now())
|
||||||
sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now()
|
|
||||||
)
|
|
||||||
updated_at = sqlalchemy.Column(
|
updated_at = sqlalchemy.Column(
|
||||||
sqlalchemy.DateTime,
|
sqlalchemy.DateTime,
|
||||||
nullable=False,
|
nullable=False,
|
||||||
|
|||||||
@@ -9,9 +9,7 @@ class User(Base):
|
|||||||
id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
|
id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
|
||||||
user = sqlalchemy.Column(sqlalchemy.String(255), nullable=False)
|
user = sqlalchemy.Column(sqlalchemy.String(255), nullable=False)
|
||||||
password = sqlalchemy.Column(sqlalchemy.String(255), nullable=False)
|
password = sqlalchemy.Column(sqlalchemy.String(255), nullable=False)
|
||||||
created_at = sqlalchemy.Column(
|
created_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now())
|
||||||
sqlalchemy.DateTime, nullable=False, server_default=sqlalchemy.func.now()
|
|
||||||
)
|
|
||||||
updated_at = sqlalchemy.Column(
|
updated_at = sqlalchemy.Column(
|
||||||
sqlalchemy.DateTime,
|
sqlalchemy.DateTime,
|
||||||
nullable=False,
|
nullable=False,
|
||||||
|
|||||||
@@ -11,6 +11,4 @@ class SQLiteDatabaseManager(database.BaseDatabaseManager):
|
|||||||
|
|
||||||
async def initialize(self) -> None:
|
async def initialize(self) -> None:
|
||||||
sqlite_path = 'data/langbot.db'
|
sqlite_path = 'data/langbot.db'
|
||||||
self.engine = sqlalchemy_asyncio.create_async_engine(
|
self.engine = sqlalchemy_asyncio.create_async_engine(f'sqlite+aiosqlite:///{sqlite_path}')
|
||||||
f'sqlite+aiosqlite:///{sqlite_path}'
|
|
||||||
)
|
|
||||||
|
|||||||
+9
-27
@@ -58,24 +58,18 @@ class PersistenceManager:
|
|||||||
for item in metadata.initial_metadata:
|
for item in metadata.initial_metadata:
|
||||||
# check if the item exists
|
# check if the item exists
|
||||||
result = await self.execute_async(
|
result = await self.execute_async(
|
||||||
sqlalchemy.select(metadata.Metadata).where(
|
sqlalchemy.select(metadata.Metadata).where(metadata.Metadata.key == item['key'])
|
||||||
metadata.Metadata.key == item['key']
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
row = result.first()
|
row = result.first()
|
||||||
if row is None:
|
if row is None:
|
||||||
await self.execute_async(
|
await self.execute_async(sqlalchemy.insert(metadata.Metadata).values(item))
|
||||||
sqlalchemy.insert(metadata.Metadata).values(item)
|
|
||||||
)
|
|
||||||
|
|
||||||
# write default pipeline
|
# write default pipeline
|
||||||
result = await self.execute_async(sqlalchemy.select(pipeline.LegacyPipeline))
|
result = await self.execute_async(sqlalchemy.select(pipeline.LegacyPipeline))
|
||||||
if result.first() is None:
|
if result.first() is None:
|
||||||
self.ap.logger.info('Creating default pipeline...')
|
self.ap.logger.info('Creating default pipeline...')
|
||||||
|
|
||||||
pipeline_config = json.load(
|
pipeline_config = json.load(open('templates/default-pipeline-config.json', 'r', encoding='utf-8'))
|
||||||
open('templates/default-pipeline-config.json', 'r', encoding='utf-8')
|
|
||||||
)
|
|
||||||
|
|
||||||
pipeline_data = {
|
pipeline_data = {
|
||||||
'uuid': str(uuid.uuid4()),
|
'uuid': str(uuid.uuid4()),
|
||||||
@@ -87,16 +81,12 @@ class PersistenceManager:
|
|||||||
'config': pipeline_config,
|
'config': pipeline_config,
|
||||||
}
|
}
|
||||||
|
|
||||||
await self.execute_async(
|
await self.execute_async(sqlalchemy.insert(pipeline.LegacyPipeline).values(pipeline_data))
|
||||||
sqlalchemy.insert(pipeline.LegacyPipeline).values(pipeline_data)
|
|
||||||
)
|
|
||||||
# =================================
|
# =================================
|
||||||
|
|
||||||
# run migrations
|
# run migrations
|
||||||
database_version = await self.execute_async(
|
database_version = await self.execute_async(
|
||||||
sqlalchemy.select(metadata.Metadata).where(
|
sqlalchemy.select(metadata.Metadata).where(metadata.Metadata.key == 'database_version')
|
||||||
metadata.Metadata.key == 'database_version'
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
database_version = int(database_version.fetchone()[1])
|
database_version = int(database_version.fetchone()[1])
|
||||||
@@ -122,17 +112,11 @@ class PersistenceManager:
|
|||||||
.values({'value': str(migration_instance.number)})
|
.values({'value': str(migration_instance.number)})
|
||||||
)
|
)
|
||||||
last_migration_number = migration_instance.number
|
last_migration_number = migration_instance.number
|
||||||
self.ap.logger.info(
|
self.ap.logger.info(f'Migration {migration_instance.number} completed.')
|
||||||
f'Migration {migration_instance.number} completed.'
|
|
||||||
)
|
|
||||||
|
|
||||||
self.ap.logger.info(
|
self.ap.logger.info(f'Successfully upgraded database to version {last_migration_number}.')
|
||||||
f'Successfully upgraded database to version {last_migration_number}.'
|
|
||||||
)
|
|
||||||
|
|
||||||
async def execute_async(
|
async def execute_async(self, *args, **kwargs) -> sqlalchemy.engine.cursor.CursorResult:
|
||||||
self, *args, **kwargs
|
|
||||||
) -> sqlalchemy.engine.cursor.CursorResult:
|
|
||||||
async with self.get_db_engine().connect() as conn:
|
async with self.get_db_engine().connect() as conn:
|
||||||
result = await conn.execute(*args, **kwargs)
|
result = await conn.execute(*args, **kwargs)
|
||||||
await conn.commit()
|
await conn.commit()
|
||||||
@@ -141,9 +125,7 @@ class PersistenceManager:
|
|||||||
def get_db_engine(self) -> sqlalchemy_asyncio.AsyncEngine:
|
def get_db_engine(self) -> sqlalchemy_asyncio.AsyncEngine:
|
||||||
return self.db.get_engine()
|
return self.db.get_engine()
|
||||||
|
|
||||||
def serialize_model(
|
def serialize_model(self, model: typing.Type[sqlalchemy.Base], data: sqlalchemy.Base) -> dict:
|
||||||
self, model: typing.Type[sqlalchemy.Base], data: sqlalchemy.Base
|
|
||||||
) -> dict:
|
|
||||||
return {
|
return {
|
||||||
column.name: getattr(data, column.name)
|
column.name: getattr(data, column.name)
|
||||||
if not isinstance(getattr(data, column.name), (datetime.datetime))
|
if not isinstance(getattr(data, column.name), (datetime.datetime))
|
||||||
|
|||||||
@@ -14,9 +14,7 @@ class BanSessionCheckStage(stage.PipelineStage):
|
|||||||
async def initialize(self, pipeline_config: dict):
|
async def initialize(self, pipeline_config: dict):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def process(
|
async def process(self, query: core_entities.Query, stage_inst_name: str) -> entities.StageProcessResult:
|
||||||
self, query: core_entities.Query, stage_inst_name: str
|
|
||||||
) -> entities.StageProcessResult:
|
|
||||||
found = False
|
found = False
|
||||||
|
|
||||||
mode = query.pipeline_config['trigger']['access-control']['mode']
|
mode = query.pipeline_config['trigger']['access-control']['mode']
|
||||||
@@ -41,11 +39,7 @@ class BanSessionCheckStage(stage.PipelineStage):
|
|||||||
ctn = not found
|
ctn = not found
|
||||||
|
|
||||||
return entities.StageProcessResult(
|
return entities.StageProcessResult(
|
||||||
result_type=entities.ResultType.CONTINUE
|
result_type=entities.ResultType.CONTINUE if ctn else entities.ResultType.INTERRUPT,
|
||||||
if ctn
|
|
||||||
else entities.ResultType.INTERRUPT,
|
|
||||||
new_query=query,
|
new_query=query,
|
||||||
console_notice=f'根据访问控制忽略消息: {query.launcher_type.value}_{query.launcher_id}'
|
console_notice=f'根据访问控制忽略消息: {query.launcher_type.value}_{query.launcher_id}' if not ctn else '',
|
||||||
if not ctn
|
|
||||||
else '',
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -65,9 +65,7 @@ class ContentFilterStage(stage.PipelineStage):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if query.pipeline_config['safety']['content-filter']['scope'] == 'output-msg':
|
if query.pipeline_config['safety']['content-filter']['scope'] == 'output-msg':
|
||||||
return entities.StageProcessResult(
|
return entities.StageProcessResult(result_type=entities.ResultType.CONTINUE, new_query=query)
|
||||||
result_type=entities.ResultType.CONTINUE, new_query=query
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
for filter in self.filter_chain:
|
for filter in self.filter_chain:
|
||||||
if filter_entities.EnableStage.PRE in filter.enable_stages:
|
if filter_entities.EnableStage.PRE in filter.enable_stages:
|
||||||
@@ -86,13 +84,9 @@ class ContentFilterStage(stage.PipelineStage):
|
|||||||
elif result.level == filter_entities.ResultLevel.PASS: # 传到下一个
|
elif result.level == filter_entities.ResultLevel.PASS: # 传到下一个
|
||||||
message = result.replacement
|
message = result.replacement
|
||||||
|
|
||||||
query.message_chain = platform_message.MessageChain(
|
query.message_chain = platform_message.MessageChain(platform_message.Plain(message))
|
||||||
platform_message.Plain(message)
|
|
||||||
)
|
|
||||||
|
|
||||||
return entities.StageProcessResult(
|
return entities.StageProcessResult(result_type=entities.ResultType.CONTINUE, new_query=query)
|
||||||
result_type=entities.ResultType.CONTINUE, new_query=query
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _post_process(
|
async def _post_process(
|
||||||
self,
|
self,
|
||||||
@@ -103,9 +97,7 @@ class ContentFilterStage(stage.PipelineStage):
|
|||||||
只要是 PASS 或者 MASKED 的就通过此 filter,将其 replacement 设置为message,进入下一个 filter
|
只要是 PASS 或者 MASKED 的就通过此 filter,将其 replacement 设置为message,进入下一个 filter
|
||||||
"""
|
"""
|
||||||
if query.pipeline_config['safety']['content-filter']['scope'] == 'income-msg':
|
if query.pipeline_config['safety']['content-filter']['scope'] == 'income-msg':
|
||||||
return entities.StageProcessResult(
|
return entities.StageProcessResult(result_type=entities.ResultType.CONTINUE, new_query=query)
|
||||||
result_type=entities.ResultType.CONTINUE, new_query=query
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
message = message.strip()
|
message = message.strip()
|
||||||
for filter in self.filter_chain:
|
for filter in self.filter_chain:
|
||||||
@@ -127,13 +119,9 @@ class ContentFilterStage(stage.PipelineStage):
|
|||||||
|
|
||||||
query.resp_messages[-1].content = message
|
query.resp_messages[-1].content = message
|
||||||
|
|
||||||
return entities.StageProcessResult(
|
return entities.StageProcessResult(result_type=entities.ResultType.CONTINUE, new_query=query)
|
||||||
result_type=entities.ResultType.CONTINUE, new_query=query
|
|
||||||
)
|
|
||||||
|
|
||||||
async def process(
|
async def process(self, query: core_entities.Query, stage_inst_name: str) -> entities.StageProcessResult:
|
||||||
self, query: core_entities.Query, stage_inst_name: str
|
|
||||||
) -> entities.StageProcessResult:
|
|
||||||
"""处理"""
|
"""处理"""
|
||||||
if stage_inst_name == 'PreContentFilterStage':
|
if stage_inst_name == 'PreContentFilterStage':
|
||||||
contain_non_text = False
|
contain_non_text = False
|
||||||
@@ -147,9 +135,7 @@ class ContentFilterStage(stage.PipelineStage):
|
|||||||
|
|
||||||
if contain_non_text:
|
if contain_non_text:
|
||||||
self.ap.logger.debug('消息中包含非文本消息,跳过内容过滤器检查。')
|
self.ap.logger.debug('消息中包含非文本消息,跳过内容过滤器检查。')
|
||||||
return entities.StageProcessResult(
|
return entities.StageProcessResult(result_type=entities.ResultType.CONTINUE, new_query=query)
|
||||||
result_type=entities.ResultType.CONTINUE, new_query=query
|
|
||||||
)
|
|
||||||
|
|
||||||
return await self._pre_process(str(query.message_chain).strip(), query)
|
return await self._pre_process(str(query.message_chain).strip(), query)
|
||||||
elif stage_inst_name == 'PostContentFilterStage':
|
elif stage_inst_name == 'PostContentFilterStage':
|
||||||
@@ -162,8 +148,6 @@ class ContentFilterStage(stage.PipelineStage):
|
|||||||
self.ap.logger.debug(
|
self.ap.logger.debug(
|
||||||
'resp_messages[-1] 不是 Message 类型或 query.resp_messages[-1].content 不是 str 类型,跳过内容过滤器检查。'
|
'resp_messages[-1] 不是 Message 类型或 query.resp_messages[-1].content 不是 str 类型,跳过内容过滤器检查。'
|
||||||
)
|
)
|
||||||
return entities.StageProcessResult(
|
return entities.StageProcessResult(result_type=entities.ResultType.CONTINUE, new_query=query)
|
||||||
result_type=entities.ResultType.CONTINUE, new_query=query
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
raise ValueError(f'未知的 stage_inst_name: {stage_inst_name}')
|
raise ValueError(f'未知的 stage_inst_name: {stage_inst_name}')
|
||||||
|
|||||||
@@ -60,9 +60,7 @@ class ContentFilter(metaclass=abc.ABCMeta):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
async def process(
|
async def process(self, query: core_entities.Query, message: str = None, image_url=None) -> entities.FilterResult:
|
||||||
self, query: core_entities.Query, message: str = None, image_url=None
|
|
||||||
) -> entities.FilterResult:
|
|
||||||
"""处理消息
|
"""处理消息
|
||||||
|
|
||||||
分为前后阶段,具体取决于 enable_stages 的值。
|
分为前后阶段,具体取决于 enable_stages 的值。
|
||||||
|
|||||||
@@ -21,19 +21,13 @@ class BaiduCloudExamine(filter_model.ContentFilter):
|
|||||||
BAIDU_EXAMINE_TOKEN_URL,
|
BAIDU_EXAMINE_TOKEN_URL,
|
||||||
params={
|
params={
|
||||||
'grant_type': 'client_credentials',
|
'grant_type': 'client_credentials',
|
||||||
'client_id': self.ap.pipeline_cfg.data['baidu-cloud-examine'][
|
'client_id': self.ap.pipeline_cfg.data['baidu-cloud-examine']['api-key'],
|
||||||
'api-key'
|
'client_secret': self.ap.pipeline_cfg.data['baidu-cloud-examine']['api-secret'],
|
||||||
],
|
|
||||||
'client_secret': self.ap.pipeline_cfg.data['baidu-cloud-examine'][
|
|
||||||
'api-secret'
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
) as resp:
|
) as resp:
|
||||||
return (await resp.json())['access_token']
|
return (await resp.json())['access_token']
|
||||||
|
|
||||||
async def process(
|
async def process(self, query: core_entities.Query, message: str) -> entities.FilterResult:
|
||||||
self, query: core_entities.Query, message: str
|
|
||||||
) -> entities.FilterResult:
|
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.post(
|
async with session.post(
|
||||||
BAIDU_EXAMINE_URL.format(await self._get_token()),
|
BAIDU_EXAMINE_URL.format(await self._get_token()),
|
||||||
|
|||||||
@@ -13,9 +13,7 @@ class BanWordFilter(filter_model.ContentFilter):
|
|||||||
async def initialize(self):
|
async def initialize(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def process(
|
async def process(self, query: core_entities.Query, message: str) -> entities.FilterResult:
|
||||||
self, query: core_entities.Query, message: str
|
|
||||||
) -> entities.FilterResult:
|
|
||||||
found = False
|
found = False
|
||||||
|
|
||||||
for word in self.ap.sensitive_meta.data['words']:
|
for word in self.ap.sensitive_meta.data['words']:
|
||||||
@@ -31,9 +29,7 @@ class BanWordFilter(filter_model.ContentFilter):
|
|||||||
self.ap.sensitive_meta.data['mask'] * len(match[i]),
|
self.ap.sensitive_meta.data['mask'] * len(match[i]),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
message = message.replace(
|
message = message.replace(match[i], self.ap.sensitive_meta.data['mask_word'])
|
||||||
match[i], self.ap.sensitive_meta.data['mask_word']
|
|
||||||
)
|
|
||||||
|
|
||||||
return entities.FilterResult(
|
return entities.FilterResult(
|
||||||
level=entities.ResultLevel.MASKED if found else entities.ResultLevel.PASS,
|
level=entities.ResultLevel.MASKED if found else entities.ResultLevel.PASS,
|
||||||
|
|||||||
@@ -16,9 +16,7 @@ class ContentIgnore(filter_model.ContentFilter):
|
|||||||
entities.EnableStage.PRE,
|
entities.EnableStage.PRE,
|
||||||
]
|
]
|
||||||
|
|
||||||
async def process(
|
async def process(self, query: core_entities.Query, message: str) -> entities.FilterResult:
|
||||||
self, query: core_entities.Query, message: str
|
|
||||||
) -> entities.FilterResult:
|
|
||||||
if 'prefix' in query.pipeline_config['trigger']['ignore-rules']:
|
if 'prefix' in query.pipeline_config['trigger']['ignore-rules']:
|
||||||
for rule in query.pipeline_config['trigger']['ignore-rules']['prefix']:
|
for rule in query.pipeline_config['trigger']['ignore-rules']['prefix']:
|
||||||
if message.startswith(rule):
|
if message.startswith(rule):
|
||||||
|
|||||||
@@ -16,9 +16,7 @@ class Controller:
|
|||||||
|
|
||||||
def __init__(self, ap: app.Application):
|
def __init__(self, ap: app.Application):
|
||||||
self.ap = ap
|
self.ap = ap
|
||||||
self.semaphore = asyncio.Semaphore(
|
self.semaphore = asyncio.Semaphore(self.ap.instance_config.data['concurrency']['pipeline'])
|
||||||
self.ap.instance_config.data['concurrency']['pipeline']
|
|
||||||
)
|
|
||||||
|
|
||||||
async def consumer(self):
|
async def consumer(self):
|
||||||
"""事件处理循环"""
|
"""事件处理循环"""
|
||||||
@@ -32,9 +30,7 @@ class Controller:
|
|||||||
|
|
||||||
for query in queries:
|
for query in queries:
|
||||||
session = await self.ap.sess_mgr.get_session(query)
|
session = await self.ap.sess_mgr.get_session(query)
|
||||||
self.ap.logger.debug(
|
self.ap.logger.debug(f'Checking query {query} session {session}')
|
||||||
f'Checking query {query} session {session}'
|
|
||||||
)
|
|
||||||
|
|
||||||
if not session.semaphore.locked():
|
if not session.semaphore.locked():
|
||||||
selected_query = query
|
selected_query = query
|
||||||
@@ -55,22 +51,16 @@ class Controller:
|
|||||||
# find pipeline
|
# find pipeline
|
||||||
# Here firstly find the bot, then find the pipeline, in case the bot adapter's config is not the latest one.
|
# Here firstly find the bot, then find the pipeline, in case the bot adapter's config is not the latest one.
|
||||||
# Like aiocqhttp, once a client is connected, even the adapter was updated and restarted, the existing client connection will not be affected.
|
# Like aiocqhttp, once a client is connected, even the adapter was updated and restarted, the existing client connection will not be affected.
|
||||||
bot = await self.ap.platform_mgr.get_bot_by_uuid(
|
bot = await self.ap.platform_mgr.get_bot_by_uuid(selected_query.bot_uuid)
|
||||||
selected_query.bot_uuid
|
|
||||||
)
|
|
||||||
if bot:
|
if bot:
|
||||||
pipeline = (
|
pipeline = await self.ap.pipeline_mgr.get_pipeline_by_uuid(
|
||||||
await self.ap.pipeline_mgr.get_pipeline_by_uuid(
|
bot.bot_entity.use_pipeline_uuid
|
||||||
bot.bot_entity.use_pipeline_uuid
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
if pipeline:
|
if pipeline:
|
||||||
await pipeline.run(selected_query)
|
await pipeline.run(selected_query)
|
||||||
|
|
||||||
async with self.ap.query_pool:
|
async with self.ap.query_pool:
|
||||||
(
|
(await self.ap.sess_mgr.get_session(selected_query)).semaphore.release()
|
||||||
await self.ap.sess_mgr.get_session(selected_query)
|
|
||||||
).semaphore.release()
|
|
||||||
# 通知其他协程,有新的请求可以处理了
|
# 通知其他协程,有新的请求可以处理了
|
||||||
self.ap.query_pool.condition.notify_all()
|
self.ap.query_pool.condition.notify_all()
|
||||||
|
|
||||||
|
|||||||
@@ -47,9 +47,7 @@ class LongTextProcessStage(stage.PipelineStage):
|
|||||||
'未找到字体文件,且无法使用系统自带字体,更换为转发消息组件以发送长消息,您可以在配置文件中调整相关设置。'
|
'未找到字体文件,且无法使用系统自带字体,更换为转发消息组件以发送长消息,您可以在配置文件中调整相关设置。'
|
||||||
)
|
)
|
||||||
|
|
||||||
pipeline_config['output']['long-text-processing'][
|
pipeline_config['output']['long-text-processing']['strategy'] = 'forward'
|
||||||
'strategy'
|
|
||||||
] = 'forward'
|
|
||||||
except Exception:
|
except Exception:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
self.ap.logger.error(
|
self.ap.logger.error(
|
||||||
@@ -58,9 +56,7 @@ class LongTextProcessStage(stage.PipelineStage):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
pipeline_config['output']['long-text-processing']['strategy'] = (
|
pipeline_config['output']['long-text-processing']['strategy'] = 'forward'
|
||||||
'forward'
|
|
||||||
)
|
|
||||||
|
|
||||||
for strategy_cls in strategy.preregistered_strategies:
|
for strategy_cls in strategy.preregistered_strategies:
|
||||||
if strategy_cls.name == config['strategy']:
|
if strategy_cls.name == config['strategy']:
|
||||||
@@ -71,9 +67,7 @@ class LongTextProcessStage(stage.PipelineStage):
|
|||||||
|
|
||||||
await self.strategy_impl.initialize()
|
await self.strategy_impl.initialize()
|
||||||
|
|
||||||
async def process(
|
async def process(self, query: core_entities.Query, stage_inst_name: str) -> entities.StageProcessResult:
|
||||||
self, query: core_entities.Query, stage_inst_name: str
|
|
||||||
) -> entities.StageProcessResult:
|
|
||||||
# 检查是否包含非 Plain 组件
|
# 检查是否包含非 Plain 组件
|
||||||
contains_non_plain = False
|
contains_non_plain = False
|
||||||
|
|
||||||
@@ -89,11 +83,7 @@ class LongTextProcessStage(stage.PipelineStage):
|
|||||||
> query.pipeline_config['output']['long-text-processing']['threshold']
|
> query.pipeline_config['output']['long-text-processing']['threshold']
|
||||||
):
|
):
|
||||||
query.resp_message_chain[-1] = platform_message.MessageChain(
|
query.resp_message_chain[-1] = platform_message.MessageChain(
|
||||||
await self.strategy_impl.process(
|
await self.strategy_impl.process(str(query.resp_message_chain[-1]), query)
|
||||||
str(query.resp_message_chain[-1]), query
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return entities.StageProcessResult(
|
return entities.StageProcessResult(result_type=entities.ResultType.CONTINUE, new_query=query)
|
||||||
result_type=entities.ResultType.CONTINUE, new_query=query
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -13,9 +13,7 @@ Forward = platform_message.Forward
|
|||||||
|
|
||||||
@strategy_model.strategy_class('forward')
|
@strategy_model.strategy_class('forward')
|
||||||
class ForwardComponentStrategy(strategy_model.LongTextStrategy):
|
class ForwardComponentStrategy(strategy_model.LongTextStrategy):
|
||||||
async def process(
|
async def process(self, message: str, query: core_entities.Query) -> list[platform_message.MessageComponent]:
|
||||||
self, message: str, query: core_entities.Query
|
|
||||||
) -> list[platform_message.MessageComponent]:
|
|
||||||
display = ForwardMessageDiaplay(
|
display = ForwardMessageDiaplay(
|
||||||
title='群聊的聊天记录',
|
title='群聊的聊天记录',
|
||||||
brief='[聊天记录]',
|
brief='[聊天记录]',
|
||||||
|
|||||||
@@ -27,18 +27,14 @@ class Text2ImageStrategy(strategy_model.LongTextStrategy):
|
|||||||
encoding='utf-8',
|
encoding='utf-8',
|
||||||
)
|
)
|
||||||
|
|
||||||
async def process(
|
async def process(self, message: str, query: core_entities.Query) -> list[platform_message.MessageComponent]:
|
||||||
self, message: str, query: core_entities.Query
|
|
||||||
) -> list[platform_message.MessageComponent]:
|
|
||||||
img_path = self.text_to_image(
|
img_path = self.text_to_image(
|
||||||
text_str=message,
|
text_str=message,
|
||||||
save_as='temp/{}.png'.format(int(time.time())),
|
save_as='temp/{}.png'.format(int(time.time())),
|
||||||
query=query,
|
query=query,
|
||||||
)
|
)
|
||||||
|
|
||||||
compressed_path, size = self.compress_image(
|
compressed_path, size = self.compress_image(img_path, outfile='temp/{}_compressed.png'.format(int(time.time())))
|
||||||
img_path, outfile='temp/{}_compressed.png'.format(int(time.time()))
|
|
||||||
)
|
|
||||||
|
|
||||||
with open(compressed_path, 'rb') as f:
|
with open(compressed_path, 'rb') as f:
|
||||||
img = f.read()
|
img = f.read()
|
||||||
@@ -165,10 +161,7 @@ class Text2ImageStrategy(strategy_model.LongTextStrategy):
|
|||||||
numbers = self.indexNumber(rest_text)
|
numbers = self.indexNumber(rest_text)
|
||||||
|
|
||||||
for number in numbers:
|
for number in numbers:
|
||||||
if (
|
if number[1] < point < number[1] + len(number[0]) and number[1] != 0:
|
||||||
number[1] < point < number[1] + len(number[0])
|
|
||||||
and number[1] != 0
|
|
||||||
):
|
|
||||||
point = number[1]
|
point = number[1]
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -181,9 +174,7 @@ class Text2ImageStrategy(strategy_model.LongTextStrategy):
|
|||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
# 准备画布
|
# 准备画布
|
||||||
img = Image.new(
|
img = Image.new('RGBA', (width, max(280, len(final_lines) * 35 + 65)), (255, 255, 255, 255))
|
||||||
'RGBA', (width, max(280, len(final_lines) * 35 + 65)), (255, 255, 255, 255)
|
|
||||||
)
|
|
||||||
draw = ImageDraw.Draw(img, mode='RGBA')
|
draw = ImageDraw.Draw(img, mode='RGBA')
|
||||||
|
|
||||||
self.ap.logger.debug('正在绘制图片...')
|
self.ap.logger.debug('正在绘制图片...')
|
||||||
|
|||||||
@@ -49,9 +49,7 @@ class LongTextStrategy(metaclass=abc.ABCMeta):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
async def process(
|
async def process(self, message: str, query: core_entities.Query) -> list[platform_message.MessageComponent]:
|
||||||
self, message: str, query: core_entities.Query
|
|
||||||
) -> list[platform_message.MessageComponent]:
|
|
||||||
"""处理长文本
|
"""处理长文本
|
||||||
|
|
||||||
在 platform.json 中配置 long-text-process 字段,只要 文本长度超过了 threshold 就会调用此方法
|
在 platform.json 中配置 long-text-process 字段,只要 文本长度超过了 threshold 就会调用此方法
|
||||||
|
|||||||
@@ -29,12 +29,8 @@ class ConversationMessageTruncator(stage.PipelineStage):
|
|||||||
else:
|
else:
|
||||||
raise ValueError(f'未知的截断器: {use_method}')
|
raise ValueError(f'未知的截断器: {use_method}')
|
||||||
|
|
||||||
async def process(
|
async def process(self, query: core_entities.Query, stage_inst_name: str) -> entities.StageProcessResult:
|
||||||
self, query: core_entities.Query, stage_inst_name: str
|
|
||||||
) -> entities.StageProcessResult:
|
|
||||||
"""处理"""
|
"""处理"""
|
||||||
query = await self.trun.truncate(query)
|
query = await self.trun.truncate(query)
|
||||||
|
|
||||||
return entities.StageProcessResult(
|
return entities.StageProcessResult(result_type=entities.ResultType.CONTINUE, new_query=query)
|
||||||
result_type=entities.ResultType.CONTINUE, new_query=query
|
|
||||||
)
|
|
||||||
|
|||||||
+15
-47
@@ -79,26 +79,20 @@ class RuntimePipeline:
|
|||||||
query.pipeline_config = self.pipeline_entity.config
|
query.pipeline_config = self.pipeline_entity.config
|
||||||
await self.process_query(query)
|
await self.process_query(query)
|
||||||
|
|
||||||
async def _check_output(
|
async def _check_output(self, query: entities.Query, result: pipeline_entities.StageProcessResult):
|
||||||
self, query: entities.Query, result: pipeline_entities.StageProcessResult
|
|
||||||
):
|
|
||||||
"""检查输出"""
|
"""检查输出"""
|
||||||
if result.user_notice:
|
if result.user_notice:
|
||||||
# 处理str类型
|
# 处理str类型
|
||||||
|
|
||||||
if isinstance(result.user_notice, str):
|
if isinstance(result.user_notice, str):
|
||||||
result.user_notice = platform_message.MessageChain(
|
result.user_notice = platform_message.MessageChain(platform_message.Plain(result.user_notice))
|
||||||
platform_message.Plain(result.user_notice)
|
|
||||||
)
|
|
||||||
elif isinstance(result.user_notice, list):
|
elif isinstance(result.user_notice, list):
|
||||||
result.user_notice = platform_message.MessageChain(*result.user_notice)
|
result.user_notice = platform_message.MessageChain(*result.user_notice)
|
||||||
|
|
||||||
if query.pipeline_config['output']['misc']['at-sender'] and isinstance(
|
if query.pipeline_config['output']['misc']['at-sender'] and isinstance(
|
||||||
query.message_event, platform_events.GroupMessage
|
query.message_event, platform_events.GroupMessage
|
||||||
):
|
):
|
||||||
result.user_notice.insert(
|
result.user_notice.insert(0, platform_message.At(query.message_event.sender.id))
|
||||||
0, platform_message.At(query.message_event.sender.id)
|
|
||||||
)
|
|
||||||
|
|
||||||
await query.adapter.reply_message(
|
await query.adapter.reply_message(
|
||||||
message_source=query.message_event,
|
message_source=query.message_event,
|
||||||
@@ -150,37 +144,25 @@ class RuntimePipeline:
|
|||||||
result = await result
|
result = await result
|
||||||
|
|
||||||
if isinstance(result, pipeline_entities.StageProcessResult): # 直接返回结果
|
if isinstance(result, pipeline_entities.StageProcessResult): # 直接返回结果
|
||||||
self.ap.logger.debug(
|
self.ap.logger.debug(f'Stage {stage_container.inst_name} processed query {query} res {result}')
|
||||||
f'Stage {stage_container.inst_name} processed query {query} res {result}'
|
|
||||||
)
|
|
||||||
await self._check_output(query, result)
|
await self._check_output(query, result)
|
||||||
|
|
||||||
if result.result_type == pipeline_entities.ResultType.INTERRUPT:
|
if result.result_type == pipeline_entities.ResultType.INTERRUPT:
|
||||||
self.ap.logger.debug(
|
self.ap.logger.debug(f'Stage {stage_container.inst_name} interrupted query {query}')
|
||||||
f'Stage {stage_container.inst_name} interrupted query {query}'
|
|
||||||
)
|
|
||||||
break
|
break
|
||||||
elif result.result_type == pipeline_entities.ResultType.CONTINUE:
|
elif result.result_type == pipeline_entities.ResultType.CONTINUE:
|
||||||
query = result.new_query
|
query = result.new_query
|
||||||
elif isinstance(result, typing.AsyncGenerator): # 生成器
|
elif isinstance(result, typing.AsyncGenerator): # 生成器
|
||||||
self.ap.logger.debug(
|
self.ap.logger.debug(f'Stage {stage_container.inst_name} processed query {query} gen')
|
||||||
f'Stage {stage_container.inst_name} processed query {query} gen'
|
|
||||||
)
|
|
||||||
|
|
||||||
async for sub_result in result:
|
async for sub_result in result:
|
||||||
self.ap.logger.debug(
|
self.ap.logger.debug(f'Stage {stage_container.inst_name} processed query {query} res {sub_result}')
|
||||||
f'Stage {stage_container.inst_name} processed query {query} res {sub_result}'
|
|
||||||
)
|
|
||||||
await self._check_output(query, sub_result)
|
await self._check_output(query, sub_result)
|
||||||
|
|
||||||
if sub_result.result_type == pipeline_entities.ResultType.INTERRUPT:
|
if sub_result.result_type == pipeline_entities.ResultType.INTERRUPT:
|
||||||
self.ap.logger.debug(
|
self.ap.logger.debug(f'Stage {stage_container.inst_name} interrupted query {query}')
|
||||||
f'Stage {stage_container.inst_name} interrupted query {query}'
|
|
||||||
)
|
|
||||||
break
|
break
|
||||||
elif (
|
elif sub_result.result_type == pipeline_entities.ResultType.CONTINUE:
|
||||||
sub_result.result_type == pipeline_entities.ResultType.CONTINUE
|
|
||||||
):
|
|
||||||
query = sub_result.new_query
|
query = sub_result.new_query
|
||||||
await self._execute_from_stage(i + 1, query)
|
await self._execute_from_stage(i + 1, query)
|
||||||
break
|
break
|
||||||
@@ -214,12 +196,8 @@ class RuntimePipeline:
|
|||||||
|
|
||||||
await self._execute_from_stage(0, query)
|
await self._execute_from_stage(0, query)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
inst_name = (
|
inst_name = query.current_stage.inst_name if query.current_stage else 'unknown'
|
||||||
query.current_stage.inst_name if query.current_stage else 'unknown'
|
self.ap.logger.error(f'处理请求时出错 query_id={query.query_id} stage={inst_name} : {e}')
|
||||||
)
|
|
||||||
self.ap.logger.error(
|
|
||||||
f'处理请求时出错 query_id={query.query_id} stage={inst_name} : {e}'
|
|
||||||
)
|
|
||||||
self.ap.logger.debug(f'Traceback: {traceback.format_exc()}')
|
self.ap.logger.debug(f'Traceback: {traceback.format_exc()}')
|
||||||
finally:
|
finally:
|
||||||
self.ap.logger.debug(f'Query {query} processed')
|
self.ap.logger.debug(f'Query {query} processed')
|
||||||
@@ -241,18 +219,14 @@ class PipelineManager:
|
|||||||
self.pipelines = []
|
self.pipelines = []
|
||||||
|
|
||||||
async def initialize(self):
|
async def initialize(self):
|
||||||
self.stage_dict = {
|
self.stage_dict = {name: cls for name, cls in stage.preregistered_stages.items()}
|
||||||
name: cls for name, cls in stage.preregistered_stages.items()
|
|
||||||
}
|
|
||||||
|
|
||||||
await self.load_pipelines_from_db()
|
await self.load_pipelines_from_db()
|
||||||
|
|
||||||
async def load_pipelines_from_db(self):
|
async def load_pipelines_from_db(self):
|
||||||
self.ap.logger.info('Loading pipelines from db...')
|
self.ap.logger.info('Loading pipelines from db...')
|
||||||
|
|
||||||
result = await self.ap.persistence_mgr.execute_async(
|
result = await self.ap.persistence_mgr.execute_async(sqlalchemy.select(persistence_pipeline.LegacyPipeline))
|
||||||
sqlalchemy.select(persistence_pipeline.LegacyPipeline)
|
|
||||||
)
|
|
||||||
|
|
||||||
pipelines = result.all()
|
pipelines = result.all()
|
||||||
|
|
||||||
@@ -267,20 +241,14 @@ class PipelineManager:
|
|||||||
| dict,
|
| dict,
|
||||||
):
|
):
|
||||||
if isinstance(pipeline_entity, sqlalchemy.Row):
|
if isinstance(pipeline_entity, sqlalchemy.Row):
|
||||||
pipeline_entity = persistence_pipeline.LegacyPipeline(
|
pipeline_entity = persistence_pipeline.LegacyPipeline(**pipeline_entity._mapping)
|
||||||
**pipeline_entity._mapping
|
|
||||||
)
|
|
||||||
elif isinstance(pipeline_entity, dict):
|
elif isinstance(pipeline_entity, dict):
|
||||||
pipeline_entity = persistence_pipeline.LegacyPipeline(**pipeline_entity)
|
pipeline_entity = persistence_pipeline.LegacyPipeline(**pipeline_entity)
|
||||||
|
|
||||||
# initialize stage containers according to pipeline_entity.stages
|
# initialize stage containers according to pipeline_entity.stages
|
||||||
stage_containers: list[StageInstContainer] = []
|
stage_containers: list[StageInstContainer] = []
|
||||||
for stage_name in pipeline_entity.stages:
|
for stage_name in pipeline_entity.stages:
|
||||||
stage_containers.append(
|
stage_containers.append(StageInstContainer(inst_name=stage_name, inst=self.stage_dict[stage_name](self.ap)))
|
||||||
StageInstContainer(
|
|
||||||
inst_name=stage_name, inst=self.stage_dict[stage_name](self.ap)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
for stage_container in stage_containers:
|
for stage_container in stage_containers:
|
||||||
await stage_container.inst.initialize(pipeline_entity.config)
|
await stage_container.inst.initialize(pipeline_entity.config)
|
||||||
|
|||||||
@@ -44,9 +44,7 @@ class PreProcessor(stage.PipelineStage):
|
|||||||
query.use_llm_model = conversation.use_llm_model
|
query.use_llm_model = conversation.use_llm_model
|
||||||
|
|
||||||
query.use_funcs = (
|
query.use_funcs = (
|
||||||
conversation.use_funcs
|
conversation.use_funcs if query.use_llm_model.model_entity.abilities.__contains__('tool_call') else None
|
||||||
if query.use_llm_model.model_entity.abilities.__contains__('tool_call')
|
|
||||||
else None
|
|
||||||
)
|
)
|
||||||
|
|
||||||
query.variables = {
|
query.variables = {
|
||||||
@@ -59,10 +57,9 @@ class PreProcessor(stage.PipelineStage):
|
|||||||
|
|
||||||
# Check if this model supports vision, if not, remove all images
|
# Check if this model supports vision, if not, remove all images
|
||||||
# TODO this checking should be performed in runner, and in this stage, the image should be reserved
|
# TODO this checking should be performed in runner, and in this stage, the image should be reserved
|
||||||
if (
|
if query.pipeline_config['ai']['runner'][
|
||||||
query.pipeline_config['ai']['runner']['runner'] == 'local-agent'
|
'runner'
|
||||||
and not query.use_llm_model.model_entity.abilities.__contains__('vision')
|
] == 'local-agent' and not query.use_llm_model.model_entity.abilities.__contains__('vision'):
|
||||||
):
|
|
||||||
for msg in query.messages:
|
for msg in query.messages:
|
||||||
if isinstance(msg.content, list):
|
if isinstance(msg.content, list):
|
||||||
for me in msg.content:
|
for me in msg.content:
|
||||||
@@ -78,14 +75,11 @@ class PreProcessor(stage.PipelineStage):
|
|||||||
content_list.append(llm_entities.ContentElement.from_text(me.text))
|
content_list.append(llm_entities.ContentElement.from_text(me.text))
|
||||||
plain_text += me.text
|
plain_text += me.text
|
||||||
elif isinstance(me, platform_message.Image):
|
elif isinstance(me, platform_message.Image):
|
||||||
if (
|
if query.pipeline_config['ai']['runner'][
|
||||||
query.pipeline_config['ai']['runner']['runner'] != 'local-agent'
|
'runner'
|
||||||
or query.use_llm_model.model_entity.abilities.__contains__('vision')
|
] != 'local-agent' or query.use_llm_model.model_entity.abilities.__contains__('vision'):
|
||||||
):
|
|
||||||
if me.base64 is not None:
|
if me.base64 is not None:
|
||||||
content_list.append(
|
content_list.append(llm_entities.ContentElement.from_image_base64(me.base64))
|
||||||
llm_entities.ContentElement.from_image_base64(me.base64)
|
|
||||||
)
|
|
||||||
|
|
||||||
query.variables['user_message_text'] = plain_text
|
query.variables['user_message_text'] = plain_text
|
||||||
|
|
||||||
@@ -104,6 +98,4 @@ class PreProcessor(stage.PipelineStage):
|
|||||||
query.prompt.messages = event_ctx.event.default_prompt
|
query.prompt.messages = event_ctx.event.default_prompt
|
||||||
query.messages = event_ctx.event.prompt
|
query.messages = event_ctx.event.prompt
|
||||||
|
|
||||||
return entities.StageProcessResult(
|
return entities.StageProcessResult(result_type=entities.ResultType.CONTINUE, new_query=query)
|
||||||
result_type=entities.ResultType.CONTINUE, new_query=query
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -49,13 +49,9 @@ class ChatMessageHandler(handler.MessageHandler):
|
|||||||
|
|
||||||
query.resp_messages.append(mc)
|
query.resp_messages.append(mc)
|
||||||
|
|
||||||
yield entities.StageProcessResult(
|
yield entities.StageProcessResult(result_type=entities.ResultType.CONTINUE, new_query=query)
|
||||||
result_type=entities.ResultType.CONTINUE, new_query=query
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
yield entities.StageProcessResult(
|
yield entities.StageProcessResult(result_type=entities.ResultType.INTERRUPT, new_query=query)
|
||||||
result_type=entities.ResultType.INTERRUPT, new_query=query
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
if event_ctx.event.alter is not None:
|
if event_ctx.event.alter is not None:
|
||||||
# if isinstance(event_ctx.event, str): # 现在暂时不考虑多模态alter
|
# if isinstance(event_ctx.event, str): # 现在暂时不考虑多模态alter
|
||||||
@@ -69,34 +65,24 @@ class ChatMessageHandler(handler.MessageHandler):
|
|||||||
runner = r(self.ap, query.pipeline_config)
|
runner = r(self.ap, query.pipeline_config)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
raise ValueError(
|
raise ValueError(f'未找到请求运行器: {query.pipeline_config["ai"]["runner"]["runner"]}')
|
||||||
f'未找到请求运行器: {query.pipeline_config["ai"]["runner"]["runner"]}'
|
|
||||||
)
|
|
||||||
|
|
||||||
async for result in runner.run(query):
|
async for result in runner.run(query):
|
||||||
query.resp_messages.append(result)
|
query.resp_messages.append(result)
|
||||||
|
|
||||||
self.ap.logger.info(
|
self.ap.logger.info(f'对话({query.query_id})响应: {self.cut_str(result.readable_str())}')
|
||||||
f'对话({query.query_id})响应: {self.cut_str(result.readable_str())}'
|
|
||||||
)
|
|
||||||
|
|
||||||
if result.content is not None:
|
if result.content is not None:
|
||||||
text_length += len(result.content)
|
text_length += len(result.content)
|
||||||
|
|
||||||
yield entities.StageProcessResult(
|
yield entities.StageProcessResult(result_type=entities.ResultType.CONTINUE, new_query=query)
|
||||||
result_type=entities.ResultType.CONTINUE, new_query=query
|
|
||||||
)
|
|
||||||
|
|
||||||
query.session.using_conversation.messages.append(query.user_message)
|
query.session.using_conversation.messages.append(query.user_message)
|
||||||
query.session.using_conversation.messages.extend(query.resp_messages)
|
query.session.using_conversation.messages.extend(query.resp_messages)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.ap.logger.error(
|
self.ap.logger.error(f'对话({query.query_id})请求失败: {type(e).__name__} {str(e)}')
|
||||||
f'对话({query.query_id})请求失败: {type(e).__name__} {str(e)}'
|
|
||||||
)
|
|
||||||
|
|
||||||
hide_exception_info = query.pipeline_config['output']['misc'][
|
hide_exception_info = query.pipeline_config['output']['misc']['hide-exception']
|
||||||
'hide-exception'
|
|
||||||
]
|
|
||||||
|
|
||||||
yield entities.StageProcessResult(
|
yield entities.StageProcessResult(
|
||||||
result_type=entities.ResultType.INTERRUPT,
|
result_type=entities.ResultType.INTERRUPT,
|
||||||
|
|||||||
@@ -21,10 +21,7 @@ class CommandHandler(handler.MessageHandler):
|
|||||||
|
|
||||||
privilege = 1
|
privilege = 1
|
||||||
|
|
||||||
if (
|
if f'{query.launcher_type.value}_{query.launcher_id}' in self.ap.instance_config.data['admins']:
|
||||||
f'{query.launcher_type.value}_{query.launcher_id}'
|
|
||||||
in self.ap.instance_config.data['admins']
|
|
||||||
):
|
|
||||||
privilege = 2
|
privilege = 2
|
||||||
|
|
||||||
spt = command_text.split(' ')
|
spt = command_text.split(' ')
|
||||||
@@ -54,25 +51,17 @@ class CommandHandler(handler.MessageHandler):
|
|||||||
|
|
||||||
query.resp_messages.append(mc)
|
query.resp_messages.append(mc)
|
||||||
|
|
||||||
yield entities.StageProcessResult(
|
yield entities.StageProcessResult(result_type=entities.ResultType.CONTINUE, new_query=query)
|
||||||
result_type=entities.ResultType.CONTINUE, new_query=query
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
yield entities.StageProcessResult(
|
yield entities.StageProcessResult(result_type=entities.ResultType.INTERRUPT, new_query=query)
|
||||||
result_type=entities.ResultType.INTERRUPT, new_query=query
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if event_ctx.event.alter is not None:
|
if event_ctx.event.alter is not None:
|
||||||
query.message_chain = platform_message.MessageChain(
|
query.message_chain = platform_message.MessageChain([platform_message.Plain(event_ctx.event.alter)])
|
||||||
[platform_message.Plain(event_ctx.event.alter)]
|
|
||||||
)
|
|
||||||
|
|
||||||
session = await self.ap.sess_mgr.get_session(query)
|
session = await self.ap.sess_mgr.get_session(query)
|
||||||
|
|
||||||
async for ret in self.ap.cmd_mgr.execute(
|
async for ret in self.ap.cmd_mgr.execute(command_text=command_text, query=query, session=session):
|
||||||
command_text=command_text, query=query, session=session
|
|
||||||
):
|
|
||||||
if ret.error is not None:
|
if ret.error is not None:
|
||||||
query.resp_messages.append(
|
query.resp_messages.append(
|
||||||
llm_entities.Message(
|
llm_entities.Message(
|
||||||
@@ -81,13 +70,9 @@ class CommandHandler(handler.MessageHandler):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.ap.logger.info(
|
self.ap.logger.info(f'命令({query.query_id})报错: {self.cut_str(str(ret.error))}')
|
||||||
f'命令({query.query_id})报错: {self.cut_str(str(ret.error))}'
|
|
||||||
)
|
|
||||||
|
|
||||||
yield entities.StageProcessResult(
|
yield entities.StageProcessResult(result_type=entities.ResultType.CONTINUE, new_query=query)
|
||||||
result_type=entities.ResultType.CONTINUE, new_query=query
|
|
||||||
)
|
|
||||||
elif ret.text is not None or ret.image_url is not None:
|
elif ret.text is not None or ret.image_url is not None:
|
||||||
content: list[llm_entities.ContentElement] = []
|
content: list[llm_entities.ContentElement] = []
|
||||||
|
|
||||||
@@ -95,9 +80,7 @@ class CommandHandler(handler.MessageHandler):
|
|||||||
content.append(llm_entities.ContentElement.from_text(ret.text))
|
content.append(llm_entities.ContentElement.from_text(ret.text))
|
||||||
|
|
||||||
if ret.image_url is not None:
|
if ret.image_url is not None:
|
||||||
content.append(
|
content.append(llm_entities.ContentElement.from_image_url(ret.image_url))
|
||||||
llm_entities.ContentElement.from_image_url(ret.image_url)
|
|
||||||
)
|
|
||||||
|
|
||||||
query.resp_messages.append(
|
query.resp_messages.append(
|
||||||
llm_entities.Message(
|
llm_entities.Message(
|
||||||
@@ -108,10 +91,6 @@ class CommandHandler(handler.MessageHandler):
|
|||||||
|
|
||||||
self.ap.logger.info(f'命令返回: {self.cut_str(str(content[0]))}')
|
self.ap.logger.info(f'命令返回: {self.cut_str(str(content[0]))}')
|
||||||
|
|
||||||
yield entities.StageProcessResult(
|
yield entities.StageProcessResult(result_type=entities.ResultType.CONTINUE, new_query=query)
|
||||||
result_type=entities.ResultType.CONTINUE, new_query=query
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
yield entities.StageProcessResult(
|
yield entities.StageProcessResult(result_type=entities.ResultType.INTERRUPT, new_query=query)
|
||||||
result_type=entities.ResultType.INTERRUPT, new_query=query
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -72,9 +72,7 @@ class FixedWindowAlgo(algo.ReteLimitAlgo):
|
|||||||
if count >= limitation:
|
if count >= limitation:
|
||||||
if query.pipeline_config['safety']['rate-limit']['strategy'] == 'drop':
|
if query.pipeline_config['safety']['rate-limit']['strategy'] == 'drop':
|
||||||
return False
|
return False
|
||||||
elif (
|
elif query.pipeline_config['safety']['rate-limit']['strategy'] == 'wait':
|
||||||
query.pipeline_config['safety']['rate-limit']['strategy'] == 'wait'
|
|
||||||
):
|
|
||||||
# 等待下一窗口
|
# 等待下一窗口
|
||||||
await asyncio.sleep(window_size - time.time() % window_size)
|
await asyncio.sleep(window_size - time.time() % window_size)
|
||||||
|
|
||||||
|
|||||||
@@ -15,9 +15,7 @@ from ...core import entities as core_entities
|
|||||||
class SendResponseBackStage(stage.PipelineStage):
|
class SendResponseBackStage(stage.PipelineStage):
|
||||||
"""发送响应消息"""
|
"""发送响应消息"""
|
||||||
|
|
||||||
async def process(
|
async def process(self, query: core_entities.Query, stage_inst_name: str) -> entities.StageProcessResult:
|
||||||
self, query: core_entities.Query, stage_inst_name: str
|
|
||||||
) -> entities.StageProcessResult:
|
|
||||||
"""处理"""
|
"""处理"""
|
||||||
|
|
||||||
random_range = (
|
random_range = (
|
||||||
@@ -34,9 +32,7 @@ class SendResponseBackStage(stage.PipelineStage):
|
|||||||
if query.pipeline_config['output']['misc']['at-sender'] and isinstance(
|
if query.pipeline_config['output']['misc']['at-sender'] and isinstance(
|
||||||
query.message_event, platform_events.GroupMessage
|
query.message_event, platform_events.GroupMessage
|
||||||
):
|
):
|
||||||
query.resp_message_chain[-1].insert(
|
query.resp_message_chain[-1].insert(0, platform_message.At(query.message_event.sender.id))
|
||||||
0, platform_message.At(query.message_event.sender.id)
|
|
||||||
)
|
|
||||||
|
|
||||||
quote_origin = query.pipeline_config['output']['misc']['quote-origin']
|
quote_origin = query.pipeline_config['output']['misc']['quote-origin']
|
||||||
|
|
||||||
@@ -46,6 +42,4 @@ class SendResponseBackStage(stage.PipelineStage):
|
|||||||
quote_origin=quote_origin,
|
quote_origin=quote_origin,
|
||||||
)
|
)
|
||||||
|
|
||||||
return entities.StageProcessResult(
|
return entities.StageProcessResult(result_type=entities.ResultType.CONTINUE, new_query=query)
|
||||||
result_type=entities.ResultType.CONTINUE, new_query=query
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -32,13 +32,9 @@ class GroupRespondRuleCheckStage(stage.PipelineStage):
|
|||||||
await rule_inst.initialize()
|
await rule_inst.initialize()
|
||||||
self.rule_matchers.append(rule_inst)
|
self.rule_matchers.append(rule_inst)
|
||||||
|
|
||||||
async def process(
|
async def process(self, query: core_entities.Query, stage_inst_name: str) -> entities.StageProcessResult:
|
||||||
self, query: core_entities.Query, stage_inst_name: str
|
|
||||||
) -> entities.StageProcessResult:
|
|
||||||
if query.launcher_type.value != 'group': # 只处理群消息
|
if query.launcher_type.value != 'group': # 只处理群消息
|
||||||
return entities.StageProcessResult(
|
return entities.StageProcessResult(result_type=entities.ResultType.CONTINUE, new_query=query)
|
||||||
result_type=entities.ResultType.CONTINUE, new_query=query
|
|
||||||
)
|
|
||||||
|
|
||||||
rules = query.pipeline_config['trigger']['group-respond-rules']
|
rules = query.pipeline_config['trigger']['group-respond-rules']
|
||||||
|
|
||||||
@@ -49,9 +45,7 @@ class GroupRespondRuleCheckStage(stage.PipelineStage):
|
|||||||
# use_rule = rules[str(query.launcher_id)]
|
# use_rule = rules[str(query.launcher_id)]
|
||||||
|
|
||||||
for rule_matcher in self.rule_matchers: # 任意一个匹配就放行
|
for rule_matcher in self.rule_matchers: # 任意一个匹配就放行
|
||||||
res = await rule_matcher.match(
|
res = await rule_matcher.match(str(query.message_chain), query.message_chain, use_rule, query)
|
||||||
str(query.message_chain), query.message_chain, use_rule, query
|
|
||||||
)
|
|
||||||
if res.matching:
|
if res.matching:
|
||||||
query.message_chain = res.replacement
|
query.message_chain = res.replacement
|
||||||
|
|
||||||
@@ -60,6 +54,4 @@ class GroupRespondRuleCheckStage(stage.PipelineStage):
|
|||||||
new_query=query,
|
new_query=query,
|
||||||
)
|
)
|
||||||
|
|
||||||
return entities.StageProcessResult(
|
return entities.StageProcessResult(result_type=entities.ResultType.INTERRUPT, new_query=query)
|
||||||
result_type=entities.ResultType.INTERRUPT, new_query=query
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -16,10 +16,7 @@ class AtBotRule(rule_model.GroupRespondRule):
|
|||||||
rule_dict: dict,
|
rule_dict: dict,
|
||||||
query: core_entities.Query,
|
query: core_entities.Query,
|
||||||
) -> entities.RuleJudgeResult:
|
) -> entities.RuleJudgeResult:
|
||||||
if (
|
if message_chain.has(platform_message.At(query.adapter.bot_account_id)) and rule_dict['at']:
|
||||||
message_chain.has(platform_message.At(query.adapter.bot_account_id))
|
|
||||||
and rule_dict['at']
|
|
||||||
):
|
|
||||||
message_chain.remove(platform_message.At(query.adapter.bot_account_id))
|
message_chain.remove(platform_message.At(query.adapter.bot_account_id))
|
||||||
|
|
||||||
if message_chain.has(
|
if message_chain.has(
|
||||||
|
|||||||
@@ -18,6 +18,4 @@ class RandomRespRule(rule_model.GroupRespondRule):
|
|||||||
) -> entities.RuleJudgeResult:
|
) -> entities.RuleJudgeResult:
|
||||||
random_rate = rule_dict['random']
|
random_rate = rule_dict['random']
|
||||||
|
|
||||||
return entities.RuleJudgeResult(
|
return entities.RuleJudgeResult(matching=random.random() < random_rate, replacement=message_chain)
|
||||||
matching=random.random() < random_rate, replacement=message_chain
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -34,29 +34,19 @@ class ResponseWrapper(stage.PipelineStage):
|
|||||||
if isinstance(query.resp_messages[-1], platform_message.MessageChain):
|
if isinstance(query.resp_messages[-1], platform_message.MessageChain):
|
||||||
query.resp_message_chain.append(query.resp_messages[-1])
|
query.resp_message_chain.append(query.resp_messages[-1])
|
||||||
|
|
||||||
yield entities.StageProcessResult(
|
yield entities.StageProcessResult(result_type=entities.ResultType.CONTINUE, new_query=query)
|
||||||
result_type=entities.ResultType.CONTINUE, new_query=query
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if query.resp_messages[-1].role == 'command':
|
if query.resp_messages[-1].role == 'command':
|
||||||
query.resp_message_chain.append(
|
query.resp_message_chain.append(
|
||||||
query.resp_messages[-1].get_content_platform_message_chain(
|
query.resp_messages[-1].get_content_platform_message_chain(prefix_text='[bot] ')
|
||||||
prefix_text='[bot] '
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
yield entities.StageProcessResult(
|
yield entities.StageProcessResult(result_type=entities.ResultType.CONTINUE, new_query=query)
|
||||||
result_type=entities.ResultType.CONTINUE, new_query=query
|
|
||||||
)
|
|
||||||
elif query.resp_messages[-1].role == 'plugin':
|
elif query.resp_messages[-1].role == 'plugin':
|
||||||
query.resp_message_chain.append(
|
query.resp_message_chain.append(query.resp_messages[-1].get_content_platform_message_chain())
|
||||||
query.resp_messages[-1].get_content_platform_message_chain()
|
|
||||||
)
|
|
||||||
|
|
||||||
yield entities.StageProcessResult(
|
yield entities.StageProcessResult(result_type=entities.ResultType.CONTINUE, new_query=query)
|
||||||
result_type=entities.ResultType.CONTINUE, new_query=query
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
if query.resp_messages[-1].role == 'assistant':
|
if query.resp_messages[-1].role == 'assistant':
|
||||||
result = query.resp_messages[-1]
|
result = query.resp_messages[-1]
|
||||||
@@ -77,9 +67,7 @@ class ResponseWrapper(stage.PipelineStage):
|
|||||||
prefix='',
|
prefix='',
|
||||||
response_text=reply_text,
|
response_text=reply_text,
|
||||||
finish_reason='stop',
|
finish_reason='stop',
|
||||||
funcs_called=[
|
funcs_called=[fc.function.name for fc in result.tool_calls]
|
||||||
fc.function.name for fc in result.tool_calls
|
|
||||||
]
|
|
||||||
if result.tool_calls is not None
|
if result.tool_calls is not None
|
||||||
else [],
|
else [],
|
||||||
query=query,
|
query=query,
|
||||||
@@ -92,36 +80,26 @@ class ResponseWrapper(stage.PipelineStage):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if event_ctx.event.reply is not None:
|
if event_ctx.event.reply is not None:
|
||||||
query.resp_message_chain.append(
|
query.resp_message_chain.append(platform_message.MessageChain(event_ctx.event.reply))
|
||||||
platform_message.MessageChain(event_ctx.event.reply)
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
query.resp_message_chain.append(
|
query.resp_message_chain.append(result.get_content_platform_message_chain())
|
||||||
result.get_content_platform_message_chain()
|
|
||||||
)
|
|
||||||
|
|
||||||
yield entities.StageProcessResult(
|
yield entities.StageProcessResult(
|
||||||
result_type=entities.ResultType.CONTINUE,
|
result_type=entities.ResultType.CONTINUE,
|
||||||
new_query=query,
|
new_query=query,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if result.tool_calls is not None and len(result.tool_calls) > 0: # 有函数调用
|
||||||
result.tool_calls is not None and len(result.tool_calls) > 0
|
|
||||||
): # 有函数调用
|
|
||||||
function_names = [tc.function.name for tc in result.tool_calls]
|
function_names = [tc.function.name for tc in result.tool_calls]
|
||||||
|
|
||||||
reply_text = f'调用函数 {".".join(function_names)}...'
|
reply_text = f'调用函数 {".".join(function_names)}...'
|
||||||
|
|
||||||
query.resp_message_chain.append(
|
query.resp_message_chain.append(
|
||||||
platform_message.MessageChain(
|
platform_message.MessageChain([platform_message.Plain(reply_text)])
|
||||||
[platform_message.Plain(reply_text)]
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if query.pipeline_config['output']['misc'][
|
if query.pipeline_config['output']['misc']['track-function-calls']:
|
||||||
'track-function-calls'
|
|
||||||
]:
|
|
||||||
event_ctx = await self.ap.plugin_mgr.emit_event(
|
event_ctx = await self.ap.plugin_mgr.emit_event(
|
||||||
event=events.NormalMessageResponded(
|
event=events.NormalMessageResponded(
|
||||||
launcher_type=query.launcher_type.value,
|
launcher_type=query.launcher_type.value,
|
||||||
@@ -131,9 +109,7 @@ class ResponseWrapper(stage.PipelineStage):
|
|||||||
prefix='',
|
prefix='',
|
||||||
response_text=reply_text,
|
response_text=reply_text,
|
||||||
finish_reason='stop',
|
finish_reason='stop',
|
||||||
funcs_called=[
|
funcs_called=[fc.function.name for fc in result.tool_calls]
|
||||||
fc.function.name for fc in result.tool_calls
|
|
||||||
]
|
|
||||||
if result.tool_calls is not None
|
if result.tool_calls is not None
|
||||||
else [],
|
else [],
|
||||||
query=query,
|
query=query,
|
||||||
@@ -148,16 +124,12 @@ class ResponseWrapper(stage.PipelineStage):
|
|||||||
else:
|
else:
|
||||||
if event_ctx.event.reply is not None:
|
if event_ctx.event.reply is not None:
|
||||||
query.resp_message_chain.append(
|
query.resp_message_chain.append(
|
||||||
platform_message.MessageChain(
|
platform_message.MessageChain(event_ctx.event.reply)
|
||||||
event_ctx.event.reply
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
query.resp_message_chain.append(
|
query.resp_message_chain.append(
|
||||||
platform_message.MessageChain(
|
platform_message.MessageChain([platform_message.Plain(reply_text)])
|
||||||
[platform_message.Plain(reply_text)]
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
yield entities.StageProcessResult(
|
yield entities.StageProcessResult(
|
||||||
|
|||||||
@@ -32,9 +32,7 @@ class MessagePlatformAdapter(metaclass=abc.ABCMeta):
|
|||||||
self.config = config
|
self.config = config
|
||||||
self.ap = ap
|
self.ap = ap
|
||||||
|
|
||||||
async def send_message(
|
async def send_message(self, target_type: str, target_id: str, message: platform_message.MessageChain):
|
||||||
self, target_type: str, target_id: str, message: platform_message.MessageChain
|
|
||||||
):
|
|
||||||
"""主动发送消息
|
"""主动发送消息
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -66,9 +64,7 @@ class MessagePlatformAdapter(metaclass=abc.ABCMeta):
|
|||||||
def register_listener(
|
def register_listener(
|
||||||
self,
|
self,
|
||||||
event_type: typing.Type[platform_message.Event],
|
event_type: typing.Type[platform_message.Event],
|
||||||
callback: typing.Callable[
|
callback: typing.Callable[[platform_message.Event, MessagePlatformAdapter], None],
|
||||||
[platform_message.Event, MessagePlatformAdapter], None
|
|
||||||
],
|
|
||||||
):
|
):
|
||||||
"""注册事件监听器
|
"""注册事件监听器
|
||||||
|
|
||||||
@@ -81,9 +77,7 @@ class MessagePlatformAdapter(metaclass=abc.ABCMeta):
|
|||||||
def unregister_listener(
|
def unregister_listener(
|
||||||
self,
|
self,
|
||||||
event_type: typing.Type[platform_message.Event],
|
event_type: typing.Type[platform_message.Event],
|
||||||
callback: typing.Callable[
|
callback: typing.Callable[[platform_message.Event, MessagePlatformAdapter], None],
|
||||||
[platform_message.Event, MessagePlatformAdapter], None
|
|
||||||
],
|
|
||||||
):
|
):
|
||||||
"""注销事件监听器
|
"""注销事件监听器
|
||||||
|
|
||||||
|
|||||||
+6
-18
@@ -132,14 +132,10 @@ class PlatformManager:
|
|||||||
self.adapter_dict = {}
|
self.adapter_dict = {}
|
||||||
|
|
||||||
async def initialize(self):
|
async def initialize(self):
|
||||||
self.adapter_components = self.ap.discover.get_components_by_kind(
|
self.adapter_components = self.ap.discover.get_components_by_kind('MessagePlatformAdapter')
|
||||||
'MessagePlatformAdapter'
|
|
||||||
)
|
|
||||||
adapter_dict: dict[str, type[msadapter.MessagePlatformAdapter]] = {}
|
adapter_dict: dict[str, type[msadapter.MessagePlatformAdapter]] = {}
|
||||||
for component in self.adapter_components:
|
for component in self.adapter_components:
|
||||||
adapter_dict[component.metadata.name] = (
|
adapter_dict[component.metadata.name] = component.get_python_component_class()
|
||||||
component.get_python_component_class()
|
|
||||||
)
|
|
||||||
self.adapter_dict = adapter_dict
|
self.adapter_dict = adapter_dict
|
||||||
|
|
||||||
await self.load_bots_from_db()
|
await self.load_bots_from_db()
|
||||||
@@ -152,9 +148,7 @@ class PlatformManager:
|
|||||||
|
|
||||||
self.bots = []
|
self.bots = []
|
||||||
|
|
||||||
result = await self.ap.persistence_mgr.execute_async(
|
result = await self.ap.persistence_mgr.execute_async(sqlalchemy.select(persistence_bot.Bot))
|
||||||
sqlalchemy.select(persistence_bot.Bot)
|
|
||||||
)
|
|
||||||
|
|
||||||
bots = result.all()
|
bots = result.all()
|
||||||
|
|
||||||
@@ -172,13 +166,9 @@ class PlatformManager:
|
|||||||
elif isinstance(bot_entity, dict):
|
elif isinstance(bot_entity, dict):
|
||||||
bot_entity = persistence_bot.Bot(**bot_entity)
|
bot_entity = persistence_bot.Bot(**bot_entity)
|
||||||
|
|
||||||
adapter_inst = self.adapter_dict[bot_entity.adapter](
|
adapter_inst = self.adapter_dict[bot_entity.adapter](bot_entity.adapter_config, self.ap)
|
||||||
bot_entity.adapter_config, self.ap
|
|
||||||
)
|
|
||||||
|
|
||||||
runtime_bot = RuntimeBot(
|
runtime_bot = RuntimeBot(ap=self.ap, bot_entity=bot_entity, adapter=adapter_inst)
|
||||||
ap=self.ap, bot_entity=bot_entity, adapter=adapter_inst
|
|
||||||
)
|
|
||||||
|
|
||||||
await runtime_bot.initialize()
|
await runtime_bot.initialize()
|
||||||
|
|
||||||
@@ -209,9 +199,7 @@ class PlatformManager:
|
|||||||
return component.to_plain_dict()
|
return component.to_plain_dict()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_available_adapter_manifest_by_name(
|
def get_available_adapter_manifest_by_name(self, name: str) -> engine.Component | None:
|
||||||
self, name: str
|
|
||||||
) -> engine.Component | None:
|
|
||||||
for component in self.adapter_components:
|
for component in self.adapter_components:
|
||||||
if component.metadata.name == name:
|
if component.metadata.name == name:
|
||||||
return component
|
return component
|
||||||
|
|||||||
@@ -58,13 +58,7 @@ class AiocqhttpMessageConverter(adapter.MessageConverter):
|
|||||||
msg_list.append(aiocqhttp.MessageSegment.record(msg.path))
|
msg_list.append(aiocqhttp.MessageSegment.record(msg.path))
|
||||||
elif type(msg) is platform_message.Forward:
|
elif type(msg) is platform_message.Forward:
|
||||||
for node in msg.node_list:
|
for node in msg.node_list:
|
||||||
msg_list.extend(
|
msg_list.extend((await AiocqhttpMessageConverter.yiri2target(node.message_chain))[0])
|
||||||
(
|
|
||||||
await AiocqhttpMessageConverter.yiri2target(
|
|
||||||
node.message_chain
|
|
||||||
)
|
|
||||||
)[0]
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
msg_list.append(aiocqhttp.MessageSegment.text(str(msg)))
|
msg_list.append(aiocqhttp.MessageSegment.text(str(msg)))
|
||||||
@@ -77,9 +71,7 @@ class AiocqhttpMessageConverter(adapter.MessageConverter):
|
|||||||
|
|
||||||
yiri_msg_list = []
|
yiri_msg_list = []
|
||||||
|
|
||||||
yiri_msg_list.append(
|
yiri_msg_list.append(platform_message.Source(id=message_id, time=datetime.datetime.now()))
|
||||||
platform_message.Source(id=message_id, time=datetime.datetime.now())
|
|
||||||
)
|
|
||||||
|
|
||||||
for msg in message:
|
for msg in message:
|
||||||
if msg.type == 'at':
|
if msg.type == 'at':
|
||||||
@@ -94,14 +86,8 @@ class AiocqhttpMessageConverter(adapter.MessageConverter):
|
|||||||
elif msg.type == 'text':
|
elif msg.type == 'text':
|
||||||
yiri_msg_list.append(platform_message.Plain(text=msg.data['text']))
|
yiri_msg_list.append(platform_message.Plain(text=msg.data['text']))
|
||||||
elif msg.type == 'image':
|
elif msg.type == 'image':
|
||||||
image_base64, image_format = await image.qq_image_url_to_base64(
|
image_base64, image_format = await image.qq_image_url_to_base64(msg.data['url'])
|
||||||
msg.data['url']
|
yiri_msg_list.append(platform_message.Image(base64=f'data:image/{image_format};base64,{image_base64}'))
|
||||||
)
|
|
||||||
yiri_msg_list.append(
|
|
||||||
platform_message.Image(
|
|
||||||
base64=f'data:image/{image_format};base64,{image_base64}'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
chain = platform_message.MessageChain(yiri_msg_list)
|
chain = platform_message.MessageChain(yiri_msg_list)
|
||||||
|
|
||||||
@@ -115,9 +101,7 @@ class AiocqhttpEventConverter(adapter.EventConverter):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def target2yiri(event: aiocqhttp.Event):
|
async def target2yiri(event: aiocqhttp.Event):
|
||||||
yiri_chain = await AiocqhttpMessageConverter.target2yiri(
|
yiri_chain = await AiocqhttpMessageConverter.target2yiri(event.message, event.message_id)
|
||||||
event.message, event.message_id
|
|
||||||
)
|
|
||||||
|
|
||||||
if event.message_type == 'group':
|
if event.message_type == 'group':
|
||||||
permission = 'MEMBER'
|
permission = 'MEMBER'
|
||||||
@@ -137,9 +121,7 @@ class AiocqhttpEventConverter(adapter.EventConverter):
|
|||||||
name=event.sender['nickname'],
|
name=event.sender['nickname'],
|
||||||
permission=platform_entities.Permission.Member,
|
permission=platform_entities.Permission.Member,
|
||||||
),
|
),
|
||||||
special_title=event.sender['title']
|
special_title=event.sender['title'] if 'title' in event.sender else '',
|
||||||
if 'title' in event.sender
|
|
||||||
else '',
|
|
||||||
join_timestamp=0,
|
join_timestamp=0,
|
||||||
last_speak_timestamp=0,
|
last_speak_timestamp=0,
|
||||||
mute_time_remaining=0,
|
mute_time_remaining=0,
|
||||||
@@ -191,9 +173,7 @@ class AiocqhttpAdapter(adapter.MessagePlatformAdapter):
|
|||||||
else:
|
else:
|
||||||
self.bot = aiocqhttp.CQHttp()
|
self.bot = aiocqhttp.CQHttp()
|
||||||
|
|
||||||
async def send_message(
|
async def send_message(self, target_type: str, target_id: str, message: platform_message.MessageChain):
|
||||||
self, target_type: str, target_id: str, message: platform_message.MessageChain
|
|
||||||
):
|
|
||||||
aiocq_msg = (await AiocqhttpMessageConverter.yiri2target(message))[0]
|
aiocq_msg = (await AiocqhttpMessageConverter.yiri2target(message))[0]
|
||||||
|
|
||||||
if target_type == 'group':
|
if target_type == 'group':
|
||||||
@@ -207,14 +187,10 @@ class AiocqhttpAdapter(adapter.MessagePlatformAdapter):
|
|||||||
message: platform_message.MessageChain,
|
message: platform_message.MessageChain,
|
||||||
quote_origin: bool = False,
|
quote_origin: bool = False,
|
||||||
):
|
):
|
||||||
aiocq_event = await AiocqhttpEventConverter.yiri2target(
|
aiocq_event = await AiocqhttpEventConverter.yiri2target(message_source, self.bot_account_id)
|
||||||
message_source, self.bot_account_id
|
|
||||||
)
|
|
||||||
aiocq_msg = (await AiocqhttpMessageConverter.yiri2target(message))[0]
|
aiocq_msg = (await AiocqhttpMessageConverter.yiri2target(message))[0]
|
||||||
if quote_origin:
|
if quote_origin:
|
||||||
aiocq_msg = (
|
aiocq_msg = aiocqhttp.MessageSegment.reply(aiocq_event.message_id) + aiocq_msg
|
||||||
aiocqhttp.MessageSegment.reply(aiocq_event.message_id) + aiocq_msg
|
|
||||||
)
|
|
||||||
|
|
||||||
return await self.bot.send(aiocq_event, aiocq_msg)
|
return await self.bot.send(aiocq_event, aiocq_msg)
|
||||||
|
|
||||||
@@ -224,16 +200,12 @@ class AiocqhttpAdapter(adapter.MessagePlatformAdapter):
|
|||||||
def register_listener(
|
def register_listener(
|
||||||
self,
|
self,
|
||||||
event_type: typing.Type[platform_events.Event],
|
event_type: typing.Type[platform_events.Event],
|
||||||
callback: typing.Callable[
|
callback: typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None],
|
||||||
[platform_events.Event, adapter.MessagePlatformAdapter], None
|
|
||||||
],
|
|
||||||
):
|
):
|
||||||
async def on_message(event: aiocqhttp.Event):
|
async def on_message(event: aiocqhttp.Event):
|
||||||
self.bot_account_id = event.self_id
|
self.bot_account_id = event.self_id
|
||||||
try:
|
try:
|
||||||
return await callback(
|
return await callback(await self.event_converter.target2yiri(event), self)
|
||||||
await self.event_converter.target2yiri(event), self
|
|
||||||
)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
@@ -245,9 +217,7 @@ class AiocqhttpAdapter(adapter.MessagePlatformAdapter):
|
|||||||
def unregister_listener(
|
def unregister_listener(
|
||||||
self,
|
self,
|
||||||
event_type: typing.Type[platform_events.Event],
|
event_type: typing.Type[platform_events.Event],
|
||||||
callback: typing.Callable[
|
callback: typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None],
|
||||||
[platform_events.Event, adapter.MessagePlatformAdapter], None
|
|
||||||
],
|
|
||||||
):
|
):
|
||||||
return super().unregister_listener(event_type, callback)
|
return super().unregister_listener(event_type, callback)
|
||||||
|
|
||||||
|
|||||||
@@ -22,9 +22,7 @@ class DingTalkMessageConverter(adapter.MessageConverter):
|
|||||||
async def target2yiri(event: DingTalkEvent, bot_name: str):
|
async def target2yiri(event: DingTalkEvent, bot_name: str):
|
||||||
yiri_msg_list = []
|
yiri_msg_list = []
|
||||||
yiri_msg_list.append(
|
yiri_msg_list.append(
|
||||||
platform_message.Source(
|
platform_message.Source(id=event.incoming_message.message_id, time=datetime.datetime.now())
|
||||||
id=event.incoming_message.message_id, time=datetime.datetime.now()
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
for atUser in event.incoming_message.at_users:
|
for atUser in event.incoming_message.at_users:
|
||||||
@@ -133,9 +131,7 @@ class DingTalkAdapter(adapter.MessagePlatformAdapter):
|
|||||||
content = await DingTalkMessageConverter.yiri2target(message)
|
content = await DingTalkMessageConverter.yiri2target(message)
|
||||||
await self.bot.send_message(content, incoming_message)
|
await self.bot.send_message(content, incoming_message)
|
||||||
|
|
||||||
async def send_message(
|
async def send_message(self, target_type: str, target_id: str, message: platform_message.MessageChain):
|
||||||
self, target_type: str, target_id: str, message: platform_message.MessageChain
|
|
||||||
):
|
|
||||||
content = await DingTalkMessageConverter.yiri2target(message)
|
content = await DingTalkMessageConverter.yiri2target(message)
|
||||||
if target_type == 'person':
|
if target_type == 'person':
|
||||||
await self.bot.send_proactive_message_to_one(target_id, content)
|
await self.bot.send_proactive_message_to_one(target_id, content)
|
||||||
@@ -145,16 +141,12 @@ class DingTalkAdapter(adapter.MessagePlatformAdapter):
|
|||||||
def register_listener(
|
def register_listener(
|
||||||
self,
|
self,
|
||||||
event_type: typing.Type[platform_events.Event],
|
event_type: typing.Type[platform_events.Event],
|
||||||
callback: typing.Callable[
|
callback: typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None],
|
||||||
[platform_events.Event, adapter.MessagePlatformAdapter], None
|
|
||||||
],
|
|
||||||
):
|
):
|
||||||
async def on_message(event: DingTalkEvent):
|
async def on_message(event: DingTalkEvent):
|
||||||
try:
|
try:
|
||||||
return await callback(
|
return await callback(
|
||||||
await self.event_converter.target2yiri(
|
await self.event_converter.target2yiri(event, self.config['robot_name']),
|
||||||
event, self.config['robot_name']
|
|
||||||
),
|
|
||||||
self,
|
self,
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -174,8 +166,6 @@ class DingTalkAdapter(adapter.MessagePlatformAdapter):
|
|||||||
async def unregister_listener(
|
async def unregister_listener(
|
||||||
self,
|
self,
|
||||||
event_type: type,
|
event_type: type,
|
||||||
callback: typing.Callable[
|
callback: typing.Callable[[platform_events.Event, MessagePlatformAdapter], None],
|
||||||
[platform_events.Event, MessagePlatformAdapter], None
|
|
||||||
],
|
|
||||||
):
|
):
|
||||||
return super().unregister_listener(event_type, callback)
|
return super().unregister_listener(event_type, callback)
|
||||||
|
|||||||
@@ -45,9 +45,7 @@ class DiscordMessageConverter(adapter.MessageConverter):
|
|||||||
with open(ele.path, 'rb') as f:
|
with open(ele.path, 'rb') as f:
|
||||||
image_bytes = f.read()
|
image_bytes = f.read()
|
||||||
|
|
||||||
image_files.append(
|
image_files.append(discord.File(fp=image_bytes, filename=f'{uuid.uuid4()}.png'))
|
||||||
discord.File(fp=image_bytes, filename=f'{uuid.uuid4()}.png')
|
|
||||||
)
|
|
||||||
elif isinstance(ele, platform_message.Plain):
|
elif isinstance(ele, platform_message.Plain):
|
||||||
text_string += ele.text
|
text_string += ele.text
|
||||||
elif isinstance(ele, platform_message.Forward):
|
elif isinstance(ele, platform_message.Forward):
|
||||||
@@ -65,9 +63,7 @@ class DiscordMessageConverter(adapter.MessageConverter):
|
|||||||
async def target2yiri(message: discord.Message) -> platform_message.MessageChain:
|
async def target2yiri(message: discord.Message) -> platform_message.MessageChain:
|
||||||
lb_msg_list = []
|
lb_msg_list = []
|
||||||
|
|
||||||
msg_create_time = datetime.datetime.fromtimestamp(
|
msg_create_time = datetime.datetime.fromtimestamp(int(message.created_at.timestamp()))
|
||||||
int(message.created_at.timestamp())
|
|
||||||
)
|
|
||||||
|
|
||||||
lb_msg_list.append(platform_message.Source(id=message.id, time=msg_create_time))
|
lb_msg_list.append(platform_message.Source(id=message.id, time=msg_create_time))
|
||||||
|
|
||||||
@@ -97,11 +93,7 @@ class DiscordMessageConverter(adapter.MessageConverter):
|
|||||||
else:
|
else:
|
||||||
mid_at_component.append(platform_message.At(target=mid_at[2:-1]))
|
mid_at_component.append(platform_message.At(target=mid_at[2:-1]))
|
||||||
|
|
||||||
return (
|
return text_element_recur(text_split[0]) + mid_at_component + text_element_recur(text_split[1])
|
||||||
text_element_recur(text_split[0])
|
|
||||||
+ mid_at_component
|
|
||||||
+ text_element_recur(text_split[1])
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
return [platform_message.Plain(text=text_ele)]
|
return [platform_message.Plain(text=text_ele)]
|
||||||
|
|
||||||
@@ -114,11 +106,7 @@ class DiscordMessageConverter(adapter.MessageConverter):
|
|||||||
image_data = await response.read()
|
image_data = await response.read()
|
||||||
image_base64 = base64.b64encode(image_data).decode('utf-8')
|
image_base64 = base64.b64encode(image_data).decode('utf-8')
|
||||||
image_format = response.headers['Content-Type']
|
image_format = response.headers['Content-Type']
|
||||||
element_list.append(
|
element_list.append(platform_message.Image(base64=f'data:{image_format};base64,{image_base64}'))
|
||||||
platform_message.Image(
|
|
||||||
base64=f'data:{image_format};base64,{image_base64}'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return platform_message.MessageChain(element_list)
|
return platform_message.MessageChain(element_list)
|
||||||
|
|
||||||
@@ -208,9 +196,7 @@ class DiscordAdapter(adapter.MessagePlatformAdapter):
|
|||||||
|
|
||||||
self.bot = MyClient(intents=intents, **args)
|
self.bot = MyClient(intents=intents, **args)
|
||||||
|
|
||||||
async def send_message(
|
async def send_message(self, target_type: str, target_id: str, message: platform_message.MessageChain):
|
||||||
self, target_type: str, target_id: str, message: platform_message.MessageChain
|
|
||||||
):
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def reply_message(
|
async def reply_message(
|
||||||
@@ -243,18 +229,14 @@ class DiscordAdapter(adapter.MessagePlatformAdapter):
|
|||||||
def register_listener(
|
def register_listener(
|
||||||
self,
|
self,
|
||||||
event_type: typing.Type[platform_events.Event],
|
event_type: typing.Type[platform_events.Event],
|
||||||
callback: typing.Callable[
|
callback: typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None],
|
||||||
[platform_events.Event, adapter.MessagePlatformAdapter], None
|
|
||||||
],
|
|
||||||
):
|
):
|
||||||
self.listeners[event_type] = callback
|
self.listeners[event_type] = callback
|
||||||
|
|
||||||
def unregister_listener(
|
def unregister_listener(
|
||||||
self,
|
self,
|
||||||
event_type: typing.Type[platform_events.Event],
|
event_type: typing.Type[platform_events.Event],
|
||||||
callback: typing.Callable[
|
callback: typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None],
|
||||||
[platform_events.Event, adapter.MessagePlatformAdapter], None
|
|
||||||
],
|
|
||||||
):
|
):
|
||||||
self.listeners.pop(event_type)
|
self.listeners.pop(event_type)
|
||||||
|
|
||||||
|
|||||||
@@ -40,14 +40,10 @@ class GewechatMessageConverter(adapter.MessageConverter):
|
|||||||
content_list.append({'type': 'image', 'image': component.url})
|
content_list.append({'type': 'image', 'image': component.url})
|
||||||
|
|
||||||
elif isinstance(component, platform_message.Voice):
|
elif isinstance(component, platform_message.Voice):
|
||||||
content_list.append(
|
content_list.append({'type': 'voice', 'url': component.url, 'length': component.length})
|
||||||
{'type': 'voice', 'url': component.url, 'length': component.length}
|
|
||||||
)
|
|
||||||
elif isinstance(component, platform_message.Forward):
|
elif isinstance(component, platform_message.Forward):
|
||||||
for node in component.node_list:
|
for node in component.node_list:
|
||||||
content_list.extend(
|
content_list.extend(await GewechatMessageConverter.yiri2target(node.message_chain))
|
||||||
await GewechatMessageConverter.yiri2target(node.message_chain)
|
|
||||||
)
|
|
||||||
content_list.append({'type': 'image', 'image': component.url})
|
content_list.append({'type': 'image', 'image': component.url})
|
||||||
elif isinstance(component, platform_message.WeChatMiniPrograms):
|
elif isinstance(component, platform_message.WeChatMiniPrograms):
|
||||||
content_list.append(
|
content_list.append(
|
||||||
@@ -88,44 +84,26 @@ class GewechatMessageConverter(adapter.MessageConverter):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
elif isinstance(component, platform_message.WeChatForwardLink):
|
elif isinstance(component, platform_message.WeChatForwardLink):
|
||||||
content_list.append(
|
content_list.append({'type': 'WeChatForwardLink', 'xml_data': component.xml_data})
|
||||||
{'type': 'WeChatForwardLink', 'xml_data': component.xml_data}
|
|
||||||
)
|
|
||||||
elif isinstance(component, platform_message.Voice):
|
elif isinstance(component, platform_message.Voice):
|
||||||
content_list.append(
|
content_list.append({'type': 'voice', 'url': component.url, 'length': component.length})
|
||||||
{'type': 'voice', 'url': component.url, 'length': component.length}
|
|
||||||
)
|
|
||||||
elif isinstance(component, platform_message.WeChatForwardImage):
|
elif isinstance(component, platform_message.WeChatForwardImage):
|
||||||
content_list.append(
|
content_list.append({'type': 'WeChatForwardImage', 'xml_data': component.xml_data})
|
||||||
{'type': 'WeChatForwardImage', 'xml_data': component.xml_data}
|
|
||||||
)
|
|
||||||
elif isinstance(component, platform_message.WeChatForwardFile):
|
elif isinstance(component, platform_message.WeChatForwardFile):
|
||||||
content_list.append(
|
content_list.append({'type': 'WeChatForwardFile', 'xml_data': component.xml_data})
|
||||||
{'type': 'WeChatForwardFile', 'xml_data': component.xml_data}
|
|
||||||
)
|
|
||||||
elif isinstance(component, platform_message.WeChatAppMsg):
|
elif isinstance(component, platform_message.WeChatAppMsg):
|
||||||
content_list.append(
|
content_list.append({'type': 'WeChatAppMsg', 'app_msg': component.app_msg})
|
||||||
{'type': 'WeChatAppMsg', 'app_msg': component.app_msg}
|
|
||||||
)
|
|
||||||
# 引用消息转发
|
# 引用消息转发
|
||||||
elif isinstance(component, platform_message.WeChatForwardQuote):
|
elif isinstance(component, platform_message.WeChatForwardQuote):
|
||||||
content_list.append(
|
content_list.append({'type': 'WeChatAppMsg', 'app_msg': component.app_msg})
|
||||||
{'type': 'WeChatAppMsg', 'app_msg': component.app_msg}
|
|
||||||
)
|
|
||||||
elif isinstance(component, platform_message.Forward):
|
elif isinstance(component, platform_message.Forward):
|
||||||
for node in component.node_list:
|
for node in component.node_list:
|
||||||
if node.message_chain:
|
if node.message_chain:
|
||||||
content_list.extend(
|
content_list.extend(await GewechatMessageConverter.yiri2target(node.message_chain))
|
||||||
await GewechatMessageConverter.yiri2target(
|
|
||||||
node.message_chain
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return content_list
|
return content_list
|
||||||
|
|
||||||
async def target2yiri(
|
async def target2yiri(self, message: dict, bot_account_id: str) -> platform_message.MessageChain:
|
||||||
self, message: dict, bot_account_id: str
|
|
||||||
) -> platform_message.MessageChain:
|
|
||||||
"""外部消息转平台消息"""
|
"""外部消息转平台消息"""
|
||||||
# 数据预处理
|
# 数据预处理
|
||||||
message_list = []
|
message_list = []
|
||||||
@@ -163,28 +141,20 @@ class GewechatMessageConverter(adapter.MessageConverter):
|
|||||||
|
|
||||||
return platform_message.MessageChain(message_list)
|
return platform_message.MessageChain(message_list)
|
||||||
|
|
||||||
async def _handler_text(
|
async def _handler_text(self, message: Optional[dict], content_no_preifx: str) -> platform_message.MessageChain:
|
||||||
self, message: Optional[dict], content_no_preifx: str
|
|
||||||
) -> platform_message.MessageChain:
|
|
||||||
"""处理文本消息 (msg_type=1)"""
|
"""处理文本消息 (msg_type=1)"""
|
||||||
if message and self._is_group_message(message):
|
if message and self._is_group_message(message):
|
||||||
pattern = r'@\S{1,20}'
|
pattern = r'@\S{1,20}'
|
||||||
content_no_preifx = re.sub(pattern, '', content_no_preifx)
|
content_no_preifx = re.sub(pattern, '', content_no_preifx)
|
||||||
|
|
||||||
return platform_message.MessageChain(
|
return platform_message.MessageChain([platform_message.Plain(content_no_preifx)])
|
||||||
[platform_message.Plain(content_no_preifx)]
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _handler_image(
|
async def _handler_image(self, message: Optional[dict], content_no_preifx: str) -> platform_message.MessageChain:
|
||||||
self, message: Optional[dict], content_no_preifx: str
|
|
||||||
) -> platform_message.MessageChain:
|
|
||||||
"""处理图像消息 (msg_type=3)"""
|
"""处理图像消息 (msg_type=3)"""
|
||||||
try:
|
try:
|
||||||
image_xml = content_no_preifx
|
image_xml = content_no_preifx
|
||||||
if not image_xml:
|
if not image_xml:
|
||||||
return platform_message.MessageChain(
|
return platform_message.MessageChain([platform_message.Unknown('[图片内容为空]')])
|
||||||
[platform_message.Unknown('[图片内容为空]')]
|
|
||||||
)
|
|
||||||
|
|
||||||
base64_str, image_format = await image.get_gewechat_image_base64(
|
base64_str, image_format = await image.get_gewechat_image_base64(
|
||||||
gewechat_url=self.config['gewechat_url'],
|
gewechat_url=self.config['gewechat_url'],
|
||||||
@@ -196,21 +166,15 @@ class GewechatMessageConverter(adapter.MessageConverter):
|
|||||||
)
|
)
|
||||||
|
|
||||||
elements = [
|
elements = [
|
||||||
platform_message.Image(
|
platform_message.Image(base64=f'data:image/{image_format};base64,{base64_str}'),
|
||||||
base64=f'data:image/{image_format};base64,{base64_str}'
|
|
||||||
),
|
|
||||||
platform_message.WeChatForwardImage(xml_data=image_xml), # 微信消息转发
|
platform_message.WeChatForwardImage(xml_data=image_xml), # 微信消息转发
|
||||||
]
|
]
|
||||||
return platform_message.MessageChain(elements)
|
return platform_message.MessageChain(elements)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'处理图片失败: {str(e)}')
|
print(f'处理图片失败: {str(e)}')
|
||||||
return platform_message.MessageChain(
|
return platform_message.MessageChain([platform_message.Unknown('[图片处理失败]')])
|
||||||
[platform_message.Unknown('[图片处理失败]')]
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _handler_voice(
|
async def _handler_voice(self, message: Optional[dict], content_no_preifx: str) -> platform_message.MessageChain:
|
||||||
self, message: Optional[dict], content_no_preifx: str
|
|
||||||
) -> platform_message.MessageChain:
|
|
||||||
"""处理语音消息 (msg_type=34)"""
|
"""处理语音消息 (msg_type=34)"""
|
||||||
message_List = []
|
message_List = []
|
||||||
try:
|
try:
|
||||||
@@ -223,9 +187,7 @@ class GewechatMessageConverter(adapter.MessageConverter):
|
|||||||
return platform_message.MessageChain(message_List)
|
return platform_message.MessageChain(message_List)
|
||||||
|
|
||||||
# 转换为平台支持的语音格式(如 Silk 格式)
|
# 转换为平台支持的语音格式(如 Silk 格式)
|
||||||
voice_element = platform_message.Voice(
|
voice_element = platform_message.Voice(base64=f'data:audio/silk;base64,{audio_base64}')
|
||||||
base64=f'data:audio/silk;base64,{audio_base64}'
|
|
||||||
)
|
|
||||||
message_List.append(voice_element)
|
message_List.append(voice_element)
|
||||||
|
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
@@ -237,9 +199,7 @@ class GewechatMessageConverter(adapter.MessageConverter):
|
|||||||
|
|
||||||
return platform_message.MessageChain(message_List)
|
return platform_message.MessageChain(message_List)
|
||||||
|
|
||||||
async def _handler_compound(
|
async def _handler_compound(self, message: Optional[dict], content_no_preifx: str) -> platform_message.MessageChain:
|
||||||
self, message: Optional[dict], content_no_preifx: str
|
|
||||||
) -> platform_message.MessageChain:
|
|
||||||
"""处理复合消息 (msg_type=49),根据子类型分派"""
|
"""处理复合消息 (msg_type=49),根据子类型分派"""
|
||||||
try:
|
try:
|
||||||
xml_data = ET.fromstring(content_no_preifx)
|
xml_data = ET.fromstring(content_no_preifx)
|
||||||
@@ -254,33 +214,21 @@ class GewechatMessageConverter(adapter.MessageConverter):
|
|||||||
'6': self._handler_compound_file,
|
'6': self._handler_compound_file,
|
||||||
'33': self._handler_compound_mini_program,
|
'33': self._handler_compound_mini_program,
|
||||||
'36': self._handler_compound_mini_program,
|
'36': self._handler_compound_mini_program,
|
||||||
'2000': partial(
|
'2000': partial(self._handler_compound_unsupported, text='[转账消息]'),
|
||||||
self._handler_compound_unsupported, text='[转账消息]'
|
'2001': partial(self._handler_compound_unsupported, text='[红包消息]'),
|
||||||
),
|
'51': partial(self._handler_compound_unsupported, text='[视频号消息]'),
|
||||||
'2001': partial(
|
|
||||||
self._handler_compound_unsupported, text='[红包消息]'
|
|
||||||
),
|
|
||||||
'51': partial(
|
|
||||||
self._handler_compound_unsupported, text='[视频号消息]'
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handler = sub_handler_map.get(
|
handler = sub_handler_map.get(data_type, self._handler_compound_unsupported)
|
||||||
data_type, self._handler_compound_unsupported
|
|
||||||
)
|
|
||||||
return await handler(
|
return await handler(
|
||||||
message=message, # 原始msg
|
message=message, # 原始msg
|
||||||
xml_data=xml_data, # xml数据
|
xml_data=xml_data, # xml数据
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return platform_message.MessageChain(
|
return platform_message.MessageChain([platform_message.Unknown(text=content_no_preifx)])
|
||||||
[platform_message.Unknown(text=content_no_preifx)]
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'解析复合消息失败: {str(e)}')
|
print(f'解析复合消息失败: {str(e)}')
|
||||||
return platform_message.MessageChain(
|
return platform_message.MessageChain([platform_message.Unknown(text=content_no_preifx)])
|
||||||
[platform_message.Unknown(text=content_no_preifx)]
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _handler_compound_quote(
|
async def _handler_compound_quote(
|
||||||
self, message: Optional[dict], xml_data: ET.Element
|
self, message: Optional[dict], xml_data: ET.Element
|
||||||
@@ -296,9 +244,7 @@ class GewechatMessageConverter(adapter.MessageConverter):
|
|||||||
user_data = appmsg_data.findtext('.//title') or ''
|
user_data = appmsg_data.findtext('.//title') or ''
|
||||||
quote_data = appmsg_data.find('.//refermsg').findtext('.//content')
|
quote_data = appmsg_data.find('.//refermsg').findtext('.//content')
|
||||||
message_list.append(
|
message_list.append(
|
||||||
platform_message.WeChatForwardQuote(
|
platform_message.WeChatForwardQuote(app_msg=ET.tostring(appmsg_data, encoding='unicode'))
|
||||||
app_msg=ET.tostring(appmsg_data, encoding='unicode')
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
# quote_data原始的消息
|
# quote_data原始的消息
|
||||||
if quote_data:
|
if quote_data:
|
||||||
@@ -311,22 +257,14 @@ class GewechatMessageConverter(adapter.MessageConverter):
|
|||||||
# 引用消息展开
|
# 引用消息展开
|
||||||
quote_data_xml = ET.fromstring(quote_data)
|
quote_data_xml = ET.fromstring(quote_data)
|
||||||
if quote_data_xml.find('img'):
|
if quote_data_xml.find('img'):
|
||||||
quote_data_message_list.extend(
|
quote_data_message_list.extend(await self._handler_image(None, quote_data))
|
||||||
await self._handler_image(None, quote_data)
|
|
||||||
)
|
|
||||||
elif quote_data_xml.find('voicemsg'):
|
elif quote_data_xml.find('voicemsg'):
|
||||||
quote_data_message_list.extend(
|
quote_data_message_list.extend(await self._handler_voice(None, quote_data))
|
||||||
await self._handler_voice(None, quote_data)
|
|
||||||
)
|
|
||||||
elif quote_data_xml.find('videomsg'):
|
elif quote_data_xml.find('videomsg'):
|
||||||
quote_data_message_list.extend(
|
quote_data_message_list.extend(await self._handler_default(None, quote_data)) # 先不处理
|
||||||
await self._handler_default(None, quote_data)
|
|
||||||
) # 先不处理
|
|
||||||
else:
|
else:
|
||||||
# appmsg
|
# appmsg
|
||||||
quote_data_message_list.extend(
|
quote_data_message_list.extend(await self._handler_compound(None, quote_data))
|
||||||
await self._handler_compound(None, quote_data)
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'处理引用消息异常 expcetion:{e}')
|
print(f'处理引用消息异常 expcetion:{e}')
|
||||||
quote_data_message_list.append(platform_message.Plain(quote_data))
|
quote_data_message_list.append(platform_message.Plain(quote_data))
|
||||||
@@ -351,18 +289,12 @@ class GewechatMessageConverter(adapter.MessageConverter):
|
|||||||
# print(f"quote_message_chain plain [msg_type={comp.type}][message={comp.text}]")
|
# print(f"quote_message_chain plain [msg_type={comp.type}][message={comp.text}]")
|
||||||
return platform_message.MessageChain(message_list)
|
return platform_message.MessageChain(message_list)
|
||||||
|
|
||||||
async def _handler_compound_file(
|
async def _handler_compound_file(self, message: dict, xml_data: ET.Element) -> platform_message.MessageChain:
|
||||||
self, message: dict, xml_data: ET.Element
|
|
||||||
) -> platform_message.MessageChain:
|
|
||||||
"""处理文件消息 (data_type=6)"""
|
"""处理文件消息 (data_type=6)"""
|
||||||
xml_data_str = ET.tostring(xml_data, encoding='unicode')
|
xml_data_str = ET.tostring(xml_data, encoding='unicode')
|
||||||
return platform_message.MessageChain(
|
return platform_message.MessageChain([platform_message.WeChatForwardFile(xml_data=xml_data_str)])
|
||||||
[platform_message.WeChatForwardFile(xml_data=xml_data_str)]
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _handler_compound_link(
|
async def _handler_compound_link(self, message: dict, xml_data: ET.Element) -> platform_message.MessageChain:
|
||||||
self, message: dict, xml_data: ET.Element
|
|
||||||
) -> platform_message.MessageChain:
|
|
||||||
"""处理链接消息(如公众号文章、外部网页)"""
|
"""处理链接消息(如公众号文章、外部网页)"""
|
||||||
message_list = []
|
message_list = []
|
||||||
try:
|
try:
|
||||||
@@ -381,9 +313,7 @@ class GewechatMessageConverter(adapter.MessageConverter):
|
|||||||
# 转发消息
|
# 转发消息
|
||||||
xml_data_str = ET.tostring(xml_data, encoding='unicode')
|
xml_data_str = ET.tostring(xml_data, encoding='unicode')
|
||||||
# print(xml_data_str)
|
# print(xml_data_str)
|
||||||
message_list.append(
|
message_list.append(platform_message.WeChatForwardLink(xml_data=xml_data_str))
|
||||||
platform_message.WeChatForwardLink(xml_data=xml_data_str)
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'解析链接消息失败: {str(e)}')
|
print(f'解析链接消息失败: {str(e)}')
|
||||||
return platform_message.MessageChain(message_list)
|
return platform_message.MessageChain(message_list)
|
||||||
@@ -393,21 +323,15 @@ class GewechatMessageConverter(adapter.MessageConverter):
|
|||||||
) -> platform_message.MessageChain:
|
) -> platform_message.MessageChain:
|
||||||
"""处理小程序消息(如小程序卡片、服务通知)"""
|
"""处理小程序消息(如小程序卡片、服务通知)"""
|
||||||
xml_data_str = ET.tostring(xml_data, encoding='unicode')
|
xml_data_str = ET.tostring(xml_data, encoding='unicode')
|
||||||
return platform_message.MessageChain(
|
return platform_message.MessageChain([platform_message.WeChatForwardMiniPrograms(xml_data=xml_data_str)])
|
||||||
[platform_message.WeChatForwardMiniPrograms(xml_data=xml_data_str)]
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _handler_default(
|
async def _handler_default(self, message: Optional[dict], content_no_preifx: str) -> platform_message.MessageChain:
|
||||||
self, message: Optional[dict], content_no_preifx: str
|
|
||||||
) -> platform_message.MessageChain:
|
|
||||||
"""处理未知消息类型"""
|
"""处理未知消息类型"""
|
||||||
if message:
|
if message:
|
||||||
msg_type = message['Data']['MsgType']
|
msg_type = message['Data']['MsgType']
|
||||||
else:
|
else:
|
||||||
msg_type = ''
|
msg_type = ''
|
||||||
return platform_message.MessageChain(
|
return platform_message.MessageChain([platform_message.Unknown(text=f'[未知消息类型 msg_type:{msg_type}]')])
|
||||||
[platform_message.Unknown(text=f'[未知消息类型 msg_type:{msg_type}]')]
|
|
||||||
)
|
|
||||||
|
|
||||||
def _handler_compound_unsupported(
|
def _handler_compound_unsupported(
|
||||||
self, message: dict, xml_data: str, text: Optional[str] = None
|
self, message: dict, xml_data: str, text: Optional[str] = None
|
||||||
@@ -416,11 +340,7 @@ class GewechatMessageConverter(adapter.MessageConverter):
|
|||||||
if not text:
|
if not text:
|
||||||
text = f'[xml_data={xml_data}]'
|
text = f'[xml_data={xml_data}]'
|
||||||
content_list = []
|
content_list = []
|
||||||
content_list.append(
|
content_list.append(platform_message.Unknown(text=f'[处理未支持复合消息类型[msg_type=49]|{text}'))
|
||||||
platform_message.Unknown(
|
|
||||||
text=f'[处理未支持复合消息类型[msg_type=49]|{text}'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return platform_message.MessageChain(content_list)
|
return platform_message.MessageChain(content_list)
|
||||||
|
|
||||||
@@ -448,9 +368,7 @@ class GewechatMessageConverter(adapter.MessageConverter):
|
|||||||
appmsg_data = xml_data.find('.//appmsg')
|
appmsg_data = xml_data.find('.//appmsg')
|
||||||
tousername = message['Wxid']
|
tousername = message['Wxid']
|
||||||
if appmsg_data: # 接收方: 所属微信的wxid
|
if appmsg_data: # 接收方: 所属微信的wxid
|
||||||
quote_id = appmsg_data.find('.//refermsg').findtext(
|
quote_id = appmsg_data.find('.//refermsg').findtext('.//chatusr') # 引用消息的原发送者
|
||||||
'.//chatusr'
|
|
||||||
) # 引用消息的原发送者
|
|
||||||
ats_bot = ats_bot or (quote_id == tousername)
|
ats_bot = ats_bot or (quote_id == tousername)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'_ats_bot got except: {e}')
|
print(f'_ats_bot got except: {e}')
|
||||||
@@ -458,9 +376,7 @@ class GewechatMessageConverter(adapter.MessageConverter):
|
|||||||
return ats_bot
|
return ats_bot
|
||||||
|
|
||||||
# 提取一下content前面的sender_id, 和去掉前缀的内容
|
# 提取一下content前面的sender_id, 和去掉前缀的内容
|
||||||
def _extract_content_and_sender(
|
def _extract_content_and_sender(self, raw_content: str) -> Tuple[str, Optional[str]]:
|
||||||
self, raw_content: str
|
|
||||||
) -> Tuple[str, Optional[str]]:
|
|
||||||
try:
|
try:
|
||||||
# 检查消息开头,如果有 wxid_sbitaz0mt65n22:\n 则删掉
|
# 检查消息开头,如果有 wxid_sbitaz0mt65n22:\n 则删掉
|
||||||
# add: 有些用户的wxid不是上述格式。换成user_name:
|
# add: 有些用户的wxid不是上述格式。换成user_name:
|
||||||
@@ -490,21 +406,17 @@ class GewechatEventConverter(adapter.EventConverter):
|
|||||||
async def yiri2target(event: platform_events.MessageEvent) -> dict:
|
async def yiri2target(event: platform_events.MessageEvent) -> dict:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def target2yiri(
|
async def target2yiri(self, event: dict, bot_account_id: str) -> platform_events.MessageEvent:
|
||||||
self, event: dict, bot_account_id: str
|
|
||||||
) -> platform_events.MessageEvent:
|
|
||||||
# print(event)
|
# print(event)
|
||||||
# 排除自己发消息回调回答问题
|
# 排除自己发消息回调回答问题
|
||||||
if event['Wxid'] == event['Data']['FromUserName']['string']:
|
if event['Wxid'] == event['Data']['FromUserName']['string']:
|
||||||
return None
|
return None
|
||||||
# 排除公众号以及微信团队消息
|
# 排除公众号以及微信团队消息
|
||||||
if event['Data']['FromUserName']['string'].startswith('gh_') or event['Data'][
|
if event['Data']['FromUserName']['string'].startswith('gh_') or event['Data']['FromUserName'][
|
||||||
'FromUserName'
|
'string'
|
||||||
]['string'].startswith('weixin'):
|
].startswith('weixin'):
|
||||||
return None
|
return None
|
||||||
message_chain = await self.message_converter.target2yiri(
|
message_chain = await self.message_converter.target2yiri(copy.deepcopy(event), bot_account_id)
|
||||||
copy.deepcopy(event), bot_account_id
|
|
||||||
)
|
|
||||||
|
|
||||||
if not message_chain:
|
if not message_chain:
|
||||||
return None
|
return None
|
||||||
@@ -589,9 +501,7 @@ class GeWeChatAdapter(adapter.MessagePlatformAdapter):
|
|||||||
return 'ok'
|
return 'ok'
|
||||||
elif 'TypeName' in data and data['TypeName'] == 'AddMsg':
|
elif 'TypeName' in data and data['TypeName'] == 'AddMsg':
|
||||||
try:
|
try:
|
||||||
event = await self.event_converter.target2yiri(
|
event = await self.event_converter.target2yiri(data.copy(), self.bot_account_id)
|
||||||
data.copy(), self.bot_account_id
|
|
||||||
)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
@@ -600,9 +510,7 @@ class GeWeChatAdapter(adapter.MessagePlatformAdapter):
|
|||||||
|
|
||||||
return 'ok'
|
return 'ok'
|
||||||
|
|
||||||
async def _handle_message(
|
async def _handle_message(self, message: platform_message.MessageChain, target_id: str):
|
||||||
self, message: platform_message.MessageChain, target_id: str
|
|
||||||
):
|
|
||||||
"""统一消息处理核心逻辑"""
|
"""统一消息处理核心逻辑"""
|
||||||
content_list = await self.message_converter.yiri2target(message)
|
content_list = await self.message_converter.yiri2target(message)
|
||||||
at_targets = [item['target'] for item in content_list if item['type'] == 'at']
|
at_targets = [item['target'] for item in content_list if item['type'] == 'at']
|
||||||
@@ -611,9 +519,9 @@ class GeWeChatAdapter(adapter.MessagePlatformAdapter):
|
|||||||
at_targets = at_targets or []
|
at_targets = at_targets or []
|
||||||
member_info = []
|
member_info = []
|
||||||
if at_targets:
|
if at_targets:
|
||||||
member_info = self.bot.get_chatroom_member_detail(
|
member_info = self.bot.get_chatroom_member_detail(self.config['app_id'], target_id, at_targets[::-1])[
|
||||||
self.config['app_id'], target_id, at_targets[::-1]
|
'data'
|
||||||
)['data']
|
]
|
||||||
|
|
||||||
# 处理消息组件
|
# 处理消息组件
|
||||||
for msg in content_list:
|
for msg in content_list:
|
||||||
@@ -694,9 +602,7 @@ class GeWeChatAdapter(adapter.MessagePlatformAdapter):
|
|||||||
self.ap.logger.warning(f'未处理的消息类型: {msg["type"]}')
|
self.ap.logger.warning(f'未处理的消息类型: {msg["type"]}')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
async def send_message(
|
async def send_message(self, target_type: str, target_id: str, message: platform_message.MessageChain):
|
||||||
self, target_type: str, target_id: str, message: platform_message.MessageChain
|
|
||||||
):
|
|
||||||
"""主动发送消息"""
|
"""主动发送消息"""
|
||||||
return await self._handle_message(message, target_id)
|
return await self._handle_message(message, target_id)
|
||||||
|
|
||||||
@@ -708,9 +614,7 @@ class GeWeChatAdapter(adapter.MessagePlatformAdapter):
|
|||||||
):
|
):
|
||||||
"""回复消息"""
|
"""回复消息"""
|
||||||
if message_source.source_platform_object:
|
if message_source.source_platform_object:
|
||||||
target_id = message_source.source_platform_object['Data']['FromUserName'][
|
target_id = message_source.source_platform_object['Data']['FromUserName']['string']
|
||||||
'string'
|
|
||||||
]
|
|
||||||
return await self._handle_message(message, target_id)
|
return await self._handle_message(message, target_id)
|
||||||
|
|
||||||
async def is_muted(self, group_id: int) -> bool:
|
async def is_muted(self, group_id: int) -> bool:
|
||||||
@@ -719,18 +623,14 @@ class GeWeChatAdapter(adapter.MessagePlatformAdapter):
|
|||||||
def register_listener(
|
def register_listener(
|
||||||
self,
|
self,
|
||||||
event_type: typing.Type[platform_events.Event],
|
event_type: typing.Type[platform_events.Event],
|
||||||
callback: typing.Callable[
|
callback: typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None],
|
||||||
[platform_events.Event, adapter.MessagePlatformAdapter], None
|
|
||||||
],
|
|
||||||
):
|
):
|
||||||
self.listeners[event_type] = callback
|
self.listeners[event_type] = callback
|
||||||
|
|
||||||
def unregister_listener(
|
def unregister_listener(
|
||||||
self,
|
self,
|
||||||
event_type: typing.Type[platform_events.Event],
|
event_type: typing.Type[platform_events.Event],
|
||||||
callback: typing.Callable[
|
callback: typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None],
|
||||||
[platform_events.Event, adapter.MessagePlatformAdapter], None
|
|
||||||
],
|
|
||||||
):
|
):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -742,14 +642,10 @@ class GeWeChatAdapter(adapter.MessagePlatformAdapter):
|
|||||||
json={'app_id': self.config['app_id']},
|
json={'app_id': self.config['app_id']},
|
||||||
) as response:
|
) as response:
|
||||||
if response.status != 200:
|
if response.status != 200:
|
||||||
raise Exception(
|
raise Exception(f'获取gewechat token失败: {await response.text()}')
|
||||||
f'获取gewechat token失败: {await response.text()}'
|
|
||||||
)
|
|
||||||
self.config['token'] = (await response.json())['data']
|
self.config['token'] = (await response.json())['data']
|
||||||
|
|
||||||
self.bot = gewechat_client.GewechatClient(
|
self.bot = gewechat_client.GewechatClient(f'{self.config["gewechat_url"]}/v2/api', self.config['token'])
|
||||||
f'{self.config["gewechat_url"]}/v2/api', self.config['token']
|
|
||||||
)
|
|
||||||
|
|
||||||
def gewechat_login_process():
|
def gewechat_login_process():
|
||||||
app_id, error_msg = self.bot.login(self.config['app_id'])
|
app_id, error_msg = self.bot.login(self.config['app_id'])
|
||||||
|
|||||||
@@ -71,14 +71,10 @@ class LarkMessageConverter(adapter.MessageConverter):
|
|||||||
pending_paragraph.append({'tag': 'md', 'text': text})
|
pending_paragraph.append({'tag': 'md', 'text': text})
|
||||||
except UnicodeError:
|
except UnicodeError:
|
||||||
# If still fails, replace invalid characters
|
# If still fails, replace invalid characters
|
||||||
text = msg.text.encode('utf-8', errors='replace').decode(
|
text = msg.text.encode('utf-8', errors='replace').decode('utf-8')
|
||||||
'utf-8'
|
|
||||||
)
|
|
||||||
pending_paragraph.append({'tag': 'md', 'text': text})
|
pending_paragraph.append({'tag': 'md', 'text': text})
|
||||||
elif isinstance(msg, platform_message.At):
|
elif isinstance(msg, platform_message.At):
|
||||||
pending_paragraph.append(
|
pending_paragraph.append({'tag': 'at', 'user_id': msg.target, 'style': []})
|
||||||
{'tag': 'at', 'user_id': msg.target, 'style': []}
|
|
||||||
)
|
|
||||||
elif isinstance(msg, platform_message.AtAll):
|
elif isinstance(msg, platform_message.AtAll):
|
||||||
pending_paragraph.append({'tag': 'at', 'user_id': 'all', 'style': []})
|
pending_paragraph.append({'tag': 'at', 'user_id': 'all', 'style': []})
|
||||||
elif isinstance(msg, platform_message.Image):
|
elif isinstance(msg, platform_message.Image):
|
||||||
@@ -166,11 +162,7 @@ class LarkMessageConverter(adapter.MessageConverter):
|
|||||||
os.unlink(temp_file.name)
|
os.unlink(temp_file.name)
|
||||||
elif isinstance(msg, platform_message.Forward):
|
elif isinstance(msg, platform_message.Forward):
|
||||||
for node in msg.node_list:
|
for node in msg.node_list:
|
||||||
message_elements.extend(
|
message_elements.extend(await LarkMessageConverter.yiri2target(node.message_chain, api_client))
|
||||||
await LarkMessageConverter.yiri2target(
|
|
||||||
node.message_chain, api_client
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if pending_paragraph:
|
if pending_paragraph:
|
||||||
message_elements.append(pending_paragraph)
|
message_elements.append(pending_paragraph)
|
||||||
@@ -186,13 +178,9 @@ class LarkMessageConverter(adapter.MessageConverter):
|
|||||||
|
|
||||||
lb_msg_list = []
|
lb_msg_list = []
|
||||||
|
|
||||||
msg_create_time = datetime.datetime.fromtimestamp(
|
msg_create_time = datetime.datetime.fromtimestamp(int(message.create_time) / 1000)
|
||||||
int(message.create_time) / 1000
|
|
||||||
)
|
|
||||||
|
|
||||||
lb_msg_list.append(
|
lb_msg_list.append(platform_message.Source(id=message.message_id, time=msg_create_time))
|
||||||
platform_message.Source(id=message.message_id, time=msg_create_time)
|
|
||||||
)
|
|
||||||
|
|
||||||
if message.message_type == 'text':
|
if message.message_type == 'text':
|
||||||
element_list = []
|
element_list = []
|
||||||
@@ -222,9 +210,7 @@ class LarkMessageConverter(adapter.MessageConverter):
|
|||||||
left_text = text_split[0]
|
left_text = text_split[0]
|
||||||
right_text = text_split[1]
|
right_text = text_split[1]
|
||||||
|
|
||||||
new_list.extend(
|
new_list.extend(text_element_recur({'tag': 'text', 'text': left_text, 'style': []}))
|
||||||
text_element_recur({'tag': 'text', 'text': left_text, 'style': []})
|
|
||||||
)
|
|
||||||
|
|
||||||
new_list.append(
|
new_list.append(
|
||||||
{
|
{
|
||||||
@@ -235,15 +221,11 @@ class LarkMessageConverter(adapter.MessageConverter):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
new_list.extend(
|
new_list.extend(text_element_recur({'tag': 'text', 'text': right_text, 'style': []}))
|
||||||
text_element_recur({'tag': 'text', 'text': right_text, 'style': []})
|
|
||||||
)
|
|
||||||
|
|
||||||
return new_list
|
return new_list
|
||||||
|
|
||||||
element_list = text_element_recur(
|
element_list = text_element_recur({'tag': 'text', 'text': message_content['text'], 'style': []})
|
||||||
{'tag': 'text', 'text': message_content['text'], 'style': []}
|
|
||||||
)
|
|
||||||
|
|
||||||
message_content = {'title': '', 'content': element_list}
|
message_content = {'title': '', 'content': element_list}
|
||||||
|
|
||||||
@@ -258,9 +240,7 @@ class LarkMessageConverter(adapter.MessageConverter):
|
|||||||
|
|
||||||
message_content['content'] = new_list
|
message_content['content'] = new_list
|
||||||
elif message.message_type == 'image':
|
elif message.message_type == 'image':
|
||||||
message_content['content'] = [
|
message_content['content'] = [{'tag': 'img', 'image_key': message_content['image_key'], 'style': []}]
|
||||||
{'tag': 'img', 'image_key': message_content['image_key'], 'style': []}
|
|
||||||
]
|
|
||||||
|
|
||||||
for ele in message_content['content']:
|
for ele in message_content['content']:
|
||||||
if ele['tag'] == 'text':
|
if ele['tag'] == 'text':
|
||||||
@@ -278,9 +258,7 @@ class LarkMessageConverter(adapter.MessageConverter):
|
|||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
|
|
||||||
response: GetMessageResourceResponse = (
|
response: GetMessageResourceResponse = await api_client.im.v1.message_resource.aget(request)
|
||||||
await api_client.im.v1.message_resource.aget(request)
|
|
||||||
)
|
|
||||||
|
|
||||||
if not response.success():
|
if not response.success():
|
||||||
raise Exception(
|
raise Exception(
|
||||||
@@ -292,11 +270,7 @@ class LarkMessageConverter(adapter.MessageConverter):
|
|||||||
|
|
||||||
image_format = response.raw.headers['content-type']
|
image_format = response.raw.headers['content-type']
|
||||||
|
|
||||||
lb_msg_list.append(
|
lb_msg_list.append(platform_message.Image(base64=f'data:{image_format};base64,{image_base64}'))
|
||||||
platform_message.Image(
|
|
||||||
base64=f'data:{image_format};base64,{image_base64}'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return platform_message.MessageChain(lb_msg_list)
|
return platform_message.MessageChain(lb_msg_list)
|
||||||
|
|
||||||
@@ -312,9 +286,7 @@ class LarkEventConverter(adapter.EventConverter):
|
|||||||
async def target2yiri(
|
async def target2yiri(
|
||||||
event: lark_oapi.im.v1.P2ImMessageReceiveV1, api_client: lark_oapi.Client
|
event: lark_oapi.im.v1.P2ImMessageReceiveV1, api_client: lark_oapi.Client
|
||||||
) -> platform_events.Event:
|
) -> platform_events.Event:
|
||||||
message_chain = await LarkMessageConverter.target2yiri(
|
message_chain = await LarkMessageConverter.target2yiri(event.event.message, api_client)
|
||||||
event.event.message, api_client
|
|
||||||
)
|
|
||||||
|
|
||||||
if event.event.message.chat_type == 'p2p':
|
if event.event.message.chat_type == 'p2p':
|
||||||
return platform_events.FriendMessage(
|
return platform_events.FriendMessage(
|
||||||
@@ -402,9 +374,7 @@ class LarkAdapter(adapter.MessagePlatformAdapter):
|
|||||||
p2v1.schema = context.schema
|
p2v1.schema = context.schema
|
||||||
if 'im.message.receive_v1' == type:
|
if 'im.message.receive_v1' == type:
|
||||||
try:
|
try:
|
||||||
event = await self.event_converter.target2yiri(
|
event = await self.event_converter.target2yiri(p2v1, self.api_client)
|
||||||
p2v1, self.api_client
|
|
||||||
)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
@@ -425,26 +395,15 @@ class LarkAdapter(adapter.MessagePlatformAdapter):
|
|||||||
asyncio.create_task(on_message(event))
|
asyncio.create_task(on_message(event))
|
||||||
|
|
||||||
event_handler = (
|
event_handler = (
|
||||||
lark_oapi.EventDispatcherHandler.builder('', '')
|
lark_oapi.EventDispatcherHandler.builder('', '').register_p2_im_message_receive_v1(sync_on_message).build()
|
||||||
.register_p2_im_message_receive_v1(sync_on_message)
|
|
||||||
.build()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.bot_account_id = config['bot_name']
|
self.bot_account_id = config['bot_name']
|
||||||
|
|
||||||
self.bot = lark_oapi.ws.Client(
|
self.bot = lark_oapi.ws.Client(config['app_id'], config['app_secret'], event_handler=event_handler)
|
||||||
config['app_id'], config['app_secret'], event_handler=event_handler
|
self.api_client = lark_oapi.Client.builder().app_id(config['app_id']).app_secret(config['app_secret']).build()
|
||||||
)
|
|
||||||
self.api_client = (
|
|
||||||
lark_oapi.Client.builder()
|
|
||||||
.app_id(config['app_id'])
|
|
||||||
.app_secret(config['app_secret'])
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
|
|
||||||
async def send_message(
|
async def send_message(self, target_type: str, target_id: str, message: platform_message.MessageChain):
|
||||||
self, target_type: str, target_id: str, message: platform_message.MessageChain
|
|
||||||
):
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def reply_message(
|
async def reply_message(
|
||||||
@@ -455,9 +414,7 @@ class LarkAdapter(adapter.MessagePlatformAdapter):
|
|||||||
):
|
):
|
||||||
# 不再需要了,因为message_id已经被包含到message_chain中
|
# 不再需要了,因为message_id已经被包含到message_chain中
|
||||||
# lark_event = await self.event_converter.yiri2target(message_source)
|
# lark_event = await self.event_converter.yiri2target(message_source)
|
||||||
lark_message = await self.message_converter.yiri2target(
|
lark_message = await self.message_converter.yiri2target(message, self.api_client)
|
||||||
message, self.api_client
|
|
||||||
)
|
|
||||||
|
|
||||||
final_content = {
|
final_content = {
|
||||||
'zh_cn': {
|
'zh_cn': {
|
||||||
@@ -480,9 +437,7 @@ class LarkAdapter(adapter.MessagePlatformAdapter):
|
|||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
|
|
||||||
response: ReplyMessageResponse = await self.api_client.im.v1.message.areply(
|
response: ReplyMessageResponse = await self.api_client.im.v1.message.areply(request)
|
||||||
request
|
|
||||||
)
|
|
||||||
|
|
||||||
if not response.success():
|
if not response.success():
|
||||||
raise Exception(
|
raise Exception(
|
||||||
@@ -495,18 +450,14 @@ class LarkAdapter(adapter.MessagePlatformAdapter):
|
|||||||
def register_listener(
|
def register_listener(
|
||||||
self,
|
self,
|
||||||
event_type: typing.Type[platform_events.Event],
|
event_type: typing.Type[platform_events.Event],
|
||||||
callback: typing.Callable[
|
callback: typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None],
|
||||||
[platform_events.Event, adapter.MessagePlatformAdapter], None
|
|
||||||
],
|
|
||||||
):
|
):
|
||||||
self.listeners[event_type] = callback
|
self.listeners[event_type] = callback
|
||||||
|
|
||||||
def unregister_listener(
|
def unregister_listener(
|
||||||
self,
|
self,
|
||||||
event_type: typing.Type[platform_events.Event],
|
event_type: typing.Type[platform_events.Event],
|
||||||
callback: typing.Callable[
|
callback: typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None],
|
||||||
[platform_events.Event, adapter.MessagePlatformAdapter], None
|
|
||||||
],
|
|
||||||
):
|
):
|
||||||
self.listeners.pop(event_type)
|
self.listeners.pop(event_type)
|
||||||
|
|
||||||
|
|||||||
@@ -29,9 +29,7 @@ class NakuruProjectMessageConverter(adapter_model.MessageConverter):
|
|||||||
elif type(message_chain) is str:
|
elif type(message_chain) is str:
|
||||||
msg_list = [platform_message.Plain(message_chain)]
|
msg_list = [platform_message.Plain(message_chain)]
|
||||||
else:
|
else:
|
||||||
raise Exception(
|
raise Exception('Unknown message type: ' + str(message_chain) + str(type(message_chain)))
|
||||||
'Unknown message type: ' + str(message_chain) + str(type(message_chain))
|
|
||||||
)
|
|
||||||
|
|
||||||
nakuru_msg_list = []
|
nakuru_msg_list = []
|
||||||
|
|
||||||
@@ -63,9 +61,7 @@ class NakuruProjectMessageConverter(adapter_model.MessageConverter):
|
|||||||
# 遍历并转换
|
# 遍历并转换
|
||||||
for yiri_forward_node in yiri_forward_node_list:
|
for yiri_forward_node in yiri_forward_node_list:
|
||||||
try:
|
try:
|
||||||
content_list = NakuruProjectMessageConverter.yiri2target(
|
content_list = NakuruProjectMessageConverter.yiri2target(yiri_forward_node.message_chain)
|
||||||
yiri_forward_node.message_chain
|
|
||||||
)
|
|
||||||
nakuru_forward_node = nkc.Node(
|
nakuru_forward_node = nkc.Node(
|
||||||
name=yiri_forward_node.sender_name,
|
name=yiri_forward_node.sender_name,
|
||||||
uin=yiri_forward_node.sender_id,
|
uin=yiri_forward_node.sender_id,
|
||||||
@@ -87,9 +83,7 @@ class NakuruProjectMessageConverter(adapter_model.MessageConverter):
|
|||||||
return nakuru_msg_list
|
return nakuru_msg_list
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def target2yiri(
|
def target2yiri(message_chain: typing.Any, message_id: int = -1) -> platform_message.MessageChain:
|
||||||
message_chain: typing.Any, message_id: int = -1
|
|
||||||
) -> platform_message.MessageChain:
|
|
||||||
"""将Yiri的消息链转换为YiriMirai的消息链"""
|
"""将Yiri的消息链转换为YiriMirai的消息链"""
|
||||||
assert type(message_chain) is list
|
assert type(message_chain) is list
|
||||||
|
|
||||||
@@ -97,9 +91,7 @@ class NakuruProjectMessageConverter(adapter_model.MessageConverter):
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
# 添加Source组件以标记message_id等信息
|
# 添加Source组件以标记message_id等信息
|
||||||
yiri_msg_list.append(
|
yiri_msg_list.append(platform_message.Source(id=message_id, time=datetime.datetime.now()))
|
||||||
platform_message.Source(id=message_id, time=datetime.datetime.now())
|
|
||||||
)
|
|
||||||
for component in message_chain:
|
for component in message_chain:
|
||||||
if type(component) is nkc.Plain:
|
if type(component) is nkc.Plain:
|
||||||
yiri_msg_list.append(platform_message.Plain(text=component.text))
|
yiri_msg_list.append(platform_message.Plain(text=component.text))
|
||||||
@@ -130,9 +122,7 @@ class NakuruProjectEventConverter(adapter_model.EventConverter):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def target2yiri(event: typing.Any) -> platform_events.Event:
|
def target2yiri(event: typing.Any) -> platform_events.Event:
|
||||||
yiri_chain = NakuruProjectMessageConverter.target2yiri(
|
yiri_chain = NakuruProjectMessageConverter.target2yiri(event.message, event.message_id)
|
||||||
event.message, event.message_id
|
|
||||||
)
|
|
||||||
if type(event) is nakuru.FriendMessage: # 私聊消息事件
|
if type(event) is nakuru.FriendMessage: # 私聊消息事件
|
||||||
return platform_events.FriendMessage(
|
return platform_events.FriendMessage(
|
||||||
sender=platform_entities.Friend(
|
sender=platform_entities.Friend(
|
||||||
@@ -206,9 +196,7 @@ class NakuruAdapter(adapter_model.MessagePlatformAdapter):
|
|||||||
):
|
):
|
||||||
task = None
|
task = None
|
||||||
|
|
||||||
converted_msg = (
|
converted_msg = self.message_converter.yiri2target(message) if not converted else message
|
||||||
self.message_converter.yiri2target(message) if not converted else message
|
|
||||||
)
|
|
||||||
|
|
||||||
# 检查是否有转发消息
|
# 检查是否有转发消息
|
||||||
has_forward = False
|
has_forward = False
|
||||||
@@ -250,13 +238,9 @@ class NakuruAdapter(adapter_model.MessagePlatformAdapter):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
if type(message_source) is platform_events.GroupMessage:
|
if type(message_source) is platform_events.GroupMessage:
|
||||||
await self.send_message(
|
await self.send_message('group', message_source.sender.group.id, message, converted=True)
|
||||||
'group', message_source.sender.group.id, message, converted=True
|
|
||||||
)
|
|
||||||
elif type(message_source) is platform_events.FriendMessage:
|
elif type(message_source) is platform_events.FriendMessage:
|
||||||
await self.send_message(
|
await self.send_message('person', message_source.sender.id, message, converted=True)
|
||||||
'person', message_source.sender.id, message, converted=True
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
raise Exception('Unknown message source type: ' + str(type(message_source)))
|
raise Exception('Unknown message source type: ' + str(type(message_source)))
|
||||||
|
|
||||||
@@ -264,17 +248,13 @@ class NakuruAdapter(adapter_model.MessagePlatformAdapter):
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
# 检查是否被禁言
|
# 检查是否被禁言
|
||||||
group_member_info = asyncio.run(
|
group_member_info = asyncio.run(self.bot.getGroupMemberInfo(group_id, self.bot_account_id))
|
||||||
self.bot.getGroupMemberInfo(group_id, self.bot_account_id)
|
|
||||||
)
|
|
||||||
return group_member_info.shut_up_timestamp > int(time.time())
|
return group_member_info.shut_up_timestamp > int(time.time())
|
||||||
|
|
||||||
def register_listener(
|
def register_listener(
|
||||||
self,
|
self,
|
||||||
event_type: typing.Type[platform_events.Event],
|
event_type: typing.Type[platform_events.Event],
|
||||||
callback: typing.Callable[
|
callback: typing.Callable[[platform_events.Event, adapter_model.MessagePlatformAdapter], None],
|
||||||
[platform_events.Event, adapter_model.MessagePlatformAdapter], None
|
|
||||||
],
|
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
source_cls = NakuruProjectEventConverter.yiri2target(event_type)
|
source_cls = NakuruProjectEventConverter.yiri2target(event_type)
|
||||||
@@ -301,9 +281,7 @@ class NakuruAdapter(adapter_model.MessagePlatformAdapter):
|
|||||||
def unregister_listener(
|
def unregister_listener(
|
||||||
self,
|
self,
|
||||||
event_type: typing.Type[platform_events.Event],
|
event_type: typing.Type[platform_events.Event],
|
||||||
callback: typing.Callable[
|
callback: typing.Callable[[platform_events.Event, adapter_model.MessagePlatformAdapter], None],
|
||||||
[platform_events.Event, adapter_model.MessagePlatformAdapter], None
|
|
||||||
],
|
|
||||||
):
|
):
|
||||||
nakuru_event_name = self.event_converter.yiri2target(event_type).__name__
|
nakuru_event_name = self.event_converter.yiri2target(event_type).__name__
|
||||||
|
|
||||||
@@ -312,10 +290,7 @@ class NakuruAdapter(adapter_model.MessagePlatformAdapter):
|
|||||||
# 从本对象的监听器列表中查找并删除
|
# 从本对象的监听器列表中查找并删除
|
||||||
target_wrapper = None
|
target_wrapper = None
|
||||||
for listener in self.listener_list:
|
for listener in self.listener_list:
|
||||||
if (
|
if listener['event_type'] == event_type and listener['callable'] == callback:
|
||||||
listener['event_type'] == event_type
|
|
||||||
and listener['callable'] == callback
|
|
||||||
):
|
|
||||||
target_wrapper = listener['wrapper']
|
target_wrapper = listener['wrapper']
|
||||||
self.listener_list.remove(listener)
|
self.listener_list.remove(listener)
|
||||||
break
|
break
|
||||||
@@ -334,14 +309,8 @@ class NakuruAdapter(adapter_model.MessagePlatformAdapter):
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
resp = requests.get(
|
resp = requests.get(
|
||||||
url='http://{}:{}/get_login_info'.format(
|
url='http://{}:{}/get_login_info'.format(self.cfg['host'], self.cfg['http_port']),
|
||||||
self.cfg['host'], self.cfg['http_port']
|
headers={'Authorization': 'Bearer ' + self.cfg['token'] if 'token' in self.cfg else ''},
|
||||||
),
|
|
||||||
headers={
|
|
||||||
'Authorization': 'Bearer ' + self.cfg['token']
|
|
||||||
if 'token' in self.cfg
|
|
||||||
else ''
|
|
||||||
},
|
|
||||||
timeout=5,
|
timeout=5,
|
||||||
proxies=None,
|
proxies=None,
|
||||||
)
|
)
|
||||||
@@ -349,9 +318,7 @@ class NakuruAdapter(adapter_model.MessagePlatformAdapter):
|
|||||||
raise Exception('go-cqhttp拒绝访问,请检查配置文件中nakuru适配器的配置')
|
raise Exception('go-cqhttp拒绝访问,请检查配置文件中nakuru适配器的配置')
|
||||||
self.bot_account_id = int(resp.json()['data']['user_id'])
|
self.bot_account_id = int(resp.json()['data']['user_id'])
|
||||||
except Exception:
|
except Exception:
|
||||||
raise Exception(
|
raise Exception('获取go-cqhttp账号信息失败, 请检查是否已启动go-cqhttp并配置正确')
|
||||||
'获取go-cqhttp账号信息失败, 请检查是否已启动go-cqhttp并配置正确'
|
|
||||||
)
|
|
||||||
await self.bot._run()
|
await self.bot._run()
|
||||||
self.ap.logger.info('运行 Nakuru 适配器')
|
self.ap.logger.info('运行 Nakuru 适配器')
|
||||||
while True:
|
while True:
|
||||||
|
|||||||
@@ -25,9 +25,7 @@ class OAMessageConverter(adapter.MessageConverter):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
async def target2yiri(message: str, message_id=-1):
|
async def target2yiri(message: str, message_id=-1):
|
||||||
yiri_msg_list = []
|
yiri_msg_list = []
|
||||||
yiri_msg_list.append(
|
yiri_msg_list.append(platform_message.Source(id=message_id, time=datetime.datetime.now()))
|
||||||
platform_message.Source(id=message_id, time=datetime.datetime.now())
|
|
||||||
)
|
|
||||||
|
|
||||||
yiri_msg_list.append(platform_message.Plain(text=message))
|
yiri_msg_list.append(platform_message.Plain(text=message))
|
||||||
chain = platform_message.MessageChain(yiri_msg_list)
|
chain = platform_message.MessageChain(yiri_msg_list)
|
||||||
@@ -39,9 +37,7 @@ class OAEventConverter(adapter.EventConverter):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
async def target2yiri(event: OAEvent):
|
async def target2yiri(event: OAEvent):
|
||||||
if event.type == 'text':
|
if event.type == 'text':
|
||||||
yiri_chain = await OAMessageConverter.target2yiri(
|
yiri_chain = await OAMessageConverter.target2yiri(event.message, event.message_id)
|
||||||
event.message, event.message_id
|
|
||||||
)
|
|
||||||
|
|
||||||
friend = platform_entities.Friend(
|
friend = platform_entities.Friend(
|
||||||
id=event.user_id,
|
id=event.user_id,
|
||||||
@@ -81,9 +77,7 @@ class OfficialAccountAdapter(adapter.MessagePlatformAdapter):
|
|||||||
]
|
]
|
||||||
missing_keys = [key for key in required_keys if key not in config]
|
missing_keys = [key for key in required_keys if key not in config]
|
||||||
if missing_keys:
|
if missing_keys:
|
||||||
raise ParamNotEnoughError(
|
raise ParamNotEnoughError('微信公众号缺少相关配置项,请查看文档或联系管理员')
|
||||||
'微信公众号缺少相关配置项,请查看文档或联系管理员'
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.config['Mode'] == 'drop':
|
if self.config['Mode'] == 'drop':
|
||||||
self.bot = OAClient(
|
self.bot = OAClient(
|
||||||
@@ -114,28 +108,20 @@ class OfficialAccountAdapter(adapter.MessagePlatformAdapter):
|
|||||||
await self.bot.set_message(message_source.message_chain.message_id, content)
|
await self.bot.set_message(message_source.message_chain.message_id, content)
|
||||||
elif isinstance(self.bot, OAClientForLongerResponse):
|
elif isinstance(self.bot, OAClientForLongerResponse):
|
||||||
from_user = message_source.sender.id
|
from_user = message_source.sender.id
|
||||||
await self.bot.set_message(
|
await self.bot.set_message(from_user, message_source.message_chain.message_id, content)
|
||||||
from_user, message_source.message_chain.message_id, content
|
|
||||||
)
|
|
||||||
|
|
||||||
async def send_message(
|
async def send_message(self, target_type: str, target_id: str, message: platform_message.MessageChain):
|
||||||
self, target_type: str, target_id: str, message: platform_message.MessageChain
|
|
||||||
):
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def register_listener(
|
def register_listener(
|
||||||
self,
|
self,
|
||||||
event_type: type,
|
event_type: type,
|
||||||
callback: typing.Callable[
|
callback: typing.Callable[[platform_events.Event, MessagePlatformAdapter], None],
|
||||||
[platform_events.Event, MessagePlatformAdapter], None
|
|
||||||
],
|
|
||||||
):
|
):
|
||||||
async def on_message(event: OAEvent):
|
async def on_message(event: OAEvent):
|
||||||
self.bot_account_id = event.receiver_id
|
self.bot_account_id = event.receiver_id
|
||||||
try:
|
try:
|
||||||
return await callback(
|
return await callback(await self.event_converter.target2yiri(event), self)
|
||||||
await self.event_converter.target2yiri(event), self
|
|
||||||
)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
@@ -161,8 +147,6 @@ class OfficialAccountAdapter(adapter.MessagePlatformAdapter):
|
|||||||
async def unregister_listener(
|
async def unregister_listener(
|
||||||
self,
|
self,
|
||||||
event_type: type,
|
event_type: type,
|
||||||
callback: typing.Callable[
|
callback: typing.Callable[[platform_events.Event, MessagePlatformAdapter], None],
|
||||||
[platform_events.Event, MessagePlatformAdapter], None
|
|
||||||
],
|
|
||||||
):
|
):
|
||||||
return super().unregister_listener(event_type, callback)
|
return super().unregister_listener(event_type, callback)
|
||||||
|
|||||||
@@ -147,9 +147,7 @@ class OfficialMessageConverter(adapter_model.MessageConverter):
|
|||||||
elif type(message_chain) is str:
|
elif type(message_chain) is str:
|
||||||
msg_list = [platform_message.Plain(text=message_chain)]
|
msg_list = [platform_message.Plain(text=message_chain)]
|
||||||
else:
|
else:
|
||||||
raise Exception(
|
raise Exception('Unknown message type: ' + str(message_chain) + str(type(message_chain)))
|
||||||
'Unknown message type: ' + str(message_chain) + str(type(message_chain))
|
|
||||||
)
|
|
||||||
|
|
||||||
offcial_messages: list[dict] = []
|
offcial_messages: list[dict] = []
|
||||||
"""
|
"""
|
||||||
@@ -172,19 +170,13 @@ class OfficialMessageConverter(adapter_model.MessageConverter):
|
|||||||
if component.url is not None:
|
if component.url is not None:
|
||||||
offcial_messages.append({'type': 'image', 'content': component.url})
|
offcial_messages.append({'type': 'image', 'content': component.url})
|
||||||
elif component.path is not None:
|
elif component.path is not None:
|
||||||
offcial_messages.append(
|
offcial_messages.append({'type': 'file_image', 'content': component.path})
|
||||||
{'type': 'file_image', 'content': component.path}
|
|
||||||
)
|
|
||||||
elif type(component) is platform_message.At:
|
elif type(component) is platform_message.At:
|
||||||
offcial_messages.append({'type': 'at', 'content': ''})
|
offcial_messages.append({'type': 'at', 'content': ''})
|
||||||
elif type(component) is platform_message.AtAll:
|
elif type(component) is platform_message.AtAll:
|
||||||
print(
|
print('上层组件要求发送 AtAll 消息,但 QQ 官方 API 不支持此消息类型,忽略此消息。')
|
||||||
'上层组件要求发送 AtAll 消息,但 QQ 官方 API 不支持此消息类型,忽略此消息。'
|
|
||||||
)
|
|
||||||
elif type(component) is platform_message.Voice:
|
elif type(component) is platform_message.Voice:
|
||||||
print(
|
print('上层组件要求发送 Voice 消息,但 QQ 官方 API 不支持此消息类型,忽略此消息。')
|
||||||
'上层组件要求发送 Voice 消息,但 QQ 官方 API 不支持此消息类型,忽略此消息。'
|
|
||||||
)
|
|
||||||
elif type(component) is forward.Forward:
|
elif type(component) is forward.Forward:
|
||||||
# 转发消息
|
# 转发消息
|
||||||
yiri_forward_node_list = component.node_list
|
yiri_forward_node_list = component.node_list
|
||||||
@@ -195,9 +187,7 @@ class OfficialMessageConverter(adapter_model.MessageConverter):
|
|||||||
message_chain = yiri_forward_node.message_chain
|
message_chain = yiri_forward_node.message_chain
|
||||||
|
|
||||||
# 平铺
|
# 平铺
|
||||||
offcial_messages.extend(
|
offcial_messages.extend(OfficialMessageConverter.yiri2target(message_chain))
|
||||||
OfficialMessageConverter.yiri2target(message_chain)
|
|
||||||
)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
@@ -219,11 +209,7 @@ class OfficialMessageConverter(adapter_model.MessageConverter):
|
|||||||
yiri_msg_list = []
|
yiri_msg_list = []
|
||||||
# 存id
|
# 存id
|
||||||
|
|
||||||
yiri_msg_list.append(
|
yiri_msg_list.append(platform_message.Source(id=save_msg_id(message_id), time=datetime.datetime.now()))
|
||||||
platform_message.Source(
|
|
||||||
id=save_msg_id(message_id), time=datetime.datetime.now()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if type(message) not in [botpy_message.DirectMessage, botpy_message.C2CMessage]:
|
if type(message) not in [botpy_message.DirectMessage, botpy_message.C2CMessage]:
|
||||||
yiri_msg_list.append(platform_message.At(target=bot_account_id))
|
yiri_msg_list.append(platform_message.At(target=bot_account_id))
|
||||||
@@ -239,9 +225,7 @@ class OfficialMessageConverter(adapter_model.MessageConverter):
|
|||||||
if attachment.content_type.startswith('image'):
|
if attachment.content_type.startswith('image'):
|
||||||
yiri_msg_list.append(platform_message.Image(url=attachment.url))
|
yiri_msg_list.append(platform_message.Image(url=attachment.url))
|
||||||
else:
|
else:
|
||||||
logging.warning(
|
logging.warning('不支持的附件类型:' + attachment.content_type + ',忽略此附件。')
|
||||||
'不支持的附件类型:' + attachment.content_type + ',忽略此附件。'
|
|
||||||
)
|
|
||||||
|
|
||||||
content = re.sub(r'<@!\d+>', '', str(message.content))
|
content = re.sub(r'<@!\d+>', '', str(message.content))
|
||||||
if content.strip() != '':
|
if content.strip() != '':
|
||||||
@@ -264,9 +248,7 @@ class OfficialEventConverter(adapter_model.EventConverter):
|
|||||||
elif event == platform_events.FriendMessage:
|
elif event == platform_events.FriendMessage:
|
||||||
return botpy_message.DirectMessage
|
return botpy_message.DirectMessage
|
||||||
else:
|
else:
|
||||||
raise Exception(
|
raise Exception('未支持转换的事件类型(YiriMirai -> Official): ' + str(event))
|
||||||
'未支持转换的事件类型(YiriMirai -> Official): ' + str(event)
|
|
||||||
)
|
|
||||||
|
|
||||||
def target2yiri(
|
def target2yiri(
|
||||||
self,
|
self,
|
||||||
@@ -297,21 +279,13 @@ class OfficialEventConverter(adapter_model.EventConverter):
|
|||||||
),
|
),
|
||||||
special_title='',
|
special_title='',
|
||||||
join_timestamp=int(
|
join_timestamp=int(
|
||||||
datetime.datetime.strptime(
|
datetime.datetime.strptime(event.member.joined_at, '%Y-%m-%dT%H:%M:%S%z').timestamp()
|
||||||
event.member.joined_at, '%Y-%m-%dT%H:%M:%S%z'
|
|
||||||
).timestamp()
|
|
||||||
),
|
),
|
||||||
last_speak_timestamp=datetime.datetime.now().timestamp(),
|
last_speak_timestamp=datetime.datetime.now().timestamp(),
|
||||||
mute_time_remaining=0,
|
mute_time_remaining=0,
|
||||||
),
|
),
|
||||||
message_chain=OfficialMessageConverter.extract_message_chain_from_obj(
|
message_chain=OfficialMessageConverter.extract_message_chain_from_obj(event, event.id),
|
||||||
event, event.id
|
time=int(datetime.datetime.strptime(event.timestamp, '%Y-%m-%dT%H:%M:%S%z').timestamp()),
|
||||||
),
|
|
||||||
time=int(
|
|
||||||
datetime.datetime.strptime(
|
|
||||||
event.timestamp, '%Y-%m-%dT%H:%M:%S%z'
|
|
||||||
).timestamp()
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
elif isinstance(event, botpy_message.DirectMessage): # 频道私聊,转私聊事件
|
elif isinstance(event, botpy_message.DirectMessage): # 频道私聊,转私聊事件
|
||||||
return platform_events.FriendMessage(
|
return platform_events.FriendMessage(
|
||||||
@@ -320,14 +294,8 @@ class OfficialEventConverter(adapter_model.EventConverter):
|
|||||||
nickname=event.author.username,
|
nickname=event.author.username,
|
||||||
remark=event.author.username,
|
remark=event.author.username,
|
||||||
),
|
),
|
||||||
message_chain=OfficialMessageConverter.extract_message_chain_from_obj(
|
message_chain=OfficialMessageConverter.extract_message_chain_from_obj(event, event.id),
|
||||||
event, event.id
|
time=int(datetime.datetime.strptime(event.timestamp, '%Y-%m-%dT%H:%M:%S%z').timestamp()),
|
||||||
),
|
|
||||||
time=int(
|
|
||||||
datetime.datetime.strptime(
|
|
||||||
event.timestamp, '%Y-%m-%dT%H:%M:%S%z'
|
|
||||||
).timestamp()
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
elif isinstance(event, botpy_message.GroupMessage): # 群聊,转群聊事件
|
elif isinstance(event, botpy_message.GroupMessage): # 群聊,转群聊事件
|
||||||
author_member_id = event.author.member_openid
|
author_member_id = event.author.member_openid
|
||||||
@@ -347,14 +315,8 @@ class OfficialEventConverter(adapter_model.EventConverter):
|
|||||||
last_speak_timestamp=datetime.datetime.now().timestamp(),
|
last_speak_timestamp=datetime.datetime.now().timestamp(),
|
||||||
mute_time_remaining=0,
|
mute_time_remaining=0,
|
||||||
),
|
),
|
||||||
message_chain=OfficialMessageConverter.extract_message_chain_from_obj(
|
message_chain=OfficialMessageConverter.extract_message_chain_from_obj(event, event.id),
|
||||||
event, event.id
|
time=int(datetime.datetime.strptime(event.timestamp, '%Y-%m-%dT%H:%M:%S%z').timestamp()),
|
||||||
),
|
|
||||||
time=int(
|
|
||||||
datetime.datetime.strptime(
|
|
||||||
event.timestamp, '%Y-%m-%dT%H:%M:%S%z'
|
|
||||||
).timestamp()
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
elif isinstance(event, botpy_message.C2CMessage): # 私聊,转私聊事件
|
elif isinstance(event, botpy_message.C2CMessage): # 私聊,转私聊事件
|
||||||
user_id_alter = event.author.user_openid
|
user_id_alter = event.author.user_openid
|
||||||
@@ -365,14 +327,8 @@ class OfficialEventConverter(adapter_model.EventConverter):
|
|||||||
nickname=user_id_alter,
|
nickname=user_id_alter,
|
||||||
remark=user_id_alter,
|
remark=user_id_alter,
|
||||||
),
|
),
|
||||||
message_chain=OfficialMessageConverter.extract_message_chain_from_obj(
|
message_chain=OfficialMessageConverter.extract_message_chain_from_obj(event, event.id),
|
||||||
event, event.id
|
time=int(datetime.datetime.strptime(event.timestamp, '%Y-%m-%dT%H:%M:%S%z').timestamp()),
|
||||||
),
|
|
||||||
time=int(
|
|
||||||
datetime.datetime.strptime(
|
|
||||||
event.timestamp, '%Y-%m-%dT%H:%M:%S%z'
|
|
||||||
).timestamp()
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -420,9 +376,7 @@ class OfficialAdapter(adapter_model.MessagePlatformAdapter):
|
|||||||
|
|
||||||
self.bot = botpy.Client(intents=intents)
|
self.bot = botpy.Client(intents=intents)
|
||||||
|
|
||||||
async def send_message(
|
async def send_message(self, target_type: str, target_id: str, message: platform_message.MessageChain):
|
||||||
self, target_type: str, target_id: str, message: platform_message.MessageChain
|
|
||||||
):
|
|
||||||
message_list = self.message_converter.yiri2target(message)
|
message_list = self.message_converter.yiri2target(message)
|
||||||
|
|
||||||
for msg in message_list:
|
for msg in message_list:
|
||||||
@@ -468,22 +422,16 @@ class OfficialAdapter(adapter_model.MessagePlatformAdapter):
|
|||||||
|
|
||||||
if quote_origin:
|
if quote_origin:
|
||||||
args['message_reference'] = botpy_message_type.Reference(
|
args['message_reference'] = botpy_message_type.Reference(
|
||||||
message_id=cached_message_ids[
|
message_id=cached_message_ids[str(message_source.message_chain.message_id)]
|
||||||
str(message_source.message_chain.message_id)
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(message_source, platform_events.GroupMessage):
|
if isinstance(message_source, platform_events.GroupMessage):
|
||||||
args['channel_id'] = str(message_source.sender.group.id)
|
args['channel_id'] = str(message_source.sender.group.id)
|
||||||
args['msg_id'] = cached_message_ids[
|
args['msg_id'] = cached_message_ids[str(message_source.message_chain.message_id)]
|
||||||
str(message_source.message_chain.message_id)
|
|
||||||
]
|
|
||||||
await self.bot.api.post_message(**args)
|
await self.bot.api.post_message(**args)
|
||||||
elif isinstance(message_source, platform_events.FriendMessage):
|
elif isinstance(message_source, platform_events.FriendMessage):
|
||||||
args['guild_id'] = str(message_source.sender.id)
|
args['guild_id'] = str(message_source.sender.id)
|
||||||
args['msg_id'] = cached_message_ids[
|
args['msg_id'] = cached_message_ids[str(message_source.message_chain.message_id)]
|
||||||
str(message_source.message_chain.message_id)
|
|
||||||
]
|
|
||||||
await self.bot.api.post_dms(**args)
|
await self.bot.api.post_dms(**args)
|
||||||
elif isinstance(message_source, OfficialGroupMessage):
|
elif isinstance(message_source, OfficialGroupMessage):
|
||||||
if 'file_image' in args: # 暂不支持发送文件图片
|
if 'file_image' in args: # 暂不支持发送文件图片
|
||||||
@@ -502,9 +450,7 @@ class OfficialAdapter(adapter_model.MessagePlatformAdapter):
|
|||||||
args['media'] = uploadMedia
|
args['media'] = uploadMedia
|
||||||
args['msg_type'] = 7
|
args['msg_type'] = 7
|
||||||
|
|
||||||
args['msg_id'] = cached_message_ids[
|
args['msg_id'] = cached_message_ids[str(message_source.message_chain.message_id)]
|
||||||
str(message_source.message_chain.message_id)
|
|
||||||
]
|
|
||||||
args['msg_seq'] = self.group_msg_seq
|
args['msg_seq'] = self.group_msg_seq
|
||||||
self.group_msg_seq += 1
|
self.group_msg_seq += 1
|
||||||
|
|
||||||
@@ -523,9 +469,7 @@ class OfficialAdapter(adapter_model.MessagePlatformAdapter):
|
|||||||
args['media'] = uploadMedia
|
args['media'] = uploadMedia
|
||||||
args['msg_type'] = 7
|
args['msg_type'] = 7
|
||||||
|
|
||||||
args['msg_id'] = cached_message_ids[
|
args['msg_id'] = cached_message_ids[str(message_source.message_chain.message_id)]
|
||||||
str(message_source.message_chain.message_id)
|
|
||||||
]
|
|
||||||
|
|
||||||
args['msg_seq'] = self.c2c_msg_seq
|
args['msg_seq'] = self.c2c_msg_seq
|
||||||
self.c2c_msg_seq += 1
|
self.c2c_msg_seq += 1
|
||||||
@@ -538,9 +482,7 @@ class OfficialAdapter(adapter_model.MessagePlatformAdapter):
|
|||||||
def register_listener(
|
def register_listener(
|
||||||
self,
|
self,
|
||||||
event_type: typing.Type[platform_events.Event],
|
event_type: typing.Type[platform_events.Event],
|
||||||
callback: typing.Callable[
|
callback: typing.Callable[[platform_events.Event, adapter_model.MessagePlatformAdapter], None],
|
||||||
[platform_events.Event, adapter_model.MessagePlatformAdapter], None
|
|
||||||
],
|
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
|
|
||||||
@@ -563,9 +505,7 @@ class OfficialAdapter(adapter_model.MessagePlatformAdapter):
|
|||||||
def unregister_listener(
|
def unregister_listener(
|
||||||
self,
|
self,
|
||||||
event_type: typing.Type[platform_events.Event],
|
event_type: typing.Type[platform_events.Event],
|
||||||
callback: typing.Callable[
|
callback: typing.Callable[[platform_events.Event, adapter_model.MessagePlatformAdapter], None],
|
||||||
[platform_events.Event, adapter_model.MessagePlatformAdapter], None
|
|
||||||
],
|
|
||||||
):
|
):
|
||||||
delattr(self.bot, event_handler_mapping[event_type])
|
delattr(self.bot, event_handler_mapping[event_type])
|
||||||
|
|
||||||
|
|||||||
@@ -35,13 +35,9 @@ class QQOfficialMessageConverter(adapter.MessageConverter):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
async def target2yiri(message: str, message_id: str, pic_url: str, content_type):
|
async def target2yiri(message: str, message_id: str, pic_url: str, content_type):
|
||||||
yiri_msg_list = []
|
yiri_msg_list = []
|
||||||
yiri_msg_list.append(
|
yiri_msg_list.append(platform_message.Source(id=message_id, time=datetime.datetime.now()))
|
||||||
platform_message.Source(id=message_id, time=datetime.datetime.now())
|
|
||||||
)
|
|
||||||
if pic_url is not None:
|
if pic_url is not None:
|
||||||
base64_url = await image.get_qq_official_image_base64(
|
base64_url = await image.get_qq_official_image_base64(pic_url=pic_url, content_type=content_type)
|
||||||
pic_url=pic_url, content_type=content_type
|
|
||||||
)
|
|
||||||
yiri_msg_list.append(platform_message.Image(base64=base64_url))
|
yiri_msg_list.append(platform_message.Image(base64=base64_url))
|
||||||
|
|
||||||
yiri_msg_list.append(platform_message.Plain(text=message))
|
yiri_msg_list.append(platform_message.Plain(text=message))
|
||||||
@@ -75,11 +71,7 @@ class QQOfficialEventConverter(adapter.EventConverter):
|
|||||||
return platform_events.FriendMessage(
|
return platform_events.FriendMessage(
|
||||||
sender=friend,
|
sender=friend,
|
||||||
message_chain=yiri_chain,
|
message_chain=yiri_chain,
|
||||||
time=int(
|
time=int(datetime.datetime.strptime(event.timestamp, '%Y-%m-%dT%H:%M:%S%z').timestamp()),
|
||||||
datetime.datetime.strptime(
|
|
||||||
event.timestamp, '%Y-%m-%dT%H:%M:%S%z'
|
|
||||||
).timestamp()
|
|
||||||
),
|
|
||||||
source_platform_object=event,
|
source_platform_object=event,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -89,9 +81,7 @@ class QQOfficialEventConverter(adapter.EventConverter):
|
|||||||
nickname=event.t,
|
nickname=event.t,
|
||||||
remark='',
|
remark='',
|
||||||
)
|
)
|
||||||
return platform_events.FriendMessage(
|
return platform_events.FriendMessage(sender=friend, message_chain=yiri_chain, source_platform_object=event)
|
||||||
sender=friend, message_chain=yiri_chain, source_platform_object=event
|
|
||||||
)
|
|
||||||
if event.t == 'GROUP_AT_MESSAGE_CREATE':
|
if event.t == 'GROUP_AT_MESSAGE_CREATE':
|
||||||
yiri_chain.insert(0, platform_message.At(target='justbot'))
|
yiri_chain.insert(0, platform_message.At(target='justbot'))
|
||||||
|
|
||||||
@@ -109,11 +99,7 @@ class QQOfficialEventConverter(adapter.EventConverter):
|
|||||||
last_speak_timestamp=0,
|
last_speak_timestamp=0,
|
||||||
mute_time_remaining=0,
|
mute_time_remaining=0,
|
||||||
)
|
)
|
||||||
time = int(
|
time = int(datetime.datetime.strptime(event.timestamp, '%Y-%m-%dT%H:%M:%S%z').timestamp())
|
||||||
datetime.datetime.strptime(
|
|
||||||
event.timestamp, '%Y-%m-%dT%H:%M:%S%z'
|
|
||||||
).timestamp()
|
|
||||||
)
|
|
||||||
return platform_events.GroupMessage(
|
return platform_events.GroupMessage(
|
||||||
sender=sender,
|
sender=sender,
|
||||||
message_chain=yiri_chain,
|
message_chain=yiri_chain,
|
||||||
@@ -136,11 +122,7 @@ class QQOfficialEventConverter(adapter.EventConverter):
|
|||||||
last_speak_timestamp=0,
|
last_speak_timestamp=0,
|
||||||
mute_time_remaining=0,
|
mute_time_remaining=0,
|
||||||
)
|
)
|
||||||
time = int(
|
time = int(datetime.datetime.strptime(event.timestamp, '%Y-%m-%dT%H:%M:%S%z').timestamp())
|
||||||
datetime.datetime.strptime(
|
|
||||||
event.timestamp, '%Y-%m-%dT%H:%M:%S%z'
|
|
||||||
).timestamp()
|
|
||||||
)
|
|
||||||
return platform_events.GroupMessage(
|
return platform_events.GroupMessage(
|
||||||
sender=sender,
|
sender=sender,
|
||||||
message_chain=yiri_chain,
|
message_chain=yiri_chain,
|
||||||
@@ -167,9 +149,7 @@ class QQOfficialAdapter(adapter.MessagePlatformAdapter):
|
|||||||
]
|
]
|
||||||
missing_keys = [key for key in required_keys if key not in config]
|
missing_keys = [key for key in required_keys if key not in config]
|
||||||
if missing_keys:
|
if missing_keys:
|
||||||
raise ParamNotEnoughError(
|
raise ParamNotEnoughError('QQ官方机器人缺少相关配置项,请查看文档或联系管理员')
|
||||||
'QQ官方机器人缺少相关配置项,请查看文档或联系管理员'
|
|
||||||
)
|
|
||||||
|
|
||||||
self.bot = QQOfficialClient(
|
self.bot = QQOfficialClient(
|
||||||
app_id=config['appid'],
|
app_id=config['appid'],
|
||||||
@@ -229,24 +209,18 @@ class QQOfficialAdapter(adapter.MessagePlatformAdapter):
|
|||||||
qq_official_event.d_id,
|
qq_official_event.d_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def send_message(
|
async def send_message(self, target_type: str, target_id: str, message: platform_message.MessageChain):
|
||||||
self, target_type: str, target_id: str, message: platform_message.MessageChain
|
|
||||||
):
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def register_listener(
|
def register_listener(
|
||||||
self,
|
self,
|
||||||
event_type: typing.Type[platform_events.Event],
|
event_type: typing.Type[platform_events.Event],
|
||||||
callback: typing.Callable[
|
callback: typing.Callable[[platform_events.Event, adapter.MessagePlatformAdapter], None],
|
||||||
[platform_events.Event, adapter.MessagePlatformAdapter], None
|
|
||||||
],
|
|
||||||
):
|
):
|
||||||
async def on_message(event: QQOfficialEvent):
|
async def on_message(event: QQOfficialEvent):
|
||||||
self.bot_account_id = 'justbot'
|
self.bot_account_id = 'justbot'
|
||||||
try:
|
try:
|
||||||
return await callback(
|
return await callback(await self.event_converter.target2yiri(event), self)
|
||||||
await self.event_converter.target2yiri(event), self
|
|
||||||
)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
@@ -274,8 +248,6 @@ class QQOfficialAdapter(adapter.MessagePlatformAdapter):
|
|||||||
def unregister_listener(
|
def unregister_listener(
|
||||||
self,
|
self,
|
||||||
event_type: type,
|
event_type: type,
|
||||||
callback: typing.Callable[
|
callback: typing.Callable[[platform_events.Event, MessagePlatformAdapter], None],
|
||||||
[platform_events.Event, MessagePlatformAdapter], None
|
|
||||||
],
|
|
||||||
):
|
):
|
||||||
return super().unregister_listener(event_type, callback)
|
return super().unregister_listener(event_type, callback)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user