refactor to async

This commit is contained in:
XYenon 2022-05-02 18:34:48 +08:00
parent d1e79d8ed0
commit 4b23b770fb
8 changed files with 486 additions and 318 deletions

View File

@ -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

View File

@ -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(

View File

@ -1,3 +1,4 @@
import asyncio
import copy
import logging
import tempfile
@ -5,14 +6,10 @@ 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 efb_qq_slave import BaseClient, QQMessengerChannel
from ehforwarderbot import Chat, Message, MsgType, Status, coordinator
from ehforwarderbot.chat import (
@ -31,8 +28,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 quart.logging import create_serving_logger
from requests import RequestException
from .ChatMgr import ChatManager
@ -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,19 +187,19 @@ class GoCQHttp(BaseClient):
return main_text, messages, at_dict
@self.coolq_bot.on_message
def handle_msg(context):
async def handle_msg(context: Event):
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)
user = await 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)
chat: PrivateChat = await self.chat_manager.build_efb_chat_as_private(context)
else:
chat = self.chat_manager.build_efb_chat_as_group(context)
chat = await self.chat_manager.build_efb_chat_as_group(context)
if "anonymous" not in context or context["anonymous"] is None:
if context["message_type"] == "group":
@ -221,18 +211,18 @@ class GoCQHttp(BaseClient):
uid=ChatID("__{context[uid_prefix]}__".format(context=context)),
)
else:
user = self.get_user_info(qq_uid, group_id=context["group_id"])
user = await 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)
author = await self.chat_manager.build_or_get_efb_member(chat, context)
elif context["message_type"] == "private":
author = chat.other
else:
author = self.chat_manager.build_or_get_efb_member(chat, context)
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)
main_text, messages, at_dict = message_elements_wrapper(context, msg_elements, chat)
main_text, messages, at_dict = await message_elements_wrapper(context, msg_elements, chat)
if main_text != "":
messages.append(self.msg_decorator.qq_text_simple_wrapper(main_text, at_dict))
@ -262,19 +252,19 @@ class GoCQHttp(BaseClient):
send_message_wrapper(efb_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,
)
@ -283,9 +273,9 @@ class GoCQHttp(BaseClient):
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,7 +288,7 @@ 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,
)
@ -306,19 +296,19 @@ class GoCQHttp(BaseClient):
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,
)
@ -327,7 +317,7 @@ class GoCQHttp(BaseClient):
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,27 +334,27 @@ 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)
@self.coolq_bot.on_notice("offline_file")
def handle_offline_file_upload_msg(context):
async def handle_offline_file_upload_msg(context: Event):
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"])
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
@ -376,10 +366,10 @@ class GoCQHttp(BaseClient):
threading.Thread(target=self.async_download_file, args=[], kwargs=param_dict).start()
@self.coolq_bot.on_notice("group_upload")
def handle_group_file_upload_msg(context):
async def handle_group_file_upload_msg(context: Event):
context["event_description"] = "\u2139 Group File Upload Event"
context["uid_prefix"] = "group_upload"
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"]
@ -387,7 +377,7 @@ class GoCQHttp(BaseClient):
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"]
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
@ -404,19 +394,19 @@ class GoCQHttp(BaseClient):
threading.Thread(target=self.async_download_group_file, args=[], kwargs=param_dict).start()
@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 +416,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 +429,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 +439,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 +459,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 +516,30 @@ 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"
config.accesslog = create_serving_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 +549,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 +567,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 +589,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 +606,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 +633,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 +643,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 +658,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 +680,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 +703,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,27 +727,27 @@ 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):
for member in await self.get_group_member_list(group_id):
if member["user_id"] == user_id:
user["is_in_group"] = True
user["in_group_info"] = member
@ -798,9 +757,9 @@ class GoCQHttp(BaseClient):
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 +768,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 +776,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)
res = await self.coolq_bot.call_action(func_name, **kwargs)
except RequestException 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,30 +798,31 @@ 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):
async def check_status_periodically(self, run_once: bool = False):
interval = 300
while True:
self.logger.debug("Start checking status...")
flag = True
interval = 300
try:
flag = self.check_running_status()
flag = await self.check_running_status()
except CoolQDisconnectedException as e:
if self.repeat_counter < 3:
self.deliver_alert_to_master(
@ -909,10 +868,9 @@ class GoCQHttp(BaseClient):
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 run_once:
return
await asyncio.sleep(interval)
def deliver_alert_to_master(self, message: str):
context = {
@ -922,8 +880,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 +891,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,13 +900,14 @@ 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
while True:
self.logger.debug("Start updating friend & group list")
if self.is_connected and self.is_logged_in:
try:
self.update_friend_list()
self.update_group_list()
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()
@ -958,13 +917,13 @@ class GoCQHttp(BaseClient):
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()
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"]
@ -972,7 +931,7 @@ class GoCQHttp(BaseClient):
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 = asyncio.run(self.chat_manager.build_efb_chat_as_group(context))
try:
author = chat.get_member(SystemChatMember.SYSTEM_ID)
except KeyError:
@ -1014,8 +973,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 +983,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,18 +1005,18 @@ 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"
@ -1076,15 +1035,15 @@ 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)
@ -1098,32 +1057,35 @@ 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
while True:
latest_version = self.channel.check_updates()
if latest_version is not None:
self.deliver_alert_to_master(
@ -1132,25 +1094,21 @@ class GoCQHttp(BaseClient):
"<code>pip3 install --upgrade efb-qq-slave</code>".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()
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()

View File

@ -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"])

View File

@ -1,14 +1,12 @@
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
from ehforwarderbot import Message, coordinator
from pkg_resources import resource_filename
# created by JogleLew and jqqqqqqqqqq, optimized based on Tim's emoji support, updated by xzsk2 to mobileqq v8.8.11
qq_emoji_list = {
@ -640,13 +638,6 @@ 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
@ -715,7 +706,7 @@ def download_file(download_url):
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)
return ("Error occurs when downloading files: ") + str(e)
else:
if file.seek(0, 2) <= 0:
raise EOFError("File downloaded is Empty")
@ -732,7 +723,7 @@ def download_user_avatar(uid: str):
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)
return ("Error occurs when downloading files: ") + str(e)
if file.seek(0, 2) <= 0:
raise EOFError("File downloaded is Empty")
file.seek(0)
@ -748,7 +739,7 @@ def download_group_avatar(uid: str):
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)
return ("Error occurs when downloading files: ") + str(e)
if file.seek(0, 2) <= 0:
raise EOFError("File downloaded is Empty")
file.seek(0)

View File

@ -1,3 +1,3 @@
from . import GoCQHttp # noqa: F401
__version__ = "2.2.3"
__version__ = "3.0.0"

228
pdm.lock
View File

@ -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"},

View File

@ -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",
]