Beispiel #1
0
    async def role_show(self, ctx):
        """ Shows all self-assignable roles. """

        await self.check_channel(ctx)

        assignable_roles = sorted(self.bot.sql.roles.get_assignable_roles(
            ctx.guild),
                                  key=lambda r: r.name)
        if not assignable_roles:
            prefix = self.bot.prefix(ctx.guild)
            embed = discord.Embed(colour=discord.Colour.dark_purple())
            embed.set_author(name="No self-assignable roles")
            embed.description = (
                f"Moderators can use the `{prefix}role joinable/unjoinable` "
                "commands to change this list!")
            await ctx.send(embed=embed)
            return

        embed = discord.Embed(colour=discord.Colour.dark_teal())
        embed.set_author(name="Self-assignable roles")

        descr = StringBuilder(sep=", ")
        for role in assignable_roles:
            descr.write(role.mention)
        embed.description = str(descr)
        await ctx.send(embed=embed)
Beispiel #2
0
    async def message_violator():
        logger.debug("Sending message to user who violated the filter")
        response = StringBuilder()
        response.write(
            f"The message you posted in {message.channel.mention} contains or links to a file "
        )
        response.writeln(
            f"that violates a {filter_type.value} content filter: `{hashsum.hex()}`."
        )
        response.writeln(f"Your original link: <{url}>")

        if reupload:
            response.writeln(
                "The filtered file has been attached to this message.")

        if severity >= FilterType.JAIL.level:
            if roles.jail is not None:
                response.writeln(
                    "This offense is serious enough to warrant immediate revocation of posting privileges.\n"
                    f"As such, you have been assigned the `{roles.jail.name}` role, until a moderator clears you."
                )

        kwargs = {}
        if reupload:
            response.writeln(
                "In case the link is broken, the file has been attached below:"
            )
            filename = os.path.basename(urlparse(url).path)
            kwargs["file"] = discord.File(binio.getbuffer(), filename=filename)

        kwargs["content"] = str(response)
        await message.author.send(**kwargs)
Beispiel #3
0
    async def role_unjoinable(self, ctx, *roles: RoleConv):
        """ Allows a moderator to remove roles from the self-assignable group. """

        logger.info(
            "Removing joinable roles for guild '%s' (%d): [%s]",
            ctx.guild.name,
            ctx.guild.id,
            ", ".join(role.name for role in roles),
        )

        if not roles:
            raise CommandFailed()

        # Remove roles from database
        with self.bot.sql.transaction():
            for role in roles:
                self.bot.sql.roles.remove_assignable_role(ctx.guild, role)

        # Send response
        embed = discord.Embed(colour=discord.Colour.dark_teal())
        embed.set_author(name="Made roles not joinable")
        descr = StringBuilder(sep=", ")
        for role in roles:
            descr.write(role.mention)
        embed.description = str(descr)
        await ctx.send(embed=embed)

        # Send journal event
        content = f"Roles were set as not joinable: {self.str_roles(roles)}"
        self.journal.send("joinable/remove",
                          ctx.guild,
                          content,
                          icon="role",
                          roles=roles)
Beispiel #4
0
    async def channel_set(self, ctx, *channels: TextChannelConv):
        """ Overwrites the channel(s) in the restricted role channel list to exactly this. """

        logger.info(
            "Setting channels to be used for role commands in guild '%s' (%d): [%s]",
            ctx.guild.name,
            ctx.guild.id,
            ", ".join(channel.name for channel in channels),
        )

        if not channels:
            raise CommandFailed()

        # Write new channel list to database
        with self.bot.sql.transaction():
            self.bot.sql.roles.remove_all_role_command_channels(ctx.guild)
            for channel in channels:
                self.bot.sql.roles.add_role_command_channel(ctx.guild, channel)

        # Send response
        embed = discord.Embed(colour=discord.Colour.dark_teal())
        embed.set_author(name="Set channels to be used for adding roles")
        descr = StringBuilder(sep=", ")
        for channel in channels:
            descr.write(channel.mention)
        embed.description = str(descr)
        await ctx.send(embed=embed)

        # Send journal event
        self.channel_journal(ctx.guild)
