Skip to content

Commit f4ebed0

Browse files
Flesh out the info cog/command to be more expansible (#53)
* flesh out the info Cog as it felt a bit barren * add error handler for Cog * add relative datetimes * pin to dpy fix commit * more versioning fixes
1 parent 96d29f1 commit f4ebed0

File tree

5 files changed

+132
-57
lines changed

5 files changed

+132
-57
lines changed

core/bot.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
import asyncpg
3636
import discord
3737
from discord.ext import commands
38+
from discord.ext.commands.cog import Cog # type: ignore # stubs
39+
40+
from constants import GUILD_ID
3841

3942
from .context import Context
4043
from .core import CONFIG
@@ -77,6 +80,11 @@ async def get_context(
7780
) -> Context:
7881
return await super().get_context(message, cls=Context)
7982

83+
async def add_cog(self, cog: Cog, /, *, override: bool = False) -> None: # type: ignore
84+
# we patch this since we're a single guild bot.
85+
# it allows for guild syncing only.
86+
return await super().add_cog(cog, override=override, guild=discord.Object(id=GUILD_ID))
87+
8088
async def on_ready(self) -> None:
8189
"""On Bot ready - cache is built."""
8290
assert self.user

modules/info.py

Lines changed: 110 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -22,57 +22,117 @@
2222
"""
2323
from __future__ import annotations
2424

25+
from typing import TypeVar
26+
2527
import discord
2628
from discord.ext import commands
2729

2830
import core
31+
from constants import GUILD_ID
2932

3033

31-
class InformationEmbed(discord.Embed):
32-
"""A subclass of discord.Embed.
34+
EntityT = TypeVar("EntityT", discord.Member, discord.User, discord.Role, discord.abc.GuildChannel, discord.Guild)
3335

34-
This class allows to automatically get information within the class instead of recreating the embed each time
3536

36-
:param author: The embed author. Expects discord.Member or discord.User
37-
:param entity: The Member, User, Role, TextChannel, or Guild to get information about.
38-
"""
37+
class NoPermissions(commands.CommandError):
38+
pass
3939

40-
def __init__(
41-
self,
42-
*,
43-
author: discord.Member | discord.User,
44-
entity: discord.Member | discord.User | discord.Role | discord.TextChannel | discord.Guild,
45-
) -> None:
46-
super().__init__()
47-
created_at: str = f"{discord.utils.format_dt(entity.created_at)} ({discord.utils.format_dt(entity.created_at, 'R')})"
48-
if isinstance(entity, discord.Member) and entity.joined_at:
49-
joined_at = f"\n\nJoined At: {discord.utils.format_dt(entity.joined_at)} ({discord.utils.format_dt(entity.joined_at, 'R')})"
50-
else:
51-
joined_at = ""
5240

53-
description = f"Name: {entity.name}\n\nID: {entity.id}\n\nCreated At: {created_at}{joined_at}"
54-
if isinstance(entity, discord.Member):
55-
self.set_thumbnail(url=entity.guild_avatar or entity.display_avatar or None)
56-
elif isinstance(entity, discord.User):
57-
self.set_thumbnail(url=entity.display_avatar or None)
41+
class Information(core.Cog):
42+
"""Information commands which allows you to get information about users, the guild, roles, and channels."""
43+
44+
def __init__(self, bot: core.Bot) -> None:
45+
self.bot: core.Bot = bot
46+
47+
async def cog_command_error(self, ctx: core.Context, error: commands.CommandError) -> None: # type: ignore # bad lib types.
48+
error = getattr(error, "original", error)
49+
50+
if isinstance(error, NoPermissions):
51+
await ctx.send("Sorry, you don't have permissions to view details on this object.")
52+
return
53+
54+
def _embed_factory(self, entity: EntityT) -> discord.Embed:
55+
embed = discord.Embed(title=f"Info on {entity.name}!", colour=discord.Colour.random())
56+
embed.add_field(name="ID:", value=entity.id)
57+
embed.timestamp = discord.utils.utcnow()
58+
59+
if isinstance(entity, discord.User):
60+
return self._user_info(entity, embed=embed)
61+
elif isinstance(entity, discord.Member):
62+
embed = self._user_info(entity, embed=embed) # type: ignore # superclass
63+
return self._member_info(entity, embed=embed)
5864
elif isinstance(entity, discord.Role):
59-
description += f"\n\nHoisted: {entity.hoist}\n\nMentionable: {entity.mentionable}\n\n"
60-
elif isinstance(entity, discord.TextChannel):
61-
description += f"\n\nCategory: {entity.category}\n\nNSFW: {entity.nsfw}"
62-
else: # Change to elif when other types are added
63-
description += f"\n\nOwner: {entity.owner}"
64-
self.set_thumbnail(url=entity.icon or None)
65+
return self._role_info(entity, embed=embed)
66+
elif isinstance(entity, discord.abc.GuildChannel):
67+
return self._channel_info(entity, embed=embed)
68+
else:
69+
return self._guild_info(entity, embed=embed)
6570

66-
self.description = description
67-
self.set_author(name=author.name, icon_url=author.display_avatar)
68-
self.color = 0x7289DA
71+
def _member_info(self, member: discord.Member, /, *, embed: discord.Embed) -> discord.Embed:
72+
if member.joined_at:
73+
joined_at_fmt = (
74+
discord.utils.format_dt(member.joined_at, "F") + "\n" f"({discord.utils.format_dt(member.joined_at, 'R')})"
75+
)
76+
embed.add_field(name="Member joined the guild on:", value=joined_at_fmt)
6977

78+
roles = [role.mention for role in member.roles[1:]]
79+
roles.reverse()
80+
embed.add_field(name="Member's top 5 roles:-", value="\n".join(roles[:5]), inline=False)
81+
embed.colour = member.colour or embed.colour
7082

71-
class Information(core.Cog):
72-
"""Information commands which allows you to get information about users, the guild, roles, and channels."""
83+
return embed
7384

74-
def __init__(self, bot: core.Bot) -> None:
75-
self.bot = bot
85+
def _user_info(self, user: discord.User, /, *, embed: discord.Embed) -> discord.Embed:
86+
embed = discord.Embed(title=f"Info on {user.display_name}!", colour=discord.Colour.random())
87+
embed.set_author(name=user.name)
88+
embed.set_image(url=user.display_avatar.url)
89+
created_at_fmt = (
90+
discord.utils.format_dt(user.created_at, "F") + "\n" f"({discord.utils.format_dt(user.created_at, 'R')})"
91+
)
92+
embed.add_field(name="Account was created on:", value=created_at_fmt)
93+
94+
embed.timestamp = discord.utils.utcnow()
95+
96+
return embed
97+
98+
def _role_info(self, role: discord.Role, /, *, embed: discord.Embed) -> discord.Embed:
99+
embed.colour = role.colour or embed.colour
100+
embed.add_field(name="Mentionable?", value=role.mentionable)
101+
embed.add_field(name="Hoisted?", value=role.hoist)
102+
embed.add_field(name="Member count:", value=len(role.members))
103+
embed.add_field(name="Created on:", value=discord.utils.format_dt(role.created_at, "F"))
104+
105+
return embed
106+
107+
def _channel_info(self, channel: discord.abc.GuildChannel, /, *, embed: discord.Embed) -> discord.Embed:
108+
sneaky_role = channel.guild.default_role
109+
permissions = channel.permissions_for(sneaky_role)
110+
111+
allowed_to_read = discord.Permissions(read_messages=True, view_channel=True)
112+
113+
if not permissions.is_strict_superset(allowed_to_read):
114+
# They cannot read this channel
115+
raise NoPermissions("Cannot read this channel.")
116+
117+
embed.url = channel.jump_url
118+
119+
embed.add_field(name="Channel type:", value=channel.type.name, inline=False)
120+
121+
embed.add_field(name="Created on:", value=discord.utils.format_dt(channel.created_at, "F"), inline=False)
122+
123+
is_private = not (permissions.view_channel or permissions.read_messages)
124+
embed.add_field(name="Private Channel?", value=is_private)
125+
126+
return embed
127+
128+
def _guild_info(self, guild: discord.Guild, /, *, embed: discord.Embed) -> discord.Embed:
129+
if guild.id != GUILD_ID:
130+
raise NoPermissions("Unreachable but better safe than sorry.")
131+
132+
embed.add_field(name="Created on:", value=discord.utils.format_dt(guild.created_at, "F"))
133+
embed.set_thumbnail(url=(guild.icon and guild.icon.url))
134+
135+
return embed
76136

77137
@commands.group(
78138
name="information",
@@ -81,18 +141,21 @@ def __init__(self, bot: core.Bot) -> None:
81141
invoke_without_command=True,
82142
)
83143
async def info(
84-
self, ctx: core.Context, entity: discord.Member | discord.User | discord.Role | discord.TextChannel = commands.Author
144+
self,
145+
ctx: core.Context,
146+
*,
147+
entity: discord.Member
148+
| discord.User
149+
| discord.Role
150+
| discord.abc.GuildChannel
151+
| discord.Guild = commands.CurrentGuild,
85152
) -> None:
86-
"""Get information about a object
87-
Args:
88-
entity: The user, role, or TextChannel to get information about"""
89-
embed = InformationEmbed(author=ctx.author, entity=entity)
90-
await ctx.send(embed=embed)
91-
92-
@info.command(name="guild", brief="Get the current guild's information.")
93-
async def guild_info(self, ctx: core.GuildContext):
94-
embed = InformationEmbed(author=ctx.author, entity=ctx.guild)
95-
await ctx.reply(embed=embed)
153+
"""Get information about a specific Pythonista related object.
154+
155+
entity: Accepts a Person's ID, a Role ID or a Channel ID. Defaults to showing info on the Guild.
156+
"""
157+
embed = self._embed_factory(entity) # type: ignore # we ignore because converter sadness
158+
await ctx.reply(embed=embed, mention_author=False)
96159

97160

98161
async def setup(bot: core.Bot) -> None:

poetry.lock

Lines changed: 12 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ readme = "README.md"
1212

1313
[tool.poetry.dependencies]
1414
python = "^3.11"
15-
"discord.py" = "*"
15+
"discord.py" = { git = "https://github.com/Rapptz/discord.py.git", rev = "e6a0dc5bc0ba8e739b0def446378088bea65d1df" }
1616
aiohttp = "*"
1717
asyncpg = "*"
1818
toml = "*"

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
discord.py>=1.6.0
1+
discord.py @ git+https://github.com/Rapptz/discord.py@e6a0dc5
22
aiohttp~=3.7.3
33
asyncpg~=0.27.0
44
toml>=0.10.2

0 commit comments

Comments
 (0)