Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 5 additions & 11 deletions src/database/dal/gw2/gw2_session_chars_dal.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,14 @@ def __init__(self, db_session, log):
self.db_utils = DBUtilsAsync(db_session)
self.log = log

async def insert_session_char(self, gw2_api, api_characters, insert_args: dict):
for char_name in api_characters:
uri = f"characters/{char_name}/core"
current_char = await gw2_api.call_api(uri, insert_args["api_key"])
name = current_char["name"]
profession = current_char["profession"]
deaths = current_char["deaths"]

async def insert_session_char(self, characters_data: list[dict], insert_args: dict):
for char in characters_data:
stmt = Gw2SessionChars(
session_id=insert_args["session_id"],
user_id=insert_args["user_id"],
name=name,
profession=profession,
deaths=deaths,
name=char["name"],
profession=char["profession"],
deaths=char["deaths"],
start=insert_args["start"],
end=insert_args["end"],
)
Expand Down
36 changes: 22 additions & 14 deletions src/gw2/cogs/characters.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import asyncio
import discord
from discord.ext import commands
from src.bot.tools import bot_utils, chat_formatting
from src.database.dal.gw2.gw2_key_dal import Gw2KeyDal
from src.gw2.cogs.gw2 import GuildWars2
from src.gw2.constants import gw2_messages
from src.gw2.tools import gw2_utils
from src.gw2.tools.gw2_client import Gw2Client
from src.gw2.tools.gw2_cooldowns import GW2CoolDowns

Expand Down Expand Up @@ -49,9 +51,16 @@ async def characters(ctx):
if "characters" not in permissions or "account" not in permissions:
return await bot_utils.send_error_msg(ctx, gw2_messages.API_KEY_NO_PERMISSION, True)

progress_msg = await gw2_utils.send_progress_embed(
ctx, "Please wait, I'm fetching your character data... (this may take a moment)"
)

try:
await ctx.message.channel.typing()
api_req_acc = await gw2_api.call_api("account", api_key)
# Fetch account info and all characters in parallel
api_req_acc, characters_data = await asyncio.gather(
gw2_api.call_api("account", api_key),
gw2_api.call_api("characters?ids=all", api_key),
)

