Пример #1
0
    def test_basic_verify(self):
        with self.subTest("Valid text message"):
            msg = EFBMsg()
            msg.deliver_to = self.channel
            msg.author = self.chat
            msg.chat = self.chat
            msg.type = MsgType.Text
            msg.text = "Message"
            msg.verify()

        for i in (MsgType.Image, MsgType.Audio, MsgType.File, MsgType.Sticker):
            with self.subTest(f"Valid {i} message"), NamedTemporaryFile() as f:
                msg = EFBMsg()
                msg.deliver_to = self.channel
                msg.author = self.chat
                msg.chat = self.chat
                msg.type = i
                msg.file = f
                msg.filename = "test.bin"
                msg.path = f.name
                msg.mime = "application/octet-stream"
                msg.verify()

        with self.subTest("Missing deliver_to"), self.assertRaises(ValueError):
            msg = EFBMsg()
            msg.author = self.chat
            msg.chat = self.chat
            msg.type = MsgType.Text
            msg.text = "Message"
            msg.verify()

        with self.subTest("Missing author"), self.assertRaises(ValueError):
            msg = EFBMsg()
            msg.deliver_to = self.channel
            msg.chat = self.chat
            msg.type = MsgType.Text
            msg.text = "Message"
            msg.verify()

        with self.subTest("Missing chat"), self.assertRaises(ValueError):
            msg = EFBMsg()
            msg.deliver_to = self.channel
            msg.author = self.chat
            msg.type = MsgType.Text
            msg.text = "Message"
            msg.verify()

        with self.subTest("Missing type"), self.assertRaises(ValueError):
            msg = EFBMsg()
            msg.deliver_to = self.channel
            msg.author = self.chat
            msg.chat = self.chat
            msg.text = "Message"
            msg.verify()
Пример #2
0
    def master_qr_code(self, uuid, status, qrcode=None):
        status = int(status)
        if self.qr_uuid == (uuid, status):
            return
        self.qr_uuid = (uuid, status)

        msg = EFBMsg()
        msg.uid = f"ews_auth_{uuid}_{status}"
        msg.type = MsgType.Text
        msg.chat = EFBChat(self).system()
        msg.chat.chat_name = self._("EWS User Auth")
        msg.author = msg.chat
        msg.deliver_to = coordinator.master

        if status == 201:
            msg.type = MsgType.Text
            msg.text = self._('Confirm on your phone.')
        elif status == 200:
            msg.type = MsgType.Text
            msg.text = self._("Successfully logged in.")
        elif uuid != self.qr_uuid:
            msg.type = MsgType.Image
            file = NamedTemporaryFile(suffix=".png")
            qr_url = "https://login.weixin.qq.com/l/" + uuid
            QRCode(qr_url).png(file, scale=10)
            msg.text = self._("QR code expired, please scan the new one.")
            msg.path = file.name
            msg.file = file
            msg.mime = 'image/png'
        if status in (200, 201) or uuid != self.qr_uuid:
            coordinator.send_message(msg)
Пример #3
0
    def master_qr_code(self, uuid, status, qrcode=None):
        status = int(status)
        msg = EFBMsg()
        msg.type = MsgType.Text
        msg.chat = EFBChat(self).system()
        msg.chat.chat_name = self._("EWS User Auth")
        msg.author = msg.chat
        msg.deliver_to = coordinator.master

        if status == 201:
            msg.type = MsgType.Text
            msg.text = self._('Confirm on your phone.')
        elif status == 200:
            msg.type = MsgType.Text
            msg.text = self._("Successfully logged in.")
        elif uuid != self.qr_uuid:
            msg.type = MsgType.Image
            # path = os.path.join("storage", self.channel_id)
            # if not os.path.exists(path):
            #     os.makedirs(path)
            # path = os.path.join(path, 'QR-%s.jpg' % int(time.time()))
            # self.logger.debug("master_qr_code file path: %s", path)
            file = NamedTemporaryFile(suffix=".png")
            qr_url = "https://login.weixin.qq.com/l/" + uuid
            QRCode(qr_url).png(file, scale=10)
            msg.text = self._("QR code expired, please scan the new one.")
            msg.path = file.name
            msg.file = file
            msg.mime = 'image/png'
        if status in (200, 201) or uuid != self.qr_uuid:
            coordinator.send_message(msg)
            self.qr_uuid = uuid
