예제 #1
0
    def send_message(self, msg: 'Message') -> 'Message':
        chat_info = msg.chat.uid.split('_')
        chat_type = chat_info[0]
        chat_uid = chat_info[1]
        messages = []
        if msg.edit:
            try:
                asyncio.run_coroutine_threadsafe(self.bot.recall(int(msg.uid)), self.loop)
            except:
                print_exc()
                raise EFBOperationNotSupported("Failed to recall the message!\n"
                                               "This message may have already expired.")
        logging.getLogger(__name__).debug(f"Target: {msg.target}")
        if msg.type in [MsgType.Text, MsgType.Link]:
            if isinstance(msg.target, Message):
                max_length = 50
                uin = msg.target.author.uid.split("_")[1]
                messages.append(At(target=int(uin), display="@"))
                tgt_text = process_quote_text(msg.target.text, max_length)
                msg.text = "%s\n\n%s" % (tgt_text, msg.text)
            messages.append(Plain(text=msg.text))

        elif msg.type in (MsgType.Image, MsgType.Sticker, MsgType.Animation):
            self.logger.info("[%s] Image/Sticker/Animation %s", msg.uid, msg.type)
            messages.append(Image(path=msg.file.name))
            if msg.text:
                messages.append(Plain(text=msg.text))
        else:
            raise EFBOperationNotSupported(f"Unsupported message type {msg.type}")
        return_message = self.mirai_send_messages(chat_type, chat_info, messages)
        self.logger.debug(return_message)
        msg.uid = return_message.messageId
        return msg
예제 #2
0
    def react_to_message_status(self, status: ReactToMessage):
        if self.accept_message_reactions == "reject_one":
            raise EFBMessageReactionNotPossible(
                "Message reaction is rejected by flag.")
        if self.accept_message_reactions == "reject_all":
            raise EFBOperationNotSupported(
                "All message reactions are rejected by flag.")
        message = self.messages_sent.get(status.msg_id)
        if message is None:
            raise EFBOperationNotSupported("Message is not found.")

        if status.reaction is None:
            for idx, i in message.reactions.items():
                message.reactions[idx] = [
                    j for j in i if not isinstance(j, SelfChatMember)
                ]
        else:
            if status.reaction not in message.reactions:
                message.reactions[status.reaction] = []
            message.reactions[status.reaction].append(message.chat.self)

        coordinator.send_status(
            MessageReactionsUpdate(chat=message.chat,
                                   msg_id=message.uid,
                                   reactions=message.reactions))
 def get_chat_picture(self, chat: EFBChat) -> IO[bytes]:
     uid = chat.chat_uid
     if uid in wxpy.Chat.SYSTEM_ACCOUNTS:
         wxpy_chat: wxpy.Chat = wxpy.Chat(wxpy.utils.wrap_user_name(uid), self.bot)
     else:
         wxpy_chat = wxpy.utils.ensure_one(self.bot.search(puid=uid))
     f = None
     try:
         f = tempfile.NamedTemporaryFile(suffix='.jpg')
         data = wxpy_chat.get_avatar(None)
         if not data:
             raise EFBOperationNotSupported()
         f.write(data)
         f.seek(0)
         return f
     except (TypeError, ResponseError):
         if f is not None:
             f.close()
         raise EFBOperationNotSupported()
예제 #4
0
 def send_status(self, status: EFBStatus):
     if isinstance(status, EFBMessageRemoval):
         if not status.message.author.is_self:
             raise EFBMessageError(self._('You can only recall your own messages.'))
         try:
             ews_utils.message_to_dummy_message(status.message.uid, self).recall()
         except wxpy.ResponseError as e:
             raise EFBMessageError(
                 self._('Failed to recall the message.') + '{0} ({1})'.format(e.err_msg, e.err_code))
     else:
         raise EFBOperationNotSupported()
예제 #5
0
 def get_message_by_id(self, chat_uid: str, msg_id: str) -> Optional['EFBMsg']:
     msg_log = self.db.get_msg_log(slave_origin_uid=chat_uid, slave_msg_id=msg_id)
     if msg_log is not None:
         if msg_log.pickle:
             return pickle.loads(msg_log.pickle)
         else:
             # Pickled data is not recorded.
             raise EFBOperationNotSupported(self._("Message is not possible to be retrieved."))
     else:
         # Message is not found.
         return
