Skip to content

Commit

Permalink
Fix various bugs (#69)
Browse files Browse the repository at this point in the history
* Fix game mode KeyError, add logs

* Adding additional try-catch

* Changed print to loggers

* fixed voice channel leave

* Skip reassigning

* Fix dumb bug

* Bug fixes

* Cap name

* Fix winrate percentage
  • Loading branch information
kwkevinlin committed Apr 8, 2023
1 parent fddab2d commit 1776564
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 46 deletions.
64 changes: 45 additions & 19 deletions clients/fortnite_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from database.mysql import MySQL
from exceptions import UserDoesNotExist, UserStatisticsNotFound
from utils.dates import get_playing_session_date
from logger import get_logger_with_context


FORTNITE_API_TOKEN = os.getenv("FORTNITE_API_TOKEN")
Expand All @@ -22,6 +23,7 @@ async def get_player_stats(ctx, player_name, silent):

player_stats = await _get_player_latest_season_stats(account_info)

# TODO: Asyncio with above
twitch_stream = await twitch.get_twitch_stream(player_name)

message = _create_message(account_info, player_stats, twitch_stream)
Expand Down Expand Up @@ -49,25 +51,32 @@ async def _get_player_account_info(player_name):
headers=_get_headers()
) as resp:
if resp.status == 404:
raise UserDoesNotExist(f"Username not found: {player_name}")
raise UserDoesNotExist(f"Player not found: {player_name}")

resp_json = await resp.json()
# TODO: Move this elsewhere
logger = get_logger_with_context(identifier=player_name)

try:
resp_json = await resp.json()

# TODO: Convert to logger
print(f"Closest username matches: {resp_json['matches']}")
logger.info("Closest username matches: %s", resp_json['matches'])

best_match = resp_json["matches"][0]
matched_username = best_match["matches"][0]["value"]
matched_platform = best_match["matches"][0]["platform"].capitalize()
best_match = resp_json["matches"][0]
matched_username = best_match["matches"][0]["value"]
matched_platform = best_match["matches"][0]["platform"].capitalize()
except Exception as exc:
logger.error("Invalid response received from the API: %s", resp_json)
logger.error(repr(exc))
raise UserDoesNotExist("API broke and returned bad data..")

if player_name == matched_username:
name = player_name
if player_name.lower() == matched_username.lower():
name = matched_username
else:
name = f"{player_name} ({matched_platform}: {matched_username})"

return {
"account_id": best_match["accountId"],
"epic_username": matched_username,
"platform_username": matched_username,
"readable_name": name
}

Expand All @@ -83,17 +92,15 @@ async def _get_player_latest_season_stats(account_info):
if not _is_latest_season(season_id, player_stats):
latest_season_id = _get_latest_season_id(player_stats)
_set_fortnite_season_id(latest_season_id)
# TODO: Convert to logger
print(f"Found new season ID, setting latest season ID to: {latest_season_id}")
# TODO: Move this elsewhere
logger = get_logger_with_context(identifier=account_info["readable_name"])
logger.info("Found new season ID, setting latest season ID to: %s", latest_season_id)

player_stats = await _get_player_season_stats(account_info, latest_season_id)

mode_breakdown = player_stats["global_stats"]
mode_breakdown = _append_all_mode_stats(mode_breakdown)
mode_breakdown["duos"] = mode_breakdown.pop("duo")
mode_breakdown["trios"] = mode_breakdown.pop("trio")
mode_breakdown["squads"] = mode_breakdown.pop("squad")

mode_breakdown = _rename_game_modes(mode_breakdown)
return mode_breakdown


Expand All @@ -114,8 +121,16 @@ async def _get_player_season_stats(account_info, season_id):
raise_for_status=True
) as resp:
resp_json = await resp.json()

if resp_json["result"] is True:
if not resp_json["global_stats"] or "season" not in resp_json["account"]:
raise UserStatisticsNotFound(f"Player does not have sufficient data: {account_info['readable_name']}")

