feat: Enhance emoji and sticker update listeners to process changes more efficiently

This commit is contained in:
Slipstream 2025-05-29 11:23:20 -06:00
parent aadfac5045
commit d7341b3ec2
Signed by: slipstream
GPG Key ID: 13E498CE010AC6FD
2 changed files with 139 additions and 57 deletions

View File

@ -268,57 +268,76 @@ class GurtCog(commands.Cog, name="Gurt"): # Added explicit Cog name
self.user_relationships[user_id_1][user_id_2] = new_score
# print(f"Updated relationship {user_id_1}-{user_id_2}: {current_score:.1f} -> {new_score:.1f} ({change:+.1f})") # Debug log
async def _fetch_and_process_guild_assets(self, guild: discord.Guild):
"""Iterates through a guild's emojis and stickers, generates descriptions, and updates EmojiManager."""
print(f"Processing assets for guild: {guild.name} ({guild.id})")
processed_count = 0
# Emojis
for emoji in guild.emojis:
async def _process_single_emoji(self, emoji: discord.Emoji):
"""Processes a single emoji: generates description if needed and updates EmojiManager."""
try:
name_key = f":{emoji.name}:"
emoji_url = str(emoji.url)
mime_type = "image/gif" if emoji.animated else "image/png"
guild_id = emoji.guild.id # Get guild_id from the emoji object
# Check if already processed with a description to avoid re-processing unless necessary
existing_emoji = await self.emoji_manager.get_emoji(name_key)
if existing_emoji and existing_emoji.get("url") == emoji_url and existing_emoji.get("description") and existing_emoji.get("description") != "No description generated.":
# print(f"Skipping already processed emoji: {name_key} in guild {guild.name}")
continue
if existing_emoji and \
existing_emoji.get("id") == str(emoji.id) and \
existing_emoji.get("url") == emoji_url and \
existing_emoji.get("description") and \
existing_emoji.get("description") != "No description generated.":
# print(f"Skipping already processed emoji: {name_key} in guild {emoji.guild.name}")
return
print(f"Generating description for emoji: {name_key} in guild {guild.name}")
print(f"Generating description for emoji: {name_key} in guild {emoji.guild.name}")
mime_type = "image/gif" if emoji.animated else "image/png"
description = await api.generate_image_description(self, emoji_url, emoji.name, "emoji", mime_type)
await self.emoji_manager.add_emoji(name_key, str(emoji.id), emoji.animated, guild.id, emoji_url, description or "No description generated.")
processed_count +=1
await self.emoji_manager.add_emoji(name_key, str(emoji.id), emoji.animated, guild_id, emoji_url, description or "No description generated.")
await asyncio.sleep(1) # Rate limiting
except Exception as e:
print(f"Error processing emoji {emoji.name} in guild {guild.name}: {e}")
print(f"Error processing single emoji {emoji.name} (ID: {emoji.id}) in guild {emoji.guild.name}: {e}")
# Stickers
for sticker in guild.stickers:
async def _process_single_sticker(self, sticker: discord.StickerItem):
"""Processes a single sticker: generates description if needed and updates EmojiManager."""
try:
name_key = f":{sticker.name}:"
sticker_url = str(sticker.url)
guild_id = sticker.guild_id # Stickers have guild_id directly
existing_sticker = await self.emoji_manager.get_sticker(name_key)
if existing_sticker and existing_sticker.get("url") == sticker_url and existing_sticker.get("description") and existing_sticker.get("description") not in ["No description generated.", "Lottie animation, visual description not applicable."]:
# print(f"Skipping already processed sticker: {name_key} in guild {guild.name}")
continue
if existing_sticker and \
existing_sticker.get("id") == str(sticker.id) and \
existing_sticker.get("url") == sticker_url and \
existing_sticker.get("description") and \
existing_sticker.get("description") not in ["No description generated.", "Lottie animation, visual description not applicable."]:
# print(f"Skipping already processed sticker: {name_key} in guild ID {guild_id}")
return
print(f"Generating description for sticker: {sticker.name} in guild {guild.name}")
print(f"Generating description for sticker: {sticker.name} (ID: {sticker.id}) in guild ID {guild_id}")
description_to_add = "No description generated."
if sticker.format == discord.StickerFormatType.png or sticker.format == discord.StickerFormatType.apng:
mime_type = "image/png" # APNG is also fine as image/png for Gemini
mime_type = "image/png"
description = await api.generate_image_description(self, sticker_url, sticker.name, "sticker", mime_type)
await self.emoji_manager.add_sticker(name_key, str(sticker.id), guild.id, sticker_url, description or "No description generated.")
description_to_add = description or "No description generated."
elif sticker.format == discord.StickerFormatType.lottie:
await self.emoji_manager.add_sticker(name_key, str(sticker.id), guild.id, sticker_url, "Lottie animation, visual description not applicable.")
description_to_add = "Lottie animation, visual description not applicable."
else:
print(f"Skipping sticker {sticker.name} due to unsupported format: {sticker.format}")
await self.emoji_manager.add_sticker(name_key, str(sticker.id), guild.id, sticker_url, f"Unsupported format: {sticker.format}, visual description not applicable.")
processed_count += 1
description_to_add = f"Unsupported format: {sticker.format}, visual description not applicable."
await self.emoji_manager.add_sticker(name_key, str(sticker.id), guild_id, sticker_url, description_to_add)
await asyncio.sleep(1) # Rate limiting
except Exception as e:
print(f"Error processing sticker {sticker.name} in guild {guild.name}: {e}")
print(f"Finished processing {processed_count} new/updated assets for guild: {guild.name} ({guild.id})")
print(f"Error processing single sticker {sticker.name} (ID: {sticker.id}) in guild ID {sticker.guild_id}: {e}")
async def _fetch_and_process_guild_assets(self, guild: discord.Guild):
"""Iterates through a guild's emojis and stickers, and processes each one concurrently."""
print(f"Queueing asset processing for guild: {guild.name} ({guild.id})")
emoji_tasks = [asyncio.create_task(self._process_single_emoji(emoji)) for emoji in guild.emojis]
sticker_tasks = [asyncio.create_task(self._process_single_sticker(sticker)) for sticker in guild.stickers]
all_tasks = emoji_tasks + sticker_tasks
if all_tasks:
await asyncio.gather(*all_tasks, return_exceptions=True) # Wait for all tasks for this guild to complete
print(f"Finished concurrent asset processing for guild: {guild.name} ({guild.id}). Processed {len(all_tasks)} potential items.")
else:
print(f"No emojis or stickers to process for guild: {guild.name} ({guild.id})")
async def initial_emoji_sticker_scan(self):
"""Scans all guilds GURT is in on startup for emojis and stickers."""

