aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bot/exts/info/information.py179
1 files changed, 98 insertions, 81 deletions
diff --git a/bot/exts/info/information.py b/bot/exts/info/information.py
index 38e760ee3..73a377bf0 100644
--- a/bot/exts/info/information.py
+++ b/bot/exts/info/information.py
@@ -2,11 +2,10 @@ import colorsys
import logging
import pprint
import textwrap
-from collections import Counter, defaultdict
-from string import Template
-from typing import Any, Mapping, Optional, Tuple, Union
+from collections import defaultdict
+from typing import Any, DefaultDict, Dict, Mapping, Optional, Tuple, Union
-from discord import ChannelType, Colour, Embed, Guild, Message, Role, Status, utils
+from discord import ChannelType, Colour, Embed, Guild, Message, Role, utils
from discord.abc import GuildChannel
from discord.ext.commands import BucketType, Cog, Context, Paginator, command, group, has_any_role
@@ -22,12 +21,6 @@ from bot.utils.time import time_since
log = logging.getLogger(__name__)
-STATUS_EMOTES = {
- Status.offline: constants.Emojis.status_offline,
- Status.dnd: constants.Emojis.status_dnd,
- Status.idle: constants.Emojis.status_idle
-}
-
class Information(Cog):
"""A cog with commands for generating embeds with server info, such as server stats and user info."""
@@ -36,47 +29,60 @@ class Information(Cog):
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.
+ def is_staff_channel(guild: Guild, channel: GuildChannel) -> bool:
+ """Determines if a given channel is staff-only."""
+ if channel.type is ChannelType.category:
+ return False
+
+ # Channel is staff-only if staff have explicit read allow perms
+ # and @everyone has explicit read deny perms
+ return any(
+ channel.overwrites_for(guild.get_role(staff_role)).read_messages is True
+ and channel.overwrites_for(guild.default_role).read_messages is False
+ for staff_role in constants.STAFF_ROLES
+ )
- 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?
+ @staticmethod
+ def get_channel_type_counts(guild: Guild) -> DefaultDict[str, int]:
+ """Return the total amounts of the various types of channels in `guild`."""
+ channel_counter = defaultdict(int)
- 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
+ if Information.is_staff_channel(guild, channel):
+ channel_counter["staff"] += 1
+ else:
+ channel_counter[str(channel.type)] += 1
- return len(channel_ids)
+ return channel_counter
@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)
+ def get_member_counts(guild: Guild) -> Dict[str, int]:
+ """Return the total number of members for certain roles in `guild`."""
+ roles = (
+ guild.get_role(role_id) for role_id in (
+ constants.Roles.helpers, constants.Roles.moderators, constants.Roles.admins,
+ constants.Roles.owners, constants.Roles.contributors,
+ )
+ )
+ return {role.name.title(): len(role.members) for role in roles}
+
+ def get_extended_server_info(self, guild: Guild) -> str:
+ """Return additional server info only visible in moderation channels."""
+ talentpool_count = len(self.bot.get_cog("Talentpool").watched_users)
+ bb_count = len(self.bot.get_cog("Big Brother").watched_users)
+
+ defcon_cog = self.bot.get_cog("Defcon")
+ defcon_status = "Enabled" if defcon_cog.enabled else "Disabled"
+ defcon_days = defcon_cog.days.days if defcon_cog.enabled else "-"
+ python_general = self.bot.get_channel(constants.Channels.python_discussion)
+
+ return textwrap.dedent(f"""
+ Nominated: {talentpool_count}
+ BB-watched: {bb_count}
+ Defcon status: {defcon_status}
+ Defcon days: {defcon_days}
+ {python_general.mention} cooldown: {python_general.slowmode_delay}s
+ """)
@has_any_role(*constants.STAFF_ROLES)
@command(name="roles")
@@ -145,51 +151,62 @@ class Information(Cog):
@command(name="server", aliases=["server_info", "guild", "guild_info"])
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")
- features = ", ".join(ctx.guild.features)
region = ctx.guild.region
+ num_roles = len(ctx.guild.roles) - 1 # Exclude @everyone
- roles = len(ctx.guild.roles)
- member_count = ctx.guild.member_count
- channel_counts = self.get_channel_type_counts(ctx.guild)
+ # 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 = ""
- # How many of each user status?
+ # 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
- 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})
+ 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 ctx.channel.id in constants.MODERATION_CHANNELS:
+ embed.add_field(
+ name="Moderation:", value=self.get_extended_server_info(ctx.guild)
+ )
+
await ctx.send(embed=embed)
@command(name="user", aliases=["user_info", "member", "member_info"])