Esempio n. 1
0
def facebook_to_matrix(message: Message) -> TextMessageEventContent:
    content = TextMessageEventContent(msgtype=MessageType.TEXT,
                                      body=message.text)
    text = message.text
    for m in reversed(message.mentions):
        original = text[m.offset:m.offset + m.length]
        if len(original) > 0 and original[0] == "@":
            original = original[1:]
        text = f"{text[:m.offset]}@{m.thread_id}\u2063{original}\u2063{text[m.offset + m.length:]}"
    html = escape(text)
    output = []
    codeblock = False
    blockquote = False
    line: str
    lines = html.split("\n")
    for i, line in enumerate(lines):
        blockquote, line = _handle_blockquote(output, blockquote, line)
        codeblock, line, post_args = _handle_codeblock_pre(
            output, codeblock, line)
        output.append(_convert_formatting(line))
        if i != len(lines) - 1:
            output.append("<br/>")
        _handle_codeblock_post(output, *post_args)
    html = "".join(output)

    html = MENTION_REGEX.sub(_mention_replacer, html)
    if html != escape(content.body).replace("\n", "<br/>"):
        content.format = Format.HTML
        content.formatted_body = html
    return content
Esempio n. 2
0
def facebook_to_matrix(message: fbchat.MessageData) -> TextMessageEventContent:
    text = message.text or ""
    content = TextMessageEventContent(msgtype=MessageType.TEXT, body=text)
    for m in reversed(message.mentions):
        original = text[m.offset:m.offset + m.length]
        if len(original) > 0 and original[0] == "@":
            original = original[1:]
        text = f"{text[:m.offset]}@{m.thread_id}\u2063{original}\u2063{text[m.offset + m.length:]}"
    html = escape(text)
    output = []
    if html:
        codeblock = False
        blockquote = False
        line: str
        lines = html.split("\n")
        for i, line in enumerate(lines):
            blockquote, line = _handle_blockquote(output, blockquote, line)
            codeblock, line, post_args = _handle_codeblock_pre(output, codeblock, line)
            output.append(_convert_formatting(line))
            if i != len(lines) - 1:
                output.append("<br/>")
            _handle_codeblock_post(output, *post_args)
    for attachment in message.attachments:
        if ((isinstance(attachment, fbchat.ShareAttachment)
             and attachment.original_url.rstrip("/") not in text)):
            output.append(f"<br/><a href='{attachment.original_url}'>{attachment.title}</a>")
            content.body += f"\n{attachment.title}: {attachment.original_url}"
    html = "".join(output)

    html = MENTION_REGEX.sub(_mention_replacer, html)
    if html != escape(content.body).replace("\n", "<br/>"):
        content.format = Format.HTML
        content.formatted_body = html
    return content
Esempio n. 3
0
 async def send_simple_html(self, room_id: str, html: str):
     ''' Publish a simple HTML message in the selected room. '''
     from mautrix.types import Format
     content = TextMessageEventContent(msgtype=MessageType.TEXT)
     content.format = Format.HTML
     content.formatted_body = html
     await self._client.send_message(room_id=room_id, content=content)
Esempio n. 4
0
async def signal_to_matrix(message: MessageData) -> TextMessageEventContent:
    content = TextMessageEventContent(msgtype=MessageType.TEXT,
                                      body=message.body)
    surrogated_text = add_surrogate(message.body)
    if message.mentions:
        text_chunks = []
        html_chunks = []
        last_offset = 0
        for mention in message.mentions:
            before = surrogated_text[last_offset:mention.start]
            last_offset = mention.start + mention.length

            text_chunks.append(before)
            html_chunks.append(escape(before))
            puppet = await pu.Puppet.get_by_address(Address(uuid=mention.uuid))
            name = add_surrogate(puppet.name or puppet.mxid)
            text_chunks.append(name)
            html_chunks.append(
                f'<a href="https://matrix.to/#/{puppet.mxid}">{name}</a>')
        end = surrogated_text[last_offset:]
        text_chunks.append(end)
        html_chunks.append(escape(end))
        content.body = del_surrogate("".join(text_chunks))
        content.format = Format.HTML
        content.formatted_body = del_surrogate("".join(html_chunks))
    return content
