Files
aikaterna-cogs/warcraftlogs/warcraftlogs.py
Draper ae9dbf569c Data API Complicance (Red 3.4) (#136)
* Simple ones first

* Less simple but still simple.

* Slightly more complicated

* use correct name

* move to module

* Black -l 120

* review

* give users the proper feedback

Co-authored-by: aikaterna <20862007+aikaterna@users.noreply.github.com>
2020-08-26 09:57:43 -07:00

338 lines
13 KiB
Python

from imaplib 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 = [1003, 1002, 1001, 1000]
self.partitions = [2, 1]
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)
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"
else:
zone_name = "ZG"
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"
else:
zone_name = zone_name
if phase_num == "1":
phase_num = "Phase 1 & 2"
else:
phase_num = "Phase 3 & 4"
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