Beispiel #1
0
 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
Beispiel #2
0
 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)
Beispiel #3
0
 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)
Beispiel #4
0
 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)
Beispiel #5
0
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)
Beispiel #6
0
 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
Beispiel #7
0
 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
Beispiel #8
0
 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)
Beispiel #9
0
 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)
Beispiel #10
0
 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)