Skip to content

Commit c8db454

Browse files
committed
feat(stats): Add comprehensive guild statistics and detailed guild information commands
1 parent 8cded16 commit c8db454

1 file changed

Lines changed: 279 additions & 2 deletions

File tree

cogs/Stats.py

Lines changed: 279 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,51 @@ async def save_stats_to_mongodb(self):
106106
# Calculate total user count
107107
total_users = sum(guild.member_count or 0 for guild in self.bot.guilds)
108108

109+
# Get detailed guild information
110+
guild_details = []
111+
guild_basic_list = []
112+
for guild in self.bot.guilds:
113+
try:
114+
basic_info = {
115+
"id": str(guild.id),
116+
"name": guild.name,
117+
"member_count": guild.member_count or 0
118+
}
119+
guild_basic_list.append(basic_info)
120+
121+
detailed_info = {
122+
"id": str(guild.id),
123+
"name": guild.name,
124+
"member_count": guild.member_count or 0,
125+
"owner_id": str(guild.owner_id) if guild.owner_id else None,
126+
"created_at": guild.created_at.isoformat() if guild.created_at else None,
127+
"features": list(guild.features) if guild.features else [],
128+
"verification_level": str(guild.verification_level) if guild.verification_level else "none",
129+
"premium_tier": guild.premium_tier if hasattr(guild, 'premium_tier') else 0,
130+
"premium_subscription_count": guild.premium_subscription_count if hasattr(guild, 'premium_subscription_count') else 0,
131+
"large": guild.large if hasattr(guild, 'large') else False,
132+
"icon": guild.icon.url if guild.icon else None,
133+
"banner": guild.banner.url if guild.banner else None,
134+
"description": guild.description,
135+
"region": str(guild.preferred_locale) if hasattr(guild, 'preferred_locale') else None,
136+
"channels_count": len(guild.channels) if guild.channels else 0,
137+
"roles_count": len(guild.roles) if guild.roles else 0,
138+
"emojis_count": len(guild.emojis) if guild.emojis else 0,
139+
"joined_at": guild.me.joined_at.isoformat() if guild.me and guild.me.joined_at else None,
140+
"permissions": guild.me.guild_permissions.value if guild.me else 0,
141+
"last_updated": datetime.now().isoformat()
142+
}
143+
guild_details.append(detailed_info)
144+
except Exception as e:
145+
logging.warning(f"Error getting details for guild {guild.id}: {e}")
146+
basic_info = {
147+
"id": str(guild.id),
148+
"name": getattr(guild, 'name', 'Unknown'),
149+
"member_count": getattr(guild, 'member_count', 0) or 0
150+
}
151+
guild_basic_list.append(basic_info)
152+
guild_details.append(basic_info)
153+
109154
# Prepare comprehensive stats document
110155
stats_doc = {
111156
"command_count": self.command_count,
@@ -122,9 +167,19 @@ async def save_stats_to_mongodb(self):
122167
"cpu_usage": round(cpu_usage, 2),
123168
"cached_users": len(self.bot.users),
124169

125-
# Guild information
170+
# Guild information - comprehensive
126171
"guild_count": len(self.bot.guilds),
127172
"guild_list": [str(guild.id) for guild in self.bot.guilds],
173+
"guild_details": guild_details, # Full detailed information
174+
"guild_basic": guild_basic_list, # Basic information for quick access
175+
176+
# Guild statistics
177+
"largest_guild": max(guild_details, key=lambda g: g.get('member_count', 0)) if guild_details else None,
178+
"total_channels": sum(g.get('channels_count', 0) for g in guild_details),
179+
"total_roles": sum(g.get('roles_count', 0) for g in guild_details),
180+
"total_emojis": sum(g.get('emojis_count', 0) for g in guild_details),
181+
"premium_guilds": len([g for g in guild_details if g.get('premium_tier', 0) > 0]),
182+
"large_guilds": len([g for g in guild_details if g.get('large', False)]),
128183

129184
# Uptime information
130185
"uptime_start": self.start_time.timestamp(),
@@ -144,9 +199,37 @@ async def save_stats_to_mongodb(self):
144199
upsert=True
145200
)
146201

202+
# Also save individual guild documents for detailed tracking
203+
try:
204+
for guild_info in guild_details:
205+
await self.db.db.guild_cache.update_one(
206+
{"_id": guild_info["id"]},
207+
{"$set": {
208+
**guild_info,
209+
"bot_joined_at": guild_info.get("joined_at"),
210+
"last_seen": datetime.now().isoformat(),
211+
"is_active": True
212+
}},
213+
upsert=True
214+
)
215+
216+
# Mark guilds we're no longer in as inactive
217+
current_guild_ids = [g["id"] for g in guild_details]
218+
await self.db.db.guild_cache.update_many(
219+
{"_id": {"$nin": current_guild_ids}, "is_active": True},
220+
{"$set": {
221+
"is_active": False,
222+
"left_at": datetime.now().isoformat()
223+
}}
224+
)
225+
226+
logging.debug(f"Updated {len(guild_details)} guild cache entries")
227+
except Exception as e:
228+
logging.error(f"Error updating guild cache: {e}")
229+
147230
success = result.modified_count > 0 or result.upserted_id is not None
148231
if success:
149-
logging.debug("Stats saved to MongoDB successfully")
232+
logging.debug("Stats and guild data saved to MongoDB successfully")
150233
else:
151234
logging.warning("Failed to save stats to MongoDB")
152235

