Example #1
0
    async def mute(self,
                   ctx,
                   user: discord.Member,
                   duration: Optional[Duration],
                   *,
                   reason: Reason = None):
        """Mutes a user using the guild's configured mute role.

        Sends the user a DM and logs this action to the guild's modlog if configured to do so.
        """
        reason, note, audit_reason = reason or (None, None, None)
        audit_reason = audit_reason or Reason.format_reason(ctx)
        dt = exact_timedelta(duration) if duration else "permanent"

        try:
            muted_user, has_role = await MutedUser().convert(ctx, str(user.id))
        except commands.BadArgument:
            muted_user, has_role = None, None

        # if already muted, edit the duration
        if muted_user and has_role:
            # first make sure they have an infraction. it's hard to edit an infraction that doesn't exist.
            if await modlog.has_active_infraction(ctx.guild.id, user.id,
                                                  'mute'):
                i, edited, old = await self._do_mute_duration_edit(
                    guild=ctx.guild,
                    user=user,
                    new_duration=duration,
                    edited_by=ctx.author)
                old = exact_timedelta(old) if old else 'permanent'
                await ctx.send(f"{TICK_YELLOW} User is already muted (#{i})" +
                               (f", changed duration instead ({old} -> {dt})."
                                if edited else '.'))

            # just kidding, we couldn't find an infraction. let's see if they want to create one.
            # note: we ask for a confirmation so things don't break when two infractions go through simultaneously
            else:
                await ctx.confirm_action(
                    f"{TICK_YELLOW} This user appears to have this guild's mute role, "
                    f"but does not have any active mute infractions. "
                    f"Would you like to create an infraction? (y/n)")
                await LogEvent('mute', ctx.guild, user, ctx.author, reason,
                               note, duration).dispatch()
                await ctx.send(OK_HAND)

        # otherwise, mute the user like normal
        else:
            delivered = await self._do_mute(guild=ctx.guild,
                                            user=user,
                                            mod=ctx.author,
                                            reason=reason,
                                            note=note,
                                            audit_reason=audit_reason,
                                            duration=duration)
            await ctx.send(
                f"{OK_HAND} Muted **{user}** ({dt}). {notified(delivered)}")
Example #2
0
async def edit_infraction_and_message(infraction, **kwargs):
    if 'edited_by' in kwargs:
        edit = f"(edited by {kwargs.pop('edited_by')})"
    else:
        edit = ''

    k1, k2 = kwargs.copy(), kwargs.copy()
    if 'duration' in kwargs:
        duration = kwargs.pop('duration')
        k1['ends_at'] = infraction.created_at + duration if duration else None
        d = exact_timedelta(duration) if duration else 'permanent'
        k2['duration'] = f"{d} {edit}"
    if 'reason' in kwargs:
        r = kwargs.pop('reason')
        k1['reason'] = k2['reason'] = f"{r} {edit}"
    if 'note' in kwargs:
        n = kwargs.pop('note')
        k1['note'] = k2['note'] = f"{n} {edit}"

    i = await db.edit_record(infraction, **k1)
    try:
        m = await edit_log_message(infraction, **k2)
    except discord.HTTPException as e:
        raise OuranosCommandError(
            f"I edited the infraction, but was unable to edit the modlog message "
            f"({e.text.lower().capitalize()}).")
    return i, m
Example #3
0
async def log_forceban(guild, user, mod, reason, note, duration):
    infraction = await new_infraction(guild.id, user.id, mod.id, 'ban', reason,
                                      note, duration, True)
    duration = exact_timedelta(duration) if duration else None
    content = format_log_message(EMOJI_BAN, 'USER FORCEBANNED',
                                 infraction.infraction_id, duration, user, mod,
                                 reason, note)
    message = await new_log_message(guild, content)
    await db.edit_record(infraction, message_id=message.id)
