Пример #1
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()
Пример #2
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()
Пример #3
0
 def _bot_send_video(self, chat: wxpy.Chat, filename: str, file: IO[bytes]) -> wxpy.SentMessage:
     try:
         return chat.send_video(filename, file=file)
     except wxpy.ResponseError as e:
         e = self.substitute_known_error_reason(e)
         raise EFBMessageError(self._("Error from Web WeChat while sending video: [{code}] {message}")
                               .format(code=e.err_code, message=e.err_msg))
Пример #4
0
 def _bot_send_msg(self, chat: wxpy.Chat, message: str) -> wxpy.SentMessage:
     try:
         return chat.send_msg(message)
     except wxpy.ResponseError as e:
         e = self.substitute_known_error_reason(e)
         raise EFBMessageError(self._("Error from Web WeChat while sending message: [{code}] {message}")
                               .format(code=e.err_code, message=e.err_msg))
Пример #5
0
 def _bot_send_msg(self, chat: wxpy.Chat, message: str) -> wxpy.SentMessage:
     try:
         return chat.send_msg(message)
     except wxpy.ResponseError as e:
         raise EFBMessageError(
             self.
             _("Unknown error occurred while delivering the message: [{code}] {message}"
               ).format(code=e.err_code, message=e.err_msg))
Пример #6
0
 def _bot_send_video(self, chat: wxpy.Chat, filename: str,
                     file: IO[bytes]) -> wxpy.SentMessage:
     try:
         return chat.send_video(filename, file=file)
     except wxpy.ResponseError as e:
         raise EFBMessageError(
             self.
             _("Unknown error occurred while delivering the video: [{code}] {message}"
               ).format(code=e.err_code, message=e.err_msg))
Пример #7
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()
Пример #8
0
    def _check_file_download(self, file_obj: telegram.File):
        """
        Check if the file is available for download..

        Args:
            file_obj (telegram.File): PTB file object

        Raises:
            EFBMessageError: When file exceeds the maximum download size.
        """
        size = getattr(file_obj, "file_size", None)
        if size and size > telegram.constants.MAX_FILESIZE_DOWNLOAD:
            raise EFBMessageError(
                self._("Attachment is too large. Maximum is 20 MB. (AT01)"))
    def _check_file_download(self, file_obj: Any):
        """
        Check if the file is available for download..

        Args:
            file_obj (telegram.File): PTB file object

        Raises:
            EFBMessageError: When file exceeds the maximum download size.
        """
        size = getattr(file_obj, "file_size", None)
        if size and size > MAX_FILESIZE_DOWNLOAD:
            size_str = humanize.naturalsize(size)
            max_size_str = humanize.naturalsize(MAX_FILESIZE_DOWNLOAD)
            raise EFBMessageError(
                self.
                _("Attachment is too large ({size}). Maximum allowed by Telegram Bot API is {max_size}. (AT01)"
                  ).format(size=size_str, max_size=max_size_str))
Пример #10
0
    def _download_file(
            self,
            file_obj: telegram.File,
            mime: Optional[str] = None) -> Tuple[IO[bytes], str, str, str]:
        """
        Download media file from telegram platform.

        Args:
            file_obj (telegram.File): PTB file object
            mime (str): MIME type of the message

        Returns:
            Tuple[IO[bytes], str, str, str]:
                ``tempfile`` file-like object, MIME type, proposed file name, file path

        Raises:
            EFBMessageError: When file exceeds the maximum download size.
        """
        size = getattr(file_obj, "file_size", None)
        file_id = file_obj.file_id
        if size and size > telegram.constants.MAX_FILESIZE_DOWNLOAD:
            raise EFBMessageError(
                self._("Attachment is too large. Maximum is 20 MB. (AT01)"))
        f = self.bot.get_file(file_id)
        if not mime:
            ext = os.path.splitext(f.file_path)[1]
            mime = mimetypes.guess_type(f.file_path, strict=False)[0]
        else:
            ext = mimetypes.guess_extension(mime, strict=False)
        file = tempfile.NamedTemporaryFile(suffix=ext)
        full_path = file.name
        f.download(out=file)
        file.seek(0)
        mime = getattr(file_obj, "mime_type", mime
                       or magic.from_file(full_path, mime=True))
        if type(mime) is bytes:
            mime = mime.decode()
        return file, mime, os.path.basename(full_path), full_path