Esempio n. 5
0
async def twitter_to_matrix(message: MessageData) -> TextMessageEventContent:
    content = TextMessageEventContent(msgtype=MessageType.TEXT,
                                      body=message.text,
                                      format=Format.HTML,
                                      formatted_body=message.text)
    for entity in reversed(message.entities.all) if message.entities else []:
        start, end = entity.indices
        if isinstance(entity, MessageEntityURL):
            content.body = content.body[:
                                        start] + entity.expanded_url + content.body[
                                            end:]
            content.formatted_body = (
                f'{content.formatted_body[:start]}'
                f'<a href="{entity.expanded_url}">{entity.display_url}</a>'
                f'{content.formatted_body[end:]}')
        elif isinstance(entity, MessageEntityUserMention):
            puppet = await pu.Puppet.get_by_twid(entity.id, create=False)
            if puppet:
                user_url = f"https://matrix.to/#/{puppet.mxid}"
                content.formatted_body = (
                    f'{content.formatted_body[:start]}'
                    f'<a href="{user_url}">{puppet.name or entity.name}</a>'
                    f'{content.formatted_body[end:]}')
        else:
            # Get the sigil (# or $) from the body
            text = content.formatted_body[start:end][0] + entity.text
            content.formatted_body = (f'{content.formatted_body[:start]}'
                                      f'<font color="#0000ff">{text}</font>'
                                      f'{content.formatted_body[end:]}')
    if content.formatted_body == content.body:
        content.formatted_body = None
        content.format = None
    content.body = html.unescape(content.body)
    return content
async def _add_forward_header(source: 'AbstractUser', content: TextMessageEventContent,
                              fwd_from: MessageFwdHeader) -> None:
    if not content.formatted_body or content.format != Format.HTML:
        content.format = Format.HTML
        content.formatted_body = escape(content.body)
    fwd_from_html, fwd_from_text = None, None
    if isinstance(fwd_from.from_id, PeerUser):
        user = u.User.get_by_tgid(TelegramID(fwd_from.from_id.user_id))
        if user:
            fwd_from_text = user.displayname or user.mxid
            fwd_from_html = (f"<a href='https://matrix.to/#/{user.mxid}'>"
                             f"{escape(fwd_from_text)}</a>")

        if not fwd_from_text:
            puppet = pu.Puppet.get(TelegramID(fwd_from.from_id.user_id), create=False)
            if puppet and puppet.displayname:
                fwd_from_text = puppet.displayname or puppet.mxid
                fwd_from_html = (f"<a href='https://matrix.to/#/{puppet.mxid}'>"
                                 f"{escape(fwd_from_text)}</a>")

        if not fwd_from_text:
            try:
                user = await source.client.get_entity(fwd_from.from_id)
                if user:
                    fwd_from_text, _ = pu.Puppet.get_displayname(user, False)
                    fwd_from_html = f"<b>{escape(fwd_from_text)}</b>"
            except (ValueError, RPCError):
                fwd_from_text = fwd_from_html = "unknown user"
    elif isinstance(fwd_from.from_id, (PeerChannel, PeerChat)):
        from_id = (fwd_from.from_id.chat_id if isinstance(fwd_from.from_id, PeerChat)
                   else fwd_from.from_id.channel_id)
        portal = po.Portal.get_by_tgid(TelegramID(from_id))
        if portal and portal.title:
            fwd_from_text = portal.title
            if portal.alias:
                fwd_from_html = (f"<a href='https://matrix.to/#/{portal.alias}'>"
                                 f"{escape(fwd_from_text)}</a>")
            else:
                fwd_from_html = f"channel <b>{escape(fwd_from_text)}</b>"
        else:
            try:
                channel = await source.client.get_entity(fwd_from.from_id)
                if channel:
                    fwd_from_text = f"channel {channel.title}"
                    fwd_from_html = f"channel <b>{escape(channel.title)}</b>"
            except (ValueError, RPCError):
                fwd_from_text = fwd_from_html = "unknown channel"
    elif fwd_from.from_name:
        fwd_from_text = fwd_from.from_name
        fwd_from_html = f"<b>{escape(fwd_from.from_name)}</b>"
    else:
        fwd_from_text = "unknown source"
        fwd_from_html = f"unknown source"

    content.body = "\n".join([f"> {line}" for line in content.body.split("\n")])
    content.body = f"Forwarded from {fwd_from_text}:\n{content.body}"
    content.formatted_body = (
        f"Forwarded message from {fwd_from_html}<br/>"
        f"<tg-forward><blockquote>{content.formatted_body}</blockquote></tg-forward>")
