From 3abd70a5f7b59651c6cce7de4b96f5cc6ea642a4 Mon Sep 17 00:00:00 2001 From: Evanroby <107794516+Evanroby@users.noreply.github.com> Date: Sun, 29 Mar 2026 21:27:30 +0200 Subject: [PATCH 1/2] [Easterhunt]: Improvements] --- easterhunt/commands/owner.py | 18 ++- easterhunt/commands/user.py | 284 +++++++++++++++++------------------ easterhunt/db.py | 50 +++++- easterhunt/easterhunt.py | 45 ++++-- easterhunt/utils.py | 30 ---- themoviedb/themoviedb.py | 2 +- themoviedb/tmdb_utils.py | 4 +- 7 files changed, 232 insertions(+), 201 deletions(-) diff --git a/easterhunt/commands/owner.py b/easterhunt/commands/owner.py index b8ad5798..e1a94bbb 100644 --- a/easterhunt/commands/owner.py +++ b/easterhunt/commands/owner.py @@ -27,6 +27,8 @@ from redbot.core.utils.chat_formatting import humanize_number from redbot.core.utils.views import ConfirmView +from ..achievements.achievements import achievements as achievement_list + class OwnerCommands(commands.Cog): @commands.is_owner() @@ -121,7 +123,7 @@ async def ownerset_resetuser(self, ctx: commands.Context, user: discord.Member): description=f"Are you sure you want to reset {user.mention}'s Easter hunt data? This will clear all their eggs, shards, gems, pity counters, and streaks. This action cannot be undone!", color=discord.Color.red(), ) - msg = await ctx.send( + await ctx.send( embed=embed, view=view, reference=ctx.message.to_reference(fail_if_not_exists=False), @@ -150,7 +152,7 @@ async def ownerset_resetshift(self, ctx: commands.Context, user: discord.Member) description=f"Are you sure you want to reset {user.mention}'s shifts?", color=discord.Color.red(), ) - msg = await ctx.send( + await ctx.send( embed=embed, view=view, reference=ctx.message.to_reference(fail_if_not_exists=False), @@ -165,6 +167,7 @@ async def ownerset_resetshift(self, ctx: commands.Context, user: discord.Member) await self.db.set_user_field(user.id, "active_hunt", False) await self.db.set_user_field(user.id, "active_work", False) await self.db.set_user_field(user.id, "last_work", 0) + await self.db.set_user_field(user.id, "active_job_type", None) await ctx.send( f"{user.mention}'s Easter hunt and work data has been reset by {ctx.author.mention}!" ) @@ -188,7 +191,7 @@ async def ownerset_resetall(self, ctx: commands.Context): description="Are you sure you want to reset ALL Easter hunt data? This will clear all user data (eggs, shards, gems, pity counters, streaks) and global config (custom image URLs) for everyone. This action cannot be undone!", color=discord.Color.red(), ) - msg = await ctx.send(embed=embed, view=view) + await ctx.send(embed=embed, view=view) await view.wait() if view.result is None: @@ -209,10 +212,13 @@ async def ownerset_setachievement( Use the achievement key from [p]easterhunt achievements. """ + valid_keys = {a["key"] for a in achievement_list} + if key not in valid_keys: + return await ctx.send( + f"Invalid achievement key: `{key}`\nValid keys: {', '.join(sorted(valid_keys))}" + ) achievements = await self.db.get_achievements(user.id) - if key not in achievements: - return await ctx.send(f"Invalid achievement key: {key}") achievements[key] = value await self.db.set_achievements(user.id, achievements) status = "unlocked" if value else "locked" - await ctx.send(f"Set {user.name}'s {key} achievement to {status}.") + await ctx.send(f"Set {user.name}'s `{key}` achievement to {status}.") diff --git a/easterhunt/commands/user.py b/easterhunt/commands/user.py index d21a679c..56671613 100644 --- a/easterhunt/commands/user.py +++ b/easterhunt/commands/user.py @@ -26,7 +26,7 @@ import random import time from datetime import datetime, timedelta -from typing import Dict, Final, Optional +from typing import Optional import discord from red_commons.logging import getLogger @@ -38,7 +38,6 @@ from ..utils import ( calculate_hunt_probabilities, check_hunt_cooldown, - find_target_player, process_hunt_outcome, update_hunt_streak, ) @@ -97,7 +96,7 @@ async def hunt(self, ctx: commands.Context): end_time = int((datetime.now() + timedelta(minutes=1)).timestamp()) await self.db.set_user_field(user.id, "active_hunt", True) await ctx.send( - f"You sets off on an Easter egg hunt! Searching the fields... ๐Ÿฐ๐ŸŒพ\ni will be back .", + f"You set off on an Easter egg hunt! Searching the fields... ๐Ÿฐ๐ŸŒพ\ni will be back .", reference=ctx.message.to_reference(fail_if_not_exists=False), ) @@ -120,16 +119,16 @@ async def hunt(self, ctx: commands.Context): ] weights = [adjusted_chances[outcome] for outcome in outcomes] result = random.choices(outcomes, weights=weights, k=1)[0] - if pity_counters.get("silver", 0) >= 50: - result = "silver" - elif pity_counters.get("gold", 0) >= 75: - result = "gold" - elif pity_counters.get("shiny", 0) >= 150: - result = "shiny" + if can_roll_mythical and pity_counters.get("mythical", 0) >= 500: + result = "mythical" elif can_roll_legendary and pity_counters.get("legendary", 0) >= 150: result = "legendary" - elif can_roll_mythical and pity_counters.get("mythical", 0) >= 500: - result = "mythical" + elif pity_counters.get("shiny", 0) >= 150: + result = "shiny" + elif pity_counters.get("gold", 0) >= 75: + result = "gold" + elif pity_counters.get("silver", 0) >= 50: + result = "silver" embed = await process_hunt_outcome( self.db, @@ -182,11 +181,7 @@ async def send_hunt_events(self, channel, user): for interval in intervals: current += interval * scale adjusted_intervals.append(current) - unique_events = ( - random.sample(events * 2, num_events) - if len(events) < num_events - else random.sample(events, num_events) - ) + unique_events = random.sample(events, num_events) prev_time = 0 for i, interval in enumerate(adjusted_intervals): sleep_time = interval - prev_time @@ -226,7 +221,7 @@ async def progress(self, ctx: commands.Context): embed = discord.Embed( title=f"{user.display_name}'s Easter Hunt Progress ๐Ÿฐ", - color=await ctx.embed_colour(), + color=await ctx.embed_color(), ) embed.add_field(name="๐Ÿƒ Hunt Streak", value=str(streak), inline=False) embed.add_field( @@ -250,17 +245,17 @@ async def inventory(self, ctx: commands.Context, member: Optional[discord.Member mention_author=False, ) - shards = await self.db.get_user_field(ctx.author.id, "shards") - eggs = await self.db.get_eggs(ctx.author.id) - gems = await self.db.get_user_field(ctx.author.id, "gems") + shards = await self.db.get_user_field(member.id, "shards") + eggs = await self.db.get_eggs(member.id) + gems = await self.db.get_user_field(member.id, "gems") embed = discord.Embed( - title=f"{ctx.author.name}'s Easter Stash ๐Ÿฐ", - colour=await ctx.embed_colour(), + title=f"{member.name}'s Easter Stash ๐Ÿฐ", + color=await ctx.embed_color(), description="Your collected treasures from hunts, work, and more!", ) - if ctx.author.display_avatar: - embed.set_thumbnail(url=ctx.author.display_avatar.url) + if member.display_avatar: + embed.set_thumbnail(url=member.display_avatar.url) embed.add_field(name="๐Ÿช™ Egg Shards:", value=humanize_number(shards), inline=True) embed.add_field(name="๐Ÿ’Ž Hidden Gems:", value=humanize_number(gems), inline=True) egg_list = box( @@ -296,34 +291,34 @@ async def achievements(self, ctx: commands.Context, member: Optional[discord.Mem user_achievements = await self.db.get_achievements(member.id) shards = await self.db.get_user_field(member.id, "shards") gems = await self.db.get_user_field(member.id, "gems") - - for achievement in achievements: - if achievement["condition_type"] == "egg": - condition = ( - eggs.get(achievement["condition_key"], 0) >= achievement["condition_value"] - ) - elif achievement["condition_type"] == "streak": - streak = await self.db.get_user_field(member.id, "hunt_streak") - condition = streak >= achievement["condition_value"] - elif achievement["condition_type"] == "gems": - condition = gems >= achievement["condition_value"] - else: - condition = False - - if condition and not user_achievements.get(achievement["key"], False): - new_shards = shards + achievement["reward"] - await self.db.set_user_field(member.id, "shards", new_shards) - user_achievements[achievement["key"]] = True - await self.db.set_achievements(member.id, user_achievements) - await ctx.send( - f"๐ŸŽ‰ Congratulations {member.mention}! You've unlocked the **{achievement['name']}** achievement " - f"and received {achievement['reward']} egg shards!" - ) - shards = new_shards + if member == ctx.author: + for achievement in achievements: + if achievement["condition_type"] == "egg": + condition = ( + eggs.get(achievement["condition_key"], 0) >= achievement["condition_value"] + ) + elif achievement["condition_type"] == "streak": + streak = await self.db.get_user_field(member.id, "hunt_streak") + condition = streak >= achievement["condition_value"] + elif achievement["condition_type"] == "gems": + condition = gems >= achievement["condition_value"] + else: + condition = False + + if condition and not user_achievements.get(achievement["key"], False): + new_shards = shards + achievement["reward"] + await self.db.set_user_field(member.id, "shards", new_shards) + user_achievements[achievement["key"]] = True + await self.db.set_achievements(member.id, user_achievements) + await ctx.send( + f"๐ŸŽ‰ Congratulations {member.mention}! You've unlocked the **{achievement['name']}** achievement " + f"and received {achievement['reward']} egg shards!" + ) + shards = new_shards embed = discord.Embed( title="Easter Hunt Achievements", - color=await ctx.embed_colour(), + color=await ctx.embed_color(), description="Complete these achievements to earn egg shards!", ) @@ -373,8 +368,7 @@ async def daily(self, ctx: commands.Context): ) shards = random.randint(5, 140) - # Rare chance for a gem in daily - if random.random() < 0.05: # 5% chance + if random.random() < 0.05: gems = await self.db.get_user_field(user.id, "gems") await self.db.set_user_field(user.id, "gems", gems + 1) await ctx.send("You found a hidden gem in your daily gift! ๐Ÿ’Ž") @@ -464,7 +458,7 @@ async def give( await self.db.set_user_field(giver.id, "last_give", current_time) embed = discord.Embed( title="Egg Gift ๐ŸŽ", - color=await ctx.embed_colour(), + color=await ctx.embed_color(), description=( f"{giver.mention} has given {amount} {egg_type.capitalize()} Egg(s) to {member.mention}! ๐Ÿฅš\n" f"{member.mention} now has {receiver_count + amount} {egg_type.capitalize()} Egg(s)." @@ -497,11 +491,14 @@ async def tradeshards(self, ctx: commands.Context, amount: int): new_shard_amount = current_shards - amount currency_name = await bank.get_currency_name(ctx.guild) - await self.db.set_user_field(ctx.author.id, "shards", new_shard_amount) try: await bank.deposit_credits(ctx.author, credit) except errors.BalanceTooHigh: - log.error("User's balance is too high to get any credits.") + return await ctx.send( + f"Your {currency_name} balance is too high to accept more credits! No shards were deducted.", + reference=ctx.message.to_reference(fail_if_not_exists=False), + ) + await self.db.set_user_field(ctx.author.id, "shards", new_shard_amount) await ctx.send( f"Successfully traded {humanize_number(amount)} shards for {humanize_number(credit)} {currency_name}!", reference=ctx.message.to_reference(fail_if_not_exists=False), @@ -577,7 +574,7 @@ async def resetme(self, ctx: commands.Context): return await ctx.send("Reset cancelled.") await self.db.delete_user_data(ctx.author.id) await ctx.send( - f"Your Easter hunt data has been reset!", + "Your Easter hunt data has been reset!", reference=ctx.message.to_reference(fail_if_not_exists=False), ) @@ -664,6 +661,7 @@ async def start_job(self, interaction, job_type, user): work_ends = time.time() + 300 await self.db.set_user_field(user.id, "active_work", True) await self.db.set_user_field(user.id, "last_work", work_ends) + await self.db.set_user_field(user.id, "active_job_type", job_type) await interaction.response.send_message( f"{user.mention} starts working as a {job_type.replace('_', ' ').title()}! Shift begins... ๐Ÿฐ๐Ÿ’ผ\nYou finish your shift " ) @@ -673,109 +671,103 @@ async def start_job(self, interaction, job_type, user): ) except discord.HTTPException as e: log.error(f"Error starting job for {user}: {e}") - await interaction.channel.send( - f"{user.mention}, something went wrong starting your shift! It has been cancelled." - ) + try: + await interaction.channel.send( + f"{user.mention}, something went wrong starting your shift! It has been cancelled." + ) + except discord.HTTPException: + pass await self.db.set_user_field(user.id, "active_work", False) await self.db.set_user_field(user.id, "last_work", 0) + await self.db.set_user_field(user.id, "active_job_type", None) if user.id in self.active_tasks: del self.active_tasks[user.id] - async def run_job(self, interaction, job_type, user, work_ends): - try: - await asyncio.sleep(300) - current_time = time.time() - if job_type == "stealer": - if random.random() < 0.3: - target, stolen_egg_type = await find_target_player( - self.db, user.id, interaction.guild + async def _execute_job_outcome(self, user_id: int, job_type: str, guild) -> str: + """Execute job outcome logic and return a result message string.""" + if job_type == "stealer": + if random.random() < 0.3 and guild is not None: + target, stolen_egg_type = await self.db.find_target_player(user_id, guild) + if target and stolen_egg_type: + target_count = await self.db.get_egg_count(target.id, stolen_egg_type) + user_count = await self.db.get_egg_count(user_id, stolen_egg_type) + await self.db.set_egg_count( + target.id, stolen_egg_type, max(0, target_count - 1) ) - if target and stolen_egg_type: - target_count = await self.db.get_egg_count(target.id, stolen_egg_type) - user_count = await self.db.get_egg_count(user.id, stolen_egg_type) - await self.db.set_egg_count( - target.id, stolen_egg_type, max(0, target_count - 1) - ) - await self.db.set_egg_count(user.id, stolen_egg_type, user_count + 1) - - await interaction.channel.send( - f"{user.mention} sneaks back from stealing! You nabbed a {stolen_egg_type.title()} Egg from {target.name}!" - ) - await interaction.channel.send( - f"{target.name} lost a {stolen_egg_type.title()} Egg to {user.mention}!" - ) - else: - await interaction.channel.send( - f"{user.mention} couldnโ€™t find anyone to steal from! Better luck next shift!" - ) + await self.db.set_egg_count(user_id, stolen_egg_type, user_count + 1) + return f"sneaks back from stealing! You nabbed a **{stolen_egg_type.title()} Egg** from {target.name}! ๐Ÿ•ต๏ธ" else: - if random.random() < 0.6: - egg_type = random.choice(["common", "silver"]) - user_count = await self.db.get_egg_count(user.id, egg_type) - await self.db.set_egg_count(user.id, egg_type, user_count + 1) - await interaction.channel.send( - f"{user.mention} sneaks back from stealing! You nabbed a {egg_type.title()} Egg from a distracted bunny!" - ) - else: - await interaction.channel.send( - f"{user.mention} got caught red-handed by an angry bunny! No eggs for youโ€”better luck next shift!" - ) - elif job_type == "store_clerk": - shards = random.randint(5, 25) - current_shards = await self.db.get_user_field(user.id, "shards") - await self.db.set_user_field(user.id, "shards", current_shards + shards) - await interaction.channel.send( - f"{user.mention} finishes a shift at the Egg Emporium! Sold some eggs and earned {shards} Egg Shardsโ€”nice hustle!" - ) - elif job_type == "egg_giver": - shards = random.randint(3, 25) - current_shards = await self.db.get_user_field(user.id, "shards") - await self.db.set_user_field(user.id, "shards", current_shards + shards) - await interaction.channel.send( - f"{user.mention} hops around giving out Common Eggs! The bunnies loved itโ€”you earned {shards} Egg Shards for your kindness!" - ) - elif job_type == "egg_painter": - egg_type = random.choice(["common", "silver"]) - amount = random.randint(1, 25) if egg_type == "common" else 1 - user_count = await self.db.get_egg_count(user.id, egg_type) - await self.db.set_egg_count(user.id, egg_type, user_count + amount) - await interaction.channel.send( - f"{user.mention} finishes painting eggs! You created {amount} {egg_type.title()} Egg(s)!" - ) - if random.random() < 0.1: # 10% chance for gem - gems = await self.db.get_user_field(user.id, "gems") - await self.db.set_user_field(user.id, "gems", gems + 1) - await interaction.channel.send( - f"{user.mention} found a hidden gem while painting! ๐Ÿ’Ž" - ) - elif job_type == "gem_miner": - if random.random() < 0.4: # 40% chance for gem - gems = await self.db.get_user_field(user.id, "gems") - await self.db.set_user_field(user.id, "gems", gems + 1) - await interaction.channel.send(f"{user.mention} mined a hidden gem! ๐Ÿ’Ž") + return "couldn't find anyone to steal from! Better luck next shift." + else: + if random.random() < 0.6: + egg_type = random.choice(["common", "silver"]) + user_count = await self.db.get_egg_count(user_id, egg_type) + await self.db.set_egg_count(user_id, egg_type, user_count + 1) + return f"nabbed a **{egg_type.title()} Egg** from a distracted bunny! ๐Ÿ‡" + else: + return "got caught red-handed by an angry bunny! No eggsโ€”better luck next shift! ๐Ÿฐ" + + elif job_type == "store_clerk": + shards = random.randint(5, 25) + current_shards = await self.db.get_user_field(user_id, "shards") + await self.db.set_user_field(user_id, "shards", current_shards + shards) + return f"finished a shift at the Egg Emporium! Earned **{shards} Egg Shards**โ€”nice hustle! ๐Ÿช" + + elif job_type == "egg_giver": + shards = random.randint(3, 25) + current_shards = await self.db.get_user_field(user_id, "shards") + await self.db.set_user_field(user_id, "shards", current_shards + shards) + return f"hopped around giving out eggs! The bunnies loved itโ€”earned **{shards} Egg Shards**! ๐Ÿฅš" + + elif job_type == "egg_painter": + egg_type = random.choice(["common", "silver"]) + amount = random.randint(1, 25) if egg_type == "common" else 1 + user_count = await self.db.get_egg_count(user_id, egg_type) + await self.db.set_egg_count(user_id, egg_type, user_count + amount) + result = f"finished painting eggs! Created **{amount} {egg_type.title()} Egg(s)**! ๐ŸŽจ" + if random.random() < 0.1: + gems = await self.db.get_user_field(user_id, "gems") + await self.db.set_user_field(user_id, "gems", gems + 1) + result += " Also found a **hidden gem** while painting! ๐Ÿ’Ž" + return result + + elif job_type == "gem_miner": + if random.random() < 0.4: + gems = await self.db.get_user_field(user_id, "gems") + await self.db.set_user_field(user_id, "gems", gems + 1) + return "mined a **hidden gem**! ๐Ÿ’Ž" + else: + if random.random() < 0.5: + shards = random.randint(1, 25) + current_shards = await self.db.get_user_field(user_id, "shards") + await self.db.set_user_field(user_id, "shards", current_shards + shards) + return f"found **{shards} shards** while mining." else: - if random.random() < 0.5: - shards = random.randint(1, 25) - current_shards = await self.db.get_user_field(user.id, "shards") - await self.db.set_user_field(user.id, "shards", current_shards + shards) - await interaction.channel.send( - f"{user.mention} found {shards} shards while mining." - ) - else: - await interaction.channel.send( - f"{user.mention} dug around but found nothing this time." - ) - - await self.db.set_user_field(user.id, "last_work", current_time) + return "dug around but found nothing this time. ๐Ÿชจ" + + return "finished their shift." + + async def run_job(self, interaction, job_type, user, work_ends): + try: + await asyncio.sleep(300) + result_message = await self._execute_job_outcome(user.id, job_type, interaction.guild) + try: + await interaction.channel.send(f"{user.mention} {result_message}") + except discord.HTTPException as e: + log.error(f"Failed to send job result message for {user}: {e}") except asyncio.CancelledError: pass - except discord.HTTPException as e: - log.error(f"Error in run_job for {user}: {e}") - await interaction.channel.send( - f"{user.mention}, something went wrong during your shift! It has been cancelled." - ) + except Exception as e: + log.error(f"Unexpected error in run_job for {user}: {e}") + try: + await interaction.channel.send( + f"{user.mention}, something went wrong during your shift! It has been cancelled." + ) + except discord.HTTPException: + pass finally: await self.db.set_user_field(user.id, "active_work", False) await self.db.set_user_field(user.id, "last_work", 0) + await self.db.set_user_field(user.id, "active_job_type", None) if user.id in self.active_tasks: del self.active_tasks[user.id] diff --git a/easterhunt/db.py b/easterhunt/db.py index 49cb2175..0313ae9a 100644 --- a/easterhunt/db.py +++ b/easterhunt/db.py @@ -23,9 +23,11 @@ """ import json +import random from typing import Dict, List, Optional, Tuple import aiosqlite +import discord import orjson from redbot.core.data_manager import cog_data_path @@ -39,6 +41,7 @@ def __init__(self, bot): async def initialize(self): self.conn = await aiosqlite.connect(self.db_path) + await self.conn.execute("PRAGMA foreign_keys = ON") await self.create_tables() async def close(self): @@ -60,7 +63,8 @@ async def create_tables(self): hunt_streak INTEGER DEFAULT 0, last_hunt_time REAL DEFAULT 0, pity_counter_json TEXT DEFAULT '{}', - achievements_json TEXT DEFAULT '{}' + achievements_json TEXT DEFAULT '{}', + active_job_type TEXT DEFAULT NULL )""", """CREATE TABLE IF NOT EXISTS user_eggs ( user_id INTEGER, @@ -77,6 +81,12 @@ async def create_tables(self): async with self.conn.cursor() as cursor: for query in queries: await cursor.execute(query) + try: + await cursor.execute( + "ALTER TABLE users ADD COLUMN active_job_type TEXT DEFAULT NULL" + ) + except Exception: + pass await self.conn.commit() async def ensure_user(self, user_id: int): @@ -186,18 +196,52 @@ async def get_user_count(self) -> int: async def reset_all(self): async with self.conn.cursor() as cursor: + await cursor.execute("DELETE FROM user_eggs") await cursor.execute("DELETE FROM users") await cursor.execute("DELETE FROM egg_images") await self.conn.commit() async def get_leaderboard_data(self) -> List[Tuple[int, int]]: async with self.conn.cursor() as cursor: - await cursor.execute(""" + await cursor.execute( + """ SELECT user_id, SUM(count) as total FROM user_eggs WHERE egg_type IN ('common', 'silver', 'gold') GROUP BY user_id HAVING total > 0 ORDER BY total DESC - """) + """ + ) return await cursor.fetchall() + + async def find_target_player( + self, user_id: int, guild + ) -> Tuple[Optional[discord.Member], Optional[str]]: + """Find a random guild member with eggs to steal from, excluding the requesting user.""" + potential_targets = [] + async with self.conn.cursor() as cursor: + await cursor.execute( + """ + SELECT DISTINCT user_id + FROM user_eggs + WHERE user_id != ? AND count > 0 + """, + (user_id,), + ) + rows = await cursor.fetchall() + for (target_id,) in rows: + member = guild.get_member(target_id) + if member and not member.bot: + eggs = await self.get_eggs(member.id) + potential_targets.append((member, eggs)) + + if not potential_targets: + return None, None + + target, target_eggs = random.choice(potential_targets) + available_egg_types = [egg_type for egg_type, count in target_eggs.items() if count > 0] + if not available_egg_types: + return None, None + egg_type = random.choice(available_egg_types) + return target, egg_type diff --git a/easterhunt/easterhunt.py b/easterhunt/easterhunt.py index 4aff750e..9ee5d47b 100644 --- a/easterhunt/easterhunt.py +++ b/easterhunt/easterhunt.py @@ -27,12 +27,15 @@ from typing import Final import discord +from red_commons.logging import getLogger from redbot.core import commands from .commands.owner import OwnerCommands from .commands.user import UserCommands from .db import Database +log = getLogger("red.maxcogs.easterhunt") + class EasterHunt(UserCommands, OwnerCommands, commands.Cog): """ @@ -41,7 +44,7 @@ class EasterHunt(UserCommands, OwnerCommands, commands.Cog): It includes various commands for interacting with the game, managing progress, and viewing leaderboards. """ - __version__: Final[str] = "2.0.0" + __version__: Final[str] = "2.1.0" __author__: Final[str] = "MAX" __docs__: Final[str] = "https://github.com/ltzmax/maxcogs/tree/master/easterhunt/README.md" @@ -63,40 +66,56 @@ async def red_delete_data_for_user(self, *, requester: str, user_id: int) -> Non async def cog_load(self): await self.db.initialize() current_time = time.time() + + async with self.db.conn.cursor() as cursor: + await cursor.execute("UPDATE users SET active_hunt = 0 WHERE active_hunt = 1") + await self.db.conn.commit() + stale_users = await self.db.get_stale_active_users() for user_id, last_work in stale_users: user = self.bot.get_user(user_id) if not user: + await self.db.set_user_field(user_id, "active_work", False) + await self.db.set_user_field(user_id, "last_work", 0) + await self.db.set_user_field(user_id, "active_job_type", None) continue if last_work <= current_time: - await self.db.set_user_field(user_id, "active_work", 0) - await self.db.set_user_field(user_id, "last_work", 0) + job_type = await self.db.get_user_field(user_id, "active_job_type") + await self.resume_job(user, 0, job_type) else: remaining_time = last_work - current_time - if remaining_time > 0: - self.active_tasks[user_id] = self.bot.loop.create_task( - self.resume_job(user, remaining_time) - ) + job_type = await self.db.get_user_field(user_id, "active_job_type") + self.active_tasks[user_id] = self.bot.loop.create_task( + self.resume_job(user, remaining_time, job_type) + ) async def cog_unload(self): - await self.db.close() - for user_id, task in self.active_tasks.items(): + for user_id, task in list(self.active_tasks.items()): task.cancel() user = self.bot.get_user(user_id) if user: await self.db.set_user_field(user_id, "active_work", False) await self.db.set_user_field(user_id, "last_work", 0) + await self.db.set_user_field(user_id, "active_job_type", None) self.active_tasks.clear() + await self.db.close() - async def resume_job(self, user, remaining_time): + async def resume_job(self, user, remaining_time, job_type): try: - await asyncio.sleep(remaining_time) - current_time = time.time() - await self.db.set_user_field(user.id, "last_work", current_time) + if remaining_time > 0: + await asyncio.sleep(remaining_time) + result_message = await self._execute_job_outcome(user.id, job_type, guild=None) + try: + await user.send( + f"๐Ÿฐ Your shift as a **{job_type.replace('_', ' ').title()}** finished while the bot was restarting!\n{result_message}" + ) + except discord.HTTPException: + pass except asyncio.CancelledError: pass finally: await self.db.set_user_field(user.id, "active_work", False) await self.db.set_user_field(user.id, "last_work", 0) + await self.db.set_user_field(user.id, "active_job_type", None) if user.id in self.active_tasks: del self.active_tasks[user.id] diff --git a/easterhunt/utils.py b/easterhunt/utils.py index 09c96f58..3c8c29aa 100644 --- a/easterhunt/utils.py +++ b/easterhunt/utils.py @@ -164,33 +164,3 @@ async def process_hunt_outcome( if image_url: embed.set_image(url=image_url) return embed - - -async def find_target_player(db, user_id: int, guild) -> tuple[discord.Member | None, str | None]: - """Find a random player with eggs to steal from, excluding the user.""" - potential_targets = [] - async with db.conn.cursor() as cursor: - await cursor.execute( - """ - SELECT DISTINCT user_id - FROM user_eggs - WHERE user_id != ? AND count > 0 - """, - (user_id,), - ) - rows = await cursor.fetchall() - for (target_id,) in rows: - member = guild.get_member(target_id) - if member and not member.bot: - eggs = await db.get_eggs(member.id) - potential_targets.append((member, eggs)) - - if not potential_targets: - return None, None - - target, target_eggs = random.choice(potential_targets) - available_egg_types = [egg_type for egg_type, count in target_eggs.items() if count > 0] - if not available_egg_types: - return None, None - egg_type = random.choice(available_egg_types) - return target, egg_type diff --git a/themoviedb/themoviedb.py b/themoviedb/themoviedb.py index f44c241d..f744d7b0 100644 --- a/themoviedb/themoviedb.py +++ b/themoviedb/themoviedb.py @@ -479,7 +479,7 @@ async def tmdbset_creds(self, ctx: commands.Context): embed = discord.Embed( title="TMDB API Key", description=msg, - colour=await ctx.embed_colour(), + colour=await ctx.embed_color(), ) embed.set_footer(text="You can also set your API key by using the button.") await ctx.send(embed=embed, view=view) diff --git a/themoviedb/tmdb_utils.py b/themoviedb/tmdb_utils.py index 0d313576..e1662641 100644 --- a/themoviedb/tmdb_utils.py +++ b/themoviedb/tmdb_utils.py @@ -255,7 +255,7 @@ async def build_embed(ctx, data, item_id, index, results, item_type="movie"): fields = {k: v for k, v in fields.items() if v} embed = discord.Embed( - title=title, url=url, description=description, colour=await ctx.embed_colour() + title=title, url=url, description=description, colour=await ctx.embed_color() ) total_length = len(embed.title) + len(embed.description) @@ -558,7 +558,7 @@ async def fetch_person(person): title=data.get("name", "Unknown"), url=f"https://www.themoviedb.org/person/{person['id']}", description=data.get("biography", "No biography available.")[:3048], - colour=await ctx.embed_colour(), + colour=await ctx.embed_color(), ) fields = { From 8afcfdf1ce7f19af50582e10d6b3e5c6c937ef1b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 29 Mar 2026 19:29:06 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- easterhunt/db.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/easterhunt/db.py b/easterhunt/db.py index 0313ae9a..48ee5169 100644 --- a/easterhunt/db.py +++ b/easterhunt/db.py @@ -203,16 +203,14 @@ async def reset_all(self): async def get_leaderboard_data(self) -> List[Tuple[int, int]]: async with self.conn.cursor() as cursor: - await cursor.execute( - """ + await cursor.execute(""" SELECT user_id, SUM(count) as total FROM user_eggs WHERE egg_type IN ('common', 'silver', 'gold') GROUP BY user_id HAVING total > 0 ORDER BY total DESC - """ - ) + """) return await cursor.fetchall() async def find_target_player(