Example #4
0
async def log_mute(guild, user, mod, reason, note, duration):
    infraction = await new_infraction(guild.id, user.id, mod.id, 'mute',
                                      reason, note, duration, True)
    duration = exact_timedelta(duration) if duration else None
    content = format_log_message(EMOJI_MUTE, 'MEMBER MUTED',
                                 infraction.infraction_id, duration, user, mod,
                                 reason, note)
    message = await new_log_message(guild, content)
    await db.edit_record(infraction, message_id=message.id)
Example #5
0
async def log_mass_mute(guild, users, mod, reason, note, duration):
    infraction_ids = await get_case_id(guild.id, count=len(users))
    user_ids = [user.id for user in users]

    await new_infractions_bulk(guild.id, user_ids, mod.id, 'mute', reason,
                               note, duration, True, infraction_ids)

    duration = exact_timedelta(duration) if duration else None
    content = format_mass_action_log_message(
        EMOJI_MUTE,
        'USERS MASS-MUTED', (infraction_ids[0], infraction_ids[-1]), duration,
        len(users), mod, reason, note)
    message = await new_log_message(guild, content)

    await db.Infraction.filter(infraction_id__in=infraction_ids
                               ).update(message_id=message.id)
Example #6
0
    async def history(self, ctx, *, user: UserID):
        """Returns useful info on a user's recent infractions."""
        history = await self._get_history(ctx.guild.id, user.id)
        now = time.time()
        s = ""

        h_by_type = {}
        for _type in [
                'note', 'warn', 'mute', 'unmute', 'kick', 'ban', 'unban'
        ]:
            h_by_type.update({i: _type for i in getattr(history, _type)})
        infraction_ids = sorted(h_by_type.keys(), reverse=True)
        infractions = [
            await modlog.get_infraction(ctx.guild.id, i)
            for i in infraction_ids
        ]

        if infractions:
            _recent = infractions[:5]
            s += f"Recent infractions for {user} (showing {len(_recent)}/{len(infractions)}):```\n"
            for infraction in _recent:
                mod = await self.bot.get_or_fetch_member(
                    ctx.guild, infraction.mod_id) or infraction.mod_id
                dt = approximate_timedelta(now - infraction.created_at)
                dt_tot = exact_timedelta(
                    infraction.ends_at -
                    infraction.created_at) if infraction.ends_at else None
                dt_rem = approximate_timedelta(
                    infraction.ends_at - now) if infraction.ends_at else None
                active = 'active ' if infraction.active else ''
                s += f"#{infraction.infraction_id}: {active}{infraction.type} by {mod} ({dt} ago)\n"
                if dt_tot:
                    rem = f" ({dt_rem} remaining)" if infraction.active else ''
                    s += f"\tduration: {dt_tot}{rem}\n"
                if infraction.reason:
                    s += f"\treason: {infraction.reason}\n"
            s += "```"

        else:
            s += f"No infractions for {user}."

        await ctx.send(s)
