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
|
|
|
|
|
from datetime import timedelta, datetime
|
|
|
|
|
from gettext import translation
|
2021-06-04 14:04:17 +00:00
|
|
|
|
from typing import Any, Dict, List, BinaryIO, Tuple, Optional, Union
|
2021-02-15 15:32:47 +00:00
|
|
|
|
|
2021-05-30 17:40:01 +00:00
|
|
|
|
import cherrypy
|
2021-02-15 15:32:47 +00:00
|
|
|
|
import cqhttp
|
|
|
|
|
from PIL import Image
|
2021-05-30 17:40:01 +00:00
|
|
|
|
from cherrypy._cpserver import Server
|
2021-02-15 15:32:47 +00:00
|
|
|
|
from cherrypy.process.wspbus import states
|
|
|
|
|
from cqhttp import CQHttp
|
|
|
|
|
from efb_qq_slave import BaseClient, QQMessengerChannel
|
|
|
|
|
from ehforwarderbot import Message, MsgType, Chat, coordinator, Status
|
|
|
|
|
from ehforwarderbot.chat import SelfChatMember, ChatMember, SystemChatMember, PrivateChat
|
|
|
|
|
from ehforwarderbot.exceptions import EFBMessageError, EFBOperationNotSupported, EFBChatNotFound
|
|
|
|
|
from ehforwarderbot.message import MessageCommands, MessageCommand
|
|
|
|
|
from ehforwarderbot.status import MessageRemoval
|
|
|
|
|
from ehforwarderbot.types import ChatID
|
|
|
|
|
from ehforwarderbot.utils import extra
|
|
|
|
|
from pkg_resources import resource_filename
|
|
|
|
|
from requests import RequestException
|
|
|
|
|
|
|
|
|
|
from .ChatMgr import ChatManager
|
2021-06-02 13:18:24 +00:00
|
|
|
|
from .Exceptions import CoolQDisconnectedException, CoolQAPIFailureException, CoolQOfflineException
|
2021-02-15 15:32:47 +00:00
|
|
|
|
from .MsgDecorator import QQMsgProcessor
|
|
|
|
|
from .Utils import qq_emoji_list, async_send_messages_to_master, process_quote_text, coolq_text_encode, \
|
2021-06-02 13:18:24 +00:00
|
|
|
|
download_user_avatar, download_group_avatar, download_file
|
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
|
|
|
|
|
|
|
|
|
|
translator = translation("efb_qq_slave",
|
|
|
|
|
resource_filename('efb_qq_slave', 'Clients/CoolQ/locale'),
|
|
|
|
|
fallback=True)
|
|
|
|
|
|
|
|
|
|
_ = translator.gettext
|
|
|
|
|
ngettext = translator.ngettext
|
|
|
|
|
|
|
|
|
|
friend_list = []
|
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] = {}
|
2021-02-15 15:32:47 +00:00
|
|
|
|
group_list = []
|
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] = {}
|
2021-02-15 15:32:47 +00:00
|
|
|
|
discuss_list = []
|
|
|
|
|
extra_group_list = []
|
|
|
|
|
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)
|
|
|
|
|
self.client_config = config[self.client_id]
|
|
|
|
|
self.coolq_bot = CQHttp(api_root=self.client_config['api_root'],
|
|
|
|
|
access_token=self.client_config['access_token']
|
|
|
|
|
)
|
|
|
|
|
self.channel = channel
|
|
|
|
|
self.chat_manager = ChatManager(channel)
|
|
|
|
|
|
|
|
|
|
self.is_connected = False
|
|
|
|
|
self.is_logged_in = False
|
|
|
|
|
self.msg_decorator = QQMsgProcessor(instance=self)
|
|
|
|
|
|
2021-06-09 12:54:47 +00:00
|
|
|
|
def message_element_wrapper(context: Dict[str, Any], msg_element: Dict[str, Any], chat: Chat) -> \
|
2021-06-04 14:04:17 +00:00
|
|
|
|
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 = ''
|
|
|
|
|
messages: List[Message] = []
|
|
|
|
|
at_list: List[Tuple[Tuple[int, int], Union[Chat, ChatMember]]] = []
|
|
|
|
|
if msg_type == 'text':
|
|
|
|
|
main_text = msg_data['text']
|
|
|
|
|
elif msg_type == 'face':
|
|
|
|
|
qq_face = int(msg_data['id'])
|
|
|
|
|
if qq_face in qq_emoji_list:
|
|
|
|
|
main_text = qq_emoji_list[qq_face]
|
|
|
|
|
else:
|
|
|
|
|
main_text = '\u2753' # ❓
|
|
|
|
|
elif msg_type == 'sface':
|
|
|
|
|
main_text = '\u2753' # ❓
|
|
|
|
|
elif msg_type == 'at':
|
|
|
|
|
# todo Recheck if bug exists
|
|
|
|
|
g_id = context['group_id']
|
|
|
|
|
my_uid = 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']
|
|
|
|
|
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)
|
|
|
|
|
substitution_end = len(main_text) + len(group_card) + 1
|
2021-06-09 12:54:47 +00:00
|
|
|
|
main_text = '@{} '.format(group_card)
|
2021-06-04 14:04:17 +00:00
|
|
|
|
if str(my_uid) == str(msg_data['qq']) or str(msg_data['qq']) == 'all':
|
|
|
|
|
at_dict = ((substitution_begin, substitution_end), chat.self)
|
|
|
|
|
at_list.append(at_dict)
|
2021-06-09 12:54:47 +00:00
|
|
|
|
elif msg_type == 'reply':
|
|
|
|
|
ref_user = self.get_user_info(msg_data['qq'])
|
|
|
|
|
main_text = f'「{ref_user["remark"]}({ref_user["nickname"]}):{msg_data["text"]}」\n' \
|
|
|
|
|
'- - - - - - - - - - - - - - -\n'
|
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
|
|
|
|
|
|
2021-06-09 12:54:47 +00:00
|
|
|
|
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)
|
|
|
|
|
main_text_len = len(main_text)
|
|
|
|
|
for at_tuple in sub_at_list:
|
|
|
|
|
pos = (at_tuple[0][0] + main_text_len, at_tuple[0][1] + main_text_len)
|
|
|
|
|
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
|
|
|
|
|
def handle_msg(context):
|
|
|
|
|
self.logger.debug(repr(context))
|
2021-06-03 15:36:22 +00:00
|
|
|
|
msg_elements = context['message']
|
2021-02-15 15:32:47 +00:00
|
|
|
|
qq_uid = context['user_id']
|
|
|
|
|
chat: Chat
|
|
|
|
|
author: ChatMember
|
|
|
|
|
|
2021-06-03 15:36:22 +00:00
|
|
|
|
user = self.get_user_info(qq_uid)
|
2021-02-15 15:32:47 +00:00
|
|
|
|
if context['message_type'] == 'private':
|
2021-06-03 15:36:22 +00:00
|
|
|
|
context['alias'] = user['remark']
|
2021-02-15 15:32:47 +00:00
|
|
|
|
chat: PrivateChat = self.chat_manager.build_efb_chat_as_private(context)
|
|
|
|
|
else:
|
|
|
|
|
chat = self.chat_manager.build_efb_chat_as_group(context)
|
|
|
|
|
|
|
|
|
|
if 'anonymous' not in context or context['anonymous'] is None:
|
|
|
|
|
if context['message_type'] == 'group':
|
|
|
|
|
if context['sub_type'] == 'notice':
|
|
|
|
|
context['event_description'] = self._("System Notification")
|
|
|
|
|
context['uid_prefix'] = 'group_notification'
|
|
|
|
|
author = chat.add_system_member(
|
|
|
|
|
name=context['event_description'],
|
|
|
|
|
uid=ChatID("__{context[uid_prefix]}__".format(context=context))
|
|
|
|
|
)
|
|
|
|
|
else:
|
2021-06-03 15:36:22 +00:00
|
|
|
|
user = self.get_user_info(qq_uid, group_id=context['group_id'])
|
|
|
|
|
context['nickname'] = user['remark']
|
|
|
|
|
context['alias'] = user['in_group_info']['card']
|
2021-02-15 15:32:47 +00:00
|
|
|
|
author = 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)
|
|
|
|
|
else: # anonymous user in group
|
|
|
|
|
author = self.chat_manager.build_efb_chat_as_anonymous_user(chat, context)
|
|
|
|
|
|
2021-06-09 12:54:47 +00:00
|
|
|
|
main_text, messages, at_dict = 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))
|
2021-02-15 15:32:47 +00:00
|
|
|
|
uid: str = str(uuid.uuid4())
|
|
|
|
|
coolq_msg_id = context['message_id']
|
|
|
|
|
for i in range(len(messages)):
|
|
|
|
|
if not isinstance(messages[i], Message):
|
|
|
|
|
continue
|
|
|
|
|
efb_msg: Message = messages[i]
|
|
|
|
|
efb_msg.uid = uid + '_' + str(coolq_msg_id) + '_' + str(i)
|
|
|
|
|
efb_msg.chat = chat
|
|
|
|
|
efb_msg.author = author
|
|
|
|
|
# if qq_uid != '80000000':
|
|
|
|
|
|
|
|
|
|
# Append discuss group into group list
|
|
|
|
|
if context['message_type'] == 'discuss' and efb_msg.chat not in self.discuss_list:
|
|
|
|
|
self.discuss_list.append(efb_msg.chat)
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
@self.coolq_bot.on_notice('group_increase')
|
|
|
|
|
def handle_group_increase_msg(context):
|
|
|
|
|
context['event_description'] = self._('\u2139 Group Member Increase Event')
|
|
|
|
|
if (context['sub_type']) == 'invite':
|
|
|
|
|
text = self._('{nickname}({context[user_id]}) joined the group({group_name}) via invitation')
|
|
|
|
|
else:
|
|
|
|
|
text = self._('{nickname}({context[user_id]}) joined the group({group_name})')
|
|
|
|
|
|
|
|
|
|
original_group = self.get_group_info(context['group_id'], False)
|
|
|
|
|
group_name = context['group_id']
|
|
|
|
|
if original_group is not None and 'group_name' in original_group:
|
|
|
|
|
group_name = original_group['group_name']
|
|
|
|
|
text = text.format(nickname=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_decrease')
|
|
|
|
|
def handle_group_decrease_msg(context):
|
|
|
|
|
context['event_description'] = self._("\u2139 Group Member Decrease Event")
|
|
|
|
|
original_group = self.get_group_info(context['group_id'], False)
|
|
|
|
|
group_name = context['group_id']
|
|
|
|
|
if original_group is not None and 'group_name' in original_group:
|
|
|
|
|
group_name = original_group['group_name']
|
|
|
|
|
text = ''
|
|
|
|
|
if context['sub_type'] == 'kick_me':
|
|
|
|
|
text = self._("You've been kicked from the group({})").format(group_name)
|
|
|
|
|
else:
|
|
|
|
|
if context['sub_type'] == 'leave':
|
|
|
|
|
text = self._('{nickname}({context[user_id]}) quited the group({group_name})')
|
|
|
|
|
else:
|
|
|
|
|
text = self._('{nickname}({context[user_id]}) was kicked from the group({group_name})')
|
|
|
|
|
text = text.format(nickname=self.get_stranger_info(context['user_id'])['nickname'],
|
|
|
|
|
context=context,
|
|
|
|
|
group_name=group_name)
|
|
|
|
|
context['message'] = text
|
|
|
|
|
self.send_efb_group_notice(context)
|
|
|
|
|
|
2021-06-02 16:20:31 +00:00
|
|
|
|
@self.coolq_bot.on_notice('offline_file')
|
|
|
|
|
def handle_offline_file_upload_msg(context):
|
|
|
|
|
context['event_description'] = self._("\u2139 Offline File Upload Event")
|
|
|
|
|
context['uid_prefix'] = 'offline_file'
|
|
|
|
|
file_info_msg = self._('Filename: {file[name]}\n'
|
|
|
|
|
'File size: {file[size]}').format(file=context['file'])
|
|
|
|
|
user = self.get_user_info(context['user_id'])
|
|
|
|
|
text = self._('{remark}({nickname}) uploaded a file to you\n')
|
|
|
|
|
text = text.format(remark=user['remark'], nickname=user['nickname']) + file_info_msg
|
|
|
|
|
context['message'] = text
|
|
|
|
|
self.send_msg_to_master(context)
|
|
|
|
|
param_dict = {
|
|
|
|
|
'context': context,
|
|
|
|
|
'download_url': context['file']['url'],
|
|
|
|
|
}
|
|
|
|
|
threading.Thread(target=self.async_download_file, args=[], kwargs=param_dict).start()
|
|
|
|
|
|
2021-02-15 15:32:47 +00:00
|
|
|
|
@self.coolq_bot.on_notice('group_upload')
|
|
|
|
|
def handle_group_file_upload_msg(context):
|
|
|
|
|
context['event_description'] = self._("\u2139 Group File Upload Event")
|
2021-06-03 15:36:22 +00:00
|
|
|
|
context['uid_prefix'] = 'group_upload'
|
2021-02-15 15:32:47 +00:00
|
|
|
|
original_group = self.get_group_info(context['group_id'], False)
|
|
|
|
|
group_name = context['group_id']
|
|
|
|
|
if original_group is not None and 'group_name' in original_group:
|
|
|
|
|
group_name = original_group['group_name']
|
|
|
|
|
|
|
|
|
|
file_info_msg = self._('File ID: {file[id]}\n'
|
|
|
|
|
'Filename: {file[name]}\n'
|
|
|
|
|
'File size: {file[size]}').format(file=context['file'])
|
2021-06-03 15:36:22 +00:00
|
|
|
|
member_info = self.get_user_info(context['user_id'], group_id=context['group_id'])['in_group_info']
|
2021-02-15 15:32:47 +00:00
|
|
|
|
group_card = member_info['card'] if member_info['card'] != '' else member_info['nickname']
|
|
|
|
|
text = self._('{member_card}({context[user_id]}) uploaded a file to group({group_name})\n')
|
|
|
|
|
text = text.format(member_card=group_card,
|
|
|
|
|
context=context,
|
|
|
|
|
group_name=group_name) + file_info_msg
|
|
|
|
|
context['message'] = text
|
|
|
|
|
self.send_efb_group_notice(context)
|
|
|
|
|
|
|
|
|
|
param_dict = {
|
|
|
|
|
'context': context,
|
|
|
|
|
'group_id': context['group_id'],
|
|
|
|
|
'file_id': context['file']['id'],
|
2021-05-30 17:16:50 +00:00
|
|
|
|
'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
|
|
|
|
|
|
|
|
|
@self.coolq_bot.on_notice('friend_add')
|
|
|
|
|
def handle_friend_add_msg(context):
|
|
|
|
|
context['event_description'] = self._('\u2139 New Friend Event')
|
|
|
|
|
context['uid_prefix'] = 'friend_add'
|
|
|
|
|
text = self._('{nickname}({context[user_id]}) has become your friend!')
|
|
|
|
|
text = text.format(nickname=self.get_stranger_info(context['user_id'])['nickname'],
|
|
|
|
|
context=context)
|
|
|
|
|
context['message'] = text
|
|
|
|
|
self.send_msg_to_master(context)
|
|
|
|
|
|
|
|
|
|
@self.coolq_bot.on_request('friend') # Add friend request
|
|
|
|
|
def handle_add_friend_request(context):
|
|
|
|
|
self.logger.debug(repr(context))
|
|
|
|
|
context['event_description'] = self._('\u2139 New Friend Request')
|
|
|
|
|
context['uid_prefix'] = 'friend_request'
|
|
|
|
|
text = self._('{nickname}({context[user_id]}) wants to be your friend!\n'
|
|
|
|
|
'Here is the verification comment:\n'
|
|
|
|
|
'{context[comment]}')
|
|
|
|
|
text = text.format(nickname=self.get_stranger_info(context['user_id'])['nickname'],
|
|
|
|
|
context=context)
|
|
|
|
|
context['message'] = text
|
|
|
|
|
commands = [MessageCommand(
|
|
|
|
|
name=self._("Accept"),
|
|
|
|
|
callable_name="process_friend_request",
|
|
|
|
|
kwargs={'result': 'accept',
|
|
|
|
|
'flag': context['flag']}
|
|
|
|
|
), MessageCommand(
|
|
|
|
|
name=self._("Decline"),
|
|
|
|
|
callable_name="process_friend_request",
|
|
|
|
|
kwargs={'result': 'decline',
|
|
|
|
|
'flag': context['flag']}
|
|
|
|
|
)]
|
|
|
|
|
context['commands'] = commands
|
|
|
|
|
self.send_msg_to_master(context)
|
|
|
|
|
|
|
|
|
|
@self.coolq_bot.on_request('group')
|
|
|
|
|
def handle_group_request(context):
|
|
|
|
|
self.logger.debug(repr(context))
|
|
|
|
|
context['uid_prefix'] = 'group_request'
|
|
|
|
|
context['group_name'] = self._('[Request]') + 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'], 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)
|
|
|
|
|
msg.chat = 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']):
|
|
|
|
|
name = "{}({})[{}] ".format(
|
|
|
|
|
self.get_stranger_info(context['user_id'])['nickname'], self.get_friend_remark(context['user_id']),
|
|
|
|
|
context['user_id'])
|
|
|
|
|
else:
|
|
|
|
|
name = "{}[{}] ".format(self.get_stranger_info(context['user_id'])['nickname'], context['user_id'])
|
|
|
|
|
msg.text = "{} wants to join the group {}({}). \nHere is the comment: {}".format(
|
|
|
|
|
name, group_name, context['group_id_orig'], context['comment']
|
|
|
|
|
)
|
|
|
|
|
msg.commands = MessageCommands([MessageCommand(
|
|
|
|
|
name=self._("Accept"),
|
|
|
|
|
callable_name="process_group_request",
|
|
|
|
|
kwargs={'result': 'accept',
|
|
|
|
|
'flag': context['flag'],
|
|
|
|
|
'sub_type': context['sub_type']}
|
|
|
|
|
), MessageCommand(
|
|
|
|
|
name=self._("Decline"),
|
|
|
|
|
callable_name="process_group_request",
|
|
|
|
|
kwargs={'result': 'decline',
|
|
|
|
|
'flag': context['flag'],
|
|
|
|
|
'sub_type': context['sub_type']}
|
|
|
|
|
)])
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
@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 self._("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.'
|
|
|
|
|
|
|
|
|
|
def logout(self):
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
@extra(name=_("Check CoolQ Status"),
|
|
|
|
|
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)
|
|
|
|
|
return 'Done'
|
|
|
|
|
|
2021-06-03 15:36:22 +00:00
|
|
|
|
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)
|
2021-02-15 15:32:47 +00:00
|
|
|
|
|
|
|
|
|
def get_login_info(self) -> Dict[Any, Any]:
|
|
|
|
|
res = self.coolq_bot.get_status()
|
|
|
|
|
if 'good' in res or 'online' in res:
|
|
|
|
|
data = self.coolq_bot.get_login_info()
|
|
|
|
|
return {'status': 0, 'data': {'uid': data['user_id'], 'nickname': data['nickname']}}
|
|
|
|
|
else:
|
|
|
|
|
return {'status': 1}
|
|
|
|
|
|
|
|
|
|
def get_groups(self) -> List:
|
|
|
|
|
# todo Add support for discuss group iteration
|
|
|
|
|
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)
|
|
|
|
|
groups.append(efb_chat)
|
|
|
|
|
for i in range(len(self.extra_group_list)):
|
|
|
|
|
does_exist = False
|
|
|
|
|
for j in range(len(res)):
|
|
|
|
|
if str(self.extra_group_list[i]['group_id']) == str(res[i]['group_id']):
|
|
|
|
|
does_exist = True
|
|
|
|
|
break
|
|
|
|
|
if does_exist:
|
|
|
|
|
continue
|
|
|
|
|
context = {'message_type': 'group',
|
|
|
|
|
'group_id': self.extra_group_list[i]['group_id']}
|
|
|
|
|
efb_chat = self.chat_manager.build_efb_chat_as_group(context)
|
|
|
|
|
groups.append(efb_chat)
|
|
|
|
|
return groups + self.discuss_list
|
|
|
|
|
|
|
|
|
|
def get_friends(self) -> List:
|
|
|
|
|
try:
|
|
|
|
|
self.update_friend_list() # Force update friend list
|
|
|
|
|
except CoolQAPIFailureException:
|
|
|
|
|
self.deliver_alert_to_master(self._('Failed to retrieve the friend list.\n'
|
|
|
|
|
'Only groups are shown.'))
|
|
|
|
|
return []
|
|
|
|
|
users = []
|
2021-06-03 14:18:49 +00:00
|
|
|
|
for current_user in self.friend_list:
|
2021-02-15 15:32:47 +00:00
|
|
|
|
context = {'user_id': str(current_user['user_id']),
|
2021-06-03 14:18:49 +00:00
|
|
|
|
'nickname': current_user['nickname'],
|
2021-02-15 15:32:47 +00:00
|
|
|
|
'alias': current_user['remark']}
|
|
|
|
|
efb_chat = self.chat_manager.build_efb_chat_as_private(context)
|
|
|
|
|
users.append(efb_chat)
|
|
|
|
|
return users
|
|
|
|
|
|
|
|
|
|
def receive_message(self):
|
|
|
|
|
# Replaced by handle_msg()
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def send_message(self, msg: 'Message') -> 'Message':
|
|
|
|
|
# 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)
|
|
|
|
|
chat_type = msg.chat.uid.split('_')
|
|
|
|
|
|
|
|
|
|
self.logger.debug('[%s] Is edited: %s', msg.uid, msg.edit)
|
|
|
|
|
if msg.edit:
|
2021-06-02 13:18:24 +00:00
|
|
|
|
try:
|
|
|
|
|
uid_type = msg.uid.split('_')
|
|
|
|
|
self.recall_message(uid_type[1])
|
|
|
|
|
except CoolQAPIFailureException:
|
|
|
|
|
raise EFBOperationNotSupported(self._("Failed to recall the message!\n"
|
|
|
|
|
"This message may have already expired."))
|
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
|
|
|
|
|
self.coolq_api_query("set_group_kick",
|
|
|
|
|
group_id=group_id,
|
|
|
|
|
user_id=user_id)
|
|
|
|
|
else:
|
|
|
|
|
if isinstance(msg.target, Message):
|
|
|
|
|
max_length = 50
|
|
|
|
|
tgt_text = coolq_text_encode(process_quote_text(msg.target.text, max_length))
|
|
|
|
|
tgt_alias = ""
|
|
|
|
|
if chat_type[0] != 'private' and not isinstance(msg.target.author, SelfChatMember):
|
|
|
|
|
tgt_alias += m.coolq_code_at_wrapper(msg.target.author.uid)
|
|
|
|
|
else:
|
|
|
|
|
tgt_alias = ""
|
|
|
|
|
msg.text = "%s%s\n\n%s" % (tgt_alias, tgt_text, coolq_text_encode(msg.text))
|
|
|
|
|
msg.uid = 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 = ''
|
2021-06-02 13:18:24 +00:00
|
|
|
|
if not self.can_send_image:
|
|
|
|
|
self.check_features() # Force checking features
|
|
|
|
|
raise EFBOperationNotSupported(self._("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)
|
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)
|
|
|
|
|
img = img.convert('RGB').convert('P', palette=Image.ADAPTIVE, colors=255)
|
|
|
|
|
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
|
|
|
|
msg.uid = 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)
|
|
|
|
|
# todo More MsgType Support
|
|
|
|
|
elif msg.type is MsgType.Voice:
|
|
|
|
|
if not self.can_send_voice:
|
|
|
|
|
self.check_features() # Force checking features
|
|
|
|
|
raise EFBOperationNotSupported(self._("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)
|
|
|
|
|
if msg.text:
|
|
|
|
|
self.coolq_send_message(chat_type[0], chat_type[1], msg.text)
|
|
|
|
|
return msg
|
|
|
|
|
|
2021-06-04 03:40:17 +00:00
|
|
|
|
def call_msg_decorator(self, msg_type: str, *args) -> List[Message]:
|
|
|
|
|
try:
|
|
|
|
|
func = getattr(self.msg_decorator, 'qq_{}_wrapper'.format(msg_type))
|
|
|
|
|
except AttributeError:
|
|
|
|
|
self.deliver_alert_to_master(self._(f"Unsupported message type: {msg_type}"))
|
|
|
|
|
self.logger.error(f"Unsupported message type: {msg_type}")
|
|
|
|
|
return []
|
|
|
|
|
else:
|
|
|
|
|
return func(*args)
|
2021-02-15 15:32:47 +00:00
|
|
|
|
|
|
|
|
|
def get_qq_uid(self):
|
|
|
|
|
res = self.get_login_info()
|
|
|
|
|
if res['status'] == 0:
|
|
|
|
|
return res['data']['uid']
|
|
|
|
|
else:
|
|
|
|
|
return None
|
|
|
|
|
|
2021-06-03 15:36:22 +00:00
|
|
|
|
def get_group_member_list(self, group_id, no_cache=False) -> List[Dict[str, Any]]:
|
2021-06-03 14:18:49 +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:
|
2021-06-03 15:36:22 +00:00
|
|
|
|
member_list = 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:
|
|
|
|
|
self.deliver_alert_to_master(self._("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] = {
|
2021-02-15 15:32:47 +00:00
|
|
|
|
'members': member_list,
|
|
|
|
|
'time': datetime.now()
|
|
|
|
|
}
|
2021-06-03 14:18:49 +00:00
|
|
|
|
return self.group_member_dict[group_id]['members']
|
2021-02-15 15:32:47 +00:00
|
|
|
|
|
2021-06-03 15:36:22 +00:00
|
|
|
|
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):
|
2021-06-02 16:20:31 +00:00
|
|
|
|
self.update_friend_list()
|
2021-06-03 15:42:53 +00:00
|
|
|
|
friend = copy.deepcopy(self.friend_dict.get(user_id))
|
2021-06-02 16:20:31 +00:00
|
|
|
|
if friend:
|
2021-06-03 15:42:53 +00:00
|
|
|
|
user = friend
|
2021-06-02 16:20:31 +00:00
|
|
|
|
user['is_friend'] = True
|
|
|
|
|
else:
|
2021-06-03 15:42:53 +00:00
|
|
|
|
user = copy.deepcopy(self.stranger_dict.get(user_id))
|
2021-06-03 15:36:22 +00:00
|
|
|
|
if no_cache or (user is None):
|
|
|
|
|
user = self.coolq_api_query('get_stranger_info', user_id=user_id)
|
2021-06-03 15:42:53 +00:00
|
|
|
|
self.stranger_dict[user_id] = copy.deepcopy(user)
|
2021-06-02 16:20:31 +00:00
|
|
|
|
user['is_friend'] = False
|
2021-06-03 15:36:22 +00:00
|
|
|
|
if group_id is not None:
|
|
|
|
|
user['is_in_group'] = False
|
|
|
|
|
for member in self.get_group_member_list(group_id):
|
|
|
|
|
if member['user_id'] == user_id:
|
|
|
|
|
user['is_in_group'] = True
|
|
|
|
|
user['in_group_info'] = member
|
|
|
|
|
break
|
|
|
|
|
remark = user.get('remark')
|
|
|
|
|
if not remark:
|
|
|
|
|
user['remark'] = user['nickname']
|
2021-06-02 16:20:31 +00:00
|
|
|
|
return user
|
|
|
|
|
|
|
|
|
|
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:
|
2021-06-03 14:18:49 +00:00
|
|
|
|
self.update_group_list()
|
|
|
|
|
group = self.group_dict.get(group_id)
|
|
|
|
|
if group:
|
|
|
|
|
return group
|
|
|
|
|
if no_cache:
|
|
|
|
|
for extra_group in self.extra_group_list:
|
|
|
|
|
if extra_group['group_id'] == group_id:
|
|
|
|
|
return extra_group
|
2021-02-15 15:32:47 +00:00
|
|
|
|
try:
|
|
|
|
|
external_group = self.get_external_group_info(group_id)
|
2021-06-03 14:18:49 +00:00
|
|
|
|
except CoolQAPIFailureException:
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
return str(uuid.uuid4()) + '_' + str(res['message_id'])
|
|
|
|
|
|
|
|
|
|
def _coolq_api_wrapper(self, func_name, **kwargs):
|
|
|
|
|
try:
|
|
|
|
|
func = getattr(self.coolq_bot, func_name)
|
|
|
|
|
res = func(**kwargs)
|
|
|
|
|
except RequestException as e:
|
|
|
|
|
raise CoolQDisconnectedException(self._('Unable to connect to CoolQ Client!'
|
|
|
|
|
'Error Message:\n{}').format(str(e)))
|
|
|
|
|
except cqhttp.Error as ex:
|
|
|
|
|
api_ex = CoolQAPIFailureException(self._('CoolQ HTTP API encountered an error!\n'
|
|
|
|
|
'Status Code:{} '
|
|
|
|
|
'Return Code:{}').format(ex.status_code, ex.retcode))
|
|
|
|
|
setattr(api_ex, 'status_code', ex.status_code)
|
|
|
|
|
setattr(api_ex, 'retcode', ex.retcode)
|
|
|
|
|
raise api_ex
|
|
|
|
|
else:
|
|
|
|
|
return res
|
|
|
|
|
|
|
|
|
|
def check_running_status(self):
|
|
|
|
|
res = self._coolq_api_wrapper('get_status')
|
|
|
|
|
if res['good'] or res['online']:
|
|
|
|
|
return True
|
|
|
|
|
else:
|
|
|
|
|
raise CoolQOfflineException(self._("CoolQ Client isn't working correctly!"))
|
|
|
|
|
|
|
|
|
|
def coolq_api_query(self, func_name, **kwargs):
|
|
|
|
|
""" # 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)
|
|
|
|
|
elif self.repeat_counter < 3:
|
|
|
|
|
self.deliver_alert_to_master(self._('Your status is offline.\n'
|
|
|
|
|
'You may try login with /0_login'))
|
|
|
|
|
self.repeat_counter += 1
|
|
|
|
|
|
|
|
|
|
def check_status_periodically(self, t_event):
|
|
|
|
|
self.logger.debug('Start checking status...')
|
|
|
|
|
flag = True
|
|
|
|
|
interval = 300
|
|
|
|
|
try:
|
|
|
|
|
flag = self.check_running_status()
|
|
|
|
|
except CoolQDisconnectedException as e:
|
|
|
|
|
if self.repeat_counter < 3:
|
|
|
|
|
self.deliver_alert_to_master(self._("We're unable to communicate with CoolQ Client.\n"
|
|
|
|
|
"Please check the connection and credentials provided.\n"
|
|
|
|
|
"{}").format(str(e)))
|
|
|
|
|
self.repeat_counter += 1
|
|
|
|
|
self.is_connected = False
|
|
|
|
|
self.is_logged_in = False
|
|
|
|
|
interval = 3600
|
|
|
|
|
except (CoolQOfflineException, CoolQAPIFailureException):
|
|
|
|
|
if self.repeat_counter < 3:
|
|
|
|
|
self.deliver_alert_to_master(self._('CoolQ is running in abnormal status.\n'
|
|
|
|
|
'You may need to relogin your account '
|
|
|
|
|
'or have a check in CoolQ Client.\n'))
|
|
|
|
|
self.repeat_counter += 1
|
|
|
|
|
self.is_connected = True
|
|
|
|
|
self.is_logged_in = False
|
|
|
|
|
interval = 3600
|
|
|
|
|
else:
|
|
|
|
|
if not flag:
|
|
|
|
|
if self.repeat_counter < 3:
|
|
|
|
|
self.deliver_alert_to_master(self._("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
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
def deliver_alert_to_master(self, message: str):
|
|
|
|
|
context = {'message': message, 'uid_prefix': 'alert', 'event_description': self._('CoolQ Alert')}
|
|
|
|
|
self.send_msg_to_master(context)
|
|
|
|
|
|
|
|
|
|
def update_friend_list(self):
|
|
|
|
|
self.friend_list = 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:
|
2021-05-30 17:16:50 +00:00
|
|
|
|
if friend['remark'] == '':
|
2021-02-15 15:32:47 +00:00
|
|
|
|
friend['remark'] = friend['nickname']
|
2021-06-03 14:18:49 +00:00
|
|
|
|
self.friend_dict[friend['user_id']] = friend
|
2021-02-15 15:32:47 +00:00
|
|
|
|
else:
|
|
|
|
|
self.logger.warning('Failed to update friend list')
|
|
|
|
|
|
|
|
|
|
def update_group_list(self):
|
|
|
|
|
self.group_list = self.coolq_api_query('get_group_list')
|
|
|
|
|
if self.group_list:
|
|
|
|
|
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:
|
|
|
|
|
self.group_dict[group['group_id']] = group
|
2021-02-15 15:32:47 +00:00
|
|
|
|
else:
|
|
|
|
|
self.logger.warning('Failed to update group list')
|
|
|
|
|
|
|
|
|
|
def update_contacts_periodically(self, t_event):
|
|
|
|
|
self.logger.debug('Start updating friend & group list')
|
|
|
|
|
interval = 1800
|
|
|
|
|
if self.is_connected and self.is_logged_in:
|
|
|
|
|
try:
|
|
|
|
|
self.update_friend_list()
|
|
|
|
|
self.update_group_list()
|
|
|
|
|
except CoolQAPIFailureException as ex:
|
|
|
|
|
if getattr(ex, 'status_code') == 200 and getattr(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(self._('Errors occurred when updating contacts: ')
|
|
|
|
|
+ getattr(ex, 'message'))
|
|
|
|
|
self.update_repeat_counter += 1
|
|
|
|
|
else:
|
|
|
|
|
self.update_repeat_counter = 0
|
|
|
|
|
self.logger.debug('Update completed')
|
|
|
|
|
if t_event is not None and not t_event.is_set():
|
|
|
|
|
self.update_contacts_timer = threading.Timer(interval, self.update_contacts_periodically, [t_event])
|
|
|
|
|
self.update_contacts_timer.start()
|
|
|
|
|
|
|
|
|
|
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):
|
|
|
|
|
self.update_friend_list()
|
|
|
|
|
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
|
2021-06-03 14:18:49 +00:00
|
|
|
|
return self.friend_dict[uid]['remark']
|
2021-02-15 15:32:47 +00:00
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
try:
|
|
|
|
|
author = chat.get_member(SystemChatMember.SYSTEM_ID)
|
|
|
|
|
except KeyError:
|
|
|
|
|
author = chat.add_system_member()
|
|
|
|
|
msg = Message(
|
|
|
|
|
uid="__group_notice__.%s" % int(time.time()),
|
|
|
|
|
type=MsgType.Text,
|
|
|
|
|
chat=chat,
|
|
|
|
|
author=author,
|
|
|
|
|
text=context['message'],
|
|
|
|
|
deliver_to=coordinator.master
|
|
|
|
|
)
|
|
|
|
|
coordinator.send_message(msg)
|
|
|
|
|
|
|
|
|
|
def send_msg_to_master(self, context):
|
|
|
|
|
self.logger.debug(repr(context))
|
|
|
|
|
if not getattr(coordinator, 'master', None): # Master Channel not initialized
|
|
|
|
|
raise Exception(context['message'])
|
|
|
|
|
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(
|
|
|
|
|
uid="__{context[uid_prefix]}__.{uni_id}".format(context=context,
|
|
|
|
|
uni_id=str(int(time.time()))),
|
|
|
|
|
type=MsgType.Text,
|
|
|
|
|
chat=chat,
|
|
|
|
|
author=author,
|
|
|
|
|
deliver_to=coordinator.master
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if 'message' in context:
|
|
|
|
|
msg.text = context['message']
|
|
|
|
|
if 'commands' in context:
|
|
|
|
|
msg.commands = MessageCommands(context['commands'])
|
|
|
|
|
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
|
|
|
|
|
def get_external_group_info(self, group_id): # Special thanks to @lwl12 for thinking of this name
|
2021-06-03 14:18:49 +00:00
|
|
|
|
res = self.coolq_api_query('get_group_info', group_id=group_id)
|
2021-02-15 15:32:47 +00:00
|
|
|
|
return res
|
|
|
|
|
|
|
|
|
|
def send_status(self, status: 'Status'):
|
|
|
|
|
if isinstance(status, MessageRemoval):
|
|
|
|
|
if not isinstance(status.message.author, SelfChatMember):
|
|
|
|
|
raise EFBMessageError(self._('You can only recall your own messages.'))
|
|
|
|
|
try:
|
|
|
|
|
uid_type = status.message.uid.split('_')
|
|
|
|
|
self.recall_message(uid_type[1])
|
|
|
|
|
except CoolQAPIFailureException:
|
|
|
|
|
raise EFBMessageError(
|
|
|
|
|
self._('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)
|
|
|
|
|
|
|
|
|
|
def send_cookie_expired_alarm(self):
|
|
|
|
|
self.deliver_alert_to_master(self._('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.'))
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
except CoolQAPIFailureException as e:
|
|
|
|
|
return (self._('Failed to process request! Error Message:\n')
|
|
|
|
|
+ getattr(e, 'message', repr(e)))
|
|
|
|
|
return 'Done'
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
except CoolQAPIFailureException as e:
|
|
|
|
|
return (self._('Failed to process request! Error Message:\n')
|
|
|
|
|
+ getattr(e, 'message', repr(e)))
|
|
|
|
|
return 'Done'
|
|
|
|
|
|
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):
|
|
|
|
|
context['message'] = self._("[Download] ") + res
|
|
|
|
|
self.send_efb_group_notice(context)
|
|
|
|
|
elif res is None:
|
|
|
|
|
pass
|
|
|
|
|
else:
|
|
|
|
|
data = {'file': res, 'filename': context['file']['name']}
|
|
|
|
|
context['message_type'] = 'group'
|
|
|
|
|
efb_msg = self.msg_decorator.qq_file_after_wrapper(data)
|
|
|
|
|
efb_msg.uid = str(context['user_id']) + '_' + str(uuid.uuid4()) + '_' + str(1)
|
|
|
|
|
efb_msg.text = 'Sent a file\n{}'.format(context['file']['name'])
|
2021-06-02 16:20:31 +00:00
|
|
|
|
if context['uid_prefix'] == 'offline_file':
|
|
|
|
|
efb_msg.chat = 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)
|
2021-02-15 15:32:47 +00:00
|
|
|
|
efb_msg.author = 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)
|
|
|
|
|
|
2021-06-02 16:20:31 +00:00
|
|
|
|
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)
|
|
|
|
|
download_url = file['url']
|
|
|
|
|
self.async_download_file(context, download_url)
|
|
|
|
|
|
2021-02-15 15:32:47 +00:00
|
|
|
|
def get_chat_picture(self, chat: 'Chat') -> BinaryIO:
|
|
|
|
|
chat_type = chat.uid.split('_')
|
|
|
|
|
if chat_type[0] == 'private':
|
|
|
|
|
return download_user_avatar(chat_type[1])
|
|
|
|
|
elif chat_type[0] == 'group':
|
|
|
|
|
return download_group_avatar(chat_type[1])
|
|
|
|
|
else:
|
|
|
|
|
return download_group_avatar("")
|
|
|
|
|
|
|
|
|
|
def get_chats(self):
|
|
|
|
|
qq_chats = self.get_friends()
|
|
|
|
|
group_chats = self.get_groups()
|
|
|
|
|
return qq_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)
|
|
|
|
|
context = {"user_id": qq_uid}
|
|
|
|
|
if remark is not None:
|
|
|
|
|
context['alias'] = remark
|
|
|
|
|
return 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)
|
|
|
|
|
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)
|
|
|
|
|
raise EFBChatNotFound()
|
|
|
|
|
|
|
|
|
|
def check_self_update(self, t_event):
|
|
|
|
|
interval = 60 * 60 * 24
|
|
|
|
|
latest_version = self.channel.check_updates()
|
|
|
|
|
if latest_version is not None:
|
|
|
|
|
self.deliver_alert_to_master("New version({version}) of EFB-QQ-Slave has released! "
|
|
|
|
|
"Please manually update EQS by stopping ehForwarderbot first and then execute "
|
|
|
|
|
"<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()
|
|
|
|
|
|
|
|
|
|
def poll(self):
|
|
|
|
|
self.check_self_update(threading.Event())
|
|
|
|
|
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']
|