示例#1
0
    async def _participants_to_power_levels(
            self, users: List[Union[TypeUser, TypeParticipant]],
            levels: PowerLevelStateEventContent) -> bool:
        bot_level = levels.get_user_level(self.main_intent.mxid)
        if bot_level < levels.get_event_level(EventType.ROOM_POWER_LEVELS):
            return False
        changed = False
        admin_power_level = min(75 if self.peer_type == "channel" else 50,
                                bot_level)
        if levels.get_event_level(
                EventType.ROOM_POWER_LEVELS) != admin_power_level:
            changed = True
            levels.events[EventType.ROOM_POWER_LEVELS] = admin_power_level

        for user in users:
            # The User objects we get from TelegramClient.get_participants have a custom
            # participant property
            participant = getattr(user, "participant", user)

            puppet = p.Puppet.get(TelegramID(participant.user_id))
            user = u.User.get_by_tgid(TelegramID(participant.user_id))
            new_level = self._get_level_from_participant(participant)

            if user:
                await user.register_portal(self)
                changed = self._participant_to_power_levels(
                    levels, user, new_level, bot_level) or changed

            if puppet:
                changed = self._participant_to_power_levels(
                    levels, puppet, new_level, bot_level) or changed
        return changed
示例#2
0
    def _participants_to_power_levels(
            self, participants: List[TypeParticipant],
            levels: PowerLevelStateEventContent) -> bool:
        bot_level = levels.get_user_level(self.main_intent.mxid)
        if bot_level < levels.get_event_level(EventType.ROOM_POWER_LEVELS):
            return False
        changed = False
        admin_power_level = min(75 if self.peer_type == "channel" else 50,
                                bot_level)
        if levels.get_event_level(
                EventType.ROOM_POWER_LEVELS) != admin_power_level:
            changed = True
            levels.events[EventType.ROOM_POWER_LEVELS] = admin_power_level

        for participant in participants:
            puppet = p.Puppet.get(TelegramID(participant.user_id))
            user = u.User.get_by_tgid(TelegramID(participant.user_id))
            new_level = self._get_level_from_participant(participant)

            if user:
                user.register_portal(self)
                changed = self._participant_to_power_levels(
                    levels, user, new_level, bot_level) or changed

            if puppet:
                changed = self._participant_to_power_levels(
                    levels, puppet, new_level, bot_level) or changed
        return changed
示例#3
0
async def participants_to_power_levels(
    portal: po.Portal,
    users: list[TypeUser | TypeChatParticipant | TypeChannelParticipant],
    levels: PowerLevelContent,
) -> bool:
    bot_level = levels.get_user_level(portal.main_intent.mxid)
    if bot_level < levels.get_event_level(EventType.ROOM_POWER_LEVELS):
        return False
    changed = False
    admin_power_level = min(75 if portal.peer_type == "channel" else 50,
                            bot_level)
    if levels.get_event_level(
            EventType.ROOM_POWER_LEVELS) != admin_power_level:
        changed = True
        levels.events[EventType.ROOM_POWER_LEVELS] = admin_power_level

    for user in users:
        # The User objects we get from TelegramClient.get_participants have a custom
        # participant property
        participant = getattr(user, "participant", user)

        puppet = await pu.Puppet.get_by_tgid(TelegramID(participant.user_id))
        user = await u.User.get_by_tgid(TelegramID(participant.user_id))
        new_level = _get_level_from_participant(portal.az.bot_mxid,
                                                participant, levels)

        if user:
            await user.register_portal(portal)
            changed = _participant_to_power_levels(levels, user, new_level,
                                                   bot_level) or changed

        if puppet:
            changed = _participant_to_power_levels(levels, puppet, new_level,
                                                   bot_level) or changed
    return changed
