示例#1
0
    def test_reply_with_cmdprefix(self, command_processor: CommandProcessor,
                                  mocker: MockFixture) -> None:
        mocker.patch("mautrix_telegram.user.config", self.config)

        evt = CommandEvent(
            processor=command_processor,
            room_id=RoomID("#mock_room:example.org"),
            event_id=EventID("$H45H:example.org"),
            sender=u.User(UserID("@sender:example.org")),
            command="help",
            args=[],
            is_management=False,
            is_portal=False,
        )

        mock_az = command_processor.az

        evt.reply("$cmdprefix+sp ....$cmdprefix+sp...$cmdprefix $cmdprefix",
                  allow_html=False,
                  render_markdown=False)

        mock_az.intent.send_notice.assert_called_with(
            RoomID("#mock_room:example.org"),
            "tg ....tg+sp...tg tg",
            html=None,
        )
示例#2
0
    async def test_handle_delegated_handler(
            self, command_processor: CommandProcessor,
            boolean2: Tuple[bool, bool], mocker: MockFixture) -> None:
        mocker.patch('mautrix_telegram.user.config', self.config)
        mocker.patch('mautrix.bridge.commands.handler.command_handlers', {
            "help": AsyncMock(),
            "unknown-command": AsyncMock()
        })

        sender = u.User(UserID("@sender:example.org"))
        sender.command_status = {"foo": AsyncMock(), "next": AsyncMock()}

        result = await command_processor.handle(
            room_id=RoomID("#mock_room:example.org"),
            event_id=EventID("$H45H:example.org"),
            sender=sender,  # u.User
            command="foo",
            args=[],
            is_management=boolean2[0],
            is_portal=boolean2[1])

        assert result is None
        command_handlers = mautrix.bridge.commands.handler.command_handlers
        command_handlers["help"].mock.assert_not_called()  # type: ignore
        command_handlers["unknown-command"].mock.assert_not_called(
        )  # type: ignore
        sender.command_status["foo"].mock.assert_not_called()  # type: ignore
        sender.command_status["next"].mock.assert_called_once()  # type: ignore
示例#3
0
    async def handle_telegram_edit(self, source: 'AbstractUser', sender: p.Puppet, evt: Message
                                   ) -> None:
        if not self.mxid:
            return
        elif hasattr(evt, "media") and isinstance(evt.media, MessageMediaGame):
            self.log.debug("Ignoring game message edit event")
            return

        async with self.send_lock(sender.tgid if sender else None, required=False):
            tg_space = self.tgid if self.peer_type == "channel" else source.tgid

            temporary_identifier = EventID(
                f"${random.randint(1000000000000, 9999999999999)}TGBRIDGEDITEMP")
            duplicate_found = self.dedup.check(evt, (temporary_identifier, tg_space),
                                               force_hash=True)
            if duplicate_found:
                mxid, other_tg_space = duplicate_found
                if tg_space != other_tg_space:
                    prev_edit_msg = DBMessage.get_one_by_tgid(TelegramID(evt.id), tg_space, -1)
                    if not prev_edit_msg:
                        return
                    DBMessage(mxid=mxid, mx_room=self.mxid, tg_space=tg_space,
                              tgid=TelegramID(evt.id), edit_index=prev_edit_msg.edit_index + 1
                              ).insert()
                return

        content = await formatter.telegram_to_matrix(evt, source, self.main_intent,
                                                     no_reply_fallback=True)
        editing_msg = DBMessage.get_one_by_tgid(TelegramID(evt.id), tg_space)
        if not editing_msg:
            self.log.info(f"Didn't find edited message {evt.id}@{tg_space} (src {source.tgid}) "
                          "in database.")
            return

        content.msgtype = (MessageType.NOTICE if (sender and sender.is_bot
                                                  and self.get_config("bot_messages_as_notices"))
                           else MessageType.TEXT)
        content.external_url = self._get_external_url(evt)
        content.set_edit(editing_msg.mxid)

        # TODO remove this stuff once mautrix-python generates m.new_content
        new_content = content.serialize()
        del new_content["m.relates_to"]
        content["m.new_content"] = new_content
        content.body = f"Edit: {content.body}"
        content.format = Format.HTML
        content.formatted_body = (f"<a href=\"https://matrix.to/#/{editing_msg.mx_room}/"
                                  f"{editing_msg.mxid}\">Edit</a>: "
                                  f"{content.formatted_body or escape_html(content.body)}")

        intent = sender.intent_for(self) if sender else self.main_intent
        await intent.set_typing(self.mxid, is_typing=False)
        event_id = await intent.send_message(self.mxid, content)

        prev_edit_msg = DBMessage.get_one_by_tgid(TelegramID(evt.id), tg_space, -1) or editing_msg
        DBMessage(mxid=event_id, mx_room=self.mxid, tg_space=tg_space, tgid=TelegramID(evt.id),
                  edit_index=prev_edit_msg.edit_index + 1).insert()
        DBMessage.update_by_mxid(temporary_identifier, self.mxid, mxid=event_id)
示例#4
0
    async def test_permission_granted(
        self,
        is_management: bool,
        puppet_whitelisted: bool,
        matrix_puppet_whitelisted: bool,
        is_admin: bool,
        is_logged_in: bool,
        command_processor: CommandProcessor,
        boolean: bool,
        mocker: MockFixture,
    ) -> None:
        mocker.patch("mautrix_telegram.user.config", self.config)

        command = "testcmd"

        mock_handler = Mock()

        command_handler = CommandHandler(
            handler=mock_handler,
            needs_auth=False,
            needs_puppeting=False,
            needs_matrix_puppeting=False,
            needs_admin=False,
            management_only=False,
            name=command,
            help_text="No real command",
            help_args="mock mockmock",
            help_section=HelpSection("Mock Section", 42, ""),
        )

        sender = u.User(UserID("@sender:example.org"))
        sender.puppet_whitelisted = puppet_whitelisted
        sender.matrix_puppet_whitelisted = matrix_puppet_whitelisted
        sender.is_admin = is_admin
        mocker.patch.object(u.User, 'is_logged_in', return_value=is_logged_in)

        event = CommandEvent(
            processor=command_processor,
            room_id=RoomID("#mock_room:example.org"),
            event_id=EventID("$H45H:example.org"),
            sender=sender,
            command=command,
            args=[],
            content=Mock(),
            is_management=is_management,
            is_portal=boolean,
        )

        assert not await command_handler.get_permission_error(event)
        assert command_handler.has_permission(
            HelpCacheKey(is_management=is_management,
                         puppet_whitelisted=puppet_whitelisted,
                         matrix_puppet_whitelisted=matrix_puppet_whitelisted,
                         is_admin=is_admin,
                         is_logged_in=is_logged_in,
                         is_portal=boolean))
示例#5
0
 async def handle_room_pin(room_id: RoomID, sender_mxid: UserID,
                           new_events: Set[str], old_events: Set[str]) -> None:
     portal = po.Portal.get_by_mxid(room_id)
     sender = await u.User.get_by_mxid(sender_mxid).ensure_started()
     if await sender.has_full_access(allow_bot=True) and portal:
         events = new_events - old_events
         if len(events) > 0:
             # New event pinned, set that as pinned in Telegram.
             await portal.handle_matrix_pin(sender, EventID(events.pop()))
         elif len(new_events) == 0:
             # All pinned events removed, remove pinned event in Telegram.
             await portal.handle_matrix_pin(sender, None)
