This commit is contained in:
parent
59ef883aef
commit
e103b19638
@ -25,6 +25,7 @@ class StarboardCog(commands.Cog):
|
|||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.emoji_pattern = re.compile(r'<a?:.+?:\d+>|[\U00010000-\U0010ffff]')
|
self.emoji_pattern = re.compile(r'<a?:.+?:\d+>|[\U00010000-\U0010ffff]')
|
||||||
self.pending_updates = {} # Store message IDs that are being processed to prevent race conditions
|
self.pending_updates = {} # Store message IDs that are being processed to prevent race conditions
|
||||||
|
self.lock = asyncio.Lock() # Global lock for database operations
|
||||||
|
|
||||||
@commands.Cog.listener()
|
@commands.Cog.listener()
|
||||||
async def on_raw_reaction_add(self, payload):
|
async def on_raw_reaction_add(self, payload):
|
||||||
@ -94,46 +95,97 @@ class StarboardCog(commands.Cog):
|
|||||||
# Acquire lock for this message to prevent race conditions
|
# Acquire lock for this message to prevent race conditions
|
||||||
message_key = f"{payload.guild_id}:{payload.message_id}"
|
message_key = f"{payload.guild_id}:{payload.message_id}"
|
||||||
if message_key in self.pending_updates:
|
if message_key in self.pending_updates:
|
||||||
|
log.debug(f"Skipping concurrent update for message {payload.message_id} in guild {payload.guild_id}")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.pending_updates[message_key] = True
|
self.pending_updates[message_key] = True
|
||||||
try:
|
try:
|
||||||
# Get the message
|
# Get the message with retry logic
|
||||||
try:
|
message = None
|
||||||
message = await source_channel.fetch_message(payload.message_id)
|
retry_attempts = 3
|
||||||
except discord.NotFound:
|
for attempt in range(retry_attempts):
|
||||||
return
|
try:
|
||||||
except discord.HTTPException as e:
|
message = await source_channel.fetch_message(payload.message_id)
|
||||||
log.error(f"Error fetching message {payload.message_id}: {e}")
|
break
|
||||||
|
except discord.NotFound:
|
||||||
|
log.warning(f"Message {payload.message_id} not found in channel {source_channel.id}")
|
||||||
|
return
|
||||||
|
except discord.HTTPException as e:
|
||||||
|
if attempt < retry_attempts - 1:
|
||||||
|
log.warning(f"Error fetching message {payload.message_id}, attempt {attempt+1}/{retry_attempts}: {e}")
|
||||||
|
await asyncio.sleep(1) # Wait before retrying
|
||||||
|
else:
|
||||||
|
log.error(f"Failed to fetch message {payload.message_id} after {retry_attempts} attempts: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not message:
|
||||||
|
log.error(f"Could not retrieve message {payload.message_id} after multiple attempts")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check if message is from a bot and if we should ignore bot messages
|
# Check if message is from a bot and if we should ignore bot messages
|
||||||
if message.author.bot and settings.get('ignore_bots', True):
|
if message.author.bot and settings.get('ignore_bots', True):
|
||||||
|
log.debug(f"Ignoring bot message {message.id} from {message.author.name}")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check if the user is starring their own message and if that's allowed
|
# Check if the user is starring their own message and if that's allowed
|
||||||
if is_add and payload.user_id == message.author.id and not settings.get('self_star', False):
|
if is_add and payload.user_id == message.author.id and not settings.get('self_star', False):
|
||||||
|
log.debug(f"User {payload.user_id} attempted to star their own message {message.id}, but self-starring is disabled")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Update the reaction in the database
|
# Update the reaction in the database with retry logic
|
||||||
if is_add:
|
star_count = None
|
||||||
star_count = await settings_manager.add_starboard_reaction(
|
retry_attempts = 3
|
||||||
guild.id, message.id, payload.user_id
|
for attempt in range(retry_attempts):
|
||||||
)
|
try:
|
||||||
else:
|
if is_add:
|
||||||
star_count = await settings_manager.remove_starboard_reaction(
|
star_count = await settings_manager.add_starboard_reaction(
|
||||||
guild.id, message.id, payload.user_id
|
guild.id, message.id, payload.user_id
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
star_count = await settings_manager.remove_starboard_reaction(
|
||||||
|
guild.id, message.id, payload.user_id
|
||||||
|
)
|
||||||
|
|
||||||
|
# If we got a valid count, break out of the retry loop
|
||||||
|
if isinstance(star_count, int):
|
||||||
|
break
|
||||||
|
|
||||||
|
# If we couldn't get a valid count, try to fetch it directly
|
||||||
|
star_count = await settings_manager.get_starboard_reaction_count(guild.id, message.id)
|
||||||
|
if isinstance(star_count, int):
|
||||||
|
break
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
if attempt < retry_attempts - 1:
|
||||||
|
log.warning(f"Error updating reaction for message {message.id}, attempt {attempt+1}/{retry_attempts}: {e}")
|
||||||
|
await asyncio.sleep(1) # Wait before retrying
|
||||||
|
else:
|
||||||
|
log.error(f"Failed to update reaction for message {message.id} after {retry_attempts} attempts: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
# If we couldn't get a valid count, fetch it directly
|
|
||||||
if not isinstance(star_count, int):
|
if not isinstance(star_count, int):
|
||||||
star_count = await settings_manager.get_starboard_reaction_count(guild.id, message.id)
|
log.error(f"Could not get valid star count for message {message.id}")
|
||||||
|
return
|
||||||
|
|
||||||
|
log.info(f"Message {message.id} in guild {guild.id} now has {star_count} stars (action: {'add' if is_add else 'remove'})")
|
||||||
|
|
||||||
# Get the threshold from settings
|
# Get the threshold from settings
|
||||||
threshold = settings.get('threshold', 3)
|
threshold = settings.get('threshold', 3)
|
||||||
|
|
||||||
# Check if this message is already in the starboard
|
# Check if this message is already in the starboard
|
||||||
entry = await settings_manager.get_starboard_entry(guild.id, message.id)
|
entry = None
|
||||||
|
retry_attempts = 3
|
||||||
|
for attempt in range(retry_attempts):
|
||||||
|
try:
|
||||||
|
entry = await settings_manager.get_starboard_entry(guild.id, message.id)
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
if attempt < retry_attempts - 1:
|
||||||
|
log.warning(f"Error getting starboard entry for message {message.id}, attempt {attempt+1}/{retry_attempts}: {e}")
|
||||||
|
await asyncio.sleep(1) # Wait before retrying
|
||||||
|
else:
|
||||||
|
log.error(f"Failed to get starboard entry for message {message.id} after {retry_attempts} attempts: {e}")
|
||||||
|
# Continue with entry=None, which will create a new entry if needed
|
||||||
|
|
||||||
if star_count >= threshold:
|
if star_count >= threshold:
|
||||||
# Message should be in starboard
|
# Message should be in starboard
|
||||||
@ -143,40 +195,50 @@ class StarboardCog(commands.Cog):
|
|||||||
starboard_message = await starboard_channel.fetch_message(entry.get('starboard_message_id'))
|
starboard_message = await starboard_channel.fetch_message(entry.get('starboard_message_id'))
|
||||||
await self._update_starboard_message(starboard_message, message, star_count)
|
await self._update_starboard_message(starboard_message, message, star_count)
|
||||||
await settings_manager.update_starboard_entry(guild.id, message.id, star_count)
|
await settings_manager.update_starboard_entry(guild.id, message.id, star_count)
|
||||||
|
log.info(f"Updated starboard message {starboard_message.id} for original message {message.id}")
|
||||||
except discord.NotFound:
|
except discord.NotFound:
|
||||||
# Starboard message was deleted, create a new one
|
# Starboard message was deleted, create a new one
|
||||||
|
log.warning(f"Starboard message {entry.get('starboard_message_id')} was deleted, creating a new one")
|
||||||
starboard_message = await self._create_starboard_message(starboard_channel, message, star_count)
|
starboard_message = await self._create_starboard_message(starboard_channel, message, star_count)
|
||||||
if starboard_message:
|
if starboard_message:
|
||||||
await settings_manager.create_starboard_entry(
|
await settings_manager.create_starboard_entry(
|
||||||
guild.id, message.id, source_channel.id,
|
guild.id, message.id, source_channel.id,
|
||||||
starboard_message.id, message.author.id, star_count
|
starboard_message.id, message.author.id, star_count
|
||||||
)
|
)
|
||||||
|
log.info(f"Created new starboard message {starboard_message.id} for original message {message.id}")
|
||||||
except discord.HTTPException as e:
|
except discord.HTTPException as e:
|
||||||
log.error(f"Error updating starboard message: {e}")
|
log.error(f"Error updating starboard message for {message.id}: {e}")
|
||||||
else:
|
else:
|
||||||
# Create new entry
|
# Create new entry
|
||||||
|
log.info(f"Creating new starboard entry for message {message.id} with {star_count} stars")
|
||||||
starboard_message = await self._create_starboard_message(starboard_channel, message, star_count)
|
starboard_message = await self._create_starboard_message(starboard_channel, message, star_count)
|
||||||
if starboard_message:
|
if starboard_message:
|
||||||
await settings_manager.create_starboard_entry(
|
await settings_manager.create_starboard_entry(
|
||||||
guild.id, message.id, source_channel.id,
|
guild.id, message.id, source_channel.id,
|
||||||
starboard_message.id, message.author.id, star_count
|
starboard_message.id, message.author.id, star_count
|
||||||
)
|
)
|
||||||
|
log.info(f"Created starboard message {starboard_message.id} for original message {message.id}")
|
||||||
elif entry:
|
elif entry:
|
||||||
# Message is below threshold but exists in starboard
|
# Message is below threshold but exists in starboard
|
||||||
|
log.info(f"Message {message.id} now has {star_count} stars, below threshold of {threshold}. Removing from starboard.")
|
||||||
try:
|
try:
|
||||||
# Delete the starboard message if it exists
|
# Delete the starboard message if it exists
|
||||||
starboard_message = await starboard_channel.fetch_message(entry.get('starboard_message_id'))
|
starboard_message = await starboard_channel.fetch_message(entry.get('starboard_message_id'))
|
||||||
await starboard_message.delete()
|
await starboard_message.delete()
|
||||||
except (discord.NotFound, discord.HTTPException):
|
log.info(f"Deleted starboard message {entry.get('starboard_message_id')}")
|
||||||
pass # Message already deleted or couldn't be deleted
|
except discord.NotFound:
|
||||||
|
log.warning(f"Starboard message {entry.get('starboard_message_id')} already deleted")
|
||||||
|
except discord.HTTPException as e:
|
||||||
|
log.error(f"Error deleting starboard message {entry.get('starboard_message_id')}: {e}")
|
||||||
|
|
||||||
# Delete the entry from the database
|
# Delete the entry from the database
|
||||||
# Note: We don't have a dedicated function for this yet, but we could add one
|
await settings_manager.delete_starboard_entry(guild.id, message.id)
|
||||||
# For now, we'll just update the star count
|
except Exception as e:
|
||||||
await settings_manager.update_starboard_entry(guild.id, message.id, star_count)
|
log.exception(f"Unexpected error processing star reaction for message {payload.message_id}: {e}")
|
||||||
finally:
|
finally:
|
||||||
# Release the lock
|
# Release the lock
|
||||||
self.pending_updates.pop(message_key, None)
|
self.pending_updates.pop(message_key, None)
|
||||||
|
log.debug(f"Released lock for message {payload.message_id} in guild {payload.guild_id}")
|
||||||
|
|
||||||
async def _create_starboard_message(self, starboard_channel, message, star_count):
|
async def _create_starboard_message(self, starboard_channel, message, star_count):
|
||||||
"""Create a new message in the starboard channel."""
|
"""Create a new message in the starboard channel."""
|
||||||
@ -209,6 +271,8 @@ class StarboardCog(commands.Cog):
|
|||||||
|
|
||||||
def _create_starboard_embed(self, message, star_count):
|
def _create_starboard_embed(self, message, star_count):
|
||||||
"""Create an embed for the starboard message."""
|
"""Create an embed for the starboard message."""
|
||||||
|
# We're not using star_count in the embed directly, but it's passed for potential future use
|
||||||
|
# such as changing embed color based on star count
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
description=message.content,
|
description=message.content,
|
||||||
color=0xFFAC33, # Gold color for stars
|
color=0xFFAC33, # Gold color for stars
|
||||||
@ -368,6 +432,145 @@ class StarboardCog(commands.Cog):
|
|||||||
|
|
||||||
await ctx.send(embed=embed)
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
@starboard_group.command(name="clear", description="Clear all starboard entries")
|
||||||
|
@commands.has_permissions(administrator=True)
|
||||||
|
@app_commands.default_permissions(administrator=True)
|
||||||
|
async def starboard_clear(self, ctx):
|
||||||
|
"""Clear all entries from the starboard."""
|
||||||
|
# Ask for confirmation
|
||||||
|
await ctx.send("⚠️ **Warning**: This will delete all starboard entries for this server. Are you sure? (yes/no)")
|
||||||
|
|
||||||
|
def check(m):
|
||||||
|
return m.author == ctx.author and m.channel == ctx.channel and m.content.lower() in ["yes", "no"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Wait for confirmation
|
||||||
|
response = await self.bot.wait_for("message", check=check, timeout=30.0)
|
||||||
|
|
||||||
|
if response.content.lower() != "yes":
|
||||||
|
await ctx.send("❌ Operation cancelled.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get the starboard channel
|
||||||
|
settings = await settings_manager.get_starboard_settings(ctx.guild.id)
|
||||||
|
if not settings or not settings.get('starboard_channel_id'):
|
||||||
|
await ctx.send("❌ Starboard channel not set.")
|
||||||
|
return
|
||||||
|
|
||||||
|
starboard_channel = ctx.guild.get_channel(settings.get('starboard_channel_id'))
|
||||||
|
if not starboard_channel:
|
||||||
|
await ctx.send("❌ Starboard channel not found.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get all entries
|
||||||
|
entries = await settings_manager.clear_starboard_entries(ctx.guild.id)
|
||||||
|
|
||||||
|
if not entries:
|
||||||
|
await ctx.send("✅ Starboard cleared. No entries were found.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Delete all messages from the starboard channel
|
||||||
|
status_message = await ctx.send(f"🔄 Clearing {len(entries)} entries from the starboard...")
|
||||||
|
|
||||||
|
deleted_count = 0
|
||||||
|
failed_count = 0
|
||||||
|
|
||||||
|
# Convert entries to a list of dictionaries
|
||||||
|
entries_list = [dict(entry) for entry in entries]
|
||||||
|
|
||||||
|
# Delete messages in batches to avoid rate limits
|
||||||
|
for entry in entries_list:
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
message = await starboard_channel.fetch_message(entry['starboard_message_id'])
|
||||||
|
await message.delete()
|
||||||
|
deleted_count += 1
|
||||||
|
except discord.NotFound:
|
||||||
|
# Message already deleted
|
||||||
|
deleted_count += 1
|
||||||
|
except discord.HTTPException as e:
|
||||||
|
log.error(f"Error deleting starboard message {entry['starboard_message_id']}: {e}")
|
||||||
|
failed_count += 1
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"Unexpected error deleting starboard message: {e}")
|
||||||
|
failed_count += 1
|
||||||
|
|
||||||
|
await status_message.edit(content=f"✅ Starboard cleared. Deleted {deleted_count} messages. Failed to delete {failed_count} messages.")
|
||||||
|
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
await ctx.send("❌ Confirmation timed out. Operation cancelled.")
|
||||||
|
except Exception as e:
|
||||||
|
log.exception(f"Error clearing starboard: {e}")
|
||||||
|
await ctx.send(f"❌ An error occurred while clearing the starboard: {str(e)}")
|
||||||
|
|
||||||
|
@starboard_group.command(name="stats", description="Show starboard statistics")
|
||||||
|
async def starboard_stats(self, ctx):
|
||||||
|
"""Display statistics about the starboard."""
|
||||||
|
try:
|
||||||
|
# Get the starboard settings
|
||||||
|
settings = await settings_manager.get_starboard_settings(ctx.guild.id)
|
||||||
|
if not settings:
|
||||||
|
await ctx.send("❌ Failed to retrieve starboard settings.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get a connection to the database
|
||||||
|
conn = await asyncio.wait_for(settings_manager.pg_pool.acquire(), timeout=5.0)
|
||||||
|
try:
|
||||||
|
# Get the total number of entries
|
||||||
|
total_entries = await conn.fetchval(
|
||||||
|
"""
|
||||||
|
SELECT COUNT(*) FROM starboard_entries
|
||||||
|
WHERE guild_id = $1
|
||||||
|
""",
|
||||||
|
ctx.guild.id
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get the total number of reactions
|
||||||
|
total_reactions = await conn.fetchval(
|
||||||
|
"""
|
||||||
|
SELECT COUNT(*) FROM starboard_reactions
|
||||||
|
WHERE guild_id = $1
|
||||||
|
""",
|
||||||
|
ctx.guild.id
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get the most starred message
|
||||||
|
most_starred = await conn.fetchrow(
|
||||||
|
"""
|
||||||
|
SELECT * FROM starboard_entries
|
||||||
|
WHERE guild_id = $1
|
||||||
|
ORDER BY star_count DESC
|
||||||
|
LIMIT 1
|
||||||
|
""",
|
||||||
|
ctx.guild.id
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create an embed to display the statistics
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="Starboard Statistics",
|
||||||
|
color=discord.Color.gold(),
|
||||||
|
timestamp=datetime.datetime.now()
|
||||||
|
)
|
||||||
|
|
||||||
|
embed.add_field(name="Total Entries", value=str(total_entries), inline=True)
|
||||||
|
embed.add_field(name="Total Reactions", value=str(total_reactions), inline=True)
|
||||||
|
|
||||||
|
if most_starred:
|
||||||
|
most_starred_dict = dict(most_starred)
|
||||||
|
embed.add_field(
|
||||||
|
name="Most Starred Message",
|
||||||
|
value=f"[Jump to Message](https://discord.com/channels/{ctx.guild.id}/{most_starred_dict['original_channel_id']}/{most_starred_dict['original_message_id']})\n{most_starred_dict['star_count']} stars",
|
||||||
|
inline=False
|
||||||
|
)
|
||||||
|
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
finally:
|
||||||
|
# Release the connection
|
||||||
|
await settings_manager.pg_pool.release(conn)
|
||||||
|
except Exception as e:
|
||||||
|
log.exception(f"Error getting starboard statistics: {e}")
|
||||||
|
await ctx.send(f"❌ An error occurred while getting starboard statistics: {str(e)}")
|
||||||
|
|
||||||
async def setup(bot):
|
async def setup(bot):
|
||||||
"""Add the cog to the bot."""
|
"""Add the cog to the bot."""
|
||||||
await bot.add_cog(StarboardCog(bot))
|
await bot.add_cog(StarboardCog(bot))
|
||||||
|
@ -367,27 +367,67 @@ async def update_starboard_settings(guild_id: int, **kwargs):
|
|||||||
log.warning(f"No valid settings provided for starboard update for guild {guild_id}")
|
log.warning(f"No valid settings provided for starboard update for guild {guild_id}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Use a timeout to prevent hanging on database operations
|
||||||
try:
|
try:
|
||||||
async with pg_pool.acquire() as conn:
|
# Acquire a connection with a timeout
|
||||||
|
conn = None
|
||||||
|
try:
|
||||||
|
conn = await asyncio.wait_for(pg_pool.acquire(), timeout=5.0)
|
||||||
|
|
||||||
# Ensure guild exists
|
# Ensure guild exists
|
||||||
await conn.execute("INSERT INTO guilds (guild_id) VALUES ($1) ON CONFLICT (guild_id) DO NOTHING;", guild_id)
|
try:
|
||||||
|
await conn.execute("INSERT INTO guilds (guild_id) VALUES ($1) ON CONFLICT (guild_id) DO NOTHING;", guild_id)
|
||||||
|
except Exception as e:
|
||||||
|
if "another operation is in progress" in str(e) or "attached to a different loop" in str(e):
|
||||||
|
log.warning(f"Connection issue when inserting guild {guild_id}: {e}")
|
||||||
|
# Try to reset the connection
|
||||||
|
await conn.close()
|
||||||
|
conn = await asyncio.wait_for(pg_pool.acquire(), timeout=5.0)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
# Build the SET clause for the UPDATE statement
|
# Build the SET clause for the UPDATE statement
|
||||||
set_clause = ", ".join(f"{key} = ${i+2}" for i, key in enumerate(update_dict.keys()))
|
set_clause = ", ".join(f"{key} = ${i+2}" for i, key in enumerate(update_dict.keys()))
|
||||||
values = [guild_id] + list(update_dict.values())
|
values = [guild_id] + list(update_dict.values())
|
||||||
|
|
||||||
# Update the settings
|
# Update the settings
|
||||||
await conn.execute(
|
try:
|
||||||
f"""
|
await conn.execute(
|
||||||
INSERT INTO starboard_settings (guild_id)
|
f"""
|
||||||
VALUES ($1)
|
INSERT INTO starboard_settings (guild_id)
|
||||||
ON CONFLICT (guild_id) DO UPDATE SET {set_clause};
|
VALUES ($1)
|
||||||
""",
|
ON CONFLICT (guild_id) DO UPDATE SET {set_clause};
|
||||||
*values
|
""",
|
||||||
)
|
*values
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
if "another operation is in progress" in str(e) or "attached to a different loop" in str(e):
|
||||||
|
log.warning(f"Connection issue when updating starboard settings for guild {guild_id}: {e}")
|
||||||
|
# Try to reset the connection
|
||||||
|
await conn.close()
|
||||||
|
conn = await asyncio.wait_for(pg_pool.acquire(), timeout=5.0)
|
||||||
|
|
||||||
|
# Try again with the new connection
|
||||||
|
await conn.execute(
|
||||||
|
f"""
|
||||||
|
INSERT INTO starboard_settings (guild_id)
|
||||||
|
VALUES ($1)
|
||||||
|
ON CONFLICT (guild_id) DO UPDATE SET {set_clause};
|
||||||
|
""",
|
||||||
|
*values
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
log.info(f"Updated starboard settings for guild {guild_id}: {update_dict}")
|
log.info(f"Updated starboard settings for guild {guild_id}: {update_dict}")
|
||||||
return True
|
return True
|
||||||
|
finally:
|
||||||
|
# Always release the connection back to the pool
|
||||||
|
if conn:
|
||||||
|
await pg_pool.release(conn)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
log.error(f"Timeout acquiring database connection for starboard settings update (Guild: {guild_id})")
|
||||||
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.exception(f"Database error updating starboard settings for guild {guild_id}: {e}")
|
log.exception(f"Database error updating starboard settings for guild {guild_id}: {e}")
|
||||||
return False
|
return False
|
||||||
@ -449,7 +489,11 @@ async def update_starboard_entry(guild_id: int, original_message_id: int, star_c
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with pg_pool.acquire() as conn:
|
# Acquire a connection with a timeout
|
||||||
|
conn = None
|
||||||
|
try:
|
||||||
|
conn = await asyncio.wait_for(pg_pool.acquire(), timeout=5.0)
|
||||||
|
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
"""
|
"""
|
||||||
UPDATE starboard_entries
|
UPDATE starboard_entries
|
||||||
@ -461,41 +505,204 @@ async def update_starboard_entry(guild_id: int, original_message_id: int, star_c
|
|||||||
|
|
||||||
log.info(f"Updated star count to {star_count} for message {original_message_id} in guild {guild_id}")
|
log.info(f"Updated star count to {star_count} for message {original_message_id} in guild {guild_id}")
|
||||||
return True
|
return True
|
||||||
|
finally:
|
||||||
|
# Always release the connection back to the pool
|
||||||
|
if conn:
|
||||||
|
await pg_pool.release(conn)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
log.error(f"Timeout acquiring database connection for starboard entry update (Guild: {guild_id}, Message: {original_message_id})")
|
||||||
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.exception(f"Database error updating starboard entry for message {original_message_id} in guild {guild_id}: {e}")
|
log.exception(f"Database error updating starboard entry for message {original_message_id} in guild {guild_id}: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
async def delete_starboard_entry(guild_id: int, original_message_id: int):
|
||||||
|
"""Deletes a starboard entry."""
|
||||||
|
if not pg_pool:
|
||||||
|
log.error(f"PostgreSQL pool not initialized, cannot delete starboard entry.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Acquire a connection with a timeout
|
||||||
|
conn = None
|
||||||
|
try:
|
||||||
|
conn = await asyncio.wait_for(pg_pool.acquire(), timeout=5.0)
|
||||||
|
|
||||||
|
# Delete the entry
|
||||||
|
await conn.execute(
|
||||||
|
"""
|
||||||
|
DELETE FROM starboard_entries
|
||||||
|
WHERE guild_id = $1 AND original_message_id = $2
|
||||||
|
""",
|
||||||
|
guild_id, original_message_id
|
||||||
|
)
|
||||||
|
|
||||||
|
# Also delete any reactions associated with this message
|
||||||
|
await conn.execute(
|
||||||
|
"""
|
||||||
|
DELETE FROM starboard_reactions
|
||||||
|
WHERE guild_id = $1 AND message_id = $2
|
||||||
|
""",
|
||||||
|
guild_id, original_message_id
|
||||||
|
)
|
||||||
|
|
||||||
|
log.info(f"Deleted starboard entry for message {original_message_id} in guild {guild_id}")
|
||||||
|
return True
|
||||||
|
finally:
|
||||||
|
# Always release the connection back to the pool
|
||||||
|
if conn:
|
||||||
|
await pg_pool.release(conn)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
log.error(f"Timeout acquiring database connection for starboard entry deletion (Guild: {guild_id}, Message: {original_message_id})")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
log.exception(f"Database error deleting starboard entry for message {original_message_id} in guild {guild_id}: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def clear_starboard_entries(guild_id: int):
|
||||||
|
"""Clears all starboard entries for a guild."""
|
||||||
|
if not pg_pool:
|
||||||
|
log.error(f"PostgreSQL pool not initialized, cannot clear starboard entries.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Acquire a connection with a timeout
|
||||||
|
conn = None
|
||||||
|
try:
|
||||||
|
conn = await asyncio.wait_for(pg_pool.acquire(), timeout=5.0)
|
||||||
|
|
||||||
|
# Get all starboard entries for this guild
|
||||||
|
entries = await conn.fetch(
|
||||||
|
"""
|
||||||
|
SELECT * FROM starboard_entries
|
||||||
|
WHERE guild_id = $1
|
||||||
|
""",
|
||||||
|
guild_id
|
||||||
|
)
|
||||||
|
|
||||||
|
# Delete all entries
|
||||||
|
await conn.execute(
|
||||||
|
"""
|
||||||
|
DELETE FROM starboard_entries
|
||||||
|
WHERE guild_id = $1
|
||||||
|
""",
|
||||||
|
guild_id
|
||||||
|
)
|
||||||
|
|
||||||
|
# Delete all reactions
|
||||||
|
await conn.execute(
|
||||||
|
"""
|
||||||
|
DELETE FROM starboard_reactions
|
||||||
|
WHERE guild_id = $1
|
||||||
|
""",
|
||||||
|
guild_id
|
||||||
|
)
|
||||||
|
|
||||||
|
log.info(f"Cleared {len(entries)} starboard entries for guild {guild_id}")
|
||||||
|
return entries
|
||||||
|
finally:
|
||||||
|
# Always release the connection back to the pool
|
||||||
|
if conn:
|
||||||
|
await pg_pool.release(conn)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
log.error(f"Timeout acquiring database connection for clearing starboard entries (Guild: {guild_id})")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
log.exception(f"Database error clearing starboard entries for guild {guild_id}: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
async def add_starboard_reaction(guild_id: int, message_id: int, user_id: int):
|
async def add_starboard_reaction(guild_id: int, message_id: int, user_id: int):
|
||||||
"""Records a user's star reaction to a message."""
|
"""Records a user's star reaction to a message."""
|
||||||
if not pg_pool:
|
if not pg_pool:
|
||||||
log.error(f"PostgreSQL pool not initialized, cannot add starboard reaction.")
|
log.error(f"PostgreSQL pool not initialized, cannot add starboard reaction.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Use a timeout to prevent hanging on database operations
|
||||||
try:
|
try:
|
||||||
async with pg_pool.acquire() as conn:
|
# Acquire a connection with a timeout
|
||||||
|
conn = None
|
||||||
|
try:
|
||||||
|
conn = await asyncio.wait_for(pg_pool.acquire(), timeout=5.0)
|
||||||
|
|
||||||
# Ensure guild exists
|
# Ensure guild exists
|
||||||
await conn.execute("INSERT INTO guilds (guild_id) VALUES ($1) ON CONFLICT (guild_id) DO NOTHING;", guild_id)
|
try:
|
||||||
|
await conn.execute("INSERT INTO guilds (guild_id) VALUES ($1) ON CONFLICT (guild_id) DO NOTHING;", guild_id)
|
||||||
|
except Exception as e:
|
||||||
|
if "another operation is in progress" in str(e) or "attached to a different loop" in str(e):
|
||||||
|
log.warning(f"Connection issue when inserting guild {guild_id}: {e}")
|
||||||
|
# Try to reset the connection
|
||||||
|
await conn.close()
|
||||||
|
conn = await asyncio.wait_for(pg_pool.acquire(), timeout=5.0)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
# Add the reaction record
|
# Add the reaction record
|
||||||
await conn.execute(
|
try:
|
||||||
"""
|
await conn.execute(
|
||||||
INSERT INTO starboard_reactions (guild_id, message_id, user_id)
|
"""
|
||||||
VALUES ($1, $2, $3)
|
INSERT INTO starboard_reactions (guild_id, message_id, user_id)
|
||||||
ON CONFLICT (guild_id, message_id, user_id) DO NOTHING;
|
VALUES ($1, $2, $3)
|
||||||
""",
|
ON CONFLICT (guild_id, message_id, user_id) DO NOTHING;
|
||||||
guild_id, message_id, user_id
|
""",
|
||||||
)
|
guild_id, message_id, user_id
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
if "another operation is in progress" in str(e) or "attached to a different loop" in str(e):
|
||||||
|
log.warning(f"Connection issue when adding reaction for message {message_id} in guild {guild_id}: {e}")
|
||||||
|
# Try to reset the connection
|
||||||
|
await conn.close()
|
||||||
|
conn = await asyncio.wait_for(pg_pool.acquire(), timeout=5.0)
|
||||||
|
|
||||||
|
# Try again with the new connection
|
||||||
|
await conn.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO starboard_reactions (guild_id, message_id, user_id)
|
||||||
|
VALUES ($1, $2, $3)
|
||||||
|
ON CONFLICT (guild_id, message_id, user_id) DO NOTHING;
|
||||||
|
""",
|
||||||
|
guild_id, message_id, user_id
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
# Count total reactions for this message
|
# Count total reactions for this message
|
||||||
count = await conn.fetchval(
|
try:
|
||||||
"""
|
count = await conn.fetchval(
|
||||||
SELECT COUNT(*) FROM starboard_reactions
|
"""
|
||||||
WHERE guild_id = $1 AND message_id = $2
|
SELECT COUNT(*) FROM starboard_reactions
|
||||||
""",
|
WHERE guild_id = $1 AND message_id = $2
|
||||||
guild_id, message_id
|
""",
|
||||||
)
|
guild_id, message_id
|
||||||
|
)
|
||||||
|
return count
|
||||||
|
except Exception as e:
|
||||||
|
if "another operation is in progress" in str(e) or "attached to a different loop" in str(e):
|
||||||
|
log.warning(f"Connection issue when counting reactions for message {message_id} in guild {guild_id}: {e}")
|
||||||
|
# Try to reset the connection
|
||||||
|
await conn.close()
|
||||||
|
conn = await asyncio.wait_for(pg_pool.acquire(), timeout=5.0)
|
||||||
|
|
||||||
return count
|
# Try again with the new connection
|
||||||
|
count = await conn.fetchval(
|
||||||
|
"""
|
||||||
|
SELECT COUNT(*) FROM starboard_reactions
|
||||||
|
WHERE guild_id = $1 AND message_id = $2
|
||||||
|
""",
|
||||||
|
guild_id, message_id
|
||||||
|
)
|
||||||
|
return count
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
# Always release the connection back to the pool
|
||||||
|
if conn:
|
||||||
|
try:
|
||||||
|
await pg_pool.release(conn)
|
||||||
|
except Exception as e:
|
||||||
|
log.warning(f"Error releasing connection: {e}")
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
log.error(f"Timeout acquiring database connection for adding starboard reaction (Guild: {guild_id}, Message: {message_id})")
|
||||||
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.exception(f"Database error adding starboard reaction for message {message_id} in guild {guild_id}: {e}")
|
log.exception(f"Database error adding starboard reaction for message {message_id} in guild {guild_id}: {e}")
|
||||||
return False
|
return False
|
||||||
@ -506,27 +713,78 @@ async def remove_starboard_reaction(guild_id: int, message_id: int, user_id: int
|
|||||||
log.error(f"PostgreSQL pool not initialized, cannot remove starboard reaction.")
|
log.error(f"PostgreSQL pool not initialized, cannot remove starboard reaction.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Use a timeout to prevent hanging on database operations
|
||||||
try:
|
try:
|
||||||
async with pg_pool.acquire() as conn:
|
# Acquire a connection with a timeout
|
||||||
|
conn = None
|
||||||
|
try:
|
||||||
|
conn = await asyncio.wait_for(pg_pool.acquire(), timeout=5.0)
|
||||||
|
|
||||||
# Remove the reaction record
|
# Remove the reaction record
|
||||||
await conn.execute(
|
try:
|
||||||
"""
|
await conn.execute(
|
||||||
DELETE FROM starboard_reactions
|
"""
|
||||||
WHERE guild_id = $1 AND message_id = $2 AND user_id = $3
|
DELETE FROM starboard_reactions
|
||||||
""",
|
WHERE guild_id = $1 AND message_id = $2 AND user_id = $3
|
||||||
guild_id, message_id, user_id
|
""",
|
||||||
)
|
guild_id, message_id, user_id
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
if "another operation is in progress" in str(e) or "attached to a different loop" in str(e):
|
||||||
|
log.warning(f"Connection issue when removing reaction for message {message_id} in guild {guild_id}: {e}")
|
||||||
|
# Try to reset the connection
|
||||||
|
await conn.close()
|
||||||
|
conn = await asyncio.wait_for(pg_pool.acquire(), timeout=5.0)
|
||||||
|
|
||||||
|
# Try again with the new connection
|
||||||
|
await conn.execute(
|
||||||
|
"""
|
||||||
|
DELETE FROM starboard_reactions
|
||||||
|
WHERE guild_id = $1 AND message_id = $2 AND user_id = $3
|
||||||
|
""",
|
||||||
|
guild_id, message_id, user_id
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
# Count remaining reactions for this message
|
# Count remaining reactions for this message
|
||||||
count = await conn.fetchval(
|
try:
|
||||||
"""
|
count = await conn.fetchval(
|
||||||
SELECT COUNT(*) FROM starboard_reactions
|
"""
|
||||||
WHERE guild_id = $1 AND message_id = $2
|
SELECT COUNT(*) FROM starboard_reactions
|
||||||
""",
|
WHERE guild_id = $1 AND message_id = $2
|
||||||
guild_id, message_id
|
""",
|
||||||
)
|
guild_id, message_id
|
||||||
|
)
|
||||||
|
return count
|
||||||
|
except Exception as e:
|
||||||
|
if "another operation is in progress" in str(e) or "attached to a different loop" in str(e):
|
||||||
|
log.warning(f"Connection issue when counting reactions for message {message_id} in guild {guild_id}: {e}")
|
||||||
|
# Try to reset the connection
|
||||||
|
await conn.close()
|
||||||
|
conn = await asyncio.wait_for(pg_pool.acquire(), timeout=5.0)
|
||||||
|
|
||||||
return count
|
# Try again with the new connection
|
||||||
|
count = await conn.fetchval(
|
||||||
|
"""
|
||||||
|
SELECT COUNT(*) FROM starboard_reactions
|
||||||
|
WHERE guild_id = $1 AND message_id = $2
|
||||||
|
""",
|
||||||
|
guild_id, message_id
|
||||||
|
)
|
||||||
|
return count
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
# Always release the connection back to the pool
|
||||||
|
if conn:
|
||||||
|
try:
|
||||||
|
await pg_pool.release(conn)
|
||||||
|
except Exception as e:
|
||||||
|
log.warning(f"Error releasing connection: {e}")
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
log.error(f"Timeout acquiring database connection for removing starboard reaction (Guild: {guild_id}, Message: {message_id})")
|
||||||
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.exception(f"Database error removing starboard reaction for message {message_id} in guild {guild_id}: {e}")
|
log.exception(f"Database error removing starboard reaction for message {message_id} in guild {guild_id}: {e}")
|
||||||
return False
|
return False
|
||||||
|
Loading…
x
Reference in New Issue
Block a user