Skip to content

Commit f807aeb

Browse files
committed
Updated Emoji Handling & Shard Management
- Introduced `cache_application_emojis` in `emoji_manager.py` to manage application emoji caching. - Updated `shard_manager.py` to store shard distrubution logging/storing code - Refactored `bot.py` to have an `all_shards_loaded` method and moved some startup steps into there. - Updated `about.py` to dynamically fetch the emoji markdown for the bot's information embed, no longer hard-coding the ID value.
1 parent b3a03f3 commit f807aeb

4 files changed

Lines changed: 161 additions & 71 deletions

File tree

src/core/bot.py

Lines changed: 41 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import asyncio
12
import logging
23
import nextcord
34
from nextcord import Interaction, SlashOption
@@ -12,7 +13,9 @@
1213
from config.messages import cached_messages, stored_messages
1314
from core import log_manager
1415
from core.api_connections_manager import start_all_api_connections
16+
from core.emoji_manager import cache_application_emojis
1517
from core.log_manager import LogIfFailure
18+
from core.shard_manager import log_and_store_shard_distribution
1619
from core.server_join_and_leave_manager import handle_server_join, handle_server_remove
1720
from core.scheduling import start_scheduler, stop_scheduler
1821
from core.shard_manager import calculate_shard_count
@@ -44,6 +47,8 @@
4447

4548
from features.options_menu import entrypoint_ui
4649

50+
startup_time = None
51+
4752
# INIT BOT ==============================================================================================================================================================
4853
intents = nextcord.Intents.default()
4954
intents.message_content = True
@@ -64,6 +69,32 @@
6469

6570
def get_bot() -> commands.AutoShardedBot: return bot
6671

72+
async def all_shards_loaded():
73+
"""
74+
Called once all shards are started (not necessarily ready with all info cached)
75+
76+
:return: None
77+
:rtype: None
78+
"""
79+
80+
# Wait a second
81+
await asyncio.sleep(1)
82+
83+
# Update bot ID
84+
global_settings.update_bot_id(bot)
85+
86+
# Initialize the bot's emoji cache
87+
await cache_application_emojis(bot)
88+
89+
# Initialize the bot's view cache
90+
init_views(bot)
91+
92+
# Start API connections
93+
start_all_api_connections()
94+
95+
# Update shard distribution
96+
log_and_store_shard_distribution(bot)
97+
6798
@bot.event
6899
async def on_ready() -> None: # Bot load
69100
"""
@@ -74,82 +105,18 @@ async def on_ready() -> None: # Bot load
74105
:return: None
75106
:rtype: None
76107
"""
77-
startup_start = time.time()
108+
global startup_time
78109

79110
await bot.wait_until_ready()
80111
logging.info(f"============================== Logged in as: {bot.user.name} with all shards ready. ==============================")
81112
logging.info(f"Bot is running with {bot.shard_count} shards across {len(bot.guilds)} guilds.")
82113

83114
# Initialize core systems
84115
init_start = time.time()
85-
init_views(bot)
86116
global_settings.set_bot_load_status(True)
87-
global_settings.update_bot_id(bot)
88117
start_scheduler()
89-
start_all_api_connections()
90118
logging.info(f"Core systems initialized in {time.time() - init_start:.2f}s")
91119