if resp_json["result"] is False:
raise UserStatisticsNotFound(f"User statistics not found: {account_info['readable_name']}")
if resp_json["name"] is None:
raise UserStatisticsNotFound(f"Player statistics not found: {account_info['readable_name']}")
else:
raise UserStatisticsNotFound(f"Player has a private account: {account_info['readable_name']}")

return resp_json

Expand Down Expand Up @@ -165,14 +180,25 @@ def _append_all_mode_stats(mode_breakdown):
all_stats["matchesplayed"] += stats["matchesplayed"]
all_stats["kills"] += stats["kills"]

all_stats["winrate"] = all_stats["placetop1"] / all_stats["matchesplayed"]
all_stats["winrate"] = all_stats["placetop1"] / all_stats["matchesplayed"] * 100
all_stats["kd"] = all_stats["kills"] / (all_stats["matchesplayed"] - all_stats["placetop1"])

mode_breakdown["all"] = all_stats

return mode_breakdown


def _rename_game_modes(mode_breakdown):
"""Rename game modes to synchronize with what Discord utils expects."""
if "duo" in mode_breakdown:
mode_breakdown["duos"] = mode_breakdown.pop("duo")
if "trio" in mode_breakdown:
mode_breakdown["trios"] = mode_breakdown.pop("trio")
if "squad" in mode_breakdown:
mode_breakdown["squads"] = mode_breakdown.pop("squad")
return mode_breakdown


def _create_message(account_info, stats_breakdown, twitch_stream):
""" Create player stats Discord message """
wins_count = stats_breakdown["all"]["placetop1"]
Expand All @@ -185,7 +211,7 @@ def _create_message(account_info, stats_breakdown, twitch_stream):
color_metric=kd_ratio,
create_stats_func=_create_stats_str,
stats_breakdown=stats_breakdown,
username=account_info["epic_username"],
username=account_info["platform_username"],
twitch_stream=twitch_stream
)

Expand Down
6 changes: 3 additions & 3 deletions clients/interactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ def should_add_player_to_squad_player_session_list(member, before, after):
otherwise False
"""
return discord_utils.in_fortnite_role(member) and \
discord_utils.joined_fortnite_voice_channel(before, after)
discord_utils.joined_fortnite_voice_channel(before, after)


def should_remove_player_from_squad_player_session_list(member, before, after):
""" Return True if player should be removed from current session
""" Return True if player should be removed from current session
squad player list, otherwise False """
return discord_utils.in_fortnite_role(member) and \
discord_utils.left_fortnite_voice_channel(before, after)


