#!/usr/bin/env python3 import requests import sys from argparse import ArgumentParser from datetime import datetime, timedelta import urllib.parse PSTAT = dict() def get_player_info(name): url = f'https://ddnet.org/players/?json2={urllib.parse.quote(name)}' r = requests.get(url) if r.status_code != 200: sys.exit(f'Error fetching json for {name}') j = r.json() if len(j) == 0: sys.exit(f'Could not find player {name}') return j def get_map_json(name): url = f'https://ddnet.org/maps/?json={urllib.parse.quote(name)}' r = requests.get(url) if r.status_code != 200: sys.exit(f'Error fetching json for map {name}') j = r.json() if len(j) == 0: sys.exit(f'Could not find map {name}') return j def get_map_stat(name): j = get_map_json(name) finishers = j['finishers'] try: release = datetime.fromtimestamp(j['release']) except KeyError: release = datetime.fromtimestamp(j['first_finish']) now = datetime.now() elapsed = now - release avrg = finishers / elapsed.days return f'{finishers} finishers, avrg: {avrg:.2f}/days', finishers, avrg, release, j def has_player_finished_map(mname, pname): global PSTAT if PSTAT.get(pname, None) is None: PSTAT[pname] = get_player_info(pname) pstat = PSTAT[pname] for t in pstat['types']: for map in pstat['types'][t]['maps']: if map != mname: continue entry = pstat['types'][t]['maps'][map] finishes = entry['finishes'] return datetime.fromtimestamp(entry['first_finish']) if finishes > 0 else False def print_map_stat(name, pname): stat, finishers, avrg, release_date, j = get_map_stat(name) finished = has_player_finished_map(name, pname) date_formated = f'{release_date.year}-{release_date.month:02}-{release_date.day:02}' msg = f'[{j["type"]}/{name}] - {j["finishes"]}/{j["points"]}' \ f' - {finishers} finishers, avrg: {avrg:.2f}/days - {date_formated} / finished: {finished}' print(msg) def get_next_maps(opt): global PSTAT name = opt.name j = get_player_info(name) for player in opt.player: PSTAT[player] = get_player_info(player) unfinished = {} for t in j['types']: for map in j['types'][t]['maps']: entry = j['types'][t]['maps'][map] if entry['finishes'] > 0: continue cont = False for player in opt.player: if has_player_finished_map(map, player): cont = True break if cont: continue unfinished[map] = entry unfinished[map]['type'] = t filtered_unfinished = dict(sorted(unfinished.items(), key=lambda x: x[1]['total_finishes'], reverse=True)) i = 0 for entry in filtered_unfinished: if opt.type is not None and filtered_unfinished[entry]["type"] != opt.type: continue stat, finishers, avrg, release_date, j = get_map_stat(entry) date_formated = f'{release_date.year}-{release_date.month:02}-{release_date.day:02}' msg = f'[{filtered_unfinished[entry]["type"]}/{entry}] - {filtered_unfinished[entry]["total_finishes"]}/{filtered_unfinished[entry]["points"]}' \ f' - {finishers} finishers, avrg: {avrg:.2f}/days - {date_formated}' print(msg) i += 1 if i == opt.limit: break if not opt.all: return print('-' * 64) unsorted = {} for entry in filtered_unfinished: if opt.type is not None and filtered_unfinished[entry]["type"] != opt.type: continue stat, finishers, avrg, release_date, j = get_map_stat(entry) unsorted[entry] = {'finishers':finishers, 'avrg':avrg, 'release_date':release_date, 'type':filtered_unfinished[entry]['type'], 'total_finishes':filtered_unfinished[entry]['total_finishes'], 'points':filtered_unfinished[entry]['points']} sorted_maps = dict(sorted(unsorted.items(), key=lambda x: x[1]['avrg'], reverse=True)) i = 0 for entry in sorted_maps: cont = False for player in opt.player: if has_player_finished_map(entry, player): cont = True break if cont: continue release = sorted_maps[entry]['release_date'] date_formated = f'{release.year}-{release.month:02}-{release.day:02}' msg = f'[{sorted_maps[entry]["type"]}/{entry}] - {sorted_maps[entry]["total_finishes"]}/{sorted_maps[entry]["points"]}' \ f' - {sorted_maps[entry]["finishers"]} finishers, avrg: {sorted_maps[entry]["avrg"]:.2f}/days - {date_formated}' print(msg) i += 1 if i == opt.limit: break def get_points(opt): name = opt.name timespan = opt.timespan j = get_player_info(name) now = datetime.now() delta = now - timedelta(days=timespan) finishes = {} points = 0 # parse for t in j['types']: for map in j['types'][t]['maps']: entry = j['types'][t]['maps'][map] finished = entry.get('first_finish', None) if finished: time = datetime.fromtimestamp(finished) if time > delta: points += int(entry["points"]) date_formated = f'{time.year}-{time.month:02}-{time.day:02} {time.hour: >2}:{time.minute:02}' finishes[map] = entry finishes[map]['type'] = t finishes[map]['date_formated'] = date_formated finishes[map]['timestamp'] = time # sort finishes = dict(sorted(finishes.items(), key=lambda x: x[1]['first_finish'])) days = {} # display for finish in finishes: entry = finishes[finish] date_formated = entry['date_formated'] timestamp = entry['timestamp'] type = entry['type'] map = finish print(f'[{date_formated}] {type}/{map} - {entry["points"]}') key = f'{timestamp.year}-{timestamp.month:02}-{timestamp.day:02}' if not days.get(key, None): days[key] = 0 days[key] += entry['points'] for day in days: print(f'[{day}] {days[day]}') print(f'Total points: {points}') def main(): arg = ArgumentParser() arg.add_argument('name', help='Player name', default='laxa', type=str) arg.add_argument('-t', '--timespan', type=int, help='Timespan to check for in days', default=7) arg.add_argument('-n', '--next', default=False, help='Display next maps', action='store_true') arg.add_argument('--type', type=str, help='Filter with these categories for next', default=None) arg.add_argument('-l', '--limit', type=int, default=10, help='Limit next maps number') arg.add_argument('-a', '--all', action='store_true', default=False, help='Get stats for all unfinished maps') arg.add_argument('-m', '--map', help='Get map stats', default=None, type=str) arg.add_argument('-p', '--player', help='Filter maps to play with these player(s)', type=str, default=[], nargs="*") opt = arg.parse_args() if opt.next: get_next_maps(opt) elif opt.map: print_map_stat(opt.map, opt.name) else: get_points(opt) if __name__ == '__main__': main()