Пример #4
0
def test_verify_missing_author():
    msg = EFBMsg()
    msg.deliver_to = coordinator.master
    msg.chat = chat
    msg.type = MsgType.Text
    msg.text = "Message"
    with pytest.raises(ValueError):
        msg.verify()
Пример #5
0
def test_verify_text_msg(chat):
    msg = EFBMsg()
    msg.deliver_to = coordinator.master
    msg.author = chat
    msg.chat = chat
    msg.type = MsgType.Text
    msg.text = "Message"
    msg.verify()
Пример #6
0
def test_verify_missing_deliver_to(chat):
    msg = EFBMsg()
    msg.author = chat
    msg.chat = chat
    msg.type = MsgType.Text
    msg.text = "Message"
    with pytest.raises(ValueError):
        msg.verify()
Пример #7
0
def test_verify_missing_author(chat, master_channel):
    msg = EFBMsg()
    msg.deliver_to = master_channel
    msg.chat = chat
    msg.type = MsgType.Text
    msg.text = "Message"
    with pytest.raises(ValueError):
        msg.verify()
Пример #8
0
def test_verify_same_author_and_chat():
    msg = EFBMsg()
    msg.deliver_to = coordinator.master

    msg.author = patch_chat_0
    msg.chat = patch_chat_0
    msg.text = "Message"
    msg.verify()

    patch_chat_0.verify.assert_called_once()
Пример #9
0
 def send_message_recall_status(self):
     slave = next(iter(coordinator.slaves.values()))
     alice = slave.get_chat('alice')
     msg = EFBMsg()
     msg.deliver_to = slave
     msg.chat = alice
     msg.author = EFBChat(self).self()
     msg.uid = "1"
     status = EFBMessageRemoval(self, slave, msg)
     return coordinator.send_status(status)
Пример #10
0
 def send_text_msg(self):
     slave = next(iter(coordinator.slaves.values()))
     wonderland = slave.get_chat('wonderland001')
     msg = EFBMsg()
     msg.deliver_to = slave
     msg.chat = wonderland
     msg.author = EFBChat(self).self()
     msg.type = MsgType.Text
     msg.text = "Hello, world."
     return coordinator.send_message(msg)
Пример #11
0
 def send_message_recall_status(self):
     slave = coordinator.slaves['tests.mocks.slave']
     alice = slave.get_chat('alice')
     msg = EFBMsg()
     msg.deliver_to = slave
     msg.chat = alice
     msg.author = EFBChat(self).self()
     msg.uid = "1"
     status = EFBMessageRemoval(self, slave, msg)
     return coordinator.send_status(status)
Пример #12
0
 def send_text_msg(self):
     slave = coordinator.slaves['tests.mocks.slave']
     wonderland = slave.get_chat('wonderland001')
     msg = EFBMsg()
     msg.deliver_to = slave
     msg.chat = wonderland
     msg.author = EFBChat(self).self()
     msg.type = MsgType.Text
     msg.text = "Hello, world."
     return coordinator.send_message(msg)
Пример #13
0
def base_message(chat, master_channel):
    msg = EFBMsg()
    msg.deliver_to = master_channel

    msg.author = chat
    msg.chat = chat
    msg.text = "Message"
    msg.uid = "0"

    return msg
Пример #14
0
def test_verify_same_author_and_chat(patch_chat_0, master_channel):
    msg = EFBMsg()
    msg.deliver_to = master_channel

    msg.author = patch_chat_0
    msg.chat = patch_chat_0
    msg.text = "Message"
    msg.verify()

    patch_chat_0.verify.assert_called_once()
