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 _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 _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 _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 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 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
def send_markdown(self, room_id: RoomID, markdown: str, msgtype: MessageType = MessageType.TEXT, edits: Optional[Union[EventID, MessageEvent]] = None, 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: if edits: raise ValueError( "Can't use edits and relates_to at the same time.") content.relates_to = relates_to elif edits: content.set_edit(edits) return self.send_message(room_id, content, **kwargs)
async def send_markdown( self, room_id: RoomID, markdown: str, *, allow_html: bool = False, msgtype: MessageType = MessageType.TEXT, edits: EventID | MessageEvent | None = None, relates_to: RelatesTo | None = None, **kwargs, ) -> EventID: content = TextMessageEventContent(msgtype=msgtype, format=Format.HTML) content.body, content.formatted_body = await parse_formatted( markdown, allow_html=allow_html ) if relates_to: if edits: raise ValueError("Can't use edits and relates_to at the same time.") content.relates_to = relates_to elif edits: content.set_edit(edits) return await self.send_message(room_id, content, **kwargs)
async def _handle_encrypted_wait( self, evt: EncryptedEvent, err: SessionNotFound, wait: int ) -> None: self.log.debug( f"Couldn't find session {err.session_id} trying to decrypt {evt.event_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." ) asyncio.create_task( self.e2ee.crypto.request_room_key( evt.room_id, evt.content.sender_key, evt.content.session_id, from_devices={evt.sender: [evt.content.device_id]}, ) ) try: event_id = await self.az.intent.send_notice(evt.room_id, msg) except IntentError: self.log.debug("IntentError while sending encryption error", exc_info=True) self.log.error( "Got IntentError while trying to send encryption error message. " "This likely means the bridge bot is not in the room, which can " "happen if you force-enable e2ee on the homeserver without enabling " "it by default on the bridge (bridge -> encryption -> default)." ) return got_keys = await self.e2ee.crypto.wait_for_session( evt.room_id, err.sender_key, err.session_id, timeout=wait ) if got_keys: self.log.debug( f"Got session {err.session_id} after waiting more, " f"trying to decrypt {evt.event_id} again" ) try: decrypted = await self.e2ee.decrypt(evt, wait_session_timeout=0) except DecryptionError as e: self.send_decrypted_checkpoint(evt, e, True, retry_num=1) self.log.warning(f"Failed to decrypt {evt.event_id}: {e}") self.log.trace("%s decryption traceback:", evt.event_id, exc_info=True) msg = f"\u26a0 Your message was not bridged: {e}" else: self.send_decrypted_checkpoint(decrypted, retry_num=1) await self.az.intent.redact(evt.room_id, event_id) await self.int_handle_event(decrypted, send_bridge_checkpoint=False) return else: error_message = f"Didn't get {err.session_id}, giving up on {evt.event_id}" self.log.warning(error_message) self.send_decrypted_checkpoint(evt, error_message, True, retry_num=1) 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)