92-
# Log shard distribution and save data for next startup (optimized)
93-
shard_start = time.time()
94-
total_guilds = len(bot.guilds)
95-
shard_guild_counts = {}
96-
97-
# Single iteration through guilds to count by shard
98-
for guild in bot.guilds:
99-
shard_id = guild.shard_id
100-
shard_guild_counts[shard_id] = shard_guild_counts.get(shard_id, 0) + 1
101-
102-
logging.info(f"Shard distribution calculated in {time.time() - shard_start:.2f}s")
103-
104-
# Save guild count data for next startup (async file operations)
105-
try:
106-
shard_config_path = os.path.join("generated", "configure", "shard_config.json")
107-
os.makedirs(os.path.dirname(shard_config_path), exist_ok=True)
108-
109-
# Cache config value to avoid multiple calls
110-
sharding_config = global_settings.get_configs()["sharding"]
111-
112-
shard_data = {
113-
"last_guild_count": total_guilds,
114-
"last_shard_count": bot.shard_count,
115-
"last_updated": "2025-06-24",
116-
"guilds_per_shard_config": sharding_config["guilds-per-shard"]
117-
}
118-
119-
with open(shard_config_path, 'w') as f:
120-
json.dump(shard_data, f, indent=2)
121-
122-
logging.info(f"Saved guild count data: {total_guilds} guilds across {bot.shard_count} shards")
123-
124-
except Exception as e:
125-
logging.warning(f"Could not save shard data: {e}")
126-
127-
# Display shard distribution (optimized)
128-
if shard_guild_counts:
129-
logging.info("Shard distribution:")
130-
max_guilds_per_shard = max(shard_guild_counts.values())
131-
for shard_id in sorted(shard_guild_counts.keys()):
132-
guild_count = shard_guild_counts[shard_id]
133-
logging.info(f" Shard {shard_id}: {guild_count} guilds")
134-
135-
# Intelligent recommendations (cached config)
136-
if sharding_config["enabled"]:
137-
guilds_per_shard = sharding_config["guilds-per-shard"]
138-
optimal_shards = max(1, (total_guilds // guilds_per_shard) + 1)
139-
140-
if max_guilds_per_shard > guilds_per_shard * 1.5: # 50% over target
141-
logging.warning(f"⚠️ HIGH SHARD LOAD: Some shards have {max_guilds_per_shard} guilds (target: {guilds_per_shard}). Consider restarting - next startup will use {optimal_shards} shards.")
142-
elif bot.shard_count < optimal_shards:
143-
logging.info(f"💡 SCALING SUGGESTION: Current: {bot.shard_count} shards, Optimal: {optimal_shards} shards. Restart to apply.")
144-
elif bot.shard_count > optimal_shards * 1.5: # Over-sharded
145-
logging.info(f"💡 OPTIMIZATION: You might be over-sharded. Current: {bot.shard_count}, Optimal: {optimal_shards}. Restart to optimize.")
146-
else:
147-
logging.info(f"✅ SHARD COUNT: Optimal ({bot.shard_count} shards for {total_guilds} guilds)")
148-
else:
149-
logging.info(f"ℹ️ AUTO-SHARDING: Disabled in config. Using Discord's recommendation ({bot.shard_count} shards)")
150-
else:
151-
logging.info("No guilds found on any shards.")
152-
153120
# Print detailed guild info only in debug mode
154121
if logging.getLevelName(logging.getLogger().getEffectiveLevel()) == "DEBUG":
155122
for guild in bot.guilds:
@@ -186,8 +153,8 @@ async def on_ready() -> None: # Bot load
186153
logging.info("Bot startup notification is disabled. Skipping notification.")
187154

188155
# Log total startup time
189-
total_startup_time = time.time() - startup_start
190-
logging.info(f"🚀 STARTUP COMPLETE: Total time {total_startup_time:.2f}s for {total_guilds} guilds ({total_guilds/total_startup_time:.1f} guilds/sec)")
156+
total_startup_time = time.time() - startup_time
157+
logging.info(f"🚀 STARTUP COMPLETE: Total time {total_startup_time:.2f}s for {len(bot.guilds)} guilds ({len(bot.guilds)/total_startup_time:.1f} guilds/sec)")
191158

192159

193160
@bot.event
@@ -206,6 +173,11 @@ async def on_shard_ready(shard_id: int) -> None:
206173
with global_settings.ShardLoadedStatus() as shards_loaded:
207174
shards_loaded.append(shard_id)
208175

176+
# Check if all shards are loaded
177+
if len(shards_loaded) == bot.shard_count:
178+
logging.info(f"All {bot.shard_count} shards are loaded. Running post-shard initialization code...")
179+
await all_shards_loaded()
180+
209181
@bot.event
210182
async def on_close() -> None:
211183
"""
@@ -792,12 +764,14 @@ def run() -> None:
792764
:return: None
793765
:rtype: None
794766
"""
767+
global startup_time
795768

796769
# Get token
797770
token = os.environ['DISCORD_AUTH_TOKEN']
798771
logging.info(f"Running bot with token: {token[:5]}*******...")
799772

800773
logging.info(f"Running in {global_settings.get_environment_type()} mode.")
774+
startup_time = time.time()
801775

802776
try:
803777
bot.run(token)

src/core/emoji_manager.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import nextcord
2+
import logging
3+
4+
application_emoji_cache = []
5+
async def cache_application_emojis(bot: nextcord.Client) -> None:
6+
global application_emoji_cache
7+
application_emoji_cache = await bot.fetch_application_emojis()
8+
9+
def get_emoji_markdown(emoji_name: str, bot=None) -> str:
10+
"""
11+
Returns the markdown representation of an emoji given its name.
12+
13+
Args:
14+
emoji_name (str): The name of the emoji.
15+
bot (nextcord.Client, optional): The bot instance to use for retrieving the emoji. If not provided, it will use the global bot instance.
16+
17+
Returns:
18+
str: The markdown representation of the emoji or a placeholder if not found.
19+
"""
20+
global application_emoji_cache
21+
22+
if len(application_emoji_cache) == 0:
23+
logging.warning("Emoji cache is empty. Ensure that application emojis are cached before calling this function.")
24+
return f":{emoji_name}:"
25+
26+
# Get the bot
27+
from core.bot import get_bot
28+
bot = bot or get_bot()
29+
30+
# Retrieve the emoji object from the bot's emoji cache
31+
emoji = nextcord.utils.get(application_emoji_cache, name=emoji_name)
32+
33+
if emoji:
34+
# Return the markdown representation of the emoji
35+
return str(emoji)
36+
else:
37+
# If the emoji is not found, return a placeholder or an empty string
38+
logging.warning(f"Emoji '{emoji_name}' not found in the bot's emoji cache.")
39+
return f":{emoji_name}:"

src/core/shard_manager.py

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
1+
import nextcord
12
import logging
23
import os
34
import json
5+
import time
6+
import datetime
47

58
import config.global_settings as global_settings
69

7-
# Calculate optimal shard count based on configuration and previous data
810
def calculate_shard_count():
9-
"""Calculate optimal shard count based on configuration and stored guild data"""
11+
"""
12+
Returns the optimal shard count based on config and previous guild data, or None to use Discord's default.
13+
14+
:return: The calculated optimal shard count, or None if auto-sharding is disabled or no data is available.
15+
:rtype: Optional[int]
16+
"""
17+
# Calculate optimal shard count based on configuration and previous data
1018
try:
1119
# Check if sharding is enabled in config
1220
if not global_settings.get_configs()["sharding.enabled"]:
@@ -39,4 +47,72 @@ def calculate_shard_count():
3947

4048
except Exception as e:
4149
logging.warning(f"Error calculating shard count: {e}. Using Discord's recommendation.")
42-
return None
50+
return None
51+
52+
def log_and_store_shard_distribution(bot: nextcord.Client):
53+
"""
54+
Logs the bot's shard distribution, saves guild/shard stats for next startup, and suggests sharding adjustments.
55+
56+
Args:
57+
bot (nextcord.Client): The Discord bot client (with sharding).
58+
"""
59+
# Log shard distribution and save data for next startup (optimized)
60+
shard_start = time.time()
61+
total_guilds = len(bot.guilds)
62+
shard_guild_counts = {}
63+
64+
# Single iteration through guilds to count by shard
65+
for guild in bot.guilds:
66+
shard_id = guild.shard_id
67+
shard_guild_counts[shard_id] = shard_guild_counts.get(shard_id, 0) + 1
68+
69+
logging.info(f"Shard distribution calculated in {time.time() - shard_start:.2f}s")
70+
71+
# Save guild count data for next startup (async file operations)
72+
try:
73+
shard_config_path = os.path.join("generated", "configure", "shard_config.json")
74+
os.makedirs(os.path.dirname(shard_config_path), exist_ok=True)
75+
76+
# Cache config value to avoid multiple calls
77+
sharding_config = global_settings.get_configs()["sharding"]
78+
79+
shard_data = {
80+
"last_guild_count": total_guilds,
81+
"last_shard_count": bot.shard_count,
82+
"last_updated": datetime.datetime.now(datetime.timezone.utc).isoformat(),
83+
"guilds_per_shard_config": sharding_config["guilds-per-shard"]
84+
}
85+
86+
with open(shard_config_path, 'w') as f:
87+
json.dump(shard_data, f, indent=2)
88+
89+
logging.info(f"Saved guild count data: {total_guilds} guilds across {bot.shard_count} shards")
90+
91+
except Exception as e:
92+
logging.warning(f"Could not save shard data: {e}")
93+
94+
# Display shard distribution (optimized)
95+
if shard_guild_counts:
96+
logging.info("Shard distribution:")
97+
max_guilds_per_shard = max(shard_guild_counts.values())
98+
for shard_id in sorted(shard_guild_counts.keys()):
99+
guild_count = shard_guild_counts[shard_id]
100+
logging.info(f" Shard {shard_id}: {guild_count} guilds")
101+
102+
# Intelligent recommendations (cached config)
103+
if sharding_config["enabled"]:
104+
guilds_per_shard = sharding_config["guilds-per-shard"]
105+
optimal_shards = max(1, (total_guilds // guilds_per_shard) + 1)
106+
107+
if max_guilds_per_shard > guilds_per_shard * 1.5: # 50% over target
108+
logging.warning(f"⚠️ HIGH SHARD LOAD: Some shards have {max_guilds_per_shard} guilds (target: {guilds_per_shard}). Consider restarting - next startup will use {optimal_shards} shards.")
109+
elif bot.shard_count < optimal_shards:
110+
logging.info(f"💡 SCALING SUGGESTION: Current: {bot.shard_count} shards, Optimal: {optimal_shards} shards. Restart to apply.")
111+
elif bot.shard_count > optimal_shards * 1.5: # Over-sharded
112+
logging.info(f"💡 OPTIMIZATION: You might be over-sharded. Current: {bot.shard_count}, Optimal: {optimal_shards}. Restart to optimize.")
113+
else:
114+
logging.info(f"✅ SHARD COUNT: Optimal ({bot.shard_count} shards for {total_guilds} guilds)")
115+
else:
116+
logging.info(f"ℹ️ AUTO-SHARDING: Disabled in config. Using Discord's recommendation ({bot.shard_count} shards)")
117+
else:
118+
logging.info("No guilds found on any shards.")

src/features/about.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from datetime import datetime
66

77
from config.global_settings import get_configs
8+
from core.emoji_manager import get_emoji_markdown
89

910
async def run_bot_about_command(interaction: Interaction) -> None:
1011
"""
@@ -84,7 +85,7 @@ async def run_bot_about_command(interaction: Interaction) -> None:
8485

8586
# Create the embed
8687
embed = nextcord.Embed(
87-
title="<:infiniboticon:1379549397708312677>  InfiniBot Information",
88+
title=f"{get_emoji_markdown('infiniboticon')}  InfiniBot Information",
8889
description="A powerful, multipurpose Discord bot for server management and engagement.",
8990
color=nextcord.Color.blurple()
9091
)

0 commit comments

Comments
 (0)