From 34897b956a3cabfd2401e9eb0dcfa3800385d9c8 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Fri, 14 Aug 2026 00:07:41 -0700 Subject: [PATCH 1/5] Add error handling for Hypixel API responses Handle non-OK responses from Hypixel API with an error message. --- extensions/game_commands/game_commands.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/extensions/game_commands/game_commands.py b/extensions/game_commands/game_commands.py index f5cb6bf..78a7504 100644 --- a/extensions/game_commands/game_commands.py +++ b/extensions/game_commands/game_commands.py @@ -180,7 +180,10 @@ async def hypixel_request(self, *args, **kwargs): if self.session is None: self.session = aiohttp.ClientSession() async with self.session.get(*args, **kwargs) as resp: - return await resp.json() + data = await resp.json() + if resp.status not in (200, 404): + print(f"{Color.MAGENTA}Game Commands{Color.RESET} > {Color.RED}[ERROR]{Color.RESET} Non-OK response from Hypixel: {data}") + return async def cog_unload(self) -> None: if self.session is not None: From f4ac4cf22485e9f62c9d542505a392f133497535 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Fri, 14 Aug 2026 00:12:10 -0700 Subject: [PATCH 2/5] Return data from Hypixel API response Return the JSON data from the response instead of None. --- extensions/game_commands/game_commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/game_commands/game_commands.py b/extensions/game_commands/game_commands.py index 78a7504..55ab5d7 100644 --- a/extensions/game_commands/game_commands.py +++ b/extensions/game_commands/game_commands.py @@ -183,7 +183,7 @@ async def hypixel_request(self, *args, **kwargs): data = await resp.json() if resp.status not in (200, 404): print(f"{Color.MAGENTA}Game Commands{Color.RESET} > {Color.RED}[ERROR]{Color.RESET} Non-OK response from Hypixel: {data}") - return + return data async def cog_unload(self) -> None: if self.session is not None: From a512399a3692d8beffe1fb4af617029dd6aae4ed Mon Sep 17 00:00:00 2001 From: bxlastendes Date: Sat, 15 Aug 2026 11:07:08 +0200 Subject: [PATCH 3/5] fixed the shard table legendary -> epic --- extensions/game_commands/classavg_calculator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/game_commands/classavg_calculator.py b/extensions/game_commands/classavg_calculator.py index 8048a62..a501aeb 100644 --- a/extensions/game_commands/classavg_calculator.py +++ b/extensions/game_commands/classavg_calculator.py @@ -7,8 +7,8 @@ async def calculate_classavg(member_data, global_boost=0.0, mayor_boost=0.0): attributes = member_data.get("attributes", {}) SHARD_TABLE = { - 0: 0, 1: 1, 2: 2, 3: 3, 4: 5, - 5: 7, 6: 9, 7: 12, 8: 15, 9: 19, 10: 24 + 0: 0, 1: 1, 2: 2, 3: 4, 4: 6, + 5: 9, 6: 12, 7: 16, 8: 20, 9: 25, 10: 32 } catagrad_level = min(max(attributes.get("catacombs_graduate", 0), 0), 10) From e7f740050f6ed5c2a02cc489fe99460a7526573d Mon Sep 17 00:00:00 2001 From: bxlastendes Date: Sat, 15 Aug 2026 12:00:43 +0200 Subject: [PATCH 4/5] fixed a bug where mango and grape could not be used as profiles fixed a bug where you had to provide a profile to use boosts ingame added a message for profiles that are already class average 50 --- extensions/game_commands/game_commands.py | 167 ++++++++++++---------- 1 file changed, 94 insertions(+), 73 deletions(-) diff --git a/extensions/game_commands/game_commands.py b/extensions/game_commands/game_commands.py index 55ab5d7..5dae6a2 100644 --- a/extensions/game_commands/game_commands.py +++ b/extensions/game_commands/game_commands.py @@ -8,6 +8,7 @@ import asyncio import traceback import time +import re from core.colors import Color @@ -429,63 +430,70 @@ async def slayers(self, name, args, *, officer: bool = False, head: str = None): async def ca50(self, name, args, *, officer: bool = False, head: str = None): chat_msg = lambda msg: self.send_chat_message(name, msg, officer=officer, head=head) - if len(args) == 0 and name.startswith("@"): - return await chat_msg(f"Must provide player name for Discord commands.") + args = list(args) - player = args.pop(0) if args else name - profile = None global_boost = 0.0 mayor_boost = 0.0 - if args: - first_arg = args[0].lower().replace("%", "") - is_num = first_arg.replace(".", "", 1).isdigit() - is_prefix = first_arg.startswith("m") or first_arg.startswith("g") - is_keyword = first_arg in ["mayor", "global"] - - if not is_num and not is_prefix and not is_keyword: + if name.startswith("@"): + if not args: + return await chat_msg( + f"Must provide a player name for Discord commands." + ) + player = args.pop(0) + else: + player = name + if args: + first = args[0].lower() + + is_short_flag = re.fullmatch(r"[mg]\d+(?:\.\d+)?%?", first) is not None + is_keyword = first in ("m", "g", "mayor", "global") + + if not is_short_flag and not is_keyword: + profile = args.pop(0) + + if name.startswith("@") and args: + first = args[0].lower() + + is_short_flag = re.fullmatch(r"[mg]\d+(?:\.\d+)?%?", first) is not None + is_keyword = first in ("m", "g", "mayor", "global") + + if not is_short_flag and not is_keyword: profile = args.pop(0) + # Boost Parser + while args: current = args.pop(0).lower() - - if current.startswith("m") and current[1:].replace("%", "").replace(".", "", 1).isdigit(): - args.insert(0, current[1:]) - current = "m" - elif current.startswith("g") and current[1:].replace("%", "").replace(".", "", 1).isdigit(): - args.insert(0, current[1:]) - current = "g" - - # Auswertung der Flags - if current in ["paul", "mayor", "m"]: - if args: - val = args.pop(0).replace("%", "") - try: - mayor_boost = float(val) / 100.0 if float(val) > 1.0 else float(val) - except ValueError: - return await chat_msg(f"Invalid mayor boost value after '{current}'.") - - elif current in ["global", "g"]: - if args: - val = args.pop(0).replace("%", "") - try: - global_boost = float(val) / 100.0 if float(val) > 1.0 else float(val) - except ValueError: - return await chat_msg(f"Invalid global boost value after '{current}'.") - - else: - val = current.replace("%", "") - try: - num = float(val) / 100.0 if float(val) > 1.0 else float(val) - if global_boost == 0.0: - global_boost = num - else: - mayor_boost = num - except ValueError: - return await chat_msg(f"Unknown argument: '{current}'. Use numbers, 'm', 'g' or profile names.") - - # --- API FETCHING --- + + m_match = re.fullmatch(r"m(\d+(?:\.\d+)?)%?", current) + g_match = re.fullmatch(r"g(\d+(?:\.\d+)?)%?", current) + + if m_match: + mayor_boost = float(m_match.group(1)) / 100.0 + continue + + if g_match: + global_boost = float(g_match.group(1)) / 100.0 + continue + + if current == "mayor": + if not args: + return await chat_msg("Missing mayor boost value.") + mayor_boost = float(args.pop(0).replace("%", "")) / 100.0 + continue + + if current == "global": + if not args: + return await chat_msg("Missing global boost value.") + global_boost = float(args.pop(0).replace("%", "")) / 100.0 + continue + + return await chat_msg(f"Unknown argument: '{current}'.") + + # API FETCHING + try: uuid, player = await self.get_info(player) except aiohttp.ClientResponseError as e: @@ -493,45 +501,58 @@ async def ca50(self, name, args, *, officer: bool = False, head: str = None): return await chat_msg(f"{player} does not exist.") raise - data = await self.hypixel_request(f"https://api.hypixel.net/v2/skyblock/profiles?key={GameCommandConfig.hypixel_api_key}&uuid={uuid}") - if not data["success"]: + data = await self.hypixel_request( + f"https://api.hypixel.net/v2/skyblock/profiles?key={GameCommandConfig.hypixel_api_key}&uuid={uuid}" + ) + + if not data.get("success", False): return await chat_msg(f"Failed to get {player}'s profile.") - profiles = data["profiles"] + profiles = data.get("profiles", []) if not profiles: return await chat_msg(f"{player} has no profiles.") if profile is None: - profile_list = [p for p in profiles if p.get("selected")] - if not profile_list: - profile_list = profiles - profile_obj = profile_list[0] + selected = [p for p in profiles if p.get("selected")] + profile_obj = selected[0] if selected else profiles[0] else: - profile_list = [p for p in profiles if p.get("cute_name", "").lower() == profile.lower()] - if not profile_list: - valid_profiles = ", ".join([p.get('cute_name', 'Unknown') for p in profiles]) - return await chat_msg(f"Invalid profile: {valid_profiles}.") - profile_obj = profile_list[0] + matches = [ + p for p in profiles + if p.get("cute_name", "").casefold() == profile.casefold() + ] + + if not matches: + valid = ", ".join(p.get("cute_name", "Unknown") for p in profiles) + return await chat_msg(f"Invalid profile: {valid}.") - member = profile_obj["members"].get(uuid) + profile_obj = matches[0] + + member = profile_obj.get("members", {}).get(uuid) if member is None: return await chat_msg(f"{player} is not in the profile.") + # Calculation + result = await calculate_classavg( - member, - global_boost=global_boost, - mayor_boost=mayor_boost + member, + global_boost=global_boost, + mayor_boost=mayor_boost, ) - runs_dict = result.get("runs", {}) - class_details = [ - f"{runs} {cls.capitalize()}" - for cls, runs in runs_dict.items() - if runs > 0 - ] - details_str = ", ".join(class_details) + order = ["archer", "berserk", "healer", "mage", "tank"] + + details = ", ".join( + f"{result['runs'][cls]} {cls.capitalize()}" + for cls in order + if result["runs"].get(cls, 0) > 0 + ) + + if result["total_runs"] == 0: + return await chat_msg( + f"{player} ({profile_obj['cute_name']}) already has Class Average 50! GG!" + ) await chat_msg( f"It will take {result['total_runs']:,} M7 runs for {player} " - f"({profile_obj.get('cute_name', 'Unknown')}) to reach Class Average 50 ({details_str})" - ) + f"({profile_obj['cute_name']}) to reach Class Average 50 ({details})" + ) \ No newline at end of file From 4afc397caa9ed20421ea0c0fb10186dc789c0bd4 Mon Sep 17 00:00:00 2001 From: plun1331 Date: Sat, 15 Aug 2026 13:47:18 -0700 Subject: [PATCH 5/5] Refactor player and profile name handling in game commands --- extensions/game_commands/game_commands.py | 79 +++++++++-------------- 1 file changed, 30 insertions(+), 49 deletions(-) diff --git a/extensions/game_commands/game_commands.py b/extensions/game_commands/game_commands.py index 5dae6a2..f160e1d 100644 --- a/extensions/game_commands/game_commands.py +++ b/extensions/game_commands/game_commands.py @@ -432,38 +432,30 @@ async def ca50(self, name, args, *, officer: bool = False, head: str = None): args = list(args) - profile = None - global_boost = 0.0 - mayor_boost = 0.0 - - if name.startswith("@"): - if not args: - return await chat_msg( - f"Must provide a player name for Discord commands." - ) - player = args.pop(0) - else: - player = name - if args: - first = args[0].lower() - - is_short_flag = re.fullmatch(r"[mg]\d+(?:\.\d+)?%?", first) is not None - is_keyword = first in ("m", "g", "mayor", "global") - - if not is_short_flag and not is_keyword: - profile = args.pop(0) - - if name.startswith("@") and args: + player_name = None + if args: first = args[0].lower() - - is_short_flag = re.fullmatch(r"[mg]\d+(?:\.\d+)?%?", first) is not None - is_keyword = first in ("m", "g", "mayor", "global") - - if not is_short_flag and not is_keyword: - profile = args.pop(0) + is_first_short_flag = re.fullmatch(r"[mg]\d+(?:\.\d+)?%?", first) is not None + is_first_keyword = first in ("m", "g", "mayor", "global") + if not is_first_short_flag and not is_first_keyword: + player_name = args.pop(0) + + profile_name = None + if args: + second = args[0].lower() + is_second_short_flag = re.fullmatch(r"[mg]\d+(?:\.\d+)?%?", second) is not None + is_second_keyword = second in ("m", "g", "mayor", "global") + if not is_second_short_flag and not is_second_keyword: + profile_name = args.pop(0) + + if player_name is None and name.startswith("@"): + return await chat_msg("Must provide player name for Discord commands.") + elif player_name is None: + player_name = name # Boost Parser - + global_boost = 0.0 + mayor_boost = 0.0 while args: current = args.pop(0).lower() @@ -493,66 +485,55 @@ async def ca50(self, name, args, *, officer: bool = False, head: str = None): return await chat_msg(f"Unknown argument: '{current}'.") # API FETCHING - try: - uuid, player = await self.get_info(player) + uuid, player_name = await self.get_info(player_name) except aiohttp.ClientResponseError as e: if e.status == 404: - return await chat_msg(f"{player} does not exist.") + return await chat_msg(f"{player_name} does not exist.") raise - data = await self.hypixel_request( f"https://api.hypixel.net/v2/skyblock/profiles?key={GameCommandConfig.hypixel_api_key}&uuid={uuid}" ) - if not data.get("success", False): - return await chat_msg(f"Failed to get {player}'s profile.") - + return await chat_msg(f"Failed to get {player_name}'s profile.") profiles = data.get("profiles", []) if not profiles: - return await chat_msg(f"{player} has no profiles.") + return await chat_msg(f"{player_name} has no profiles.") - if profile is None: + if profile_name is None: selected = [p for p in profiles if p.get("selected")] profile_obj = selected[0] if selected else profiles[0] else: matches = [ p for p in profiles - if p.get("cute_name", "").casefold() == profile.casefold() + if p.get("cute_name", "").lower() == profile_name.lower() ] - if not matches: valid = ", ".join(p.get("cute_name", "Unknown") for p in profiles) return await chat_msg(f"Invalid profile: {valid}.") - profile_obj = matches[0] member = profile_obj.get("members", {}).get(uuid) if member is None: - return await chat_msg(f"{player} is not in the profile.") + return await chat_msg(f"{player_name} is not in the profile.") # Calculation - result = await calculate_classavg( member, global_boost=global_boost, mayor_boost=mayor_boost, ) - order = ["archer", "berserk", "healer", "mage", "tank"] - details = ", ".join( f"{result['runs'][cls]} {cls.capitalize()}" for cls in order if result["runs"].get(cls, 0) > 0 ) - if result["total_runs"] == 0: return await chat_msg( - f"{player} ({profile_obj['cute_name']}) already has Class Average 50! GG!" + f"{player_name} ({profile_obj['cute_name']}) already has Class Average 50! GG!" ) - await chat_msg( - f"It will take {result['total_runs']:,} M7 runs for {player} " + f"It will take {result['total_runs']:,} M7 runs for {player_name} " f"({profile_obj['cute_name']}) to reach Class Average 50 ({details})" ) \ No newline at end of file