Exemple #1
0
    async def create_user_embed(self, ctx: Context, user: Member) -> Embed:
        """Creates an embed containing information on the `user`."""
        created = time_since(user.created_at, max_units=3)

        # Custom status
        custom_status = ''
        for activity in user.activities:
            # Check activity.state for None value if user has a custom status set
            # This guards against a custom status with an emoji but no text, which will cause
            # escape_markdown to raise an exception
            # This can be reworked after a move to d.py 1.3.0+, which adds a CustomActivity class
            if activity.name == 'Custom Status' and activity.state:
                state = escape_markdown(activity.state)
                custom_status = f'Status: {state}\n'

        name = str(user)
        if user.nick:
            name = f"{user.nick} ({name})"

        joined = time_since(user.joined_at, precision="days")
        roles = ", ".join(role.mention for role in user.roles[1:])

        description = [
            textwrap.dedent(f"""
                **User Information**
                Created: {created}
                Profile: {user.mention}
                ID: {user.id}
                {custom_status}
                **Member Information**
                Joined: {joined}
                Roles: {roles or None}
            """).strip()
        ]

        # Show more verbose output in moderation channels for infractions and nominations
        if ctx.channel.id in constants.MODERATION_CHANNELS:
            description.append(await self.expanded_user_infraction_counts(user))
            description.append(await self.user_nomination_counts(user))
        else:
            description.append(await self.basic_user_infraction_counts(user))

        # Let's build the embed now
        embed = Embed(
            title=name,
            description="\n\n".join(description)
        )

        embed.set_thumbnail(url=user.avatar_url_as(format="png"))
        embed.colour = user.top_role.colour if roles else Colour.blurple()

        return embed
Exemple #2
0
    async def create_user_embed(self, ctx: Context, user: Member) -> Embed:
        """Creates an embed containing information on the `user`."""
        created = time_since(user.created_at, max_units=3)

        # Custom status
        custom_status = ''
        for activity in user.activities:
            if isinstance(activity, CustomActivity):
                state = ""

                if activity.name:
                    state = escape_markdown(activity.name)

                emoji = ""
                if activity.emoji:
                    # If an emoji is unicode use the emoji, else write the emote like :abc:
                    if not activity.emoji.id:
                        emoji += activity.emoji.name + " "
                    else:
                        emoji += f"`:{activity.emoji.name}:` "

                custom_status = f'Status: {emoji}{state}\n'

        name = str(user)
        if user.nick:
            name = f"{user.nick} ({name})"

        badges = []

        for badge, is_set in user.public_flags:
            if is_set and (emoji := getattr(constants.Emojis, f"badge_{badge}",
                                            None)):
                badges.append(emoji)
Exemple #3
0
    def _get_time_delta(time_string: str) -> str:
        """Returns the time in human-readable time delta format."""
        date_time = datetime.datetime.strptime(
            time_string, "%Y-%m-%dT%H:%M:%S.%fZ").replace(tzinfo=None)
        time_delta = time_since(date_time, precision="minutes", max_units=1)

        return time_delta
Exemple #4
0
    async def server_info(self, ctx: Context) -> None:
        """Returns an embed full of server information."""
        embed = Embed(colour=Colour.blurple(), title="Server Information")

        created = time_since(ctx.guild.created_at, precision="days")
        region = ctx.guild.region
        num_roles = len(ctx.guild.roles) - 1  # Exclude @everyone

        # Server Features are only useful in certain channels
        if ctx.channel.id in (*constants.MODERATION_CHANNELS,
                              constants.Channels.dev_core,
                              constants.Channels.dev_contrib):
            features = f"\nFeatures: {', '.join(ctx.guild.features)}"
        else:
            features = ""

        # Member status
        py_invite = await self.bot.fetch_invite(constants.Guild.invite)
        online_presences = py_invite.approximate_presence_count
        offline_presences = py_invite.approximate_member_count - online_presences
        member_status = (
            f"{constants.Emojis.status_online} {online_presences} "
            f"{constants.Emojis.status_offline} {offline_presences}")

        embed.description = textwrap.dedent(f"""
            Created: {created}
            Voice region: {region}\
            {features}
            Roles: {num_roles}
            Member status: {member_status}
        """)
        embed.set_thumbnail(url=ctx.guild.icon_url)

        # Members
        total_members = ctx.guild.member_count
        member_counts = self.get_member_counts(ctx.guild)
        member_info = "\n".join(f"{role}: {count}"
                                for role, count in member_counts.items())
        embed.add_field(name=f"Members: {total_members}", value=member_info)

        # Channels
        total_channels = len(ctx.guild.channels)
        channel_counts = self.get_channel_type_counts(ctx.guild)
        channel_info = "\n".join(
            f"{channel.title()}: {count}"
            for channel, count in sorted(channel_counts.items()))
        embed.add_field(name=f"Channels: {total_channels}", value=channel_info)

        # Additional info if ran in moderation channels
        if is_mod_channel(ctx.channel):
            embed.add_field(name="Moderation:",
                            value=self.get_extended_server_info(ctx))

        await ctx.send(embed=embed)