示例#6
0
    def test_reply(self, command_processor: CommandProcessor,
                   mocker: MockFixture) -> None:
        mocker.patch("mautrix_telegram.user.config", self.config)

        evt = CommandEvent(
            processor=command_processor,
            room_id=RoomID("#mock_room:example.org"),
            event_id=EventID("$H45H:example.org"),
            sender=u.User(UserID("@sender:example.org")),
            command="help",
            args=[],
            content=Mock(),
            is_management=True,
            is_portal=False,
        )

        mock_az = command_processor.az

        message = "**This** <i>was</i><br/><strong>all</strong>fun*!"

        # html, no markdown
        evt.reply(message, allow_html=True, render_markdown=False)
        mock_az.intent.send_notice.assert_called_with(
            RoomID("#mock_room:example.org"),
            "**This** <i>was</i><br/><strong>all</strong>fun*!",
            html="**This** <i>was</i><br/><strong>all</strong>fun*!\n",
        )

        # html, markdown (default)
        evt.reply(message, allow_html=True, render_markdown=True)
        mock_az.intent.send_notice.assert_called_with(
            RoomID("#mock_room:example.org"),
            "**This** <i>was</i><br/><strong>all</strong>fun*!",
            html=("<p><strong>This</strong> <i>was</i><br/>"
                  "<strong>all</strong>fun*!</p>\n"),
        )

        # no html, no markdown
        evt.reply(message, allow_html=False, render_markdown=False)
        mock_az.intent.send_notice.assert_called_with(
            RoomID("#mock_room:example.org"),
            "**This** <i>was</i><br/><strong>all</strong>fun*!",
            html=None,
        )

        # no html, markdown
        evt.reply(message, allow_html=False, render_markdown=True)
        mock_az.intent.send_notice.assert_called_with(
            RoomID("#mock_room:example.org"),
            "**This** <i>was</i><br/><strong>all</strong>fun*!",
            html="<p><strong>This</strong> &lt;i&gt;was&lt;/i&gt;&lt;br/&gt;"
            "&lt;strong&gt;all&lt;/strong&gt;fun*!</p>\n")
示例#7
0
    async def test_permissions_denied(
        self,
        needs_auth: bool,
        needs_puppeting: bool,
        needs_matrix_puppeting: bool,
        needs_admin: bool,
        management_only: bool,
        command_processor: CommandProcessor,
        boolean: bool,
        mocker: MockFixture,
    ) -> None:
        mocker.patch("mautrix_telegram.user.config", self.config)

        command = "testcmd"

        mock_handler = Mock()

        command_handler = CommandHandler(
            handler=mock_handler,
            needs_auth=needs_auth,
            needs_puppeting=needs_puppeting,
            needs_matrix_puppeting=needs_matrix_puppeting,
            needs_admin=needs_admin,
            management_only=management_only,
            name=command,
            help_text="No real command",
            help_args="mock mockmock",
            help_section=HelpSection("Mock Section", 42, ""),
        )

        sender = u.User(UserID("@sender:example.org"))
        sender.puppet_whitelisted = False
        sender.matrix_puppet_whitelisted = False
        sender.is_admin = False

        event = CommandEvent(
            processor=command_processor,
            room_id=RoomID("#mock_room:example.org"),
            event_id=EventID("$H45H:example.org"),
            sender=sender,
            command=command,
            args=[],
            content=Mock(),
            is_management=False,
            is_portal=boolean,
        )

        assert await command_handler.get_permission_error(event)
        assert not command_handler.has_permission(
            HelpCacheKey(False, False, False, False, False, False))
