Skip to content
Merged
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
270 changes: 171 additions & 99 deletions tux/cogs/info/info.py
Original file line number Diff line number Diff line change
@@ -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 <subcommand>",
)
@commands.hybrid_group(name="info", aliases=["i"], usage="info <subcommand>")
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:
Expand Down