Пример #15
0
 def send_efb_group_notice(self, context):
     context['message_type'] = 'group'
     self.logger.debug(repr(context))
     msg = EFBMsg()
     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
     msg.uid = "__group_notice__.%s" % int(time.time())
     msg.text = context['message']
     coordinator.send_message(msg)
Пример #16
0
 def send_location_msg(self):
     slave = next(iter(coordinator.slaves.values()))
     alice = slave.get_chat('alice')
     msg = EFBMsg()
     msg.deliver_to = slave
     msg.chat = alice
     msg.author = EFBChat(self).self()
     msg.type = MsgType.Location
     msg.text = "I'm not here."
     msg.attributes = EFBMsgLocationAttribute(latitude=0.1, longitude=1.0)
     return coordinator.send_message(msg)
Пример #17
0
def test_pickle_minimum_text_message():
    msg = EFBMsg()
    msg.deliver_to = coordinator.master
    msg.author = chat
    msg.chat = chat
    msg.type = MsgType.Text
    msg.text = "Message"
    msg.uid = "message_id"
    msg_dup = pickle.loads(pickle.dumps(msg))
    for i in ("deliver_to", "author", "chat", "type", "text", "uid"):
        assert getattr(msg, i) == getattr(msg_dup, i)
Пример #18
0
def test_verify_media_msg(chat, master_channel, media_type):
    with NamedTemporaryFile() as f:
        msg = EFBMsg()
        msg.deliver_to = master_channel
        msg.author = chat
        msg.chat = chat
        msg.type = media_type
        msg.file = f
        msg.filename = "test.bin"
        msg.path = f.name
        msg.mime = "application/octet-stream"
        msg.verify()
Пример #19
0
 def send_link_msg(self):
     slave = next(iter(coordinator.slaves.values()))
     alice = slave.get_chat('alice')
     msg = EFBMsg()
     msg.deliver_to = slave
     msg.chat = alice
     msg.author = EFBChat(self).self()
     msg.type = MsgType.Link
     msg.text = "Check it out."
     msg.attributes = EFBMsgLinkAttribute(title="Example",
                                          url="https://example.com")
     return coordinator.send_message(msg)
Пример #20
0
def test_pickle_message_removal():
    msg = EFBMsg()
    slave = next(iter(coordinator.slaves.values()))
    msg.chat = slave.get_chat("alice")
    msg.uid = "uid"
    msg_removal = EFBMessageRemoval(source_channel=coordinator.master,
                                    destination_channel=slave,
                                    message=msg)
    msg_removal_dup = pickle.loads(pickle.dumps(msg_removal))

    # Assume EFBMsg is picklable
    for i in ("source_channel", "destination_channel"):
        assert getattr(msg_removal, i) == getattr(msg_removal_dup, i)
Пример #21
0
def test_pickle_message_removal(slave_channel, master_channel):
    msg = EFBMsg()
    msg.chat = slave_channel.alice
    msg.uid = "uid"
    msg_removal = EFBMessageRemoval(
        source_channel=master_channel,
        destination_channel=slave_channel,
        message=msg
    )
    msg_removal_dup = pickle.loads(pickle.dumps(msg_removal))

    # Assume EFBMsg is picklable
    for i in ("source_channel", "destination_channel"):
        assert getattr(msg_removal, i) == getattr(msg_removal_dup, i)
Пример #22
0
def test_verify_link_message():
    msg = EFBMsg()
    msg.deliver_to = coordinator.master

    msg.author = patch_chat_0
    msg.chat = patch_chat_1
    msg.text = "Message"
    msg.verify()

    msg.type = MsgType.Link
    msg.attributes = EFBMsgLinkAttribute(title='Title', url='URL')
    msg.attributes.verify = mock.Mock()
    msg.verify()

    msg.attributes.verify.assert_called_once()
Пример #23
0
 def wechat_system_msg(self, msg: wxpy.Message) -> Optional[EFBMsg]:
     if msg.recalled_message_id:
         efb_msg = EFBMsg()
         efb_msg.chat = self.channel.chats.wxpy_chat_to_efb_chat(msg.chat)
         efb_msg.author = self.channel.chats.wxpy_chat_to_efb_chat(msg.sender)
         efb_msg.uid = str(msg.recalled_message_id)
         coordinator.send_status(EFBMessageRemoval(source_channel=self.channel,
                                                   destination_channel=coordinator.master,
                                                   message=efb_msg))
         return None
     efb_msg = EFBMsg()
     efb_msg.text = msg.text
     efb_msg.type = MsgType.Text
     efb_msg.author = EFBChat(self.channel).system()
     return efb_msg
Пример #24
0
def test_verify_status_message():
    msg = EFBMsg()
    msg.deliver_to = coordinator.master

    msg.author = patch_chat_0
    msg.chat = patch_chat_1
    msg.text = "Message"
    msg.verify()

    msg.type = MsgType.Status
    msg.attributes = EFBMsgStatusAttribute(status_type=EFBMsgStatusAttribute.Types.TYPING)
    msg.attributes.verify = mock.Mock()
    msg.verify()

    msg.attributes.verify.assert_called_once()
Пример #25
0
def test_verify_location_message():
    msg = EFBMsg()
    msg.deliver_to = coordinator.master

    msg.author = patch_chat_0
    msg.chat = patch_chat_1
    msg.text = "Message"
    msg.verify()

    msg.type = MsgType.Location
    msg.attributes = EFBMsgLocationAttribute(latitude=0.0, longitude=0.0)
    msg.attributes.verify = mock.Mock()
    msg.verify()

    msg.attributes.verify.assert_called_once()
Пример #26
0
def test_pickle_media_message(media_type):
    with NamedTemporaryFile() as f:
        msg = EFBMsg()
        msg.deliver_to = coordinator.master
        msg.author = chat
        msg.chat = chat
        msg.type = media_type
        msg.file = f
        msg.filename = "test.bin"
        msg.path = f.name
        msg.mime = "application/octet-stream"
        msg.uid = "message_id"
        msg.verify()
        msg_dup = pickle.loads(pickle.dumps(msg))
        for attr in ("deliver_to", "author", "chat", "type", "chat", "filename", "path", "mime", "text", "uid"):
            assert getattr(msg, attr) == getattr(msg_dup, attr)
Пример #27
0
def test_verify_message_command():
    msg = EFBMsg()
    msg.deliver_to = coordinator.master

    msg.author = patch_chat_0
    msg.chat = patch_chat_1
    msg.text = "Message"
    msg.verify()

    msg.type = MsgType.Text
    msg.attributes = None
    msg.commands = EFBMsgCommands(
        [EFBMsgCommand(name="Command 1", callable_name="command_1")])

    msg.commands.commands[0].verify = mock.Mock()

    msg.verify()

    msg.commands.commands[0].verify.assert_called_once()
Пример #28
0
 def handle_group_request(context):
     self.logger.debug(repr(context))
     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 = EFBMsg()
     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 = EFBMsgCommands([EFBMsgCommand(
         name=self._("Accept"),
         callable_name="process_group_request",
         kwargs={'result': 'accept',
                 'flag': context['flag'],
                 'sub_type': context['sub_type']}
     ), EFBMsgCommand(
         name=self._("Decline"),
         callable_name="process_group_request",
         kwargs={'result': 'decline',
                 'flag': context['flag'],
                 'sub_type': context['sub_type']}
     )])
     coordinator.send_message(msg)
