Add the multiple games feature

master
Lertsenem 2021-07-03 20:23:48 +02:00
parent 7b9d3f13f4
commit 18a4b6674e
1 changed files with 352 additions and 147 deletions

View File

@ -1,10 +1,13 @@
import argparse import argparse
import configparser import configparser
import copy
import datetime import datetime
import html import html
import io
import json import json
import logging import logging
import pathlib import pathlib
import pprint
import requests import requests
import sys import sys
import urllib import urllib
@ -31,6 +34,62 @@ LOG_DUMMY.addHandler(logging.NullHandler())
DEFAULT_DIR_TEMPLATES = ROOTDIR / "templates" DEFAULT_DIR_TEMPLATES = ROOTDIR / "templates"
# =============================================================================
class StoreOptionKeyPair(argparse.Action):
def __init__(
self,
option_strings,
dest,
nargs=1,
const=None,
default=None,
type=None,
choices=None,
required=False,
help=None,
metavar="KEY=VALUE",
):
if nargs == 0:
raise ValueError(
'nargs for append actions must be > 0; if arg '
'strings are not supplying the value to append, '
'the append const action may be more appropriate'
)
if const is not None and nargs != '?':
raise ValueError('nargs must be %r to supply const' % '?')
super(StoreOptionKeyPair, self).__init__(
option_strings=option_strings,
dest=dest,
nargs=nargs,
const=const,
default=default,
type=type,
choices=choices,
required=required,
help=help,
metavar=metavar,
)
def __call__(self, parser, namespace, values, option_string=None):
try:
options = getattr(namespace, self.dest)
except AttributeError:
options = {}
for kv in values:
try:
k,v = kv.split("=")
except ValueError:
k = kv
v = True
options[k] = v
setattr(namespace, self.dest, options)
# ============================================================================= # =============================================================================
def get_templates_list( def get_templates_list(
dir_templates = DEFAULT_DIR_TEMPLATES, dir_templates = DEFAULT_DIR_TEMPLATES,
@ -47,6 +106,76 @@ def get_templates_list(
return templates_list return templates_list
# =============================================================================
def get_infos_from_file(
lkrz_file_path,
options = {},
outform = "dict",
log = LOG_DUMMY,
):
if not lkrz_file_path.exists():
raise IOError( "lkrz file '{}' does not exist" \
.format(str(lkrz_file_path)) )
lkrz = configparser.ConfigParser()
lkrz.read(str(lkrz_file_path))
log.info("Loading data from '{}'".format(str(lkrz_file_path)))
if s in lkrz:
section = lkrz[s]
if s == "Tournament":
tournament = smashgg.Tournament(
id = 0,
name = section["name"],
game = section["game"],
slug = section["slug"],
startAt = datetime.datetime.strptime(
section["date"],
"%Y-%m-%d %H:%M:%S",
),
numEntrants = int(section["numEntrants"]),
venueName = section["location"],
) \
.clean_name(
options.get("name_seo_delimiter", None)
)
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"],
twitterHandle = section["twitter"],
chars = chars,
)
top_players[player.gamerTag] = player
# Re-sort top players by their placement
top_players = sorted(
top_players.values(),
key = lambda p: p.placement,
)
return format_infos(
outform,
tournament,
top_players,
)
# ============================================================================= # =============================================================================
def get_infos_from_url( def get_infos_from_url(
url, url,
@ -65,25 +194,76 @@ def get_infos_from_url(
if outform not in [ "dict", "lkrz" ]: if outform not in [ "dict", "lkrz" ]:
raise ValueError("Unsupported outform") raise ValueError("Unsupported outform")
# -------------------------------------------------------------------------
tournament = None
event = None
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
if url_parsed.netloc == "smash.gg": if url_parsed.netloc == "smash.gg":
if (url_parsed.path.split("/")[1] != "tournament"): if (url_parsed.path.split("/")[1] != "tournament"):
raise Exception("No tournament found in url {}".format(url_parsed.path.split("/"))) log.error("Incomplete URL '{}'".format(url))
raise Exception("No tournament found in url {}".format(url))
# Get infos from smash.gg and write the config file try:
tournament, top_players = getTournamentTop( tournament = url_parsed.path.split("/")[2]
id_or_slug = url_parsed.path.split("/")[2], except:
import_options = options, log.error("Incomplete URL '{}'".format(url))
top = top, raise Exception("No tournament slug found in url {}".format(url))
token = token,
proxy = proxy,
log = log,
)
if tournament is None or top_players is None: try:
log.error("Could not load data from smash.gg") if (url_parsed.path.split("/")[3] == "event"):
raise Exception("Could not load data from smash.gg") event = url_parsed.path.split("/")[4]
except:
log.info("No event slug found in url")
# -------------------------------------------------------------------------
return get_infos_from_id_or_slug(
id_or_slug = tournament,
event_slug = event,
token = token,
options = options,
outform = outform,
top = top,
proxy = proxy,
log = log,
)
# =============================================================================
def get_infos_from_id_or_slug(
id_or_slug,
event_slug = None,
token = "",
options = [],
outform = "dict",
top = 8,
proxy = None,
log = LOG_DUMMY,
):
# Get infos from smash.gg and write the config file
tournament, top_players = getTournamentTop(
id_or_slug = id_or_slug,
event_slug = event_slug,
import_options = options,
top = top,
token = token,
proxy = proxy,
log = log,
)
if tournament is None or top_players is None:
log.error("Could not load data from smash.gg")
raise Exception("Could not load data from smash.gg")
return format_infos(outform, tournament, top_players)
# =============================================================================
def format_infos(
outform,
tournament,
top_players,
):
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
if outform == "dict": if outform == "dict":
@ -117,36 +297,24 @@ def init_resources(
# Start resources download according to game # Start resources download according to game
if game in ssbu.GAME.list_names(): if game in ssbu.GAME.list_names():
resources.download_res( game = ssbu
dstdir = imgdir,
game = ssbu,
source = source,
store_raw = store_raw,
proxy = proxy,
log = log,
)
elif game in melee.GAME.list_names(): elif game in melee.GAME.list_names():
resources.download_res_ssbm( game = melee
dstdir = imgdir,
game = melee,
source = source,
store_raw = store_raw,
proxy = proxy,
log = log,
)
elif game in pplus.GAME.list_names(): elif game in pplus.GAME.list_names():
resources.download_res( game = pplus
dstdir = imgdir,
game = pplus,
source = source,
store_raw = store_raw,
proxy = proxy,
log = log,
)
else: else:
log.error("Unknown game '{}'".format(game)) log.error("Unknown game '{}'".format(game))
return 1 return 1
resources.download_res(
dstdir = imgdir,
game = game,
source = source,
store_raw = store_raw,
proxy = proxy,
log = log,
)
return 0 return 0
# ============================================================================= # =============================================================================
@ -161,12 +329,20 @@ def generate_pic(
log = LOG_DUMMY, log = LOG_DUMMY,
): ):
if outform.startswith("."):
outform = outform[1:]
if outform not in ["svg", "png"]: if outform not in ["svg", "png"]:
raise Exception("Unsupported outform") raise Exception("Unsupported outform")
if type(infos_or_lkrzfile) == str: if type(infos_or_lkrzfile) == str:
# TODO : load lkrz as dict infos # load lkrz as dict infos
raise NotImplementedError() infos = get_infos_from_file(
lkrz_file_path = pathlib.Path(infos_or_lkrzfile),
options = options,
outform = "dict",
log = log,
)
else: else:
infos = infos_or_lkrzfile infos = infos_or_lkrzfile
@ -185,7 +361,7 @@ def generate_pic(
), ),
"dir_res_ssbu": dir_res, "dir_res_ssbu": dir_res,
"dir_template": str(dir_templates/template), "dir_template": str(dir_templates/template),
"options": options.get("template_options", []), "options": options,
} }
pic = export.generate_pic( pic = export.generate_pic(
@ -195,7 +371,7 @@ def generate_pic(
outform, outform,
log = log, log = log,
cachedir = dir_cache, cachedir = dir_cache,
options = { "svg_embed_png": options.get("svg_embed_png",False) }, options = options,
) )
if pic is None: if pic is None:
@ -308,21 +484,24 @@ def main():
) )
top8_parser.add_argument( top8_parser.add_argument(
"--template-options", "-TO", "--template-options", "-TO",
action = "append", nargs="+",
default = [], action = StoreOptionKeyPair,
default = {},
help = "Template-specific options (like 'covid' or 'animated')", help = "Template-specific options (like 'covid' or 'animated')",
) )
top8_parser.add_argument( top8_parser.add_argument(
"--export-options", "-EO", "--export-options", "-EO",
action = "append", nargs="+",
default = [], action = StoreOptionKeyPair,
default = {},
help = "Export options (like 'svg_embed_png')", help = "Export options (like 'svg_embed_png')",
) )
top8_parser.add_argument( top8_parser.add_argument(
"--import-options", "-IO", "--import-options", "-IO",
action = "append", nargs="+",
default = [], action = StoreOptionKeyPair,
default = {},
help = "Import options (like 'use_smashgg_prefixes')", help = "Import options (like 'use_smashgg_prefixes')",
) )
@ -378,6 +557,10 @@ def main():
log.addHandler(log_handler_console) log.addHandler(log_handler_console)
# Print all arguments in debug
# -------------------------------------------------------------------------
log.debug( "Command arguments:\n{}".format( pprint.pformat(vars(args))) )
# Print version if required # Print version if required
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
if args.version: if args.version:
@ -423,58 +606,21 @@ def main():
# #
tournament = None tournament = None
top_players = {} top_players = {}
lkrz_file = None
if args.lkrz_file is not None and args.lkrz_file.exists(): all_options = {
lkrz = configparser.ConfigParser() **args.import_options,
lkrz.read(str(args.lkrz_file)) **args.template_options,
**args.export_options,
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"],
twitterHandle = section["twitter"],
chars = chars,
)
top_players[player.gamerTag] = player
# Re-sort top players by their placement
top_players = sorted(
top_players.values(),
key = lambda p: p.placement,
)
else:
# Determine the nature of the 'tournament' argument :
# - url
# - id or slug
# - lkrz file
# url
if ( args.tournament.startswith("http://")
or args.tournament.startswith("https://") ):
infos = get_infos_from_url( infos = get_infos_from_url(
url = args.tournament, url = args.tournament,
token = args.token, token = args.token,
@ -485,29 +631,33 @@ def main():
log = log, log = log,
) )
tournament = infos["tournament"] # lkrz file
top_players = infos["players"] elif pathlib.Path(args.tournament).exists():
infos = get_infos_from_file(
if tournament is None or top_players is None: lkrz_file_path = pathlib.Path(args.tournament),
log.error("Could not load data from smash.gg") options = args.import_options,
return 1 outform = "dict",
log = log,
# Save a lkrz file
lkrz_data = "\n".join(
[ tournament.conf() ] \
+ list(map(
lambda p:p.conf(),
top_players,
))
) )
if args.lkrz_file is None: # id or slug
args.lkrz_file = pathlib.Path( else:
"{}.lkrz".format(tournament.slug) infos = get_infos_from_id_or_slug(
) id_or_slug = args.tournament,
token = args.token,
options = args.import_options,
outform = "dict",
top = 8,
proxy = args.proxy,
log = log,
)
with args.lkrz_file.open("w", encoding="utf8") as f: tournament = infos["tournament"]
f.write(lkrz_data) top_players = infos["players"]
if tournament is None or top_players is None:
log.error("Could not load data")
return 1
# Default outfile is 'tournament-slug.svg' # Default outfile is 'tournament-slug.svg'
if args.outfile is None: if args.outfile is None:
@ -515,48 +665,60 @@ def main():
"{}.svg".format(tournament.slug), "{}.svg".format(tournament.slug),
) )
# Build the context which will be passed to the template # Save a lkrz file
if not args.no_lkrz:
lkrz_data = format_infos("lkrz", tournament, top_players)
lkrz_file = args.outfile.with_suffix(".lkrz")
with lkrz_file.open("w", encoding="utf8") as f:
f.write(lkrz_data)
# If the outfile we were asked for was a .lkrz, we're done
if args.outfile.suffix == ".lkrz":
return 0
# Otherwise, let's generate the picture file
# First build the context which will be passed to the template
try: try:
dir_res_ssbu = args.imgdir.as_uri() # not absolute => error dir_res = (args.imgdir / tournament.game.name).as_uri() # not absolute => error
except ValueError: except ValueError:
dir_res_ssbu = args.imgdir.as_posix() dir_res = (args.imgdir / tournament.game.name).as_posix()
context = { pic = generate_pic(
"tournament": tournament.clean_name(args.name_seo_delimiter), infos_or_lkrzfile = infos,
"players" : sorted( template = args.template,
top_players, outform = args.outfile.suffix,
key = lambda p: p.placement, options = all_options,
), dir_templates = args.templatesdir,
"dir_res_ssbu": dir_res_ssbu, dir_res = dir_res,
"dir_template": str(args.templatesdir / args.template), dir_cache = args.cachedir,
"options": args.template_options,
}
rv = export.generate_outfile(
args.templatesdir,
args.template,
context,
args.outfile,
log = log, log = log,
cachedir = args.cachedir,
options={"svg_embed_png": "svg_embed_png" in args.export_options},
) )
if rv is None: if pic is None:
return 1 return 1
log.info("Successfully saved outfile as '{}'".format(rv)) log.info("Saving picture as '{}'".format(args.outfile))
if type(pic) == io.StringIO:
openmode = "w"
else:
openmode = "wb"
with args.outfile.open(openmode) as f:
f.write(pic.read())
return 0 return 0
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
def getTournamentTop( def getTournamentTop(
id_or_slug, id_or_slug,
import_options = {} event_slug = None,
import_options = [],
top = 8, top = 8,
token = "", token = "",
proxy = None, proxy = None,
log=LOG_DUMMY): log=LOG_DUMMY,
):
"""Returns a tuple : the smashgg.Tournament object and a list of the top """Returns a tuple : the smashgg.Tournament object and a list of the top
smashgg.Player in that tournament.""" smashgg.Player in that tournament."""
@ -605,6 +767,36 @@ def getTournamentTop(
return event return event
# -------------------------------------------------------------------------
# Select the specified event
def selectEventBySlug(data, slug, log=LOG_DUMMY):
try:
event = data["events"][0]
except:
log.error("No event found in data")
log.debug(data)
return None
for e in data["events"]:
try:
slug_full = e["slug"]
except KeyError:
continue
if ( slug == slug_full
or slug == slug_full.split("/")[-1] ):
log.info("Selected Event '{}' by slug '{}'" \
.format(
e["name"],
slug,
))
return e
log.error("No Event matching slug '{}' found".format(slug))
return None
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
data = None data = None
@ -644,7 +836,11 @@ def getTournamentTop(
log.error("Failed to load Tournament") log.error("Failed to load Tournament")
return None,None return None,None
event = selectBiggestEvent(tournament_data, log) event = None
if event_slug is None:
event = selectBiggestEvent(tournament_data, log)
else:
event = selectEventBySlug(tournament_data, event_slug, log)
if event is None : if event is None :
return None,None return None,None
@ -653,6 +849,7 @@ def getTournamentTop(
tournament = smashgg.Tournament( tournament = smashgg.Tournament(
id = tournament_data["id"], id = tournament_data["id"],
slug = tournament_data["slug"], slug = tournament_data["slug"],
game = event["videogame"],
name = tournament_data["name"], name = tournament_data["name"],
startAt = \ startAt = \
datetime.datetime. \ datetime.datetime. \
@ -663,7 +860,10 @@ def getTournamentTop(
city = tournament_data["city"], city = tournament_data["city"],
countryCode = tournament_data["countryCode"], countryCode = tournament_data["countryCode"],
hashtag = tournament_data["hashtag"], hashtag = tournament_data["hashtag"],
) ) \
.clean_name(
import_options.get("name_seo_delimiter", None)
)
# Get the top players # Get the top players
@ -688,10 +888,10 @@ def getTournamentTop(
["authorizations"] \ ["authorizations"] \
[0] \ [0] \
["externalUsername"] ["externalUsername"]
except TypeError: except:
twitterHandle = None twitterHandle = None
if import_options.get("use_smashgg_prefixes", True): if "use_smashgg_prefixes" in import_options:
prefix = participant_data["prefix"] prefix = participant_data["prefix"]
else: else:
prefix = "" prefix = ""
@ -733,7 +933,11 @@ def getTournamentTop(
log.error("Failed to load Tournament") log.error("Failed to load Tournament")
return None,None return None,None
event = selectBiggestEvent(tournament_data, log) event = None
if event_slug is None:
event = selectBiggestEvent(tournament_data, log)
else:
event = selectEventBySlug(tournament_data, event_slug, log)
if event is None : if event is None :
return None,None return None,None
@ -753,6 +957,7 @@ def getTournamentTop(
eid = slct["entrant"]["id"] eid = slct["entrant"]["id"]
try: try:
top_players[eid].add_character_selection( top_players[eid].add_character_selection(
game = tournament.game,
character = slct["selectionValue"], character = slct["selectionValue"],
win = (winnerId == eid), win = (winnerId == eid),
) )