177 lines
6.6 KiB
Python
177 lines
6.6 KiB
Python
# Most of this code is copied from https://github.com/Kowlin/Sentinel/blob/master/githubcards/http.py
|
|
# Most of the source of this file can be found at: https://github.com/Kowlin/GraphQL-WoWLogs/blob/master/wowlogs/http.py
|
|
|
|
from enum import unique
|
|
import aiohttp
|
|
import logging
|
|
|
|
from redbot.core.bot import Red
|
|
from redbot.core.config import Config
|
|
|
|
from datetime import datetime
|
|
|
|
from .calls import Queries
|
|
|
|
log = logging.getLogger("red.aikaterna.warcraftlogs.http")
|
|
baseurl = "https://classic.warcraftlogs.com"
|
|
graphql_url = baseurl + "/api/v2/client"
|
|
|
|
|
|
async def generate_bearer(bot: Red, config: Config) -> str:
|
|
"""Generate the Bearer token used in GraphQL queries
|
|
|
|
Bot and Config are imported here from the main class,
|
|
due the need to save data to both of them."""
|
|
tokens = await bot.get_shared_api_tokens("warcraftlogs")
|
|
|
|
client_id = tokens.get("client_id", "")
|
|
client_secret = tokens.get("client_secret", "")
|
|
if not client_id:
|
|
log.error("Generate bearer: No valid client ID set")
|
|
return None
|
|
elif not client_secret:
|
|
log.error("Generate bearer: No valid client secret set")
|
|
return None
|
|
else:
|
|
headers = {"User-Agent": "Red-DiscordBot/WarcraftLogsCog"}
|
|
async with aiohttp.ClientSession(
|
|
headers=headers, auth=aiohttp.BasicAuth(login=client_id, password=client_secret)
|
|
) as session:
|
|
form = aiohttp.FormData()
|
|
form.add_field("grant_type", "client_credentials")
|
|
request = await session.post(f"{baseurl}/oauth/token", data=form)
|
|
json = await request.json()
|
|
if json.get("error", ""):
|
|
log.error("There is an error generating the bearer key, probably a misconfigured client id and secret.")
|
|
log.error(f"{json['error']}: {json['error_description']}")
|
|
return None
|
|
|
|
timestamp_now = int(datetime.utcnow().timestamp()) # Round the timestamp down to a full number
|
|
bearer_timestamp = (
|
|
timestamp_now + json["expires_in"] - 120
|
|
) # Reduce the timestamp by 2 min to be on the safe side of possible errors
|
|
await config.bearer_timestamp.set(bearer_timestamp)
|
|
|
|
log.info("Bearer token created")
|
|
await bot.set_shared_api_tokens("warcraftlogs", bearer=json["access_token"])
|
|
return json["access_token"]
|
|
|
|
|
|
class WoWLogsClient:
|
|
"""This is where the magic happens."""
|
|
|
|
def __init__(self, bearer: str) -> None:
|
|
self.session: aiohttp.ClientSession
|
|
self._bearer: str
|
|
self._create_session(bearer)
|
|
|
|
def _create_session(self, bearer: str) -> None:
|
|
headers = {
|
|
"Authorization": f"Bearer {bearer}",
|
|
"Content-Type": "application/json",
|
|
# Not strictly required to set an user agent, yet still respectful.
|
|
"User-Agent": "Red-DiscordBot/WarcraftLogsCog",
|
|
}
|
|
self._bearer = bearer
|
|
self.session = aiohttp.ClientSession(headers=headers)
|
|
|
|
async def recreate_session(self, bearer: str) -> None:
|
|
await self.session.close()
|
|
self._create_session(bearer)
|
|
|
|
async def check_bearer(self):
|
|
async with self.session.post(graphql_url, json={"query": Queries.check_bearer}) as call:
|
|
try:
|
|
await call.json()
|
|
except aiohttp.ContentTypeError:
|
|
log.error("Bearer token has been invalidated")
|
|
return False
|
|
return True
|
|
|
|
async def get_overview(self, char_name: str, char_realm: str, char_server: str, zone_id: int):
|
|
async with self.session.post(
|
|
graphql_url,
|
|
json={
|
|
"query": Queries.get_overview,
|
|
"variables": {
|
|
"char_name": char_name,
|
|
"char_realm": char_realm,
|
|
"char_server": char_server,
|
|
"zone_id": zone_id,
|
|
},
|
|
},
|
|
) as call:
|
|
try:
|
|
json = await call.json()
|
|
except aiohttp.ContentTypeError:
|
|
log.error("Bearer token has been invalidated")
|
|
return None
|
|
|
|
error = json.get("error", None)
|
|
if error:
|
|
log.error(f"Error: {error}")
|
|
|
|
return json
|
|
|
|
async def get_last_encounter(self, char_name: str, char_realm: str, char_server: str):
|
|
async with self.session.post(
|
|
graphql_url,
|
|
json={
|
|
"query": Queries.get_last_encounter,
|
|
"variables": {"char_name": char_name, "char_realm": char_realm, "char_server": char_server},
|
|
},
|
|
) as call:
|
|
try:
|
|
json = await call.json()
|
|
except aiohttp.ContentTypeError:
|
|
log.error("Bearer token has been invalidated")
|
|
return None
|
|
|
|
error = json.get("error", None)
|
|
if error:
|
|
log.error(f"Error: {error}")
|
|
return json
|
|
|
|
if json["data"]["characterData"]["character"] is None:
|
|
return False
|
|
|
|
data = json["data"]["characterData"]["character"]["recentReports"]["data"]
|
|
unique_encouters = {"ids": [], "latest": 0, "latest_time": 0}
|
|
for fight in data[0]["fights"]:
|
|
if fight["encounterID"] not in unique_encouters["ids"]:
|
|
unique_encouters["ids"].append(int(fight["encounterID"]))
|
|
if fight["endTime"] > unique_encouters["latest_time"]:
|
|
unique_encouters["latest"] = fight["encounterID"]
|
|
unique_encouters["latest_time"] = fight["endTime"]
|
|
return unique_encouters
|
|
|
|
async def get_gear(self, char_name: str, char_realm: str, char_server: str, encounter_id: int):
|
|
async with self.session.post(
|
|
graphql_url,
|
|
json={
|
|
"query": Queries.get_gear,
|
|
"variables": {
|
|
"char_name": char_name,
|
|
"char_realm": char_realm,
|
|
"char_server": char_server,
|
|
"encounter": encounter_id,
|
|
},
|
|
},
|
|
) as call:
|
|
try:
|
|
json = await call.json()
|
|
except aiohttp.ContentTypeError:
|
|
log.error("Bearer token has been invalidated")
|
|
return None
|
|
|
|
error = json.get("error", None)
|
|
if error:
|
|
log.error(f"Error: {error}")
|
|
return json
|
|
|
|
if json["data"]["characterData"]["character"] is None:
|
|
return False
|
|
|
|
data = json["data"]["characterData"]["character"]
|
|
return data
|