Example #7
0
async def edit_infractions_and_messages_bulk(infractions,
                                             linked=True,
                                             **kwargs):
    """Edits a set of infractions. All must be linked to the same log message."""
    inf = infractions[0]

    for i in infractions:
        if i.type != inf.type:
            raise OuranosCommandError(
                "Infraction type mismatch during bulk edit.")
        if linked:
            if i.message_id != inf.message_id:
                raise OuranosCommandError(
                    "Infraction message_id mismatch during bulk edit.")
            elif i.created_at != inf.created_at:
                raise OuranosCommandError(
                    "Infraction created_at mismatch during bulk edit.")

    if 'edited_by' in kwargs:
        edit = f"(edited by {kwargs.pop('edited_by')})"
    else:
        edit = ''

    k1, k2 = kwargs.copy(), kwargs.copy()
    if 'duration' in kwargs:
        duration = kwargs.pop('duration')
        k1['ends_at'] = inf.created_at + duration if duration else None
        d = exact_timedelta(duration) if duration else 'permanent'
        k2['duration'] = f"{d} {edit}"
    if 'reason' in kwargs:
        r = kwargs.pop('reason')
        k1['reason'] = k2['reason'] = f"{r} {edit}"
    if 'note' in kwargs:
        n = kwargs.pop('note')
        k1['note'] = k2['note'] = f"{n} {edit}"

    i = await db.edit_records_bulk(infractions, **k1)

    if linked:
        try:
            m = await edit_log_message(inf, **k2)
        except discord.HTTPException as e:
            raise OuranosCommandError(
                f"I edited the infractions, but was unable to edit the modlog message "
                f"({e.text.lower()}).")
        return i, m

    # edit a batch of messages
    else:
        count = 0
        skip = set()
        for inf in infractions:
            if inf.infraction_id in skip:
                continue

            # make sure we only edit each message once
            if inf.bulk_infraction_id_range:
                start, end = inf.bulk_infraction_id_range
                for _i_id in range(start, end + 1):
                    skip.add(_i_id)

            # actually edit now
            try:
                await edit_log_message(inf, **k2)
                count += 1
            except discord.HTTPException as e:
                raise OuranosCommandError(
                    f"I edited the infractions, but was unable to edit the modlog message "
                    f"for #{inf.infraction_id} ({e.text.lower()}).")
        return i, count
Example #8
0
    async def ban(self,
                  ctx,
                  user: UserID,
                  duration: Optional[Duration],
                  *,
                  reason: Reason = None):
        """Bans a user from the guild.

        This will also work if the user is not in the server.

        Sends the user a DM and logs this action to the guild's modlog if configured to do so.
        """
        reason, note, audit_reason = reason or (None, None, None)
        audit_reason = audit_reason or Reason.format_reason(ctx)
        dt = exact_timedelta(duration) if duration else "permanent"

        try:
            banned_user, banned_in_guild = await BannedUser().convert(
                ctx, str(user.id))
        except commands.BadArgument:
            banned_user, banned_in_guild = None, None

        # if already banned, edit the duration
        if banned_user and banned_in_guild:
            # first make sure they have an infraction. it's hard to edit an infraction that doesn't exist.
            if await modlog.has_active_infraction(ctx.guild.id, user.id,
                                                  'ban'):
                i, edited, old = await self._do_ban_duration_edit(
                    guild=ctx.guild,
                    user=user,
                    new_duration=duration,
                    edited_by=ctx.author)
                old = exact_timedelta(old) if old else 'permanent'
                await ctx.send(f"{TICK_YELLOW} User is already banned (#{i})" +
                               (f", changed duration instead ({old} -> {dt})."
                                if edited else '.'))

            # just kidding, we couldn't find an infraction. let's see if they want to create one.
            # note: we ask for a confirmation so things don't break when two infractions go through simultaneously
            else:
                await ctx.confirm_action(
                    f"{TICK_YELLOW} This user appears to be banned from this guild, "
                    f"but does not have any active ban infractions. "
                    f"Would you like to create an infraction? (y/n)")
                await LogEvent('ban', ctx.guild, user, ctx.author, reason,
                               note, duration).dispatch()
                await ctx.send(OK_HAND)

        # we didn't seem to find anything weird, so let's just ban!
        else:
            user, delivered, force = await self._do_ban(
                guild=ctx.guild,
                user=user,
                mod=ctx.author,
                reason=reason,
                note=note,
                audit_reason=audit_reason,
                duration=duration)
            banned = 'Forcebanned' if force else 'Banned'
            await ctx.send(
                f"{HAMMER} {banned} **{user}** ({dt}). {notified(delivered)}")