Пример #11
0
    def _download_file(self, tg_msg, file_obj, mime=''):
        """
        Download media file from telegram platform.

        Args:
            tg_msg: Telegram message instance
            file_obj: File object
            mime: Type of message

        Returns:
            tuple of str[2]: Full path of the file, MIME type
        """

        size = getattr(file_obj, "file_size", None)
        file_id = file_obj.file_id
        if size and size > telegram.constants.MAX_FILESIZE_DOWNLOAD:
            raise EFBMessageError(
                self._("Attachment is too large. Maximum is 20 MB. (AT01)"))
        f = self.bot.get_file(file_id)

        # self.logger.log(99, 'f.file_path[%r]', f.file_path)
        if not mime:
            ext = os.path.splitext(f.file_path)[1]
            mime = mimetypes.guess_type(f.file_path, strict=False)[0]
        else:
            ext = mimetypes.guess_extension(mime, strict=False) or ".unknown"

        file = tempfile.NamedTemporaryFile(suffix=ext)
        full_path = file.name
        f.download(out=file)
        file.seek(0)

        mime = getattr(file_obj, "mime_type", mime
                       or magic.from_file(full_path, mime=True))
        if type(mime) is bytes:
            mime = mime.decode()
        # self.logger.log(99, 'mime[%s], full_path[%s]', mime, full_path)
        return file, mime, os.path.basename(full_path)