示例#4
0
 def set_power_level(self, room_id: RoomID, user_id: UserID,
                     level: int) -> None:
     if not room_id:
         raise ValueError("room_id is empty")
     elif not user_id:
         raise ValueError("user_id is empty")
     room_state = self._get_room_state(room_id)
     power_levels = room_state.power_levels
     if not power_levels:
         power_levels = PowerLevelStateEventContent()
     power_levels.users[user_id] = level or 0
     room_state.edit(power_levels=power_levels)
示例#5
0
    async def _create_matrix_room(
            self,
            source: 'u.User',
            info: Optional[HangoutsChat] = None) -> RoomID:
        if self.mxid:
            await self._update_matrix_room(source, info)
            return self.mxid

        info = await self.update_info(source=source, info=info)
        self.log.debug("Creating Matrix room")
        name: Optional[str] = None
        power_levels = PowerLevelStateEventContent()
        if self.is_direct:
            users = [
                user for user in info.users if user.id_.gaia_id != source.gid
            ]
            self.other_user_id = users[0].id_.gaia_id
            puppet = p.Puppet.get_by_gid(self.other_user_id)
            await puppet.update_info(source=source,
                                     info=info.get_user(self.other_user_id))
            power_levels.users[source.mxid] = 50
        else:
            name = self.name
        power_levels.users[self.main_intent.mxid] = 100
        initial_state = [{
            "type": EventType.ROOM_POWER_LEVELS.serialize(),
            "content": power_levels.serialize(),
        }]
        if config["appservice.community_id"]:
            initial_state.append({
                "type": "m.room.related_groups",
                "content": {
                    "groups": [config["appservice.community_id"]]
                },
            })
        self.mxid = await self.main_intent.create_room(
            name=name,
            is_direct=self.is_direct,
            invitees=[source.mxid],
            initial_state=initial_state)
        self.save()
        self.log.debug(f"Matrix room created: {self.mxid}")
        if not self.mxid:
            raise Exception("Failed to create room: no mxid required")
        self.by_mxid[self.mxid] = self
        await self._update_participants(source, info)
        puppet = p.Puppet.get_by_custom_mxid(source.mxid)
        if puppet:
            await puppet.intent.ensure_joined(self.mxid)
        await source._community_helper.add_room(source._community_id,
                                                self.mxid)
        return self.mxid
示例#6
0
 async def get_power_levels(
         self, room_id: RoomID) -> PowerLevelStateEventContent | None:
     power_levels_json = await self.db.fetchval(
         "SELECT power_levels FROM mx_room_state WHERE room_id=$1", room_id)
     if power_levels_json is None:
         return None
     return PowerLevelStateEventContent.parse_json(power_levels_json)
示例#7
0
async def warn_missing_power(levels: PowerLevelStateEventContent, evt: CommandEvent) -> None:
    if levels.get_user_level(evt.az.bot_mxid) < levels.redact:
        await evt.reply(
            "Warning: The bot does not have privileges to redact messages on Matrix. "
            "Message deletions from Telegram will not be bridged unless you give "
            f"redaction permissions to [{evt.az.bot_mxid}](https://matrix.to/#/{evt.az.bot_mxid})"
        )
示例#8
0
 async def set_power_levels(self, room_id: RoomID,
                            content: PowerLevelStateEventContent) -> None:
     await self.db.execute(
         "INSERT INTO mx_room_state (room_id, power_levels) VALUES ($1, $2) "
         "ON CONFLICT (room_id) DO UPDATE SET power_levels=$2",
         room_id,
         content.json(),
     )
示例#9
0
 def _participant_to_power_levels(levels: PowerLevelStateEventContent,
                                  user: Union['u.User', p.Puppet],
                                  new_level: int, bot_level: int) -> bool:
     new_level = min(new_level, bot_level)
     user_level = levels.get_user_level(user.mxid)
     if user_level != new_level and user_level < bot_level:
         levels.users[user.mxid] = new_level
         return True
     return False
