diff --git a/tux/cogs/info/info.py b/tux/cogs/info/info.py index 04796c30a..45403ab9d 100644 --- a/tux/cogs/info/info.py +++ b/tux/cogs/info/info.py @@ -1,169 +1,241 @@ +from collections.abc import Generator, Iterable, Iterator + import discord from discord.ext import commands +from reactionmenu import ViewButton, ViewMenu class Info(commands.Cog): def __init__(self, bot: commands.Bot) -> None: self.bot = bot - @commands.hybrid_group( - name="info", - aliases=["i"], - usage="info ", - ) + @commands.hybrid_group(name="info", aliases=["i"], usage="info ") async def info(self, ctx: commands.Context[commands.Bot]) -> None: """ Information commands. Parameters ---------- - ctx : commands.Context[commands.Bot] - The discord context object. + ctx : commands.Context + The context object associated with the command. """ + if ctx.invoked_subcommand is None: await ctx.send_help("info") - @info.command( - name="server", - aliases=["s"], - usage="info server", - ) + @info.command(name="server", aliases=["s"], usage="info server") async def server(self, ctx: commands.Context[commands.Bot]) -> None: """ Show information about the server. Parameters ---------- - ctx : commands.Context[commands.Bot] - The discord context object. + ctx : commands.Context + The context object associated with the command. """ - - if not ctx.guild: - return guild = ctx.guild + if not guild: + return - embed = discord.Embed( - title=ctx.guild.name, - description=guild.description or "No description available.", - color=discord.Color.blurple(), + embed: discord.Embed = ( + discord.Embed( + title=guild.name, + description=guild.description or "No description available.", + color=discord.Color.blurple(), + ) + .set_author(name="Server Information", icon_url=guild.icon) + .add_field(name="Owner", value=str(guild.owner.mention) if guild.owner else "Unknown") + .add_field(name="Vanity URL", value=guild.vanity_url_code or "None") + .add_field(name="Boosts", value=guild.premium_subscription_count) + .add_field(name="Text Channels", value=len(guild.text_channels)) + .add_field(name="Voice Channels", value=len(guild.voice_channels)) + .add_field(name="Forum Channels", value=len(guild.forums)) + .add_field(name="Emojis", value=f"{len(guild.emojis)}/{guild.emoji_limit}") + .add_field(name="Stickers", value=f"{len(guild.stickers)}/{guild.sticker_limit}") + .add_field(name="Roles", value=len(guild.roles)) + .add_field(name="Humans", value=sum(not member.bot for member in guild.members)) + .add_field(name="Bots", value=sum(member.bot for member in guild.members)) + .add_field(name="Bans", value=len([entry async for entry in guild.bans(limit=2000)])) + .set_footer(text=f"ID: {guild.id} | Created: {guild.created_at.strftime('%B %d, %Y')}") ) - embed.set_author(name="Server Information", icon_url=guild.icon) - embed.add_field(name="Owner", value=str(guild.owner.mention) if guild.owner else "Unknown") - embed.add_field(name="Vanity URL", value=guild.vanity_url_code or "None") - embed.add_field(name="Boosts", value=guild.premium_subscription_count) - embed.add_field(name="Text Channels", value=len(guild.text_channels)) - embed.add_field(name="Voice Channels", value=len(guild.voice_channels)) - embed.add_field(name="Forum Channels", value=len(guild.forums)) - embed.add_field(name="Emojis", value=f"{len(guild.emojis)}/{guild.emoji_limit}") - embed.add_field(name="Stickers", value=f"{len(guild.stickers)}/{guild.sticker_limit}") - embed.add_field(name="Roles", value=len(guild.roles)) - embed.add_field(name="Humans", value=sum(not member.bot for member in guild.members)) - embed.add_field(name="Bots", value=sum(member.bot for member in guild.members)) - embed.add_field(name="Bans", value=len([entry async for entry in guild.bans(limit=2000)])) - embed.set_footer(text=f"ID: {guild.id} | Created: {guild.created_at.strftime('%B %d, %Y')}") - await ctx.send(embed=embed) - @info.command( - name="member", - aliases=["m", "user", "u"], - usage="info member [member]", - ) + @info.command(name="member", aliases=["m", "user", "u"], usage="info member [member]") async def member(self, ctx: commands.Context[commands.Bot], member: discord.Member) -> None: """ Show information about a member. Parameters ---------- - ctx : commands.Context[commands.Bot] - The discord context object. + ctx : commands.Context + The context object associated with the command. member : discord.Member The member to get information about. """ - - bot_status = "✅" if member.bot else "❌" - joined = discord.utils.format_dt(member.joined_at, "R") if member.joined_at else "Unknown" - created = discord.utils.format_dt(member.created_at, "R") if member.created_at else "Unknown" - roles = ", ".join(role.mention for role in member.roles[1:]) if member.roles[1:] else "No roles" - fetched_member = await self.bot.fetch_user(member.id) - - embed = discord.Embed( - title=member.display_name, - description="Here is some information about the member.", - color=discord.Color.blurple(), + embed: discord.Embed = ( + discord.Embed( + title=member.display_name, + description="Here is some information about the member.", + color=discord.Color.blurple(), + ) + .set_thumbnail(url=member.display_avatar.url) + .set_image( + url=(await self.bot.fetch_user(member.id)).banner, # Fetched member's banner + ) + .add_field(name="Bot?", value="✅" if member.bot else "❌", inline=False) + .add_field(name="Username", value=member.name, inline=False) + .add_field(name="ID", value=str(member.id), inline=False) + .add_field( + name="Joined", + value=discord.utils.format_dt(member.joined_at, "R") if member.joined_at else "Unknown", + inline=False, + ) + .add_field( + name="Registered", + value=discord.utils.format_dt(member.created_at, "R") if member.created_at else "Unknown", + inline=False, + ) + .add_field( + name="Roles", + value=", ".join(role.mention for role in member.roles[1:]) if member.roles[1:] else "No roles", + inline=False, + ) ) - embed.set_thumbnail(url=member.display_avatar.url) - embed.set_image(url=fetched_member.banner) - embed.add_field(name="Bot?", value=bot_status, inline=False) - embed.add_field(name="Username", value=member.name, inline=False) - embed.add_field(name="ID", value=str(member.id), inline=False) - embed.add_field(name="Joined", value=joined, inline=False) - embed.add_field(name="Registered", value=created, inline=False) - embed.add_field(name="Roles", value=roles, inline=False) - await ctx.send(embed=embed) - @info.command( - name="roles", - aliases=["r"], - usage="info roles", - ) + @info.command(name="roles", aliases=["r"], usage="info roles") async def roles(self, ctx: commands.Context[commands.Bot]) -> None: """ List all roles in the server. Parameters ---------- - ctx : commands.Context[commands.Bot] - The discord context object. + ctx : commands.Context + The context object associated with the command. """ - if not ctx.guild: - return - guild = ctx.guild - roles = [role.mention for role in guild.roles] - - embed = discord.Embed( - title="Server Roles", - description=f"Role list for {guild.name}", - color=discord.Color.blurple(), - ) + if not guild: + return - embed.add_field(name="Roles", value=", ".join(roles), inline=False) + roles: list[str] = [role.mention for role in guild.roles] - await ctx.send(embed=embed) + await self.paginated_embed(ctx, "Server Roles", "roles", guild.name, roles, 32) - @info.command( - name="emotes", - aliases=["e"], - usage="info emotes", - ) + @info.command(name="emotes", aliases=["e"], usage="info emotes") async def emotes(self, ctx: commands.Context[commands.Bot]) -> None: """ List all emotes in the server. Parameters ---------- - ctx : commands.Context[commands.Bot] - The discord context object. + ctx : commands.Context + The context object associated with the command. """ - if not ctx.guild: + guild = ctx.guild + if not guild: return - guild = ctx.guild - emotes = [str(emote) for emote in guild.emojis] + emotes: list[str] = [str(emote) for emote in guild.emojis] + await self.paginated_embed(ctx, "Server Emotes", "emotes", guild.name, emotes, 128) + + async def paginated_embed( + self, + ctx: commands.Context[commands.Bot], + title: str, + list_type: str, + guild_name: str, + items: Iterable[str], + chunk_size: int, + ) -> None: + """ + Send a paginated embed. - embed = discord.Embed( - title="Server Emotes", - description=f"Emote list for {guild.name}", - color=discord.Color.blurple(), - ) + Parameters + ---------- + ctx : commands.Context + The context object associated with the command. + title : str + The title of the embed. + list_type : str + The type of list (e.g., roles, emotes). + guild_name : str + The name of the guild. + items : Iterable[str] + The items to display in the embed. + chunk_size : int + The size of each chunk for pagination. + """ + embed: discord.Embed = discord.Embed(title=title, color=discord.Color.blurple()) + chunks: list[list[str]] = list(self._chunks(iter(items), chunk_size)) - embed.add_field(name="Emotes", value=" ".join(emotes) if emotes else "No emotes available", inline=False) + if not chunks: + embed.description = "No items available." + await ctx.send(embed=embed) + return - await ctx.send(embed=embed) + menu: ViewMenu = ViewMenu(ctx, menu_type=ViewMenu.TypeEmbed) + for chunk in chunks: + page_embed: discord.Embed = embed.copy() + page_embed.description = f"{list_type.capitalize()} list for {guild_name}:\n{' '.join(chunk)}" + menu.add_page(page_embed) + + self._add_buttons_to_menu(menu) + await menu.start() + + def _chunks(self, it: Iterator[str], size: int) -> Generator[list[str], None, None]: + """ + Split an iterator into chunks of a specified size. + + Parameters + ---------- + it : Iterator[str] + The input iterator to be split into chunks. + size : int + The size of each chunk. + + Yields + ------ + List[str] + A list containing a chunk of elements from the input iterator. The last + list may contain fewer elements if there are not enough remaining to fill + a complete chunk. + """ + chunk: list[str] = [] + for item in it: + chunk.append(item) + if len(chunk) == size: + yield chunk + chunk = [] + if chunk: + yield chunk + + def _add_buttons_to_menu(self, menu: ViewMenu) -> ViewMenu: + """ + Add buttons to the menu. + + Parameters + ---------- + menu : ViewMenu + The menu to add buttons to. + + Returns + ------- + ViewMenu + The menu with buttons added. + """ + buttons = [ + ViewButton.go_to_first_page(), + ViewButton.back(), + ViewButton.next(), + ViewButton.go_to_last_page(), + ViewButton.end_session(), + ] + + for button in buttons: + menu.add_button(button) + return menu async def setup(bot: commands.Bot) -> None: