def build_link_chats_info_str(self,
                                  links: List[EFBChannelChatIDStr]) -> str:
        """Build a string indicating all linked chats in argument.

        Returns:
            String that starts with a line break.
        """
        msg = ""
        for i in links:
            channel_id, chat_id, _ = etm_utils.chat_id_str_to_id(i)
            chat_object = self.chat_manager.get_chat(channel_id, chat_id)
            if chat_object:
                msg += "\n- %s (%s:%s)" % (chat_object.full_name, channel_id,
                                           chat_id)
            else:
                try:
                    module = coordinator.get_module_by_id(channel_id)
                    if isinstance(module, Channel):
                        channel_name = f"{module.channel_emoji} {module.channel_name}"
                    else:  # module is Middleware
                        channel_name = module.middleware_name
                    msg += self._(
                        "\n- {channel_name}: Unknown chat ({channel_id}:{chat_id})"
                    ).format(channel_name=channel_name,
                             channel_id=channel_id,
                             chat_id=chat_id)
                except NameError:
                    # TRANSLATORS: ‘channel’ here means an EFB channel.
                    msg += self._(
                        "\n- Unknown channel {channel_id}: ({chat_id})"
                    ).format(channel_id=channel_id, chat_id=chat_id)
        return msg
    def __setstate__(self, state: Dict[str, Any]):
        # Try to load channel object
        try:
            state['channel'] = coordinator.get_module_by_id(state['channel'])
        except NameError:
            del state['channel']

        self.__dict__.update(state)