Esempio n. 7
0
async def telegram_to_matrix(
        evt: Message,
        source: "AbstractUser",
        main_intent: Optional[IntentAPI] = None,
        prefix_text: Optional[str] = None,
        prefix_html: Optional[str] = None,
        override_text: str = None,
        override_entities: List[TypeMessageEntity] = None,
        no_reply_fallback: bool = False) -> TextMessageEventContent:
    content = TextMessageEventContent(
        msgtype=MessageType.TEXT,
        body=add_surrogate(override_text or evt.message),
    )
    entities = override_entities or evt.entities
    if entities:
        content.format = Format.HTML
        content.formatted_body = _telegram_entities_to_matrix_catch(
            content.body, entities)

    if prefix_html:
        if not content.formatted_body:
            content.format = Format.HTML
            content.formatted_body = escape(content.body)
        content.formatted_body = prefix_html + content.formatted_body
    if prefix_text:
        content.body = prefix_text + content.body

    if evt.fwd_from:
        await _add_forward_header(source, content, evt.fwd_from)

    if evt.reply_to_msg_id and not no_reply_fallback:
        await _add_reply_header(source, content, evt, main_intent)

    if isinstance(evt, Message) and evt.post and evt.post_author:
        if not content.formatted_body:
            content.formatted_body = escape(content.body)
        content.body += f"\n- {evt.post_author}"
        content.formatted_body += f"<br/><i>- <u>{evt.post_author}</u></i>"

    content.body = del_surrogate(content.body)

    if content.formatted_body:
        content.formatted_body = del_surrogate(
            content.formatted_body.replace("\n", "<br/>"))

    return content
Esempio n. 8
0
async def facebook_to_matrix(
        msg: Union[graphql.MessageText,
                   mqtt.Message]) -> TextMessageEventContent:
    if isinstance(msg, mqtt.Message):
        text = msg.text
        mentions = msg.mentions
    elif isinstance(msg, graphql.MessageText):
        text = msg.text
        mentions = msg.ranges
    else:
        raise ValueError(
            f"Unsupported Facebook message type {type(msg).__name__}")
    text = text or ""
    content = TextMessageEventContent(msgtype=MessageType.TEXT, body=text)
    mention_user_ids = []
    for m in reversed(mentions):
        original = text[m.offset:m.offset + m.length]
        if len(original) > 0 and original[0] == "@":
            original = original[1:]
        mention_user_ids.append(int(m.user_id))
        text = f"{text[:m.offset]}@{m.user_id}\u2063{original}\u2063{text[m.offset + m.length:]}"
    html = escape(text)
    output = []
    if html:
        codeblock = False
        blockquote = False
        line: str
        lines = html.split("\n")
        for i, line in enumerate(lines):
            blockquote, line = _handle_blockquote(output, blockquote, line)
            codeblock, line, post_args = _handle_codeblock_pre(
                output, codeblock, line)
            output.append(_convert_formatting(line))
            if i != len(lines) - 1:
                output.append("<br/>")
            _handle_codeblock_post(output, *post_args)
    html = "".join(output)

    mention_user_map = {}
    for fbid in mention_user_ids:
        user = await u.User.get_by_fbid(fbid)
        if user:
            mention_user_map[fbid] = user.mxid
        else:
            puppet = await pu.Puppet.get_by_fbid(fbid, create=False)
            mention_user_map[fbid] = puppet.mxid if puppet else None

    def _mention_replacer(match: Match) -> str:
        mxid = mention_user_map[int(match.group(1))]
        if not mxid:
            return match.group(2)
        return f"<a href=\"https://matrix.to/#/{mxid}\">{match.group(2)}</a>"

    html = MENTION_REGEX.sub(_mention_replacer, html)
    if html != escape(content.body).replace("\n", "<br/>"):
        content.format = Format.HTML
        content.formatted_body = html
    return content