color = ctx.bot.settings["gw2"]["EmbedColor"]
embed = discord.Embed(
Expand All @@ -62,20 +71,17 @@ async def characters(ctx):
embed.set_thumbnail(url=ctx.message.author.display_avatar.url)
embed.set_author(name=ctx.message.author.display_name, icon_url=ctx.message.author.display_avatar.url)

api_req_characters = await gw2_api.call_api("characters", api_key)
for char_name in api_req_characters:
await ctx.message.channel.typing()
current_char = await gw2_api.call_api(f"characters/{char_name}/core", api_key)
days = (current_char["age"] / 60) / 24
created = current_char["created"].split("T", 1)[0]
for char in characters_data:
days = (char["age"] / 60) / 24
created = char["created"].split("T", 1)[0]
embed.add_field(
name=char_name,
name=char["name"],
value=chat_formatting.inline(
f"Race: {current_char['race']}\n"
f"Gender: {current_char['gender']}\n"
f"Profession: {current_char['profession']}\n"
f"Level: {current_char['level']}\n"
f"Deaths: {current_char['deaths']}\n"
f"Race: {char['race']}\n"
f"Gender: {char['gender']}\n"
f"Profession: {char['profession']}\n"
f"Level: {char['level']}\n"
f"Deaths: {char['deaths']}\n"
f"Age: {round(days)} days\n"
f"Date: {created}\n"
),
Expand All @@ -84,9 +90,11 @@ async def characters(ctx):
embed.set_footer(
icon_url=ctx.bot.user.display_avatar.url, text=f"{bot_utils.get_current_date_time_str_long()} UTC"
)
await progress_msg.delete()
await bot_utils.send_embed(ctx, embed)

except Exception as e:
await progress_msg.delete()
await bot_utils.send_error_msg(ctx, e)
return ctx.bot.log.error(ctx, e)

Expand Down
91 changes: 43 additions & 48 deletions src/gw2/cogs/worlds.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import asyncio
import discord
from discord.ext import commands
from src.bot.tools import bot_utils, chat_formatting
Expand Down Expand Up @@ -41,34 +42,14 @@ async def worlds_na(ctx):
if not result:
return None

gw2_api = Gw2Client(ctx.bot)
embed_na = discord.Embed(description=chat_formatting.inline(gw2_messages.NA_SERVERS_TITLE))

failed_worlds = []

for world in worlds_ids:
wid = world["id"]
try:
await ctx.message.channel.typing()
matches = await gw2_api.call_api(f"wvw/matches?world={wid}")
if wid < 2001:
tier_number = matches["id"].replace("1-", "")
embed_na.add_field(
name=world["name"],
value=chat_formatting.inline(f"T{tier_number} {world['population']}"),
)
except Exception as e:
# Log the error but continue with other worlds
failed_worlds.append(f"{world['name']} (ID: {wid})")
ctx.bot.log.warning(f"Failed to fetch WvW data for world {world['name']} (ID: {wid}): {e}")
continue
progress_msg = await gw2_utils.send_progress_embed(
ctx, "Please wait, I'm fetching NA world data... (this may take a moment)"
)

# Add footer if some worlds failed
if failed_worlds:
embed_na.set_footer(
text=f"⚠️ Failed to load: {', '.join(failed_worlds[:3])}" + ("..." if len(failed_worlds) > 3 else "")
)
na_worlds = [w for w in worlds_ids if w["id"] < 2001]
embed_na, _ = await _fetch_worlds_matches(ctx, na_worlds, gw2_messages.NA_SERVERS_TITLE, "1-")

await progress_msg.delete()
await _send_paginated_worlds_embed(ctx, embed_na)
return None

Expand All @@ -86,36 +67,50 @@ async def worlds_eu(ctx):
if not result:
return None

gw2_api = Gw2Client(ctx.bot)
embed_eu = discord.Embed(description=chat_formatting.inline(gw2_messages.EU_SERVERS_TITLE))
progress_msg = await gw2_utils.send_progress_embed(
ctx, "Please wait, I'm fetching EU world data... (this may take a moment)"
)

eu_worlds = [w for w in worlds_ids if w["id"] > 2001]
embed_eu, _ = await _fetch_worlds_matches(ctx, eu_worlds, gw2_messages.EU_SERVERS_TITLE, "2-")

await progress_msg.delete()
await _send_paginated_worlds_embed(ctx, embed_eu)
return None


async def _fetch_worlds_matches(ctx, worlds, title, region_prefix):
"""Fetch WvW match data for a list of worlds in parallel."""
gw2_api = Gw2Client(ctx.bot)
embed = discord.Embed(description=chat_formatting.inline(title))
failed_worlds = []
sem = asyncio.Semaphore(5)

async def _fetch_match(world):
async with sem:
return await gw2_api.call_api(f"wvw/matches?world={world['id']}")

for world in worlds_ids:
wid = world["id"]
try:
await ctx.message.channel.typing()
matches = await gw2_api.call_api(f"wvw/matches?world={wid}")
if wid > 2001:
tier_number = matches["id"].replace("2-", "")
embed_eu.add_field(
name=world["name"],
value=chat_formatting.inline(f"T{tier_number} {world['population']}"),
)
except Exception as e:
# Log the error but continue with other worlds
failed_worlds.append(f"{world['name']} (ID: {wid})")
ctx.bot.log.warning(f"Failed to fetch WvW data for world {world['name']} (ID: {wid}): {e}")
tasks = [_fetch_match(w) for w in worlds]
results = await asyncio.gather(*tasks, return_exceptions=True)

for world, result in zip(worlds, results, strict=True):
if isinstance(result, Exception):
failed_worlds.append(f"{world['name']} (ID: {world['id']})")
ctx.bot.log.warning(f"Failed to fetch WvW data for world {world['name']} (ID: {world['id']}): {result}")
continue
tier_number = result["id"].replace(region_prefix, "")
embed.add_field(
name=world["name"],
value=chat_formatting.inline(f"T{tier_number} {world['population']}"),
)

# Add footer if some worlds failed
if failed_worlds:
embed_eu.set_footer(
text=f"⚠️ Failed to load: {', '.join(failed_worlds[:3])}" + ("..." if len(failed_worlds) > 3 else "")
embed.set_footer(
text=f"\u26a0\ufe0f Failed to load: {', '.join(failed_worlds[:3])}"
+ ("..." if len(failed_worlds) > 3 else "")
)

await _send_paginated_worlds_embed(ctx, embed_eu)
return None
return embed, failed_worlds


async def _send_paginated_worlds_embed(ctx, embed):
Expand Down
76 changes: 55 additions & 21 deletions src/gw2/cogs/wvw.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import asyncio
import discord
from discord.ext import commands
from src.bot.tools import bot_utils, chat_formatting
Expand Down Expand Up @@ -78,19 +79,25 @@ async def info(self, ctx, *, world: str = None):
return await bot_utils.send_error_msg(ctx, f"{gw2_messages.INVALID_WORLD_NAME}\n{world}")
return None

try:
await ctx.message.channel.typing()
matches = await gw2_api.call_api(f"wvw/matches?world={wid}")
progress_msg = await gw2_utils.send_progress_embed(
ctx, "Please wait, I'm fetching WvW info... (this may take a moment)"
)

# Resolve world info: WR team IDs vs legacy worlds
try:
# Fetch match data and world info in parallel when possible
if is_wr_team_id(wid):
matches = await gw2_api.call_api(f"wvw/matches?world={wid}")
world_name = get_team_name(wid) or f"Team {wid}"
population = "N/A"
else:
worldinfo = await gw2_api.call_api(f"worlds?id={wid}")
matches, worldinfo = await asyncio.gather(
gw2_api.call_api(f"wvw/matches?world={wid}"),
gw2_api.call_api(f"worlds?id={wid}"),
)
world_name = worldinfo["name"]
population = worldinfo["population"]
except Exception as e:
await progress_msg.delete()
await bot_utils.send_error_msg(ctx, e)
return ctx.bot.log.error(ctx, e)

Expand Down Expand Up @@ -149,6 +156,7 @@ async def info(self, ctx, *, world: str = None):
embed.add_field(name="Deaths", value=chat_formatting.inline(deaths))
embed.add_field(name="K/D ratio", value=chat_formatting.inline(str(kd)))
embed.add_field(name="Population", value=chat_formatting.inline(population), inline=False)
await progress_msg.delete()
await bot_utils.send_embed(ctx, embed)
return None

Expand Down Expand Up @@ -176,20 +184,32 @@ async def match(self, ctx, *, world: str = None):
return await bot_utils.send_error_msg(ctx, f"{gw2_messages.INVALID_WORLD_NAME}: {world}")
return None

progress_msg = await gw2_utils.send_progress_embed(
ctx, "Please wait, I'm fetching WvW match data... (this may take a moment)"
)

try:
await ctx.message.channel.typing()
matches = await gw2_api.call_api(f"wvw/matches?world={wid}")

tier = _resolve_tier(matches)

green_worlds_names = await _get_map_names_embed_values(ctx, "green", matches)
blue_worlds_names = await _get_map_names_embed_values(ctx, "blue", matches)
red_worlds_names = await _get_map_names_embed_values(ctx, "red", matches)

green_values = await _get_match_embed_values("green", matches)
blue_values = await _get_match_embed_values("blue", matches)
red_values = await _get_match_embed_values("red", matches)
(
green_worlds_names,
blue_worlds_names,
red_worlds_names,
green_values,
blue_values,
red_values,
) = await asyncio.gather(
_get_map_names_embed_values(ctx, "green", matches),
_get_map_names_embed_values(ctx, "blue", matches),
_get_map_names_embed_values(ctx, "red", matches),
_get_match_embed_values("green", matches),
_get_match_embed_values("blue", matches),
_get_match_embed_values("red", matches),
)
except Exception as e:
await progress_msg.delete()
await bot_utils.send_error_msg(ctx, e)
return ctx.bot.log.error(ctx, e)

Expand All @@ -201,6 +221,7 @@ async def match(self, ctx, *, world: str = None):
embed.add_field(name="--------------------", value=green_values)
embed.add_field(name="--------------------", value=blue_values)
embed.add_field(name="--------------------", value=red_values)
await progress_msg.delete()
await bot_utils.send_embed(ctx, embed)
return None

Expand Down Expand Up @@ -228,20 +249,32 @@ async def kdr(self, ctx, *, world: str = None):
return await bot_utils.send_error_msg(ctx, f"{gw2_messages.INVALID_WORLD_NAME}: {world}")
return None

progress_msg = await gw2_utils.send_progress_embed(
ctx, "Please wait, I'm fetching WvW K/D data... (this may take a moment)"
)

try:
await ctx.message.channel.typing()
matches = await gw2_api.call_api(f"wvw/matches?world={wid}")

tier = _resolve_tier(matches)

green_worlds_names = await _get_map_names_embed_values(ctx, "green", matches)
blue_worlds_names = await _get_map_names_embed_values(ctx, "blue", matches)
red_worlds_names = await _get_map_names_embed_values(ctx, "red", matches)

green_values = await _get_kdr_embed_values("green", matches)
blue_values = await _get_kdr_embed_values("blue", matches)
red_values = await _get_kdr_embed_values("red", matches)
(
green_worlds_names,
blue_worlds_names,
red_worlds_names,
green_values,
blue_values,
red_values,
) = await asyncio.gather(
_get_map_names_embed_values(ctx, "green", matches),
_get_map_names_embed_values(ctx, "blue", matches),
_get_map_names_embed_values(ctx, "red", matches),
_get_kdr_embed_values("green", matches),
_get_kdr_embed_values("blue", matches),
_get_kdr_embed_values("red", matches),
)
except Exception as e:
await progress_msg.delete()
await bot_utils.send_error_msg(ctx, e)
return ctx.bot.log.error(ctx, e)

Expand All @@ -253,6 +286,7 @@ async def kdr(self, ctx, *, world: str = None):
embed.add_field(name="--------------------", value=green_values)
embed.add_field(name="--------------------", value=blue_values)
embed.add_field(name="--------------------", value=red_values)
await progress_msg.delete()
await bot_utils.send_embed(ctx, embed)
return None

Expand Down
Loading