示例#10
0
def _participant_to_power_levels(
    levels: PowerLevelContent,
    user: u.User | pu.Puppet,
    new_level: int,
    bot_level: int,
) -> bool:
    new_level = min(new_level, bot_level)
    user_level = levels.get_user_level(user.mxid)
    if user_level != new_level and user_level < bot_level:
        levels.users[user.mxid] = new_level
        return True
    return False
示例#11
0
def _get_level_from_participant(
    bot_mxid: UserID,
    participant: TypeUser | TypeChatParticipant | TypeChannelParticipant,
    levels: PowerLevelContent,
) -> int:
    # TODO use the power level requirements to get better precision in channels
    if isinstance(participant,
                  (ChatParticipantAdmin, ChannelParticipantAdmin)):
        return levels.state_default or 50
    elif isinstance(participant,
                    (ChatParticipantCreator, ChannelParticipantCreator)):
        return levels.get_user_level(bot_mxid) - 5
    return levels.users_default or 0
示例#12
0
 async def get_power_levels(
         self,
         room_id: RoomID,
         ignore_cache: bool = False) -> PowerLevelStateEventContent:
     await self.ensure_joined(room_id)
     if not ignore_cache:
         levels = await self.state_store.get_power_levels(room_id)
         if levels:
             return levels
     try:
         levels = await self.get_state_event(room_id,
                                             EventType.ROOM_POWER_LEVELS)
     except MNotFound:
         levels = PowerLevelStateEventContent()
     await self.state_store.set_power_levels(room_id, levels)
     return levels
示例#13
0
 def deserialize(self, data: Dict[str, Any]) -> None:
     self.members = {
         room_id: {
             user_id: Member.deserialize(member)
             for user_id, member in members.items()
         }
         for room_id, members in data["members"].items()
     }
     self.full_member_list = data["full_member_list"]
     self.power_levels = {
         room_id: PowerLevelStateEventContent.deserialize(content)
         for room_id, content in data["power_levels"].items()
     }
     self.encryption = {
         room_id: (RoomEncryptionStateEventContent.deserialize(content)
                   if content is not None else None)
         for room_id, content in data["encryption"].items()
     }
示例#14
0
    def deserialize(self, data: SerializedStateStore) -> None:
        """
        Parse a previously serialized dict into this state store.

        Args:
            data: A dict returned by :meth:`serialize`.
        """
        self.members = {
            room_id: {
                user_id: Member.deserialize(member)
                for user_id, member in members.items()
            }
            for room_id, members in data["members"].items()
        }
        self.full_member_list = data["full_member_list"]
        self.power_levels = {
            room_id: PowerLevelStateEventContent.deserialize(content)
            for room_id, content in data["power_levels"].items()
        }
        self.encryption = {
            room_id: (RoomEncryptionStateEventContent.deserialize(content)
                      if content is not None else None)
            for room_id, content in data["encryption"].items()
        }
示例#15
0
 def process_result_value(self, value: Dict,
                          dialect) -> Optional[PowerLevelStateEventContent]:
     if value is not None:
         return PowerLevelStateEventContent.deserialize(json.loads(value))
     return None