Пример #12
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
Пример #13
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
Пример #14
0
    def send_message(self, msg: EFBMsg) -> EFBMsg:
        """
        Process a message from slave channel and deliver it to the user.

        Args:
            msg (EFBMsg): The message.
        """
        try:
            xid = msg.uid
            self.logger.debug("[%s] Slave message delivered to ETM.\n%s", xid, msg)

            chat_uid = utils.chat_id_to_str(chat=msg.chat)
            tg_chat = self.db.get_chat_assoc(slave_uid=chat_uid)
            if tg_chat:
                tg_chat = tg_chat[0]

            self.logger.debug("[%s] The message should deliver to %s", xid, tg_chat)

            if tg_chat == ETMChat.MUTE_CHAT_ID:
                self.logger.debug("[%s] Sender of the message is muted.", xid)
                return msg

            multi_slaves = False

            if tg_chat:
                slaves = self.db.get_chat_assoc(master_uid=tg_chat)
                if slaves and len(slaves) > 1:
                    multi_slaves = True
                    self.logger.debug("[%s] Sender is linked with other chats in a Telegram group.", xid)

            self.logger.debug("[%s] Message is in chat %s", xid, msg.chat)

            # Generate chat text template & Decide type target
            tg_dest = self.channel.config['admins'][0]
            if tg_chat:  # if this chat is linked
                tg_dest = int(utils.chat_id_str_to_id(tg_chat)[1])

            msg_template = self.generate_message_template(msg, tg_chat, multi_slaves)

            self.logger.debug("[%s] Message is sent to Telegram chat %s, with header \"%s\".",
                              xid, tg_dest, msg_template)

            # When editing message
            old_msg_id: Tuple[str, str] = None
            if msg.edit:
                old_msg = self.db.get_msg_log(slave_msg_id=msg.uid,
                                              slave_origin_uid=utils.chat_id_to_str(chat=msg.chat))
                if old_msg:
                    old_msg_id: Tuple[str, str] = utils.message_id_str_to_id(old_msg.master_msg_id)
                else:
                    self.logger.info('[%s] Was supposed to edit this message, '
                                     'but it does not exist in database. Sending new message instead.',
                                     msg.uid)

            # When targeting a message (reply to)
            target_msg_id: Tuple[str, str] = None
            if isinstance(msg.target, EFBMsg):
                self.logger.debug("[%s] Message is replying to %s.", msg.uid, msg.target)
                log = self.db.get_msg_log(
                    slave_msg_id=msg.target.uid,
                    slave_origin_uid=utils.chat_id_to_str(chat=msg.target.chat)
                )
                if not log:
                    self.logger.debug("[%s] Target message %s is not found in database.", msg.uid, msg.target)
                else:
                    self.logger.debug("[%s] Target message has database entry: %s.", msg.uid, log)
                    target_msg_id = utils.message_id_str_to_id(log.master_msg_id)
                    if not target_msg_id or target_msg_id[0] != str(tg_dest):
                        self.logger.error('[%s] Trying to reply to a message not from this chat. '
                                          'Message destination: %s. Target message: %s.',
                                          msg.uid, tg_dest, target_msg_id)
                    else:
                        target_msg_id = target_msg_id[1]

            commands: Optional[List[EFBMsgCommand]] = None
            reply_markup: Optional[telegram.InlineKeyboardMarkup] = None
            if msg.commands:
                commands = msg.commands.commands
                if old_msg_id:
                    raise EFBMessageError(self._('Command message cannot be edited'))
                buttons = []
                for i, ival in enumerate(commands):
                    buttons.append([telegram.InlineKeyboardButton(ival.name, callback_data=str(i))])
                reply_markup = telegram.InlineKeyboardMarkup(buttons)

            msg.text = msg.text or ""

            # Type dispatching
            if msg.type == MsgType.Text:
                tg_msg = self.slave_message_text(msg, tg_dest, msg_template, old_msg_id, target_msg_id, reply_markup)
            elif msg.type == MsgType.Link:
                tg_msg = self.slave_message_link(msg, tg_dest, msg_template, old_msg_id, target_msg_id, reply_markup)
            elif msg.type in [MsgType.Image, MsgType.Sticker]:
                tg_msg = self.slave_message_image(msg, tg_dest, msg_template, old_msg_id, target_msg_id, reply_markup)
            elif msg.type == MsgType.File:
                tg_msg = self.slave_message_file(msg, tg_dest, msg_template, old_msg_id, target_msg_id, reply_markup)
            elif msg.type == MsgType.Audio:
                tg_msg = self.slave_message_audio(msg, tg_dest, msg_template, old_msg_id, target_msg_id, reply_markup)
            elif msg.type == MsgType.Location:
                tg_msg = self.slave_message_location(msg, tg_dest, msg_template, old_msg_id, target_msg_id,
                                                     reply_markup)
            elif msg.type == MsgType.Video:
                tg_msg = self.slave_message_video(msg, tg_dest, msg_template, old_msg_id, target_msg_id, reply_markup)
            elif msg.type == MsgType.Unsupported:
                tg_msg = self.slave_message_unsupported(msg, tg_dest, msg_template, old_msg_id, target_msg_id,
                                                        reply_markup)
            else:
                self.bot.send_chat_action(tg_dest, telegram.ChatAction.TYPING)
                tg_msg = self.bot.send_message(tg_dest, prefix=msg_template,
                                               text=self._("Unsupported type of message. (UT01)"))

            if tg_msg and msg.commands:
                self.channel.commands.register_command(tg_msg, ETMCommandMsgStorage(
                    commands, coordinator.slaves[msg.chat.channel_id], msg_template, msg.text
                ))

            self.logger.debug("[%s] Message is sent to the user.", xid)
            if not msg.author.is_system:
                msg_log = {"master_msg_id": utils.message_id_to_str(tg_msg.chat.id, tg_msg.message_id),
                           "text": msg.text or "Sent a %s." % msg.type,
                           "msg_type": msg.type,
                           "sent_to": "master" if msg.author.is_self else 'slave',
                           "slave_origin_uid": utils.chat_id_to_str(chat=msg.chat),
                           "slave_origin_display_name": msg.chat.chat_alias,
                           "slave_member_uid": msg.author.chat_uid if not msg.author.is_self else None,
                           "slave_member_display_name": msg.author.chat_alias if not msg.author.is_self else None,
                           "slave_message_id": msg.uid,
                           "update": msg.edit
                           }
                self.db.add_msg_log(**msg_log)
                self.logger.debug("[%s] Message inserted/updated to the database.", xid)
        except Exception as e:
            self.logger.error("[%s] Error occurred while processing message from slave channel.\nMessage: %s\n%s\n%s",
                              xid, repr(msg), repr(e), traceback.format_exc())