Exemple #5
0
    async def create_user_embed(self, ctx: Context, user: Member) -> Embed:
        """Creates an embed containing information on the `user`."""
        created = time_since(user.created_at, max_units=3)

        name = str(user)
        if user.nick:
            name = f"{user.nick} ({name})"

        badges = []

        for badge, is_set in user.public_flags:
            if is_set and (emoji := getattr(constants.Emojis, f"badge_{badge}", None)):
                badges.append(emoji)
Exemple #6
0
    async def server_info(self, ctx: Context) -> None:
        """Returns an embed full of server information."""
        created = time_since(ctx.guild.created_at, precision="days")
        features = ", ".join(ctx.guild.features)
        region = ctx.guild.region

        roles = len(ctx.guild.roles)
        member_count = ctx.guild.member_count
        channel_counts = self.get_channel_type_counts(ctx.guild)

        # How many of each user status?
        py_invite = await self.bot.fetch_invite(constants.Guild.invite)
        online_presences = py_invite.approximate_presence_count
        offline_presences = py_invite.approximate_member_count - online_presences
        embed = Embed(colour=Colour.blurple())

        # How many staff members and staff channels do we have?
        staff_member_count = len(ctx.guild.get_role(constants.Roles.helpers).members)
        staff_channel_count = self.get_staff_channel_count(ctx.guild)

        # Because channel_counts lacks leading whitespace, it breaks the dedent if it's inserted directly by the
        # f-string. While this is correctly formatted by Discord, it makes unit testing difficult. To keep the
        # formatting without joining a tuple of strings we can use a Template string to insert the already-formatted
        # channel_counts after the dedent is made.
        embed.description = Template(
            textwrap.dedent(f"""
                **Server information**
                Created: {created}
                Voice region: {region}
                Features: {features}

                **Channel counts**
                $channel_counts
                Staff channels: {staff_channel_count}

                **Member counts**
                Members: {member_count:,}
                Staff members: {staff_member_count}
                Roles: {roles}

                **Member statuses**
                {constants.Emojis.status_online} {online_presences:,}
                {constants.Emojis.status_offline} {offline_presences:,}
            """)
        ).substitute({"channel_counts": channel_counts})
        embed.set_thumbnail(url=ctx.guild.icon_url)

        await ctx.send(embed=embed)
Exemple #7
0
    async def server_info(self, ctx: Context) -> None:
        """Returns an embed full of server information."""
        created = time_since(ctx.guild.created_at, precision="days")
        features = ", ".join(ctx.guild.features)
        region = ctx.guild.region

        roles = len(ctx.guild.roles)
        member_count = ctx.guild.member_count

        # How many of each type of channel?
        channels = Counter(c.type for c in ctx.guild.channels)
        channel_counts = "".join(
            sorted(f"{str(ch).title()} channels: {channels[ch]}\n"
                   for ch in channels)).strip()

        # How many of each user status?
        statuses = Counter(member.status for member in ctx.guild.members)
        embed = Embed(colour=Colour.blurple())

        # Because channel_counts lacks leading whitespace, it breaks the dedent if it's inserted directly by the
        # f-string. While this is correctly formated by Discord, it makes unit testing difficult. To keep the formatting
        # without joining a tuple of strings we can use a Template string to insert the already-formatted channel_counts
        # after the dedent is made.
        embed.description = Template(
            textwrap.dedent(f"""
                **Server information**
                Created: {created}
                Voice region: {region}
                Features: {features}

                **Counts**
                Members: {member_count:,}
                Roles: {roles}
                $channel_counts

                **Members**
                {constants.Emojis.status_online} {statuses[Status.online]:,}
                {constants.Emojis.status_idle} {statuses[Status.idle]:,}
                {constants.Emojis.status_dnd} {statuses[Status.dnd]:,}
                {constants.Emojis.status_offline} {statuses[Status.offline]:,}
            """)).substitute({"channel_counts": channel_counts})
        embed.set_thumbnail(url=ctx.guild.icon_url)

        await ctx.send(embed=embed)
