Skip to content

Instantly share code, notes, and snippets.

@blaxter
Created April 21, 2016 16:57
Show Gist options
  • Save blaxter/58ec728eab328bc52b60482b3e0e9443 to your computer and use it in GitHub Desktop.
Save blaxter/58ec728eab328bc52b60482b3e0e9443 to your computer and use it in GitHub Desktop.
Warcraftlogs tool
#!/usr/bin/python
import requests
import lxml.html
import ConfigParser
import os
import sys
from collections import defaultdict
from itertools import groupby
class WarcraftLogs(object):
BASE_URL = 'https://www.warcraftlogs.com:443/v1/'
ENCOUNTERS_PER_PAGE = 5000
def __init__(self, api_key):
self.api_key = api_key
def boss_id_by_name(self, boss_name):
for zone in self._get('zones'):
for encounter in zone['encounters']:
if encounter['name'] == boss_name:
return encounter['id']
raise Exception("Not found %s" % boss_name)
def get_zones_and_bosses(self):
zones = self._get('zones')
ret = {}
for zone in zones:
ret[zone['name']] = [e['name'] for e in zone['encounters']]
return ret
def all_encounters_by_boss_and_difficulty(self, boss, difficulty, search=""):
boss_id = self.boss_id_by_name(boss)
total_pages, current_page = 1, 1
ret, seen = list(), set()
while current_page <= total_pages:
res = self._get('rankings/encounter/%d' % boss_id,
difficulty=difficulty, page=current_page, filter=search,
limit=self.ENCOUNTERS_PER_PAGE)
if current_page == 1:
total_pages = 1 + res['total'] / self.ENCOUNTERS_PER_PAGE
for r in res['rankings']:
if r['reportID'] in seen:
continue
seen.add(r['reportID'])
ret.append({'size': r.get('size', 20),
'id': r['reportID'],
'fight': r['fightID']})
current_page += 1
return ret
def healers_in(self, encounter):
return self._composition_parse_count(encounter, "healer")
def tanks_in(self, encounter):
return self._composition_parse_count(encounter, "tank")
def _composition_parse_count(self, encounter, role):
start_time, end_time = 0, 0
try:
res = self._get('report/fights/%s' % encounter['id'])
except:
print("Error on %r" % encounter)
return 0
if 'fights' not in res:
print("Error on %r (%r)" % (encounter, res))
return 0
for fight in res['fights']:
if fight['id'] == encounter['fight']:
start_time = fight['start_time']
end_time = fight['end_time']
break
if start_time == 0:
raise Exception("Not found in report/fights")
url = ('https://www.warcraftlogs.com/reports/summary/%s/%d/%d/%d/0/0/Any/0/-1/0/0' %
(encounter['id'], encounter['fight'], start_time, end_time))
body = requests.get(url)
dom = lxml.html.fromstring(body.text)
roles = ['tank', 'dps', 'healer']
if role not in roles:
raise Exception("Unknown role %r" % role)
ret = len(dom.xpath('//tr[@class="composition-row"]')[roles.index(role)].getchildren()[1].getchildren())
if ret == 0:
raise Exception(url)
return ret
def _get(self, resource, **kwargs):
kwargs.update({'api_key': self.api_key})
return requests.get(self.BASE_URL + resource, params=kwargs).json()
if __name__ == "__main__":
import argparse
class ModeAction(argparse.Action):
NORMAL = 3
HEROIC = 4
MYTHIC = 5
def __call__(self, parser, namespace, value, option_string=None):
if value == 'normal':
mode = self.NORMAL
elif value == "heroic":
mode = self.HEROIC
else:
mode = self.MYTHIC
setattr(namespace, self.dest, mode)
parser = argparse.ArgumentParser(description='Warcraft logs tool')
parser.add_argument('--config', default='wcl.config', nargs='?',
help='Config file with warcraftlogs\'s api key')
parser.add_argument('--list', action='store_true',
help='List zones and bosses availables')
parser.add_argument('--count', action='store_true',
help='Count number of logs per boss')
parser.add_argument('boss', nargs='?', help='Boss name')
parser.add_argument('--mode', choices=['normal', 'heroic', 'mythic'],
action=ModeAction, default=ModeAction.MYTHIC,
help='Either normal, heroic or mythic')
parser.add_argument('--tanks', action='store_true',
help='Count number of tanks')
parser.add_argument('--healers', action='store_true',
help='Count number of healers')
args = parser.parse_args()
config = ConfigParser.ConfigParser()
config.readfp(open(args.config))
api_key = config.get('wcl', 'api_key')
if not api_key:
raise Exception('Api key not found under wcl section')
sys.exit(1)
def parse_all_encounters(args, target):
"""
Count encounters getting each one individually
"""
encounters = w.all_encounters_by_boss_and_difficulty(args.boss, args.mode)
data = defaultdict(list)
print "Total encounters: %d" % len(encounters)
current = 1
for encounter in encounters:
try:
print "Fetching info %d" % current
if target == 'healers':
n_targets = w.healers_in(encounter)
else:
n_targets = w.tanks_in(encounter)
print(n_targets)
if n_targets > 0:
data[str(encounter['size'])].append(n_targets)
current += 1
except Exception as e:
print(repr(e))
for size in sorted(data.keys()):
total = len(data[size])
print "Hay %d combates con %s jugadores" % (total, size)
grouped = [[i[0], len(list(i[1]))] for i in groupby(sorted(data[size]))]
grouped = sorted(grouped, key=lambda e: e[0])
for el in grouped:
print("\tCon %d %s son %.2f%%" %
(el[0], target, 100.0 * float(el[1]) / float(total)))
def smart_count(args, target):
"""
Count encounters using the 'filter' parameter and then counting
the reports page by page discarting same encounters
"""
total = len(w.all_encounters_by_boss_and_difficulty(args.boss, args.mode))
print "Total encounters: %d\n" % total
data = {}
still_data = True
import Queue, threading
q = Queue.Queue()
def count_logs():
while still_data:
try:
n = q.get(False, 1)
if target == 'healers':
search = 'heal.'
else:
search = 'tank.'
search += str(n)
count = len(w.all_encounters_by_boss_and_difficulty(args.boss, args.mode, search))
data[n] = count
q.task_done()
except Queue.Empty:
pass
n_workers = 10
[threading.Thread(target=count_logs).start() for n in xrange(0, n_workers)]
max_n = 7 if target == "healers" else 4
start = 0
while still_data:
for n in xrange(start, start + max_n + 1):
q.put(n)
q.join()
start += max_n + 1
still_data = data[sorted(data.keys())[-1]] != 0
for n in sorted(data.keys()):
count = data[n]
if count == 0:
continue
print("Logs with %d %s: %d (%.2f%%)" %
(n, target, count, 100.0 * float(count) / float(total)))
w = WarcraftLogs(api_key)
if args.list:
raids = w.get_zones_and_bosses()
for raid, bosses in raids.iteritems():
print(raid)
for boss in bosses:
print "\t%s" % boss
sys.exit(0)
elif args.count:
print len(w.all_encounters_by_boss_and_difficulty(args.boss, args.mode))
sys.exit(0)
if args.tanks:
target = 'tanks'
elif args.healers:
target = 'healers'
else:
parser.print_help()
sys.exit(1)
if args.mode != ModeAction.MYTHIC:
parse_all_encounters(args, target)
else:
smart_count(args, target)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment