255 lines
9.0 KiB
Python
255 lines
9.0 KiB
Python
import io
|
||
import botpy
|
||
from PIL import Image as PILImage
|
||
from botpy.message import Message, DirectMessage
|
||
import re
|
||
import asyncio
|
||
import requests
|
||
from util import general_utils as gu
|
||
|
||
from botpy.types.message import Reference
|
||
from botpy import Client
|
||
import time
|
||
from ._platfrom import Platform
|
||
from ._nakuru_translation_layer import(
|
||
NakuruGuildMessage,
|
||
NakuruGuildMember,
|
||
gocq_compatible_receive,
|
||
gocq_compatible_send
|
||
)
|
||
from typing import Union
|
||
|
||
# QQ 机器人官方框架
|
||
class botClient(Client):
|
||
def set_platform(self, platform: 'QQOfficial'):
|
||
self.platform = platform
|
||
|
||
# 收到频道消息
|
||
async def on_at_message_create(self, message: Message):
|
||
# 转换层
|
||
nakuru_guild_message = gocq_compatible_receive(message)
|
||
self.platform.new_sub_thread(self.platform.handle_msg, (nakuru_guild_message, True))
|
||
|
||
# 收到私聊消息
|
||
async def on_direct_message_create(self, message: DirectMessage):
|
||
# 转换层
|
||
nakuru_guild_message = gocq_compatible_receive(message)
|
||
self.platform.new_sub_thread(self.platform.handle_msg, (nakuru_guild_message, False))
|
||
|
||
class QQOfficial(Platform):
|
||
|
||
def __init__(self, cfg: dict, message_handler: callable, global_object) -> None:
|
||
super().__init__(message_handler)
|
||
|
||
self.loop = asyncio.new_event_loop()
|
||
asyncio.set_event_loop(self.loop)
|
||
|
||
self.qqchan_cnt = 0
|
||
self.waiting: dict = {}
|
||
|
||
self.cfg = cfg
|
||
self.appid = cfg['qqbot']['appid']
|
||
self.token = cfg['qqbot']['token']
|
||
self.secret = cfg['qqbot_secret']
|
||
self.unique_session = cfg['uniqueSessionMode']
|
||
self.logger: gu.Logger = global_object.logger
|
||
|
||
self.intents = botpy.Intents(
|
||
public_guild_messages=True,
|
||
direct_message=cfg['direct_message_mode']
|
||
)
|
||
self.client = botClient(
|
||
intents=self.intents,
|
||
bot_log=False
|
||
)
|
||
self.client.set_platform(self)
|
||
|
||
def run(self):
|
||
try:
|
||
self.loop.run_until_complete(self.client.run(
|
||
appid=self.appid,
|
||
secret=self.secret
|
||
))
|
||
except BaseException as e:
|
||
print(e)
|
||
self.client = botClient(
|
||
intents=self.intents,
|
||
bot_log=False
|
||
)
|
||
self.client.set_platform(self)
|
||
self.client.run(
|
||
appid=self.appid,
|
||
token=self.token
|
||
)
|
||
|
||
async def handle_msg(self, message: NakuruGuildMessage, is_group: bool):
|
||
_t = "/私聊" if not is_group else ""
|
||
self.logger.log(f"{message.sender.nickname}({message.sender.tiny_id}{_t}) -> {self.parse_message_outline(message)}", tag="QQ_OFFICIAL")
|
||
# 解析出 session_id
|
||
if self.unique_session or not is_group:
|
||
session_id = message.sender.user_id
|
||
else:
|
||
session_id = message.channel_id
|
||
|
||
# 解析出 role
|
||
sender_id = str(message.sender.tiny_id)
|
||
if sender_id == self.cfg['admin_qqchan'] or \
|
||
sender_id in self.cfg['other_admins']:
|
||
role = 'admin'
|
||
else:
|
||
role = 'member'
|
||
|
||
message_result = await self.message_handler(
|
||
message=message,
|
||
session_id=session_id,
|
||
role=role,
|
||
platform='qqchan'
|
||
)
|
||
|
||
if message_result is None:
|
||
return
|
||
|
||
self.reply_msg(is_group, message, message_result.result_message)
|
||
if message_result.callback is not None:
|
||
message_result.callback()
|
||
|
||
# 如果是等待回复的消息
|
||
if session_id in self.waiting and self.waiting[session_id] == '':
|
||
self.waiting[session_id] = message
|
||
|
||
def reply_msg(self,
|
||
is_group: bool,
|
||
message: NakuruGuildMessage,
|
||
res: Union[str, list]):
|
||
'''
|
||
回复频道消息
|
||
'''
|
||
self.logger.log(f"{message.sender.nickname}({message.sender.tiny_id}) <- {self.parse_message_outline(res)}", tag="QQ_OFFICIAL")
|
||
self.qqchan_cnt += 1
|
||
|
||
plain_text = ''
|
||
image_path = ''
|
||
msg_ref = None
|
||
|
||
if isinstance(res, list):
|
||
plain_text, image_path = gocq_compatible_send(res)
|
||
elif isinstance(res, str):
|
||
plain_text = res
|
||
|
||
if self.cfg['qq_pic_mode']:
|
||
# 文本转图片,并且加上原来的图片
|
||
if plain_text != '' or image_path != '':
|
||
if image_path is not None and image_path != '':
|
||
if image_path.startswith("http"):
|
||
plain_text += "\n\n" + ""
|
||
else:
|
||
plain_text += "\n\n" + ""
|
||
image_path = gu.create_markdown_image("".join(plain_text))
|
||
plain_text = ""
|
||
|
||
else:
|
||
if image_path is not None and image_path != '':
|
||
msg_ref = None
|
||
if image_path.startswith("http"):
|
||
pic_res = requests.get(image_path, stream = True)
|
||
if pic_res.status_code == 200:
|
||
image = PILImage.open(io.BytesIO(pic_res.content))
|
||
image_path = gu.save_temp_img(image)
|
||
|
||
if message.raw_message is not None and image_path == '': # file_image与message_reference不能同时传入
|
||
msg_ref = Reference(message_id=message.raw_message.id, ignore_get_message_error=False)
|
||
|
||
# 到这里,我们得到了 plain_text,image_path,msg_ref
|
||
data = {
|
||
'content': plain_text,
|
||
'msg_id': message.message_id,
|
||
'message_reference': msg_ref
|
||
}
|
||
if is_group:
|
||
data['channel_id'] = str(message.channel_id)
|
||
else:
|
||
data['guild_id'] = str(message.guild_id)
|
||
if image_path != '':
|
||
data['file_image'] = image_path
|
||
|
||
try:
|
||
# await self._send_wrapper(**data)
|
||
self._send_wrapper(**data)
|
||
except BaseException as e:
|
||
print(e)
|
||
# 分割过长的消息
|
||
if "msg over length" in str(e):
|
||
split_res = []
|
||
split_res.append(plain_text[:len(plain_text)//2])
|
||
split_res.append(plain_text[len(plain_text)//2:])
|
||
for i in split_res:
|
||
data['content'] = i
|
||
# await self._send_wrapper(**data)
|
||
self._send_wrapper(**data)
|
||
else:
|
||
# 发送qq信息
|
||
try:
|
||
# 防止被qq频道过滤消息
|
||
plain_text = plain_text.replace(".", " . ")
|
||
# await self._send_wrapper(**data)
|
||
self._send_wrapper(**data)
|
||
|
||
except BaseException as e:
|
||
try:
|
||
data['content'] = str.join(" ", plain_text)
|
||
# await self._send_wrapper(**data)
|
||
self._send_wrapper(**data)
|
||
except BaseException as e:
|
||
plain_text = re.sub(r'(https|http)?:\/\/(\w|\.|\/|\?|\=|\&|\%)*\b', '[被隐藏的链接]', str(e), flags=re.MULTILINE)
|
||
plain_text = plain_text.replace(".", "·")
|
||
data['content'] = plain_text
|
||
# await self._send_wrapper(**data)
|
||
self._send_wrapper(**data)
|
||
|
||
def _send_wrapper(self, **kwargs):
|
||
if 'channel_id' in kwargs:
|
||
asyncio.run_coroutine_threadsafe(self.client.api.post_message(**kwargs), self.loop).result()
|
||
else:
|
||
asyncio.run_coroutine_threadsafe(self.client.api.post_dms(**kwargs), self.loop).result()
|
||
|
||
|
||
def send_msg(self, channel_id: int, message_chain: list, message_id: int = None):
|
||
'''
|
||
推送消息, 如果有 message_id,那么就是回复消息。非异步。
|
||
'''
|
||
_n = NakuruGuildMessage()
|
||
_n.channel_id = channel_id
|
||
_n.message_id = message_id
|
||
# await self.reply_msg(_n, message_chain)
|
||
self.reply_msg(_n, message_chain)
|
||
|
||
def send(self, message_obj, message_chain: list):
|
||
'''
|
||
发送信息。内容同 reply_msg。非异步。
|
||
'''
|
||
# await self.reply_msg(message_obj, message_chain)
|
||
self.reply_msg(message_obj, message_chain)
|
||
|
||
def wait_for_message(self, channel_id: int) -> NakuruGuildMessage:
|
||
'''
|
||
等待指定 channel_id 的下一条信息,超时 300s 后抛出异常
|
||
'''
|
||
self.waiting[channel_id] = ''
|
||
cnt = 0
|
||
while True:
|
||
if channel_id in self.waiting and self.waiting[channel_id] != '':
|
||
# 去掉
|
||
ret = self.waiting[channel_id]
|
||
del self.waiting[channel_id]
|
||
return ret
|
||
cnt += 1
|
||
if cnt > 300:
|
||
raise Exception("等待消息超时。")
|
||
time.sleep(1)
|
||
|
||
def get_cnt(self):
|
||
return self.qqchan_cnt
|
||
|
||
def set_cnt(self, cnt):
|
||
self.qqchan_cnt = cnt
|