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
async def send_bridge_notice( self, text: str, edit: EventID | None = None, state_event: BridgeStateEvent | None = None, important: bool = False, error_code: str | None = None, error_message: str | None = None, ) -> EventID | None: if state_event: await self.push_bridge_state( state_event, error=error_code, message=error_message if error_code else text ) if self.config["bridge.disable_bridge_notices"]: return None if not important and not self.config["bridge.unimportant_bridge_notices"]: self.log.debug("Not sending unimportant bridge notice: %s", text) return None event_id = None try: self.log.debug("Sending bridge notice: %s", text) content = TextMessageEventContent( body=text, msgtype=(MessageType.TEXT if important else MessageType.NOTICE) ) if edit: content.set_edit(edit) # This is locked to prevent notices going out in the wrong order async with self._notice_send_lock: event_id = await self.az.intent.send_message(await self.get_notice_room(), content) except Exception: self.log.warning("Failed to send bridge notice", exc_info=True) return edit or event_id
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
async def matrix_to_signal( content: TextMessageEventContent) -> Tuple[str, List[Mention]]: if content.msgtype == MessageType.EMOTE: content.body = f"/me {content.body}" if content.formatted_body: content.formatted_body = f"/me {content.formatted_body}" mentions = [] if content.format == Format.HTML and content.formatted_body: parsed = MatrixParser.parse(add_surrogate(content.formatted_body)) text = del_surrogate(parsed.text) for mention in parsed.entities: mxid = mention.extra_info["user_id"] user = await u.User.get_by_mxid(mxid, create=False) if user and user.uuid: uuid = user.uuid else: puppet = await pu.Puppet.get_by_mxid(mxid, create=False) if puppet: uuid = puppet.uuid else: continue mentions.append( Mention(uuid=uuid, start=mention.offset, length=mention.length)) else: text = content.body return text, mentions
async def matrix_to_facebook(content: TextMessageEventContent, room_id: RoomID) -> SendParams: mentions = [] reply_to = None if content.relates_to.rel_type == RelationType.REPLY: message = await DBMessage.get_by_mxid(content.relates_to.event_id, room_id) if message: content.trim_reply_fallback() reply_to = message.fbid if content.format == Format.HTML and content.formatted_body: parsed = MatrixParser.parse(content.formatted_body) text = parsed.text mentions = [] for mention in parsed.entities: mxid = mention.extra_info["user_id"] user = await u.User.get_by_mxid(mxid, create=False) if user and user.fbid: fbid = user.fbid else: puppet = await pu.Puppet.get_by_mxid(mxid, create=False) if puppet: fbid = puppet.fbid else: continue mentions.append(Mention(user_id=str(fbid), offset=mention.offset, length=mention.length)) else: text = content.body return SendParams(text=text, mentions=mentions, reply_to=reply_to)
async def ping_handler(self, evt: MessageEvent, message: str = "") -> None: diff = int(time() * 1000) - evt.timestamp pretty_diff = self.prettify_diff(diff) text_message = f'"{message[:20]}" took' if message else "took" html_message = f'"{escape(message[:20])}" took' if message else "took" content = TextMessageEventContent( msgtype=MessageType.NOTICE, format=Format.HTML, body= f"{evt.sender}: Pong! (ping {text_message} {pretty_diff} to arrive)", formatted_body= f"<a href='https://matrix.to/#/{evt.sender}'>{evt.sender}</a>: Pong! " f"(<a href='https://matrix.to/#/{evt.room_id}/{evt.event_id}'>ping</a> {html_message} " f"{pretty_diff} to arrive)", relates_to=RelatesTo( rel_type=RelationType("xyz.maubot.pong"), event_id=evt.event_id, )) pong_from = evt.sender.split(":", 1)[1] content.relates_to["from"] = pong_from content.relates_to["ms"] = diff content["pong"] = { "ms": diff, "from": pong_from, "ping": evt.event_id, } await evt.respond(content)
async def _send_reminder(self, reminder: ReminderInfo) -> None: if len(reminder.users) == 0: self.log.debug( f"Cancelling reminder {reminder}, no users left to remind") return wait = (reminder.date - datetime.now(tz=pytz.UTC)).total_seconds() if wait > 0: self.log.debug(f"Waiting {wait} seconds to send {reminder}") await asyncio.sleep(wait) else: self.log.debug(f"Sending {reminder} immediately") users = " ".join(reminder.users) users_html = " ".join( f"<a href='https://matrix.to/#/{user_id}'>{user_id}</a>" for user_id in reminder.users) content = TextMessageEventContent( msgtype=MessageType.TEXT, body=f"{users}: {reminder.message}", format=Format.HTML, formatted_body=f"{users_html}: {escape(reminder.message)}") content["xyz.maubot.reminder"] = { "id": reminder.id, "message": reminder.message, "targets": list(reminder.users), "reply_to": reminder.reply_to, } if reminder.reply_to: content.set_reply(await self.client.get_event(reminder.room_id, reminder.reply_to)) await self.client.send_message(reminder.room_id, content)
async def _handle_matrix_text(self, sender_id: TelegramID, event_id: EventID, space: TelegramID, client: 'MautrixTelegramClient', content: TextMessageEventContent, reply_to: TelegramID) -> None: if content.formatted_body and content.format == Format.HTML: message, entities = formatter.matrix_to_telegram( content.formatted_body) else: message, entities = formatter.matrix_text_to_telegram(content.body) async with self.send_lock(sender_id): lp = self.get_config("telegram_link_preview") if content.get_edit(): orig_msg = DBMessage.get_by_mxid(content.get_edit(), self.mxid, space) if orig_msg: response = await client.edit_message( self.peer, orig_msg.tgid, message, formatting_entities=entities, link_preview=lp) self._add_telegram_message_to_db(event_id, space, -1, response) return response = await client.send_message(self.peer, message, reply_to=reply_to, formatting_entities=entities, link_preview=lp) self._add_telegram_message_to_db(event_id, space, 0, response) await self._send_delivery_receipt(event_id)
def send_text(self, room_id: RoomID, text: str, html: Optional[str] = None, msgtype: MessageType = MessageType.TEXT, relates_to: Optional[RelatesTo] = None, **kwargs) -> Awaitable[EventID]: """ Send a text message to a room. Args: room_id: The ID of the room to send the message to. text: The text to send. If set to None, the given HTML is used instead. html: The HTML to send. msgtype: The message type to send. Defaults to :attr:`MessageType.TEXT` (normal text message) relates_to: Message relation metadata used for things like replies. **kwargs: Optional parameters to pass to the :meth:`HTTPAPI.request` method. Returns: The ID of the event that was sent. """ if html: if not text: text = html content = TextMessageEventContent(msgtype=msgtype, body=text, format=Format.HTML, formatted_body=html) else: content = TextMessageEventContent(msgtype=msgtype, body=text) if relates_to: content.relates_to = relates_to return self.send_message(room_id, content, **kwargs)
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
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)
async def _handle_matrix_text(self, sender_id: TelegramID, event_id: EventID, space: TelegramID, client: 'MautrixTelegramClient', content: TextMessageEventContent, reply_to: TelegramID) -> None: async with self.send_lock(sender_id): lp = self.get_config("telegram_link_preview") if content.get_edit(): orig_msg = DBMessage.get_by_mxid(content.get_edit(), self.mxid, space) if orig_msg: response = await client.edit_message( self.peer, orig_msg.tgid, content, parse_mode=self._matrix_event_to_entities, link_preview=lp) self._add_telegram_message_to_db(event_id, space, -1, response) return response = await client.send_message( self.peer, content, reply_to=reply_to, parse_mode=self._matrix_event_to_entities, link_preview=lp) self._add_telegram_message_to_db(event_id, space, 0, response) await self._send_delivery_receipt(event_id)
async def _handle_encrypted_wait(self, evt: EncryptedEvent, err: SessionNotFound, wait: int) -> None: self.log.warning( f"Didn't find session {err.session_id}, waiting even longer") msg = ( "\u26a0 Your message was not bridged: the bridge hasn't received the decryption " f"keys. The bridge will retry for {wait} seconds. If this error keeps happening, " "try restarting your client.") event_id = await self.az.intent.send_notice(evt.room_id, msg) got_keys = await self.e2ee.crypto.wait_for_session(evt.room_id, err.sender_key, err.session_id, timeout=wait) if got_keys: try: decrypted = await self.e2ee.decrypt(evt, wait_session_timeout=0) except DecryptionError as e: msg = f"\u26a0 Your message was not bridged: {e}" else: await self.az.intent.redact(evt.room_id, event_id) await self.int_handle_event(decrypted) return else: msg = ( "\u26a0 Your message was not bridged: the bridge hasn't received the decryption " "keys. If this error keeps happening, try restarting your client." ) content = TextMessageEventContent(msgtype=MessageType.NOTICE, body=msg) content.set_edit(event_id) await self.az.intent.send_message(evt.room_id, 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>")
async def _expire_telegram_photo(self, intent: IntentAPI, event_id: EventID, ttl: int) -> None: try: content = TextMessageEventContent(msgtype=MessageType.NOTICE, body="Photo has expired") content.set_edit(event_id) await asyncio.sleep(ttl) await self._send_message(intent, content) except Exception: self.log.warning("Failed to expire Telegram photo %s", event_id, exc_info=True)
async def _edit(self, room_id: RoomID, event_id: EventID, text: str) -> None: content = TextMessageEventContent(msgtype=MessageType.NOTICE, body=text, format=Format.HTML, formatted_body=markdown.render(text)) content.set_edit(event_id) await self.client.send_message(room_id, content)
async def login_qr(evt: CommandEvent) -> EventID: login_as = evt.sender if len(evt.args) > 0 and evt.sender.is_admin: login_as = await u.User.get_by_mxid(UserID(evt.args[0])) if not qrcode or not QRLogin: return await evt.reply("This bridge instance does not support logging in with a QR code.") if await login_as.is_logged_in(): return await evt.reply(f"You are already logged in as {login_as.human_tg_id}.") await login_as.ensure_started(even_if_no_session=True) qr_login = QRLogin(login_as.client, ignored_ids=[]) qr_event_id: EventID | None = None async def upload_qr() -> None: nonlocal qr_event_id buffer = io.BytesIO() image = qrcode.make(qr_login.url) size = image.pixel_size image.save(buffer, "PNG") qr = buffer.getvalue() mxc = await evt.az.intent.upload_media(qr, "image/png", "login-qr.png", len(qr)) content = MediaMessageEventContent( body=qr_login.url, url=mxc, msgtype=MessageType.IMAGE, info=ImageInfo(mimetype="image/png", size=len(qr), width=size, height=size), ) if qr_event_id: content.set_edit(qr_event_id) await evt.az.intent.send_message(evt.room_id, content) else: content.set_reply(evt.event_id) qr_event_id = await evt.az.intent.send_message(evt.room_id, content) retries = 4 while retries > 0: await qr_login.recreate() await upload_qr() try: user = await qr_login.wait() break except asyncio.TimeoutError: retries -= 1 except SessionPasswordNeededError: evt.sender.command_status = { "next": enter_password, "login_as": login_as if login_as != evt.sender else None, "action": "Login (password entry)", } return await evt.reply( "Your account has two-factor authentication. Please send your password here." ) else: timeout = TextMessageEventContent(body="Login timed out", msgtype=MessageType.TEXT) timeout.set_edit(qr_event_id) return await evt.az.intent.send_message(evt.room_id, timeout) return await _finish_sign_in(evt, user, login_as=login_as)
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
async def handle_signal_message(self, sender: 'p.Puppet', message: MessageData) -> None: if (sender.uuid, message.timestamp) in self._msgts_dedup: self.log.debug( f"Ignoring message {message.timestamp} by {sender.uuid}" " as it was already handled (message.timestamp in dedup queue)" ) return old_message = await DBMessage.get_by_signal_id(sender.uuid, message.timestamp, self.chat_id, self.receiver) if old_message is not None: self.log.debug( f"Ignoring message {message.timestamp} by {sender.uuid}" " as it was already handled (message.id found in database)") return self.log.debug( f"Started handling message {message.timestamp} by {sender.uuid}") self.log.trace(f"Message content: {message}") self._msgts_dedup.appendleft((sender.uuid, message.timestamp)) intent = sender.intent_for(self) await intent.set_typing(self.mxid, False) event_id = None reply_to = await self._find_quote_event_id(message.quote) for attachment in message.all_attachments: content = await self._handle_signal_attachment(intent, attachment) if content: if reply_to and not message.body: # If there's no text, set the first image as the reply content.set_reply(reply_to) reply_to = None event_id = await self._send_message( intent, content, timestamp=message.timestamp) if message.body: content = TextMessageEventContent(msgtype=MessageType.TEXT, body=message.body) if reply_to: content.set_reply(reply_to) event_id = await self._send_message(intent, content, timestamp=message.timestamp) if event_id: msg = DBMessage(mxid=event_id, mx_room=self.mxid, sender=sender.uuid, timestamp=message.timestamp, signal_chat_id=self.chat_id, signal_receiver=self.receiver) await msg.insert() await self._send_delivery_receipt(event_id) self.log.debug( f"Handled Signal message {message.timestamp} -> {event_id}") else: self.log.debug(f"Didn't get event ID for {message.timestamp}")
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
def send_markdown(self, room_id: RoomID, markdown: str, msgtype: MessageType = MessageType.TEXT, relates_to: Optional[RelatesTo] = None, **kwargs) -> Awaitable[EventID]: content = TextMessageEventContent(msgtype=msgtype, format=Format.HTML) content.body, content.formatted_body = parse_markdown(markdown) if relates_to: content.relates_to = relates_to return self.send_message(room_id, content, **kwargs)
async def _add_facebook_reply(self, content: TextMessageEventContent, reply: str) -> None: if reply: message = DBMessage.get_by_fbid(reply, self.fb_receiver) if message: evt = await self.main_intent.get_event(message.mx_room, message.mxid) if evt: if isinstance(evt.content, TextMessageEventContent): evt.content.trim_reply_fallback() content.set_reply(evt)
async def send_bridge_notice(self, text: str, edit: Optional[EventID] = None ) -> Optional[EventID]: event_id = None try: content = TextMessageEventContent(msgtype=MessageType.NOTICE, body=text) if edit: content.set_edit(edit) event_id = await self.az.intent.send_message(await self.get_notice_room(), content) except Exception: self.log.warning("Failed to send bridge notice '%s'", text, exc_info=True) return edit or event_id
async def send_bridge_notice(self, text: str, edit: Optional[EventID] = None, important: bool = False) -> Optional[EventID]: event_id = None try: self.log.debug("Sending bridge notice: %s", text) content = TextMessageEventContent(body=text, msgtype=(MessageType.TEXT if important else MessageType.NOTICE)) if edit: content.set_edit(edit) # This is locked to prevent notices going out in the wrong order async with self._notice_send_lock: event_id = await self.az.intent.send_message(await self.get_notice_room(), content) except Exception: self.log.warning("Failed to send bridge notice", exc_info=True) return edit or event_id
async def edit_handler(self, evt: MessageEvent, stmt: SedStatement, original_sed: HistoricalSed ) -> None: orig_evt = await self.client.get_event(evt.room_id, original_sed.seds_event) replaced = self._exec(stmt, orig_evt.content.body) content = TextMessageEventContent( msgtype=MessageType.NOTICE, body=replaced, format=Format.HTML, formatted_body=self.highlight_edits(replaced, orig_evt.content.body, stmt.highlight_edits), relates_to=RelatesTo(rel_type=RelationType.REPLACE, event_id=original_sed.output_event)) if orig_evt.content.msgtype == MessageType.EMOTE: displayname = await self._get_displayname(orig_evt.room_id, orig_evt.sender) content.body = f"* {displayname} {content.body}" content.formatted_body = f"* {escape(displayname)} {content.formatted_body}" await self.client.send_message(evt.room_id, content)
async def _handle_matrix_message(self, sender: 'u.User', content: MessageEventContent, event_id: EventID) -> None: if not content.body or not content.msgtype: self.log.debug( f"Ignoring message {event_id} in {self.mxid} without body or msgtype" ) return logged_in = not await sender.needs_relaybot(self) client = sender.client if logged_in else self.bot.client sender_id = sender.tgid if logged_in else self.bot.tgid space = ( self.tgid if self.peer_type == "channel" # Channels have their own ID space else (sender.tgid if logged_in else self.bot.tgid)) reply_to = formatter.matrix_reply_to_telegram(content, space, room_id=self.mxid) media = (MessageType.STICKER, MessageType.IMAGE, MessageType.FILE, MessageType.AUDIO, MessageType.VIDEO) if content.msgtype == MessageType.NOTICE: bridge_notices = self.get_config("bridge_notices.default") excepted = sender.mxid in self.get_config( "bridge_notices.exceptions") if not bridge_notices and not excepted: return if content.msgtype in (MessageType.TEXT, MessageType.EMOTE, MessageType.NOTICE): await self._pre_process_matrix_message(sender, not logged_in, content) await self._handle_matrix_text(sender_id, event_id, space, client, content, reply_to) elif content.msgtype == MessageType.LOCATION: await self._pre_process_matrix_message(sender, not logged_in, content) await self._handle_matrix_location(sender_id, event_id, space, client, content, reply_to) elif content.msgtype in media: content["net.maunium.telegram.internal.filename"] = content.body try: caption_content: MessageEventContent = sender.command_status[ "caption"] reply_to = reply_to or formatter.matrix_reply_to_telegram( caption_content, space, room_id=self.mxid) sender.command_status = None except (KeyError, TypeError): caption_content = None if logged_in else TextMessageEventContent( body=content.body) if caption_content: caption_content.msgtype = content.msgtype await self._pre_process_matrix_message(sender, not logged_in, caption_content) await self._handle_matrix_file(sender_id, event_id, space, client, content, reply_to, caption_content) else: self.log.trace("Unhandled Matrix event: %s", content)
async def _send_xkcd(self, room_id: RoomID, xkcd: XKCDInfo) -> None: info = await self._get_media_info(xkcd.img) if self.config["inline"]: content = TextMessageEventContent( msgtype=MessageType.TEXT, format=Format.HTML, external_url=f"https://xkcd.com/{xkcd.num}", body=f"{xkcd.num}: **{xkcd.title}**\n" f"{xkcd.img}\n{xkcd.alt}", formatted_body=f"{xkcd.num}: <strong>{xkcd.safe_title}</strong><br/>" f"<img src='{info.mxc_uri}' title='{xkcd.alt}'/>") content["license"] = "CC-BY-NC-2.5" content["license_url"] = "https://xkcd.com/license.html" await self.client.send_message(room_id, content) else: await self.client.send_text(room_id, text=f"{xkcd.num}: **{xkcd.title}**", html=f"{xkcd.num}: <strong>{xkcd.safe_title}</strong>") content = MediaMessageEventContent(url=info.mxc_uri, body=info.file_name, msgtype=MessageType.IMAGE, external_url=f"https://xkcd.com/{xkcd.num}", info=ImageInfo( mimetype=info.mime_type, size=info.size, width=info.width, height=info.height, ),) content["license"] = "CC-BY-NC-2.5" content["license_url"] = "https://xkcd.com/license.html" await self.client.send_message(room_id, content) await self.client.send_text(room_id, text=xkcd.alt)
async def handle_telegram_poll(self, source: 'AbstractUser', intent: IntentAPI, evt: Message, relates_to: RelatesTo) -> EventID: poll: Poll = evt.media.poll poll_id = self._encode_msgid(source, evt) _n = 0 def n() -> int: nonlocal _n _n += 1 return _n text_answers = "\n".join(f"{n()}. {answer.text}" for answer in poll.answers) html_answers = "\n".join(f"<li>{answer.text}</li>" for answer in poll.answers) content = TextMessageEventContent( msgtype=MessageType.TEXT, format=Format.HTML, body=f"Poll: {poll.question}\n{text_answers}\n" f"Vote with !tg vote {poll_id} <choice number>", formatted_body=f"<strong>Poll</strong>: {poll.question}<br/>\n" f"<ol>{html_answers}</ol>\n" f"Vote with <code>!tg vote {poll_id} <choice number></code>", relates_to=relates_to, external_url=self._get_external_url(evt)) await intent.set_typing(self.mxid, is_typing=False) return await self._send_message(intent, content, timestamp=evt.date)
async def respond( self, content: str | MessageEventContent, event_type: EventType = EventType.ROOM_MESSAGE, markdown: bool = True, allow_html: bool = False, reply: bool | str = False, edits: EventID | MessageEvent | None = None, ) -> EventID: if isinstance(content, str): content = TextMessageEventContent(msgtype=MessageType.NOTICE, body=content) if allow_html or markdown: content.format = Format.HTML content.body, content.formatted_body = await parse_formatted( content.body, render_markdown=markdown, allow_html=allow_html ) if edits: content.set_edit(edits) elif reply: if reply != "force" and self.disable_reply: content.body = f"{self.sender}: {content.body}" fmt_body = content.formatted_body or escape(content.body).replace("\n", "<br>") content.formatted_body = ( f'<a href="https://matrix.to/#/{self.sender}">' f"{self.sender}" f"</a>: {fmt_body}" ) else: content.set_reply(self) return await self.client.send_message_event(self.room_id, event_type, content)
async def handle_hangouts_message(self, source: 'u.User', sender: 'p.Puppet', event: ChatMessageEvent) -> None: async with self.optional_send_lock(sender.gid): if event.id_ in self._dedup: return self._dedup.appendleft(event.id_) if not self.mxid: mxid = await self.create_matrix_room(source) if not mxid: # Failed to create return if not await self._bridge_own_message_pm(source, sender, f"message {event.id_}"): return intent = sender.intent_for(self) self.log.debug("Handling hangouts message %s", event.id_) event_id = None if event.attachments: self.log.debug("Processing attachments.") self.log.trace("Attachments: %s", event.attachments) event_id = await self.process_hangouts_attachments(event, intent) # Just to fallback to text if something else hasn't worked. if not event_id: content = TextMessageEventContent(msgtype=MessageType.TEXT, body=event.text) event_id = await self._send_message(intent, content, timestamp=event.timestamp) DBMessage(mxid=event_id, mx_room=self.mxid, gid=event.id_, receiver=self.receiver, index=0, date=event.timestamp).insert() self.log.debug("Handled Hangouts message %s -> %s", event.id_, event_id) await self._send_delivery_receipt(event_id)