efb-qq-plugin-go-cqhttp/efb_qq_plugin_go_cqhttp/GoCQHttp.py

1115 lines
49 KiB
Python
Raw Normal View History

2022-05-02 10:34:48 +00:00
import asyncio
2021-06-03 15:36:22 +00:00
import copy
2021-02-15 15:32:47 +00:00
import logging
import tempfile
import threading
import time
import uuid
2022-02-05 12:58:20 +00:00
from datetime import datetime, timedelta
from typing import Any, BinaryIO, Dict, List, Optional, Tuple, Union
2021-02-15 15:32:47 +00:00
2022-05-02 10:34:48 +00:00
import aiocqhttp
from aiocqhttp import CQHttp, Event
2021-02-15 15:32:47 +00:00
from efb_qq_slave import BaseClient, QQMessengerChannel
2022-02-05 12:58:20 +00:00
from ehforwarderbot import Chat, Message, MsgType, Status, coordinator
from ehforwarderbot.chat import (
ChatMember,
2022-03-09 14:06:40 +00:00
GroupChat,
2022-02-05 12:58:20 +00:00
PrivateChat,
SelfChatMember,
SystemChatMember,
)
from ehforwarderbot.exceptions import (
EFBChatNotFound,
EFBMessageError,
EFBOperationNotSupported,
)
from ehforwarderbot.message import MessageCommand, MessageCommands
2021-02-15 15:32:47 +00:00
from ehforwarderbot.status import MessageRemoval
from ehforwarderbot.types import ChatID, MessageID
2021-02-15 15:32:47 +00:00
from ehforwarderbot.utils import extra
2022-05-02 10:34:48 +00:00
from hypercorn.asyncio import serve
from hypercorn.config import Config as HyperConfig
2022-02-05 12:58:20 +00:00
from PIL import Image
2022-05-02 10:34:48 +00:00
from quart.logging import create_serving_logger
2021-02-15 15:32:47 +00:00
from requests import RequestException
from .ChatMgr import ChatManager
2022-02-05 12:58:20 +00:00
from .Exceptions import (
CoolQAPIFailureException,
CoolQDisconnectedException,
CoolQOfflineException,
)
2021-02-15 15:32:47 +00:00
from .MsgDecorator import QQMsgProcessor
2022-02-05 12:58:20 +00:00
from .Utils import (
async_send_messages_to_master,
coolq_text_encode,
download_file,
download_group_avatar,
download_user_avatar,
process_quote_text,
qq_emoji_list,
strf_time,
2022-02-05 12:58:20 +00:00
)
2021-02-15 15:32:47 +00:00
2021-05-30 17:40:01 +00:00
class GoCQHttp(BaseClient):
2021-06-04 13:20:24 +00:00
client_name: str = "GoCQHttp Client"
client_id: str = "GoCQHttp"
2021-02-15 15:32:47 +00:00
client_config: Dict[str, Any]
coolq_bot: CQHttp = None
logger: logging.Logger = logging.getLogger(__name__)
channel: QQMessengerChannel
2022-03-09 14:06:40 +00:00
friend_list: List[Dict] = []
2021-06-03 14:18:49 +00:00
friend_dict: Dict[int, dict] = {}
2021-06-03 15:36:22 +00:00
stranger_dict: Dict[int, dict] = {}
2022-03-09 14:06:40 +00:00
group_list: List[Dict] = []
2021-06-03 14:18:49 +00:00
group_dict: Dict[int, dict] = {}
group_member_dict: Dict[int, Dict[str, Any]] = {}
group_member_info_dict: Dict[Tuple[int, int], dict] = {}
2022-03-09 14:06:40 +00:00
discuss_list: List[Dict] = []
extra_group_list: List[Dict] = []
2021-02-15 15:32:47 +00:00
repeat_counter = 0
update_repeat_counter = 0
def __init__(self, client_id: str, config: Dict[str, Any], channel):
super().__init__(client_id, config)
self.client_config = config[self.client_id]
2022-02-05 12:58:20 +00:00
self.coolq_bot = CQHttp(
api_root=self.client_config["api_root"],
access_token=self.client_config["access_token"],
)
2021-02-15 15:32:47 +00:00
self.channel = channel
self.chat_manager = ChatManager(channel)
self.is_connected = False
self.is_logged_in = False
self.msg_decorator = QQMsgProcessor(instance=self)
2022-05-02 10:34:48 +00:00
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]]:
2022-03-09 14:06:40 +00:00
fmt_msgs: List[Dict] = []
2022-03-06 12:22:40 +00:00
for msg in msg_elements:
2022-05-02 10:34:48 +00:00
from_user = await self.get_user_info(msg["sender"]["user_id"])
2022-03-06 12:22:40 +00:00
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)
msg["content"].append(footer_text)
for i, inner_msg in enumerate(msg["content"]):
if "content" in inner_msg:
if i == 1:
fmt_msgs.pop()
msg["content"].pop()
2022-05-02 10:34:48 +00:00
fmt_msgs += await forward_msgs_wrapper([inner_msg])
2022-03-06 12:22:40 +00:00
else:
fmt_msgs.append(inner_msg)
return fmt_msgs
2022-05-02 10:34:48 +00:00
async def message_element_wrapper(
2022-02-05 12:58:20 +00:00
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"]
msg_data = msg_element["data"]
main_text: str = ""
2021-06-04 14:04:17 +00:00
messages: List[Message] = []
at_list: List[Tuple[Tuple[int, int], Union[Chat, ChatMember]]] = []
2022-02-05 12:58:20 +00:00
if msg_type == "text":
main_text = msg_data["text"]
elif msg_type == "face":
qq_face = int(msg_data["id"])
2021-06-04 14:04:17 +00:00
if qq_face in qq_emoji_list:
main_text = qq_emoji_list[qq_face]
else:
2022-02-05 12:58:20 +00:00
main_text = "\u2753" # ❓
elif msg_type == "sface":
main_text = "\u2753" # ❓
elif msg_type == "at":
2021-06-04 14:04:17 +00:00
# todo Recheck if bug exists
2022-02-05 12:58:20 +00:00
g_id = context["group_id"]
2022-05-02 10:34:48 +00:00
my_uid = await self.get_qq_uid()
2022-02-05 12:58:20 +00:00
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"
2021-06-04 14:04:17 +00:00
else:
member_info = (await self.get_user_info(msg_data["qq"], group_id=g_id))["in_group_info"]
2022-02-05 12:58:20 +00:00
group_card = member_info["card"] if member_info["card"] != "" else member_info["nickname"]
self.logger.debug("Group card: {}".format(group_card))
2021-06-04 14:04:17 +00:00
substitution_begin = len(main_text)
substitution_end = len(main_text) + len(group_card) + 1
2022-02-05 12:58:20 +00:00
main_text = "@{} ".format(group_card)
if str(my_uid) == str(msg_data["qq"]) or str(msg_data["qq"]) == "all":
2021-06-04 14:04:17 +00:00
at_dict = ((substitution_begin, substitution_end), chat.self)
at_list.append(at_dict)
2022-02-05 12:58:20 +00:00
elif msg_type == "reply":
2022-05-02 10:34:48 +00:00
ref_user = await self.get_user_info(msg_data["qq"])
2022-02-05 12:58:20 +00:00
main_text = (
f'{ref_user["remark"]}{ref_user["nickname"]}{msg_data["text"]}\n'
"- - - - - - - - - - - - - - -\n"
)
2022-03-06 12:22:40 +00:00
elif msg_type == "forward":
forward_msgs = (await self.coolq_api_query("get_forward_msg", message_id=msg_data["id"]))["messages"]
2022-03-06 12:22:40 +00:00
logging.debug(f"Forwarded message: {forward_msgs}")
2022-05-02 10:34:48 +00:00
fmt_forward_msgs = await forward_msgs_wrapper(forward_msgs)
2022-03-06 12:22:40 +00:00
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)
2022-05-02 10:34:48 +00:00
main_text, messages, _ = await message_elements_wrapper(context, fmt_forward_msgs, chat)
2022-03-09 14:06:40 +00:00
return main_text, messages, []
2021-06-04 14:04:17 +00:00
else:
messages.extend(self.call_msg_decorator(msg_type, msg_data, chat))
return main_text, messages, at_list
2022-05-02 10:34:48 +00:00
async def message_elements_wrapper(
2022-02-05 12:58:20 +00:00
context: Dict[str, Any], msg_elements: List[Dict[str, Any]], chat: Chat
) -> Tuple[str, List[Message], Dict[Tuple[int, int], Union[Chat, ChatMember]]]:
2021-06-09 12:54:47 +00:00
messages: List[Message] = []
2022-02-05 12:58:20 +00:00
main_text: str = ""
2021-06-09 12:54:47 +00:00
at_dict: Dict[Tuple[int, int], Union[Chat, ChatMember]] = {}
for msg_element in msg_elements:
2022-05-02 10:34:48 +00:00
sub_main_text, sub_messages, sub_at_list = await message_element_wrapper(context, msg_element, chat)
2021-06-09 12:54:47 +00:00
main_text_len = len(main_text)
for at_tuple in sub_at_list:
2022-02-05 12:58:20 +00:00
pos = (
at_tuple[0][0] + main_text_len,
at_tuple[0][1] + main_text_len,
)
2021-06-09 12:54:47 +00:00
at_dict[pos] = at_tuple[1]
main_text += sub_main_text
messages.extend(sub_messages)
return main_text, messages, at_dict
2021-02-15 15:32:47 +00:00
@self.coolq_bot.on_message
2022-05-02 10:34:48 +00:00
async def handle_msg(context: Event):
2021-02-15 15:32:47 +00:00
self.logger.debug(repr(context))
2022-02-05 12:58:20 +00:00
msg_elements = context["message"]
qq_uid = context["user_id"]
2021-02-15 15:32:47 +00:00
chat: Chat
author: ChatMember
2022-05-02 10:34:48 +00:00
user = await self.get_user_info(qq_uid)
2022-02-05 12:58:20 +00:00
if context["message_type"] == "private":
context["alias"] = user["remark"]
2022-05-02 10:34:48 +00:00
chat: PrivateChat = await self.chat_manager.build_efb_chat_as_private(context)
2021-02-15 15:32:47 +00:00
else:
2022-05-02 10:34:48 +00:00
chat = await self.chat_manager.build_efb_chat_as_group(context)
2021-02-15 15:32:47 +00:00
2022-02-05 12:58:20 +00:00
if "anonymous" not in context or context["anonymous"] is None:
if context["message_type"] == "group":
if context["sub_type"] == "notice":
2022-03-09 14:06:40 +00:00
context["event_description"] = "System Notification"
2022-02-05 12:58:20 +00:00
context["uid_prefix"] = "group_notification"
2021-02-15 15:32:47 +00:00
author = chat.add_system_member(
2022-02-05 12:58:20 +00:00
name=context["event_description"],
uid=ChatID("__{context[uid_prefix]}__".format(context=context)),
2021-02-15 15:32:47 +00:00
)
else:
2022-05-02 10:34:48 +00:00
user = await self.get_user_info(qq_uid, group_id=context["group_id"])
2022-02-05 12:58:20 +00:00
context["nickname"] = user["remark"]
context["alias"] = user["in_group_info"]["card"]
2022-05-02 10:34:48 +00:00
author = await self.chat_manager.build_or_get_efb_member(chat, context)
2022-02-05 12:58:20 +00:00
elif context["message_type"] == "private":
2021-02-15 15:32:47 +00:00
author = chat.other
else:
2022-05-02 10:34:48 +00:00
author = await self.chat_manager.build_or_get_efb_member(chat, context)
2021-02-15 15:32:47 +00:00
else: # anonymous user in group
author = self.chat_manager.build_efb_chat_as_anonymous_user(chat, context)
2022-05-02 10:34:48 +00:00
main_text, messages, at_dict = await message_elements_wrapper(context, msg_elements, chat)
2021-06-04 14:04:17 +00:00
2021-02-15 15:32:47 +00:00
if main_text != "":
2021-06-04 14:04:17 +00:00
messages.append(self.msg_decorator.qq_text_simple_wrapper(main_text, at_dict))
2022-02-05 12:58:20 +00:00
coolq_msg_id = context["message_id"]
2021-02-15 15:32:47 +00:00
for i in range(len(messages)):
if not isinstance(messages[i], Message):
continue
efb_msg: Message = messages[i]
2022-03-09 14:06:40 +00:00
efb_msg.uid = (
f"{chat.uid.split('_')[-1]}_{coolq_msg_id}_{i}"
if i > 0
else f"{chat.uid.split('_')[-1]}_{coolq_msg_id}"
)
2021-02-15 15:32:47 +00:00
efb_msg.chat = chat
efb_msg.author = author
# if qq_uid != '80000000':
# Append discuss group into group list
2022-02-05 12:58:20 +00:00
if context["message_type"] == "discuss" and efb_msg.chat not in self.discuss_list:
2021-02-15 15:32:47 +00:00
self.discuss_list.append(efb_msg.chat)
efb_msg.deliver_to = coordinator.master
def send_message_wrapper(*args, **kwargs):
threading.Thread(target=async_send_messages_to_master, args=args, kwargs=kwargs).start()
send_message_wrapper(efb_msg)
2022-02-05 12:58:20 +00:00
@self.coolq_bot.on_notice("group_increase")
2022-05-02 10:34:48 +00:00
async def handle_group_increase_msg(context: Event):
2022-03-09 14:06:40 +00:00
context["event_description"] = "\u2139 Group Member Increase Event"
2022-02-05 12:58:20 +00:00
if (context["sub_type"]) == "invite":
2022-03-09 14:06:40 +00:00
text = "{nickname}({context[user_id]}) joined the group({group_name}) via invitation"
2021-02-15 15:32:47 +00:00
else:
2022-03-09 14:06:40 +00:00
text = "{nickname}({context[user_id]}) joined the group({group_name})"
2022-02-05 12:58:20 +00:00
2022-05-02 10:34:48 +00:00
original_group = await self.get_group_info(context["group_id"], False)
2022-02-05 12:58:20 +00:00
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=(await self.get_stranger_info(context["user_id"]))["nickname"],
2022-02-05 12:58:20 +00:00
context=context,
group_name=group_name,
)
2021-02-15 15:32:47 +00:00
2022-02-05 12:58:20 +00:00
context["message"] = text
2021-02-15 15:32:47 +00:00
self.send_efb_group_notice(context)
2022-02-05 12:58:20 +00:00
@self.coolq_bot.on_notice("group_decrease")
2022-05-02 10:34:48 +00:00
async def handle_group_decrease_msg(context: Event):
2022-03-09 14:06:40 +00:00
context["event_description"] = "\u2139 Group Member Decrease Event"
2022-05-02 10:34:48 +00:00
original_group = await self.get_group_info(context["group_id"], False)
2022-02-05 12:58:20 +00:00
group_name = context["group_id"]
if original_group is not None and "group_name" in original_group:
group_name = original_group["group_name"]
text = ""
if context["sub_type"] == "kick_me":
2022-03-09 14:06:40 +00:00
text = ("You've been kicked from the group({})").format(group_name)
2021-02-15 15:32:47 +00:00
else:
2022-02-05 12:58:20 +00:00
if context["sub_type"] == "leave":
2022-03-09 14:06:40 +00:00
text = "{nickname}({context[user_id]}) quited the group({group_name})"
2021-02-15 15:32:47 +00:00
else:
2022-03-09 14:06:40 +00:00
text = "{nickname}({context[user_id]}) was kicked from the group({group_name})"
2022-02-05 12:58:20 +00:00
text = text.format(
nickname=(await self.get_stranger_info(context["user_id"]))["nickname"],
2022-02-05 12:58:20 +00:00
context=context,
group_name=group_name,
)
context["message"] = text
2021-02-15 15:32:47 +00:00
self.send_efb_group_notice(context)
@self.coolq_bot.on_notice("group_admin")
2022-05-02 10:34:48 +00:00
async def handle_group_admin_msg(context: Event):
2022-03-14 05:13:26 +00:00
context["event_description"] = "\u2139 Group Admin Change Event"
if (context["sub_type"]) == "set":
2022-03-14 05:13:26 +00:00
text = "{nickname}({context[user_id]}) has been appointed as the group({group_name}) administrator"
else:
2022-03-14 05:13:26 +00:00
text = "{nickname}({context[user_id]}) has been de-appointed as the group({group_name}) administrator"
2022-05-02 10:34:48 +00:00
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=(await self.get_stranger_info(context["user_id"]))["nickname"],
context=context,
group_name=group_name,
)
context["message"] = text
self.send_efb_group_notice(context)
@self.coolq_bot.on_notice("group_ban")
2022-05-02 10:34:48 +00:00
async def handle_group_ban_msg(context: Event):
2022-03-14 05:13:26 +00:00
context["event_description"] = "\u2139 Group Member Restrict Event"
if (context["sub_type"]) == "ban":
2022-03-14 05:13:26 +00:00
text = (
"{nickname}({context[user_id]}) "
"is restricted for speaking for {time} at the group({group_name}) by "
"{nickname_}({context[operator_id]})"
)
time_text = strf_time(context["duration"])
else:
2022-03-14 05:13:26 +00:00
text = (
"{nickname}({context[user_id]}) "
"is lifted from restrictions at the group({group_name}) by "
"{nickname_}({context[operator_id]}){time}"
)
time_text = ""
2022-05-02 10:34:48 +00:00
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=(await self.get_stranger_info(context["user_id"]))["nickname"],
context=context,
time=time_text,
group_name=group_name,
nickname_=(await self.get_stranger_info(context["operator_id"]))["nickname"],
)
context["message"] = text
self.send_efb_group_notice(context)
2022-02-05 12:58:20 +00:00
@self.coolq_bot.on_notice("offline_file")
2022-05-02 10:34:48 +00:00
async def handle_offline_file_upload_msg(context: Event):
2022-03-09 14:06:40 +00:00
context["event_description"] = "\u2139 Offline File Upload Event"
2022-02-05 12:58:20 +00:00
context["uid_prefix"] = "offline_file"
2022-03-09 14:06:40 +00:00
file_info_msg = ("Filename: {file[name]}\n" "File size: {file[size]}").format(file=context["file"])
2022-05-02 10:34:48 +00:00
user = await self.get_user_info(context["user_id"])
2022-03-09 14:06:40 +00:00
text = "{remark}({nickname}) uploaded a file to you\n"
2022-02-05 12:58:20 +00:00
text = text.format(remark=user["remark"], nickname=user["nickname"]) + file_info_msg
context["message"] = text
2021-06-02 16:20:31 +00:00
self.send_msg_to_master(context)
param_dict = {
2022-02-05 12:58:20 +00:00
"context": context,
"download_url": context["file"]["url"],
2021-06-02 16:20:31 +00:00
}
threading.Thread(target=self.async_download_file, args=[], kwargs=param_dict).start()
2022-02-05 12:58:20 +00:00
@self.coolq_bot.on_notice("group_upload")
2022-05-02 10:34:48 +00:00
async def handle_group_file_upload_msg(context: Event):
2022-03-09 14:06:40 +00:00
context["event_description"] = "\u2139 Group File Upload Event"
2022-02-05 12:58:20 +00:00
context["uid_prefix"] = "group_upload"
2022-05-02 10:34:48 +00:00
original_group = await self.get_group_info(context["group_id"], False)
2022-02-05 12:58:20 +00:00
group_name = context["group_id"]
if original_group is not None and "group_name" in original_group:
group_name = original_group["group_name"]
2022-03-09 14:06:40 +00:00
file_info_msg = ("File ID: {file[id]}\n" "Filename: {file[name]}\n" "File size: {file[size]}").format(
2022-02-05 12:58:20 +00:00
file=context["file"]
)
member_info = (await self.get_user_info(context["user_id"], group_id=context["group_id"]))["in_group_info"]
2022-02-05 12:58:20 +00:00
group_card = member_info["card"] if member_info["card"] != "" else member_info["nickname"]
2022-03-09 14:06:40 +00:00
text = "{member_card}({context[user_id]}) uploaded a file to group({group_name})\n"
2022-02-05 12:58:20 +00:00
text = text.format(member_card=group_card, context=context, group_name=group_name) + file_info_msg
context["message"] = text
2021-02-15 15:32:47 +00:00
self.send_efb_group_notice(context)
param_dict = {
2022-02-05 12:58:20 +00:00
"context": context,
"group_id": context["group_id"],
"file_id": context["file"]["id"],
"busid": context["file"]["busid"],
2021-02-15 15:32:47 +00:00
}
2021-06-02 16:20:31 +00:00
threading.Thread(target=self.async_download_group_file, args=[], kwargs=param_dict).start()
2021-02-15 15:32:47 +00:00
2022-02-05 12:58:20 +00:00
@self.coolq_bot.on_notice("friend_add")
2022-05-02 10:34:48 +00:00
async def handle_friend_add_msg(context: Event):
2022-03-09 14:06:40 +00:00
context["event_description"] = "\u2139 New Friend Event"
2022-02-05 12:58:20 +00:00
context["uid_prefix"] = "friend_add"
2022-03-09 14:06:40 +00:00
text = "{nickname}({context[user_id]}) has become your friend!"
2022-02-05 12:58:20 +00:00
text = text.format(
nickname=(await self.get_stranger_info(context["user_id"]))["nickname"],
2022-02-05 12:58:20 +00:00
context=context,
)
context["message"] = text
2021-02-15 15:32:47 +00:00
self.send_msg_to_master(context)
@self.coolq_bot.on_notice("group_recall")
2022-05-02 10:34:48 +00:00
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']}")
2022-03-09 14:06:40 +00:00
efb_msg = Message(chat=chat, uid=MessageID(f"{chat.uid.split('_')[-1]}_{coolq_msg_id}"))
coordinator.send_status(
MessageRemoval(source_channel=self.channel, destination_channel=coordinator.master, message=efb_msg)
)
@self.coolq_bot.on_notice("friend_recall")
2022-05-02 10:34:48 +00:00
async def handle_friend_recall_msg(context: Event):
coolq_msg_id = context["message_id"]
try:
2022-05-02 10:34:48 +00:00
chat: PrivateChat = await self.chat_manager.build_efb_chat_as_private(context)
2022-03-09 14:06:40 +00:00
except Exception:
return
2022-03-09 14:06:40 +00:00
efb_msg = Message(chat=chat, uid=MessageID(f"{chat.uid.split('_')[-1]}_{coolq_msg_id}"))
coordinator.send_status(
MessageRemoval(source_channel=self.channel, destination_channel=coordinator.master, message=efb_msg)
)
2022-02-05 12:58:20 +00:00
@self.coolq_bot.on_request("friend") # Add friend request
2022-05-02 10:34:48 +00:00
async def handle_add_friend_request(context: Event):
2021-02-15 15:32:47 +00:00
self.logger.debug(repr(context))
2022-03-09 14:06:40 +00:00
context["event_description"] = "\u2139 New Friend Request"
2022-02-05 12:58:20 +00:00
context["uid_prefix"] = "friend_request"
2022-03-09 14:06:40 +00:00
text = (
2022-02-05 12:58:20 +00:00
"{nickname}({context[user_id]}) wants to be your friend!\n"
"Here is the verification comment:\n"
"{context[comment]}"
)
text = text.format(
nickname=(await self.get_stranger_info(context["user_id"]))["nickname"],
2022-02-05 12:58:20 +00:00
context=context,
)
context["message"] = text
commands = [
MessageCommand(
2022-03-09 14:06:40 +00:00
name=("Accept"),
2022-02-05 12:58:20 +00:00
callable_name="process_friend_request",
kwargs={"result": "accept", "flag": context["flag"]},
),
MessageCommand(
2022-03-09 14:06:40 +00:00
name=("Decline"),
2022-02-05 12:58:20 +00:00
callable_name="process_friend_request",
kwargs={"result": "decline", "flag": context["flag"]},
),
]
context["commands"] = commands
2021-02-15 15:32:47 +00:00
self.send_msg_to_master(context)
2022-02-05 12:58:20 +00:00
@self.coolq_bot.on_request("group")
2022-05-02 10:34:48 +00:00
async def handle_group_request(context: Event):
2021-02-15 15:32:47 +00:00
self.logger.debug(repr(context))
2022-02-05 12:58:20 +00:00
context["uid_prefix"] = "group_request"
context["group_name"] = ("[Request]") + (await self.get_group_info(context["group_id"]))["group_name"]
2022-02-05 12:58:20 +00:00
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"
2022-05-02 10:34:48 +00:00
original_group = await self.get_group_info(context["group_id_orig"], False)
2022-02-05 12:58:20 +00:00
group_name = context["group_id"]
if original_group is not None and "group_name" in original_group:
group_name = original_group["group_name"]
2021-02-15 15:32:47 +00:00
msg = Message()
2022-02-05 12:58:20 +00:00
msg.uid = "group" + "_" + str(context["group_id"])
msg.author = (self.chat_manager.build_efb_chat_as_system_user(context)).other
2022-05-02 10:34:48 +00:00
msg.chat = await self.chat_manager.build_efb_chat_as_group(context)
2021-02-15 15:32:47 +00:00
msg.deliver_to = coordinator.master
msg.type = MsgType.Text
name = ""
2022-05-02 10:34:48 +00:00
if not await self.get_friend_remark(context["user_id"]):
2021-02-15 15:32:47 +00:00
name = "{}({})[{}] ".format(
(await self.get_stranger_info(context["user_id"]))["nickname"],
2022-05-02 10:34:48 +00:00
await self.get_friend_remark(context["user_id"]),
2022-02-05 12:58:20 +00:00
context["user_id"],
)
2021-02-15 15:32:47 +00:00
else:
2022-02-05 12:58:20 +00:00
name = "{}[{}] ".format(
(await self.get_stranger_info(context["user_id"]))["nickname"],
2022-02-05 12:58:20 +00:00
context["user_id"],
)
2021-02-15 15:32:47 +00:00
msg.text = "{} wants to join the group {}({}). \nHere is the comment: {}".format(
2022-02-05 12:58:20 +00:00
name, group_name, context["group_id_orig"], context["comment"]
)
msg.commands = MessageCommands(
[
MessageCommand(
2022-03-09 14:06:40 +00:00
name=("Accept"),
2022-02-05 12:58:20 +00:00
callable_name="process_group_request",
kwargs={
"result": "accept",
"flag": context["flag"],
"sub_type": context["sub_type"],
},
),
MessageCommand(
2022-03-09 14:06:40 +00:00
name=("Decline"),
2022-02-05 12:58:20 +00:00
callable_name="process_group_request",
kwargs={
"result": "decline",
"flag": context["flag"],
"sub_type": context["sub_type"],
},
),
]
2021-02-15 15:32:47 +00:00
)
coordinator.send_message(msg)
2022-05-02 10:34:48 +00:00
asyncio.run(self.check_status_periodically(run_once=True))
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
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
2021-02-15 15:32:47 +00:00
def logout(self):
raise NotImplementedError
2022-02-05 12:58:20 +00:00
@extra(
2022-03-09 14:06:40 +00:00
name=("Check CoolQ Status"),
desc=("Force efb-qq-slave to refresh status from CoolQ Client.\n" "Usage: {function_name}"),
2022-02-05 12:58:20 +00:00
)
2021-02-15 15:32:47 +00:00
def login(self, param: str = ""):
2022-05-02 10:34:48 +00:00
asyncio.run(self.check_status_periodically(run_once=True))
2022-02-05 12:58:20 +00:00
return "Done"
2021-02-15 15:32:47 +00:00
2022-05-02 10:34:48 +00:00
async def get_stranger_info(self, user_id: int, no_cache: bool = False) -> Dict[str, Any]:
2021-06-03 15:36:22 +00:00
user_id = int(user_id)
2022-05-02 10:34:48 +00:00
return await self.get_user_info(user_id, no_cache=no_cache)
2021-02-15 15:32:47 +00:00
2022-05-02 10:34:48 +00:00
async def get_login_info(self) -> Dict[Any, Any]:
res = await self.coolq_bot.get_status()
2022-02-05 12:58:20 +00:00
if "good" in res or "online" in res:
2022-05-02 10:34:48 +00:00
data = await self.coolq_bot.get_login_info()
2022-02-05 12:58:20 +00:00
return {
"status": 0,
"data": {"uid": data["user_id"], "nickname": data["nickname"]},
}
2021-02-15 15:32:47 +00:00
else:
2022-02-05 12:58:20 +00:00
return {"status": 1}
2021-02-15 15:32:47 +00:00
2022-05-02 10:34:48 +00:00
async def get_groups(self) -> List:
2021-02-15 15:32:47 +00:00
# todo Add support for discuss group iteration
2022-05-02 10:34:48 +00:00
await self.update_group_list() # Force update group list
2021-02-15 15:32:47 +00:00
res = self.group_list
# res = self.coolq_bot.get_group_list()
groups = []
for i in range(len(res)):
2022-02-05 12:58:20 +00:00
context = {"message_type": "group", "group_id": res[i]["group_id"]}
2022-05-02 10:34:48 +00:00
efb_chat = await self.chat_manager.build_efb_chat_as_group(context)
2021-02-15 15:32:47 +00:00
groups.append(efb_chat)
for i in range(len(self.extra_group_list)):
does_exist = False
2022-02-05 12:58:20 +00:00
for _j in range(len(res)):
if str(self.extra_group_list[i]["group_id"]) == str(res[i]["group_id"]):
2021-02-15 15:32:47 +00:00
does_exist = True
break
if does_exist:
continue
2022-02-05 12:58:20 +00:00
context = {
"message_type": "group",
"group_id": self.extra_group_list[i]["group_id"],
}
2022-05-02 10:34:48 +00:00
efb_chat = await self.chat_manager.build_efb_chat_as_group(context)
2021-02-15 15:32:47 +00:00
groups.append(efb_chat)
return groups + self.discuss_list
2022-05-02 10:34:48 +00:00
async def get_friends(self) -> List:
2021-02-15 15:32:47 +00:00
try:
2022-05-02 10:34:48 +00:00
await self.update_friend_list() # Force update friend list
2021-02-15 15:32:47 +00:00
except CoolQAPIFailureException:
2022-03-09 14:06:40 +00:00
self.deliver_alert_to_master(("Failed to retrieve the friend list.\n" "Only groups are shown."))
2021-02-15 15:32:47 +00:00
return []
users = []
2021-06-03 14:18:49 +00:00
for current_user in self.friend_list:
2022-02-05 12:58:20 +00:00
context = {
"user_id": str(current_user["user_id"]),
"nickname": current_user["nickname"],
"alias": current_user["remark"],
}
2022-05-02 10:34:48 +00:00
efb_chat = await self.chat_manager.build_efb_chat_as_private(context)
2021-02-15 15:32:47 +00:00
users.append(efb_chat)
return users
def receive_message(self):
# Replaced by handle_msg()
pass
2022-02-05 12:58:20 +00:00
def send_message(self, msg: "Message") -> "Message":
2021-02-15 15:32:47 +00:00
# todo Add support for edited message
"""
self.logger.info("[%s] Sending message to WeChat:\n"
"uid: %s\n"
"Type: %s\n"
"Text: %s\n"
"Target Chat: %s\n"
"Target uid: %s\n",
msg.uid,
msg.chat.chat_uid, msg.type, msg.text, repr(msg.target.chat), msg.target.uid)
"""
m = QQMsgProcessor(instance=self)
2022-02-05 12:58:20 +00:00
chat_type = msg.chat.uid.split("_")
2021-02-15 15:32:47 +00:00
2022-02-05 12:58:20 +00:00
self.logger.debug("[%s] Is edited: %s", msg.uid, msg.edit)
2021-02-15 15:32:47 +00:00
if msg.edit:
2021-06-02 13:18:24 +00:00
try:
2022-02-05 12:58:20 +00:00
uid_type = msg.uid.split("_")
2022-05-02 10:34:48 +00:00
asyncio.run(self.recall_message(uid_type[1]))
2021-06-02 13:18:24 +00:00
except CoolQAPIFailureException:
2022-02-05 12:58:20 +00:00
raise EFBOperationNotSupported(
2022-03-09 14:06:40 +00:00
("Failed to recall the message!\n" "This message may have already expired.")
2022-02-05 12:58:20 +00:00
)
2021-02-15 15:32:47 +00:00
if msg.type in [MsgType.Text, MsgType.Link]:
if msg.text == "kick`":
group_id = chat_type[1]
user_id = msg.target.author.uid
2022-05-02 10:34:48 +00:00
asyncio.run(self.coolq_api_query("set_group_kick", group_id=group_id, user_id=user_id))
2021-02-15 15:32:47 +00:00
else:
if isinstance(msg.target, Message):
max_length = 50
tgt_text = coolq_text_encode(process_quote_text(msg.target.text, max_length))
tgt_alias = ""
2022-02-05 12:58:20 +00:00
if chat_type[0] != "private" and not isinstance(msg.target.author, SelfChatMember):
2021-02-15 15:32:47 +00:00
tgt_alias += m.coolq_code_at_wrapper(msg.target.author.uid)
else:
tgt_alias = ""
2022-02-05 12:58:20 +00:00
msg.text = "%s%s\n\n%s" % (
tgt_alias,
tgt_text,
coolq_text_encode(msg.text),
)
2022-05-02 10:34:48 +00:00
msg.uid = asyncio.run(self.coolq_send_message(chat_type[0], chat_type[1], msg.text))
2022-02-05 12:58:20 +00:00
self.logger.debug("[%s] Sent as a text message. %s", msg.uid, msg.text)
2021-02-15 15:32:47 +00:00
elif msg.type in (MsgType.Image, MsgType.Sticker, MsgType.Animation):
self.logger.info("[%s] Image/Sticker/Animation %s", msg.uid, msg.type)
2022-02-05 12:58:20 +00:00
text = ""
2021-06-02 13:18:24 +00:00
if msg.type != MsgType.Sticker:
text += m.coolq_code_image_wrapper(msg.file, msg.path)
2021-02-15 15:32:47 +00:00
else:
2021-06-02 13:18:24 +00:00
with tempfile.NamedTemporaryFile(suffix=".gif") as f:
img = Image.open(msg.file)
try:
alpha = img.split()[3]
mask = Image.eval(alpha, lambda a: 255 if a <= 128 else 0)
except IndexError:
mask = Image.eval(img.split()[0], lambda a: 0)
2022-02-05 12:58:20 +00:00
img = img.convert("RGB").convert("P", palette=Image.ADAPTIVE, colors=255)
2021-06-02 13:18:24 +00:00
img.paste(255, mask)
img.save(f, transparency=255)
msg.file.close()
f.seek(0)
text += m.coolq_code_image_wrapper(f, f.name)
2021-02-15 15:32:47 +00:00
if msg.text:
2022-05-02 10:34:48 +00:00
msg.uid = asyncio.run(
self.coolq_send_message(chat_type[0], chat_type[1], text + coolq_text_encode(msg.text))
)
2021-07-19 05:34:22 +00:00
else:
2022-05-02 10:34:48 +00:00
msg.uid = asyncio.run(self.coolq_send_message(chat_type[0], chat_type[1], text))
2021-02-15 15:32:47 +00:00
# todo More MsgType Support
elif msg.type is MsgType.Voice:
text = m.coolq_voice_image_wrapper(msg.file, msg.path)
2022-05-02 10:34:48 +00:00
msg.uid = asyncio.run(self.coolq_send_message(chat_type[0], chat_type[1], text))
2021-02-15 15:32:47 +00:00
if msg.text:
2022-05-02 10:34:48 +00:00
asyncio.run(self.coolq_send_message(chat_type[0], chat_type[1], msg.text))
2021-02-15 15:32:47 +00:00
return msg
def call_msg_decorator(self, msg_type: str, *args) -> List[Message]:
try:
2022-02-05 12:58:20 +00:00
func = getattr(self.msg_decorator, "qq_{}_wrapper".format(msg_type))
except AttributeError:
msg = f"Unsupported message type: {msg_type}"
self.logger.error(msg)
return self.msg_decorator.qq_unsupported_wrapper(msg)
else:
return func(*args)
2021-02-15 15:32:47 +00:00
2022-05-02 10:34:48 +00:00
async def get_qq_uid(self):
res = await self.get_login_info()
2022-02-05 12:58:20 +00:00
if res["status"] == 0:
return res["data"]["uid"]
2021-02-15 15:32:47 +00:00
else:
return None
2022-05-02 10:34:48 +00:00
async def get_group_member_list(self, group_id, no_cache=False) -> List[Dict[str, Any]]:
2022-02-05 12:58:20 +00:00
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
2021-02-15 15:32:47 +00:00
try:
2022-05-02 10:34:48 +00:00
member_list = await self.coolq_api_query("get_group_member_list", group_id=group_id, no_cache=no_cache)
2021-02-15 15:32:47 +00:00
except CoolQAPIFailureException as e:
2022-03-09 14:06:40 +00:00
self.deliver_alert_to_master(("Failed the get group member detail.") + "{}".format(e))
2021-06-03 15:36:22 +00:00
return []
2021-06-03 14:18:49 +00:00
self.group_member_dict[group_id] = {
2022-02-05 12:58:20 +00:00
"members": member_list,
"time": datetime.now(),
2021-02-15 15:32:47 +00:00
}
2022-02-05 12:58:20 +00:00
return self.group_member_dict[group_id]["members"]
2021-02-15 15:32:47 +00:00
2022-05-02 10:34:48 +00:00
async def get_user_info(self, user_id: int, group_id: Optional[str] = None, no_cache=False):
2021-06-03 15:36:22 +00:00
user_id = int(user_id)
if no_cache or (not self.friend_list) or (user_id not in self.friend_dict):
2022-05-02 10:34:48 +00:00
await self.update_friend_list()
2022-03-09 14:06:40 +00:00
friend = self.friend_dict.get(user_id)
2021-06-02 16:20:31 +00:00
if friend:
2022-03-09 14:06:40 +00:00
user = copy.deepcopy(friend)
2022-02-05 12:58:20 +00:00
user["is_friend"] = True
2021-06-02 16:20:31 +00:00
else:
2022-03-09 14:06:40 +00:00
if no_cache:
2022-05-02 10:34:48 +00:00
stranger = await self.coolq_api_query("get_stranger_info", user_id=user_id)
2022-03-09 14:06:40 +00:00
self.stranger_dict[user_id] = stranger
stranger = self.stranger_dict.get(user_id)
if stranger is None:
2022-05-02 10:34:48 +00:00
stranger = await self.coolq_api_query("get_stranger_info", user_id=user_id)
2022-03-09 14:06:40 +00:00
self.stranger_dict[user_id] = stranger
user = copy.deepcopy(stranger)
2022-02-05 12:58:20 +00:00
user["is_friend"] = False
2021-06-03 15:36:22 +00:00
if group_id is not None:
2022-02-05 12:58:20 +00:00
user["is_in_group"] = False
2022-05-02 10:34:48 +00:00
for member in await self.get_group_member_list(group_id):
2022-02-05 12:58:20 +00:00
if member["user_id"] == user_id:
user["is_in_group"] = True
user["in_group_info"] = member
2021-06-03 15:36:22 +00:00
break
2022-02-05 12:58:20 +00:00
remark = user.get("remark")
2021-06-03 15:36:22 +00:00
if not remark:
2022-02-05 12:58:20 +00:00
user["remark"] = user["nickname"]
2021-06-02 16:20:31 +00:00
return user
2022-05-02 10:34:48 +00:00
async def get_group_info(self, group_id, no_cache=False):
2021-02-15 15:32:47 +00:00
if no_cache or not self.group_list:
2022-05-02 10:34:48 +00:00
await self.update_group_list()
2021-06-03 14:18:49 +00:00
group = self.group_dict.get(group_id)
if group:
return group
if no_cache:
for extra_group in self.extra_group_list:
2022-02-05 12:58:20 +00:00
if extra_group["group_id"] == group_id:
2021-06-03 14:18:49 +00:00
return extra_group
2021-02-15 15:32:47 +00:00
try:
2022-05-02 10:34:48 +00:00
external_group = await self.get_external_group_info(group_id)
2021-06-03 14:18:49 +00:00
except CoolQAPIFailureException:
2022-02-05 12:58:20 +00:00
self.logger.error(f"Get external group({group_id}) info failed.")
2021-02-15 15:32:47 +00:00
return None
else:
2021-06-03 14:18:49 +00:00
self.extra_group_list.append(external_group)
return external_group
2021-02-15 15:32:47 +00:00
2022-05-02 10:34:48 +00:00
async def coolq_send_message(self, msg_type, uid, message):
2022-02-05 12:58:20 +00:00
keyword = msg_type if msg_type != "private" else "user"
2022-05-02 10:34:48 +00:00
res = await self.coolq_api_query("send_msg", message_type=msg_type, **{keyword + "_id": uid}, message=message)
return str(uid) + "_" + str(res["message_id"])
2021-02-15 15:32:47 +00:00
2022-05-02 10:34:48 +00:00
async def _coolq_api_wrapper(self, func_name, **kwargs):
2021-02-15 15:32:47 +00:00
try:
2022-05-02 10:34:48 +00:00
res = await self.coolq_bot.call_action(func_name, **kwargs)
2021-02-15 15:32:47 +00:00
except RequestException as e:
2022-03-09 14:06:40 +00:00
raise CoolQDisconnectedException(("Unable to connect to CoolQ Client!" "Error Message:\n{}").format(str(e)))
2022-05-02 10:34:48 +00:00
except aiocqhttp.Error as ex:
2022-02-05 12:58:20 +00:00
api_ex = CoolQAPIFailureException(
2022-03-09 14:06:40 +00:00
("CoolQ HTTP API encountered an error!\n" "Status Code:{} " "Return Code:{}").format(
2022-02-05 12:58:20 +00:00
ex.status_code, ex.retcode
)
)
api_ex.status_code = ex.status_code
api_ex.retcode = ex.retcode
2021-02-15 15:32:47 +00:00
raise api_ex
else:
return res
2022-05-02 10:34:48 +00:00
async def check_running_status(self):
res = await self._coolq_api_wrapper("get_status")
2022-02-05 12:58:20 +00:00
if res["good"] or res["online"]:
2021-02-15 15:32:47 +00:00
return True
else:
2022-03-09 14:06:40 +00:00
raise CoolQOfflineException(("CoolQ Client isn't working correctly!"))
2021-02-15 15:32:47 +00:00
2022-05-02 10:34:48 +00:00
async def coolq_api_query(self, func_name, **kwargs) -> Any:
2022-02-05 12:58:20 +00:00
"""# Do not call get_status too frequently
2021-02-15 15:32:47 +00:00
if self.check_running_status():
return self._coolq_api_wrapper(func_name, **kwargs)
"""
if self.is_logged_in and self.is_connected:
2022-05-02 10:34:48 +00:00
return await self._coolq_api_wrapper(func_name, **kwargs)
2021-02-15 15:32:47 +00:00
elif self.repeat_counter < 3:
2022-03-09 14:06:40 +00:00
self.deliver_alert_to_master(("Your status is offline.\n" "You may try login with /0_login"))
2021-02-15 15:32:47 +00:00
self.repeat_counter += 1
2022-05-02 10:34:48 +00:00
async def check_status_periodically(self, run_once: bool = False):
2021-02-15 15:32:47 +00:00
interval = 300
2022-05-02 10:34:48 +00:00
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're unable to communicate with CoolQ Client.\n"
"Please check the connection and credentials provided.\n"
"{}"
).format(str(e))
2022-02-05 12:58:20 +00:00
)
2022-05-02 10:34:48 +00:00
self.repeat_counter += 1
self.is_connected = False
self.is_logged_in = False
interval = 3600
except (CoolQOfflineException, CoolQAPIFailureException):
2021-02-15 15:32:47 +00:00
if self.repeat_counter < 3:
2022-02-05 12:58:20 +00:00
self.deliver_alert_to_master(
2022-03-09 14:06:40 +00:00
(
2022-05-02 10:34:48 +00:00
"CoolQ is running in abnormal status.\n"
"You may need to relogin your account "
"or have a check in CoolQ Client.\n"
2022-02-05 12:58:20 +00:00
)
)
2021-02-15 15:32:47 +00:00
self.repeat_counter += 1
self.is_connected = True
self.is_logged_in = False
interval = 3600
else:
2022-05-02 10:34:48 +00:00
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)
2021-02-15 15:32:47 +00:00
def deliver_alert_to_master(self, message: str):
2022-02-05 12:58:20 +00:00
context = {
"message": message,
"uid_prefix": "alert",
2022-03-09 14:06:40 +00:00
"event_description": ("CoolQ Alert"),
2022-02-05 12:58:20 +00:00
}
2021-02-15 15:32:47 +00:00
self.send_msg_to_master(context)
2022-05-02 10:34:48 +00:00
async def update_friend_list(self):
self.friend_list = await self.coolq_api_query("get_friend_list")
2021-02-15 15:32:47 +00:00
if self.friend_list:
2022-02-05 12:58:20 +00:00
self.logger.debug("Update friend list completed. Entries: %s", len(self.friend_list))
2021-02-15 15:32:47 +00:00
for friend in self.friend_list:
2022-02-05 12:58:20 +00:00
if friend["remark"] == "":
friend["remark"] = friend["nickname"]
self.friend_dict[friend["user_id"]] = friend
2021-02-15 15:32:47 +00:00
else:
2022-02-05 12:58:20 +00:00
self.logger.warning("Failed to update friend list")
2021-02-15 15:32:47 +00:00
2022-05-02 10:34:48 +00:00
async def update_group_list(self):
self.group_list = await self.coolq_api_query("get_group_list")
2021-02-15 15:32:47 +00:00
if self.group_list:
2022-02-05 12:58:20 +00:00
self.logger.debug("Update group list completed. Entries: %s", len(self.group_list))
2021-06-03 14:18:49 +00:00
for group in self.group_list:
2022-02-05 12:58:20 +00:00
self.group_dict[group["group_id"]] = group
2021-02-15 15:32:47 +00:00
else:
2022-02-05 12:58:20 +00:00
self.logger.warning("Failed to update group list")
2021-02-15 15:32:47 +00:00
2022-05-02 10:34:48 +00:00
async def update_contacts_periodically(self, run_once: bool = False):
2021-02-15 15:32:47 +00:00
interval = 1800
2022-05-02 10:34:48 +00:00
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)
2021-02-15 15:32:47 +00:00
2022-05-02 10:34:48 +00:00
async def get_friend_remark(self, uid):
2021-06-03 14:18:49 +00:00
if (not self.friend_list) or (uid not in self.friend_dict):
2022-05-02 10:34:48 +00:00
await self.update_friend_list()
2021-06-03 14:18:49 +00:00
if uid not in self.friend_dict:
2021-02-15 15:32:47 +00:00
return None # I don't think you have such a friend
2022-02-05 12:58:20 +00:00
return self.friend_dict[uid]["remark"]
2021-02-15 15:32:47 +00:00
def send_efb_group_notice(self, context):
2022-02-05 12:58:20 +00:00
context["message_type"] = "group"
2021-02-15 15:32:47 +00:00
self.logger.debug(repr(context))
2022-05-02 10:34:48 +00:00
chat = asyncio.run(self.chat_manager.build_efb_chat_as_group(context))
2021-02-15 15:32:47 +00:00
try:
author = chat.get_member(SystemChatMember.SYSTEM_ID)
except KeyError:
author = chat.add_system_member()
event_description = context.get("event_description", "")
2021-02-15 15:32:47 +00:00
msg = Message(
uid="__group_notice__.%s" % int(time.time()),
type=MsgType.Text,
chat=chat,
author=author,
text=(event_description + "\n\n" + context["message"]) if event_description else context["message"],
2022-02-05 12:58:20 +00:00
deliver_to=coordinator.master,
2021-02-15 15:32:47 +00:00
)
coordinator.send_message(msg)
def send_msg_to_master(self, context):
self.logger.debug(repr(context))
2022-02-05 12:58:20 +00:00
if not getattr(coordinator, "master", None): # Master Channel not initialized
raise Exception(context["message"])
2021-02-15 15:32:47 +00:00
chat = self.chat_manager.build_efb_chat_as_system_user(context)
try:
author = chat.get_member(SystemChatMember.SYSTEM_ID)
except KeyError:
author = chat.add_system_member()
msg = Message(
2022-02-05 12:58:20 +00:00
uid="__{context[uid_prefix]}__.{uni_id}".format(context=context, uni_id=str(int(time.time()))),
2021-02-15 15:32:47 +00:00
type=MsgType.Text,
chat=chat,
author=author,
2022-02-05 12:58:20 +00:00
deliver_to=coordinator.master,
2021-02-15 15:32:47 +00:00
)
2022-02-05 12:58:20 +00:00
if "message" in context:
msg.text = context["message"]
if "commands" in context:
msg.commands = MessageCommands(context["commands"])
2021-02-15 15:32:47 +00:00
coordinator.send_message(msg)
# As the old saying goes
# A programmer spent 20% of time on coding
# while the rest 80% on considering a variable/function/class name
2022-05-02 10:34:48 +00:00
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)
2021-02-15 15:32:47 +00:00
return res
2022-02-05 12:58:20 +00:00
def send_status(self, status: "Status"):
2021-02-15 15:32:47 +00:00
if isinstance(status, MessageRemoval):
if not isinstance(status.message.author, SelfChatMember):
2022-03-09 14:06:40 +00:00
raise EFBMessageError(("You can only recall your own messages."))
2021-02-15 15:32:47 +00:00
try:
2022-02-05 12:58:20 +00:00
uid_type = status.message.uid.split("_")
2022-05-02 10:34:48 +00:00
asyncio.run(self.recall_message(uid_type[1]))
2021-02-15 15:32:47 +00:00
except CoolQAPIFailureException:
2022-03-09 14:06:40 +00:00
raise EFBMessageError(("Failed to recall the message. This message may have already expired."))
2021-02-15 15:32:47 +00:00
else:
raise EFBOperationNotSupported()
# todo
2022-05-02 10:34:48 +00:00
async def recall_message(self, message_id):
await self.coolq_api_query("delete_msg", message_id=message_id)
2021-02-15 15:32:47 +00:00
def send_cookie_expired_alarm(self):
2022-02-05 12:58:20 +00:00
self.deliver_alert_to_master(
2022-03-09 14:06:40 +00:00
(
2022-02-05 12:58:20 +00:00
"Your cookie of CoolQ Client seems to be expired. "
"Although it will not affect the normal functioning of sending/receiving "
"messages, however, you may encounter issues like failing to retrieve "
"friend list. Please consult "
"https://github.com/milkice233/efb-qq-slave/wiki/Workaround-for-expired"
"-cookies-of-CoolQ for solutions."
)
)
2021-02-15 15:32:47 +00:00
2022-05-02 10:34:48 +00:00
async def process_friend_request(self, result, flag):
2022-02-05 12:58:20 +00:00
res = "true" if result == "accept" else "false"
2021-02-15 15:32:47 +00:00
try:
2022-05-02 10:34:48 +00:00
await self.coolq_api_query("set_friend_add_request", approve=res, flag=flag)
2021-02-15 15:32:47 +00:00
except CoolQAPIFailureException as e:
2022-03-09 14:06:40 +00:00
return ("Failed to process request! Error Message:\n") + getattr(e, "message", repr(e))
2022-02-05 12:58:20 +00:00
return "Done"
2021-02-15 15:32:47 +00:00
2022-05-02 10:34:48 +00:00
async def process_group_request(self, result, flag, sub_type):
2022-02-05 12:58:20 +00:00
res = "true" if result == "accept" else "false"
2021-02-15 15:32:47 +00:00
try:
2022-05-02 10:34:48 +00:00
await self.coolq_api_query("set_group_add_request", approve=res, flag=flag, sub_type=sub_type)
2021-02-15 15:32:47 +00:00
except CoolQAPIFailureException as e:
2022-03-09 14:06:40 +00:00
return ("Failed to process request! Error Message:\n") + getattr(e, "message", repr(e))
2022-02-05 12:58:20 +00:00
return "Done"
2021-02-15 15:32:47 +00:00
2021-06-02 16:20:31 +00:00
def async_download_file(self, context, download_url):
2021-05-30 17:16:50 +00:00
res = download_file(download_url)
2021-02-15 15:32:47 +00:00
if isinstance(res, str):
2022-03-09 14:06:40 +00:00
context["message"] = ("[Download] ") + res
2021-02-15 15:32:47 +00:00
self.send_efb_group_notice(context)
elif res is None:
pass
else:
2022-02-05 12:58:20 +00:00
data = {"file": res, "filename": context["file"]["name"]}
context["message_type"] = "group"
2021-02-15 15:32:47 +00:00
efb_msg = self.msg_decorator.qq_file_after_wrapper(data)
2022-02-05 12:58:20 +00:00
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":
2022-05-02 10:34:48 +00:00
efb_msg.chat = asyncio.run(self.chat_manager.build_efb_chat_as_private(context))
2022-02-05 12:58:20 +00:00
elif context["uid_prefix"] == "group_upload":
2022-05-02 10:34:48 +00:00
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))
2021-02-15 15:32:47 +00:00
efb_msg.deliver_to = coordinator.master
async_send_messages_to_master(efb_msg)
2022-05-02 10:34:48 +00:00
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)
2022-02-05 12:58:20 +00:00
download_url = file["url"]
2021-06-02 16:20:31 +00:00
self.async_download_file(context, download_url)
2022-02-05 12:58:20 +00:00
def get_chat_picture(self, chat: "Chat") -> BinaryIO:
chat_type = chat.uid.split("_")
if chat_type[0] == "private":
2021-02-15 15:32:47 +00:00
return download_user_avatar(chat_type[1])
2022-02-05 12:58:20 +00:00
elif chat_type[0] == "group":
2021-02-15 15:32:47 +00:00
return download_group_avatar(chat_type[1])
else:
return download_group_avatar("")
def get_chats(self):
2022-05-02 10:34:48 +00:00
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
2021-02-15 15:32:47 +00:00
2022-02-05 12:58:20 +00:00
def get_chat(self, chat_uid: ChatID) -> "Chat":
2021-02-15 15:32:47 +00:00
# todo what is member_uid used for?
2022-02-05 12:58:20 +00:00
chat_type = chat_uid.split("_")
if chat_type[0] == "private":
2021-02-15 15:32:47 +00:00
qq_uid = int(chat_type[1])
2022-05-02 10:34:48 +00:00
remark = asyncio.run(self.get_friend_remark(qq_uid))
2022-03-09 14:06:40 +00:00
context: Dict[str, Any] = {"user_id": qq_uid}
2021-02-15 15:32:47 +00:00
if remark is not None:
2022-02-05 12:58:20 +00:00
context["alias"] = remark
2022-05-02 10:34:48 +00:00
return asyncio.run(self.chat_manager.build_efb_chat_as_private(context))
2022-02-05 12:58:20 +00:00
elif chat_type[0] == "group":
2021-02-15 15:32:47 +00:00
group_id = int(chat_type[1])
2022-02-05 12:58:20 +00:00
context = {"message_type": "group", "group_id": group_id}
2022-05-02 10:34:48 +00:00
return asyncio.run(self.chat_manager.build_efb_chat_as_group(context, update_member=True))
2022-02-05 12:58:20 +00:00
elif chat_type[0] == "discuss":
2021-02-15 15:32:47 +00:00
discuss_id = int(chat_type[1])
2022-02-05 12:58:20 +00:00
context = {"message_type": "discuss", "discuss_id": discuss_id}
2022-05-02 10:34:48 +00:00
return asyncio.run(self.chat_manager.build_efb_chat_as_group(context))
2021-02-15 15:32:47 +00:00
raise EFBChatNotFound()
2022-05-02 10:34:48 +00:00
async def check_self_update(self, run_once: bool = False):
2021-02-15 15:32:47 +00:00
interval = 60 * 60 * 24
2022-05-02 10:34:48 +00:00
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 "
"<code>pip3 install --upgrade efb-qq-slave</code>".format(version=latest_version)
)
else:
pass
if run_once:
return
await asyncio.sleep(interval)
2021-02-15 15:32:47 +00:00
def poll(self):
2022-05-02 10:34:48 +00:00
asyncio.run(self.check_self_update(run_once=True))
2022-02-05 12:58:20 +00:00
self.run_instance(
host=self.client_config["host"],
port=self.client_config["port"],
debug=False,
)
2021-02-15 15:32:47 +00:00
def stop_polling(self):
2022-05-02 10:34:48 +00:00
self.logger.debug("Gracefully stopping QQ Slave")
self.shutdown_event.set()
self.loop.stop()
self.t.join()