Пример #29
0
    def test_chain_verify(self):
        patch_chat_0 = self.chat.copy()
        patch_chat_1 = self.chat.copy()
        patch_chat_0.verify = mock.Mock()
        patch_chat_1.verify = mock.Mock()

        msg = EFBMsg()
        msg.deliver_to = self.channel

        with self.subTest("Different author and chat"):
            msg.author = patch_chat_0
            msg.chat = patch_chat_1
            msg.text = "Message"
            msg.verify()

            patch_chat_0.verify.assert_called_once()
            patch_chat_1.verify.assert_called_once()

        patch_chat_0.verify.reset_mock()

        with self.subTest("Same author and chat"):
            msg.author = patch_chat_0
            msg.chat = patch_chat_0
            msg.text = "Message"
            msg.verify()

            patch_chat_0.verify.assert_called_once()

        with self.subTest("Link message"):
            msg.type = MsgType.Link
            msg.attributes = EFBMsgLinkAttribute(title='Title', url='URL')
            msg.attributes.verify = mock.Mock()
            msg.verify()

            msg.attributes.verify.assert_called_once()

        with self.subTest("Location message"):
            msg.type = MsgType.Location
            msg.attributes = EFBMsgLocationAttribute(latitude=0.0,
                                                     longitude=0.0)
            msg.attributes.verify = mock.Mock()
            msg.verify()

            msg.attributes.verify.assert_called_once()

        with self.subTest("Status message"):
            msg.type = MsgType.Status
            msg.attributes = EFBMsgStatusAttribute(
                status_type=EFBMsgStatusAttribute.Types.TYPING)
            msg.attributes.verify = mock.Mock()
            msg.verify()

            msg.attributes.verify.assert_called_once()

        with self.subTest("Message Command"):
            msg.type = MsgType.Text
            msg.attributes = None
            msg.commands = EFBMsgCommands(
                [EFBMsgCommand(name="Command 1", callable_name="command_1")])

            msg.commands.commands[0].verify = mock.Mock()

            msg.verify()

            msg.commands.commands[0].verify.assert_called_once()