Esempio n. 3
0
    def build_etm_msg(self, chat_manager: ChatObjectCacheManager,
                      recur: bool = True) -> ETMMsg:
        c_module, c_id, _ = chat_id_str_to_id(self.slave_origin_uid)
        a_module, a_id, a_grp = chat_id_str_to_id(self.slave_member_uid)
        chat: 'ETMChatType' = chat_manager.get_chat(c_module, c_id, build_dummy=True)
        author: 'ETMChatMember' = chat_manager.get_chat_member(a_module, a_grp, a_id, build_dummy=True)  # type: ignore
        msg = ETMMsg(
            uid=self.slave_message_id,
            chat=chat,
            author=author,
            text=self.text,
            type=MsgType(self.msg_type),
            type_telegram=TGMsgType(self.media_type),
            mime=self.mime or None,
            file_id=self.file_id or None,
        )
        with suppress(NameError):
            to_module = coordinator.get_module_by_id(self.sent_to)
            if isinstance(to_module, Channel):
                msg.deliver_to = to_module

        # - ``target``: ``master_msg_id`` of the target message
        # - ``is_system``
        # - ``attributes``
        # - ``commands``
        # - ``substitutions``: ``Dict[Tuple[int, int], SlaveChatID]``
        # - ``reactions``: ``Dict[str, Collection[SlaveChatID]]``
        if self.pickle:
            misc_data: PickledDict = pickle.loads(self.pickle)

            if 'target' in misc_data and recur:
                target_row = self.get_or_none(MsgLog.master_msg_id == misc_data['target'])
                if target_row:
                    msg.target = target_row.build_etm_msg(chat_manager, recur=False)
            if 'is_system' in misc_data:
                msg.is_system = misc_data['is_system']
            if 'attributes' in misc_data:
                msg.attributes = misc_data['attributes']
            if 'commands' in misc_data:
                msg.commands = misc_data['commands']
            if 'substitutions' in misc_data:
                subs = Substitutions({})
                for sk, sv in misc_data['substitutions'].items():
                    module_id, chat_id, group_id = chat_id_str_to_id(sv)
                    if group_id:
                        subs[sk] = chat_manager.get_chat_member(module_id, group_id, chat_id, build_dummy=True)
                    else:
                        subs[sk] = chat_manager.get_chat(module_id, chat_id, build_dummy=True)
                msg.substitutions = subs
            if 'reactions' in misc_data:
                reactions: Dict[ReactionName, List[ETMChatMember]] = {}
                for rk, rv in misc_data['reactions'].items():
                    reactions[rk] = []
                    for idx in rv:
                        module_id, chat_id, group_id = chat_id_str_to_id(idx)
                        reactions[rk].append(chat_manager.get_chat_member(module_id, group_id, chat_id, build_dummy=True))  # type: ignore
                msg.reactions = reactions
        return msg
    def link_chat(self, update, args):
        """Actual code of linking a chat by manipulating database.
        Triggered by ``/start BASE64(msg_id_to_str(chat_id, msg_id))``.
        """
        try:
            msg_id = utils.message_id_str_to_id(utils.b64de(args[0]))
            storage_key: Tuple[int, int] = (int(msg_id[0]), int(msg_id[1]))
            data = self.msg_storage[storage_key]
        except KeyError:
            return update.message.reply_text(
                self._("Session expired or unknown parameter. (SE02)"))
        chat: ETMChatType = data.chats[0]
        chat_display_name = chat.full_name
        slave_channel, slave_chat_uid = chat.module_id, chat.uid
        try:
            coordinator.get_module_by_id(slave_channel)
        except NameError:
            self.bot.edit_message_text(text=self._(
                "{module_id} is not activated in current profile. "
                "It cannot be linked.").format(module_id=slave_channel),
                                       chat_id=storage_key[0],
                                       message_id=storage_key[1])

        # Use channel ID if command is forwarded from a channel.
        forwarded_chat: Chat = update.effective_message.forward_from_chat
        if forwarded_chat and forwarded_chat.type == telegram.Chat.CHANNEL:
            tg_chat_to_link = forwarded_chat.id
        else:
            tg_chat_to_link = update.effective_chat.id

        txt = self._('Trying to link chat {0}...').format(chat_display_name)
        msg = self.bot.send_message(tg_chat_to_link, text=txt)

        chat.link(self.channel.channel_id, tg_chat_to_link,
                  self.channel.flag("multiple_slave_chats"))

        txt = self._("Chat {0} is now linked.").format(chat_display_name)
        self.bot.edit_message_text(text=txt,
                                   chat_id=msg.chat.id,
                                   message_id=msg.message_id)

        self.bot.edit_message_text(chat_id=storage_key[0],
                                   message_id=storage_key[1],
                                   text=txt)
        self.msg_storage.pop(storage_key, None)
    def chat_head_req_generate(self,
                               chat_id: TelegramChatID,
                               message_id: TelegramMessageID = None,
                               offset: int = 0,
                               pattern: str = "",
                               chats: List[EFBChannelChatIDStr] = None):
        """
        Generate the list for chat head, and update it to a message.

        Args:
            chat_id: Chat ID
            message_id: ID of message to be updated, None to send a new message.
            offset: Offset for pagination.
            pattern: Regex String used as a filter.
            chats: Specified list of chats to start a chat head.
        """
        if message_id is None:
            message_id = self.bot.send_message(
                chat_id, text=self._("Processing...")).message_id
        self.bot.send_chat_action(chat_id, ChatAction.TYPING)

        if chats and len(chats):
            if len(chats) == 1:
                slave_channel_id, slave_chat_id, _ = utils.chat_id_str_to_id(
                    chats[0])
                # TODO: Channel might be gone, add a check here.
                chat = self.chat_manager.get_chat(slave_channel_id,
                                                  slave_chat_id)
                if chat:
                    msg_text = self._(
                        'This group is linked to {0}. '
                        'Send a message to this group to deliver it to the chat.\n'
                        'Do NOT reply to this system message.').format(
                            chat.full_name)

                else:
                    try:
                        channel = coordinator.get_module_by_id(
                            slave_channel_id)
                        if isinstance(channel, Channel):
                            name = channel.channel_name
                        else:
                            name = channel.middleware_name
                        msg_text = self._(
                            "This group is linked to an unknown chat ({chat_id}) "
                            "on channel {channel_name} ({channel_id}). Possibly you can "
                            "no longer reach this chat. Send /unlink_all to unlink all chats "
                            "from this group.").format(
                                channel_name=name,
                                channel_id=slave_channel_id,
                                chat_id=slave_chat_id)
                    except NameError:
                        msg_text = self._(
                            "This group is linked to a chat from a channel that is not activated "
                            "({channel_id}, {chat_id}). You cannot reach this chat unless the channel is "
                            "enabled. Send /unlink_all to unlink all chats "
                            "from this group.").format(
                                channel_id=slave_channel_id,
                                chat_id=slave_chat_id)
                self.bot.edit_message_text(text=msg_text,
                                           chat_id=chat_id,
                                           message_id=message_id)
                return ConversationHandler.END
            else:
                msg_text = self._(
                    "This Telegram group is linked to the following chats, "
                    "choose one to start a conversation with.")
        else:
            msg_text = "Choose a chat you want to start a conversation with."

        legend, chat_btn_list = self.slave_chats_pagination(
            (chat_id, message_id), offset, pattern=pattern, source_chats=chats)

        msg_text += self._("\n\nLegend:\n")
        for i in legend:
            msg_text += f"{i}\n"
        self.bot.edit_message_text(
            text=msg_text,
            chat_id=chat_id,
            message_id=message_id,
            reply_markup=InlineKeyboardMarkup(chat_btn_list))

        self.chat_head_handler.conversations[(
            chat_id, message_id)] = Flags.CHAT_HEAD_CONFIRM
    def slave_chats_pagination(self, storage_id: Tuple[TelegramChatID, TelegramMessageID],
                               offset: int = 0,
                               pattern: Optional[str] = "",
                               source_chats: Optional[List[EFBChannelChatIDStr]] = None,
                               filter_availability: bool = True) \
            -> Tuple[List[str], List[List[InlineKeyboardButton]]]:
        """
        Generate a list of (list of) `InlineKeyboardButton`s of chats in slave channels,
        based on the status of message located by `storage_id` and the paging from
        `offset` value.

        Args:
            pattern: Regular expression filter for chat details
            storage_id (Tuple[int, int]): Message_storage ID for generating the buttons list.
            offset (int): Offset for pagination
            source_chats (Optional[List[str]]): A list of chats used to generate the pagination list.
                Each str is in the format of "{channel_id}.{chat_uid}".
            filter_availability (bool): Whether to filter chats based on the availabilities.
                Only works when ``source_chats`` is specified.

        Returns:
            Tuple[List[str], List[List[telegram.InlineKeyboardButton]]]:
                A tuple: legend, chat_btn_list
                `legend` is the legend of all Emoji headings in the entire list.
                `chat_btn_list` is a list which can be fit into `telegram.InlineKeyboardMarkup`.
        """
        self.logger.debug(
            "Generating pagination of chats.\nStorage ID: %s; Offset: %s; Filter: %s; Source chats: %s;",
            storage_id, offset, pattern, source_chats)
        legend: List[str] = [
            self._("{0}: Linked").format(Emoji.LINK),
            self._("{0}: User").format(Emoji.USER),
            self._("{0}: Group").format(Emoji.GROUP),
        ]

        chat_list: Optional[ChatListStorage] = self.msg_storage.get(
            storage_id, None)

        if chat_list is None or chat_list.length == 0:
            # Generate the full chat list first
            re_filter: Union[str, Pattern, None] = None
            if pattern:
                self.logger.debug("Filter pattern: %s", pattern)
                escaped_pattern = re.escape(pattern)
                # Use simple string match if no regex significance is found.
                if pattern == escaped_pattern:
                    re_filter = pattern
                else:
                    # Use simple string match if regex provided is invalid
                    try:
                        re_filter = re.compile(pattern,
                                               re.DOTALL | re.IGNORECASE)
                    except re.error:
                        re_filter = pattern
            chats: List[ETMChatType] = []
            if source_chats:
                for s_chat in source_chats:
                    channel_id, chat_uid, _ = utils.chat_id_str_to_id(s_chat)
                    with suppress(NameError):
                        coordinator.get_module_by_id(channel_id)
                    chat = self.chat_manager.get_chat(
                        channel_id,
                        chat_uid,
                        build_dummy=not filter_availability)
                    if not chat:
                        self.logger.debug(
                            "slave_chats_pagination with chat list: Chat %s not found.",
                            s_chat)
                        continue
                    if chat.match(re_filter):
                        chats.append(chat)
            else:
                for etm_chat in self.chat_manager.all_chats:
                    if etm_chat.match(re_filter):
                        chats.append(etm_chat)

            chats.sort(key=lambda a: a.last_message_time, reverse=True)
            chat_list = self.msg_storage[storage_id] = ChatListStorage(
                chats, offset)

        # self._db_update_slave_chats_cache(chat_list.chats)

        for ch in chat_list.channels.values():
            legend.append(f"{ch.channel_emoji}: {ch.channel_name}")

        # Build inline button list
        chat_btn_list: List[List[InlineKeyboardButton]] = []
        chats_per_page = self.channel.flag("chats_per_page")
        for idx in range(offset, min(offset + chats_per_page,
                                     chat_list.length)):
            chat = chat_list.chats[idx]
            if chat.linked:
                mode = Emoji.LINK
            else:
                mode = ""
            chat_type = chat.chat_type_emoji
            chat_name = chat.long_name
            button_text = f"{chat.channel_emoji}{chat_type}{mode}: {chat_name}"
            button_callback = f"chat {idx}"
            chat_btn_list.append([
                InlineKeyboardButton(button_text,
                                     callback_data=button_callback)
            ])

        # Pagination
        page_number_row: List[InlineKeyboardButton] = []

        if offset - chats_per_page >= 0:
            page_number_row.append(
                InlineKeyboardButton(
                    self._("< Prev"),
                    callback_data=f"offset {offset - chats_per_page}"))
        page_number_row.append(
            InlineKeyboardButton(self._("Cancel"),
                                 callback_data=Flags.CANCEL_PROCESS))
        if offset + chats_per_page < chat_list.length:
            page_number_row.append(
                InlineKeyboardButton(
                    self._("Next >"),
                    callback_data=f"offset {offset + chats_per_page}"))
        chat_btn_list.append(page_number_row)

        return legend, chat_btn_list
