def attach_media(self, msg: Message, attachment: Dict[str, Any]):
        """
        Attach an attachment to a message.

        Args:
            msg: Message to be attached to
            attachment:
                Dict of information of the attachment
                ``fbchat`` entity is not used as it is not completed.
        """
        self.logger.debug("[%s] Trying to attach media: %s", msg.uid, attachment)

        blob_attachment: Dict[str, Any] = attachment.get('mercury', {}).get('blob_attachment', {})
        attachment_type: str = blob_attachment.get("__typename", None)
        if 'sticker_attachment' in attachment.get('mercury', {}):
            attachment_type = '__Sticker'
        if 'extensible_attachment' in attachment.get('mercury', {}):
            attachment_type = '__Link'
            extensible_type = get_value(attachment, ('mercury', 'extensible_attachment',
                                                     'story_attachment', 'target', '__typename'))
            if extensible_type == 'MessageLocation':
                attachment_type = 'MessageLocation'
            elif extensible_type == 'MessageLiveLocation':
                attachment_type = 'MessageLocation'  # TODO: Change if live location is supported by framework.

        self.logger.debug("[%s] The attachment has type %s", msg.uid, attachment_type)

        msg.filename = attachment.get('filename', None)
        msg.mime = attachment.get('mimeType', None)
        if attachment_type == "MessageAudio":
            msg.type = MsgType.Voice
            msg.filename = msg.filename or 'audio.mp3'
            msg.mime = msg.mime or 'audio/mpeg'
            ext = os.path.splitext(msg.filename)[1]
            msg.file = NamedTemporaryFile(suffix=ext)
            msg.file.write(requests.get(blob_attachment['playable_url']).content)
            msg.file.seek(0)
            msg.path = Path(msg.file.name)
        elif attachment_type == 'MessageImage':
            msg.type = MsgType.Image
            msg.filename = msg.filename or 'image.png'
            msg.mime = msg.mime or 'image/png'
            attribution_app = get_value(blob_attachment, ('attribution_app', 'name'))
            if attribution_app:
                if msg.text:
                    msg.text += " (via %s)" % attribution_app
                else:
                    msg.text = "via %s" % attribution_app
            ext = os.path.splitext(msg.filename)[1]
            msg.file = NamedTemporaryFile(suffix=ext)
            url = self.fetchImageUrl(attachment['id'])
            msg.file.write(requests.get(url).content)
            msg.file.seek(0)
            msg.path = Path(msg.file.name)
        elif attachment_type == 'MessageAnimatedImage':
            msg.type = MsgType.Animation
            msg.filename = msg.filename or 'image.gif'
            msg.mime = msg.mime or 'image/gif'
            ext = os.path.splitext(msg.filename)[1]
            msg.file = NamedTemporaryFile(suffix=ext)
            msg.file.write(requests.get(blob_attachment['animated_image']['uri'], allow_redirects=True).content)
            msg.file.seek(0)
            msg.path = Path(msg.file.name)
        elif attachment_type == 'MessageFile':
            msg.type = MsgType.File
            msg.filename = msg.filename or 'file'
            msg.mime = msg.mime or 'application/octet-stream'
            ext = os.path.splitext(msg.filename)[1]
            msg.file = NamedTemporaryFile(suffix=ext)
            msg.file.write(requests.get(self.process_url(blob_attachment['url'], True), allow_redirects=True).content)
            msg.file.seek(0)
            msg.path = Path(msg.file.name)
        elif attachment_type == 'MessageVideo':
            msg.type = MsgType.Image
            msg.filename = msg.filename or 'video.mp4'
            msg.mime = msg.mime or 'video/mpeg'
            ext = os.path.splitext(msg.filename)[1]
            msg.file = NamedTemporaryFile(suffix=ext)
            msg.file.write(requests.get(blob_attachment['playable_url'], allow_redirects=True).content)
            msg.file.seek(0)
            msg.path = Path(msg.file.name)
        elif attachment_type == '__Sticker':
            if get_value(attachment, ('mercury', 'sticker_attachment', 'pack', 'id')) == "227877430692340":
                self.logger.debug("[%s] Sticker received is a \"Like\" sticker. Converting message to text.", msg.uid)

                sticker_id = get_value(attachment, ('mercury', 'sticker_attachment', 'id'))
                for i in EmojiSize:
                    if sticker_id == i.value:
                        msg.type = MsgType.Text
                        msg.text = "👍 (%s)" % i.name[0]
                        return
            msg.type = MsgType.Sticker
            url = attachment['mercury']['sticker_attachment']['url']
            msg.text = attachment['mercury']['sticker_attachment'].get('label', '')
            filename = os.path.split(urllib.parse.urlparse(url).path)[1]
            msg.filename = filename
            ext = os.path.splitext(filename)[1]
            response = requests.get(url)
            msg.mime = response.headers['content-type']
            msg.file = NamedTemporaryFile(suffix=ext)
            msg.file.write(response.content)
            msg.file.seek(0)
            msg.path = Path(msg.file.name)
        elif attachment_type == '__Link':
            msg.type = MsgType.Link
            link_information = get_value(attachment, ('mercury', 'extensible_attachment', 'story_attachment'), {})
            title = get_value(link_information, ('title_with_entities', 'text'), '')
            description = get_value(link_information, ('description', 'text'), '')
            source = get_value(link_information, ('source', 'text'), None)
            if source:
                description += " (via %s)" % source
            preview = get_value(link_information, ('media', 'playable_url'), None) if \
                get_value(link_information, ('media', 'is_playable'), False) else None
            preview = preview or get_value(link_information, ('media', 'image', 'uri'), None)
            url = link_information.get('url', preview)
            msg.attributes = LinkAttribute(title=title,
                                           description=description,
                                           image=self.process_url(preview),
                                           url=self.process_url(url))
        elif attachment_type == 'MessageLocation':
            msg.type = MsgType.Location
            link_information = get_value(attachment, ('mercury', 'extensible_attachment', 'story_attachment'), {})
            title = get_value(link_information, ('title_with_entities', 'text'), '')
            description = get_value(link_information, ('description', 'text'), '')
            msg.text = '\n'.join([title, description])
            preview = get_value(link_information, ('media', 'image', 'uri'), None)
            matches = re.search(r'markers=([\d.-]+)%2C([\d.-]+)', preview)
            if matches:
                latitude, longitude = map(float, matches.groups())
            else:
                msg.type = MsgType.Unsupported
                msg.text = self._("Message type unsupported.\n{content}").format(msg.text)
                return
            msg.attributes = LocationAttribute(
                latitude=latitude, longitude=longitude
            )
        else:
            msg.type = MsgType.Unsupported
            msg.text = self._("Message type unsupported.\n{content}").format(msg.text)
    def send_message(self, msg: EFBMessage) -> Message:
        self.logger.debug("Received message from master: %s", msg)

        try:
            target_msg_offset = 0
            prefix = ""

            mentions = []

            # Send message reaction
            # if msg.target and msg.text.startswith('r`') and \
            #         msg.target.uid.startswith("mid.$"):
            #     self.logger.debug("[%s] Message is a reaction to another message: %s", msg.uid, msg.text)
            #     msg_id = ".".join(msg.target.uid.split(".", 2)[:2])
            #     if getattr(MessageReaction, msg.text[2:], None):
            #         self.client.reactToMessage(msg_id, getattr(MessageReaction, msg.text[2:]))
            #     else:
            #         self.client.reactToMessage(msg_id, self.CustomReaction(msg.text[2:]))
            #     msg.uid = "__reaction__"
            #     return msg

            # Message substitutions
            if msg.substitutions:
                self.logger.debug("[%s] Message has substitutions: %s", msg.uid, msg.substitutions)
                for i in msg.substitutions:
                    mentions.append(Mention(msg.substitutions[i].id,
                                            target_msg_offset + i[0], i[1] - i[0]))
                self.logger.debug("[%s] Translated to mentions: %s", msg.uid, mentions)

            fb_msg = Message(text=prefix + msg.text, mentions=mentions)
            thread: Thread = self.client.fetchThreadInfo(msg.chat.uid)[str(msg.chat.uid)]

            if msg.target and msg.target.uid:
                fb_msg.reply_to_id = msg.target.uid

            if msg.type in (MsgType.Text, MsgType.Unsupported):
                # Remove variation selector-16 (force colored emoji) for
                # matching.
                emoji_compare = msg.text.replace("\uFE0F", "")
                if emoji_compare == "👍":
                    fb_msg.sticker = Sticker(uid=EmojiSize.SMALL.value)
                    if not prefix:
                        fb_msg.text = None
                elif emoji_compare[:-1] == "👍" and emoji_compare[-1] in 'SML':
                    if emoji_compare[-1] == 'S':
                        fb_msg.sticker = Sticker(uid=EmojiSize.SMALL.value)
                    elif emoji_compare[-1] == 'M':
                        fb_msg.sticker = Sticker(uid=EmojiSize.MEDIUM.value)
                    elif emoji_compare[-1] == 'L':
                        fb_msg.sticker = Sticker(uid=EmojiSize.LARGE.value)
                    if not prefix:
                        fb_msg.text = None
                elif emoji_compare[:-1] in emoji.UNICODE_EMOJI and emoji_compare[-1] in 'SML':  # type: ignore
                    self.logger.debug("[%s] Message is an Emoji message: %s", msg.uid, emoji_compare)
                    if emoji_compare[-1] == 'S':
                        fb_msg.emoji_size = EmojiSize.SMALL
                    elif emoji_compare[-1] == 'M':
                        fb_msg.emoji_size = EmojiSize.MEDIUM
                    elif emoji_compare[-1] == 'L':
                        fb_msg.emoji_size = EmojiSize.LARGE
                    fb_msg.text = emoji_compare[:-1]
                msg.uid = self.client.send(fb_msg, thread_id=thread.uid, thread_type=thread.type)
            elif msg.type in (MsgType.Image, MsgType.Sticker, MsgType.Animation):
                msg_uid = self.client.send_image_file(msg.filename, msg.file, msg.mime, message=fb_msg,
                                                      thread_id=thread.uid, thread_type=thread.type)
                if msg_uid.startswith('mid.$'):
                    self.client.sent_messages.add(msg_uid)
                    self.logger.debug("Sent message with ID %s", msg_uid)
                msg.uid = msg_uid
            elif msg.type == MsgType.Voice:
                files = self.upload_file(msg, voice_clip=True)
                msg_uid = self.client._sendFiles(files=files, message=fb_msg,
                                                 thread_id=thread.uid, thread_type=thread.type)
                if msg_uid.startswith('mid.$'):
                    self.client.sent_messages.add(msg_uid)
                    self.logger.debug("Sent message with ID %s", msg_uid)
                msg.uid = msg_uid
            elif msg.type in (MsgType.File, MsgType.Video):
                files = self.upload_file(msg)
                msg_uid = self.client._sendFiles(files=files, message=fb_msg,
                                                 thread_id=thread.uid, thread_type=thread.type)
                if msg_uid.startswith('mid.$'):
                    self.client.sent_messages.add(msg_uid)
                    self.logger.debug("Sent message with ID %s", msg_uid)
                msg.uid = msg_uid
            elif msg.type == MsgType.Status:
                assert (isinstance(msg.attributes, StatusAttribute))
                status: StatusAttribute = msg.attributes
                if status.status_type in (StatusAttribute.Types.TYPING,
                                          StatusAttribute.Types.UPLOADING_VOICE,
                                          StatusAttribute.Types.UPLOADING_VIDEO,
                                          StatusAttribute.Types.UPLOADING_IMAGE,
                                          StatusAttribute.Types.UPLOADING_FILE):
                    self.client.setTypingStatus(TypingStatus.TYPING, thread_id=thread.uid, thread_type=thread.type)
                    threading.Timer(status.timeout / 1000, self.stop_typing, args=(thread.uid, thread.type)).start()
            elif msg.type == MsgType.Link:
                assert (isinstance(msg.attributes, LinkAttribute))
                link: LinkAttribute = msg.attributes
                if self.flag('send_link_with_description'):
                    info: Tuple[str, ...] = (link.title,)
                    if link.description:
                        info += (link.description,)
                    info += (link.url,)
                    text = "\n".join(info)
                else:
                    text = link.url
                if fb_msg.text:
                    text = fb_msg.text + "\n" + text
                fb_msg.text = text
                msg.uid = self.client.send(fb_msg, thread_id=thread.uid, thread_type=thread.type)
            elif msg.type == MsgType.Location:
                assert (isinstance(msg.attributes, LocationAttribute))
                location_attr: LocationAttribute = msg.attributes
                location = LocationAttachment(latitude=location_attr.latitude,
                                              longitude=location_attr.longitude)
                self.client.sendPinnedLocation(location, fb_msg, thread_id=thread.uid, thread_type=thread.type)
            else:
                raise EFBMessageTypeNotSupported()
            return msg
        finally:
            if msg.file and not msg.file.closed:
                msg.file.close()
            self.client.markAsSeen()
            self.client.markAsRead(msg.chat.uid)
    def attach_msg_type(self, msg: Message, attachment: Dict[str, Any]):
        """
        Attach message_type to a message.

        Args:
            msg: Message to be attached to
            attachment:
                Dict of information of the attachment
                ``fbchat`` entity is not used as it is not completed.
        """
        blob_attachment: Dict[str, Any] = attachment.get('mercury', {}).get('blob_attachment', attachment)
        attachment_type: str = blob_attachment.get("__typename", None)
        if 'sticker_attachment' in attachment.get('mercury', {}):
            attachment_type = '__Sticker'
        if 'extensible_attachment' in attachment.get('mercury', {}):
            attachment_type = '__Link'
            if get_value(attachment, ('mercury', 'extensible_attachment',
                                      'story_attachment', 'target', '__typename')) == 'MessageLocation':
                attachment_type = 'MessageLocation'

        if attachment_type == "MessageAudio":
            msg.type = MsgType.Voice
        elif attachment_type == 'MessageImage':
            msg.type = MsgType.Image
        elif attachment_type == 'MessageAnimatedImage':
            msg.type = MsgType.Image
        elif attachment_type == 'MessageFile':
            msg.type = MsgType.File
        elif attachment_type == 'MessageVideo':
            msg.type = MsgType.Image
        elif attachment_type == '__Sticker':
            msg.type = MsgType.Sticker
            msg.text = attachment['mercury']['sticker_attachment'].get('label', '')
        elif attachment_type == '__Link':
            msg.type = MsgType.Link
            link_information = get_value(attachment, ('mercury', 'extensible_attachment', 'story_attachment'), {})
            title = get_value(link_information, ('title_with_entities', 'text'), '')
            description = get_value(link_information, ('description', 'text'), '')
            source = get_value(link_information, ('source', 'text'), None)
            if source:
                description += " (via %s)" % source
            preview = get_value(link_information, ('media', 'playable_url'), None) if \
                get_value(link_information, ('media', 'is_playable'), False) else None
            preview = preview or get_value(link_information, ('media', 'image', 'uri'), None)
            url = link_information['media'].get('url', preview)
            msg.attributes = LinkAttribute(title=title,
                                           description=description,
                                           image=self.process_url(preview),
                                           url=self.process_url(url))
        elif attachment_type == 'MessageLocation':
            msg.type = MsgType.Location
            link_information = get_value(attachment, ('mercury', 'extensible_attachment', 'story_attachment'), {})
            title = get_value(link_information, ('title_with_entities', 'text'), '')
            description = get_value(link_information, ('description', 'text'), '')
            msg.text = '\n'.join([title, description])
            preview = get_value(link_information, ('media', 'image', 'uri'), None)
            matches = re.search(r'markers=([\d.-]+)%2C([\d.-]+)', preview)
            if matches:
                latitude, longitude = map(float, matches.groups())
            else:
                msg.type = MsgType.Unsupported
                msg.text = self._("Message type unsupported.\n{content}").format(msg.text)
                return
            msg.attributes = LocationAttribute(
                latitude=latitude, longitude=longitude
            )
        else:
            msg.type = MsgType.Unsupported
            msg.text = self._("Message type unsupported.\n{content}").format(msg.text)