示例#16
0
def get_base_power_levels(portal: po.Portal,
                          levels: PowerLevelContent = None,
                          entity: TypeChat = None) -> PowerLevelContent:
    levels = levels or PowerLevelContent()
    if portal.peer_type == "user":
        overrides = portal.config["bridge.initial_power_level_overrides.user"]
        levels.ban = overrides.get("ban", 100)
        levels.kick = overrides.get("kick", 100)
        levels.invite = overrides.get("invite", 100)
        levels.redact = overrides.get("redact", 0)
        levels.events[EventType.ROOM_NAME] = 0
        levels.events[EventType.ROOM_AVATAR] = 0
        levels.events[EventType.ROOM_TOPIC] = 0
        levels.state_default = overrides.get("state_default", 0)
        levels.users_default = overrides.get("users_default", 0)
        levels.events_default = overrides.get("events_default", 0)
    else:
        overrides = portal.config["bridge.initial_power_level_overrides.group"]
        dbr = entity.default_banned_rights
        if not dbr:
            portal.log.debug(f"default_banned_rights is None in {entity}")
            dbr = ChatBannedRights(
                invite_users=True,
                change_info=True,
                pin_messages=True,
                send_stickers=False,
                send_messages=False,
                until_date=None,
            )
        levels.ban = overrides.get("ban", 50)
        levels.kick = overrides.get("kick", 50)
        levels.redact = overrides.get("redact", 50)
        levels.invite = overrides.get("invite", 50 if dbr.invite_users else 0)
        levels.events[
            EventType.ROOM_ENCRYPTION] = 50 if portal.matrix.e2ee else 99
        levels.events[EventType.ROOM_TOMBSTONE] = 99
        levels.events[EventType.ROOM_NAME] = 50 if dbr.change_info else 0
        levels.events[EventType.ROOM_AVATAR] = 50 if dbr.change_info else 0
        levels.events[EventType.ROOM_TOPIC] = 50 if dbr.change_info else 0
        levels.events[
            EventType.ROOM_PINNED_EVENTS] = 50 if dbr.pin_messages else 0
        levels.events[EventType.ROOM_POWER_LEVELS] = 75
        levels.events[EventType.ROOM_HISTORY_VISIBILITY] = 75
        levels.events[
            EventType.
            STICKER] = 50 if dbr.send_stickers else levels.events_default
        levels.state_default = overrides.get("state_default", 50)
        levels.users_default = overrides.get("users_default", 0)
        levels.events_default = overrides.get(
            "events_default",
            50 if (portal.peer_type == "channel" and not entity.megagroup
                   or entity.default_banned_rights.send_messages) else 0,
        )
    for evt_type, value in overrides.get("events", {}).items():
        levels.events[EventType.find(evt_type)] = value
    levels.users = overrides.get("users", {})
    if portal.main_intent.mxid not in levels.users:
        levels.users[portal.main_intent.mxid] = 100
    return levels
示例#17
0
 def process_bind_param(self, value: PowerLevelStateEventContent,
                        dialect) -> Optional[Dict]:
     if value is not None:
         return json.dumps(value.serialize())
     return None
示例#18
0
 def _get_base_power_levels(
         self,
         levels: PowerLevelStateEventContent = None,
         entity: TypeChat = None) -> PowerLevelStateEventContent:
     levels = levels or PowerLevelStateEventContent()
     if self.peer_type == "user":
         levels.ban = 100
         levels.kick = 100
         levels.invite = 100
         levels.redact = 0
         levels.events[EventType.ROOM_NAME] = 0
         levels.events[EventType.ROOM_AVATAR] = 0
         levels.events[EventType.ROOM_TOPIC] = 0
         levels.state_default = 0
         levels.users_default = 0
         levels.events_default = 0
     else:
         dbr = entity.default_banned_rights
         if not dbr:
             self.log.debug(f"default_banned_rights is None in {entity}")
             dbr = ChatBannedRights(invite_users=True,
                                    change_info=True,
                                    pin_messages=True,
                                    send_stickers=False,
                                    send_messages=False,
                                    until_date=None)
         levels.ban = 99
         levels.kick = 50
         levels.redact = 50
         levels.invite = 50 if dbr.invite_users else 0
         levels.events[EventType.ROOM_ENCRYPTED] = 99
         levels.events[EventType.ROOM_TOMBSTONE] = 99
         levels.events[EventType.ROOM_NAME] = 50 if dbr.change_info else 0
         levels.events[EventType.ROOM_AVATAR] = 50 if dbr.change_info else 0
         levels.events[EventType.ROOM_TOPIC] = 50 if dbr.change_info else 0
         levels.events[
             EventType.ROOM_PINNED_EVENTS] = 50 if dbr.pin_messages else 0
         levels.events[EventType.ROOM_POWER_LEVELS] = 75
         levels.events[EventType.ROOM_HISTORY_VISIBILITY] = 75
         levels.state_default = 50
         levels.users_default = 0
         levels.events_default = (
             50 if (self.peer_type == "channel" and not entity.megagroup
                    or entity.default_banned_rights.send_messages) else 0)
         levels.events[
             EventType.
             STICKER] = 50 if dbr.send_stickers else levels.events_default
     levels.users[self.main_intent.mxid] = 100
     return levels