Esempio n. 7
0
    def dispatch_message(self, msg: EFBMsg, msg_template: str,
                         old_msg_id: Optional[OldMsgID], tg_dest: TelegramChatID,
                         silent: bool = False):
        """Dispatch with header, destination and Telegram message ID and destinations."""

        xid = msg.uid

        # When targeting a message (reply to)
        target_msg_id: Optional[TelegramMessageID] = 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 = utils.message_id_str_to_id(log.master_msg_id)
                if not target_msg or target_msg[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)
                    target_msg_id = None
                else:
                    target_msg_id = target_msg[1]

        # Generate basic reply markup
        commands: Optional[List[EFBMsgCommand]] = None
        reply_markup: Optional[telegram.InlineKeyboardMarkup] = None

        if msg.commands:
            commands = msg.commands.commands
            buttons = []
            for i, ival in enumerate(commands):
                buttons.append([telegram.InlineKeyboardButton(ival.name, callback_data=str(i))])
            reply_markup = telegram.InlineKeyboardMarkup(buttons)

        reactions = self.build_reactions_footer(msg.reactions)

        msg.text = msg.text or ""

        # Type dispatching
        if msg.type == MsgType.Text:
            tg_msg = self.slave_message_text(msg, tg_dest, msg_template, reactions, old_msg_id, target_msg_id,
                                             reply_markup, silent)
        elif msg.type == MsgType.Link:
            tg_msg = self.slave_message_link(msg, tg_dest, msg_template, reactions, old_msg_id, target_msg_id,
                                             reply_markup, silent)
        elif msg.type == MsgType.Sticker:
            tg_msg = self.slave_message_sticker(msg, tg_dest, msg_template, reactions, old_msg_id, target_msg_id,
                                                reply_markup, silent)
        elif msg.type == MsgType.Image:
            if self.flag("send_image_as_file"):
                tg_msg = self.slave_message_file(msg, tg_dest, msg_template, reactions, old_msg_id, target_msg_id,
                                                 reply_markup, silent)
            else:
                tg_msg = self.slave_message_image(msg, tg_dest, msg_template, reactions, old_msg_id, target_msg_id,
                                                  reply_markup, silent)
        elif msg.type == MsgType.Animation:
            tg_msg = self.slave_message_animation(msg, tg_dest, msg_template, reactions, old_msg_id, target_msg_id,
                                                  reply_markup, silent)
        elif msg.type == MsgType.File:
            tg_msg = self.slave_message_file(msg, tg_dest, msg_template, reactions, old_msg_id, target_msg_id,
                                             reply_markup, silent)
        elif msg.type == MsgType.Audio:
            tg_msg = self.slave_message_audio(msg, tg_dest, msg_template, reactions, old_msg_id, target_msg_id,
                                              reply_markup, silent)
        elif msg.type == MsgType.Location:
            tg_msg = self.slave_message_location(msg, tg_dest, msg_template, reactions, old_msg_id, target_msg_id,
                                                 reply_markup, silent)
        elif msg.type == MsgType.Video:
            tg_msg = self.slave_message_video(msg, tg_dest, msg_template, reactions, old_msg_id, target_msg_id,
                                              reply_markup, silent)
        elif msg.type == MsgType.Unsupported:
            tg_msg = self.slave_message_unsupported(msg, tg_dest, msg_template, reactions, old_msg_id,
                                                    target_msg_id, reply_markup, silent)
        else:
            self.bot.send_chat_action(tg_dest, telegram.ChatAction.TYPING)
            tg_msg = self.bot.send_message(tg_dest, prefix=msg_template, suffix=reactions,
                                           disable_notification=silent,
                                           text=self._('Unknown type of message "{0}". (UT01)')
                                           .format(msg.type.name))

        if tg_msg and commands:
            self.channel.commands.register_command(tg_msg, ETMCommandMsgStorage(
                commands, coordinator.get_module_by_id(msg.author.module_id), msg_template, msg.text
            ))

        self.logger.debug("[%s] Message is sent to the user with telegram message id %s.%s.",
                          xid, tg_msg.chat.id, tg_msg.message_id)

        etm_msg = ETMMsg.from_efbmsg(msg, self.db)
        etm_msg.put_telegram_file(tg_msg)
        pickled_msg = etm_msg.pickle(self.db)
        self.logger.debug("[%s] Pickle size: %s", xid, len(pickled_msg))
        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.name,
                   "msg_type": msg.type.name,
                   "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,
                   "media_type": etm_msg.type_telegram.value,
                   "file_id": etm_msg.file_id,
                   "mime": etm_msg.mime,
                   "pickle": pickled_msg
                   }

        if old_msg_id and old_msg_id != tg_msg.message_id:
            msg_log['master_msg_id'] = utils.message_id_to_str(*old_msg_id)
            msg_log['master_msg_id_alt'] = utils.message_id_to_str(tg_msg.chat.id, tg_msg.message_id)

        # self.db.add_msg_log(**msg_log)
        self.db.add_task(self.db.add_msg_log, tuple(), msg_log)
Esempio n. 8
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:

                    if old_msg.master_msg_id_alt:
                        old_msg_id = utils.message_id_str_to_id(
                            old_msg.master_msg_id_alt)
                    else:
                        old_msg_id = 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: 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)
                        target_msg_id = None
                    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
                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 == 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.Image:
                if self.flag("send_image_as_file"):
                    tg_msg = self.slave_message_file(msg, tg_dest,
                                                     msg_template, old_msg_id,
                                                     target_msg_id,
                                                     reply_markup)
                else:
                    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.get_module_by_id(msg.author.module_id),
                        msg_template, msg.text))

            self.logger.debug(
                "[%s] Message is sent to the user with telegram message id %s.%s.",
                xid, tg_msg.chat.id, tg_msg.message_id)

            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
            }

            if old_msg_id and old_msg_id != tg_msg.message_id:
                msg_log['master_msg_id'] = utils.message_id_to_str(*old_msg_id)
                msg_log['master_msg_id_alt'] = utils.message_id_to_str(
                    tg_msg.chat.id, tg_msg.message_id)

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

            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())