示例#8
0
    async def handle_telegram_edit(self, source: 'AbstractUser', sender: p.Puppet, evt: Message
                                   ) -> None:
        if not self.mxid:
            self.log.trace("Ignoring edit to %d as chat has no Matrix room", evt.id)
            return
        elif hasattr(evt, "media") and isinstance(evt.media, MessageMediaGame):
            self.log.debug("Ignoring game message edit event")
            return

        async with self.send_lock(sender.tgid if sender else None, required=False):
            tg_space = self.tgid if self.peer_type == "channel" else source.tgid

            temporary_identifier = EventID(
                f"${random.randint(1000000000000, 9999999999999)}TGBRIDGEDITEMP")
            duplicate_found = self.dedup.check(evt, (temporary_identifier, tg_space),
                                               force_hash=True)
            if duplicate_found:
                mxid, other_tg_space = duplicate_found
                if tg_space != other_tg_space:
                    prev_edit_msg = DBMessage.get_one_by_tgid(TelegramID(evt.id), tg_space, -1)
                    if not prev_edit_msg:
                        return
                    DBMessage(mxid=mxid, mx_room=self.mxid, tg_space=tg_space,
                              tgid=TelegramID(evt.id), edit_index=prev_edit_msg.edit_index + 1
                              ).insert()
                return

        content = await formatter.telegram_to_matrix(evt, source, self.main_intent,
                                                     no_reply_fallback=True)
        editing_msg = DBMessage.get_one_by_tgid(TelegramID(evt.id), tg_space)
        if not editing_msg:
            self.log.info(f"Didn't find edited message {evt.id}@{tg_space} (src {source.tgid}) "
                          "in database.")
            return

        content.msgtype = (MessageType.NOTICE if (sender and sender.is_bot
                                                  and self.get_config("bot_messages_as_notices"))
                           else MessageType.TEXT)
        content.external_url = self._get_external_url(evt)
        content.set_edit(editing_msg.mxid)

        intent = sender.intent_for(self) if sender else self.main_intent
        await intent.set_typing(self.mxid, is_typing=False)
        event_id = await self._send_message(intent, content)

        prev_edit_msg = DBMessage.get_one_by_tgid(TelegramID(evt.id), tg_space, -1) or editing_msg
        DBMessage(mxid=event_id, mx_room=self.mxid, tg_space=tg_space, tgid=TelegramID(evt.id),
                  edit_index=prev_edit_msg.edit_index + 1).insert()
        DBMessage.update_by_mxid(temporary_identifier, self.mxid, mxid=event_id)
示例#9
0
    def update(self,
               event: TypeMessage,
               mxid: DedupMXID = None,
               expected_mxid: Optional[DedupMXID] = None,
               force_hash: bool = False) -> Optional[DedupMXID]:
        evt_hash = self._hash_event(
            event) if self._always_force_hash or force_hash else event.id
        try:
            found_mxid = self._dedup_mxid[evt_hash]
        except KeyError:
            return EventID("None"), TelegramID(0)

        if found_mxid != expected_mxid:
            return found_mxid
        self._dedup_mxid[evt_hash] = mxid
        return None
示例#10
0
 async def get_state_event(
         self,
         room_id: RoomID,
         event_type: EventType,
         state_key: Optional[str] = None) -> StateEventContent:
     event = await super().get_state_event(room_id, event_type, state_key)
     if self.state_store:
         fake_event = StateEvent(type=event_type,
                                 room_id=room_id,
                                 event_id=EventID(""),
                                 sender=UserID(""),
                                 state_key=state_key,
                                 timestamp=0,
                                 content=event)
         await self.state_store.update_state(fake_event)
     return event