Пример #30
0
    def process_telegram_message(self,
                                 bot: telegram.Bot,
                                 update: telegram.Update,
                                 channel_id: Optional[str] = None,
                                 chat_id: Optional[str] = None,
                                 target_msg: Optional[str] = None):
        """
        Process messages came from Telegram.

        Args:
            bot: Telegram bot
            update: Telegram message update
            channel_id: Slave channel ID if specified
            chat_id: Slave chat ID if specified
            target_msg: Target slave message if specified

        Returns:

        """
        target: Optional[str] = None
        target_channel: Optional[str] = None
        target_log: Optional['MsgLog'] = None
        # Message ID for logging
        message_id = utils.message_id_to_str(update=update)

        multi_slaves: bool = False
        destination: Optional[str] = None
        slave_msg: Optional[EFBMsg] = None

        message: telegram.Message = update.effective_message

        edited = bool(update.edited_message or update.edited_channel_post)
        self.logger.debug('[%s] Message is edited: %s, %s', message_id, edited,
                          message.edit_date)

        private_chat = update.effective_chat.type == telegram.Chat.PRIVATE

        if not private_chat:  # from group
            linked_chats = self.db.get_chat_assoc(
                master_uid=utils.chat_id_to_str(self.channel_id,
                                                update.effective_chat.id))
            if len(linked_chats) == 1:
                destination = linked_chats[0]
            elif len(linked_chats) > 1:
                multi_slaves = True

        reply_to = bool(getattr(message, "reply_to_message", None))

        # Process predefined target (slave) chat.
        if channel_id and chat_id:
            destination = utils.chat_id_to_str(channel_id, chat_id)
            if target_msg:
                target_log = self.db.get_msg_log(master_msg_id=target_msg)
                if target_log:
                    target = target_log.slave_origin_uid
                    target_channel, target_uid = utils.chat_id_str_to_id(
                        target)
                else:
                    self.logger.info(
                        "[%s], Predefined chat %d.%d with target msg")
                    return self.bot.reply_error(
                        update,
                        self._("Message is not found in database. "
                               "Please try with another message. (UC07)"))
        elif private_chat:
            if reply_to:
                dest_msg = self.db.get_msg_log(
                    master_msg_id=utils.message_id_to_str(
                        message.reply_to_message.chat.id,
                        message.reply_to_message.message_id))
                if dest_msg:
                    destination = dest_msg.slave_origin_uid
                else:
                    return self.bot.reply_error(
                        update,
                        self._("Message is not found in database. "
                               "Please try with another one. (UC03)"))
            else:
                return self.bot.reply_error(
                    update,
                    self._("Please reply to an incoming message. (UC04)"))
        else:  # group chat
            if multi_slaves:
                if reply_to:
                    dest_msg = self.db.get_msg_log(
                        master_msg_id=utils.message_id_to_str(
                            message.reply_to_message.chat.id,
                            message.reply_to_message.message_id))
                    if dest_msg:
                        destination = dest_msg.slave_origin_uid
                    else:
                        return self.bot.reply_error(
                            update,
                            self._("Message is not found in database. "
                                   "Please try with another one. (UC05)"))
                else:
                    return self.bot.reply_error(
                        update,
                        self.
                        _("This group is linked to multiple remote chats. "
                          "Please reply to an incoming message. "
                          "To unlink all remote chats, please send /unlink_all . (UC06)"
                          ))
            elif destination:
                if reply_to:
                    target_log = \
                        self.db.get_msg_log(master_msg_id=utils.message_id_to_str(
                            message.reply_to_message.chat.id,
                            message.reply_to_message.message_id))
                    if target_log:
                        target = target_log.slave_origin_uid
                        target_channel, target_uid = utils.chat_id_str_to_id(
                            target)
                    else:
                        return self.bot.reply_error(
                            update,
                            self._("Message is not found in database. "
                                   "Please try with another message. (UC07)"))
            else:
                return self.bot.reply_error(
                    update,
                    self._("This group is not linked to any chat. (UC06)"))

        self.logger.debug(
            "[%s] Telegram received. From private chat: %s; Group has multiple linked chats: %s; "
            "Message replied to another message: %s", message_id, private_chat,
            multi_slaves, reply_to)
        self.logger.debug("[%s] Destination chat = %s", message_id,
                          destination)
        channel, uid = utils.chat_id_str_to_id(destination)
        if channel not in coordinator.slaves:
            return self.bot.reply_error(
                update,
                self._("Internal error: Channel \"{0}\" not found.").format(
                    channel))

        m = EFBMsg()
        try:
            m.uid = message_id
            mtype = get_msg_type(message)
            # Chat and author related stuff
            m.author = EFBChat(self.channel).self()
            m.chat = EFBChat(coordinator.slaves[channel])
            m.chat.chat_uid = uid
            chat_info = self.db.get_slave_chat_info(channel, uid)
            if chat_info:
                m.chat.chat_name = chat_info.slave_chat_name
                m.chat.chat_alias = chat_info.slave_chat_alias
                m.chat.chat_type = ChatType(chat_info.slave_chat_type)
            m.deliver_to = coordinator.slaves[channel]
            if target and target_channel == channel:
                trgt_msg = EFBMsg()
                trgt_msg.type = MsgType.Text
                trgt_msg.text = target_log.text
                trgt_msg.uid = target_log.slave_message_id
                trgt_msg.chat = EFBChat(coordinator.slaves[target_channel])
                trgt_msg.chat.chat_name = target_log.slave_origin_display_name
                trgt_msg.chat.chat_alias = target_log.slave_origin_display_name
                trgt_msg.chat.chat_uid = utils.chat_id_str_to_id(
                    target_log.slave_origin_uid)[1]
                if target_log.slave_member_uid:
                    trgt_msg.author = EFBChat(
                        coordinator.slaves[target_channel])
                    trgt_msg.author.chat_name = target_log.slave_member_display_name
                    trgt_msg.author.chat_alias = target_log.slave_member_display_name
                    trgt_msg.author.chat_uid = target_log.slave_member_uid
                elif target_log.sent_to == 'master':
                    trgt_msg.author = trgt_msg.chat
                else:
                    trgt_msg.author = EFBChat(self.channel).self()
                m.target = trgt_msg

                self.logger.debug(
                    "[%s] This message replies to another message of the same channel.\n"
                    "Chat ID: %s; Message ID: %s.", message_id,
                    trgt_msg.chat.chat_uid, trgt_msg.uid)
            # Type specific stuff
            self.logger.debug("[%s] Message type from Telegram: %s",
                              message_id, mtype)

            if self.TYPE_DICT.get(mtype, None):
                m.type = self.TYPE_DICT[mtype]
                self.logger.debug("[%s] EFB message type: %s", message_id,
                                  mtype)
            else:
                self.logger.info(
                    "[%s] Message type %s is not supported by ETM", message_id,
                    mtype)
                raise EFBMessageTypeNotSupported(
                    "Message type %s is not supported by ETM" % mtype)

            if m.type not in coordinator.slaves[
                    channel].supported_message_types:
                self.logger.info(
                    "[%s] Message type %s is not supported by channel %s",
                    message_id, m.type.name, channel)
                raise EFBMessageTypeNotSupported(
                    "Message type %s is not supported by channel %s" %
                    (m.type, coordinator.slaves[channel].channel_name))

            # Parse message text and caption to markdown
            msg_md_text = message.text and message.text_markdown
            if msg_md_text and msg_md_text == escape_markdown(message.text):
                msg_md_text = message.text
            msg_md_text = msg_md_text or ""

            msg_md_caption = message.caption and message.caption_markdown
            if msg_md_caption and msg_md_caption == escape_markdown(
                    message.caption):
                msg_md_caption = message.caption
            msg_md_caption = msg_md_caption or ""

            # Flag for edited message
            if edited:
                m.edit = True
                text = msg_md_text or msg_md_caption
                msg_log = self.db.get_msg_log(
                    master_msg_id=utils.message_id_to_str(update=update))
                if not msg_log or msg_log == self.FAIL_FLAG:
                    raise EFBMessageNotFound()
                m.uid = msg_log.slave_message_id
                if text.startswith(self.DELETE_FLAG):
                    coordinator.send_status(
                        EFBMessageRemoval(
                            source_channel=self.channel,
                            destination_channel=coordinator.slaves[channel],
                            message=m))
                    self.db.delete_msg_log(
                        master_msg_id=utils.message_id_to_str(update=update))
                    m = None
                    return
                self.logger.debug('[%s] Message is edited (%s)', m.uid, m.edit)

            # Enclose message as an EFBMsg object by message type.
            if mtype == TGMsgType.Text:
                m.text = msg_md_text
            elif mtype == TGMsgType.Photo:
                m.text = msg_md_caption
                m.file, m.mime, m.filename, m.path = self._download_file(
                    message.photo[-1])
            elif mtype == TGMsgType.Sticker:
                # Convert WebP to the more common PNG
                m.text = ""
                m.file, m.mime, m.filename, m.path = self._download_file(
                    message.sticker, 'image/webp')
                self.logger.debug(
                    "[%s] Trying to convert WebP sticker (%s) to PNG.",
                    message_id, m.path)
                f = tempfile.NamedTemporaryFile(suffix=".png")
                Image.open(m.file).convert("RGBA").save(f, 'png')
                m.file.close()
                m.file, m.mime, m.filename, m.path = f, 'image/png', os.path.basename(
                    f.name), f.name
                self.logger.debug(
                    "[%s] WebP sticker is converted to PNG (%s).", message_id,
                    f.name)
            elif mtype == TGMsgType.Animation:
                m.text = ""
                self.logger.debug(
                    "[%s] Telegram message is a \"Telegram GIF\".", message_id)
                m.filename = getattr(message.document, "file_name",
                                     None) or None
                m.file, m.mime, m.filename, m.path = self._download_gif(
                    message.document, channel)
                m.mime = message.document.mime_type or m.mime
            elif mtype == TGMsgType.Document:
                m.text = msg_md_caption
                self.logger.debug("[%s] Telegram message type is document.",
                                  message_id)
                m.filename = getattr(message.document, "file_name",
                                     None) or None
                m.file, m.mime, filename, m.path = self._download_file(
                    message.document, message.document.mime_type)
                m.filename = m.filename or filename
                m.mime = message.document.mime_type or m.mime
            elif mtype == TGMsgType.Video:
                m.text = msg_md_caption
                m.file, m.mime, m.filename, m.path = self._download_file(
                    message.video, message.video.mime_type)
            elif mtype == TGMsgType.Audio:
                m.text = "%s - %s\n%s" % (message.audio.title,
                                          message.audio.performer,
                                          msg_md_caption)
                m.file, m.mime, m.filename, m.path = self._download_file(
                    message.audio, message.audio.mime_type)
            elif mtype == TGMsgType.Voice:
                m.text = msg_md_caption
                m.file, m.mime, m.filename, m.path = self._download_file(
                    message.voice, message.voice.mime_type)
            elif mtype == TGMsgType.Location:
                # TRANSLATORS: Message body text for location messages.
                m.text = self._("Location")
                m.attributes = EFBMsgLocationAttribute(
                    message.location.latitude, message.location.longitude)
            elif mtype == TGMsgType.Venue:
                m.text = message.location.title + "\n" + message.location.adderss
                m.attributes = EFBMsgLocationAttribute(
                    message.venue.location.latitude,
                    message.venue.location.longitude)
            elif mtype == TGMsgType.Contact:
                contact: telegram.Contact = message.contact
                m.text = self._(
                    "Shared a contact: {first_name} {last_name}\n{phone_number}"
                ).format(first_name=contact.first_name,
                         last_name=contact.last_name,
                         phone_number=contact.phone_number)
            else:
                raise EFBMessageTypeNotSupported(
                    self._("Message type {0} is not supported.").format(mtype))
                # return self.bot.reply_error(update, "Message type not supported. (MN02)")

            slave_msg = coordinator.send_message(m)
        except EFBChatNotFound as e:
            self.bot.reply_error(update, e.args[0]
                                 or self._("Chat is not found."))
        except EFBMessageTypeNotSupported as e:
            self.bot.reply_error(
                update, e.args[0] or self._("Message type is not supported."))
        except EFBOperationNotSupported as e:
            self.bot.reply_error(
                update,
                self._("Message editing is not supported.\n\n{!s}".format(e)))
        except Exception as e:
            self.bot.reply_error(
                update, self._("Message is not sent.\n\n{!r}".format(e)))
        finally:
            if m:
                msg_log_d = {
                    "master_msg_id":
                    utils.message_id_to_str(update=update),
                    "text":
                    m.text or "Sent a %s" % m.type,
                    "slave_origin_uid":
                    utils.chat_id_to_str(chat=m.chat),
                    "slave_origin_display_name":
                    "__chat__",
                    "msg_type":
                    m.type,
                    "sent_to":
                    "slave",
                    "slave_message_id":
                    None if m.edit else "%s.%s" %
                    (self.FAIL_FLAG, int(time.time())),
                    # Overwritten later if slave message ID exists
                    "update":
                    m.edit
                }

                # Store media related information to local database
                for tg_media_type in ('audio', 'animation', 'document',
                                      'video', 'voice', 'video_note'):
                    attachment = getattr(message, tg_media_type, None)
                    if attachment:
                        msg_log_d.update(media_type=tg_media_type,
                                         file_id=attachment.file_id,
                                         mime=attachment.mime_type)
                        break
                if not msg_log_d.get('media_type', None):
                    if getattr(message, 'sticker', None):
                        msg_log_d.update(media_type='sticker',
                                         file_id=message.sticker.file_id,
                                         mime='image/webp')
                    elif getattr(message, 'photo', None):
                        attachment = message.photo[-1]
                        msg_log_d.update(media_type=tg_media_type,
                                         file_id=attachment.file_id,
                                         mime='image/jpeg')

                if slave_msg:
                    msg_log_d['slave_message_id'] = slave_msg.uid
                self.db.add_msg_log(**msg_log_d)
                if m.file:
                    m.file.close()