Example #9
0
    async def _do_mass_ban(self,
                           ctx,
                           users,
                           mod,
                           reason,
                           note,
                           audit_reason,
                           duration=None):
        """Bans a set of users and dispatches the event to the modlog."""
        guild = ctx.guild
        users = set(users)
        total = len(users)

        if total <= 1:
            raise ModerationError("Not enough users to ban.")

        await ctx.confirm_action(
            f"{TICK_YELLOW} Are you sure you would like to ban {total} users? (y/n)"
        )

        # filter out already-banned users
        ban_list = set(b.user.id for b in await guild.bans())
        bannable_users = [u for u in users if u.id not in ban_list]
        already_banned = total - len(bannable_users)

        # check to make sure we can actually do this
        for user in users:
            member = await self.bot.get_or_fetch_member(guild, user.id)

            if not guild.me.guild_permissions.ban_members:
                raise BotMissingPermission('Ban Members')
            if member:
                if not guild.me.top_role > member.top_role:
                    raise BotRoleHierarchyError
                if await is_server_mod(member):
                    raise ModActionOnMod

        m = await ctx.send("Banning...")

        # do the actual bans now
        success = []
        not_found = 0
        for user in bannable_users:
            try:
                await guild.ban(user,
                                reason=audit_reason,
                                delete_message_days=0)
                success.append(user)
            except discord.NotFound:
                not_found += 1

        # dispatch the modlog event
        if success:
            await MassActionLogEvent('ban', guild, success, mod, reason, note,
                                     duration).dispatch()

        # format response message
        def _s(i):
            return "s" if i != 1 else ""

        content = f"{ZAP} Banned {len(success)}/{total} users"
        if success:
            dt = exact_timedelta(duration) if duration else 'permanent'
            content += f' ({dt}).'
        else:
            content += '.'
        extra_lines = [
            f"{already_banned} user{_s(already_banned)} already banned"
            if already_banned else "",
            f"{not_found} user{_s(not_found)} not found" if not_found else ""
        ]
        extra = ", ".join(e for e in extra_lines if e)
        if extra:
            content += " *" + extra + ".*"

        await m.edit(content=content)
Example #10
0
    async def _do_mass_mute(self,
                            ctx,
                            users,
                            mod,
                            reason,
                            note,
                            audit_reason,
                            duration=None):
        """Mutes a set of users and dispatches the event to the modlog."""
        guild = ctx.guild
        users = set(users)
        total = len(users)
        config = await db.get_config(guild)
        role = guild.get_role(config.mute_role_id if config else 0)

        if total <= 1:
            raise ModerationError("Not enough users to mute.")

        await ctx.confirm_action(
            f"{TICK_YELLOW} Are you sure you would like to mute {total} users? (y/n)"
        )

        # check to make sure we can actually do this
        not_in_guild = 0
        already_muted = 0
        mutable_members = []
        for user in users:
            member = await self.bot.get_or_fetch_member(guild, user.id)
            if not member:
                not_in_guild += 1
                continue
            elif role in member.roles:
                already_muted += 1
                continue
            else:
                mutable_members.append(member)

            if not role:
                raise NotConfigured('mute_role')
            if not guild.me.guild_permissions.manage_roles:
                raise BotMissingPermission('Manage Roles')
            if not guild.me.top_role > role:
                raise BotRoleHierarchyError
            if await is_server_mod(member):
                raise ModActionOnMod

        m = await ctx.send("Muting...")

        # do the actual mutes now
        success = []
        for member in mutable_members:
            await member.add_roles(role, reason=audit_reason)
            success.append(member)

        # dispatch the modlog event
        if success:
            await MassActionLogEvent('mute', guild, success, mod, reason, note,
                                     duration).dispatch()

        # format response message
        def _s(i):
            return "s" if i != 1 else ""

        content = f"{OK_HAND} Muted {len(success)}/{total} users"
        if success:
            dt = exact_timedelta(duration) if duration else 'permanent'
            content += f' ({dt}).'
        else:
            content += '.'
        extra_lines = [
            f"{already_muted} user{_s(already_muted)} already muted"
            if already_muted else "",
            f"{not_in_guild} user{_s(not_in_guild)} not found"
            if not_in_guild else ""
        ]
        extra = ", ".join(e for e in extra_lines if e)
        if extra:
            content += " *" + extra + ".*"

        await m.edit(content=content)