示例#19
0
    async def _create_matrix_room(
            self,
            source: 'u.User',
            info: Optional[HangoutsChat] = None) -> RoomID:
        if self.mxid:
            await self._update_matrix_room(source, info)
            return self.mxid

        info = await self.update_info(source=source, info=info)
        self.log.debug("Creating Matrix room")
        name: Optional[str] = None
        power_levels = PowerLevelStateEventContent()
        invites = [source.mxid]
        if self.is_direct:
            users = [
                user for user in info.users if user.id_.gaia_id != source.gid
            ]
            self.other_user_id = users[0].id_.gaia_id
            puppet = p.Puppet.get_by_gid(self.other_user_id)
            await puppet.update_info(source=source,
                                     info=info.get_user(
                                         self.other_user_scoped_id))
            power_levels.users[source.mxid] = 50
        power_levels.users[self.main_intent.mxid] = 100
        initial_state = [
            {
                "type": EventType.ROOM_POWER_LEVELS.serialize(),
                "content": power_levels.serialize(),
            },
            {
                "type": str(StateBridge),
                "state_key": self.bridge_info_state_key,
                "content": self.bridge_info,
            },
            {
                # TODO remove this once https://github.com/matrix-org/matrix-doc/pull/2346 is in spec
                "type": str(StateHalfShotBridge),
                "state_key": self.bridge_info_state_key,
                "content": self.bridge_info,
            }
        ]
        if config["appservice.community_id"]:
            initial_state.append({
                "type": "m.room.related_groups",
                "content": {
                    "groups": [config["appservice.community_id"]]
                },
            })
        if config["bridge.encryption.default"] and self.matrix.e2ee:
            self.encrypted = True
            initial_state.append({
                "type": "m.room.encryption",
                "content": {
                    "algorithm": "m.megolm.v1.aes-sha2"
                },
            })
            if self.is_direct:
                invites.append(self.az.bot_mxid)
        if self.encrypted or not self.is_direct:
            name = self.name
        # We lock backfill lock here so any messages that come between the room being created
        # and the initial backfill finishing wouldn't be bridged before the backfill messages.
        with self.backfill_lock:
            self.mxid = await self.main_intent.create_room(
                name=name,
                is_direct=self.is_direct,
                invitees=invites,
                initial_state=initial_state)
            if not self.mxid:
                raise Exception("Failed to create room: no mxid returned")
            if self.encrypted and self.matrix.e2ee:
                members = [self.main_intent.mxid]
                if self.is_direct:
                    # This isn't very accurate, but let's do it anyway
                    members += [source.mxid]
                    try:
                        await self.az.intent.join_room_by_id(self.mxid)
                        members += [self.az.intent.mxid]
                    except Exception:
                        self.log.warning(
                            f"Failed to add bridge bot to new private chat {self.mxid}"
                        )
                await self.matrix.e2ee.add_room(self.mxid,
                                                members=members,
                                                encrypted=True)
            self.save()
            self.log.debug(f"Matrix room created: {self.mxid}")
            self.by_mxid[self.mxid] = self
            await self._update_participants(source, info)
            puppet = p.Puppet.get_by_custom_mxid(source.mxid)
            if puppet:
                await puppet.intent.ensure_joined(self.mxid)
            await source._community_helper.add_room(source._community_id,
                                                    self.mxid)

            await self.backfill(source, is_initial=True)

        return self.mxid