Exemple #8
0
    async def _activity_review(self, member: Member) -> str:
        """
        Format the activity of the nominee.

        Adds details on how long they've been on the server, their total message count,
        and the channels they're the most active in.
        """
        log.trace(f"Fetching the metricity data for {member.id}'s review")
        try:
            user_activity = await self.bot.api_client.get(
                f"bot/users/{member.id}/metricity_review_data")
        except ResponseCodeError as e:
            if e.status == 404:
                log.trace(
                    f"The user {member.id} seems to have no activity logged in Metricity."
                )
                messages = "no"
                channels = ""
            else:
                log.trace(
                    f"An unexpected error occured while fetching information of user {member.id}."
                )
                raise
        else:
            log.trace(f"Activity found for {member.id}, formatting review.")
            messages = user_activity["total_messages"]
            # Making this part flexible to the amount of expected and returned channels.
            first_channel = user_activity["top_channel_activity"][0]
            channels = f", with {first_channel[1]} messages in {first_channel[0]}"

            if len(user_activity["top_channel_activity"]) > 1:
                channels += ", " + ", ".join(
                    f"{count} in {channel}" for channel, count in
                    user_activity["top_channel_activity"][1:-1])
                last_channel = user_activity["top_channel_activity"][-1]
                channels += f", and {last_channel[1]} in {last_channel[0]}"

        joined_at_formatted = time_since(member.joined_at)
        review = (f"{member.name} joined the server **{joined_at_formatted}**"
                  f" and has **{messages} messages**{channels}.")

        return review
Exemple #9
0
    async def create_user_embed(self, ctx: Context, user: FetchedMember) -> Embed:
        """Creates an embed containing information on the `user`."""
        on_server = bool(ctx.guild.get_member(user.id))

        created = time_since(user.created_at, max_units=3)

        name = str(user)
        if on_server and user.nick:
            name = f"{user.nick} ({name})"

        if user.public_flags.verified_bot:
            name += f" {constants.Emojis.verified_bot}"
        elif user.bot:
            name += f" {constants.Emojis.bot}"

        badges = []

        for badge, is_set in user.public_flags:
            if is_set and (emoji := getattr(constants.Emojis, f"badge_{badge}", None)):
                badges.append(emoji)
Exemple #10
0
    async def user_verification_and_messages(
            self,
            user: FetchedMember) -> Tuple[Union[bool, str], Tuple[str, str]]:
        """
        Gets the time of verification and amount of messages for `member`.

        Fetches information from the metricity database that's hosted by the site.
        If the database returns a code besides a 404, then many parts of the bot are broken including this one.
        """
        activity_output = []
        verified_at = False

        try:
            user_activity = await self.bot.api_client.get(
                f"bot/users/{user.id}/metricity_data")
        except ResponseCodeError as e:
            if e.status == 404:
                activity_output = "No activity"

        else:
            try:
                if (verified_at := user_activity["verified_at"]) is not None:
                    verified_at = time_since(parser.isoparse(verified_at),
                                             max_units=3)
            except ValueError:
                log.warning(
                    f"Could not parse ISO string correctly for user {user.id} verification date."
                )
                verified_at = None

            activity_output.append(user_activity["total_messages"]
                                   or "No messages")
            activity_output.append(user_activity["activity_blocks"]
                                   or "No activity")

            activity_output = "\n".join(
                f"{name}: {metric}" for name, metric in zip(
                    ["Messages", "Activity blocks"], activity_output))

        return verified_at, ("Activity", activity_output)
Exemple #11
0
    async def _previous_nominations_review(self,
                                           member: Member) -> Optional[str]:
        """
        Formats the review of the nominee's previous nominations.

        The number of previous nominations and unnominations are shown, as well as the reason the last one ended.
        """
        log.trace(
            f"Fetching the nomination history data for {member.id}'s review")
        history = await self.bot.api_client.get(self._pool.api_endpoint,
                                                params={
                                                    "user__id": str(member.id),
                                                    "active": "false",
                                                    "ordering": "-inserted_at"
                                                })

        log.trace(
            f"{len(history)} previous nominations found for {member.id}, formatting review."
        )
        if not history:
            return

        num_entries = sum(len(nomination["entries"]) for nomination in history)

        nomination_times = f"{num_entries} times" if num_entries > 1 else "once"
        rejection_times = f"{len(history)} times" if len(
            history) > 1 else "once"
        end_time = time_since(isoparse(
            history[0]['ended_at']).replace(tzinfo=None),
                              max_units=2)

        review = (
            f"They were nominated **{nomination_times}** before"
            f", but their nomination was called off **{rejection_times}**."
            f"\nThe last one ended {end_time} with the reason: {history[0]['end_reason']}"
        )

        return review
Exemple #12
0
    async def server_info(self, ctx: Context):
        """
        Returns an embed full of
        server information.
        """

        created = time_since(ctx.guild.created_at, precision="days")
        features = ", ".join(ctx.guild.features)
        region = ctx.guild.region

        # How many of each type of channel?
        roles = len(ctx.guild.roles)
        channels = ctx.guild.channels
        text_channels = 0
        category_channels = 0
        voice_channels = 0
        for channel in channels:
            if type(channel) == TextChannel:
                text_channels += 1
            elif type(channel) == CategoryChannel:
                category_channels += 1
            elif type(channel) == VoiceChannel:
                voice_channels += 1

        # How many of each user status?
        member_count = ctx.guild.member_count
        members = ctx.guild.members
        online = 0
        dnd = 0
        idle = 0
        offline = 0
        for member in members:
            if str(member.status) == "online":
                online += 1
            elif str(member.status) == "offline":
                offline += 1
            elif str(member.status) == "idle":
                idle += 1
            elif str(member.status) == "dnd":
                dnd += 1

        embed = Embed(colour=Colour.blurple(),
                      description=textwrap.dedent(f"""
                **Server information**
                Created: {created}
                Voice region: {region}
                Features: {features}

                **Counts**
                Members: {member_count}
                Roles: {roles}
                Text: {text_channels}
                Voice: {voice_channels}
                Channel categories: {category_channels}

                **Members**
                {Emojis.status_online} {online}
                {Emojis.status_idle} {idle}
                {Emojis.status_dnd} {dnd}
                {Emojis.status_offline} {offline}
            """))

        embed.set_thumbnail(url=ctx.guild.icon_url)

        await ctx.send(embed=embed)
Exemple #13
0
    async def user_info(self,
                        ctx: Context,
                        user: Member = None,
                        hidden: bool = False):
        """
        Returns info about a user.
        """

        # Validates hidden input
        hidden = str(hidden)

        if user is None:
            user = ctx.author

        # User information
        created = time_since(user.created_at, max_units=3)

        name = f"{user.name}#{user.discriminator}"
        if user.nick:
            name = f"{user.nick} ({name})"

        # Member information
        joined = time_since(user.joined_at, precision="days")

        # You're welcome, Volcyyyyyyyyyyyyyyyy
        roles = ", ".join(role.mention for role in user.roles
                          if role.name != "@everyone")

        # Infractions
        api_response = await self.bot.http_session.get(
            url=URLs.site_infractions_user.format(user_id=user.id),
            params={"hidden": hidden},
            headers=self.headers)

        infractions = await api_response.json()

        infr_total = 0
        infr_active = 0

        # At least it's readable.
        for infr in infractions:
            if infr["active"]:
                infr_active += 1

            infr_total += 1

        # Let's build the embed now
        embed = Embed(title=name,
                      description=textwrap.dedent(f"""
                **User Information**
                Created: {created}
                Profile: {user.mention}
                ID: {user.id}

                **Member Information**
                Joined: {joined}
                Roles: {roles or None}

                **Infractions**
                Total: {infr_total}
                Active: {infr_active}
            """))

        embed.set_thumbnail(url=user.avatar_url_as(format="png"))
        embed.colour = user.top_role.colour if roles else Colour.blurple()

        await ctx.send(embed=embed)
