async def _apply_ban(self, action: proto.Action) -> None: assert action.WhichOneof('details') == 'ban' guild = self.__get_guild(action) if guild is None: return assert action.HasField('user_id') user = fake.FakeSnowflake(id=action.user_id) if action.ban.type != proto.BanMember.UNBAN: await guild.ban(user, reason=_get_reason(action), delete_message_days=action.ban.delete_message_days) if action.ban.type != proto.BanMember.BAN: await guild.unban(user, reason=_get_reason(action))
def invert_action(action: proto.Action) -> proto.Action: new_action = proto.Action() new_action.CopyFrom(action) if action.HasField('reason'): new_action.reason = 'Undo: ' + action.reason new_action.ClearField('duration') try: INVERT_MAPPING[action.WhichOneof('details')](new_action) except KeyError: raise ValueError('Provided action cannot be inverted.') return new_action
def __get_member(self, action: proto.Action) -> discord.Member: assert action.HasField('user_id') # FIXME: This will not work once the bot is larger than a single process. guild = self.__get_guild(action) if guild is None: return None return utils.get_member_async(guild, action.user_id)
async def _apply_direct_message(self, action: proto.Action) -> None: assert action.WhichOneof('details') == 'direct_message' user = await self.__get_user(action) if user is None or not action.direct_message.content: return try: content = format.ellipsize(action.direct_message.content) await user.send(content=content) except (discord.Forbidden, discord.NotFound): # Don't cause a ruckus if the user has the bot blocked pass
async def _apply_command(self, action: proto.Action) -> None: assert action.WhichOneof('details') == 'command' channel = self.bot.get_channel(action.command.channel_id) try: guild = channel.guild except AttributeError: guild = self.bot.get_guild(action.guild_id) if action.HasField('user_id'): user = (await self.__get_member(action)) if guild is not None \ else (await self.__get_user(action)) else: user = (guild.me if guild is not None else self.bot.user) ctx = await self.bot.get_automated_context( content=action.command.command, author=user, channel=channel, guild=guild) async with ctx: await self.bot.invoke(ctx)
async def execute(self, action: proto.Action) -> None: action_type = action.WhichOneof('details') try: await getattr(self, "_apply_" + action_type)(action) if not action.HasField('duration'): return # Schedule an undo duration = timedelta(seconds=action.duration) self.bot.action_manager.schedule(datetime.utcnow() + duration, invert_action(action)) except AttributeError: raise ValueError(f'Action type not supported: {action_type}') except discord.NotFound: # If the guild or the target is not found, silence the error pass except discord.Forbidden: # TODO(james7132): Properly report missing permissions # If the guild or the target is not found, silence the error pass self.bot.logger.exception('Error while executing action:') except Exception: self.bot.logger.exception('Error while executing action:')
async def _apply_escalate(self, action: proto.Action) -> None: assert action.WhichOneof('details') == 'escalate' guild = self.__get_guild(action) if guild is None: return history = escalation_history.UserEscalationHistory( self.bot, fake.FakeSnowflake(id=action.user_id), guild) # TODO(james7132): Log this if action.escalate.amount == 0: return await history.apply_diff(guild.me, action.reason, action.escalate.amount, execute=action.escalate.amount > 0)
async def _apply_deafen(self, action: proto.Action) -> None: assert action.WhichOneof('details') == 'deafen' member = await self.__get_member(action) if member is not None: deafen = { proto.StatusType.APPLY: True, proto.StatusType.UNAPPLY: False, # TODO(james7132): Implement this properly proto.StatusType.TOGGLE: False, }[action.deafen.type] await member.edit(deafen=deafen, reason=_get_reason(action))
async def _apply_mute(self, action: proto.Action) -> None: assert action.WhichOneof('details') == 'mute' member = await self.__get_member(action) if member is not None: mute = { proto.StatusType.APPLY: True, proto.StatusType.UNAPPLY: False, # TODO(james7132): Implement this properly proto.MuteMember.TOGGLE: False, }[action.mute.type] await member.edit(mute=mute, reason=_get_reason(action))
async def _apply_change_role(self, action: proto.Action) -> None: assert action.WhichOneof('details') == 'change_role' member = await self.__get_member(action) if member is None: return roles = (member.guild.get_role(id) for id in action.change_role.role_ids) roles = [r for r in roles if r is not None] if action.change_role.type == proto.StatusType.APPLY: await member.add_roles(*roles, reason=_get_reason(action)) elif action.change_role.type == proto.StatusType.UNAPPLY: await member.remove_roles(*roles, reason=_get_reason(action)) elif action.change_role.type == proto.StatusType.TOGGLE: role_ids = set(member._roles) add_roles = [r for r in roles if r.id not in role_ids] rm_roles = [r for r in roles if r.id in role_ids] await asyncio.gather( member.add_roles(*add_roles, reason=_get_reason(action)), member.remove(*rm_roles, reason=_get_reason(action)))
async def _apply_kick(self, action: proto.Action) -> None: assert action.WhichOneof('details') == 'kick' member = await self.__get_member(action) if member is not None: await member.kick(reason=_get_reason(action))
def _get_reason(action: proto.Action) -> str: if action.HasField('reason'): return action.reason return None
def __get_user(self, action: proto.Action) -> discord.User: assert action.HasField('user_id') return utils.get_user_async(self.bot, action.user_id)
def __get_guild(self, action: proto.Action) -> discord.Guild: assert action.HasField('guild_id') return self.bot.get_guild(action.guild_id)