def send_track_question(member, before, after):
""" Return True if the track question should be sent,
Expand Down
5 changes: 3 additions & 2 deletions clients/openai.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import openai

OPENAI_MODEL = "gpt-3.5-turbo"
PROMPT_PREFIX = "Use young slangs and speak like you're chill. Be sarcastic."


def initialize():
Expand All @@ -17,14 +18,14 @@ async def ask_chatgpt(prompt, logger):
messages=[
{
"role": "user",
"content": prompt
"content": f"{PROMPT_PREFIX} {prompt}"
}
]
)

logger.info({
"name": "Ask ChatGPT interaction",
"prompt": "prompt",
"prompt": prompt,
"response": completion.to_dict_recursive()
})

Expand Down
3 changes: 0 additions & 3 deletions exceptions.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
class UserDoesNotExist(Exception):
""" User does not exist """
pass


class UserStatisticsNotFound(Exception):
""" User statistics not found (ex: private, invalid ID) """
pass


class NoSeasonDataError(Exception):
""" No season data found """
pass
39 changes: 20 additions & 19 deletions fortnite_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import commands
from auth import validate
from error_handlers import initialize_error_handlers
from exceptions import NoSeasonDataError, UserDoesNotExist, UserStatisticsNotFound
from logger import initialize_request_logger, configure_logger, get_logger_with_context, log_command


Expand Down Expand Up @@ -100,9 +101,9 @@ async def on_guild_join(guild):

@bot.event
async def on_voice_state_update(member, before, after):
""" Event handler to track squad stats on voice channel join """
logger = get_logger_with_context(identifier="Main")

""" Event handler to track squad stats on voice channel join """
try:
if interactions.should_add_player_to_squad_player_session_list(member, before, after):
if member.display_name in FORTNITE_DISCORD_ROLE_USERS_DICT:
Expand All @@ -112,12 +113,13 @@ async def on_voice_state_update(member, before, after):
if interactions.should_remove_player_from_squad_player_session_list(member, before, after):
if member.display_name in FORTNITE_DISCORD_ROLE_USERS_DICT:
if FORTNITE_DISCORD_ROLE_USERS_DICT[member.display_name] in SQUAD_PLAYERS_LIST:
SQUAD_PLAYERS_LIST.pop(FORTNITE_DISCORD_ROLE_USERS_DICT[member.display_name])
SQUAD_PLAYERS_LIST.remove(FORTNITE_DISCORD_ROLE_USERS_DICT[member.display_name])

if not interactions.send_track_question(member, before, after):
return
except Exception as e:
logger.warning("Failed to run on_voice_state_update: %s", repr(e), exc_info=True)
except Exception as exc:
logger.warning("Failed to run on_voice_state_update: %s", repr(exc), exc_info=True)
SQUAD_PLAYERS_LIST.clear()

ctx, silent = await interactions.send_track_question_and_wait(
bot,
Expand All @@ -138,11 +140,14 @@ async def help(ctx):
@bot.command(name=commands.PLAYER_SEARCH_COMMAND,
help=commands.PLAYER_SEARCH_DESCRIPTION,
aliases=commands.PLAYER_SEARCH_ALIASES)
@log_command

async def player_search(ctx, *player_name, guid=False, silent=False):
""" Searches for a player's stats, output to Discord, and log in database """
player_name = " ".join(player_name)

# TODO: Parse guid for replays
# TODO: Change guid to is_guid

logger = get_logger_with_context(ctx)
logger.info("Searching for player stats: %s", player_name)

Expand All @@ -153,14 +158,14 @@ async def player_search(ctx, *player_name, guid=False, silent=False):

try:
await fortnite_api.get_player_stats(ctx, player_name, silent)
logger.info("Returned player statistics for: %s", player_name)
except (NoSeasonDataError, UserDoesNotExist, UserStatisticsNotFound) as exc:
logger.info("Unable to retrieve statistics for '%s': %s", player_name, exc)
await ctx.send(exc)
except Exception as exc:
logger.warning(repr(exc), exc_info=_should_log_traceback(exc))
if _is_known_error(exc):
await ctx.send(f"Player not found: {player_name}")
else:
await ctx.send(f"Failed to retrieve player statistics: {repr(exc)}")

logger.info("Stats returned for: %s", player_name)
error_msg = f"Failed to retrieve player statistics: {repr(exc)}"
logger.warning(error_msg, exc_info=_should_log_traceback(exc))
await ctx.send(error_msg)


@bot.command(name=commands.TRACK_COMMAND,
Expand Down Expand Up @@ -366,15 +371,11 @@ def _should_log_traceback(exc):
""" Returns True if a traceback should be logged,
otherwise False
"""
return not _is_known_error(exc)


def _is_known_error(exc):
""" Returns True if the error is known, otherwise False """
# TODO: Change to subclass and check instance variable flag
return exc.__class__.__name__ in (
"UserDoesNotExist",
return exc.__class__.__name__ not in (
"NoSeasonDataError"
"UserDoesNotExist",
"UserStatisticsNotFound",
)


Expand Down

0 comments on commit 1776564

Please sign in to comment.