示例#11
0
    async def handle_telegram_message(self, source: 'AbstractUser',
                                      sender: p.Puppet, evt: Message) -> None:
        if not self.mxid:
            await self.create_matrix_room(source,
                                          invites=[source.mxid],
                                          update_if_exists=False)

        if (self.peer_type == "user" and sender.tgid == self.tg_receiver
                and not sender.is_real_user
                and not self.az.state_store.is_joined(self.mxid, sender.mxid)):
            self.log.debug(
                f"Ignoring private chat message {evt.id}@{source.tgid} as receiver does"
                " not have matrix puppeting and their default puppet isn't in the room"
            )
            return

        async with self.send_lock(sender.tgid if sender else None,
                                  required=False):
            tg_space = self.tgid if self.peer_type == "channel" else source.tgid

            temporary_identifier = EventID(
                f"${random.randint(1000000000000, 9999999999999)}TGBRIDGETEMP")
            duplicate_found = self.dedup.check(
                evt, (temporary_identifier, tg_space))
            if duplicate_found:
                mxid, other_tg_space = duplicate_found
                self.log.debug(
                    f"Ignoring message {evt.id}@{tg_space} (src {source.tgid}) "
                    f"as it was already handled (in space {other_tg_space})")
                if tg_space != other_tg_space:
                    DBMessage(tgid=TelegramID(evt.id),
                              mx_room=self.mxid,
                              mxid=mxid,
                              tg_space=tg_space,
                              edit_index=0).insert()
                return

        if self.backfilling or (self.dedup.pre_db_check
                                and self.peer_type == "channel"):
            msg = DBMessage.get_one_by_tgid(TelegramID(evt.id), tg_space)
            if msg:
                self.log.debug(
                    f"Ignoring message {evt.id} (src {source.tgid}) as it was already"
                    f"handled into {msg.mxid}. This duplicate was catched in the db "
                    "check. If you get this message often, consider increasing"
                    "bridge.deduplication.cache_queue_length in the config.")
                return

        if sender and not sender.displayname:
            self.log.debug(
                f"Telegram user {sender.tgid} sent a message, but doesn't have a "
                "displayname, updating info...")
            entity = await source.client.get_entity(PeerUser(sender.tgid))
            await sender.update_info(source, entity)

        allowed_media = (MessageMediaPhoto, MessageMediaDocument,
                         MessageMediaGeo, MessageMediaGame, MessageMediaPoll,
                         MessageMediaUnsupported)
        media = evt.media if hasattr(evt, "media") and isinstance(
            evt.media, allowed_media) else None
        if sender:
            intent = sender.intent_for(self)
            if self.backfilling and intent != sender.default_mxid_intent:
                intent = sender.default_mxid_intent
                self.backfill_leave.add(intent)
        else:
            intent = self.main_intent
        if not media and evt.message:
            is_bot = sender.is_bot if sender else False
            event_id = await self.handle_telegram_text(source, intent, is_bot,
                                                       evt)
        elif media:
            event_id = await {
                MessageMediaPhoto: self.handle_telegram_photo,
                MessageMediaDocument: self.handle_telegram_document,
                MessageMediaGeo: self.handle_telegram_location,
                MessageMediaPoll: self.handle_telegram_poll,
                MessageMediaUnsupported: self.handle_telegram_unsupported,
                MessageMediaGame: self.handle_telegram_game,
            }[type(media)
              ](source,
                intent,
                evt,
                relates_to=formatter.telegram_reply_to_matrix(evt, source))
        else:
            self.log.debug("Unhandled Telegram message: %s", evt)
            return

        if not event_id:
            return

        prev_id = self.dedup.update(evt, (event_id, tg_space),
                                    (temporary_identifier, tg_space))
        if prev_id:
            self.log.debug(
                f"Sent message {evt.id}@{tg_space} to Matrix as {event_id}. "
                f"Temporary dedup identifier was {temporary_identifier}, "
                f"but dedup map contained {prev_id[1]} instead! -- "
                "This was probably a race condition caused by Telegram sending updates"
                "to other clients before responding to the sender. I'll just redact "
                "the likely duplicate message now.")
            await intent.redact(self.mxid, event_id)
            return

        self.log.debug("Handled Telegram message: %s", evt)
        try:
            DBMessage(tgid=TelegramID(evt.id),
                      mx_room=self.mxid,
                      mxid=event_id,
                      tg_space=tg_space,
                      edit_index=0).insert()
            DBMessage.update_by_mxid(temporary_identifier,
                                     self.mxid,
                                     mxid=event_id)
        except IntegrityError as e:
            self.log.exception(
                f"{e.__class__.__name__} while saving message mapping. "
                "This might mean that an update was handled after it left the "
                "dedup cache queue. You can try enabling bridge.deduplication."
                "pre_db_check in the config.")
            await intent.redact(self.mxid, event_id)