Beispiel #5
0
    async def pingable_show(self, ctx):
        """ Shows all channels where a role is pingable. """
        logger.info(
            "Displaying pingable channels and roles in guild '%s' (%d)",
            ctx.guild.name,
            ctx.guild.id,
        )

        # r[0] == current channel.
        channel_role = sorted(
            self.bot.sql.roles.get_pingable_role_channels(ctx.guild),
            key=lambda r: r[0].name,
        )

        if not channel_role:
            prefix = self.bot.prefix(ctx.guild)
            embed = discord.Embed(colour=discord.Colour.dark_purple())
            embed.set_author(name="No pingable roles in this guild")
            embed.description = (
                f"Moderators can use the `{prefix}role pingable/unpingable` "
                "commands to change this list!")
            await ctx.send(embed=embed)
            return

        embed = discord.Embed(colour=discord.Colour.dark_teal())
        embed.set_author(name="Pingable roles (channel, role)")

        descr = StringBuilder(sep="\n")
        for channel, role in channel_role:
            descr.write(f"{channel.mention}: {role.mention}")
        embed.description = str(descr)
        await ctx.send(embed=embed)
Beispiel #6
0
 def build_reason(ctx, action, minutes, reason, past=False):
     full_reason = StringBuilder(f"{action} by {user_discrim(ctx.author)}")
     if minutes:
         full_reason.write(
             f" {'for' if past else 'in'} {minutes} minute{plural(minutes)}"
         )
     if reason:
         full_reason.write(f" with reason: {reason}")
     return str(full_reason)
Beispiel #7
0
    async def role_joinable(self, ctx, *roles: RoleConv):
        """ Allows a moderator to add roles to the self-assignable group. """

        logger.info(
            "Adding joinable roles for guild '%s' (%d): [%s]",
            ctx.guild.name,
            ctx.guild.id,
            ", ".join(role.name for role in roles),
        )

        if not roles:
            raise CommandFailed()

        # Get special roles
        special_roles = self.bot.sql.settings.get_special_roles(ctx.guild)

        # Ensure none of the roles grant any permissions
        for role in roles:
            embed = permissions.elevated_role_embed(ctx.guild, role, "error")
            if embed is not None:
                raise ManualCheckFailure(embed=embed)

            for attr in ("member", "guest", "mute", "jail"):
                if role == getattr(special_roles, attr):
                    embed = discord.Embed(colour=discord.Colour.red())
                    embed.set_author(name="Cannot add role as assignable")
                    embed.description = (
                        f"{role.mention} cannot be self-assignable, "
                        f"it is already used as the **{attr}** role!")
                    raise ManualCheckFailure(embed=embed)

        # Get roles that are already assignable
        assignable_roles = self.bot.sql.roles.get_assignable_roles(ctx.guild)

        # Add roles to database
        with self.bot.sql.transaction():
            for role in roles:
                if role not in assignable_roles:
                    self.bot.sql.roles.add_assignable_role(ctx.guild, role)

        # Send response
        embed = discord.Embed(colour=discord.Colour.dark_teal())
        embed.set_author(name="Made roles joinable")
        descr = StringBuilder(sep=", ")
        for role in roles:
            descr.write(role.mention)
        embed.description = str(descr)
        await ctx.send(embed=embed)

        # Send journal event
        content = f"Roles were set as joinable: {self.str_roles(roles)}"
        self.journal.send("joinable/add",
                          ctx.guild,
                          content,
                          icon="role",
                          roles=roles)
Beispiel #8
0
    def uinfo_add_voice(embed, user):
        if getattr(user, "voice", None):
            mute = user.voice.mute or user.voice.self_mute
            deaf = user.voice.deaf or user.voice.self_deaf

            states = StringBuilder(sep=" ")
            if mute:
                states.write("muted")
            if deaf:
                states.write("deafened")

            state = str(states) if states else "active"
            embed.add_field(name="Voice", value=state)
Beispiel #9
0
    async def role_unpingable(self, ctx, role: RoleConv,
                              *channels: TextChannelConv):
        logger.info(
            "Making role '%s' not pingable in guild '%s' (%d), channel(s) [%s]",
            role.name,
            ctx.guild.name,
            ctx.guild.id,
            self.str_channels(channels),
        )

        if not channels:
            raise CommandFailed()

        # See role_pingable for an explanation
        channel_role = zip(
            *self.bot.sql.roles.get_pingable_role_channels(ctx.guild))
        pingable_channels = next(channel_role, set())

        exempt_channels = []

        with self.bot.sql.transaction():
            for channel in channels:
                if channel in pingable_channels:
                    self.bot.sql.roles.remove_pingable_role_channel(
                        ctx.guild, channel, role)
                else:
                    exempt_channels.append(channel)

        if exempt_channels:
            embed = discord.Embed(colour=discord.Colour.dark_grey())
            embed.set_author(
                name="Failed to make role unpingable in channels: ")
            descr = StringBuilder(sep=", ")
            for channel in exempt_channels:
                descr.write(channel.mention)
            embed.description = str(descr)
            await ctx.send(embed=embed)
            if set(exempt_channels) == set(channels):
                raise CommandFailed()

        # Send journal event
        content = f"Role was set as not pingable in channels: {self.str_channels(channels)}, except {self.str_channels(exempt_channels)}"
        self.journal.send(
            "pingable/remove",
            ctx.guild,
            content,
            icon="role",
            role=role,
            channels=channels,
        )
