feat: Implement comprehensive non-hierarchical logging system

- Created new logging infrastructure with per-component filtering
- Added 6 log levels: DEBUG, INFO, API, WARNING, ERROR, CRITICAL
- Implemented non-hierarchical level control (any combination can be enabled)
- Migrated 917 print() statements across 31 files to structured logging
- Created web UI (system.html) for runtime configuration with dark theme
- Added global level controls to enable/disable levels across all components
- Added timestamp format control (off/time/date/datetime options)
- Implemented log rotation (10MB per file, 5 backups)
- Added API endpoints for dynamic log configuration
- Configured HTTP request logging with filtering via api.requests component
- Intercepted APScheduler logs with proper formatting
- Fixed persistence paths to use /app/memory for Docker volume compatibility
- Fixed checkbox display bug in web UI (enabled_levels now properly shown)
- Changed System Settings button to open in same tab instead of new window

Components: bot, api, api.requests, autonomous, persona, vision, llm,
conversation, mood, dm, scheduled, gpu, media, server, commands,
sentiment, core, apscheduler

All settings persist across container restarts via JSON config.
This commit is contained in:
2026-01-10 20:46:19 +02:00
parent ce00f9bd95
commit 32c2a7b930
34 changed files with 2766 additions and 936 deletions

View File

