#!/usr/bin/env python # coding: utf-8 # In[1]: from typing import List from dataclasses import asdict, dataclass import requests import logging from urllib.parse import urljoin import pandas as pd import matplotlib.pyplot as plt get_ipython().run_line_magic('matplotlib', 'inline') # import cufflinks as cf # cf.set_config_file(theme='pearl',sharing='public',offline=True) @dataclass class AscendancyUsageStat: count: int pct_of_players: float name: str @dataclass class SkillUsageStat: count: int pct_of_players: float skill_name: str skill_id: int # In[2]: class PoeNinja: base_url = 'https://poe.ninja/' class PATH: BUILDS = 'GET /api/data/%(snapshot_id)s/GetBuildOverview' def __init__(self): self.initialized = False # TODO: allow to set snapshot id, or make it a random uuid like value self.snapshot_id = '6edebd80442f53d8e46f58b063562346' self.logger = logging.getLogger('PoeNinjaAPI') def request(self, path, params=None, data=None): verb, path = path.split(" ") url = urljoin(self.base_url, path) if verb == 'GET': r = requests.get(url, params=params, data=data) r.raise_for_status() return r.json() else: raise NotImplementedError(f'{verb} not implemented') def get_builds(self, params): path = self.PATH.BUILDS % dict(snapshot_id=self.snapshot_id) return self.request(path, params=params) # In[3]: class BuildWheelAPI: data = None filters = {} def __init__(self, league: str, period: str): self.api = PoeNinja() self.league = league self.period = period self.prepare_data() def prepare_data(self): params = dict( overview=self.league, type="exp", timemachine=self.period, language="en" ) self.data = self.api.get_builds(params=params) return self def filter(self, ascendancy=None): if ascendancy and ascendancy not in self.data['classNames']: raise ValueError(f"Expected ascendancy \"{ascendancy}\" to be one of {api_data['classNames']}") self.filters['ascendancy'] = ascendancy return self @property def total_accounts(self) -> int: total_accounts = len(self.data['classes']) if self.filters.get('ascendancy'): total_accounts = 0 for asc_idx in self.data['classes']: asc = self.data['classNames'][asc_idx] if asc == self.filters['ascendancy']: total_accounts += 1 return total_accounts @property def active_skills(self): return self.data['activeSkills'] @property def active_skills_usage(self): return self.data['activeSkillUse'] def get_skill_usage_count(self, skill_id: int) -> int: skill_id = int(skill_id) count = 0 if self.filters.get('ascendancy'): for user_index in self.active_skills_usage[str(skill_id)]: ascendancy = self.data['classNames'][self.data['classes'][user_index]] if ascendancy == self.filters['ascendancy']: count = count + 1 return count else: return len(self.active_skills_usage[str(skill_id)]) def get_ascendancy_for_user(self, user_idx: int) -> str: user_idx = int(user_idx) return self.data['classNames'][self.data['classes'][user_idx]] def get_skill_name(self, skill_id: int) -> str: skill_id = int(skill_id) return self.active_skills[skill_id]['name'] def get_skill_usage_stats(self, ascendancy: str = None, skill_threshold: float = 0) -> List[SkillUsageStat]: skill_options = [] self.filter(ascendancy=ascendancy) # skill_usage_map is a list of user indexes that use the skill for skill_id, skill_usage_map in self.active_skills_usage.items(): skill_uses = self.get_skill_usage_count(skill_id=skill_id) if skill_uses and self.total_accounts: percentage_skill_uses = round(skill_uses / self.total_accounts * 100) active_skill_name = self.get_skill_name(skill_id=skill_id) skill_options.append( SkillUsageStat( count=skill_uses, pct_of_players=percentage_skill_uses, skill_name=active_skill_name, skill_id=skill_id) ) return sorted(skill_options, key=lambda item: -item.count) def get_ascendancy_usage_stats(self) -> List[AscendancyUsageStat]: asc_usages = {} for asc_idx in self.data['classes']: ascendancy = self.data['classNames'][asc_idx] if not asc_usages.get(ascendancy): asc_usages[ascendancy] = AscendancyUsageStat(count=0, pct_of_players=0, name=ascendancy) asc_usages[ascendancy].count += 1 for ascendancy, data in asc_usages.items(): data.pct = round(data.count / self.total_accounts * 100, 1) return sorted(asc_usages.values(), key=lambda item: -item.count) # In[29]: bw_ssf_day_one = BuildWheelAPI(league='archnemesis', period='day-5') bw_ssf_day_one_skill_usage_stats = bw_ssf_day_one.get_skill_usage_stats() bw_ssf_day_one_skill_usage_stats # In[ ]: bw_ssf_day_one = BuildWheelAPI(league='ssf-archnemesis', period='day-1') bw_ssf_day_one.get_skill_usage_stats() # In[ ]: bw_hc_day_one = BuildWheelAPI(league='hardcore-archnemesis', period='day-1') bw_hc_day_one.get_skill_usage_stats() # In[ ]: bw_hc_ssf_day_one = BuildWheelAPI(league='ssf-archnemesis-hc', period='day-1') bw_hc_ssf_day_one.get_skill_usage_stats()