예제 #6
0
 def send_status(self, status: 'EFBStatus'):
     if isinstance(status, EFBMessageRemoval):
         if not status.message.author.is_self:
             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()
예제 #7
0
 def get_chat_picture(self, chat: EFBChat) -> IO[bytes]:
     chat = self.chats.search_chat(uid=chat.chat_uid)
     wxpy_chat: wxpy.Chat = chat.vendor_specific['wxpy_object']
     f = None
     try:
         f = tempfile.NamedTemporaryFile(suffix='.jpg')
         wxpy_chat.get_avatar(f.name)
         f.seek(0)
         return f
     except TypeError:
         if hasattr(f, 'close'):
             f.close()
         raise EFBOperationNotSupported()
예제 #8
0
 def send_status(self, status: Status):
     if isinstance(status, MessageRemoval):
         if not isinstance(status.message.author, SelfChatMember):
             raise EFBOperationNotSupported(
                 self._('You can only recall your own messages.'))
         if status.message.uid:
             try:
                 msg_ids = json.loads(status.message.uid)
             except JSONDecodeError:
                 raise EFBMessageError(
                     self._("ID of the message to recall is invalid."))
         else:
             raise EFBMessageError(
                 self._("ID of the message to recall is not found."))
         failed = 0
         if any(len(i) == 1 for i in msg_ids):  # Message is not sent through EWS
             raise EFBOperationNotSupported(
                 self._("You may only recall messages sent via EWS.")
             )
         for i in msg_ids:
             try:
                 ews_utils.message_id_to_dummy_message(i, self).recall()
             except wxpy.ResponseError:
                 failed += 1
         if failed:
             raise EFBMessageError(
                 self.ngettext(
                     'Failed to recall {failed} of {total} message.',
                     'Failed to recall {failed} of {total} messages.',
                     len(msg_ids)
                 ).format(failed=failed, total=len(msg_ids)))
         else:
             val = (status.message.uid, len(msg_ids))
             for i in msg_ids:
                 self.slave_message.recall_msg_id_conversion[str(
                     i[1])] = val
     else:
         raise EFBOperationNotSupported()
예제 #9
0
 def get_message_by_id(self, chat: EFBChat,
                       msg_id: MessageID) -> Optional['EFBMsg']:
     origin_uid = etm_utils.chat_id_to_str(chat=chat)
     msg_log = self.db.get_msg_log(slave_origin_uid=origin_uid,
                                   slave_msg_id=msg_id)
     if msg_log is not None:
         if msg_log.pickle:
             return ETMMsg.unpickle(msg_log.pickle, self.db)
         else:
             # Pickled data is not recorded.
             raise EFBOperationNotSupported(
                 self._("Message is not possible to be retrieved."))
     else:
         # Message is not found.
         return None
 def send_status(self, status: Status):
     if isinstance(status, MessageRemoval):
         uid: str = cast(str, status.message.uid)
         rfind = uid.rfind('.')
         if rfind > 0 and uid[rfind + 1:].isdecimal():
             uid = uid[:rfind]
         return self.client.unsend(uid)
     elif isinstance(status, ReactToMessage):
         try:
             self.client.reactToMessage(
                 status.msg_id, status.reaction
                 and MessageReaction(status.reaction))
         except (FBchatException, ValueError) as e:
             self.logger.error(f"Error occurred while sending status: {e}")
             raise EFBMessageReactionNotPossible(*e.args)
         return
     # Other status types go here
     raise EFBOperationNotSupported()
 def get_chat_picture(self, chat: Chat) -> BinaryIO:
     self.logger.debug("Getting picture of chat %s", chat)
     photo_url = chat.vendor_specific.get('profile_picture_url')
     self.logger.debug("[%s] has photo_url from cache: %s", chat.uid,
                       photo_url)
     if not photo_url:
         thread = self.client.get_thread_info(chat.uid)
         photo_url = efms_utils.get_value(
             thread, ('messaging_actor', 'big_image_src', 'uri'))
     self.logger.debug("[%s] has photo_url from GraphQL: %s", chat.uid,
                       photo_url)
     if not photo_url:
         thread = self.client.fetchThreadInfo(chat.uid)[chat.uid]
         photo_url = getattr(thread, 'photo', None)
     self.logger.debug("[%s] has photo_url from legacy API: %s", chat.uid,
                       photo_url)
     if not photo_url:
         raise EFBOperationNotSupported('This chat has no picture.')
     photo = BytesIO(requests.get(photo_url).content)
     photo.seek(0)
     return photo
