Last active
September 12, 2023 06:49
-
-
Save szarroug3/e3cb6a8a77f765c28444c56c23698788 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import requests | |
import xlsxwriter | |
from argparse import ArgumentParser | |
from collections import defaultdict | |
from copy import copy | |
from datetime import datetime | |
from itertools import zip_longest | |
from time import sleep | |
from urllib.parse import urlencode | |
DOTA_BASE_URL = 'https://api.steampowered.com/IDOTA2Match_570/{{func_name}}/V1/?key={api_key}&{{params}}' | |
OPENDOTA_BASE_URL = 'https://api.opendota.com/api/{func_name}/{params}' | |
LANE_ROLE = { | |
1: ['1', '5'], | |
2: ['2'], | |
3: ['3', '4'], | |
4: ['J'] | |
} | |
COLORS = ['yellow', 'orange', 'cyan', 'silver', '#FF7F50', '#FFD700', '#ADFF2F', | |
'#40E0D0', '#00BFFF', '#D8BFD8', '#FFC0CB', '#FAEBD7', '#E6E6FA', '#FFD700'] | |
RANKS = {'1': 'Herald', '2': 'Guardian', '3': 'Crusader', '4': 'Archon', | |
'5': 'Legend', '6': 'Ancient', '7': 'Divine', '8': 'Immortal', 'N': 'Unranked'} | |
def dota_api_call(func_name, **params): | |
for retry in range(1, 6): | |
resp = requests.get(DOTA_BASE_URL.format(func_name=func_name, params=urlencode(params))) | |
if resp.ok: | |
break | |
sleep(20) | |
print('Retry #{}'.format(retry)) | |
if not resp.ok: | |
raise Exception('Something went wrong: GET {}: {} {}'.format(func_name, resp.status_code, resp.reason)) | |
return resp.json().get('result', {}) | |
def opendota_api_call(func_name, *params): | |
for retry in range(1, 6): | |
resp = requests.get(OPENDOTA_BASE_URL.format(func_name=func_name, params='/'.join(params))) | |
if resp.ok: | |
break | |
sleep(20) | |
print('Retry #{}'.format(retry)) | |
if not resp.ok: | |
raise Exception('Something went wrong: GET {}: {} {}'.format(func_name, resp.status_code, resp.reason)) | |
return resp.json() | |
def get_heroes(): | |
data = opendota_api_call('heroes') | |
return {hero['id']: hero['localized_name'] for hero in data} | |
def get_captains(match_id, side, player_names): | |
data = dota_api_call('GetMatchDetails', match_id=match_id) | |
captain_id = data.get('dire_captain') if side == 'dire' else data.get('radiant_captain') | |
enemy_captain_id = data.get('dire_captain') if side == 'radiant' else data.get('radiant_captain') | |
if captain_id: | |
captain_name = get_player_name(captain_id, player_names) | |
else: | |
captain_name = '' | |
if enemy_captain_id: | |
enemy_captain_name = get_player_name(enemy_captain_id, player_names) | |
else: | |
enemy_captain_name = '' | |
return captain_name, enemy_captain_name | |
def get_player_names(match, picks, player_names): | |
for player in match['players']: | |
hero_id = player['hero_id'] | |
account_id = player['account_id'] | |
if hero_id not in picks: | |
continue | |
picks[hero_id]['player_name'] = get_player_name(account_id, player_names) | |
return picks, player_names | |
def get_player_name(account_id, player_names): | |
account_id = str(account_id) | |
if account_id not in player_names: | |
data = opendota_api_call('players', account_id) | |
if 'profile' not in data: | |
return | |
player_names[account_id] = {'name': data['profile']['personaname']} | |
return player_names[account_id]['name'] | |
def parse_match(match, heroes): | |
try: | |
data = opendota_api_call('matches', str(match)) | |
except: | |
print('match {} could not be found using dotabuff api'.format(match)) | |
return | |
if data.get('picks_bans'): | |
return ParsedMatch(data, heroes) | |
else: | |
return UnparsedMatch(data, heroes) | |
class Match(object): | |
def __init__(self, data, team_id, players): | |
self.data = data | |
self.team_id = team_id | |
self.match_id = data['match_id'] | |
self.side = self.get_side(team_id, str(data['dire_team_id'])) | |
self.win = self.get_win(data['radiant_win']) | |
self.picks = {} | |
self.player_lanes = defaultdict() | |
def get_side(self, team_id, dire_team_id): | |
if dire_team_id == team_id: | |
return 'dire' | |
return 'radiant' | |
def get_win(self, radiant_win): | |
if self.side == 'radiant' and radiant_win: | |
return True | |
if self.side == 'dire' and not radiant_win: | |
return True | |
return False | |
class ParsedMatch(Match): | |
def __init__(self, data, heroes, team_id=None, players={}): | |
Match.__init__(self, data, team_id, players) | |
self.team_side_number = 0 if self.side == 'radiant' else 1 | |
self.first_pick = None | |
self.bans = {} | |
self.enemy_bans = {} | |
self.captain, self.enemy_captain = get_captains(self.match_id, self.side, players) | |
self.get_picks_bans(heroes) | |
self.get_support_indices() | |
self.get_player_info() | |
def get_picks_bans(self, heroes): | |
for picks_bans in self.data['picks_bans']: | |
# enemy pick, don't care | |
if picks_bans['team'] != self.team_side_number and picks_bans['is_pick']: | |
continue | |
if self.first_pick is None: | |
if picks_bans['team'] == self.team_side_number and picks_bans['is_pick']: | |
self.first_pick = picks_bans['order'] == 6 | |
hero_id = picks_bans['hero_id'] | |
hero_name = heroes[hero_id] | |
if picks_bans['is_pick']: | |
self.picks[hero_id] = {'name': hero_name, 'order': picks_bans['order']} | |
else: | |
if picks_bans['team'] == self.team_side_number: | |
self.bans[hero_id] = {'name': hero_name, 'order': picks_bans['order']} | |
else: | |
self.enemy_bans[hero_id] = {'name': hero_name, 'order': picks_bans['order']} | |
def get_support_indices(self): | |
support = {} | |
for player in self.data['players']: | |
if player['hero_id'] not in self.picks: | |
continue | |
support[player['account_id']] = 0 | |
if not player['purchase']: | |
continue | |
for item, count in player['purchase'].items(): | |
if item in ['ward_sentry', 'ward_observer', 'dust']: | |
support[player['account_id']] += count | |
self.support_indices = [account_id for account_id, count in sorted(support.items(), key=lambda item: item[1])] | |
def get_player_role(self, lane_role, support_index): | |
possibilities = LANE_ROLE.get(lane_role, ['?']) | |
if len(possibilities) == 1: | |
return possibilities[0] | |
if support_index > 2: | |
return possibilities[1] | |
return possibilities[0] | |
def get_player_info(self): | |
for player in self.data['players']: | |
hero_id = player['hero_id'] | |
if hero_id not in self.picks: | |
continue | |
role = self.get_player_role(player.get('lane_role'), self.support_indices.index(player['account_id'])) | |
self.picks[hero_id].update({'role': role, | |
'roaming': player.get('is_roaming', False)}) | |
self.player_lanes[player['account_id']] = role | |
class UnparsedMatch(Match): | |
def __init__(self, data, heroes, team_id=None, players={}, account_ids=[]): | |
Match.__init__(self, data, team_id, players) | |
self.get_player_info(heroes, account_ids) | |
def get_player_info(self, heroes, account_ids): | |
for player in self.data['players']: | |
hero_id = player['hero_id'] | |
if str(player['account_id']) not in account_ids: | |
continue | |
self.picks[hero_id] = ({'name': heroes[hero_id], | |
'roaming': player.get('is_roaming', False)}) | |
class Player(object): | |
def __init__(self, account_id, player_names, heroes, teams): | |
self.account_id = account_id | |
self.name = get_player_name(self.account_id, player_names) | |
if not self.name: | |
raise Exception('Player profile could not be found.') | |
self.rank = self.get_rank() | |
self.heroes = self.get_heroes(heroes) | |
self.recent_heroes_count, self.recent_heroes_time = self.get_recent_heroes(heroes) | |
self.statistics = self.get_player_lane_statistics(teams) | |
def __str__(self): | |
return f'{self.account_id}: {self.name}' | |
def get_rank(self): | |
data = opendota_api_call('players', self.account_id) | |
rank_tier = str(data['rank_tier']) | |
medal = RANKS[rank_tier[0]] | |
if medal == 'Immortal': | |
stars = data['leaderboard_rank'] | |
else: | |
stars = rank_tier[1] | |
if stars: | |
return '{} {}'.format(medal, stars) | |
return medal | |
def get_heroes(self, heroes): | |
data = opendota_api_call('players', self.account_id, 'heroes') | |
hero_data = [] | |
for hero in sorted(data, key=lambda h: h['games'], reverse=True)[:5]: | |
hero_id = hero['hero_id'] | |
if hero['games']: | |
win_rate = hero['win'] * 100 / hero['games'] | |
# this can happen if the player has never played this hero before | |
else: | |
win_rate = 0 | |
hero_data.append({'name': heroes[int(hero_id)], 'games': hero['games'], | |
'win_rate': '{:.1f}%'.format(win_rate)}) | |
return hero_data | |
def get_recent_heroes(self, heroes, number_of_matches=40, months_of_matches=3): | |
data = opendota_api_call('players', self.account_id, 'matches') | |
today = datetime.utcnow().replace(minute=0, second=0, microsecond=0) | |
months_ago = today.replace(month=((today.month + 12 - months_of_matches) % 12)) | |
epoch = datetime.utcfromtimestamp(0) | |
epoch_months_ago = (months_ago - epoch).total_seconds() | |
hero_data_number = defaultdict(lambda: defaultdict(int)) | |
hero_data_months = defaultdict(lambda: defaultdict(int)) | |
for index, match in enumerate(data): | |
if match['start_time'] < epoch_months_ago and index >= number_of_matches: | |
break | |
if match['start_time'] >= epoch_months_ago: | |
hero_id = match['hero_id'] | |
name = heroes[hero_id] | |
hero_data_months[name]['count'] += 1 | |
if match['player_slot'] > 100 and not match['radiant_win']: | |
hero_data_months[name]['wins'] += 1 | |
if match['player_slot'] < 100 and match['radiant_win']: | |
hero_data_months[name]['wins'] += 1 | |
if index <= number_of_matches: | |
hero_id = match['hero_id'] | |
name = heroes[hero_id] | |
hero_data_number[name]['count'] += 1 | |
if match['player_slot'] > 100 and not match['radiant_win']: | |
hero_data_number[name]['wins'] += 1 | |
if match['player_slot'] < 100 and match['radiant_win']: | |
hero_data_number[name]['wins'] += 1 | |
return hero_data_number, hero_data_months | |
def get_player_lane_statistics(self, teams): | |
lanes = defaultdict(int) | |
total = 0 | |
for team in teams: | |
for lane, count in team.player_lanes[int(self.account_id)].items(): | |
lanes[lane] += count | |
total += count | |
percentages = [] | |
for lane, count in sorted(lanes.items(), key=lambda lane: lane[1], reverse=True): | |
percentages.append('{}: {:.0f}%'.format(lane, count * 100.0/total)) | |
return ', '.join(percentages) | |
class Team(object): | |
def __init__(self, team_id, player_names, heroes, league_id, extra_match_ids=[], skip_match_ids=[]): | |
self.team_id = team_id | |
self.player_names = player_names | |
self.heroes = heroes | |
self.parsed_matches = [] | |
self.unparsed_matches = [] | |
self.pick_count = defaultdict(lambda: defaultdict(int)) | |
self.ban_count = defaultdict(int) | |
self.enemy_bans_count = defaultdict(int) | |
self.players = [] | |
self.get_team_data() | |
matches = self.get_team_matches(league_id=league_id) | |
matches = matches.union(extra_match_ids) | |
matches = [match for match in matches if match not in skip_match_ids] | |
self.parse_matches(matches) | |
self.get_player_lane_statistics() | |
def get_team_data(self): | |
data = dota_api_call('GetTeamInfoByTeamID', start_at_team_id=self.team_id, teams_requested=1) | |
if not data['teams']: | |
raise Exception('Team with ID {} not found.'.format(self.team_id)) | |
self.name = data['teams'][0]['name'] | |
for key, value in data['teams'][0].items(): | |
if not key.startswith('player') or not key.endswith('account_id'): | |
continue | |
value = str(value) | |
self.players.append(value) | |
sleep(.5) | |
def get_team_matches(self, league_id): | |
matches = set() | |
for league in league_id: | |
while True: | |
if matches: | |
matches_min = min(matches) - 1 | |
else: | |
matches_min = None | |
new_matches = self.get_matches(copy(matches), league_id=league, start_at_match_id=matches_min) | |
if len(matches) == len(new_matches): | |
break | |
matches = new_matches | |
return matches | |
def get_matches(self, matches, league_id, start_at_match_id=None): | |
params = {'matches_requested': 1000, 'league_id': league_id} | |
if start_at_match_id: | |
params['start_at_match_id'] = start_at_match_id | |
data = dota_api_call('GetMatchHistory', **params) | |
for match in data['matches']: | |
if int(self.team_id) in [match['dire_team_id'], match['radiant_team_id']]: | |
matches.add(match['match_id']) | |
return matches | |
def parse_matches(self, matches): | |
for match in sorted(set(matches), reverse=True): | |
try: | |
data = opendota_api_call('matches', str(match)) | |
except: | |
print('match {} could not be found using dotabuff api'.format(match)) | |
continue | |
if data.get('picks_bans'): | |
match_details = ParsedMatch(data, heroes=self.heroes, team_id=self.team_id, players=self.player_names) | |
for hero in match_details.picks.values(): | |
self.pick_count[hero['name']]['count'] += 1 | |
if match_details.win: | |
self.pick_count[hero['name']]['wins'] += 1 | |
for hero in match_details.enemy_bans.values(): | |
self.enemy_bans_count[hero['name']] += 1 | |
for hero in match_details.bans.values(): | |
self.ban_count[hero['name']] += 1 | |
self.parsed_matches.append(match_details) | |
else: | |
match_details = UnparsedMatch(data, heroes=self.heroes, team_id=self.team_id, players=self.player_names, account_ids=self.players) | |
self.unparsed_matches.append(match_details) | |
for hero in match_details.picks.values(): | |
self.pick_count[hero['name']]['count'] += 1 | |
if match_details.win: | |
self.pick_count[hero['name']]['wins'] += 1 | |
def get_player_lane_statistics(self): | |
self.player_lanes = defaultdict(lambda: defaultdict(int)) | |
for match in self.parsed_matches: | |
for account_id, lane in match.player_lanes.items(): | |
self.player_lanes[account_id][lane] += 1 | |
for match in self.unparsed_matches: | |
for account_id, lane in match.player_lanes.items(): | |
self.player_lanes[account_id][lane] += 1 | |
class XlsxWriter(object): | |
def __init__(self, file): | |
self.workbook = xlsxwriter.Workbook(file) | |
self.worksheet = self.workbook.add_worksheet() | |
self.colors = self.create_colors() | |
self.used_colors = set() | |
self.row = 0 | |
def close(self): | |
self.workbook.close() | |
def create_colors(self): | |
colors = {} | |
for num, color in enumerate(COLORS, start=2): | |
colors[num] = self.workbook.add_format({'bg_color': color, 'border': 0}) | |
return colors | |
def write_team_matches(self, team): | |
if not team.parsed_matches and not team.unparsed_matches: | |
return | |
if team.parsed_matches: | |
dotabuff_url = 'https://www.dotabuff.com/esports/teams/{}'.format(team.team_id) | |
self.worksheet.write_url(self.row, 0, dotabuff_url, None, team.name) | |
self.row += 1 | |
self.write_parsed_matches_header() | |
self.write_parsed_matches(team.parsed_matches, team=team) | |
if team.unparsed_matches: | |
self.write_unparsed_matches_header() | |
self.write_unparsed_matches(team.unparsed_matches, team=team) | |
def write_matches(self, parsed_matches, unparsed_matches): | |
if not parsed_matches and not unparsed_matches: | |
return | |
if parsed_matches: | |
self.write_parsed_matches_header() | |
self.write_parsed_matches(parsed_matches) | |
if unparsed_matches: | |
self.write_unparsed_matches_header() | |
self.write_unparsed_matches(unparsed_matches) | |
def write_parsed_matches_header(self): | |
self.worksheet.write(self.row, 0, 'CAPTAIN') | |
self.worksheet.write(self.row, 1, 'PICKS') | |
self.worksheet.write(self.row, 6, 'PICK') | |
self.worksheet.write(self.row, 7, 'RESULT') | |
self.worksheet.write(self.row, 8, 'SIDE') | |
self.worksheet.write(self.row, 9, 'ENEMY CAPTAIN') | |
self.worksheet.write(self.row, 10, 'ENEMY BANS') | |
self.worksheet.write(self.row, 18, 'BANS') | |
self.row += 1 | |
def write_parsed_matches(self, matches, team=None): | |
for match in matches: | |
self.write_parsed_match(match, team) | |
self.row += 1 | |
self.row += 1 | |
def write_parsed_match(self, match, team): | |
self.worksheet.write(self.row, 0, match.captain) | |
for column, pick in enumerate(sorted(match.picks.values(), key=lambda p: p['order']), start=1): | |
count = team.pick_count[pick['name']]['count'] if team else 1 | |
color = self.colors.get(count) | |
self.used_colors.add(count) | |
self.write_hero(column, color=color, **pick) | |
self.worksheet.write(self.row, 6, 'FP' if match.first_pick else 'SP') | |
dotabuff_url = 'http://www.opendota.com/matches/{}'.format(match.match_id) | |
self.worksheet.write_url(self.row, 7, dotabuff_url, None, 'W' if match.win else 'L') | |
self.worksheet.write(self.row, 8, match.side) | |
self.worksheet.write(self.row, 9, match.enemy_captain) | |
for column, ban in enumerate(sorted(match.enemy_bans.values(), key=lambda p: p['order']), start=10): | |
count = team.enemy_bans_count[ban['name']] if team else 1 | |
color = self.colors.get(count) | |
self.used_colors.add(count) | |
self.write_hero(column, color=color, **ban) | |
for column, ban in enumerate(sorted(match.bans.values(), key=lambda p: p['order']), start=18): | |
count = team.ban_count[ban['name']] if team else 1 | |
color = self.colors.get(count) | |
self.used_colors.add(count) | |
self.write_hero(column, color=color, **ban) | |
def write_unparsed_matches_header(self): | |
self.worksheet.write(self.row, 0, 'PICKS') | |
self.worksheet.write(self.row, 5, 'RESULT') | |
self.worksheet.write(self.row, 6, 'SIDE') | |
self.row += 1 | |
def write_unparsed_matches(self, matches, team=None): | |
for match in matches: | |
self.write_unparsed_match(match, team) | |
self.row += 1 | |
self.row += 1 | |
def write_unparsed_match(self, match, team): | |
for column, pick in enumerate(sorted(match.picks.values(), key=lambda p: p['name'])): | |
count = team.pick_count[pick['name']]['count'] if team else 1 | |
color = self.colors.get(count) | |
self.used_colors.add(count) | |
self.write_hero(column, color=color, **pick) | |
dotabuff_url = 'http://www.opendota.com/matches/{}'.format(match.match_id) | |
self.worksheet.write_url(self.row, 5, dotabuff_url, None, 'W' if match.win else 'L') | |
self.worksheet.write(self.row, 6, match.side) | |
def write_summary(self, team): | |
self.worksheet.write(self.row, 0, 'SUMMARY') | |
self.row += 1 | |
self.worksheet.write(self.row, 0, 'PICK') | |
self.worksheet.write(self.row, 1, 'COUNT') | |
self.worksheet.write(self.row, 2, 'WINS') | |
self.worksheet.write(self.row, 3, 'WIN RATE') | |
self.worksheet.write(self.row, 5, 'ENEMY BANS') | |
self.worksheet.write(self.row, 6, 'COUNT') | |
self.worksheet.write(self.row, 8, 'BAN') | |
self.worksheet.write(self.row, 9, 'COUNT') | |
self.row += 1 | |
pick_row, enemy_bans_row, ban_row = self.row, self.row, self.row | |
pick_count = sorted(team.pick_count.items(), key=lambda p: (-p[1]['count'], p[0])) | |
enemy_bans_count = sorted(team.enemy_bans_count.items(), key=lambda b: b[1], reverse=True) | |
ban_count = sorted(team.ban_count.items(), key=lambda b: b[1], reverse=True) | |
for pick, enemy_bans, ban in zip_longest(pick_count, enemy_bans_count, ban_count): | |
if pick: | |
win_rate = '{:.1f}%'.format(pick[1]['wins'] * 100 / pick[1]['count']) | |
if pick[1]['wins'] == pick[1]['count']: | |
self.worksheet.write(pick_row, 0, pick[0], self.colors[2]) | |
self.worksheet.write(pick_row, 1, pick[1]['count'], self.colors[2]) | |
self.worksheet.write(pick_row, 2, pick[1]['wins'], self.colors[2]) | |
self.worksheet.write(pick_row, 3, win_rate, self.colors[2]) | |
else: | |
self.worksheet.write(pick_row, 0, pick[0]) | |
self.worksheet.write(pick_row, 1, pick[1]['count']) | |
self.worksheet.write(pick_row, 2, pick[1]['wins']) | |
self.worksheet.write(pick_row, 3, win_rate) | |
pick_row += 1 | |
if enemy_bans: | |
self.worksheet.write(enemy_bans_row, 5, enemy_bans[0]) | |
self.worksheet.write(enemy_bans_row, 6, enemy_bans[1]) | |
enemy_bans_row += 1 | |
if ban: | |
self.worksheet.write(ban_row, 8, ban[0]) | |
self.worksheet.write(ban_row, 9, ban[1]) | |
ban_row += 1 | |
self.row = max(pick_row, enemy_bans_row, ban_row) | |
self.row += 1 | |
def write_hero(self, column, color=None, name=None, role=None, roaming=False, **kwargs): | |
data = name | |
if role: | |
data += ' ' + role | |
if roaming: | |
data += ' (R)' | |
if color: | |
self.worksheet.write(self.row, column, data, color) | |
else: | |
self.worksheet.write(self.row, column, data) | |
def write_legend(self): | |
if not self.used_colors: | |
return | |
self.worksheet.write(self.row, 0, 'LEGEND') | |
self.row += 1 | |
for column, count in enumerate(sorted(self.used_colors)): | |
color = self.colors.get(count) | |
if count == len(COLORS): | |
count = '{}+'.format(count) | |
if color: | |
self.worksheet.write(self.row, column, count, color) | |
else: | |
self.worksheet.write(self.row, column, count) | |
self.row += 2 | |
def write_players(self, players, highlight_heroes, player_order=[], input_players=[]): | |
if not players: | |
return | |
self.worksheet.write(self.row, 0, 'PLAYERS') | |
self.row += 1 | |
bold = self.workbook.add_format({'bold': True}) | |
starting_row = self.row | |
column = 0 | |
sorted_players = [] | |
# first order by given order | |
for account_id in player_order: | |
for player in players: | |
if player.account_id == account_id: | |
sorted_players.append(player) | |
break | |
# then order by alphabetical names | |
for player in sorted(players, key=lambda p: p.name.lower()): | |
if player.account_id not in player_order: | |
sorted_players.append(player) | |
for player in sorted_players: | |
# this will filter out players who have been on this team before but never | |
# played in one of the matches on the sheet except for players who were | |
# specified in the input args | |
if not player.statistics and player.account_id not in input_players: | |
continue | |
dotabuff_url = 'https://www.opendota.com/players/{}'.format(player.account_id) | |
self.worksheet.write_url(self.row, column, dotabuff_url, None, player.name) | |
self.worksheet.write(self.row, column+1, player.rank, bold) | |
self.worksheet.write(self.row, column+2, player.statistics) | |
self.row += 1 | |
self.worksheet.write(self.row, column, 'HERO') | |
self.worksheet.write(self.row, column+1, 'GAMES') | |
self.worksheet.write(self.row, column+2, 'WIN RATE') | |
self.row += 1 | |
for hero in player.heroes: | |
if hero['name'].lower() in highlight_heroes: | |
self.worksheet.write(self.row, column, hero['name'], self.colors[2]) | |
self.worksheet.write(self.row, column+1, hero['games'], self.colors[2]) | |
self.worksheet.write(self.row, column+2, hero['win_rate'], self.colors[2]) | |
else: | |
self.worksheet.write(self.row, column, hero['name']) | |
self.worksheet.write(self.row, column+1, hero['games']) | |
self.worksheet.write(self.row, column+2, hero['win_rate']) | |
self.row += 1 | |
self.row += 1 | |
self.worksheet.write(self.row, column, 'HERO') | |
self.worksheet.write(self.row, column+1, 'RECENT GAMES') | |
self.worksheet.write(self.row, column+2, 'RECENT WIN RATE') | |
self.row += 1 | |
for hero, data in sorted(player.recent_heroes_count.items(), key=lambda h: h[1]['count'], reverse=True): | |
win_rate = '{:.1f}%'.format(data['wins'] * 100 / data['count']) | |
if hero.lower() in highlight_heroes: | |
self.worksheet.write(self.row, column, hero, self.colors[2]) | |
self.worksheet.write(self.row, column+1, data['count'], self.colors[2]) | |
self.worksheet.write(self.row, column+2, win_rate, self.colors[2]) | |
else: | |
self.worksheet.write(self.row, column, hero) | |
self.worksheet.write(self.row, column+1, data['count']) | |
self.worksheet.write(self.row, column+2, win_rate) | |
self.row += 1 | |
self.row +=1 | |
# self.worksheet.write(self.row, column, 'HERO') | |
# self.worksheet.write(self.row, column+1, 'RECENT GAMES') | |
# self.worksheet.write(self.row, column+2, 'RECENT WIN RATE') | |
# self.row += 1 | |
# for hero, data in sorted(player.recent_heroes_time.items(), key=lambda h: h[1]['count'], reverse=True): | |
# win_rate = '{:.1f}%'.format(data['wins'] * 100 / data['count']) | |
# if hero.lower() in highlight_heroes: | |
# self.worksheet.write(self.row, column, hero, self.colors[2]) | |
# self.worksheet.write(self.row, column+1, data['count'], self.colors[2]) | |
# self.worksheet.write(self.row, column+2, win_rate, self.colors[2]) | |
# else: | |
# self.worksheet.write(self.row, column, hero) | |
# self.worksheet.write(self.row, column+1, data['count']) | |
# self.worksheet.write(self.row, column+2, win_rate) | |
# self.row += 1 | |
self.row = starting_row | |
column += 4 | |
self.row += 1 | |
def get_args(): | |
parser = ArgumentParser() | |
parser.add_argument('api_key', help='steam api key -- you can get a key from https://steamcommunity.com/dev/apikey') | |
parser.add_argument('-l', '--league-id', action='append', default=[], help='(required for team scouting only) the league to use for teams -- you can get this from dotabuff') | |
parser.add_argument('-t', '--team-id', action='append', default=[], help='(optional -- supports multiples) teams to scout -- you can get this from dotabuff') | |
parser.add_argument('-p', '--player', action='append', default=[], help='(optional -- supports multiples) players to scout -- you can get this from dotabuff') | |
parser.add_argument('-m', '--match', action='append', default=[], type=int, help='(optional -- supports multiples) extra matches to parse -- you can get this from dotabuff') | |
parser.add_argument('-s', '--skip-match', action='append', default=[], type=int, help='(optional -- supports multiples) matches to skip -- you can get this from dotabuff') | |
parser.add_argument('-c', '--counterpick-heroes', help='(optional) file of heroes to highlight if found in recent matches; file should be comma separated') | |
parser.add_argument('-o', '--ordered-players', action='store_true', help='print player information in the order players were given') | |
parser.add_argument('-f', '--file', help='the file to save results to') | |
return parser.parse_args() | |
def process(api_key, league_id=[], team_id=[], player=[], match=[], skip_match=[], counterpick_heroes=None, ordered_players=False, file=None): | |
global DOTA_BASE_URL | |
DOTA_BASE_URL = DOTA_BASE_URL.format(api_key=api_key) | |
current_time = datetime.utcnow().strftime('%Y%m%d%H%M%S') | |
file = file if file else '{}.xlsx'.format(current_time) | |
player_names = {} | |
players = {} | |
player_ids = set(player) | |
heroes = get_heroes() | |
highlight_heroes = [] | |
if counterpick_heroes: | |
with open(counterpick_heroes) as f: | |
lowered_hero_names = [h.lower() for h in heroes.values()] | |
for hero in f.read().split(','): | |
filtered_hero = hero.strip().lower() | |
if filtered_hero not in lowered_hero_names: | |
print('Warning: {} not a valid hero name and will be ignored'.format(hero)) | |
continue | |
highlight_heroes.append(filtered_hero) | |
try: | |
writer = XlsxWriter(file) | |
teams = [] | |
if team_id: | |
for tid in team_id: | |
team = Team(tid, player_names, heroes, league_id=league_id, extra_match_ids=match, skip_match_ids=skip_match) | |
teams.append(team) | |
player_ids.update(team.players) | |
writer.write_team_matches(team) | |
writer.write_legend() | |
writer.write_summary(team) | |
elif match: | |
parsed_matches = [] | |
unparsed_matches = [] | |
for m in sorted(match): | |
if m in skip_match: | |
continue | |
match_data = parse_match(m, heroes) | |
if isinstance(match_data, ParsedMatch): | |
parsed_matches.append(match_data) | |
else: | |
unparsed_matches.append(match_data) | |
writer.write_matches(parsed_matches, unparsed_matches) | |
for player_id in player_ids: | |
try: | |
players[player_id] = Player(player_id, player_names, heroes, teams) | |
except Exception as e: | |
print('Skipping player with ID {}. Player profile may be private. Error: {}'.format(player_id, e)) | |
writer.write_players(players.values(), highlight_heroes, player_order=player if ordered_players else [], input_players=player) | |
writer.close() | |
except Exception as e: | |
import traceback | |
traceback.print_exc() | |
if __name__ == '__main__': | |
process(**vars(get_args())) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment