Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 37 additions & 60 deletions bot/cogs/information.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,14 @@
import logging
import pprint
import textwrap
import typing
from collections import defaultdict
from typing import Any, Mapping, Optional

import discord
from discord import CategoryChannel, Colour, Embed, Member, Role, TextChannel, VoiceChannel, utils
from discord.ext import commands
from discord.ext.commands import BucketType, Cog, Context, command, group
from collections import Counter, defaultdict
from typing import Any, Mapping, Optional, Union

from discord import CategoryChannel, Colour, Embed, Member, Message, Role, Status, TextChannel, VoiceChannel, utils
from discord.ext.commands import Bot, BucketType, Cog, Context, Paginator, command, group
from discord.utils import escape_markdown

from bot import constants
from bot.bot import Bot
from bot.decorators import InChannelCheckFailure, in_channel, with_role
from bot.utils.checks import cooldown_with_role_bypass, with_role_check
from bot.utils.time import time_since
Expand All @@ -32,8 +28,7 @@ def __init__(self, bot: Bot):
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, key=lambda role: role.name)
roles = [role for role in roles if role.name != "@everyone"]
roles = sorted(ctx.guild.roles[1:], key=lambda role: role.name)

# Build a string
role_string = ""
Expand All @@ -46,20 +41,20 @@ async def roles_info(self, ctx: Context) -> None:
colour=Colour.blurple(),
description=role_string
)

embed.set_footer(text=f"Total roles: {len(roles)}")

await ctx.send(embed=embed)

@with_role(*constants.MODERATION_ROLES)
@command(name="role")
async def role_info(self, ctx: Context, *roles: typing.Union[Role, str]) -> None:
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):
Expand All @@ -70,29 +65,29 @@ async def role_info(self, ctx: Context, *roles: typing.Union[Role, str]) -> None
role = utils.find(lambda r: r.name.lower() == role_name.lower(), ctx.guild.roles)

if not role:
await ctx.send(f":x: Could not convert `{role_name}` to a role")
failed_roles.append(role_name)
continue

parsed_roles.append(role)

if failed_roles:
await ctx.send(
":x: I could not convert the following role names to a role: \n- "
"\n- ".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)

h, s, v = colorsys.rgb_to_hsv(*role.colour.to_rgb())

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)
Expand All @@ -104,36 +99,19 @@ async def server_info(self, ctx: Context) -> None:
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
member_count = ctx.guild.member_count

# How many of each type of channel?
channels = Counter({TextChannel: 0, VoiceChannel: 0, CategoryChannel: 0})
for channel in ctx.guild.channels:
channels[channel.__class__] += 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
statuses = Counter({status.value: 0 for status in Status})
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
statuses[member.status.value] += 1

embed = Embed(
colour=Colour.blurple(),
Expand All @@ -146,18 +124,17 @@ async def server_info(self, ctx: Context) -> None:
**Counts**
Members: {member_count:,}
Roles: {roles}
Text: {text_channels}
Voice: {voice_channels}
Channel categories: {category_channels}
Text Channels: {channels[TextChannel]}
Voice Channels: {channels[VoiceChannel]}
Channel categories: {channels[CategoryChannel]}

**Members**
{constants.Emojis.status_online} {online}
{constants.Emojis.status_idle} {idle}
{constants.Emojis.status_dnd} {dnd}
{constants.Emojis.status_offline} {offline}
{constants.Emojis.status_online} {statuses['online']}
{constants.Emojis.status_idle} {statuses['idle']}
{constants.Emojis.status_dnd} {statuses['dnd']}
{constants.Emojis.status_offline} {statuses['offline']}
""")
)

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

await ctx.send(embed=embed)
Expand Down Expand Up @@ -202,7 +179,7 @@ async def create_user_embed(self, ctx: Context, user: Member) -> Embed:
name = f"{user.nick} ({name})"

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

description = [
textwrap.dedent(f"""
Expand All @@ -213,7 +190,7 @@ async def create_user_embed(self, ctx: Context, user: Member) -> Embed:
{custom_status}
**Member Information**
Joined: {joined}
Roles: {roles or None}
Roles: {roles}
""").strip()
]

Expand Down Expand Up @@ -356,13 +333,13 @@ def format_fields(self, mapping: Mapping[str, Any], field_width: Optional[int] =
@cooldown_with_role_bypass(2, 60 * 3, BucketType.member, bypass_roles=constants.STAFF_ROLES)
@group(invoke_without_command=True)
@in_channel(constants.Channels.bot, bypass_roles=constants.STAFF_ROLES)
async def raw(self, ctx: Context, *, message: discord.Message, json: bool = False) -> None:
async def raw(self, ctx: Context, *, message: Message, json: bool = False) -> None:
"""Shows information about the raw API response."""
# I *guess* it could be deleted right as the command is invoked but I felt like it wasn't worth handling
# doing this extra request is also much easier than trying to convert everything back into a dictionary again
raw_data = await ctx.bot.http.get_message(message.channel.id, message.id)

paginator = commands.Paginator()
paginator = Paginator()

def add_content(title: str, content: str) -> None:
paginator.add_line(f'== {title} ==\n')
Expand Down Expand Up @@ -390,7 +367,7 @@ def add_content(title: str, content: str) -> None:
await ctx.send(page)

@raw.command()
async def json(self, ctx: Context, message: discord.Message) -> None:
async def json(self, ctx: Context, message: Message) -> None:
"""Shows information about the raw API response in a copy-pasteable Python format."""
await ctx.invoke(self.raw, message=message, json=True)

Expand Down