@@ -546,6 +629,200 @@ def check(reaction, user):
546629
except TimeoutError:
547630
await ctx.send("⏰ Stats reset timed out.")
548631

632+
@commands.command(name='guildstats', aliases=['serverstats'])
633+
@commands.is_owner()
634+
async def guild_stats(self, ctx):
635+
"""Show comprehensive guild statistics"""
636+
guild_count = len(self.bot.guilds)
637+
total_users = sum(guild.member_count or 0 for guild in self.bot.guilds)
638+
639+
# Calculate guild statistics
640+
large_guilds = len([g for g in self.bot.guilds if getattr(g, 'large', False)])
641+
premium_guilds = len([g for g in self.bot.guilds if getattr(g, 'premium_tier', 0) > 0])
642+
643+
# Find largest and smallest guilds
644+
guilds_with_members = [(g.name, g.member_count or 0) for g in self.bot.guilds if g.member_count]
645+
largest_guild = max(guilds_with_members, key=lambda x: x[1]) if guilds_with_members else ("None", 0)
646+
smallest_guild = min(guilds_with_members, key=lambda x: x[1]) if guilds_with_members else ("None", 0)
647+
648+
embed = discord.Embed(
649+
title="🏰 Guild Statistics",
650+
color=discord.Color.gold()
651+
)
652+
653+
embed.add_field(
654+
name="📊 Overview",
655+
value=f"**Total Guilds:** {guild_count:,}\n"
656+
f"**Total Users:** {total_users:,}\n"
657+
f"**Average Users/Guild:** {total_users // guild_count if guild_count > 0 else 0:,}",
658+
inline=True
659+
)
660+
661+
embed.add_field(
662+
name="🏆 Guild Types",
663+
value=f"**Large Guilds:** {large_guilds:,}\n"
664+
f"**Premium Guilds:** {premium_guilds:,}\n"
665+
f"**Regular Guilds:** {guild_count - large_guilds - premium_guilds:,}",
666+
inline=True
667+
)
668+
669+
embed.add_field(
670+
name="📈 Size Range",
671+
value=f"**Largest:** {largest_guild[0][:20]}{'...' if len(largest_guild[0]) > 20 else ''} ({largest_guild[1]:,})\n"
672+
f"**Smallest:** {smallest_guild[0][:20]}{'...' if len(smallest_guild[0]) > 20 else ''} ({smallest_guild[1]:,})",
673+
inline=False
674+
)
675+
676+
# Guild distribution by size
677+
size_ranges = {
678+
"Tiny (1-50)": len([g for g in self.bot.guilds if 1 <= (g.member_count or 0) <= 50]),
679+
"Small (51-200)": len([g for g in self.bot.guilds if 51 <= (g.member_count or 0) <= 200]),
680+
"Medium (201-1000)": len([g for g in self.bot.guilds if 201 <= (g.member_count or 0) <= 1000]),
681+
"Large (1001+)": len([g for g in self.bot.guilds if (g.member_count or 0) > 1000])
682+
}
683+
684+
size_distribution = "\n".join([f"**{size}:** {count}" for size, count in size_ranges.items() if count > 0])
685+
embed.add_field(
686+
name="📏 Size Distribution",
687+
value=size_distribution or "No data available",
688+
inline=False
689+
)
690+
691+
embed.timestamp = datetime.utcnow()
692+
await ctx.send(embed=embed)
693+
694+
@commands.command(name='guildlist', aliases=['serverlist'])
695+
@commands.is_owner()
696+
async def guild_list(self, ctx, page: int = 1):
697+
"""Show paginated list of guilds with details"""
698+
guilds_per_page = 10
699+
guild_list = sorted(self.bot.guilds, key=lambda g: g.member_count or 0, reverse=True)
700+
total_pages = (len(guild_list) + guilds_per_page - 1) // guilds_per_page
701+
702+
if page < 1 or page > total_pages:
703+
await ctx.send(f"❌ Invalid page number. Pages available: 1-{total_pages}")
704+
return
705+
706+
start_idx = (page - 1) * guilds_per_page
707+
end_idx = start_idx + guilds_per_page
708+
page_guilds = guild_list[start_idx:end_idx]
709+
710+
embed = discord.Embed(
711+
title=f"🏰 Guild List (Page {page}/{total_pages})",
712+
color=discord.Color.blue()
713+
)
714+
715+
for i, guild in enumerate(page_guilds, start=start_idx + 1):
716+
premium_indicator = "👑" if getattr(guild, 'premium_tier', 0) > 0 else ""
717+
large_indicator = "🏢" if getattr(guild, 'large', False) else ""
718+
719+
embed.add_field(
720+
name=f"{i}. {guild.name} {premium_indicator}{large_indicator}",
721+
value=f"**ID:** `{guild.id}`\n"
722+
f"**Members:** {guild.member_count or 0:,}\n"
723+
f"**Owner:** <@{guild.owner_id}>" if guild.owner_id else "Unknown",
724+
inline=True
725+
)
726+
727+
embed.set_footer(text=f"Total: {len(guild_list)} guilds • Use .guildlist <page> to navigate")
728+
await ctx.send(embed=embed)
729+
730+
@commands.command(name='guildinfo', aliases=['serverinfo'])
731+
@commands.is_owner()
732+
async def guild_info(self, ctx, *, guild_query: str):
733+
"""Get detailed information about a specific guild"""
734+
# Try to find guild by ID first, then by name
735+
guild = None
736+
if guild_query.isdigit():
737+
guild = self.bot.get_guild(int(guild_query))
738+
739+
if not guild:
740+
# Search by name (case insensitive)
741+
guild = discord.utils.find(
742+
lambda g: guild_query.lower() in g.name.lower(),
743+
self.bot.guilds
744+
)
745+
746+
if not guild:
747+
await ctx.send(f"❌ Guild not found: `{guild_query}`")
748+
return
749+
750+
embed = discord.Embed(
751+
title=f"🏰 {guild.name}",
752+
color=discord.Color.green()
753+
)
754+
755+
if guild.icon:
756+
embed.set_thumbnail(url=guild.icon.url)
757+
758+
embed.add_field(
759+
name="📊 Basic Info",
760+
value=f"**ID:** `{guild.id}`\n"
761+
f"**Owner:** <@{guild.owner_id}>\n"
762+
f"**Created:** <t:{int(guild.created_at.timestamp())}:R>\n"
763+
f"**Joined:** <t:{int(guild.me.joined_at.timestamp())}:R>" if guild.me.joined_at else "Unknown",
764+
inline=True
765+
)
766+
767+
embed.add_field(
768+
name="👥 Members & Channels",
769+
value=f"**Members:** {guild.member_count or 0:,}\n"
770+
f"**Channels:** {len(guild.channels):,}\n"
771+
f"**Roles:** {len(guild.roles):,}\n"
772+
f"**Emojis:** {len(guild.emojis):,}",
773+
inline=True
774+
)
775+
776+
embed.add_field(
777+
name="🎯 Features",
778+
value=f"**Verification:** {guild.verification_level}\n"
779+
f"**Premium Tier:** {guild.premium_tier if hasattr(guild, 'premium_tier') else 0}\n"
780+
f"**Large Guild:** {'Yes' if getattr(guild, 'large', False) else 'No'}\n"
781+
f"**Features:** {len(guild.features)} special",
782+
inline=True
783+
)
784+
785+
if guild.description:
786+
embed.add_field(
787+
name="📝 Description",
788+
value=guild.description[:500] + ("..." if len(guild.description) > 500 else ""),
789+
inline=False
790+
)
791+
792+
if guild.features:
793+
features_str = ", ".join(guild.features[:10])
794+
if len(guild.features) > 10:
795+
features_str += f" (+{len(guild.features) - 10} more)"
796+
embed.add_field(
797+
name="✨ Special Features",
798+
value=f"`{features_str}`",
799+
inline=False
800+
)
801+
802+
# Bot permissions in the guild
803+
if guild.me:
804+
perms = guild.me.guild_permissions
805+
important_perms = []
806+
if perms.administrator:
807+
important_perms.append("Administrator")
808+
if perms.manage_guild:
809+
important_perms.append("Manage Server")
810+
if perms.manage_channels:
811+
important_perms.append("Manage Channels")
812+
if perms.kick_members:
813+
important_perms.append("Kick Members")
814+
if perms.ban_members:
815+
important_perms.append("Ban Members")
816+
817+
embed.add_field(
818+
name="🔐 Bot Permissions",
819+
value=", ".join(important_perms[:5]) if important_perms else "Basic permissions",
820+
inline=False
821+
)
822+
823+
embed.timestamp = datetime.utcnow()
824+
await ctx.send(embed=embed)
825+
549826
def get_stats_summary(self) -> Dict:
550827
"""Get a summary of current stats"""
551828
uptime = datetime.now() - self.start_time

0 commit comments

Comments
 (0)