Exemple #14
0
class Information(Cog):
    """A cog with commands for generating embeds with server info, such as server stats and user info."""

    def __init__(self, bot: Bot):
        self.bot = bot

    @staticmethod
    def role_can_read(channel: GuildChannel, role: Role) -> bool:
        """Return True if `role` can read messages in `channel`."""
        overwrites = channel.overwrites_for(role)
        return overwrites.read_messages is True

    def get_staff_channel_count(self, guild: Guild) -> int:
        """
        Get the number of channels that are staff-only.

        We need to know two things about a channel:
        - Does the @everyone role have explicit read deny permissions?
        - Do staff roles have explicit read allow permissions?

        If the answer to both of these questions is yes, it's a staff channel.
        """
        channel_ids = set()
        for channel in guild.channels:
            if channel.type is ChannelType.category:
                continue

            everyone_can_read = self.role_can_read(channel, guild.default_role)

            for role in constants.STAFF_ROLES:
                role_can_read = self.role_can_read(channel, guild.get_role(role))
                if role_can_read and not everyone_can_read:
                    channel_ids.add(channel.id)
                    break

        return len(channel_ids)

    @staticmethod
    def get_channel_type_counts(guild: Guild) -> str:
        """Return the total amounts of the various types of channels in `guild`."""
        channel_counter = Counter(c.type for c in guild.channels)
        channel_type_list = []
        for channel, count in channel_counter.items():
            channel_type = str(channel).title()
            channel_type_list.append(f"{channel_type} channels: {count}")

        channel_type_list = sorted(channel_type_list)
        return "\n".join(channel_type_list)

    @has_any_role(*constants.STAFF_ROLES)
    @command(name="roles")
    async def roles_info(self, ctx: Context) -> None:
        """Returns a list of all roles and their corresponding IDs."""
        # Sort the roles alphabetically and remove the @everyone role
        roles = sorted(ctx.guild.roles[1:], key=lambda role: role.name)

        # Build a list
        role_list = []
        for role in roles:
            role_list.append(f"`{role.id}` - {role.mention}")

        # Build an embed
        embed = Embed(
            title=f"Role information (Total {len(roles)} role{'s' * (len(role_list) > 1)})",
            colour=Colour.blurple()
        )

        await LinePaginator.paginate(role_list, ctx, embed, empty=False)

    @has_any_role(*constants.STAFF_ROLES)
    @command(name="role")
    async def role_info(self, ctx: Context, *roles: Union[Role, str]) -> None:
        """
        Return information on a role or list of roles.

        To specify multiple roles just add to the arguments, delimit roles with spaces in them using quotation marks.
        """
        parsed_roles = []
        failed_roles = []

        for role_name in roles:
            if isinstance(role_name, Role):
                # Role conversion has already succeeded
                parsed_roles.append(role_name)
                continue

            role = utils.find(lambda r: r.name.lower() == role_name.lower(), ctx.guild.roles)

            if not role:
                failed_roles.append(role_name)
                continue

            parsed_roles.append(role)

        if failed_roles:
            await ctx.send(f":x: Could not retrieve the following roles: {', '.join(failed_roles)}")

        for role in parsed_roles:
            h, s, v = colorsys.rgb_to_hsv(*role.colour.to_rgb())

            embed = Embed(
                title=f"{role.name} info",
                colour=role.colour,
            )
            embed.add_field(name="ID", value=role.id, inline=True)
            embed.add_field(name="Colour (RGB)", value=f"#{role.colour.value:0>6x}", inline=True)
            embed.add_field(name="Colour (HSV)", value=f"{h:.2f} {s:.2f} {v}", inline=True)
            embed.add_field(name="Member count", value=len(role.members), inline=True)
            embed.add_field(name="Position", value=role.position)
            embed.add_field(name="Permission code", value=role.permissions.value, inline=True)

            await ctx.send(embed=embed)

    @command(name="server", aliases=["server_info", "guild", "guild_info"])
    async def server_info(self, ctx: Context) -> None:
        """Returns an embed full of server information."""
        created = time_since(ctx.guild.created_at, precision="days")
        features = ", ".join(ctx.guild.features)
        region = ctx.guild.region

        roles = len(ctx.guild.roles)
        member_count = ctx.guild.member_count
        channel_counts = self.get_channel_type_counts(ctx.guild)

        # How many of each user status?
        py_invite = await self.bot.fetch_invite(constants.Guild.invite)
        online_presences = py_invite.approximate_presence_count
        offline_presences = py_invite.approximate_member_count - online_presences
        embed = Embed(colour=Colour.blurple())

        # How many staff members and staff channels do we have?
        staff_member_count = len(ctx.guild.get_role(constants.Roles.helpers).members)
        staff_channel_count = self.get_staff_channel_count(ctx.guild)

        # Because channel_counts lacks leading whitespace, it breaks the dedent if it's inserted directly by the
        # f-string. While this is correctly formatted by Discord, it makes unit testing difficult. To keep the
        # formatting without joining a tuple of strings we can use a Template string to insert the already-formatted
        # channel_counts after the dedent is made.
        embed.description = Template(
            textwrap.dedent(f"""
                **Server information**
                Created: {created}
                Voice region: {region}
                Features: {features}

                **Channel counts**
                $channel_counts
                Staff channels: {staff_channel_count}

                **Member counts**
                Members: {member_count:,}
                Staff members: {staff_member_count}
                Roles: {roles}

                **Member statuses**
                {constants.Emojis.status_online} {online_presences:,}
                {constants.Emojis.status_offline} {offline_presences:,}
            """)
        ).substitute({"channel_counts": channel_counts})
        embed.set_thumbnail(url=ctx.guild.icon_url)

        await ctx.send(embed=embed)

    @command(name="user", aliases=["user_info", "member", "member_info"])
    async def user_info(self, ctx: Context, user: FetchedMember = None) -> None:
        """Returns info about a user."""
        if user is None:
            user = ctx.author

        # Do a role check if this is being executed on someone other than the caller
        elif user != ctx.author and await has_no_roles_check(ctx, *constants.MODERATION_ROLES):
            await ctx.send("You may not use this command on users other than yourself.")
            return

        # Will redirect to #bot-commands if it fails.
        if in_whitelist_check(ctx, roles=constants.STAFF_ROLES):
            embed = await self.create_user_embed(ctx, user)
            await ctx.send(embed=embed)

    async def create_user_embed(self, ctx: Context, user: FetchedMember) -> Embed:
        """Creates an embed containing information on the `user`."""
        on_server = bool(ctx.guild.get_member(user.id))

        created = time_since(user.created_at, max_units=3)

        name = str(user)
        if on_server and user.nick:
            name = f"{user.nick} ({name})"

        badges = []

        for badge, is_set in user.public_flags:
            if is_set and (emoji := getattr(constants.Emojis, f"badge_{badge}", None)):
                badges.append(emoji)

        activity = await self.user_messages(user)

        if on_server:
            joined = time_since(user.joined_at, max_units=3)
            roles = ", ".join(role.mention for role in user.roles[1:])
            membership = {"Joined": joined, "Pending": user.pending, "Roles": roles or None}
            if not is_mod_channel(ctx.channel):
                membership.pop("Pending")

            membership = textwrap.dedent("\n".join([f"{key}: {value}" for key, value in membership.items()]))
        else:
            roles = None
            membership = "The user is not a member of the server"

        fields = [
            (
                "User information",
                textwrap.dedent(f"""
                    Created: {created}
                    Profile: {user.mention}
                    ID: {user.id}
                """).strip()
            ),
            (
                "Member information",
                membership
            ),
        ]

        # Show more verbose output in moderation channels for infractions and nominations
        if is_mod_channel(ctx.channel):
            fields.append(activity)

            fields.append(await self.expanded_user_infraction_counts(user))
            fields.append(await self.user_nomination_counts(user))
        else:
            fields.append(await self.basic_user_infraction_counts(user))

        # Let's build the embed now
        embed = Embed(
            title=name,
            description=" ".join(badges)
        )

        for field_name, field_content in fields:
            embed.add_field(name=field_name, value=field_content, inline=False)

        embed.set_thumbnail(url=user.avatar_url_as(static_format="png"))
        embed.colour = user.top_role.colour if roles else Colour.blurple()

        return embed
Exemple #15
0
    def _get_time_delta(time_string: str) -> str:
        """Returns the time in human-readable time delta format."""
        date_time = dateutil.parser.isoparse(time_string).replace(tzinfo=None)
        time_delta = time_since(date_time, precision="minutes", max_units=1)

        return time_delta
Exemple #16
0
    async def user_info(self,
                        ctx: Context,
                        user: Member = None,
                        hidden: bool = False):
        """
        Returns info about a user.
        """

        if user is None:
            user = ctx.author

        # Do a role check if this is being executed on someone other than the caller
        if user != ctx.author and not with_role_check(ctx, *MODERATION_ROLES):
            await ctx.send(
                "You may not use this command on users other than yourself.")
            return

        # Non-moderators may only do this in #bot-commands and can't see hidden infractions.
        if not with_role_check(ctx, *STAFF_ROLES):
            if not ctx.channel.id == Channels.bot:
                raise InChannelCheckFailure(Channels.bot)
            # Hide hidden infractions for users without a moderation role
            hidden = False

        # User information
        created = time_since(user.created_at, max_units=3)

        name = str(user)
        if user.nick:
            name = f"{user.nick} ({name})"

        # Member information
        joined = time_since(user.joined_at, precision="days")

        # You're welcome, Volcyyyyyyyyyyyyyyyy
        roles = ", ".join(role.mention for role in user.roles
                          if role.name != "@everyone")

        # Infractions
        infractions = await self.bot.api_client.get('bot/infractions',
                                                    params={
                                                        'hidden': str(hidden),
                                                        'user__id':
                                                        str(user.id)
                                                    })

        infr_total = 0
        infr_active = 0

        # At least it's readable.
        for infr in infractions:
            if infr["active"]:
                infr_active += 1

            infr_total += 1

        # Let's build the embed now
        embed = Embed(title=name,
                      description=textwrap.dedent(f"""
                **User Information**
                Created: {created}
                Profile: {user.mention}
                ID: {user.id}

                **Member Information**
                Joined: {joined}
                Roles: {roles or None}

                **Infractions**
                Total: {infr_total}
                Active: {infr_active}
            """))

        embed.set_thumbnail(url=user.avatar_url_as(format="png"))
        embed.colour = user.top_role.colour if roles else Colour.blurple()

        await ctx.send(embed=embed)
Exemple #17
0
    async def user_info(self,
                        ctx: Context,
                        user: Member = None,
                        hidden: bool = False):
        """
        Returns info about a user.
        """

        # Do a role check if this is being executed on
        # someone other than the caller
        if user and user != ctx.author:
            if not with_role_check(ctx, *MODERATION_ROLES):
                raise BadArgument(
                    "You do not have permission to use this command on users other than yourself."
                )

        # Non-moderators may only do this in #bot-commands
        if not with_role_check(ctx, *MODERATION_ROLES):
            if not ctx.channel.id == Channels.bot:
                raise MissingPermissions("You can't do that here!")

        # Validates hidden input
        hidden = str(hidden)

        if user is None:
            user = ctx.author

        # User information
        created = time_since(user.created_at, max_units=3)

        name = f"{user.name}#{user.discriminator}"
        if user.nick:
            name = f"{user.nick} ({name})"

        # Member information
        joined = time_since(user.joined_at, precision="days")

        # You're welcome, Volcyyyyyyyyyyyyyyyy
        roles = ", ".join(role.mention for role in user.roles
                          if role.name != "@everyone")

        # Infractions
        api_response = await self.bot.http_session.get(
            url=URLs.site_infractions_user.format(user_id=user.id),
            params={"hidden": hidden},
            headers=self.headers)

        infractions = await api_response.json()

        infr_total = 0
        infr_active = 0

        # At least it's readable.
        for infr in infractions:
            if infr["active"]:
                infr_active += 1

            infr_total += 1

        # Let's build the embed now
        embed = Embed(title=name,
                      description=textwrap.dedent(f"""
                **User Information**
                Created: {created}
                Profile: {user.mention}
                ID: {user.id}

                **Member Information**
                Joined: {joined}
                Roles: {roles or None}

                **Infractions**
                Total: {infr_total}
                Active: {infr_active}
            """))

        embed.set_thumbnail(url=user.avatar_url_as(format="png"))
        embed.colour = user.top_role.colour if roles else Colour.blurple()

        await ctx.send(embed=embed)
Exemple #18
0
    async def send_header(self, message: Message, destination: TextChannel):
        """
        Sends a log message header to the given channel.

        A header is only sent if the user or channel are different than the previous, or if the configured message
        limit for a single header has been exceeded.

        :param message: the first message in the queue
        :param destination: the channel in which to send the header
        """

        last_user, last_channel, msg_count = self.last_log
        limit = BigBrotherConfig.header_message_limit

        # Send header if user/channel are different or if message limit exceeded.
        if message.author.id != last_user or message.channel.id != last_channel or msg_count > limit:
            # Retrieve watch reason from API if it's not already in the cache
            if message.author.id not in self.watch_reasons:
                log.trace(
                    f"No watch information for {message.author.id} found in cache; retrieving from API"
                )
                if destination == self.bot.get_channel(Channels.talent_pool):
                    prefix = self.nomination_prefix
                else:
                    prefix = self.infraction_watch_prefix
                user_watch_information = await self.get_watch_information(
                    message.author.id, prefix)
                self.watch_reasons[message.author.id] = user_watch_information

            self.last_log = [message.author.id, message.channel.id, 0]

            # Get reason, actor, inserted_at
            reason, actor_id, inserted_at = self.watch_reasons[
                message.author.id]

            # Setting up the default author_field
            author_field = message.author.nick or message.author.name

            # When we're dealing with a talent-pool header, add nomination info to the author field
            if destination == self.bot.get_channel(Channels.talent_pool):
                log.trace(
                    "We're sending a header to the talent-pool; let's add nomination info"
                )
                # If a reason was provided, both should be known
                if actor_id and inserted_at:
                    # Parse actor name
                    guild: GuildConfig = self.bot.get_guild(GuildConfig.id)
                    actor_as_member = guild.get_member(actor_id)
                    actor = actor_as_member.nick or actor_as_member.name

                    # Get time delta since insertion
                    date_time = parse_rfc1123(inserted_at).replace(tzinfo=None)
                    time_delta = time_since(date_time,
                                            precision="minutes",
                                            max_units=1)

                    # Adding nomination info to author_field
                    author_field = f"{author_field} (nominated {time_delta} by {actor})"
            else:
                if inserted_at:
                    # Get time delta since insertion
                    date_time = parse_rfc1123(inserted_at).replace(tzinfo=None)
                    time_delta = time_since(date_time,
                                            precision="minutes",
                                            max_units=1)

                    author_field = f"{author_field} (added {time_delta})"

            embed = Embed(
                description=
                f"{message.author.mention} in [#{message.channel.name}]({message.jump_url})"
            )
            embed.set_author(name=author_field,
                             icon_url=message.author.avatar_url)
            embed.set_footer(text=f"Reason: {reason}")
            await destination.send(embed=embed)
Exemple #19
0
        name = str(user)
        if on_server and user.nick:
            name = f"{user.nick} ({name})"

        badges = []

        for badge, is_set in user.public_flags:
            if is_set and (emoji := getattr(constants.Emojis, f"badge_{badge}",
                                            None)):
                badges.append(emoji)

        activity = await self.user_messages(user)

        if on_server:
            joined = time_since(user.joined_at, max_units=3)
            roles = ", ".join(role.mention for role in user.roles[1:])
            membership = {
                "Joined": joined,
                "Verified": not user.pending,
                "Roles": roles or None
            }
            if not is_mod_channel(ctx.channel):
                membership.pop("Verified")

            membership = textwrap.dedent("\n".join(
                [f"{key}: {value}" for key, value in membership.items()]))
        else:
            roles = None
            membership = "The user is not a member of the server"