View File

@ -650,15 +650,78 @@ async def on_guild_join_listener(cog: 'GurtCog', guild: discord.Guild):
async def on_guild_emojis_update_listener(cog: 'GurtCog', guild: discord.Guild, before: List[discord.Emoji], after: List[discord.Emoji]):
"""Listener function for on_guild_emojis_update."""
print(f"Emojis updated in guild: {guild.name} ({guild.id}). Before: {len(before)}, After: {len(after)}")
# For simplicity and to ensure all changes (add, remove, name change) are caught,
# re-process all emojis for the guild.
# A more optimized approach could diff 'before' and 'after' lists.
print(f"Re-processing all emojis for guild: {guild.name}")
asyncio.create_task(cog._fetch_and_process_guild_assets(guild)) # This will re-process stickers too, which is fine.
before_map = {emoji.id: emoji for emoji in before}
after_map = {emoji.id: emoji for emoji in after}
tasks = []
# Process added emojis
for emoji_id, emoji_obj in after_map.items():
if emoji_id not in before_map:
print(f"New emoji added: {emoji_obj.name} ({emoji_id}) in guild {guild.name}")
tasks.append(asyncio.create_task(cog._process_single_emoji(emoji_obj)))
else:
# Check for changes in existing emojis (e.g., name change)
# The _process_single_emoji method already checks if a description exists and is valid.
# If the name changes, the old key won't match, so it will be treated as new by the manager if name is key.
# If ID is the primary key for checking existence, then a name change might need explicit handling.
# Current EmojiManager uses name as key, so a name change means old is gone, new is added.
# If an emoji's URL or other relevant properties change, _process_single_emoji will handle it.
before_emoji = before_map[emoji_id]
if before_emoji.name != emoji_obj.name or str(before_emoji.url) != str(emoji_obj.url):
print(f"Emoji changed: {before_emoji.name} -> {emoji_obj.name} or URL changed in guild {guild.name}")
# Remove old entry if name changed, as EmojiManager uses name as key
if before_emoji.name != emoji_obj.name:
await cog.emoji_manager.remove_emoji(f":{before_emoji.name}:")
tasks.append(asyncio.create_task(cog._process_single_emoji(emoji_obj)))
# Process removed emojis
for emoji_id, emoji_obj in before_map.items():
if emoji_id not in after_map:
print(f"Emoji removed: {emoji_obj.name} ({emoji_id}) from guild {guild.name}")
await cog.emoji_manager.remove_emoji(f":{emoji_obj.name}:") # Remove by name key
if tasks:
print(f"Queued {len(tasks)} tasks for emoji updates in guild {guild.name}")
await asyncio.gather(*tasks, return_exceptions=True)
else:
print(f"No new or significantly changed emojis to process in guild {guild.name}")
async def on_guild_stickers_update_listener(cog: 'GurtCog', guild: discord.Guild, before: List[discord.StickerItem], after: List[discord.StickerItem]):
"""Listener function for on_guild_stickers_update."""
print(f"Stickers updated in guild: {guild.name} ({guild.id}). Before: {len(before)}, After: {len(after)}")
# Similar to emojis, re-process all assets for simplicity.
print(f"Re-processing all stickers (and emojis) for guild: {guild.name}")
asyncio.create_task(cog._fetch_and_process_guild_assets(guild))
before_map = {sticker.id: sticker for sticker in before}
after_map = {sticker.id: sticker for sticker in after}
tasks = []
# Process added or changed stickers
for sticker_id, sticker_obj in after_map.items():
if sticker_id not in before_map:
print(f"New sticker added: {sticker_obj.name} ({sticker_id}) in guild {guild.name}")
tasks.append(asyncio.create_task(cog._process_single_sticker(sticker_obj)))
else:
before_sticker = before_map[sticker_id]
# Check for relevant changes (name, URL, format)
if before_sticker.name != sticker_obj.name or \
str(before_sticker.url) != str(sticker_obj.url) or \
before_sticker.format != sticker_obj.format:
print(f"Sticker changed: {before_sticker.name} -> {sticker_obj.name} or URL/format changed in guild {guild.name}")
if before_sticker.name != sticker_obj.name:
await cog.emoji_manager.remove_sticker(f":{before_sticker.name}:")
tasks.append(asyncio.create_task(cog._process_single_sticker(sticker_obj)))
# Process removed stickers
for sticker_id, sticker_obj in before_map.items():
if sticker_id not in after_map:
print(f"Sticker removed: {sticker_obj.name} ({sticker_id}) from guild {guild.name}")
await cog.emoji_manager.remove_sticker(f":{sticker_obj.name}:")
if tasks:
print(f"Queued {len(tasks)} tasks for sticker updates in guild {guild.name}")
await asyncio.gather(*tasks, return_exceptions=True)
else:
print(f"No new or significantly changed stickers to process in guild {guild.name}")