@@ -23,6 +23,9 @@ from utils.image_handling import (
convert_gif_to_mp4
)
from utils.sleep_responses import SLEEP_RESPONSES
from utils.logger import get_logger
logger = get_logger('autonomous')
# Server-specific memory storage
_server_autonomous_messages = {} # guild_id -> rotating buffer of last general messages
@@ -48,7 +51,7 @@ def save_autonomous_config(config):
def setup_autonomous_speaking():
"""Setup autonomous speaking for all configured servers"""
# This is now handled by the server manager
print("🤖 Autonomous Miku setup delegated to server manager!")
logger.debug("Autonomous Miku setup delegated to server manager!")
async def miku_autonomous_tick_for_server(guild_id: int, action_type="general", force=False, force_action=None):
"""Run autonomous behavior for a specific server"""
@@ -71,12 +74,12 @@ async def miku_say_something_general_for_server(guild_id: int):
"""Miku says something general in a specific server"""
server_config = server_manager.get_server_config(guild_id)
if not server_config:
print(f"⚠️ No config found for server {guild_id}")
logger.warning(f"No config found for server {guild_id}")
return
channel = globals.client.get_channel(server_config.autonomous_channel_id)
if not channel:
print(f"⚠️ Autonomous channel not found for server {guild_id}")
logger.warning(f"Autonomous channel not found for server {guild_id}")
return
# Check if evil mode is active
@@ -123,7 +126,7 @@ async def miku_say_something_general_for_server(guild_id: int):
message = await query_llama(prompt, user_id=f"miku-autonomous-{guild_id}", guild_id=guild_id, response_type="autonomous_general")
if not is_too_similar(message, _server_autonomous_messages[guild_id]):
break
print("🔁 Response was too similar to past messages, retrying...")
logger.debug("Response was too similar to past messages, retrying...")
try:
await channel.send(message)
@@ -131,9 +134,9 @@ async def miku_say_something_general_for_server(guild_id: int):
if len(_server_autonomous_messages[guild_id]) > MAX_HISTORY:
_server_autonomous_messages[guild_id].pop(0)
character_name = "Evil Miku" if evil_mode else "Miku"
print(f"💬 {character_name} said something general in #{channel.name} (Server: {server_config.guild_name})")
logger.info(f"{character_name} said something general in #{channel.name} (Server: {server_config.guild_name})")
except Exception as e:
print(f"⚠️ Failed to send autonomous message: {e}")
logger.error(f"Failed to send autonomous message: {e}")
async def miku_engage_random_user_for_server(guild_id: int, user_id: str = None, engagement_type: str = None):
"""Miku engages a random user in a specific server
@@ -145,17 +148,17 @@ async def miku_engage_random_user_for_server(guild_id: int, user_id: str = None,
"""
server_config = server_manager.get_server_config(guild_id)
if not server_config:
print(f"⚠️ No config found for server {guild_id}")
logger.warning(f"No config found for server {guild_id}")
return
guild = globals.client.get_guild(guild_id)
if not guild:
print(f"⚠️ Guild {guild_id} not found.")
logger.warning(f"Guild {guild_id} not found.")
return
channel = globals.client.get_channel(server_config.autonomous_channel_id)
if not channel:
print(f"⚠️ Autonomous channel not found for server {guild_id}")
logger.warning(f"Autonomous channel not found for server {guild_id}")
return
# Get target user
@@ -164,14 +167,14 @@ async def miku_engage_random_user_for_server(guild_id: int, user_id: str = None,
try:
target = guild.get_member(int(user_id))
if not target:
print(f"⚠️ User {user_id} not found in server {guild_id}")
logger.warning(f"User {user_id} not found in server {guild_id}")
return
if target.bot:
print(f"⚠️ Cannot engage bot user {user_id}")
logger.warning(f"Cannot engage bot user {user_id}")
return
print(f"🎯 Targeting specific user: {target.display_name} (ID: {user_id})")
logger.info(f"Targeting specific user: {target.display_name} (ID: {user_id})")
except ValueError:
print(f"⚠️ Invalid user ID: {user_id}")
logger.warning(f"Invalid user ID: {user_id}")
return
else:
# Pick random user
@@ -181,11 +184,11 @@ async def miku_engage_random_user_for_server(guild_id: int, user_id: str = None,
]
if not members:
print(f"😴 No available members to talk to in server {guild_id}.")
logger.warning(f"No available members to talk to in server {guild_id}.")
return
target = random.choice(members)
print(f"🎲 Randomly selected user: {target.display_name}")
logger.info(f"Randomly selected user: {target.display_name}")
time_of_day = get_time_of_day()
@@ -196,7 +199,7 @@ async def miku_engage_random_user_for_server(guild_id: int, user_id: str = None,
now = time.time()
last_time = _server_user_engagements[guild_id].get(target.id, 0)
if now - last_time < 43200: # 12 hours in seconds
print(f"⏱️ Recently engaged {target.display_name} in server {guild_id}, switching to general message.")
logger.info(f"Recently engaged {target.display_name} in server {guild_id}, switching to general message.")
await miku_say_something_general_for_server(guild_id)
return
@@ -286,7 +289,7 @@ async def miku_engage_random_user_for_server(guild_id: int, user_id: str = None,
)
if engagement_type:
print(f"💬 Engagement type: {engagement_type}")
logger.debug(f"Engagement type: {engagement_type}")
try:
# Use consistent user_id for engaging users to enable conversation history
@@ -294,9 +297,9 @@ async def miku_engage_random_user_for_server(guild_id: int, user_id: str = None,
await channel.send(f"{target.mention} {message}")
_server_user_engagements[guild_id][target.id] = time.time()
character_name = "Evil Miku" if evil_mode else "Miku"
print(f"👤 {character_name} engaged {display_name} in server {server_config.guild_name}")
logger.info(f"{character_name} engaged {display_name} in server {server_config.guild_name}")
except Exception as e:
print(f"⚠️ Failed to engage user: {e}")
logger.error(f"Failed to engage user: {e}")
async def miku_detect_and_join_conversation_for_server(guild_id: int, force: bool = False):
"""Miku detects and joins conversations in a specific server
@@ -305,30 +308,30 @@ async def miku_detect_and_join_conversation_for_server(guild_id: int, force: boo
guild_id: The server ID
force: If True, bypass activity checks and random chance (for manual triggers)
"""
print(f"🔍 [Join Conv] Called for server {guild_id} (force={force})")
logger.debug(f"[Join Conv] Called for server {guild_id} (force={force})")
server_config = server_manager.get_server_config(guild_id)
if not server_config:
print(f"⚠️ No config found for server {guild_id}")
logger.warning(f"No config found for server {guild_id}")
return
channel = globals.client.get_channel(server_config.autonomous_channel_id)
if not isinstance(channel, TextChannel):
print(f"⚠️ Autonomous channel is invalid or not found for server {guild_id}")
logger.warning(f"Autonomous channel is invalid or not found for server {guild_id}")
return
# Fetch last 20 messages (for filtering)
try:
messages = [msg async for msg in channel.history(limit=20)]
print(f"📜 [Join Conv] Fetched {len(messages)} messages from history")
logger.debug(f"[Join Conv] Fetched {len(messages)} messages from history")
except Exception as e:
print(f"⚠️ Failed to fetch channel history for server {guild_id}: {e}")
logger.error(f"Failed to fetch channel history for server {guild_id}: {e}")
return
# Filter messages based on force mode
if force:
# When forced, use messages from real users (no time limit) - but limit to last 10
recent_msgs = [msg for msg in messages if not msg.author.bot][:10]
print(f"📊 [Join Conv] Force mode: Using last {len(recent_msgs)} messages from users (no time limit)")
logger.debug(f"[Join Conv] Force mode: Using last {len(recent_msgs)} messages from users (no time limit)")
else:
# Normal mode: Filter to messages in last 10 minutes from real users (not bots)
recent_msgs = [
@@ -336,23 +339,23 @@ async def miku_detect_and_join_conversation_for_server(guild_id: int, force: boo
if not msg.author.bot
and (datetime.now(msg.created_at.tzinfo) - msg.created_at).total_seconds() < 600
]
print(f"📊 [Join Conv] Found {len(recent_msgs)} recent messages from users (last 10 min)")
logger.debug(f"[Join Conv] Found {len(recent_msgs)} recent messages from users (last 10 min)")
user_ids = set(msg.author.id for msg in recent_msgs)
if not force:
if len(recent_msgs) < 5 or len(user_ids) < 2:
# Not enough activity
print(f"⚠️ [Join Conv] Not enough activity: {len(recent_msgs)} messages, {len(user_ids)} users (need 5+ messages, 2+ users)")
logger.debug(f"[Join Conv] Not enough activity: {len(recent_msgs)} messages, {len(user_ids)} users (need 5+ messages, 2+ users)")
return
if random.random() > 0.5:
print(f"🎲 [Join Conv] Random chance failed (50% chance)")
logger.debug(f"[Join Conv] Random chance failed (50% chance)")
return # 50% chance to engage
else:
print(f"[Join Conv] Force mode - bypassing activity checks")
logger.debug(f"[Join Conv] Force mode - bypassing activity checks")
if len(recent_msgs) < 1:
print(f"⚠️ [Join Conv] No messages found in channel history")
logger.warning(f"[Join Conv] No messages found in channel history")
return
# Use last 10 messages for context (oldest to newest)
@@ -386,27 +389,27 @@ async def miku_detect_and_join_conversation_for_server(guild_id: int, force: boo
reply = await query_llama(prompt, user_id=f"miku-conversation-{guild_id}", guild_id=guild_id, response_type="conversation_join")
await channel.send(reply)
character_name = "Evil Miku" if evil_mode else "Miku"
print(f"💬 {character_name} joined an ongoing conversation in server {server_config.guild_name}")
logger.info(f"{character_name} joined an ongoing conversation in server {server_config.guild_name}")
except Exception as e:
print(f"⚠️ Failed to interject in conversation: {e}")
logger.error(f"Failed to interject in conversation: {e}")
async def share_miku_tweet_for_server(guild_id: int):
"""Share a Miku tweet in a specific server"""
server_config = server_manager.get_server_config(guild_id)
if not server_config:
print(f"⚠️ No config found for server {guild_id}")
logger.warning(f"No config found for server {guild_id}")
return
channel = globals.client.get_channel(server_config.autonomous_channel_id)
tweets = await fetch_miku_tweets(limit=5)
if not tweets:
print(f"📭 No good tweets found for server {guild_id}")
logger.warning(f"No good tweets found for server {guild_id}")
return
fresh_tweets = [t for t in tweets if t["url"] not in LAST_SENT_TWEETS]
if not fresh_tweets:
print(f"⚠️ All fetched tweets were recently sent in server {guild_id}. Reusing tweets.")
logger.warning(f"All fetched tweets were recently sent in server {guild_id}. Reusing tweets.")
fresh_tweets = tweets
tweet = random.choice(fresh_tweets)
@@ -454,12 +457,12 @@ async def handle_custom_prompt_for_server(guild_id: int, user_prompt: str):
"""Handle custom prompt for a specific server"""
server_config = server_manager.get_server_config(guild_id)
if not server_config:
print(f"⚠️ No config found for server {guild_id}")
logger.warning(f"No config found for server {guild_id}")
return False
channel = globals.client.get_channel(server_config.autonomous_channel_id)
if not channel:
print(f"⚠️ Autonomous channel not found for server {guild_id}")
logger.warning(f"Autonomous channel not found for server {guild_id}")
return False
mood = server_config.current_mood_name
@@ -478,7 +481,7 @@ async def handle_custom_prompt_for_server(guild_id: int, user_prompt: str):
# Use consistent user_id for manual prompts to enable conversation history
message = await query_llama(prompt, user_id=f"miku-manual-{guild_id}", guild_id=guild_id, response_type="autonomous_general")
await channel.send(message)
print(f"🎤 Miku responded to custom prompt in server {server_config.guild_name}")
logger.info(f"Miku responded to custom prompt in server {server_config.guild_name}")
# Add to server-specific message history
if guild_id not in _server_autonomous_messages:
@@ -489,7 +492,7 @@ async def handle_custom_prompt_for_server(guild_id: int, user_prompt: str):
return True
except Exception as e:
print(f"Failed to send custom autonomous message: {e}")
logger.error(f"Failed to send custom autonomous message: {e}")
return False
# Legacy functions for backward compatibility - these now delegate to server-specific versions
@@ -542,7 +545,7 @@ def load_last_sent_tweets():
with open(LAST_SENT_TWEETS_FILE, "r", encoding="utf-8") as f:
LAST_SENT_TWEETS = json.load(f)
except Exception as e:
print(f"⚠️ Failed to load last sent tweets: {e}")
logger.error(f"Failed to load last sent tweets: {e}")
LAST_SENT_TWEETS = []
else:
LAST_SENT_TWEETS = []
@@ -552,7 +555,7 @@ def save_last_sent_tweets():
with open(LAST_SENT_TWEETS_FILE, "w", encoding="utf-8") as f:
json.dump(LAST_SENT_TWEETS, f)
except Exception as e:
print(f"⚠️ Failed to save last sent tweets: {e}")
logger.error(f"Failed to save last sent tweets: {e}")
def get_time_of_day():
hour = datetime.now().hour + 3
@@ -602,7 +605,7 @@ async def _analyze_message_media(message):
try:
# Handle images
if any(attachment.filename.lower().endswith(ext) for ext in [".jpg", ".jpeg", ".png", ".webp"]):
print(f" 📸 Analyzing image for reaction: {attachment.filename}")
logger.debug(f" Analyzing image for reaction: {attachment.filename}")
base64_img = await download_and_encode_image(attachment.url)
if base64_img:
description = await analyze_image_with_qwen(base64_img)
@@ -612,7 +615,7 @@ async def _analyze_message_media(message):
elif any(attachment.filename.lower().endswith(ext) for ext in [".gif", ".mp4", ".webm", ".mov"]):
is_gif = attachment.filename.lower().endswith('.gif')
media_type = "GIF" if is_gif else "video"
print(f" 🎬 Analyzing {media_type} for reaction: {attachment.filename}")
logger.debug(f" Analyzing {media_type} for reaction: {attachment.filename}")
# Download media
media_bytes_b64 = await download_and_encode_media(attachment.url)
@@ -635,7 +638,7 @@ async def _analyze_message_media(message):
return f"[{media_type}: {description}]"
except Exception as e:
print(f" ⚠️ Error analyzing media for reaction: {e}")
logger.warning(f" Error analyzing media for reaction: {e}")
continue
return None
@@ -650,25 +653,25 @@ async def miku_autonomous_reaction_for_server(guild_id: int, force_message=None,
"""
# 50% chance to proceed (unless forced or with a specific message)
if not force and force_message is None and random.random() > 0.5:
print(f"🎲 Autonomous reaction skipped for server {guild_id} (50% chance)")
logger.debug(f"Autonomous reaction skipped for server {guild_id} (50% chance)")
return
server_config = server_manager.get_server_config(guild_id)
if not server_config:
print(f"⚠️ No config found for server {guild_id}")
logger.warning(f"No config found for server {guild_id}")
return
server_name = server_config.guild_name
# Don't react if asleep
if server_config.current_mood_name == "asleep" or server_config.is_sleeping:
print(f"💤 [{server_name}] Miku is asleep, skipping autonomous reaction")
logger.info(f"[{server_name}] Miku is asleep, skipping autonomous reaction")
return
# Get the autonomous channel
channel = globals.client.get_channel(server_config.autonomous_channel_id)
if not channel:
print(f"⚠️ [{server_name}] Autonomous channel not found")
logger.warning(f"[{server_name}] Autonomous channel not found")
return
try:
@@ -677,9 +680,9 @@ async def miku_autonomous_reaction_for_server(guild_id: int, force_message=None,
target_message = force_message
# Check if we've already reacted to this message
if target_message.id in _reacted_message_ids:
print(f"⏭️ [{server_name}] Already reacted to message {target_message.id}, skipping")
logger.debug(f"[{server_name}] Already reacted to message {target_message.id}, skipping")
return
print(f"🎯 [{server_name}] Reacting to new message from {target_message.author.display_name}")
logger.info(f"[{server_name}] Reacting to new message from {target_message.author.display_name}")
else:
# Fetch recent messages (last 50 messages to get more candidates)
messages = []
@@ -697,14 +700,14 @@ async def miku_autonomous_reaction_for_server(guild_id: int, force_message=None,
messages.append(message)
if not messages:
print(f"📭 [{server_name}] No recent unreacted messages to react to")
logger.debug(f"[{server_name}] No recent unreacted messages to react to")
return
# Pick a random message from the recent ones
target_message = random.choice(messages)
# Analyze any media in the message
print(f"🔍 [{server_name}] Analyzing message for reaction from {target_message.author.display_name}")
logger.debug(f"[{server_name}] Analyzing message for reaction from {target_message.author.display_name}")
media_description = await _analyze_message_media(target_message)
# Build message content with media description if present
@@ -764,7 +767,7 @@ async def miku_autonomous_reaction_for_server(guild_id: int, force_message=None,
emoji = emojis[0]
else:
# No emoji found in response, use fallback
print(f"⚠️ [{server_name}] LLM response contained no emoji: '{original_response[:50]}' - using fallback")
logger.warning(f"[{server_name}] LLM response contained no emoji: '{original_response[:50]}' - using fallback")
emoji = "💙"
# Final validation: try adding the reaction
@@ -772,7 +775,7 @@ async def miku_autonomous_reaction_for_server(guild_id: int, force_message=None,
await target_message.add_reaction(emoji)
except discord.HTTPException as e:
if "Unknown Emoji" in str(e):
print(f"[{server_name}] Invalid emoji from LLM: '{original_response[:50]}' - using fallback")
logger.warning(f"[{server_name}] Invalid emoji from LLM: '{original_response[:50]}' - using fallback")
emoji = "💙"
await target_message.add_reaction(emoji)
else:
@@ -789,14 +792,14 @@ async def miku_autonomous_reaction_for_server(guild_id: int, force_message=None,
for msg_id in ids_to_remove:
_reacted_message_ids.discard(msg_id)
print(f"[{server_name}] Autonomous reaction: Added {emoji} to message from {target_message.author.display_name}")
logger.info(f"[{server_name}] Autonomous reaction: Added {emoji} to message from {target_message.author.display_name}")
except discord.Forbidden:
print(f"[{server_name}] Missing permissions to add reactions")
logger.error(f"[{server_name}] Missing permissions to add reactions")
except discord.HTTPException as e:
print(f"[{server_name}] Failed to add reaction: {e}")
logger.error(f"[{server_name}] Failed to add reaction: {e}")
except Exception as e:
print(f"⚠️ [{server_name}] Error in autonomous reaction: {e}")
logger.error(f"[{server_name}] Error in autonomous reaction: {e}")
async def miku_autonomous_reaction(force=False):
"""Legacy function - run autonomous reactions for all servers
@@ -816,14 +819,14 @@ async def miku_autonomous_reaction_for_dm(user_id: int, force_message=None):
"""
# 50% chance to proceed (unless forced with a specific message)
if force_message is None and random.random() > 0.5:
print(f"🎲 DM reaction skipped for user {user_id} (50% chance)")
logger.debug(f"DM reaction skipped for user {user_id} (50% chance)")
return
# Get the user object
try:
user = await globals.client.fetch_user(user_id)
if not user:
print(f"⚠️ Could not find user {user_id}")
logger.warning(f"Could not find user {user_id}")
return
dm_channel = user.dm_channel
@@ -833,7 +836,7 @@ async def miku_autonomous_reaction_for_dm(user_id: int, force_message=None):
username = user.display_name
except Exception as e:
print(f"⚠️ Error fetching DM channel for user {user_id}: {e}")
logger.error(f"Error fetching DM channel for user {user_id}: {e}")
return
try:
@@ -842,9 +845,9 @@ async def miku_autonomous_reaction_for_dm(user_id: int, force_message=None):
target_message = force_message
# Check if we've already reacted to this message
if target_message.id in _reacted_message_ids:
print(f"⏭️ [DM: {username}] Already reacted to message {target_message.id}, skipping")
logger.debug(f"[DM: {username}] Already reacted to message {target_message.id}, skipping")
return
print(f"🎯 [DM: {username}] Reacting to new message")
logger.info(f"[DM: {username}] Reacting to new message")
else:
# Fetch recent messages from DM (last 50 messages)
messages = []
@@ -862,14 +865,14 @@ async def miku_autonomous_reaction_for_dm(user_id: int, force_message=None):
messages.append(message)
if not messages:
print(f"📭 [DM: {username}] No recent unreacted messages to react to")
logger.debug(f"[DM: {username}] No recent unreacted messages to react to")
return
# Pick a random message from the recent ones
target_message = random.choice(messages)
# Analyze any media in the message
print(f"🔍 [DM: {username}] Analyzing message for reaction")
logger.debug(f"[DM: {username}] Analyzing message for reaction")
media_description = await _analyze_message_media(target_message)
# Build message content with media description if present
@@ -929,7 +932,7 @@ async def miku_autonomous_reaction_for_dm(user_id: int, force_message=None):
emoji = emojis[0]
else:
# No emoji found in response, use fallback
print(f"⚠️ [DM: {username}] LLM response contained no emoji: '{original_response[:50]}' - using fallback")
logger.warning(f"[DM: {username}] LLM response contained no emoji: '{original_response[:50]}' - using fallback")
emoji = "💙"
# Final validation: try adding the reaction
@@ -937,7 +940,7 @@ async def miku_autonomous_reaction_for_dm(user_id: int, force_message=None):
await target_message.add_reaction(emoji)
except discord.HTTPException as e:
if "Unknown Emoji" in str(e):
print(f"[DM: {username}] Invalid emoji from LLM: '{original_response[:50]}' - using fallback")
logger.warning(f"[DM: {username}] Invalid emoji from LLM: '{original_response[:50]}' - using fallback")
emoji = "💙"
await target_message.add_reaction(emoji)
else:
@@ -954,14 +957,14 @@ async def miku_autonomous_reaction_for_dm(user_id: int, force_message=None):
for msg_id in ids_to_remove:
_reacted_message_ids.discard(msg_id)
print(f"[DM: {username}] Autonomous reaction: Added {emoji} to message")
logger.info(f"[DM: {username}] Autonomous reaction: Added {emoji} to message")
except discord.Forbidden:
print(f"[DM: {username}] Missing permissions to add reactions")
logger.error(f"[DM: {username}] Missing permissions to add reactions")
except discord.HTTPException as e:
print(f"[DM: {username}] Failed to add reaction: {e}")
logger.error(f"[DM: {username}] Failed to add reaction: {e}")
except Exception as e:
print(f"⚠️ [DM: {username}] Error in autonomous reaction: {e}")
logger.error(f"[DM: {username}] Error in autonomous reaction: {e}")
async def miku_update_profile_picture_for_server(guild_id: int):
@@ -973,18 +976,18 @@ async def miku_update_profile_picture_for_server(guild_id: int):
# Check if enough time has passed
if not should_update_profile_picture():
print(f"📸 [Server: {guild_id}] Profile picture not ready for update yet")
logger.debug(f"[Server: {guild_id}] Profile picture not ready for update yet")
return
# Get server config to use current mood
server_config = server_manager.get_server_config(guild_id)
if not server_config:
print(f"⚠️ No config found for server {guild_id}")
logger.warning(f"No config found for server {guild_id}")
return
mood = server_config.current_mood_name
print(f"📸 [Server: {guild_id}] Attempting profile picture update (mood: {mood})")
logger.info(f"[Server: {guild_id}] Attempting profile picture update (mood: {mood})")
try:
success = await update_profile_picture(globals.client, mood=mood)
@@ -1001,9 +1004,9 @@ async def miku_update_profile_picture_for_server(guild_id: int):
"*updates avatar* Time for a fresh look! ✨"
]
await channel.send(random.choice(messages))
print(f"[Server: {guild_id}] Profile picture updated and announced!")
logger.info(f"[Server: {guild_id}] Profile picture updated and announced!")
else:
print(f"⚠️ [Server: {guild_id}] Profile picture update failed")
logger.warning(f"[Server: {guild_id}] Profile picture update failed")
except Exception as e:
print(f"⚠️ [Server: {guild_id}] Error updating profile picture: {e}")
logger.error(f"[Server: {guild_id}] Error updating profile picture: {e}")