Beispiel #10
0
    def build_regex(text, groups):
        # Build similar character tree
        chars = {}
        pattern = StringBuilder()
        for group in groups:
            pattern.write("[")
            char = group["character"]
            pattern.write(re.escape(char))
            for homoglyph in group["homoglyphs"]:
                pattern.write(re.escape(homoglyph["c"]))
            pattern.write("]")
            chars[char] = str(pattern)
            pattern.clear()

        # Create pattern
        for char in text:
            pattern.write(chars.get(char, char))

        return str(pattern)
Beispiel #11
0
    async def channel_show(self, ctx):
        """ Lists all channels that are allowed to be used for role commands. """

        all_channels = self.bot.sql.roles.get_role_command_channels(ctx.guild)
        prefix = self.bot.prefix(ctx.guild)
        if all_channels:
            embed = discord.Embed(colour=discord.Colour.dark_teal())
            embed.set_author(name="Permitted channels")
            descr = StringBuilder(sep=", ")
            for channel in all_channels:
                descr.write(channel.mention)
            embed.description = str(descr)
        else:
            embed = discord.Embed(colour=discord.Colour.dark_purple())
            embed.set_author(name="All channels are permitted")
            embed.description = (
                f"There are no restricted role channels set, so `{prefix}role add/remove` commands "
                "can be used anywhere.")

        await ctx.send(embed=embed)
Beispiel #12
0
    async def reactions(self, ctx, *, message: MessageConv):
        """
        Displays all reactions on a message.
        """

        logger.info("Displaying reactions for message ID %d", message.id)

        if not message.reactions:
            embed = discord.Embed(colour=discord.Colour.dark_purple())
            embed.description = "This message has no reactions."
            await ctx.send(embed=embed)
            return

        descr = StringBuilder()
        for reaction in message.reactions:
            if descr:
                descr.writeln()

            descr.write(reaction.emoji)
            async for user in reaction.users():
                descr.write(f" {user.mention}")

                if len(descr) > 1800:
                    embed = discord.Embed(colour=discord.Colour.teal())
                    embed.description = str(descr)
                    await ctx.send(embed=embed)
                    descr.clear()
                    descr.write(reaction.emoji)

        if descr:
            embed = discord.Embed(colour=discord.Colour.teal())
            embed.description = str(descr)
            await ctx.send(embed=embed)
Beispiel #13
0
    async def tracker_blacklist_show(self, ctx):
        """ Shows all blacklist entries for this guild.  """

        blacklist = self.bot.sql.settings.get_tracking_blacklist(ctx.guild)

        if not blacklist.blacklisted_users and not blacklist.blacklisted_channels:
            prefix = self.bot.prefix(ctx.guild)
            embed = discord.Embed(colour=discord.Colour.dark_purple())
            embed.set_author(name="No blacklist entries")
            embed.description = (
                f"Moderators can use the `{prefix}trackerblacklist add/remove` "
                "commands to change this list!")
            await ctx.send(embed=embed)
            return

        embed = discord.Embed(colour=discord.Colour.dark_teal())
        embed.set_author(name="Blacklist entries")

        if blacklist.blacklisted_channels:
            channel_msg = StringBuilder(sep=", ")
            for channel_id in blacklist.blacklisted_channels:
                channel = discord.utils.get(ctx.guild.channels, id=channel_id)
                channel_msg.write(channel.mention)

            embed.add_field(name="Blacklisted channels", value=channel_msg)

        if blacklist.blacklisted_users:
            user_msg = StringBuilder(sep=", ")
            for user_id in blacklist.blacklisted_users:
                user = discord.utils.get(chain(ctx.guild.members,
                                               ctx.bot.users),
                                         id=user_id)
                user_msg.write(user.mention)

            embed.add_field(name="Blacklisted channels", value=user_msg)

        await ctx.send(embed=embed)
