Files
aikaterna-cogs/warcraftlogs/warcraftlogs.py
2020-09-25 16:23:33 -07:00

351 lines
14 KiB
Python

from typing import Literal
import aiohttp
import datetime
import discord
import itertools
import json
from operator import itemgetter
from redbot.core import Config, commands, checks
from redbot.core.utils.chat_formatting import box
from redbot.core.utils.menus import menu, DEFAULT_CONTROLS
class WarcraftLogs(commands.Cog):
"""Access Warcraftlogs stats."""
async def red_delete_data_for_user(
self, *, requester: Literal["discord", "owner", "user", "user_strict"], user_id: int,
):
await self.config.user_from_id(user_id).clear()
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, 2713931001, force_registration=True)
self.session = aiohttp.ClientSession()
self.zones = [1005, 1004, 1003, 1002] # Ony and MC removed as we are now in ph 5
self.partitions = [3, 2] # No partition 1 needed here now - ZG, AQ, BWL were not present in ph 1 & 2
default_user = {
"charname": None,
"realm": None,
"region": None,
}
default_global = {
"apikey": None,
}
self.config.register_user(**default_user)
self.config.register_global(**default_global)
def cog_unload(self):
self.bot.loop.create_task(self.session.close())
@commands.command()
async def wclregion(self, ctx, region: str):
"""Set your region."""
valid_regions = ["EU", "US"]
if region.upper() not in valid_regions:
return await ctx.send("Valid regions are: {humanize_list(valid_regions)}")
await self.config.user(ctx.author).region.set(region)
await ctx.send(f"Your server's region was set to {region.upper()}.")
@commands.command()
async def wclcharname(self, ctx, charname: str):
"""Set your character's name."""
await self.config.user(ctx.author).charname.set(charname)
await ctx.send(f"Your character name was set to {charname.title()}.")
@commands.command()
async def wclrealm(self, ctx, *, realm: str):
"""Set your realm."""
realmname = realm.replace(" ", "-")
await self.config.user(ctx.author).realm.set(realmname)
await ctx.send(f"Your realm was set to {realm.title()}.")
@commands.command()
async def wclsettings(self, ctx, user: discord.User = None):
"""Show your current settings."""
if not user:
user = ctx.author
userinfo = await self.config.user(user).all()
msg = f"[Settings for {user.display_name}]\n"
charname = userinfo["charname"].title() if userinfo["charname"] else "None"
realmname = userinfo["realm"].title().replace("-", " ") if userinfo["realm"] else "None"
regionname = userinfo["region"].upper() if userinfo["region"] else "None"
msg += f"Character: {charname}\nRealm: {realmname}\nRegion: {regionname}\n"
await ctx.send(box(msg, lang="ini"))
@commands.command()
@checks.is_owner()
async def wclapikey(self, ctx, apikey: str):
"""Set the api key."""
await self.config.apikey.set(apikey)
try:
await ctx.message.delete()
except discord.errors.Forbidden:
pass
await ctx.send(f"The WarcraftLogs API key has been set.")
@commands.command()
@commands.guild_only()
async def wclrank(self, ctx, username=None, realmname=None, region=None):
"""Fetch ranking info about a player."""
userdata = await self.config.user(ctx.author).all()
apikey = await self.config.apikey()
if not apikey:
return await ctx.send("The bot owner needs to set a WarcraftLogs API key before this can be used.")
if not username:
username = userdata["charname"]
if not username:
return await ctx.send("Please specify a character name with this command.")
if not realmname:
realmname = userdata["realm"]
if not realmname:
return await ctx.send("Please specify a realm name with this command.")
if not region:
region = userdata["region"]
if not region:
return await ctx.send("Please specify a region name with this command.")
final_embed_list = []
kill_data = []
log_data = []
async with ctx.channel.typing():
for zone in self.zones:
for phase in self.partitions:
url = f"https://classic.warcraftlogs.com/v1/parses/character/{username}/{realmname}/{region}?zone={zone}&partition={phase}&api_key={apikey}"
try:
async with self.session.request("GET", url) as page:
data = await page.text()
data = json.loads(data)
except Exception as e:
return await ctx.send(
f"Oops, there was a problem fetching something (Zone {zone}/Phase {phase}): {e}"
)
if "error" in data:
return await ctx.send(
f"{username.title()} - {realmname.title()} ({region.upper()}) doesn't have any valid logs that I can see.\nError {data['status']}: {data['error']}"
)
# Logged Kills
zone_name = self.get_zone(zone)
zone_and_phase = f"{zone_name}_{phase}"
area_data = self.get_kills(data, zone_and_phase)
kill_data.append(area_data)
# Log IDs for parses
log_info = self.get_log_id(data, zone_and_phase)
log_data.append(log_info)
# Logged Kill sorting
embed1 = discord.Embed(title=f"{username.title()} - {realmname.title()} ({region.upper()})\nLogged Kills")
for item in kill_data:
zone_kills = ""
for boss_info in list(item.values()):
zone_name, phase_num = self.clean_name(list(item))
for boss_name, boss_kills in boss_info.items():
zone_kills += f"{boss_name}: {boss_kills}\n"
if zone_kills:
embed1.add_field(name=f"{zone_name}\n{phase_num}", value=zone_kills)
embed1.set_footer(text="Molten Core and Onyxia are not currently displayed as we are now in Phase 5.")
final_embed_list.append(embed1)
# Log ID sorting
wcl_url = "https://classic.warcraftlogs.com/reports/{}#fight={}"
log_embed_list = []
for item in log_data:
log_page = ""
for id_data in list(item.values()):
sorted_item = {k: v for k, v in sorted(id_data.items(), key=lambda item: item[1], reverse=True)}
short_list = dict(itertools.islice(sorted_item.items(), 5))
zone_name, phase_num = self.clean_name(list(item))
for log_id, info_list in short_list.items():
# info_list: [timestamp:int, percentile:int, spec:str, fightid:int, rank:int, outOf:int]
# log_id: encounterid-encountername
log_url = log_id.split("-")[0]
log_name = log_id.split("-")[1]
log_page += f"{wcl_url.format(log_url, info_list[3])}\n{self.time_convert(info_list[0])} UTC\nEncounter: {log_name}\nDPS Percentile: {info_list[1]} [{info_list[4]} of {info_list[5]}] ({info_list[2]})\n\n"
if id_data:
embed = discord.Embed(
title=f"{username.title()} - {realmname.title()} ({region.upper()})\nWarcraft Log IDs"
)
embed.add_field(name=f"{zone_name}\n{phase_num}", value=log_page, inline=False)
embed.set_footer(text="Up to the last 5 logs shown per encounter/phase.")
log_embed_list.append(embed)
for log_embed in log_embed_list:
final_embed_list.append(log_embed)
await menu(ctx, final_embed_list, DEFAULT_CONTROLS)
# @commands.command()
# @commands.guild_only()
# async def wclgear(self, ctx, username=None, realmname=None, region=None):
# """Fetch gear info about a player."""
# userdata = await self.config.user(ctx.author).all()
# apikey = await self.config.apikey()
# if not apikey:
# return await ctx.send("The bot owner needs to set a WarcraftLogs API key before this can be used.")
# if not username:
# username = userdata["charname"]
# if not username:
# return await ctx.send("Please specify a character name with this command.")
# if not realmname:
# realmname = userdata["realm"]
# if not realmname:
# return await ctx.send("Please specify a realm name with this command.")
# if not region:
# region = userdata["region"]
# if not region:
# return await ctx.send("Please specify a region name with this command.")
# all_encounters = []
# for zone, phase in [(x, y) for x in self.zones for y in self.partitions]:
# url = f"https://classic.warcraftlogs.com/v1/parses/character/{username}/{realmname}/{region}?zone={zone}&partition={phase}&api_key={apikey}"
# async with self.session.request("GET", url) as page:
# data = await page.text()
# data = json.loads(data)
# if "error" in data:
# return await ctx.send(
# f"{username.title()} - {realmname.title()} ({region.upper()}) doesn't have any valid logs that I can see.\nError {data['status']}: {data['error']}"
# )
# if data:
# encounter = self.get_recent_gear(data)
# if encounter:
# all_encounters.append(encounter)
# final = self.get_recent_gear(all_encounters)
# wowhead_url = "https://classic.wowhead.com/item={}"
# wcl_url = "https://classic.warcraftlogs.com/reports/{}"
# itempage = ""
# for item in final["gear"]:
# if item["id"] == 0:
# continue
# rarity = self.get_rarity(item)
# itempage += f"{rarity} [{item['name']}]({wowhead_url.format(item['id'])})\n"
# itempage += f"\nAverage ilvl: {final['ilvlKeyOrPatch']}"
# embed = discord.Embed(
# title=f"{final['characterName']} - {final['server']} ({region.upper()})\n{final['class']} ({final['spec']})",
# description=itempage,
# )
# embed.set_footer(
# text=f"Gear data pulled from {wcl_url.format(final['reportID'])}\nEncounter: {final['encounterName']}\nLog Date/Time: {self.time_convert(final['startTime'])} UTC"
# )
# await ctx.send(embed=embed)
@staticmethod
def get_rarity(item):
rarity = item["quality"]
if rarity == "common":
return ""
elif rarity == "uncommon":
return "🟩"
elif rarity == "rare":
return "🟦"
elif rarity == "epic":
return "🟪"
else:
return "🔳"
@staticmethod
def time_convert(time):
time = str(time)[0:10]
value = datetime.datetime.fromtimestamp(int(time)).strftime("%Y-%m-%d %H:%M:%S")
return value
@staticmethod
def get_kills(data, zone_and_phase):
# data is json data
# zone_and_phase: Name_Phasenum
boss_kills = {}
for encounter in data:
if encounter["encounterName"] not in boss_kills.keys():
boss_kills[encounter["encounterName"]] = 0
boss_kills[encounter["encounterName"]] += 1
complete_info = {}
complete_info[zone_and_phase] = boss_kills
return complete_info
@staticmethod
def get_zone(zone):
# Zone ID and name is available from the API, but why make another
# call to a url when it's simple for now... maybe revisit in phase 5+
if zone == 1000:
zone_name = "MoltenCore"
elif zone == 1001:
zone_name = "Onyxia"
elif zone == 1002:
zone_name = "BWL"
elif zone == 1003:
zone_name = "ZG"
elif zone == 1004:
zone_name = "AQ20"
elif zone == 1005:
zone_name = "AQ40"
else:
zone_name = None
return zone_name
@staticmethod
def clean_name(zone_and_phase):
zone_and_phase = zone_and_phase[0]
zone_name = zone_and_phase.split("_")[0]
phase_num = zone_and_phase[-1]
if zone_name == "MoltenCore":
zone_name = "Molten Core"
elif zone_name == "BWL":
zone_name = "Blackwing Lair"
elif zone_name == "ZG":
zone_name = "Zul'Gurub"
elif zone_name == "AQ20":
zone_name = "Ahn'Qiraj Ruins"
elif zone_name == "AQ40":
zone_name = "Ahn'Qiraj Temple"
else:
zone_name = zone_name
if phase_num == "1":
phase_num = "Phase 1 & 2"
elif phase_num == "2":
phase_num = "Phase 3 & 4"
else:
phase_num = "Phase 5"
return zone_name, phase_num
@staticmethod
def get_log_id(data, zone_and_phase):
report_ids = {}
for encounter in data:
keyname = f"{encounter['reportID']}-{encounter['encounterName']}"
report_ids[keyname] = [
encounter["startTime"],
encounter["percentile"],
encounter["spec"],
encounter["fightID"],
encounter["rank"],
encounter["outOf"],
]
complete_info = {}
complete_info[zone_and_phase] = report_ids
return complete_info
@staticmethod
def get_recent_gear(data):
date_sorted_data = sorted(data, key=itemgetter("startTime"), reverse=True)
for encounter in date_sorted_data:
try:
item_name = encounter["gear"][0]["name"]
if item_name == "Unknown Item":
continue
else:
return encounter
except KeyError:
return None