import argparse import configparser import datetime import html import json import logging import os, os.path import sys import lokrez.export as export import lokrez.resources as resources import lokrez.smashgg as smashgg import lokrez.version as version # ============================================================================= __version__ = version.__version__ ROOTDIR = os.path.dirname(os.path.abspath(__file__)) # ============================================================================= def main(): # ------------------------------------------------------------------------- parser = argparse.ArgumentParser( formatter_class = argparse.ArgumentDefaultsHelpFormatter, ) subparsers = parser.add_subparsers( dest = "command", help = "commands", ) parser.add_argument( "--proxy", "-p", default = None, help = "the proxy to use", ) parser.add_argument( "--token", "-t", default = None, help = "the authentication token to use", ) # ------------------------------------------------------------------------- init_parser = subparsers.add_parser( "init", ) init_parser.add_argument( "game", default = "ssbu", help = "The game you want to initialize the resources for", ) init_parser.add_argument( "--imgdir", "-ID", default = os.path.join(ROOTDIR, "res"), help = "The directory we should download the resources to", ) # ------------------------------------------------------------------------- top8_parser = subparsers.add_parser( "top8", ) top8_parser.add_argument( "tournament", default = None, help = "The tournament slug or id", ) top8_parser.add_argument( "--imgdir", "-ID", default = os.path.join(ROOTDIR, "res"), help = "The directories containing images, be careful whether " \ "you specify an absolute path or a relative one.", ) top8_parser.add_argument( "--playerskinsdb", "-PD", default = os.path.join(ROOTDIR, "data", "playerskinsdb.json"), help = "A JSON file matching player tags, characters and " \ "preferred skins", ) top8_parser.add_argument( "--templatesdir", "-TD", default = os.path.join(ROOTDIR, "templates"), help = "The local result templates directory", ) top8_parser.add_argument( "--template", "-T", default = "rebootlyon2020", help = "The local result template to use", ) top8_parser.add_argument( "--lkrz-file", "-f", default = None, help = "The lkrz file in which the results are stored ; if it " \ "does not exist, one will be created from the smashgg data", ) top8_parser.add_argument( "--outfile", "-o", default = None, help = "The SVG local result file to output to ; defaults to " \ "./tournament-slug.svg", ) top8_parser.add_argument( "--name-seo-delimiter", default = None, help = "A character that will delimit in a tournament name what " \ "really is its name, and what's actually here for SEO " \ "purposes (example: in 'Cornismash #42 - Ultimate Weekly " \ "Lyon', only the 'Cornismash #42' is the tournament name, "\ "the rest is here to help find the tournament).", ) # ------------------------------------------------------------------------- parser.add_argument( "--verbose", "-v", default = 0, action = "count", help = "increase verbosity" ) parser.add_argument( "--version", "-V", default = False, action = "store_true", help = "show version number" ) # ------------------------------------------------------------------------- args = parser.parse_args() # Set log level # ------------------------------------------------------------------------- log = logging.getLogger(version.NAME) log.setLevel(logging.DEBUG) log_handler_console = logging.StreamHandler() log_handler_console.setLevel(logging.WARNING) if(args.verbose >= 2): log_handler_console.setLevel(logging.DEBUG) elif(args.verbose >=1): log_handler_console.setLevel(logging.INFO) else: log_handler_console.setLevel(logging.WARNING) log_formatter_console = logging.Formatter("%(name)s:%(levelname)s: %(message)s") log_handler_console.setFormatter(log_formatter_console) log.addHandler(log_handler_console) # Print version if required # ------------------------------------------------------------------------- if args.version: print(version.VERSION_NAME) return 0 # ------------------------------------------------------------------------- if args.command not in [ "init", "top8" ]: parser.print_help() return 1 # ------------------------------------------------------------------------- if args.command == "init": resources.download_res_ssbu( dstdir = args.imgdir, log = log, ) return 0 # ------------------------------------------------------------------------- if args.command == "top8": # Initialize PLAYERSKINS db log.debug("loading playerskins db from '{}'" \ .format(args.playerskinsdb)) with open(args.playerskinsdb, "r") as f: smashgg.PLAYERSKINS = json.load(f) # tournament = None top_players = {} try: lkrz = configparser.ConfigParser() lkrz.read(args.lkrz_file) log.info("Loading data from '{}'".format(args.lkrz_file)) for s in lkrz: section = lkrz[s] if s == "Tournament": tournament = smashgg.Tournament( id = 0, name = section["name"], slug = section["slug"], startAt = datetime.datetime.strptime( section["date"], "%Y-%m-%d %H:%M:%S", ), numEntrants = int(section["numEntrants"]), venueName = section["location"], ) elif s.startswith("player "): chars = {} for char in section["characters"].split(","): c = char.strip() charname = c.split("_")[0] charskin = c.split("_")[1].split(" ")[0] charscore = float(c.split("(")[1].split(")")[0]) chars[(charname,charskin)] = charscore player = smashgg.Player( id = 0, prefix = section["team"], gamerTag = section["tag"], placement = section["placement"], seeding = section["seeding"], chars = chars, ) top_players[player.gamerTag] = player except Exception as e: log.warning(e) # Get infos from smash.gg and write the config file tournament, top_players = getTournamentTop( id_or_slug = args.tournament, top = 8, token = args.token, proxy = args.proxy, log = log, ) if tournament is None or top_players is None: log.error("Could not load data from smash.gg") return 1 lkrz_data = "\n".join( [ tournament.conf() ] \ + list(map( lambda p:p.conf(), top_players.values(), )) ) if args.lkrz_file is None: args.lkrz_file = "{}.lkrz".format(tournament.slug) with open(args.lkrz_file, "w", encoding="utf8") as f: f.write(lkrz_data) # Default outfile is 'tournament-slug.svg' if args.outfile is None: args.outfile = "{}.svg".format(tournament.slug) # Build the context which will be passed to the template context = { "tournament": tournament.clean_name(args.name_seo_delimiter), "players" : sorted( top_players.values(), key = lambda p: p.placement, ), "dir_res_ssbu": args.imgdir, } rv = export.generate_outfile( args.templatesdir, args.template, context, args.outfile, log, ) if rv is None: return 1 log.info("Successfully saved outfile as '{}'".format(rv)) return 0 # ----------------------------------------------------------------------------- def getTournamentTop( id_or_slug, top=8, token = "", proxy = None, log=None): """Returns a tuple : the smashgg.Tournament object and a list of the top smashgg.Player in that tournament.""" # ------------------------------------------------------------------------- # Select the right event (the one with the most entrants or the most sets) def selectBiggestEvent(data, log=None): try: event = data["events"][0] except: log.error("No event found in data") log.debug(data) return None try: numEntrants = event["numEntrants"] except KeyError: numEntrants = event["standings"]["pageInfo"]["total"] for e in data["events"]: try: ne = e["numEntrants"] except KeyError: ne = e["standings"]["pageInfo"]["total"] if ne > numEntrants: event = e numEntrants = ne log.info("Selected Event '{}' with {} entrants" \ .format( event["name"], numEntrants, )) return event # ------------------------------------------------------------------------- data = None try: data = smashgg.run_query( query_name = "getTournamentTopById", variables = { "id" : int(id_or_slug), # If this fails, it's a slug "top": top, }, query_dir = os.path.join( ROOTDIR, "queries" ), token = token, proxy = proxy, log = log, ) except ValueError: data = smashgg.run_query( query_name = "getTournamentTopBySlug", variables = { "slug" : id_or_slug, "top": top, }, query_dir = os.path.join( ROOTDIR, "queries" ), token = token, proxy = proxy, log = log, ) try: tournament_data = data["tournament"] except: log.error("Failed to load Tournaments") return None,None if tournament_data is None: log.error("Failed to load Tournament") return None,None event = selectBiggestEvent(tournament_data, log) if event is None : return None,None # Get the tournament tournament = smashgg.Tournament( id = tournament_data["id"], slug = tournament_data["slug"], name = tournament_data["name"], startAt = \ datetime.datetime. \ fromtimestamp(tournament_data["startAt"]), numEntrants = event["standings"]["pageInfo"]["total"], venueAddress = tournament_data["venueAddress"], venueName = tournament_data["venueName"], city = tournament_data["city"], countryCode = tournament_data["countryCode"], hashtag = tournament_data["hashtag"], ) # Get the top players top_players = {} standings = event["standings"]["nodes"] for standing in standings : seeding = None for seed in standing["entrant"]["seeds"]: # Take the seeding from the phase with *all* Event entrants if seed["phase"]["numSeeds"] == tournament.numEntrants: seeding = seed["groupSeedNum"] participant_data = standing["entrant"]["participants"][0] try: twitterHandle = participant_data \ ["player"] \ ["user"] \ ["authorizations"] \ [0] \ ["externalUsername"] except TypeError: twitterHandle = None player = smashgg.Player( id = standing["entrant"]["id"], prefix = participant_data["prefix"], gamerTag = participant_data["gamerTag"], placement = standing["placement"], seeding = seeding, twitterHandle = twitterHandle, ) top_players[player.id] = player # ------------------------------------------------------------------------- # Now, we need to find which characters those top players chose data = None data = smashgg.run_query( query_name = "getCharsByTournamentIdAndEntrantIds", variables = { "tournamentId" : int(tournament.id), "entrantIds": [ id for id in top_players.keys() ], }, query_dir = os.path.join( ROOTDIR, "queries" ), token = token, proxy = proxy, log = log, ) try: tournament_data = data["tournament"] except: log.error("Failed to load Tournament") return None,None if tournament_data is None: log.error("Failed to load Tournament") return None,None event = selectBiggestEvent(tournament_data, log) if event is None : return None,None # TODO check that sets number is < to hardcoded 100 max value (cf query) sets = event["sets"]["nodes"] for s in sets: try: for g in s["games"]: winnerId = g["winnerId"] for slct in g["selections"]: if slct["selectionType"] == "CHARACTER": eid = slct["entrant"]["id"] try: top_players[eid].add_character_selection( character = slct["selectionValue"], win = (winnerId == eid), ) except KeyError: pass except TypeError: # If some games or selections are null, this can happen continue # Return the data return tournament, top_players # ============================================================================= if __name__ == '__main__': rv = main() sys.exit(rv)