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
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
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
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)
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
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)
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})" )
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(), )
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
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
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
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
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() }
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() }
def process_result_value(self, value: Dict, dialect) -> Optional[PowerLevelStateEventContent]: if value is not None: return PowerLevelStateEventContent.deserialize(json.loads(value)) return None
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
def process_bind_param(self, value: PowerLevelStateEventContent, dialect) -> Optional[Dict]: if value is not None: return json.dumps(value.serialize()) return None
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
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