예제 #12
0
    def send_message(self, msg: EFBMsg):
        # 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.chat_uid.split('_')

        self.logger.debug('[%s] Is edited: %s', msg.uid, msg.edit)
        if msg.edit:
            if self.client_config['is_pro']:
                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."))

        if msg.type in [MsgType.Text, MsgType.Link]:
            if msg.text == "kick`":
                group_id = chat_type[1]
                user_id = msg.target.author.chat_uid.split('_')[1]
                self.coolq_api_query("set_group_kick",
                                     group_id=group_id,
                                     user_id=user_id)
            else:
                if isinstance(msg.target, EFBMsg):
                    max_length = 50
                    tgt_text = coolq_text_encode(process_quote_text(msg.target.text, max_length))
                    user_type = msg.target.author.chat_uid.split('_')
                    tgt_alias = ""
                    if chat_type[0] != 'private' and not msg.target.author.is_self:
                        tgt_alias += m.coolq_code_at_wrapper(user_type[1])
                    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 = ''
            if not self.client_config['is_pro']:  # CoolQ Air
                if self.client_config['air_option']['upload_to_smms']:
                    text = '[Image] {}'
                    smms_data = None
                    try:
                        smms_data = upload_image_smms(msg.file, msg.path)
                    except CoolQUnknownException as e:
                        text = '[Image]'
                        self.deliver_alert_to_master(self._('Failed to upload the image to sm.ms! Return Msg: ')
                                                     + getattr(e, 'message', repr(e)))
                    else:
                        if smms_data is not None:
                            text = text.format(smms_data['url'])
                elif 'upload_to_vim_cn' in self.client_config['air_option'] \
                        and self.client_config['air_option']['upload_to_vim_cn']:
                    text = '[Image] {}'
                    vim_cn_data = None
                    try:
                        vim_cn_data = upload_image_vim_cn(msg.file, msg.path)
                    except CoolQUnknownException as e:
                        text = '[Image]'
                        self.deliver_alert_to_master(self._('Failed to upload the image to vim-cn.com! Return Msg: ')
                                                     + getattr(e, 'message', repr(e)))
                    else:
                        if vim_cn_data is not None:
                            text = text.format(vim_cn_data)
                elif 'upload_to_mi' in self.client_config['air_option'] \
                        and self.client_config['air_option']['upload_to_mi']:
                    text = '[Image] {}'
                    mi_data = None
                    try:
                        mi_data = upload_image_mi(msg.file, msg.path)
                    except CoolQUnknownException as e:
                        text = '[Image]'
                        self.deliver_alert_to_master(self._('Failed to upload the image to mi.com! Return Msg: ')
                                                     + getattr(e, 'message', repr(e)))
                    else:
                        if mi_data is not None:
                            text = text.format(mi_data)
                elif 'upload_to_sogou' in self.client_config['air_option'] \
                        and self.client_config['air_option']['upload_to_sogou']:
                    text = '[Image] {}'
                    sogou_data = None
                    try:
                        sogou_data = upload_image_sogou(msg.file, msg.path)
                    except CoolQUnknownException as e:
                        text = '[Image]'
                        self.deliver_alert_to_master(self._('Failed to upload the image to sogou.com! Return Msg: ')
                                                     + getattr(e, 'message', repr(e)))
                    else:
                        if sogou_data is not None:
                            text = text.format(sogou_data)
                else:
                    text = '[Image]'
            else:
                if msg.type != MsgType.Sticker:
                    text += m.coolq_code_image_wrapper(msg.file, msg.path)
                else:
                    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)
            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
        return msg
 def get_chat_picture(self, chat: Chat) -> BinaryIO:
     raise EFBOperationNotSupported()
 def send_status(self, status: Status):
     raise EFBOperationNotSupported()
예제 #15
0
 def get_chat(self,
              chat_uid: ChatID,
              member_uid: Optional[ChatID] = None) -> EFBChat:
     raise EFBOperationNotSupported()