Esempio n. 9
0
 async def _handle_instagram_reel_share(
         self, source: 'u.User', intent: IntentAPI,
         item: ThreadItem) -> Optional[EventID]:
     media = item.reel_share.media
     prefix_html = None
     if item.reel_share.type == ReelShareType.REPLY:
         if item.reel_share.reel_owner_id == source.igpk:
             prefix = "Replied to your story"
         else:
             username = media.user.username
             prefix = f"Sent @{username}'s story"
             user_link = f'<a href="https://www.instagram.com/{username}/">@{username}</a>'
             prefix_html = f"Sent {user_link}'s story"
     elif item.reel_share.type == ReelShareType.REACTION:
         prefix = "Reacted to your story"
     else:
         self.log.debug(
             f"Unsupported reel share type {item.reel_share.type}")
         return None
     prefix_content = TextMessageEventContent(msgtype=MessageType.NOTICE,
                                              body=prefix)
     if prefix_html:
         prefix_content.format = Format.HTML
         prefix_content.formatted_body = prefix_html
     content = TextMessageEventContent(msgtype=MessageType.TEXT,
                                       body=item.reel_share.text)
     await self._send_message(intent,
                              prefix_content,
                              timestamp=item.timestamp // 1000)
     if isinstance(media, ExpiredMediaItem):
         # TODO send message about expired story
         pass
     else:
         fake_item_id = f"fi.mau.instagram.reel_share.{item.user_id}.{media.pk}"
         existing = await DBMessage.get_by_item_id(fake_item_id,
                                                   self.receiver)
         if existing:
             # If the user already reacted or replied to the same reel share item,
             # use a Matrix reply instead of reposting the image.
             content.set_reply(existing.mxid)
         else:
             media_event_id = await self._handle_instagram_media(
                 source, intent, item)
             await DBMessage(mxid=media_event_id,
                             mx_room=self.mxid,
                             item_id=fake_item_id,
                             receiver=self.receiver,
                             sender=media.user.pk).insert()
     return await self._send_message(intent,
                                     content,
                                     timestamp=item.timestamp // 1000)
Esempio n. 10
0
    async def _apply_emote_format(self, sender: 'u.User',
                                  content: TextMessageEventContent) -> None:
        if content.format != Format.HTML:
            content.format = Format.HTML
            content.formatted_body = escape_html(content.body).replace("\n", "<br/>")

        tpl = self.get_config("emote_format")
        puppet = p.Puppet.get(sender.tgid)
        content.formatted_body = Template(tpl).safe_substitute(
            dict(sender_mxid=sender.mxid,
                 sender_username=sender.mxid_localpart,
                 sender_displayname=escape_html(await self.get_displayname(sender)),
                 mention=f"<a href='https://matrix.to/#/{puppet.mxid}'>{puppet.displayname}</a>",
                 username=sender.username,
                 displayname=puppet.displayname,
                 body=content.body,
                 formatted_body=content.formatted_body))
        content.msgtype = MessageType.TEXT
Esempio n. 11
0
async def telegram_to_matrix(
    evt: Message | SponsoredMessage,
    source: au.AbstractUser,
    main_intent: IntentAPI | None = None,
    prefix_text: str | None = None,
    prefix_html: str | None = None,
    override_text: str = None,
    override_entities: list[TypeMessageEntity] = None,
    no_reply_fallback: bool = False,
    require_html: bool = False,
) -> TextMessageEventContent:
    content = TextMessageEventContent(
        msgtype=MessageType.TEXT,
        body=add_surrogate(override_text or evt.message),
    )
    entities = override_entities or evt.entities
    if entities:
        content.format = Format.HTML
        html = await _telegram_entities_to_matrix_catch(
            add_surrogate(content.body), entities)
        content.formatted_body = del_surrogate(html)

    if require_html:
        content.ensure_has_html()

    if prefix_html:
        content.ensure_has_html()
        content.formatted_body = prefix_html + content.formatted_body
    if prefix_text:
        content.body = prefix_text + content.body

    if getattr(evt, "fwd_from", None):
        await _add_forward_header(source, content, evt.fwd_from)

    if getattr(evt, "reply_to", None) and not no_reply_fallback:
        await _add_reply_header(source, content, evt, main_intent)

    if isinstance(evt, Message) and evt.post and evt.post_author:
        content.ensure_has_html()
        content.body += f"\n- {evt.post_author}"
        content.formatted_body += f"<br/><i>- <u>{evt.post_author}</u></i>"

    return content
Esempio n. 12
0
async def make_contact_event_content(
    source: au.AbstractUser, contact: MessageMediaContact
) -> TextMessageEventContent:
    name = " ".join(x for x in [contact.first_name, contact.last_name] if x)
    formatted_phone = f"+{contact.phone_number}"
    if phonenumbers is not None:
        try:
            parsed = phonenumbers.parse(formatted_phone)
            fmt = phonenumbers.PhoneNumberFormat.INTERNATIONAL
            formatted_phone = phonenumbers.format_number(parsed, fmt)
        except phonenumbers.NumberParseException:
            pass
    content = TextMessageEventContent(
        msgtype=MessageType.TEXT,
        body=f"Shared contact info for {name}: {formatted_phone}",
    )
    content["net.maunium.telegram.contact"] = {
        "user_id": contact.user_id,
        "first_name": contact.first_name,
        "last_name": contact.last_name,
        "phone_number": contact.phone_number,
        "vcard": contact.vcard,
    }

    puppet = await pu.Puppet.get_by_tgid(TelegramID(contact.user_id))
    if not puppet.displayname:
        try:
            entity = await source.client.get_entity(PeerUser(contact.user_id))
            await puppet.update_info(source, entity)
        except Exception as e:
            source.log.warning(f"Failed to sync puppet info of received contact: {e}")
    else:
        content.format = Format.HTML
        content.formatted_body = (
            f"Shared contact info for "
            f"<a href='https://matrix.to/#/{puppet.mxid}'>{html.escape(name)}</a>: "
            f"{html.escape(formatted_phone)}"
        )
    return content
Esempio n. 13
0
    async def _handle_facebook_attachment(
            self, intent: IntentAPI, attachment: AttachmentClass,
            reply_to: str, message_text: str) -> Optional[EventID]:
        if isinstance(attachment, AudioAttachment):
            mxc, mime, size = await self._reupload_fb_photo(
                attachment.url, intent, attachment.filename)
            event_id = await intent.send_file(
                self.mxid,
                mxc,
                file_type=MessageType.AUDIO,
                info=AudioInfo(size=size,
                               mimetype=mime,
                               duration=attachment.duration),
                file_name=attachment.filename,
                relates_to=self._get_facebook_reply(reply_to))
        # elif isinstance(attachment, VideoAttachment):
        # TODO
        elif isinstance(attachment, FileAttachment):
            mxc, mime, size = await self._reupload_fb_photo(
                attachment.url, intent, attachment.name)
            event_id = await intent.send_file(
                self.mxid,
                mxc,
                info=FileInfo(size=size, mimetype=mime),
                file_name=attachment.name,
                relates_to=self._get_facebook_reply(reply_to))
        elif isinstance(attachment, ImageAttachment):
            mxc, mime, size = await self._reupload_fb_photo(
                attachment.large_preview_url or attachment.preview_url, intent)
            info = ImageInfo(size=size,
                             mimetype=mime,
                             width=attachment.large_preview_width,
                             height=attachment.large_preview_height)
            event_id = await intent.send_image(
                self.mxid,
                mxc,
                info=info,
                file_name=f"image.{attachment.original_extension}",
                relates_to=self._get_facebook_reply(reply_to))
        elif isinstance(attachment, LocationAttachment):
            content = await self._convert_facebook_location(intent, attachment)
            content.relates_to = self._get_facebook_reply(reply_to)
            event_id = await intent.send_message(self.mxid, content)
        elif isinstance(attachment, ShareAttachment):
            # remove trailing slash for url searching
            url = attachment.original_url
            if url[-1] == "/":
                url = url[0:-1]

            title = attachment.title
            if title == "":
                title = url
            # Prevent sending urls that are already in the original message text
            if message_text is not None and url not in message_text:
                content = TextMessageEventContent(
                    msgtype=MessageType.TEXT,
                    body=f"{title}: {attachment.original_url}")
                content.format = Format.HTML
                content.formatted_body = f"<a href='{attachment.original_url}'>{title}</a>"
                event_id = await intent.send_message(self.mxid, content)
            elif message_text is None:
                content = TextMessageEventContent(
                    msgtype=MessageType.TEXT,
                    body=f"{title}: {attachment.original_url}")
                content.format = Format.HTML
                content.formatted_body = f"<a href='{attachment.original_url}'>{title}</a>"
                event_id = await intent.send_message(self.mxid, content)
            else:
                return None
        else:
            self.log.warning(f"Unsupported attachment type: {attachment}")
            return None
        self._last_bridged_mxid = event_id
        return event_id