diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 0aef9ac..b10edef 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -15,12 +15,3 @@ repos:
rev: 5.10.1
hooks:
- id: isort
-
- - repo: https://github.com/pre-commit/mirrors-mypy
- rev: v0.942
- hooks:
- - id: mypy
- additional_dependencies:
- - types-requests
- - types-setuptools
- - types-toml
diff --git a/efb_qq_plugin_go_cqhttp/ChatMgr.py b/efb_qq_plugin_go_cqhttp/ChatMgr.py
index ee0698a..2ddb477 100644
--- a/efb_qq_plugin_go_cqhttp/ChatMgr.py
+++ b/efb_qq_plugin_go_cqhttp/ChatMgr.py
@@ -20,10 +20,10 @@ class ChatManager:
channel=self.channel, uid=ChatID("__error_chat__"), name="Chat Missing"
)
- def build_efb_chat_as_private(self, context):
+ async def build_efb_chat_as_private(self, context):
uid = context["user_id"]
if "sender" not in context or "nickname" not in context["sender"]:
- i: dict = self.channel.QQClient.get_stranger_info(uid)
+ i: dict = await self.channel.QQClient.get_stranger_info(uid)
chat_name = ""
if i:
chat_name = i["nickname"]
@@ -37,13 +37,13 @@ class ChatManager:
)
return efb_chat
- def build_or_get_efb_member(self, chat: Chat, context):
+ async def build_or_get_efb_member(self, chat: Chat, context):
member_uid = context["user_id"]
with contextlib.suppress(KeyError):
return chat.get_member(str(member_uid))
chat_name = ""
if "nickname" not in context:
- i: dict = self.channel.QQClient.get_stranger_info(member_uid)
+ i: dict = await self.channel.QQClient.get_stranger_info(member_uid)
chat_name = ""
if i:
chat_name = i["nickname"]
@@ -55,20 +55,20 @@ class ChatManager:
uid=str(member_uid),
)
- def build_efb_chat_as_group(self, context, update_member=False): # Should be cached
+ async def build_efb_chat_as_group(self, context, update_member=False): # Should be cached
is_discuss = False if context["message_type"] == "group" else True
chat_uid = context["discuss_id"] if is_discuss else context["group_id"]
efb_chat = GroupChat(channel=self.channel, uid=str(chat_uid))
if not is_discuss:
efb_chat.uid = "group" + "_" + str(chat_uid)
- i = self.channel.QQClient.get_group_info(chat_uid)
+ i = await self.channel.QQClient.get_group_info(chat_uid)
if i is not None:
efb_chat.name = str(i["group_name"]) if "group_name" not in context else str(context["group_name"])
else:
efb_chat.name = str(chat_uid)
efb_chat.vendor_specific = {"is_discuss": False}
if update_member:
- members = self.channel.QQClient.get_group_member_list(chat_uid, False)
+ members = await self.channel.QQClient.get_group_member_list(chat_uid, False)
if members:
for member in members:
efb_chat.add_member(
diff --git a/efb_qq_plugin_go_cqhttp/GoCQHttp.py b/efb_qq_plugin_go_cqhttp/GoCQHttp.py
index ea18b6e..07d94c6 100644
--- a/efb_qq_plugin_go_cqhttp/GoCQHttp.py
+++ b/efb_qq_plugin_go_cqhttp/GoCQHttp.py
@@ -1,3 +1,4 @@
+import asyncio
import copy
import logging
import tempfile
@@ -5,14 +6,11 @@ import threading
import time
import uuid
from datetime import datetime, timedelta
-from gettext import translation
from typing import Any, BinaryIO, Dict, List, Optional, Tuple, Union
-import cherrypy
-import cqhttp
-from cherrypy._cpserver import Server
-from cherrypy.process.wspbus import states
-from cqhttp import CQHttp
+import aiocqhttp
+from aiocqhttp import CQHttp, Event
+from aiocqhttp.exceptions import NetworkError
from efb_qq_slave import BaseClient, QQMessengerChannel
from ehforwarderbot import Chat, Message, MsgType, Status, coordinator
from ehforwarderbot.chat import (
@@ -31,9 +29,10 @@ from ehforwarderbot.message import MessageCommand, MessageCommands
from ehforwarderbot.status import MessageRemoval
from ehforwarderbot.types import ChatID, MessageID
from ehforwarderbot.utils import extra
+from hypercorn.asyncio import serve
+from hypercorn.config import Config as HyperConfig
from PIL import Image
-from pkg_resources import resource_filename
-from requests import RequestException
+from quart.logging import create_serving_logger
from .ChatMgr import ChatManager
from .Exceptions import (
@@ -63,12 +62,6 @@ class GoCQHttp(BaseClient):
logger: logging.Logger = logging.getLogger(__name__)
channel: QQMessengerChannel
- translator = translation(
- "efb_qq_slave",
- resource_filename("efb_qq_slave", "Clients/CoolQ/locale"),
- fallback=True,
- )
-
friend_list: List[Dict] = []
friend_dict: Dict[int, dict] = {}
stranger_dict: Dict[int, dict] = {}
@@ -80,14 +73,6 @@ class GoCQHttp(BaseClient):
extra_group_list: List[Dict] = []
repeat_counter = 0
update_repeat_counter = 0
- event = threading.Event()
- update_contacts_timer: threading.Timer
- self_update_timer: threading.Timer
- check_status_timer: threading.Timer
- cherryServer: Server
-
- can_send_image: bool = False
- can_send_voice: bool = False
def __init__(self, client_id: str, config: Dict[str, Any], channel):
super().__init__(client_id, config)
@@ -103,10 +88,15 @@ class GoCQHttp(BaseClient):
self.is_logged_in = False
self.msg_decorator = QQMsgProcessor(instance=self)
- def forward_msgs_wrapper(msg_elements: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
+ self.loop = asyncio.get_event_loop()
+ self.shutdown_event = asyncio.Event()
+
+ asyncio.set_event_loop(self.loop)
+
+ async def forward_msgs_wrapper(msg_elements: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
fmt_msgs: List[Dict] = []
for msg in msg_elements:
- from_user = self.get_user_info(msg["sender"]["user_id"])
+ from_user = await self.get_user_info(msg["sender"]["user_id"])
header_text = {"data": {"text": f'{from_user["remark"]}({from_user["nickname"]}):\n'}, "type": "text"}
footer_text = {"data": {"text": "\n- - - - - - - - - - - - - - -\n"}, "type": "text"}
msg["content"].insert(0, header_text)
@@ -116,12 +106,12 @@ class GoCQHttp(BaseClient):
if i == 1:
fmt_msgs.pop()
msg["content"].pop()
- fmt_msgs += forward_msgs_wrapper([inner_msg])
+ fmt_msgs += await forward_msgs_wrapper([inner_msg])
else:
fmt_msgs.append(inner_msg)
return fmt_msgs
- def message_element_wrapper(
+ async def message_element_wrapper(
context: Dict[str, Any], msg_element: Dict[str, Any], chat: Chat
) -> Tuple[str, List[Message], List[Tuple[Tuple[int, int], Union[Chat, ChatMember]]]]:
msg_type = msg_element["type"]
@@ -142,12 +132,12 @@ class GoCQHttp(BaseClient):
elif msg_type == "at":
# todo Recheck if bug exists
g_id = context["group_id"]
- my_uid = self.get_qq_uid()
+ my_uid = await self.get_qq_uid()
self.logger.debug("My QQ uid: %s\n" "QQ mentioned: %s\n", my_uid, msg_data["qq"])
if str(msg_data["qq"]) == "all":
group_card = "all"
else:
- member_info = self.get_user_info(msg_data["qq"], group_id=g_id)["in_group_info"]
+ member_info = (await self.get_user_info(msg_data["qq"], group_id=g_id))["in_group_info"]
group_card = member_info["card"] if member_info["card"] != "" else member_info["nickname"]
self.logger.debug("Group card: {}".format(group_card))
substitution_begin = len(main_text)
@@ -157,34 +147,34 @@ class GoCQHttp(BaseClient):
at_dict = ((substitution_begin, substitution_end), chat.self)
at_list.append(at_dict)
elif msg_type == "reply":
- ref_user = self.get_user_info(msg_data["qq"])
+ ref_user = await self.get_user_info(msg_data["qq"])
main_text = (
f'「{ref_user["remark"]}({ref_user["nickname"]}):{msg_data["text"]}」\n'
"- - - - - - - - - - - - - - -\n"
)
elif msg_type == "forward":
- forward_msgs = self.coolq_api_query("get_forward_msg", message_id=msg_data["id"])["messages"]
+ forward_msgs = (await self.coolq_api_query("get_forward_msg", message_id=msg_data["id"]))["messages"]
logging.debug(f"Forwarded message: {forward_msgs}")
- fmt_forward_msgs = forward_msgs_wrapper(forward_msgs)
+ fmt_forward_msgs = await forward_msgs_wrapper(forward_msgs)
logging.debug(f"Formated forwarded message: {forward_msgs}")
header_msg = {"data": {"text": "合并转发消息开始\n- - - - - - - - - - - - - - -\n"}, "type": "text"}
footer_msg = {"data": {"text": "合并转发消息结束"}, "type": "text"}
fmt_forward_msgs.insert(0, header_msg)
fmt_forward_msgs.append(footer_msg)
- main_text, messages, _ = message_elements_wrapper(context, fmt_forward_msgs, chat)
+ main_text, messages, _ = await message_elements_wrapper(context, fmt_forward_msgs, chat)
return main_text, messages, []
else:
messages.extend(self.call_msg_decorator(msg_type, msg_data, chat))
return main_text, messages, at_list
- def message_elements_wrapper(
+ async def message_elements_wrapper(
context: Dict[str, Any], msg_elements: List[Dict[str, Any]], chat: Chat
) -> Tuple[str, List[Message], Dict[Tuple[int, int], Union[Chat, ChatMember]]]:
messages: List[Message] = []
main_text: str = ""
at_dict: Dict[Tuple[int, int], Union[Chat, ChatMember]] = {}
for msg_element in msg_elements:
- sub_main_text, sub_messages, sub_at_list = message_element_wrapper(context, msg_element, chat)
+ sub_main_text, sub_messages, sub_at_list = await message_element_wrapper(context, msg_element, chat)
main_text_len = len(main_text)
for at_tuple in sub_at_list:
pos = (
@@ -197,95 +187,97 @@ class GoCQHttp(BaseClient):
return main_text, messages, at_dict
@self.coolq_bot.on_message
- def handle_msg(context):
- self.logger.debug(repr(context))
- msg_elements = context["message"]
- qq_uid = context["user_id"]
- chat: Chat
- author: ChatMember
+ async def handle_msg(context: Event):
+ async def _handle_msg():
+ self.logger.debug(repr(context))
+ msg_elements = context["message"]
+ qq_uid = context["user_id"]
+ chat: Chat
+ author: ChatMember
- user = self.get_user_info(qq_uid)
- if context["message_type"] == "private":
- context["alias"] = user["remark"]
- chat: PrivateChat = self.chat_manager.build_efb_chat_as_private(context)
- else:
- chat = self.chat_manager.build_efb_chat_as_group(context)
-
- if "anonymous" not in context or context["anonymous"] is None:
- if context["message_type"] == "group":
- if context["sub_type"] == "notice":
- context["event_description"] = "System Notification"
- context["uid_prefix"] = "group_notification"
- author = chat.add_system_member(
- name=context["event_description"],
- uid=ChatID("__{context[uid_prefix]}__".format(context=context)),
- )
- else:
- user = self.get_user_info(qq_uid, group_id=context["group_id"])
- context["nickname"] = user["remark"]
- context["alias"] = user["in_group_info"]["card"]
- author = self.chat_manager.build_or_get_efb_member(chat, context)
- elif context["message_type"] == "private":
- author = chat.other
+ user = await self.get_user_info(qq_uid)
+ if context["message_type"] == "private":
+ context["alias"] = user["remark"]
+ chat: PrivateChat = await self.chat_manager.build_efb_chat_as_private(context)
else:
- author = self.chat_manager.build_or_get_efb_member(chat, context)
- else: # anonymous user in group
- author = self.chat_manager.build_efb_chat_as_anonymous_user(chat, context)
+ chat = await self.chat_manager.build_efb_chat_as_group(context)
- main_text, messages, at_dict = message_elements_wrapper(context, msg_elements, chat)
+ if "anonymous" not in context or context["anonymous"] is None:
+ if context["message_type"] == "group":
+ if context["sub_type"] == "notice":
+ context["event_description"] = "System Notification"
+ context["uid_prefix"] = "group_notification"
+ author = chat.add_system_member(
+ name=context["event_description"],
+ uid=ChatID("__{context[uid_prefix]}__".format(context=context)),
+ )
+ else:
+ user = await self.get_user_info(qq_uid, group_id=context["group_id"])
+ context["nickname"] = user["remark"]
+ if user["is_in_group"]:
+ context["alias"] = user["in_group_info"]["card"]
+ else:
+ context["alias"] = user["remark"]
+ author = await self.chat_manager.build_or_get_efb_member(chat, context)
+ elif context["message_type"] == "private":
+ author = chat.other
+ else:
+ author = await self.chat_manager.build_or_get_efb_member(chat, context)
+ else: # anonymous user in group
+ author = self.chat_manager.build_efb_chat_as_anonymous_user(chat, context)
- if main_text != "":
- messages.append(self.msg_decorator.qq_text_simple_wrapper(main_text, at_dict))
- coolq_msg_id = context["message_id"]
- for i in range(len(messages)):
- if not isinstance(messages[i], Message):
- continue
- efb_msg: Message = messages[i]
- efb_msg.uid = (
- f"{chat.uid.split('_')[-1]}_{coolq_msg_id}_{i}"
- if i > 0
- else f"{chat.uid.split('_')[-1]}_{coolq_msg_id}"
- )
- efb_msg.chat = chat
- efb_msg.author = author
- # if qq_uid != '80000000':
+ main_text, messages, at_dict = await message_elements_wrapper(context, msg_elements, chat)
- # Append discuss group into group list
- if context["message_type"] == "discuss" and efb_msg.chat not in self.discuss_list:
- self.discuss_list.append(efb_msg.chat)
+ if main_text != "":
+ messages.append(self.msg_decorator.qq_text_simple_wrapper(main_text, at_dict))
+ coolq_msg_id = context["message_id"]
+ for i in range(len(messages)):
+ if not isinstance(messages[i], Message):
+ continue
+ efb_msg: Message = messages[i]
+ efb_msg.uid = (
+ f"{chat.uid.split('_')[-1]}_{coolq_msg_id}_{i}"
+ if i > 0
+ else f"{chat.uid.split('_')[-1]}_{coolq_msg_id}"
+ )
+ efb_msg.chat = chat
+ efb_msg.author = author
+ # if qq_uid != '80000000':
- efb_msg.deliver_to = coordinator.master
+ # Append discuss group into group list
+ if context["message_type"] == "discuss" and efb_msg.chat not in self.discuss_list:
+ self.discuss_list.append(efb_msg.chat)
- def send_message_wrapper(*args, **kwargs):
- threading.Thread(target=async_send_messages_to_master, args=args, kwargs=kwargs).start()
+ efb_msg.deliver_to = coordinator.master
+ async_send_messages_to_master(efb_msg)
- send_message_wrapper(efb_msg)
+ asyncio.create_task(_handle_msg())
@self.coolq_bot.on_notice("group_increase")
- def handle_group_increase_msg(context):
+ async def handle_group_increase_msg(context: Event):
context["event_description"] = "\u2139 Group Member Increase Event"
if (context["sub_type"]) == "invite":
text = "{nickname}({context[user_id]}) joined the group({group_name}) via invitation"
else:
text = "{nickname}({context[user_id]}) joined the group({group_name})"
- original_group = self.get_group_info(context["group_id"], False)
+ original_group = await self.get_group_info(context["group_id"], False)
group_name = context["group_id"]
if original_group is not None and "group_name" in original_group:
group_name = original_group["group_name"]
text = text.format(
- nickname=self.get_stranger_info(context["user_id"])["nickname"],
+ nickname=(await self.get_stranger_info(context["user_id"]))["nickname"],
context=context,
group_name=group_name,
)
context["message"] = text
- self.send_efb_group_notice(context)
+ await self.send_efb_group_notice(context)
@self.coolq_bot.on_notice("group_decrease")
- def handle_group_decrease_msg(context):
+ async def handle_group_decrease_msg(context: Event):
context["event_description"] = "\u2139 Group Member Decrease Event"
- original_group = self.get_group_info(context["group_id"], False)
+ original_group = await self.get_group_info(context["group_id"], False)
group_name = context["group_id"]
if original_group is not None and "group_name" in original_group:
group_name = original_group["group_name"]
@@ -298,36 +290,36 @@ class GoCQHttp(BaseClient):
else:
text = "{nickname}({context[user_id]}) was kicked from the group({group_name})"
text = text.format(
- nickname=self.get_stranger_info(context["user_id"])["nickname"],
+ nickname=(await self.get_stranger_info(context["user_id"]))["nickname"],
context=context,
group_name=group_name,
)
context["message"] = text
- self.send_efb_group_notice(context)
+ await self.send_efb_group_notice(context)
@self.coolq_bot.on_notice("group_admin")
- def handle_group_admin_msg(context):
+ async def handle_group_admin_msg(context: Event):
context["event_description"] = "\u2139 Group Admin Change Event"
if (context["sub_type"]) == "set":
text = "{nickname}({context[user_id]}) has been appointed as the group({group_name}) administrator"
else:
text = "{nickname}({context[user_id]}) has been de-appointed as the group({group_name}) administrator"
- original_group = self.get_group_info(context["group_id"], False)
+ original_group = await self.get_group_info(context["group_id"], False)
group_name = context["group_id"]
if original_group is not None and "group_name" in original_group:
group_name = original_group["group_name"]
text = text.format(
- nickname=self.get_stranger_info(context["user_id"])["nickname"],
+ nickname=(await self.get_stranger_info(context["user_id"]))["nickname"],
context=context,
group_name=group_name,
)
context["message"] = text
- self.send_efb_group_notice(context)
+ await self.send_efb_group_notice(context)
@self.coolq_bot.on_notice("group_ban")
- def handle_group_ban_msg(context):
+ async def handle_group_ban_msg(context: Event):
context["event_description"] = "\u2139 Group Member Restrict Event"
if (context["sub_type"]) == "ban":
text = (
@@ -344,79 +336,86 @@ class GoCQHttp(BaseClient):
)
time_text = ""
- original_group = self.get_group_info(context["group_id"], False)
+ original_group = await self.get_group_info(context["group_id"], False)
group_name = context["group_id"]
if original_group is not None and "group_name" in original_group:
group_name = original_group["group_name"]
text = text.format(
- nickname=self.get_stranger_info(context["user_id"])["nickname"],
+ nickname=(await self.get_stranger_info(context["user_id"]))["nickname"],
context=context,
time=time_text,
group_name=group_name,
- nickname_=self.get_stranger_info(context["operator_id"])["nickname"],
+ nickname_=(await self.get_stranger_info(context["operator_id"]))["nickname"],
)
context["message"] = text
- self.send_efb_group_notice(context)
+ await self.send_efb_group_notice(context)
@self.coolq_bot.on_notice("offline_file")
- def handle_offline_file_upload_msg(context):
- context["event_description"] = "\u2139 Offline File Upload Event"
- context["uid_prefix"] = "offline_file"
- file_info_msg = ("Filename: {file[name]}\n" "File size: {file[size]}").format(file=context["file"])
- user = self.get_user_info(context["user_id"])
- text = "{remark}({nickname}) uploaded a file to you\n"
- text = text.format(remark=user["remark"], nickname=user["nickname"]) + file_info_msg
- context["message"] = text
- self.send_msg_to_master(context)
- param_dict = {
- "context": context,
- "download_url": context["file"]["url"],
- }
- threading.Thread(target=self.async_download_file, args=[], kwargs=param_dict).start()
+ async def handle_offline_file_upload_msg(context: Event):
+ async def _handle_offline_file_upload_msg():
+ context["event_description"] = "\u2139 Offline File Upload Event"
+ context["uid_prefix"] = "offline_file"
+ file_info_msg = ("Filename: {file[name]}\n" "File size: {file[size]}").format(file=context["file"])
+ user = await self.get_user_info(context["user_id"])
+ text = "{remark}({nickname}) uploaded a file to you\n"
+ text = text.format(remark=user["remark"], nickname=user["nickname"]) + file_info_msg
+ context["message"] = text
+ self.send_msg_to_master(context)
+ param_dict = {
+ "context": context,
+ "download_url": context["file"]["url"],
+ }
+ self.async_download_file(**param_dict)
+
+ asyncio.create_task(_handle_offline_file_upload_msg())
@self.coolq_bot.on_notice("group_upload")
- def handle_group_file_upload_msg(context):
- context["event_description"] = "\u2139 Group File Upload Event"
- context["uid_prefix"] = "group_upload"
- original_group = self.get_group_info(context["group_id"], False)
- group_name = context["group_id"]
- if original_group is not None and "group_name" in original_group:
- group_name = original_group["group_name"]
+ async def handle_group_file_upload_msg(context: Event):
+ async def _handle_group_file_upload_msg():
+ context["event_description"] = "\u2139 Group File Upload Event"
+ context["uid_prefix"] = "group_upload"
+ original_group = await self.get_group_info(context["group_id"], False)
+ group_name = context["group_id"]
+ if original_group is not None and "group_name" in original_group:
+ group_name = original_group["group_name"]
- file_info_msg = ("File ID: {file[id]}\n" "Filename: {file[name]}\n" "File size: {file[size]}").format(
- file=context["file"]
- )
- member_info = self.get_user_info(context["user_id"], group_id=context["group_id"])["in_group_info"]
- group_card = member_info["card"] if member_info["card"] != "" else member_info["nickname"]
- text = "{member_card}({context[user_id]}) uploaded a file to group({group_name})\n"
- text = text.format(member_card=group_card, context=context, group_name=group_name) + file_info_msg
- context["message"] = text
- self.send_efb_group_notice(context)
+ file_info_msg = ("File ID: {file[id]}\n" "Filename: {file[name]}\n" "File size: {file[size]}").format(
+ file=context["file"]
+ )
+ member_info = (await self.get_user_info(context["user_id"], group_id=context["group_id"]))[
+ "in_group_info"
+ ]
+ group_card = member_info["card"] if member_info["card"] != "" else member_info["nickname"]
+ text = "{member_card}({context[user_id]}) uploaded a file to group({group_name})\n"
+ text = text.format(member_card=group_card, context=context, group_name=group_name) + file_info_msg
+ context["message"] = text
+ await self.send_efb_group_notice(context)
- param_dict = {
- "context": context,
- "group_id": context["group_id"],
- "file_id": context["file"]["id"],
- "busid": context["file"]["busid"],
- }
+ param_dict = {
+ "context": context,
+ "group_id": context["group_id"],
+ "file_id": context["file"]["id"],
+ "busid": context["file"]["busid"],
+ }
+ self.async_download_group_file(**param_dict)
- threading.Thread(target=self.async_download_group_file, args=[], kwargs=param_dict).start()
+ asyncio.create_task(_handle_group_file_upload_msg())
@self.coolq_bot.on_notice("friend_add")
- def handle_friend_add_msg(context):
+ async def handle_friend_add_msg(context: Event):
context["event_description"] = "\u2139 New Friend Event"
context["uid_prefix"] = "friend_add"
text = "{nickname}({context[user_id]}) has become your friend!"
text = text.format(
- nickname=self.get_stranger_info(context["user_id"])["nickname"],
+ nickname=(await self.get_stranger_info(context["user_id"]))["nickname"],
context=context,
)
context["message"] = text
self.send_msg_to_master(context)
@self.coolq_bot.on_notice("group_recall")
- def handle_group_recall_msg(context):
+ async def handle_group_recall_msg(context: Event):
coolq_msg_id = context["message_id"]
chat = GroupChat(channel=self.channel, uid=f"group_{context['group_id']}")
@@ -426,11 +425,11 @@ class GoCQHttp(BaseClient):
)
@self.coolq_bot.on_notice("friend_recall")
- def handle_friend_recall_msg(context):
+ async def handle_friend_recall_msg(context: Event):
coolq_msg_id = context["message_id"]
try:
- chat: PrivateChat = self.chat_manager.build_efb_chat_as_private(context)
+ chat: PrivateChat = await self.chat_manager.build_efb_chat_as_private(context)
except Exception:
return
efb_msg = Message(chat=chat, uid=MessageID(f"{chat.uid.split('_')[-1]}_{coolq_msg_id}"))
@@ -439,7 +438,7 @@ class GoCQHttp(BaseClient):
)
@self.coolq_bot.on_request("friend") # Add friend request
- def handle_add_friend_request(context):
+ async def handle_add_friend_request(context: Event):
self.logger.debug(repr(context))
context["event_description"] = "\u2139 New Friend Request"
context["uid_prefix"] = "friend_request"
@@ -449,7 +448,7 @@ class GoCQHttp(BaseClient):
"{context[comment]}"
)
text = text.format(
- nickname=self.get_stranger_info(context["user_id"])["nickname"],
+ nickname=(await self.get_stranger_info(context["user_id"]))["nickname"],
context=context,
)
context["message"] = text
@@ -469,34 +468,34 @@ class GoCQHttp(BaseClient):
self.send_msg_to_master(context)
@self.coolq_bot.on_request("group")
- def handle_group_request(context):
+ async def handle_group_request(context: Event):
self.logger.debug(repr(context))
context["uid_prefix"] = "group_request"
- context["group_name"] = ("[Request]") + self.get_group_info(context["group_id"])["group_name"]
+ context["group_name"] = ("[Request]") + (await self.get_group_info(context["group_id"]))["group_name"]
context["group_id_orig"] = context["group_id"]
context["group_id"] = str(context["group_id"]) + "_notification"
context["message_type"] = "group"
context["event_description"] = "\u2139 New Group Join Request"
- original_group = self.get_group_info(context["group_id_orig"], False)
+ original_group = await self.get_group_info(context["group_id_orig"], False)
group_name = context["group_id"]
if original_group is not None and "group_name" in original_group:
group_name = original_group["group_name"]
msg = Message()
msg.uid = "group" + "_" + str(context["group_id"])
msg.author = (self.chat_manager.build_efb_chat_as_system_user(context)).other
- msg.chat = self.chat_manager.build_efb_chat_as_group(context)
+ msg.chat = await self.chat_manager.build_efb_chat_as_group(context)
msg.deliver_to = coordinator.master
msg.type = MsgType.Text
name = ""
- if not self.get_friend_remark(context["user_id"]):
+ if not await self.get_friend_remark(context["user_id"]):
name = "{}({})[{}] ".format(
- self.get_stranger_info(context["user_id"])["nickname"],
- self.get_friend_remark(context["user_id"]),
+ (await self.get_stranger_info(context["user_id"]))["nickname"],
+ await self.get_friend_remark(context["user_id"]),
context["user_id"],
)
else:
name = "{}[{}] ".format(
- self.get_stranger_info(context["user_id"])["nickname"],
+ (await self.get_stranger_info(context["user_id"]))["nickname"],
context["user_id"],
)
msg.text = "{} wants to join the group {}({}). \nHere is the comment: {}".format(
@@ -526,50 +525,32 @@ class GoCQHttp(BaseClient):
)
coordinator.send_message(msg)
- self.check_status_periodically(threading.Event())
- self.update_contacts_timer = threading.Timer(1800, self.update_contacts_periodically, [threading.Event()])
- self.update_contacts_timer.start()
- # threading.Thread(target=self.check_running_status).start()
+ asyncio.run(self.check_status_periodically(run_once=True))
- def run_instance(self, *args, **kwargs):
- # threading.Thread(target=self.coolq_bot.run, args=args, kwargs=kwargs, daemon=True).start()
- cherrypy.tree.graft(self.coolq_bot.wsgi, "/")
- cherrypy.server.unsubscribe()
- self.cherryServer = Server()
- self.cherryServer.socket_host = self.client_config["host"]
- self.cherryServer.socket_port = self.client_config["port"]
- self.cherryServer.subscribe()
- cherrypy.engine.start()
- cherrypy.engine.wait(states.EXITING)
+ def run_instance(self, host: str, port: int, debug: bool = False):
+ def _run():
+ config = HyperConfig()
+ config.access_log_format = "%(h)s %(r)s %(s)s %(b)s %(D)s"
+ access_logger = create_serving_logger()
+ access_logger.setLevel(logging.WARNING)
+ config.accesslog = access_logger
+ config.bind = [f"{host}:{port}"]
+ if debug is not None:
+ self.debug = debug
+ config.use_reloader = debug
+ config.errorlog = config.accesslog
- @extra(
- name=("Restart CoolQ Client"),
- desc=(
- "Force CoolQ to restart\n"
- "Usage: {function_name} [-l] [-c] [-e]\n"
- " -l: Restart and clean log\n"
- " -c: Restart and clean cache\n"
- " -e: Restart and clean event\n"
- ),
- )
- def relogin(self, param: str = ""):
- param_dict = dict()
- if param:
- params = param.split(" ")
- for each_param in params:
- if each_param == " ":
- continue
- if each_param == "-l":
- param_dict["clean_log"] = "true"
- elif each_param == "-c":
- param_dict["clean_cache"] = "true"
- elif each_param == "-e":
- param_dict["clean_event"] = "true"
- else:
- return ("Unknown parameter: {}.").format(param)
- self.logger.debug(repr(param_dict))
- self.coolq_api_query("_set_restart", **param_dict)
- return "Done. Please wait for a while."
+ self.loop.create_task(serve(self.coolq_bot.server_app, config, shutdown_trigger=self.shutdown_event.wait))
+ self.loop.create_task(self.check_status_periodically())
+ self.loop.create_task(self.update_contacts_periodically())
+ self.loop.run_forever()
+
+ self.t = threading.Thread(target=_run)
+ self.t.daemon = True
+ self.t.start()
+
+ def relogin(self):
+ raise NotImplementedError
def logout(self):
raise NotImplementedError
@@ -579,17 +560,17 @@ class GoCQHttp(BaseClient):
desc=("Force efb-qq-slave to refresh status from CoolQ Client.\n" "Usage: {function_name}"),
)
def login(self, param: str = ""):
- self.check_status_periodically(None)
+ asyncio.run(self.check_status_periodically(run_once=True))
return "Done"
- def get_stranger_info(self, user_id: int, no_cache: bool = False) -> Dict[str, Any]:
+ async def get_stranger_info(self, user_id: int, no_cache: bool = False) -> Dict[str, Any]:
user_id = int(user_id)
- return self.get_user_info(user_id, no_cache=no_cache)
+ return await self.get_user_info(user_id, no_cache=no_cache)
- def get_login_info(self) -> Dict[Any, Any]:
- res = self.coolq_bot.get_status()
+ async def get_login_info(self) -> Dict[Any, Any]:
+ res = await self.coolq_bot.get_status()
if "good" in res or "online" in res:
- data = self.coolq_bot.get_login_info()
+ data = await self.coolq_bot.get_login_info()
return {
"status": 0,
"data": {"uid": data["user_id"], "nickname": data["nickname"]},
@@ -597,15 +578,15 @@ class GoCQHttp(BaseClient):
else:
return {"status": 1}
- def get_groups(self) -> List:
+ async def get_groups(self) -> List:
# todo Add support for discuss group iteration
- self.update_group_list() # Force update group list
+ await self.update_group_list() # Force update group list
res = self.group_list
# res = self.coolq_bot.get_group_list()
groups = []
for i in range(len(res)):
context = {"message_type": "group", "group_id": res[i]["group_id"]}
- efb_chat = self.chat_manager.build_efb_chat_as_group(context)
+ efb_chat = await self.chat_manager.build_efb_chat_as_group(context)
groups.append(efb_chat)
for i in range(len(self.extra_group_list)):
does_exist = False
@@ -619,13 +600,13 @@ class GoCQHttp(BaseClient):
"message_type": "group",
"group_id": self.extra_group_list[i]["group_id"],
}
- efb_chat = self.chat_manager.build_efb_chat_as_group(context)
+ efb_chat = await self.chat_manager.build_efb_chat_as_group(context)
groups.append(efb_chat)
return groups + self.discuss_list
- def get_friends(self) -> List:
+ async def get_friends(self) -> List:
try:
- self.update_friend_list() # Force update friend list
+ await self.update_friend_list() # Force update friend list
except CoolQAPIFailureException:
self.deliver_alert_to_master(("Failed to retrieve the friend list.\n" "Only groups are shown."))
return []
@@ -636,7 +617,7 @@ class GoCQHttp(BaseClient):
"nickname": current_user["nickname"],
"alias": current_user["remark"],
}
- efb_chat = self.chat_manager.build_efb_chat_as_private(context)
+ efb_chat = await self.chat_manager.build_efb_chat_as_private(context)
users.append(efb_chat)
return users
@@ -663,7 +644,7 @@ class GoCQHttp(BaseClient):
if msg.edit:
try:
uid_type = msg.uid.split("_")
- self.recall_message(uid_type[1])
+ asyncio.run(self.recall_message(uid_type[1]))
except CoolQAPIFailureException:
raise EFBOperationNotSupported(
("Failed to recall the message!\n" "This message may have already expired.")
@@ -673,7 +654,7 @@ class GoCQHttp(BaseClient):
if msg.text == "kick`":
group_id = chat_type[1]
user_id = msg.target.author.uid
- self.coolq_api_query("set_group_kick", group_id=group_id, user_id=user_id)
+ asyncio.run(self.coolq_api_query("set_group_kick", group_id=group_id, user_id=user_id))
else:
if isinstance(msg.target, Message):
max_length = 50
@@ -688,16 +669,11 @@ class GoCQHttp(BaseClient):
tgt_text,
coolq_text_encode(msg.text),
)
- msg.uid = self.coolq_send_message(chat_type[0], chat_type[1], msg.text)
+ msg.uid = asyncio.run(self.coolq_send_message(chat_type[0], chat_type[1], msg.text))
self.logger.debug("[%s] Sent as a text message. %s", msg.uid, msg.text)
elif msg.type in (MsgType.Image, MsgType.Sticker, MsgType.Animation):
self.logger.info("[%s] Image/Sticker/Animation %s", msg.uid, msg.type)
text = ""
- if not self.can_send_image:
- self.check_features() # Force checking features
- raise EFBOperationNotSupported(
- ("Unable to send image now. Please check your CoolQ version " "or retry later")
- )
if msg.type != MsgType.Sticker:
text += m.coolq_code_image_wrapper(msg.file, msg.path)
else:
@@ -715,23 +691,17 @@ class GoCQHttp(BaseClient):
f.seek(0)
text += m.coolq_code_image_wrapper(f, f.name)
if msg.text:
- msg.uid = self.coolq_send_message(chat_type[0], chat_type[1], text + coolq_text_encode(msg.text))
+ msg.uid = asyncio.run(
+ self.coolq_send_message(chat_type[0], chat_type[1], text + coolq_text_encode(msg.text))
+ )
else:
- msg.uid = self.coolq_send_message(chat_type[0], chat_type[1], text)
+ msg.uid = asyncio.run(self.coolq_send_message(chat_type[0], chat_type[1], text))
# todo More MsgType Support
elif msg.type is MsgType.Voice:
- if not self.can_send_voice:
- self.check_features() # Force checking features
- raise EFBOperationNotSupported(
- (
- "Unable to send voice now. Please check your CoolQ version "
- " and install CoolQ audio library or retry later"
- )
- )
text = m.coolq_voice_image_wrapper(msg.file, msg.path)
- msg.uid = self.coolq_send_message(chat_type[0], chat_type[1], text)
+ msg.uid = asyncio.run(self.coolq_send_message(chat_type[0], chat_type[1], text))
if msg.text:
- self.coolq_send_message(chat_type[0], chat_type[1], msg.text)
+ asyncio.run(self.coolq_send_message(chat_type[0], chat_type[1], msg.text))
return msg
def call_msg_decorator(self, msg_type: str, *args) -> List[Message]:
@@ -744,21 +714,21 @@ class GoCQHttp(BaseClient):
else:
return func(*args)
- def get_qq_uid(self):
- res = self.get_login_info()
+ async def get_qq_uid(self):
+ res = await self.get_login_info()
if res["status"] == 0:
return res["data"]["uid"]
else:
return None
- def get_group_member_list(self, group_id, no_cache=False) -> List[Dict[str, Any]]:
+ async def get_group_member_list(self, group_id, no_cache=False) -> List[Dict[str, Any]]:
if (
no_cache
or (group_id not in self.group_member_dict)
or (datetime.now() - self.group_member_dict[group_id]["time"] > timedelta(hours=1))
): # Force Update
try:
- member_list = self.coolq_api_query("get_group_member_list", group_id=group_id, no_cache=no_cache)
+ member_list = await self.coolq_api_query("get_group_member_list", group_id=group_id, no_cache=no_cache)
except CoolQAPIFailureException as e:
self.deliver_alert_to_master(("Failed the get group member detail.") + "{}".format(e))
return []
@@ -768,39 +738,48 @@ class GoCQHttp(BaseClient):
}
return self.group_member_dict[group_id]["members"]
- def get_user_info(self, user_id: int, group_id: Optional[str] = None, no_cache=False):
+ async def get_user_info(self, user_id: int, group_id: Optional[str] = None, no_cache=False):
user_id = int(user_id)
if no_cache or (not self.friend_list) or (user_id not in self.friend_dict):
- self.update_friend_list()
+ await self.update_friend_list()
friend = self.friend_dict.get(user_id)
if friend:
user = copy.deepcopy(friend)
user["is_friend"] = True
else:
if no_cache:
- stranger = self.coolq_api_query("get_stranger_info", user_id=user_id)
+ stranger = await self.coolq_api_query("get_stranger_info", user_id=user_id)
self.stranger_dict[user_id] = stranger
stranger = self.stranger_dict.get(user_id)
if stranger is None:
- stranger = self.coolq_api_query("get_stranger_info", user_id=user_id)
+ stranger = await self.coolq_api_query("get_stranger_info", user_id=user_id)
self.stranger_dict[user_id] = stranger
user = copy.deepcopy(stranger)
user["is_friend"] = False
if group_id is not None:
user["is_in_group"] = False
- for member in self.get_group_member_list(group_id):
- if member["user_id"] == user_id:
- user["is_in_group"] = True
- user["in_group_info"] = member
- break
+ member = next(
+ filter(lambda member: member["user_id"] == user_id, await self.get_group_member_list(group_id)), None
+ )
+ if member is None:
+ member = next(
+ filter(
+ lambda member: member["user_id"] == user_id,
+ await self.get_group_member_list(group_id, no_cache=True),
+ ),
+ None,
+ )
+ if member is not None:
+ user["is_in_group"] = True
+ user["in_group_info"] = member
remark = user.get("remark")
if not remark:
user["remark"] = user["nickname"]
return user
- def get_group_info(self, group_id, no_cache=False):
+ async def get_group_info(self, group_id, no_cache=False):
if no_cache or not self.group_list:
- self.update_group_list()
+ await self.update_group_list()
group = self.group_dict.get(group_id)
if group:
return group
@@ -809,7 +788,7 @@ class GoCQHttp(BaseClient):
if extra_group["group_id"] == group_id:
return extra_group
try:
- external_group = self.get_external_group_info(group_id)
+ external_group = await self.get_external_group_info(group_id)
except CoolQAPIFailureException:
self.logger.error(f"Get external group({group_id}) info failed.")
return None
@@ -817,18 +796,17 @@ class GoCQHttp(BaseClient):
self.extra_group_list.append(external_group)
return external_group
- def coolq_send_message(self, msg_type, uid, message):
+ async def coolq_send_message(self, msg_type, uid, message):
keyword = msg_type if msg_type != "private" else "user"
- res = self.coolq_api_query("send_msg", message_type=msg_type, **{keyword + "_id": uid}, message=message)
+ res = await self.coolq_api_query("send_msg", message_type=msg_type, **{keyword + "_id": uid}, message=message)
return str(uid) + "_" + str(res["message_id"])
- def _coolq_api_wrapper(self, func_name, **kwargs):
+ async def _coolq_api_wrapper(self, func_name, **kwargs):
try:
- func = getattr(self.coolq_bot, func_name)
- res = func(**kwargs)
- except RequestException as e:
+ res = await self.coolq_bot.call_action(func_name, **kwargs)
+ except NetworkError as e:
raise CoolQDisconnectedException(("Unable to connect to CoolQ Client!" "Error Message:\n{}").format(str(e)))
- except cqhttp.Error as ex:
+ except aiocqhttp.Error as ex:
api_ex = CoolQAPIFailureException(
("CoolQ HTTP API encountered an error!\n" "Status Code:{} " "Return Code:{}").format(
ex.status_code, ex.retcode
@@ -840,64 +818,51 @@ class GoCQHttp(BaseClient):
else:
return res
- def check_running_status(self):
- res = self._coolq_api_wrapper("get_status")
+ async def check_running_status(self):
+ res = await self._coolq_api_wrapper("get_status")
if res["good"] or res["online"]:
return True
else:
raise CoolQOfflineException(("CoolQ Client isn't working correctly!"))
- def coolq_api_query(self, func_name, **kwargs) -> Any:
+ async def coolq_api_query(self, func_name, **kwargs) -> Any:
"""# Do not call get_status too frequently
if self.check_running_status():
return self._coolq_api_wrapper(func_name, **kwargs)
"""
if self.is_logged_in and self.is_connected:
- return self._coolq_api_wrapper(func_name, **kwargs)
+ return await self._coolq_api_wrapper(func_name, **kwargs)
elif self.repeat_counter < 3:
self.deliver_alert_to_master(("Your status is offline.\n" "You may try login with /0_login"))
self.repeat_counter += 1
- def check_status_periodically(self, t_event):
- self.logger.debug("Start checking status...")
- flag = True
+ async def check_status_periodically(self, run_once: bool = False):
interval = 300
- try:
- flag = self.check_running_status()
- except CoolQDisconnectedException as e:
- if self.repeat_counter < 3:
- self.deliver_alert_to_master(
- (
- "We're unable to communicate with CoolQ Client.\n"
- "Please check the connection and credentials provided.\n"
- "{}"
- ).format(str(e))
- )
- self.repeat_counter += 1
- self.is_connected = False
- self.is_logged_in = False
- interval = 3600
- except (CoolQOfflineException, CoolQAPIFailureException):
- if self.repeat_counter < 3:
- self.deliver_alert_to_master(
- (
- "CoolQ is running in abnormal status.\n"
- "You may need to relogin your account "
- "or have a check in CoolQ Client.\n"
- )
- )
- self.repeat_counter += 1
- self.is_connected = True
- self.is_logged_in = False
- interval = 3600
- else:
- if not flag:
+ while True:
+ self.logger.debug("Start checking status...")
+ flag = True
+ try:
+ flag = await self.check_running_status()
+ except CoolQDisconnectedException as e:
if self.repeat_counter < 3:
self.deliver_alert_to_master(
(
- "We don't know why, but status check failed.\n"
- "Please enable debug mode and consult the log "
- "for more details."
+ "We're unable to communicate with CoolQ Client.\n"
+ "Please check the connection and credentials provided.\n"
+ "{}"
+ ).format(str(e))
+ )
+ self.repeat_counter += 1
+ self.is_connected = False
+ self.is_logged_in = False
+ interval = 3600
+ except (CoolQOfflineException, CoolQAPIFailureException):
+ if self.repeat_counter < 3:
+ self.deliver_alert_to_master(
+ (
+ "CoolQ is running in abnormal status.\n"
+ "You may need to relogin your account "
+ "or have a check in CoolQ Client.\n"
)
)
self.repeat_counter += 1
@@ -905,14 +870,27 @@ class GoCQHttp(BaseClient):
self.is_logged_in = False
interval = 3600
else:
- self.logger.debug("Status: OK")
- self.is_connected = True
- self.is_logged_in = True
- self.repeat_counter = 0
- self.check_features()
- if t_event is not None and not t_event.is_set():
- self.check_status_timer = threading.Timer(interval, self.check_status_periodically, [t_event])
- self.check_status_timer.start()
+ if not flag:
+ if self.repeat_counter < 3:
+ self.deliver_alert_to_master(
+ (
+ "We don't know why, but status check failed.\n"
+ "Please enable debug mode and consult the log "
+ "for more details."
+ )
+ )
+ self.repeat_counter += 1
+ self.is_connected = True
+ self.is_logged_in = False
+ interval = 3600
+ else:
+ self.logger.debug("Status: OK")
+ self.is_connected = True
+ self.is_logged_in = True
+ self.repeat_counter = 0
+ if run_once:
+ return
+ await asyncio.sleep(interval)
def deliver_alert_to_master(self, message: str):
context = {
@@ -922,8 +900,8 @@ class GoCQHttp(BaseClient):
}
self.send_msg_to_master(context)
- def update_friend_list(self):
- self.friend_list = self.coolq_api_query("get_friend_list")
+ async def update_friend_list(self):
+ self.friend_list = await self.coolq_api_query("get_friend_list")
if self.friend_list:
self.logger.debug("Update friend list completed. Entries: %s", len(self.friend_list))
for friend in self.friend_list:
@@ -933,8 +911,8 @@ class GoCQHttp(BaseClient):
else:
self.logger.warning("Failed to update friend list")
- def update_group_list(self):
- self.group_list = self.coolq_api_query("get_group_list")
+ async def update_group_list(self):
+ self.group_list = await self.coolq_api_query("get_group_list")
if self.group_list:
self.logger.debug("Update group list completed. Entries: %s", len(self.group_list))
for group in self.group_list:
@@ -942,37 +920,38 @@ class GoCQHttp(BaseClient):
else:
self.logger.warning("Failed to update group list")
- def update_contacts_periodically(self, t_event):
- self.logger.debug("Start updating friend & group list")
+ async def update_contacts_periodically(self, run_once: bool = False):
interval = 1800
- if self.is_connected and self.is_logged_in:
- try:
- self.update_friend_list()
- self.update_group_list()
- except CoolQAPIFailureException as ex:
- if (ex.status_code) == 200 and (ex.retcode) == 104 and self.update_repeat_counter < 3:
- self.send_cookie_expired_alarm()
- if self.update_repeat_counter < 3:
- self.deliver_alert_to_master(("Errors occurred when updating contacts: ") + (ex.message))
- self.update_repeat_counter += 1
- else:
- self.update_repeat_counter = 0
- self.logger.debug("Update completed")
- if t_event is not None and not t_event.is_set():
- self.update_contacts_timer = threading.Timer(interval, self.update_contacts_periodically, [t_event])
- self.update_contacts_timer.start()
+ while True:
+ self.logger.debug("Start updating friend & group list")
+ if self.is_connected and self.is_logged_in:
+ try:
+ await self.update_friend_list()
+ await self.update_group_list()
+ except CoolQAPIFailureException as ex:
+ if (ex.status_code) == 200 and (ex.retcode) == 104 and self.update_repeat_counter < 3:
+ self.send_cookie_expired_alarm()
+ if self.update_repeat_counter < 3:
+ self.deliver_alert_to_master(("Errors occurred when updating contacts: ") + (ex.message))
+ self.update_repeat_counter += 1
+ else:
+ self.update_repeat_counter = 0
+ self.logger.debug("Update completed")
+ if run_once:
+ return
+ await asyncio.sleep(interval)
- def get_friend_remark(self, uid):
+ async def get_friend_remark(self, uid):
if (not self.friend_list) or (uid not in self.friend_dict):
- self.update_friend_list()
+ await self.update_friend_list()
if uid not in self.friend_dict:
return None # I don't think you have such a friend
return self.friend_dict[uid]["remark"]
- def send_efb_group_notice(self, context):
+ async def send_efb_group_notice(self, context):
context["message_type"] = "group"
self.logger.debug(repr(context))
- chat = self.chat_manager.build_efb_chat_as_group(context)
+ chat = await self.chat_manager.build_efb_chat_as_group(context)
try:
author = chat.get_member(SystemChatMember.SYSTEM_ID)
except KeyError:
@@ -1014,8 +993,8 @@ class GoCQHttp(BaseClient):
# As the old saying goes
# A programmer spent 20% of time on coding
# while the rest 80% on considering a variable/function/class name
- def get_external_group_info(self, group_id): # Special thanks to @lwl12 for thinking of this name
- res = self.coolq_api_query("get_group_info", group_id=group_id)
+ async def get_external_group_info(self, group_id): # Special thanks to @lwl12 for thinking of this name
+ res = await self.coolq_api_query("get_group_info", group_id=group_id)
return res
def send_status(self, status: "Status"):
@@ -1024,15 +1003,15 @@ class GoCQHttp(BaseClient):
raise EFBMessageError(("You can only recall your own messages."))
try:
uid_type = status.message.uid.split("_")
- self.recall_message(uid_type[1])
+ asyncio.run(self.recall_message(uid_type[1]))
except CoolQAPIFailureException:
raise EFBMessageError(("Failed to recall the message. This message may have already expired."))
else:
raise EFBOperationNotSupported()
# todo
- def recall_message(self, message_id):
- self.coolq_api_query("delete_msg", message_id=message_id)
+ async def recall_message(self, message_id):
+ await self.coolq_api_query("delete_msg", message_id=message_id)
def send_cookie_expired_alarm(self):
self.deliver_alert_to_master(
@@ -1046,27 +1025,27 @@ class GoCQHttp(BaseClient):
)
)
- def process_friend_request(self, result, flag):
+ async def process_friend_request(self, result, flag):
res = "true" if result == "accept" else "false"
try:
- self.coolq_api_query("set_friend_add_request", approve=res, flag=flag)
+ await self.coolq_api_query("set_friend_add_request", approve=res, flag=flag)
except CoolQAPIFailureException as e:
return ("Failed to process request! Error Message:\n") + getattr(e, "message", repr(e))
return "Done"
- def process_group_request(self, result, flag, sub_type):
+ async def process_group_request(self, result, flag, sub_type):
res = "true" if result == "accept" else "false"
try:
- self.coolq_api_query("set_group_add_request", approve=res, flag=flag, sub_type=sub_type)
+ await self.coolq_api_query("set_group_add_request", approve=res, flag=flag, sub_type=sub_type)
except CoolQAPIFailureException as e:
return ("Failed to process request! Error Message:\n") + getattr(e, "message", repr(e))
return "Done"
- def async_download_file(self, context, download_url):
+ async def async_download_file(self, context, download_url):
res = download_file(download_url)
if isinstance(res, str):
context["message"] = ("[Download] ") + res
- self.send_efb_group_notice(context)
+ await self.send_efb_group_notice(context)
elif res is None:
pass
else:
@@ -1076,17 +1055,17 @@ class GoCQHttp(BaseClient):
efb_msg.uid = str(context["user_id"]) + "_" + str(uuid.uuid4()) + "_" + str(1)
efb_msg.text = "Sent a file\n{}".format(context["file"]["name"])
if context["uid_prefix"] == "offline_file":
- efb_msg.chat = self.chat_manager.build_efb_chat_as_private(context)
+ efb_msg.chat = asyncio.run(self.chat_manager.build_efb_chat_as_private(context))
elif context["uid_prefix"] == "group_upload":
- efb_msg.chat = self.chat_manager.build_efb_chat_as_group(context)
- efb_msg.author = self.chat_manager.build_or_get_efb_member(efb_msg.chat, context)
+ efb_msg.chat = asyncio.run(self.chat_manager.build_efb_chat_as_group(context))
+ efb_msg.author = asyncio.run(self.chat_manager.build_or_get_efb_member(efb_msg.chat, context))
efb_msg.deliver_to = coordinator.master
async_send_messages_to_master(efb_msg)
- def async_download_group_file(self, context, group_id, file_id, busid):
- file = self.coolq_api_query("get_group_file_url", group_id=group_id, file_id=file_id, busid=busid)
+ async def async_download_group_file(self, context, group_id, file_id, busid):
+ file = await self.coolq_api_query("get_group_file_url", group_id=group_id, file_id=file_id, busid=busid)
download_url = file["url"]
- self.async_download_file(context, download_url)
+ await self.async_download_file(context, download_url)
def get_chat_picture(self, chat: "Chat") -> BinaryIO:
chat_type = chat.uid.split("_")
@@ -1098,59 +1077,58 @@ class GoCQHttp(BaseClient):
return download_group_avatar("")
def get_chats(self):
- qq_chats = self.get_friends()
- group_chats = self.get_groups()
- return qq_chats + group_chats
+ async def _get_chats():
+ return await asyncio.gather(self.get_friends(), self.get_groups())
+
+ friend_chats, group_chats = asyncio.run(_get_chats())
+ return friend_chats + group_chats
def get_chat(self, chat_uid: ChatID) -> "Chat":
# todo what is member_uid used for?
chat_type = chat_uid.split("_")
if chat_type[0] == "private":
qq_uid = int(chat_type[1])
- remark = self.get_friend_remark(qq_uid)
+ remark = asyncio.run(self.get_friend_remark(qq_uid))
context: Dict[str, Any] = {"user_id": qq_uid}
if remark is not None:
context["alias"] = remark
- return self.chat_manager.build_efb_chat_as_private(context)
+ return asyncio.run(self.chat_manager.build_efb_chat_as_private(context))
elif chat_type[0] == "group":
group_id = int(chat_type[1])
context = {"message_type": "group", "group_id": group_id}
- return self.chat_manager.build_efb_chat_as_group(context, update_member=True)
+ return asyncio.run(self.chat_manager.build_efb_chat_as_group(context, update_member=True))
elif chat_type[0] == "discuss":
discuss_id = int(chat_type[1])
context = {"message_type": "discuss", "discuss_id": discuss_id}
- return self.chat_manager.build_efb_chat_as_group(context)
+ return asyncio.run(self.chat_manager.build_efb_chat_as_group(context))
raise EFBChatNotFound()
- def check_self_update(self, t_event):
+ async def check_self_update(self, run_once: bool = False):
interval = 60 * 60 * 24
- latest_version = self.channel.check_updates()
- if latest_version is not None:
- self.deliver_alert_to_master(
- "New version({version}) of EFB-QQ-Slave has released! "
- "Please manually update EQS by stopping ehForwarderbot first and then execute "
- "pip3 install --upgrade efb-qq-slave
".format(version=latest_version)
- )
- else:
- if t_event is not None and not t_event.is_set():
- self.self_update_timer = threading.Timer(interval, self.check_self_update, [t_event])
- self.self_update_timer.start()
+ while True:
+ latest_version = self.channel.check_updates()
+ if latest_version is not None:
+ self.deliver_alert_to_master(
+ "New version({version}) of EFB-QQ-Slave has released! "
+ "Please manually update EQS by stopping ehForwarderbot first and then execute "
+ "pip3 install --upgrade efb-qq-slave
".format(version=latest_version)
+ )
+ else:
+ pass
+ if run_once:
+ return
+ await asyncio.sleep(interval)
def poll(self):
- self.check_self_update(threading.Event())
+ asyncio.run(self.check_self_update(run_once=True))
self.run_instance(
host=self.client_config["host"],
port=self.client_config["port"],
debug=False,
)
- self.logger.debug("EQS gracefully shut down")
def stop_polling(self):
- self.update_contacts_timer.cancel()
- self.check_status_timer.cancel()
- self.self_update_timer.cancel()
- cherrypy.engine.exit()
-
- def check_features(self):
- self.can_send_image = self.coolq_api_query("can_send_image")["yes"]
- self.can_send_voice = self.coolq_api_query("can_send_record")["yes"]
+ self.logger.debug("Gracefully stopping QQ Slave")
+ self.shutdown_event.set()
+ self.loop.stop()
+ self.t.join()
diff --git a/efb_qq_plugin_go_cqhttp/MsgDecorator.py b/efb_qq_plugin_go_cqhttp/MsgDecorator.py
index a1136c1..42152a5 100644
--- a/efb_qq_plugin_go_cqhttp/MsgDecorator.py
+++ b/efb_qq_plugin_go_cqhttp/MsgDecorator.py
@@ -1,3 +1,4 @@
+import asyncio
import base64
import html
import json
@@ -218,14 +219,14 @@ class QQMsgProcessor:
else:
return self.qq_text_simple_wrapper(text, at_list)
except Exception:
- return self.qq_group_broadcast_alternative_wrapper(data, chat)
+ return asyncio.run(self.qq_group_broadcast_alternative_wrapper(data, chat))
- def qq_group_broadcast_alternative_wrapper(self, data, chat: Chat):
+ async def qq_group_broadcast_alternative_wrapper(self, data, chat: Chat):
try:
at_list = {}
content_data = json.loads(data["content"])
group_id = content_data["mannounce"]["gc"]
- notice_raw_data = self.inst.coolq_api_query("_get_group_notice", group_id=group_id)
+ notice_raw_data = await self.inst.coolq_api_query("_get_group_notice", group_id=group_id)
notice_data = json.loads(notice_raw_data)
title_data = html.unescape(notice_data[0]["msg"]["title"])
text_data = html.unescape(notice_data[0]["msg"]["text"])
diff --git a/efb_qq_plugin_go_cqhttp/Utils.py b/efb_qq_plugin_go_cqhttp/Utils.py
index 7776b92..8ba0b6c 100644
--- a/efb_qq_plugin_go_cqhttp/Utils.py
+++ b/efb_qq_plugin_go_cqhttp/Utils.py
@@ -1,14 +1,13 @@
import logging
import tempfile
-import urllib.request
-from gettext import translation
from typing import IO, Optional
-from urllib.error import ContentTooShortError, HTTPError, URLError
import pilk
import pydub
+import requests
from ehforwarderbot import Message, coordinator
-from pkg_resources import resource_filename
+
+logger = logging.getLogger(__name__)
# created by JogleLew and jqqqqqqqqqq, optimized based on Tim's emoji support, updated by xzsk2 to mobileqq v8.8.11
qq_emoji_list = {
@@ -640,34 +639,30 @@ qq_sface_list = {
39: "[赞]",
40: "[眨眼]",
}
-translator = translation(
- "efb_qq_slave",
- resource_filename("efb_qq_slave", "Clients/CoolQ/locale"),
- fallback=True,
-)
-_ = translator.gettext
-ngettext = translator.ngettext
def cq_get_image(image_link: str) -> Optional[IO]: # Download image from QQ
- file = tempfile.NamedTemporaryFile()
try:
- urllib.request.urlretrieve(image_link, file.name)
- except (URLError, HTTPError, ContentTooShortError) as e:
- logging.getLogger(__name__).warning("Image download failed.")
- logging.getLogger(__name__).warning(str(e))
- return None
- else:
+ resp = requests.get(image_link)
+ file = tempfile.NamedTemporaryFile()
+ file.write(resp.content)
if file.seek(0, 2) <= 0:
raise EOFError("File downloaded is Empty")
file.seek(0)
- return file
+ except Exception as e:
+ file.close()
+ logger.warning("File download failed.")
+ logger.warning(str(e))
+ return None
+ return file
def async_send_messages_to_master(msg: Message):
- coordinator.send_message(msg)
- if msg.file:
- msg.file.close()
+ try:
+ coordinator.send_message(msg)
+ finally:
+ if msg.file:
+ msg.file.close()
def process_quote_text(text, max_length): # Simple wrapper for processing quoted text
@@ -708,81 +703,76 @@ def param_spliter(str_param):
def download_file(download_url):
- file = tempfile.NamedTemporaryFile()
try:
- opener = urllib.request.build_opener()
- urllib.request.install_opener(opener)
- urllib.request.urlretrieve(download_url, file.name)
- except (URLError, HTTPError, ContentTooShortError) as e:
- logging.getLogger(__name__).warning("Error occurs when downloading files: " + str(e))
- return _("Error occurs when downloading files: ") + str(e)
- else:
+ resp = requests.get(download_url)
+ file = tempfile.NamedTemporaryFile()
+ file.write(resp.content)
if file.seek(0, 2) <= 0:
raise EOFError("File downloaded is Empty")
file.seek(0)
- return file
+ except Exception as e:
+ file.close()
+ logger.warning("Error occurs when downloading files: " + str(e))
+ return ("Error occurs when downloading files: ") + str(e)
+ return file
def download_user_avatar(uid: str):
- file = tempfile.NamedTemporaryFile()
url = "https://q1.qlogo.cn/g?b=qq&nk={}&s=0".format(uid)
try:
- opener = urllib.request.build_opener()
- urllib.request.install_opener(opener)
- urllib.request.urlretrieve(url, file.name)
- except (URLError, HTTPError, ContentTooShortError) as e:
- logging.getLogger(__name__).warning("Error occurs when downloading files: " + str(e))
- return _("Error occurs when downloading files: ") + str(e)
- if file.seek(0, 2) <= 0:
- raise EOFError("File downloaded is Empty")
- file.seek(0)
+ resp = requests.get(url)
+ file = tempfile.NamedTemporaryFile()
+ file.write(resp.content)
+ if file.seek(0, 2) <= 0:
+ raise EOFError("File downloaded is Empty")
+ file.seek(0)
+ except Exception as e:
+ file.close()
+ logger.warning("Error occurs when downloading files: " + str(e))
+ raise
return file
def download_group_avatar(uid: str):
- file = tempfile.NamedTemporaryFile()
url = "https://p.qlogo.cn/gh/{}/{}/".format(uid, uid)
try:
- opener = urllib.request.build_opener()
- urllib.request.install_opener(opener)
- urllib.request.urlretrieve(url, file.name)
- except (URLError, HTTPError, ContentTooShortError) as e:
- logging.getLogger(__name__).warning("Error occurs when downloading files: " + str(e))
- return _("Error occurs when downloading files: ") + str(e)
- if file.seek(0, 2) <= 0:
- raise EOFError("File downloaded is Empty")
- file.seek(0)
+ resp = requests.get(url)
+ file = tempfile.NamedTemporaryFile()
+ file.write(resp.content)
+ if file.seek(0, 2) <= 0:
+ raise EOFError("File downloaded is Empty")
+ file.seek(0)
+ except Exception as e:
+ file.close()
+ logger.warning("Error occurs when downloading files: " + str(e))
+ raise
return file
def download_voice(voice_url: str):
- origin_file = tempfile.NamedTemporaryFile()
try:
- opener = urllib.request.build_opener()
- urllib.request.install_opener(opener)
- urllib.request.urlretrieve(voice_url, origin_file.name)
+ resp = requests.get(voice_url)
+ origin_file = tempfile.NamedTemporaryFile()
+ origin_file.write(resp.content)
+ if origin_file.seek(0, 2) <= 0:
+ raise EOFError("File downloaded is Empty")
+ origin_file.seek(0)
+ silk_header = origin_file.read(10)
+ origin_file.seek(0)
+ if b"#!SILK_V3" in silk_header:
+ with tempfile.NamedTemporaryFile() as pcm_file:
+ pilk.decode(origin_file.name, pcm_file.name)
+ audio_file = tempfile.NamedTemporaryFile()
+ pydub.AudioSegment.from_raw(file=pcm_file, sample_width=2, frame_rate=24000, channels=1).export(
+ audio_file, format="ogg", codec="libopus", parameters=["-vbr", "on"]
+ )
+ else:
+ audio_file = origin_file
except Exception as e:
- logging.getLogger(__name__).warning("Error occurs when downloading files: " + str(e))
origin_file.close()
- raise e
- finally:
- opener.close()
- if origin_file.seek(0, 2) <= 0:
- origin_file.close()
- raise EOFError("File downloaded is Empty")
- origin_file.seek(0)
- silk_header = origin_file.read(10)
- origin_file.seek(0)
- if b"#!SILK_V3" in silk_header:
- with tempfile.NamedTemporaryFile() as pcm_file:
- pilk.decode(origin_file.name, pcm_file.name)
- origin_file.close()
- audio_file = tempfile.NamedTemporaryFile()
- pydub.AudioSegment.from_raw(file=pcm_file, sample_width=2, frame_rate=24000, channels=1).export(
- audio_file, format="ogg", codec="libopus", parameters=["-vbr", "on"]
- )
- else:
- audio_file = origin_file
+ audio_file.close()
+ logger.warning("Error occurs when downloading files: " + str(e))
+ raise
return audio_file
diff --git a/efb_qq_plugin_go_cqhttp/__init__.py b/efb_qq_plugin_go_cqhttp/__init__.py
index 345fd44..28ad84a 100644
--- a/efb_qq_plugin_go_cqhttp/__init__.py
+++ b/efb_qq_plugin_go_cqhttp/__init__.py
@@ -1,3 +1,3 @@
from . import GoCQHttp # noqa: F401
-__version__ = "2.2.3"
+__version__ = "3.0.0"
diff --git a/pdm.lock b/pdm.lock
index e289e91..0b16a83 100644
--- a/pdm.lock
+++ b/pdm.lock
@@ -1,3 +1,30 @@
+[[package]]
+name = "aiocqhttp"
+version = "1.4.3"
+requires_python = ">=3.7"
+summary = "A Python SDK with async I/O for OneBot (CQHTTP)."
+dependencies = [
+ "Quart<1.0,>=0.17",
+ "httpx<1.0,>=0.11",
+]
+
+[[package]]
+name = "aiofiles"
+version = "0.8.0"
+requires_python = ">=3.6,<4.0"
+summary = "File support for asyncio."
+
+[[package]]
+name = "anyio"
+version = "3.5.0"
+requires_python = ">=3.6.2"
+summary = "High level compatibility layer for multiple asynchronous event loop implementations"
+dependencies = [
+ "idna>=2.8",
+ "sniffio>=1.1",
+ "typing-extensions; python_version < \"3.8\"",
+]
+
[[package]]
name = "apscheduler"
version = "3.6.3"
@@ -15,6 +42,11 @@ version = "0.2.1"
requires_python = ">=3.6"
summary = "Backport of the standard library zoneinfo module"
+[[package]]
+name = "blinker"
+version = "1.4"
+summary = "Fast, simple object-to-object and broadcast signaling"
+
[[package]]
name = "bullet"
version = "2.2.0"
@@ -171,6 +203,53 @@ version = "0.18.2"
requires_python = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
summary = "Clean single-source support for Python 3 and 2"
+[[package]]
+name = "h11"
+version = "0.12.0"
+requires_python = ">=3.6"
+summary = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
+
+[[package]]
+name = "h2"
+version = "4.1.0"
+requires_python = ">=3.6.1"
+summary = "HTTP/2 State-Machine based protocol implementation"
+dependencies = [
+ "hpack<5,>=4.0",
+ "hyperframe<7,>=6.0",
+]
+
+[[package]]
+name = "hpack"
+version = "4.0.0"
+requires_python = ">=3.6.1"
+summary = "Pure-Python HPACK header compression"
+
+[[package]]
+name = "httpcore"
+version = "0.14.7"
+requires_python = ">=3.6"
+summary = "A minimal low-level HTTP client."
+dependencies = [
+ "anyio==3.*",
+ "certifi",
+ "h11<0.13,>=0.11",
+ "sniffio==1.*",
+]
+
+[[package]]
+name = "httpx"
+version = "0.22.0"
+requires_python = ">=3.6"
+summary = "The next generation HTTP client."
+dependencies = [
+ "certifi",
+ "charset-normalizer",
+ "httpcore<0.15.0,>=0.14.5",
+ "rfc3986[idna2008]<2,>=1.3",
+ "sniffio",
+]
+
[[package]]
name = "humanize"
version = "4.0.0"
@@ -180,6 +259,26 @@ dependencies = [
"importlib-metadata; python_version < \"3.8\"",
]
+[[package]]
+name = "hypercorn"
+version = "0.13.2"
+requires_python = ">=3.7"
+summary = "A ASGI Server based on Hyper libraries and inspired by Gunicorn"
+dependencies = [
+ "h11",
+ "h2>=3.1.0",
+ "priority",
+ "toml",
+ "typing-extensions>=3.7.4; python_version < \"3.8\"",
+ "wsproto>=0.14.0",
+]
+
+[[package]]
+name = "hyperframe"
+version = "6.0.1"
+requires_python = ">=3.6.1"
+summary = "HTTP/2 framing layer for Python"
+
[[package]]
name = "idna"
version = "3.3"
@@ -308,6 +407,12 @@ dependencies = [
"tempora>=1.8",
]
+[[package]]
+name = "priority"
+version = "2.0.0"
+requires_python = ">=3.6.1"
+summary = "A pure-Python implementation of the HTTP/2 priority tree"
+
[[package]]
name = "pydub"
version = "0.25.1"
@@ -358,6 +463,25 @@ version = "6.0"
requires_python = ">=3.6"
summary = "YAML parser and emitter for Python"
+[[package]]
+name = "quart"
+version = "0.17.0"
+requires_python = ">=3.7"
+summary = "A Python ASGI web microframework with the same API as Flask"
+dependencies = [
+ "aiofiles",
+ "blinker",
+ "click",
+ "hypercorn>=0.11.2",
+ "importlib-metadata; python_version < \"3.8\"",
+ "itsdangerous",
+ "jinja2",
+ "markupsafe",
+ "toml",
+ "typing-extensions; python_version < \"3.8\"",
+ "werkzeug>=2.0.0",
+]
+
[[package]]
name = "requests"
version = "2.27.1"
@@ -378,6 +502,21 @@ dependencies = [
"six>=1.7.0",
]
+[[package]]
+name = "rfc3986"
+version = "1.5.0"
+summary = "Validating URI References per RFC 3986"
+
+[[package]]
+name = "rfc3986"
+version = "1.5.0"
+extras = ["idna2008"]
+summary = "Validating URI References per RFC 3986"
+dependencies = [
+ "idna",
+ "rfc3986<2,>=1.3",
+]
+
[[package]]
name = "ruamel.yaml"
version = "0.17.20"
@@ -405,6 +544,12 @@ version = "1.16.0"
requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
summary = "Python 2 and 3 compatibility utilities"
+[[package]]
+name = "sniffio"
+version = "1.2.0"
+requires_python = ">=3.5"
+summary = "Sniff out which async library your code is running under"
+
[[package]]
name = "tempora"
version = "5.0.1"
@@ -415,6 +560,12 @@ dependencies = [
"pytz",
]
+[[package]]
+name = "toml"
+version = "0.10.2"
+requires_python = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
+summary = "Python Library for Tom's Obvious, Minimal Language"
+
[[package]]
name = "tornado"
version = "6.1"
@@ -456,6 +607,15 @@ version = "2.0.2"
requires_python = ">=3.6"
summary = "The comprehensive WSGI web application library."
+[[package]]
+name = "wsproto"
+version = "1.1.0"
+requires_python = ">=3.7.0"
+summary = "WebSockets state-machine based protocol implementation"
+dependencies = [
+ "h11<1,>=0.9.0",
+]
+
[[package]]
name = "zc.lockfile"
version = "2.0"
@@ -472,9 +632,20 @@ summary = "Backport of pathlib-compatible object wrapper for zip files"
[metadata]
lock_version = "3.1"
-content_hash = "sha256:978b5cbd8b5efd554ae429512f6909ca9e5be239ca11531f954c51c274b984cc"
+content_hash = "sha256:31a92e98cb57c2c24915316aca43a1756ad1d0ad8d5ffdd78631463724d61bd5"
[metadata.files]
+"aiocqhttp 1.4.3" = [
+ {file = "aiocqhttp-1.4.3.tar.gz", hash = "sha256:197de394f97b798515d643814660a41d5ec470bd0b0608682d1e4b553e486768"},
+]
+"aiofiles 0.8.0" = [
+ {file = "aiofiles-0.8.0-py3-none-any.whl", hash = "sha256:7a973fc22b29e9962d0897805ace5856e6a566ab1f0c8e5c91ff6c866519c937"},
+ {file = "aiofiles-0.8.0.tar.gz", hash = "sha256:8334f23235248a3b2e83b2c3a78a22674f39969b96397126cc93664d9a901e59"},
+]
+"anyio 3.5.0" = [
+ {file = "anyio-3.5.0-py3-none-any.whl", hash = "sha256:b5fa16c5ff93fa1046f2eeb5bbff2dad4d3514d6cda61d02816dba34fa8c3c2e"},
+ {file = "anyio-3.5.0.tar.gz", hash = "sha256:a0aeffe2fb1fdf374a8e4b471444f0f3ac4fb9f5a5b542b48824475e0042a5a6"},
+]
"apscheduler 3.6.3" = [
{file = "APScheduler-3.6.3-py2.py3-none-any.whl", hash = "sha256:e8b1ecdb4c7cb2818913f766d5898183c7cb8936680710a4d3a966e02262e526"},
{file = "APScheduler-3.6.3.tar.gz", hash = "sha256:3bb5229eed6fbbdafc13ce962712ae66e175aa214c69bed35a06bffcf0c5e244"},
@@ -497,6 +668,9 @@ content_hash = "sha256:978b5cbd8b5efd554ae429512f6909ca9e5be239ca11531f954c51c27
{file = "backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6"},
{file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"},
]
+"blinker 1.4" = [
+ {file = "blinker-1.4.tar.gz", hash = "sha256:471aee25f3992bd325afa3772f1063dbdbbca947a041b8b89466dc00d606f8b6"},
+]
"bullet 2.2.0" = [
{file = "bullet-2.2.0-py3-none-any.whl", hash = "sha256:d22528deb914ce3ff20a4a000fa5ba37179697f384a0748f90691de8a74cf006"},
{file = "bullet-2.2.0.tar.gz", hash = "sha256:dfa0fa81810ad1a9e688815ca04f24af87ff5cdbe803b42fa634b1f50fc9d887"},
@@ -555,10 +729,38 @@ content_hash = "sha256:978b5cbd8b5efd554ae429512f6909ca9e5be239ca11531f954c51c27
"future 0.18.2" = [
{file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"},
]
+"h11 0.12.0" = [
+ {file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"},
+ {file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"},
+]
+"h2 4.1.0" = [
+ {file = "h2-4.1.0-py3-none-any.whl", hash = "sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d"},
+ {file = "h2-4.1.0.tar.gz", hash = "sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb"},
+]
+"hpack 4.0.0" = [
+ {file = "hpack-4.0.0-py3-none-any.whl", hash = "sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c"},
+ {file = "hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095"},
+]
+"httpcore 0.14.7" = [
+ {file = "httpcore-0.14.7-py3-none-any.whl", hash = "sha256:47d772f754359e56dd9d892d9593b6f9870a37aeb8ba51e9a88b09b3d68cfade"},
+ {file = "httpcore-0.14.7.tar.gz", hash = "sha256:7503ec1c0f559066e7e39bc4003fd2ce023d01cf51793e3c173b864eb456ead1"},
+]
+"httpx 0.22.0" = [
+ {file = "httpx-0.22.0-py3-none-any.whl", hash = "sha256:e35e83d1d2b9b2a609ef367cc4c1e66fd80b750348b20cc9e19d1952fc2ca3f6"},
+ {file = "httpx-0.22.0.tar.gz", hash = "sha256:d8e778f76d9bbd46af49e7f062467e3157a5a3d2ae4876a4bbfd8a51ed9c9cb4"},
+]
"humanize 4.0.0" = [
{file = "humanize-4.0.0-py3-none-any.whl", hash = "sha256:8d86333b8557dacffd4dce1dbe09c81c189e2caf7bb17a970b2212f0f58f10f2"},
{file = "humanize-4.0.0.tar.gz", hash = "sha256:ee1f872fdfc7d2ef4a28d4f80ddde9f96d36955b5d6b0dac4bdeb99502bddb00"},
]
+"hypercorn 0.13.2" = [
+ {file = "Hypercorn-0.13.2-py3-none-any.whl", hash = "sha256:ca18f91ab3fa823cbe9e949738f9f2cc07027cd647c80d8f93e4b1a2a175f112"},
+ {file = "Hypercorn-0.13.2.tar.gz", hash = "sha256:6307be5cbdf6ba411967d4661202dc4f79bd511b5d318bc4eed88b09418427f8"},
+]
+"hyperframe 6.0.1" = [
+ {file = "hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15"},
+ {file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"},
+]
"idna 3.3" = [
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
@@ -730,6 +932,10 @@ content_hash = "sha256:978b5cbd8b5efd554ae429512f6909ca9e5be239ca11531f954c51c27
{file = "portend-3.1.0-py3-none-any.whl", hash = "sha256:9e735cee3a5c1961f09e3f3ba6dc498198c2d70b473d98d0d1504b8d1e7a3d61"},
{file = "portend-3.1.0.tar.gz", hash = "sha256:239e3116045ea823f6df87d6168107ad75ccc0590e37242af0cc1e98c5d224e4"},
]
+"priority 2.0.0" = [
+ {file = "priority-2.0.0-py3-none-any.whl", hash = "sha256:6f8eefce5f3ad59baf2c080a664037bb4725cd0a790d53d59ab4059288faf6aa"},
+ {file = "priority-2.0.0.tar.gz", hash = "sha256:c965d54f1b8d0d0b19479db3924c7c36cf672dbf2aec92d43fbdaf4492ba18c0"},
+]
"pydub 0.25.1" = [
{file = "pydub-0.25.1-py2.py3-none-any.whl", hash = "sha256:65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6"},
{file = "pydub-0.25.1.tar.gz", hash = "sha256:980a33ce9949cab2a569606b65674d748ecbca4f0796887fd6f46173a7b0d30f"},
@@ -799,6 +1005,10 @@ content_hash = "sha256:978b5cbd8b5efd554ae429512f6909ca9e5be239ca11531f954c51c27
{file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
]
+"quart 0.17.0" = [
+ {file = "Quart-0.17.0-py3-none-any.whl", hash = "sha256:69480e7384935feff1f50293a8cb70c5d31f568af94ed792d043bb368b50bd50"},
+ {file = "Quart-0.17.0.tar.gz", hash = "sha256:2cf213d8b83fa701a83e3b3125e9102a937cefd1e97e9583f22ee2fa79139640"},
+]
"requests 2.27.1" = [
{file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
{file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"},
@@ -806,6 +1016,10 @@ content_hash = "sha256:978b5cbd8b5efd554ae429512f6909ca9e5be239ca11531f954c51c27
"retrying 1.3.3" = [
{file = "retrying-1.3.3.tar.gz", hash = "sha256:08c039560a6da2fe4f2c426d0766e284d3b736e355f8dd24b37367b0bb41973b"},
]
+"rfc3986 1.5.0" = [
+ {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"},
+ {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"},
+]
"ruamel.yaml 0.17.20" = [
{file = "ruamel.yaml-0.17.20-py3-none-any.whl", hash = "sha256:810eef9c46523a3f77479c66267a4708255ebe806a2d540078408c2227f011af"},
{file = "ruamel.yaml-0.17.20.tar.gz", hash = "sha256:4b8a33c1efb2b443a93fcaafcfa4d2e445f8e8c29c528d9f5cdafb7cc9e4004c"},
@@ -845,10 +1059,18 @@ content_hash = "sha256:978b5cbd8b5efd554ae429512f6909ca9e5be239ca11531f954c51c27
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
+"sniffio 1.2.0" = [
+ {file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"},
+ {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"},
+]
"tempora 5.0.1" = [
{file = "tempora-5.0.1-py3-none-any.whl", hash = "sha256:fbca6a229af666ea4ea8b2f9f80ac9a074f7cf53a97987855b1d15b6e93fd63b"},
{file = "tempora-5.0.1.tar.gz", hash = "sha256:cba0f197a64883bf3e73657efbc0324d5bf17179e7769b1385b4d75d26cd9127"},
]
+"toml 0.10.2" = [
+ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
+ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
+]
"tornado 6.1" = [
{file = "tornado-6.1-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:d371e811d6b156d82aa5f9a4e08b58debf97c302a35714f6f45e35139c332e32"},
{file = "tornado-6.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:0d321a39c36e5f2c4ff12b4ed58d41390460f798422c4504e09eb5678e09998c"},
@@ -912,6 +1134,10 @@ content_hash = "sha256:978b5cbd8b5efd554ae429512f6909ca9e5be239ca11531f954c51c27
{file = "Werkzeug-2.0.2-py3-none-any.whl", hash = "sha256:63d3dc1cf60e7b7e35e97fa9861f7397283b75d765afcaefd993d6046899de8f"},
{file = "Werkzeug-2.0.2.tar.gz", hash = "sha256:aa2bb6fc8dee8d6c504c0ac1e7f5f7dc5810a9903e793b6f715a9f015bdadb9a"},
]
+"wsproto 1.1.0" = [
+ {file = "wsproto-1.1.0-py3-none-any.whl", hash = "sha256:2218cb57952d90b9fca325c0dcfb08c3bda93e8fd8070b0a17f048e2e47a521b"},
+ {file = "wsproto-1.1.0.tar.gz", hash = "sha256:a2e56bfd5c7cd83c1369d83b5feccd6d37798b74872866e62616e0ecf111bda8"},
+]
"zc.lockfile 2.0" = [
{file = "zc.lockfile-2.0-py2.py3-none-any.whl", hash = "sha256:cc33599b549f0c8a248cb72f3bf32d77712de1ff7ee8814312eb6456b42c015f"},
{file = "zc.lockfile-2.0.tar.gz", hash = "sha256:307ad78227e48be260e64896ec8886edc7eae22d8ec53e4d528ab5537a83203b"},
diff --git a/pyproject.toml b/pyproject.toml
index 65ad123..db21514 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -9,8 +9,9 @@ dependencies = [
"requests~=2.27.1",
"python-magic~=0.4.25",
"Pillow~=9.0.1",
- "cqhttp~=1.3.1",
- "CherryPy~=18.6.1",
+ "aiocqhttp~=1.4.3",
+ "quart~=0.17.0",
+ "hypercorn~=0.13.2",
"pilk~=0.0.2",
"pydub~=0.25.1",
]