예제 #16
0
    def send_message(self, msg: EFBMsg) -> EFBMsg:
        """Send a message to WeChat.
        Supports text, image, sticker, and file.

        Args:
            msg (channel.EFBMsg): Message Object to be sent.

        Returns:
            This method returns nothing.

        Raises:
            EFBMessageTypeNotSupported: Raised when message type is not supported by the channel.
        """
        chat: wxpy.Chat = self.chats.get_wxpy_chat_by_uid(msg.chat.chat_uid)
        r: wxpy.SentMessage
        self.logger.info("[%s] Sending message to WeChat:\n"
                         "uid: %s\n"
                         "UserName: %s\n"
                         "NickName: %s\n"
                         "Type: %s\n"
                         "Text: %s",
                         msg.uid,
                         msg.chat.chat_uid, chat.user_name, chat.name, msg.type, msg.text)

        chat.mark_as_read()

        self.logger.debug('[%s] Is edited: %s', msg.uid, msg.edit)
        if msg.edit:
            if self.flag('delete_on_edit'):
                try:
                    ews_utils.message_to_dummy_message(msg.uid, self).recall()
                except wxpy.ResponseError as e:
                    self.logger.error("[%s] Trying to recall message but failed: %s", msg.uid, e)
                    raise EFBMessageError(self._('Failed to recall message, edited message was not sent.'))
            else:
                raise EFBOperationNotSupported()
        if msg.type in [MsgType.Text, MsgType.Link]:
            if isinstance(msg.target, EFBMsg):
                max_length = self.flag("max_quote_length")
                qt_txt = "%s" % msg.target.text
                if max_length > 0:
                    tgt_text = qt_txt[:max_length]
                    if len(qt_txt) >= max_length:
                        tgt_text += "…"
                    tgt_text = "「%s」" % tgt_text
                elif max_length < 0:
                    tgt_text = "「%s」" % qt_txt
                else:
                    tgt_text = ""
                if isinstance(chat, wxpy.Group) and not msg.target.author.is_self:
                    tgt_alias = "@%s\u2005 " % msg.target.author.display_name
                else:
                    tgt_alias = ""
                msg.text = "%s%s\n\n%s" % (tgt_alias, tgt_text, msg.text)
            r = self._bot_send_msg(chat, 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/GIF/Sticker %s", msg.uid, msg.type)

            convert_to = None
            file = msg.file
            assert file is not None

            if self.flag('send_stickers_and_gif_as_jpeg'):
                if msg.type == MsgType.Sticker or msg.mime == "image/gif":
                    convert_to = "image/jpeg"
            else:
                if msg.type == MsgType.Sticker:
                    convert_to = "image/gif"

            if convert_to == "image/gif":
                with NamedTemporaryFile(suffix=".gif") as f:
                    try:
                        img = Image.open(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.path = f.name
                        self.logger.debug('[%s] Image converted from %s to GIF', msg.uid, msg.mime)
                        file.close()
                        f.seek(0)
                        if os.fstat(f.fileno()).st_size > self.MAX_FILE_SIZE:
                            raise EFBMessageError(self._("Image size is too large. (IS02)"))
                        r = self._bot_send_image(chat, f.name, f)
                    finally:
                        if not file.closed:
                            file.close()
            elif convert_to == "image/jpeg":
                with NamedTemporaryFile(suffix=".jpg") as f:
                    try:
                        img = Image.open(file).convert('RGBA')
                        out = Image.new("RGBA", img.size, (255,255,255,255))
                        out.paste(img, img)
                        out.convert('RGB').save(f)
                        msg.path = f.name
                        self.logger.debug('[%s] Image converted from %s to JPEG', msg.uid, msg.mime)
                        file.close()
                        f.seek(0)
                        if os.fstat(f.fileno()).st_size > self.MAX_FILE_SIZE:
                            raise EFBMessageError(self._("Image size is too large. (IS02)"))
                        r = self._bot_send_image(chat, f.name, f)
                    finally:
                        if not file.closed:
                            file.close()
            else:
                try:
                    if os.fstat(file.fileno()).st_size > self.MAX_FILE_SIZE:
                        raise EFBMessageError(self._("Image size is too large. (IS01)"))
                    self.logger.debug("[%s] Sending %s (image) to WeChat.", msg.uid, msg.path)
                    r = self._bot_send_image(chat, msg.path, file)
                finally:
                    if not file.closed:
                        file.close()
            if msg.text:
                self._bot_send_msg(chat, msg.text)
        elif msg.type in (MsgType.File, MsgType.Audio):
            self.logger.info("[%s] Sending %s to WeChat\nFileName: %s\nPath: %s\nFilename: %s",
                             msg.uid, msg.type, msg.text, msg.path, msg.filename)
            r = self._bot_send_file(chat, msg.filename, file=msg.file)
            if msg.text:
                self._bot_send_msg(chat, msg.text)
            msg.file.close()
        elif msg.type == MsgType.Video:
            self.logger.info("[%s] Sending video to WeChat\nFileName: %s\nPath: %s", msg.uid, msg.text, msg.path)
            r = self._bot_send_video(chat, msg.path, file=msg.file)
            if msg.text:
                self._bot_send_msg(chat, msg.text)
            msg.file.close()
        else:
            raise EFBMessageTypeNotSupported()

        msg.uid = ews_utils.generate_message_uid(r)
        self.logger.debug('WeChat message is assigned with unique ID: %s', msg.uid)
        return msg
예제 #17
0
 def get_chat_picture(self, chat: EFBChat) -> IO[bytes]:
     raise EFBOperationNotSupported()
예제 #18
0
 def get_chats(self) -> List[Chat]:
     raise EFBOperationNotSupported()
예제 #19
0
    def update_group_info(self, bot: telegram.Bot, update: telegram.Update):
        """
        Update the title and profile picture of singly-linked Telegram group
        according to the linked remote chat.
        """
        if update.effective_chat.id == self.bot.get_me().id:
            return self.bot.reply_error(
                update,
                self.
                _('Send /update_info in a group where this bot is a group admin '
                  'to update group title and profile picture'))
        if update.effective_message.forward_from_chat and \
                update.effective_message.forward_from_chat.type == telegram.Chat.CHANNEL:
            tg_chat = update.effective_message.forward_from_chat.id
        else:
            tg_chat = update.effective_chat.id
        chats = self.db.get_chat_assoc(master_uid=utils.chat_id_to_str(
            channel=self.channel, chat_uid=tg_chat))
        if len(chats) != 1:
            return self.bot.reply_error(
                update,
                self.ngettext(
                    'This only works in a group linked with one chat. '
                    'Currently {0} chat linked to this group.',
                    'This only works in a group linked with one chat. '
                    'Currently {0} chats linked to this group.',
                    len(chats)).format(len(chats)))
        picture: Optional[IO] = None
        pic_resized: Optional[IO] = None
        try:
            channel_id, chat_uid = utils.chat_id_str_to_id(chats[0])
            channel = coordinator.slaves[channel_id]
            chat = channel.get_chat(chat_uid)
            if chat is None:
                raise EFBChatNotFound()
            chat = ETMChat(chat=chat, db=self.db)
            bot.set_chat_title(tg_chat, chat.chat_title)
            picture = channel.get_chat_picture(chat)
            if not picture:
                raise EFBOperationNotSupported()
            pic_img = Image.open(picture)

            if pic_img.size[0] < self.TELEGRAM_MIN_PROFILE_PICTURE_SIZE or \
                    pic_img.size[1] < self.TELEGRAM_MIN_PROFILE_PICTURE_SIZE:
                # resize
                scale = self.TELEGRAM_MIN_PROFILE_PICTURE_SIZE / min(
                    pic_img.size)
                pic_resized = io.BytesIO()
                pic_img.resize(tuple(map(lambda a: int(scale * a), pic_img.size)), Image.BICUBIC) \
                    .save(pic_resized, 'PNG')
                pic_resized.seek(0)

            picture.seek(0)

            bot.set_chat_photo(tg_chat, pic_resized or picture)
            update.message.reply_text(self._('Chat details updated.'))
        except KeyError:
            return self.bot.reply_error(update,
                                        self._('Channel linked is not found.'))
        except EFBChatNotFound:
            self.logger.exception("Chat linked is not found in channel.")
            return self.bot.reply_error(
                update, self._('Chat linked is not found in channel.'))
        except telegram.TelegramError as e:
            self.logger.exception("Error occurred while update chat details.")
            return self.bot.reply_error(
                update,
                self._('Error occurred while update chat details.\n'
                       '{0}'.format(e.message)))
        except EFBOperationNotSupported:
            return self.bot.reply_error(
                update, self._('No profile picture provided from this chat.'))
        except Exception as e:
            self.logger.exception("Unknown error caught when querying chat.")
            return self.bot.reply_error(
                update,
                self._('Error occurred while update chat details. \n'
                       '{0}'.format(e)))
        finally:
            if getattr(picture, 'close', None):
                picture.close()
            if getattr(pic_resized, 'close', None):
                pic_resized.close()
예제 #20
0
    def send_message(self, msg: Message) -> Message:
        """Send a message to WeChat.
        Supports text, image, sticker, and file.

        Args:
            msg (channel.Message): Message Object to be sent.

        Returns:
            This method returns nothing.

        Raises:
            EFBChatNotFound:
                Raised when a chat required is not found.

            EFBMessageTypeNotSupported:
                Raised when the message type sent is not supported by the
                channel.

            EFBOperationNotSupported:
                Raised when an message edit request is sent, but not
                supported by the channel.

            EFBMessageNotFound:
                Raised when an existing message indicated is not found.
                E.g.: The message to be edited, the message referred
                in the :attr:`msg.target <.Message.target>`
                attribute.

            EFBMessageError:
                Raised when other error occurred while sending or editing the
                message.
        """
        if msg.chat == self.user_auth_chat:
            raise EFBChatNotFound

        chat: wxpy.Chat = self.chats.get_wxpy_chat_by_uid(msg.chat.uid)

        # List of "SentMessage" response for all messages sent
        r: List[wxpy.SentMessage] = []
        self.logger.info("[%s] Sending message to WeChat:\n"
                         "uid: %s\n"
                         "UserName: %s\n"
                         "NickName: %s\n"
                         "Type: %s\n"
                         "Text: %s",
                         msg.uid,
                         msg.chat.uid, chat.user_name, chat.name, msg.type, msg.text)

        try:
            chat.mark_as_read()
        except wxpy.ResponseError as e:
            self.logger.exception(
                "[%s] Error occurred while marking chat as read. (%s)", msg.uid, e)

        send_text_only = False
        self.logger.debug('[%s] Is edited: %s', msg.uid, msg.edit)
        if msg.edit and msg.uid:
            if self.flag('delete_on_edit'):
                msg_ids = json.loads(msg.uid)
                if msg.type in self.MEDIA_MSG_TYPES and not msg.edit_media:
                    # Treat message as text message to prevent resend of media
                    msg_ids = msg_ids[1:]
                    send_text_only = True
                failed = 0
                for i in msg_ids:
                    try:
                        ews_utils.message_id_to_dummy_message(i, self).recall()
                    except wxpy.ResponseError as e:
                        self.logger.error(
                            "[%s] Trying to recall message but failed: %s", msg.uid, e)
                        failed += 1
                if failed:
                    raise EFBMessageError(
                        self.ngettext('Failed to recall {failed} out of {total} message, edited message was not sent.',
                                      'Failed to recall {failed} out of {total} messages, edited message was not sent.',
                                      len(msg_ids)).format(
                            failed=failed,
                            total=len(msg_ids)
                        ))
                # Not caching message ID as message recall feedback is not needed in edit mode
            else:
                raise EFBOperationNotSupported()
        if send_text_only or msg.type in [MsgType.Text, MsgType.Link]:
            if isinstance(msg.target, Message):
                max_length = self.flag("max_quote_length")
                qt_txt = msg.target.text or msg.target.type.name
                if max_length > 0:
                    if len(qt_txt) >= max_length:
                        tgt_text = qt_txt[:max_length]
                        tgt_text += "…"
                    else:
                        tgt_text = qt_txt
                elif max_length < 0:
                    tgt_text = qt_txt
                else:
                    tgt_text = ""
                if isinstance(chat, wxpy.Group) and not isinstance(msg.target.author, SelfChatMember):
                    tgt_alias = "@%s\u2005:" % msg.target.author.display_name
                else:
                    tgt_alias = ""
                msg.text = f"「{tgt_alias}{tgt_text}」\n- - - - - - - - - - - - - - -\n{msg.text}"
            r.append(self._bot_send_msg(chat, 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/GIF/Sticker %s", msg.uid, msg.type)

            convert_to = None
            file = msg.file
            assert file is not None

            if self.flag('send_stickers_and_gif_as_jpeg'):
                if msg.type == MsgType.Sticker or msg.mime == "image/gif":
                    convert_to = "image/jpeg"
            else:
                if msg.type == MsgType.Sticker:
                    convert_to = "image/gif"

            if convert_to == "image/gif":
                with NamedTemporaryFile(suffix=".gif") as f:
                    try:
                        img = Image.open(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.path = Path(f.name)
                        self.logger.debug(
                            '[%s] Image converted from %s to GIF', msg.uid, msg.mime)
                        file.close()
                        if f.seek(0, 2) > self.MAX_FILE_SIZE:
                            raise EFBMessageError(
                                self._("Image size is too large. (IS02)"))
                        f.seek(0)
                        r.append(self._bot_send_image(chat, f.name, f))
                    finally:
                        if not file.closed:
                            file.close()
            elif convert_to == "image/jpeg":
                with NamedTemporaryFile(suffix=".jpg") as f:
                    try:
                        img = Image.open(file).convert('RGBA')
                        out = Image.new("RGBA", img.size, (255, 255, 255, 255))
                        out.paste(img, img)
                        out.convert('RGB').save(f)
                        msg.path = Path(f.name)
                        self.logger.debug(
                            '[%s] Image converted from %s to JPEG', msg.uid, msg.mime)
                        file.close()
                        if f.seek(0, 2) > self.MAX_FILE_SIZE:
                            raise EFBMessageError(
                                self._("Image size is too large. (IS02)"))
                        f.seek(0)
                        r.append(self._bot_send_image(chat, f.name, f))
                    finally:
                        if not file.closed:
                            file.close()
            else:
                try:
                    if file.seek(0, 2) > self.MAX_FILE_SIZE:
                        raise EFBMessageError(
                            self._("Image size is too large. (IS01)"))
                    file.seek(0)
                    self.logger.debug(
                        "[%s] Sending %s (image) to WeChat.", msg.uid, msg.path)
                    filename = msg.filename or (msg.path and msg.path.name)
                    assert filename
                    r.append(self._bot_send_image(chat, filename, file))
                finally:
                    if not file.closed:
                        file.close()
            if msg.text:
                r.append(self._bot_send_msg(chat, msg.text))
        elif msg.type in (MsgType.File, MsgType.Audio):
            self.logger.info("[%s] Sending %s to WeChat\nFileName: %s\nPath: %s\nFilename: %s",
                             msg.uid, msg.type, msg.text, msg.path, msg.filename)
            filename = msg.filename or (msg.path and msg.path.name)
            assert filename and msg.file
            r.append(self._bot_send_file(chat, filename, file=msg.file))
            if msg.text:
                self._bot_send_msg(chat, msg.text)
            if not msg.file.closed:
                msg.file.close()
        elif msg.type == MsgType.Video:
            self.logger.info(
                "[%s] Sending video to WeChat\nFileName: %s\nPath: %s", msg.uid, msg.text, msg.path)
            filename = msg.filename or (msg.path and msg.path.name)
            assert filename and msg.file
            r.append(self._bot_send_video(chat, filename, file=msg.file))
            if msg.text:
                r.append(self._bot_send_msg(chat, msg.text))
            if not msg.file.closed:
                msg.file.close()
        else:
            raise EFBMessageTypeNotSupported()

        msg.uid = ews_utils.generate_message_uid(r)
        self.logger.debug(
            'WeChat message is assigned with unique ID: %s', msg.uid)
        return msg
예제 #21
0
 def get_message_by_id(self, chat_uid: ChatID, msg_id: MessageID) -> Optional['EFBMsg']:
     raise EFBOperationNotSupported()
예제 #22
0
 def message_removal_status(self, status: MessageRemoval):
     if not self.message_removal_possible:
         raise EFBOperationNotSupported(
             "Message removal is not possible by flag.")
    def update_group_info(self, update: Update, context: CallbackContext):
        """
        Update the title and profile picture of singly-linked Telegram group
        according to the linked remote chat.

        Triggered by ``/update_info`` command.
        """
        if update.effective_chat.type == telegram.Chat.PRIVATE:
            return self.bot.reply_error(
                update,
                self.
                _('Send /update_info to a group where this bot is a group admin '
                  'to update group title, description and profile picture.'))
        forwarded_from_chat = update.effective_message.forward_from_chat
        if forwarded_from_chat and forwarded_from_chat.type == telegram.Chat.CHANNEL:
            tg_chat = forwarded_from_chat.id
        else:
            tg_chat = update.effective_chat.id
        chats = self.db.get_chat_assoc(master_uid=utils.chat_id_to_str(
            channel=self.channel, chat_uid=tg_chat))
        if len(chats) != 1:
            return self.bot.reply_error(
                update,
                self.ngettext(
                    'This only works in a group linked with one chat. '
                    'Currently {0} chat linked to this group.',
                    'This only works in a group linked with one chat. '
                    'Currently {0} chats linked to this group.',
                    len(chats)).format(len(chats)))
        picture: Optional[IO] = None
        pic_resized: Optional[IO] = None
        channel_id, chat_uid, _ = utils.chat_id_str_to_id(chats[0])
        if channel_id not in coordinator.slaves:
            self.logger.exception(
                f"Channel linked ({channel_id}) is not found.")
            return self.bot.reply_error(
                update,
                self._('Channel linked ({channel}) is not found.').format(
                    channel=channel_id))
        channel = coordinator.slaves[channel_id]
        try:
            chat = self.chat_manager.update_chat_obj(
                channel.get_chat(chat_uid), full_update=True)

            self.bot.set_chat_title(
                tg_chat,
                self.truncate_ellipsis(chat.chat_title,
                                       self.MAX_LEN_CHAT_TITLE))

            # Update remote group members list to Telegram group description if available
            desc = chat.description
            if isinstance(chat, ETMGroupChat):
                names = [
                    i.long_name for i in chat.members
                    if not isinstance(i, SystemChatMember)
                ]
                # TRANSLATORS: Separator between group members in a Telegram group description generated by /update_info
                members = self._(", ").join(names)
                if desc:
                    desc += "\n"
                desc += self.ngettext("{count} group member: {list}",
                                      "{count} group members: {list}",
                                      len(names)).format(count=len(names),
                                                         list=members)
            if desc:
                try:
                    self.bot.set_chat_description(
                        tg_chat,
                        self.truncate_ellipsis(desc, self.MAX_LEN_CHAT_DESC))
                except BadRequest as e:
                    if "Chat description is not modified" in e.message:
                        pass
                    else:
                        self.logger.exception(
                            "Exception occurred while trying to update chat description: %s",
                            e)
                except TelegramError as e:  # description is not updated
                    self.logger.exception(
                        "Exception occurred while trying to update chat description: %s",
                        e)

            picture = channel.get_chat_picture(chat)
            if not picture:
                raise EFBOperationNotSupported()
            pic_img = Image.open(picture)

            if pic_img.size[0] < self.TELEGRAM_MIN_PROFILE_PICTURE_SIZE or \
                    pic_img.size[1] < self.TELEGRAM_MIN_PROFILE_PICTURE_SIZE:
                # resize
                scale = self.TELEGRAM_MIN_PROFILE_PICTURE_SIZE / min(
                    pic_img.size)
                pic_resized = io.BytesIO()
                pic_img.resize(tuple(map(lambda a: int(scale * a), pic_img.size)), Image.BICUBIC) \
                    .save(pic_resized, 'PNG')
                pic_resized.seek(0)

            picture.seek(0)

            self.bot.set_chat_photo(tg_chat, pic_resized or picture)
            update.message.reply_text(self._('Chat details updated.'))
        except EFBChatNotFound:
            self.logger.exception(
                "Chat linked (%s) is not found in the slave channel "
                "(%s).", channel_id, chat_uid)
            return self.bot.reply_error(
                update,
                self.
                _("Chat linked ({chat_uid}) is not found in the slave channel "
                  "({channel_name}, {channel_id}).").format(
                      channel_name=channel.channel_name,
                      channel_id=channel_id,
                      chat_uid=chat_uid))
        except TelegramError as e:
            self.logger.exception("Error occurred while update chat details.")
            return self.bot.reply_error(
                update,
                self._('Error occurred while update chat details.\n'
                       '{0}'.format(e.message)))
        except EFBOperationNotSupported:
            return self.bot.reply_error(
                update, self._('No profile picture provided from this chat.'))
        except Exception as e:
            self.logger.exception("Unknown error caught when querying chat.")
            return self.bot.reply_error(
                update,
                self._('Error occurred while update chat details. \n'
                       '{0}'.format(e)))
        finally:
            if picture and getattr(picture, 'close', None):
                picture.close()
            if pic_resized and getattr(pic_resized, 'close', None):
                pic_resized.close()
예제 #24
0
 def get_message_by_id(self, chat: 'Chat',
                       msg_id: MessageID) -> Optional['Message']:
     raise EFBOperationNotSupported()