Beispiel #14
0
    async def channel_delete(self, ctx, *channels: TextChannelConv):
        """ Removes the channel(s) from the restricted role channel list. """

        logger.info(
            "Removing channels to be used for role commands in guild '%s' (%d): [%s]",
            ctx.guild.name,
            ctx.guild.id,
            ", ".join(channel.mention for channel in channels),
        )

        if not channels:
            raise CommandFailed()

        # Remove channels from database
        with self.bot.sql.transaction():
            for channel in channels:
                self.bot.sql.roles.remove_role_command_channel(
                    ctx.guild, channel)

        # Send response
        embed = discord.Embed(colour=discord.Colour.dark_teal())
        embed.set_author(name="Removed channels to be used for adding roles")

        all_channels = self.bot.sql.roles.get_role_command_channels(ctx.guild)
        descr = StringBuilder(sep=", ")
        for channel in channels:
            descr.write(channel.mention)
        embed.add_field(name="Removed", value=str(descr))

        descr.clear()
        for channel in all_channels:
            descr.write(channel.mention)
        embed.add_field(name="Remaining", value=str(descr) or "(none)")
        await ctx.send(embed=embed)

        # Send journal event
        self.channel_journal(ctx.guild)
Beispiel #15
0
    async def reapply_show(self, ctx):
        """
        Lists all roles that are reappliable.
        Reappliable roles, in addition to all punishment and self-assignable roles, are
        automatically reapplied when the member rejoins the guild.
        """

        reapply_roles = self.bot.sql.settings.get_reapply_roles(ctx.guild)
        special_roles = self.bot.sql.settings.get_special_roles(ctx.guild)

        embed = discord.Embed(colour=discord.Colour.dark_teal())
        descr = StringBuilder(sep=", ")
        has_roles = False

        # Manually set
        for role in sorted(reapply_roles, key=lambda r: r.name):
            descr.write(role.mention)
        if descr:
            embed.add_field(name="Manually designated", value=str(descr))
            has_roles = True
        else:
            embed.add_field(name="Manually designated", value="(none)")

        # Punishment roles
        descr.clear()
        if special_roles.mute_role is not None:
            descr.write(special_roles.mute_role.mention)
        if special_roles.jail_role is not None:
            descr.write(special_roles.jail_role.mention)
        if descr:
            embed.add_field(name="Punishment roles", value=str(descr))
            has_roles = True

        # Self-assignable roles
        if "SelfAssignableRoles" in self.bot.cogs:
            assignable_roles = self.bot.sql.roles.get_assignable_roles(
                ctx.guild)
            if assignable_roles:
                embed.add_field(
                    name="Self-assignable roles",
                    value=", ".join(role.mention
                                    for role in sorted(assignable_roles,
                                                       key=lambda r: r.name)),
                )
                has_roles = True

        # Send final embed
        if has_roles:
            embed.title = "\N{MILITARY MEDAL} Roles which are automatically reapplied"
        else:
            embed.colour = discord.Colour.dark_purple()

        await ctx.send(embed=embed)
Beispiel #16
0
def fancy_timedelta(delta):
    """
    Formats a fancy time difference.
    When given a datetime object, it calculates the difference from the present.
    """

    if isinstance(delta, datetime):
        delta = abs(datetime.now() - delta)

    result = StringBuilder(sep=" ")
    years, days = divmod(delta.days, 365)
    months, days = divmod(days, 30)
    weeks, days = divmod(days, 7)
    hours, seconds = divmod(delta.seconds, 3600)
    minutes, seconds = divmod(seconds, 60)
    seconds += delta.microseconds / 1e6
    seconds = round(seconds, 2)

    if years:
        result.write(f"{years}y")
    if months:
        result.write(f"{months}mo")
    if weeks:
        result.write(f"{weeks}w")
    if days:
        result.write(f"{days}d")
    if hours:
        result.write(f"{hours}h")
    if minutes:
        result.write(f"{minutes}m")
    if seconds:
        result.write(f"{seconds}s")

    return str(result)
Beispiel #17
0
    async def role_pingable(self,
                            ctx,
                            role: RoleConv,
                            *channels: TextChannelConv,
                            original=None):
        logger.info(
            "Making role '%s' pingable in guild '%s' (%d), channel(s) [%s]",
            role.name,
            ctx.guild.name,
            ctx.guild.id,
            self.str_channels(channels),
        )

        if not channels:
            raise CommandFailed()

        # self.bot.sql.roles.get_pingable_role_channels(ctx.guild) gets a set
        # of tuples (channel, role).
        # Zip converts it into a set of channels and a set of roles.
        channel_role = zip(
            *self.bot.sql.roles.get_pingable_role_channels(ctx.guild))
        # this makes a list of all channels and rows, currently it's a channel and row pair.
        # next gets the next item from the zip iterator which is the set of channels.
        # if the iterator is exhausted i.e there's no pingable channels, the default value set() will be used.
        pingable_channels = next(channel_role, set())

        # channels that were already in the database will not be added, user
        # will be informed.
        exempt_channels = []

        with self.bot.sql.transaction():
            for channel in channels:
                if channel not in pingable_channels:
                    self.bot.sql.roles.add_pingable_role_channel(
                        ctx.guild, channel, role, original)
                else:
                    exempt_channels.append(channel)

        if exempt_channels:
            embed = discord.Embed(colour=discord.Colour.dark_grey())
            embed.set_author(name="Failed to make role pingable in channels: ")
            descr = StringBuilder(sep=", ")
            for channel in exempt_channels:
                descr.write(channel.mention)
            embed.description = str(descr)
            await ctx.send(embed=embed)
            # Did not put the embed in CommandFailed.  All channels must fail
            # to be added for the entire command to 'fail', imo.
            if set(exempt_channels) == set(channels):
                raise CommandFailed()

        # Send journal event
        content = f"Role was set as pingable in channels: {self.str_channels(channels)}, except {self.str_channels(exempt_channels)}"
        self.journal.send(
            "pingable/add",
            ctx.guild,
            content,
            icon="role",
            role=role,
            channels=channels,
        )
Beispiel #18
0
    async def member_update(self, before, after):
        """ Handles update of member information. """

        changes = MemberChanges()
        timestamp = datetime.now()

        if before.avatar != after.avatar:
            logger.info(
                "Member '%s' (%d) has changed their profile picture (%s)",
                before.name,
                before.id,
                after.avatar,
            )
            changes.avatar_url = after.avatar_url

        if before.name != after.name:
            logger.info(
                "Member '%s' (%d) has changed name to '%s'",
                before.name,
                before.id,
                after.name,
            )
            changes.username = after.name

        if before.nick != after.nick and after.nick is not None:
            logger.info(
                "Member '%s' (%d) has changed nick to '%s'",
                before.display_name,
                before.id,
                after.nick,
            )
            changes.nickname = after.nick

        # Check if there were any changes
        if not changes:
            return

        attrs = StringBuilder(sep=", ")
        with self.bot.sql.transaction():
            if changes.avatar_url is not None:
                avatar, avatar_ext = await self._download_avatar(
                    changes.avatar_url)
                self.bot.sql.alias.add_avatar(before, timestamp, avatar,
                                              avatar_ext)
                attrs.write(f"avatar: {changes.avatar_url}")
            if changes.username is not None:
                self.bot.sql.alias.add_username(before, timestamp,
                                                changes.username)
                attrs.write(f"name: {changes.username}")
            if changes.nickname is not None:
                self.bot.sql.alias.add_nickname(before, timestamp,
                                                changes.nickname)
                attrs.write(f"nick: {changes.nickname}")

        content = f"{user_discrim(before)} updated {attrs}"
        self.journal.send(
            "member/update",
            before.guild,
            content,
            icon="person",
            before=before,
            after=after,
            changes=changes,
        )
Beispiel #19
0
def unicode_repr(s):
    """
    Similar to repr(), but always escapes characters that aren't "readable".
    That is, any characters not in READABLE_CHAR_SET.
    """

    result = StringBuilder('"')
    for ch in s:
        if ch == "\n":
            result.write("\\n")
        elif ch == "\t":
            result.write("\\t")
        elif ch == '"':
            result.write('\\"')
        elif ch in READABLE_CHAR_SET:
            result.write(ch)
        else:
            num = ord(ch)
            if num < 0x100:
                result.write(f"\\x{num:02x}")
            elif num < 0x10000:
                result.write(f"\\u{num:04x}")
            elif num < 0x100000000:
                result.write(f"\\U{num:08x}")
            else:
                raise ValueError(f"Character {ch!r} (ord {num:x}) too big for escaping")
    result.write('"')
    return str(result)