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 configparser
import copy
import datetime
import html
import io
import json
import logging
import pathlib
import pprint
import requests
import sys
import urllib
@ -31,6 +34,62 @@ LOG_DUMMY.addHandler(logging.NullHandler())
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(
dir_templates = DEFAULT_DIR_TEMPLATES,
@ -47,6 +106,76 @@ def get_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(
url,
@ -65,25 +194,76 @@ def get_infos_from_url(
if outform not in [ "dict", "lkrz" ]:
raise ValueError("Unsupported outform")
# -------------------------------------------------------------------------
tournament = None
event = None
# -------------------------------------------------------------------------
if url_parsed.netloc == "smash.gg":
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
tournament, top_players = getTournamentTop(
id_or_slug = url_parsed.path.split("/")[2],
import_options = options,
top = top,
token = token,
proxy = proxy,
log = log,
)
try:
tournament = url_parsed.path.split("/")[2]
except:
log.error("Incomplete URL '{}'".format(url))
raise Exception("No tournament slug found in url {}".format(url))
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")
try:
if (url_parsed.path.split("/")[3] == "event"):
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":
@ -117,36 +297,24 @@ def init_resources(
# Start resources download according to game
if game in ssbu.GAME.list_names():
resources.download_res(
dstdir = imgdir,
game = ssbu,
source = source,
store_raw = store_raw,
proxy = proxy,
log = log,
)
game = ssbu
elif game in melee.GAME.list_names():
resources.download_res_ssbm(
dstdir = imgdir,
game = melee,
source = source,
store_raw = store_raw,
proxy = proxy,
log = log,
)
game = melee
elif game in pplus.GAME.list_names():
resources.download_res(
dstdir = imgdir,
game = pplus,
source = source,
store_raw = store_raw,
proxy = proxy,
log = log,
)
game = pplus
else:
log.error("Unknown game '{}'".format(game))
return 1
resources.download_res(
dstdir = imgdir,
game = game,
source = source,
store_raw = store_raw,
proxy = proxy,
log = log,
)
return 0
# =============================================================================
@ -161,12 +329,20 @@ def generate_pic(
log = LOG_DUMMY,
):
if outform.startswith("."):
outform = outform[1:]
if outform not in ["svg", "png"]:
raise Exception("Unsupported outform")
if type(infos_or_lkrzfile) == str:
# TODO : load lkrz as dict infos
raise NotImplementedError()
# load lkrz as dict infos
infos = get_infos_from_file(
lkrz_file_path = pathlib.Path(infos_or_lkrzfile),
options = options,
outform = "dict",
log = log,
)
else:
infos = infos_or_lkrzfile
@ -185,7 +361,7 @@ def generate_pic(
),
"dir_res_ssbu": dir_res,
"dir_template": str(dir_templates/template),
"options": options.get("template_options", []),
"options": options,
}
pic = export.generate_pic(
@ -195,7 +371,7 @@ def generate_pic(
outform,
log = log,
cachedir = dir_cache,
options = { "svg_embed_png": options.get("svg_embed_png",False) },
options = options,
)
if pic is None:
@ -308,21 +484,24 @@ def main():
)
top8_parser.add_argument(
"--template-options", "-TO",
action = "append",
default = [],
nargs="+",
action = StoreOptionKeyPair,
default = {},
help = "Template-specific options (like 'covid' or 'animated')",
)
top8_parser.add_argument(
"--export-options", "-EO",
action = "append",
default = [],
nargs="+",
action = StoreOptionKeyPair,
default = {},
help = "Export options (like 'svg_embed_png')",
)
top8_parser.add_argument(
"--import-options", "-IO",
action = "append",
default = [],
nargs="+",
action = StoreOptionKeyPair,
default = {},
help = "Import options (like 'use_smashgg_prefixes')",
)
@ -378,6 +557,10 @@ def main():
log.addHandler(log_handler_console)
# Print all arguments in debug
# -------------------------------------------------------------------------
log.debug( "Command arguments:\n{}".format( pprint.pformat(vars(args))) )
# Print version if required
# -------------------------------------------------------------------------
if args.version:
@ -423,58 +606,21 @@ def main():
#
tournament = None
top_players = {}
lkrz_file = None
if args.lkrz_file is not None and args.lkrz_file.exists():
lkrz = configparser.ConfigParser()
lkrz.read(str(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"],
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:
all_options = {
**args.import_options,
**args.template_options,
**args.export_options,
}
# 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(
url = args.tournament,
token = args.token,
@ -485,29 +631,33 @@ def main():
log = log,
)
tournament = infos["tournament"]
top_players = infos["players"]
if tournament is None or top_players is None:
log.error("Could not load data from smash.gg")
return 1
# Save a lkrz file
lkrz_data = "\n".join(
[ tournament.conf() ] \
+ list(map(
lambda p:p.conf(),
top_players,
))
# lkrz file
elif pathlib.Path(args.tournament).exists():
infos = get_infos_from_file(
lkrz_file_path = pathlib.Path(args.tournament),
options = args.import_options,
outform = "dict",
log = log,
)
if args.lkrz_file is None:
args.lkrz_file = pathlib.Path(
"{}.lkrz".format(tournament.slug)
)
# id or slug
else:
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:
f.write(lkrz_data)
tournament = infos["tournament"]
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'
if args.outfile is None:
@ -515,48 +665,60 @@ def main():
"{}.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:
dir_res_ssbu = args.imgdir.as_uri() # not absolute => error
dir_res = (args.imgdir / tournament.game.name).as_uri() # not absolute => error
except ValueError:
dir_res_ssbu = args.imgdir.as_posix()
dir_res = (args.imgdir / tournament.game.name).as_posix()
context = {
"tournament": tournament.clean_name(args.name_seo_delimiter),
"players" : sorted(
top_players,
key = lambda p: p.placement,
),
"dir_res_ssbu": dir_res_ssbu,
"dir_template": str(args.templatesdir / args.template),
"options": args.template_options,
}
rv = export.generate_outfile(
args.templatesdir,
args.template,
context,
args.outfile,
pic = generate_pic(
infos_or_lkrzfile = infos,
template = args.template,
outform = args.outfile.suffix,
options = all_options,
dir_templates = args.templatesdir,
dir_res = dir_res,
dir_cache = args.cachedir,
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
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
# -----------------------------------------------------------------------------
def getTournamentTop(
id_or_slug,
import_options = {}
event_slug = None,
import_options = [],
top = 8,
token = "",
proxy = None,
log=LOG_DUMMY):
log=LOG_DUMMY,
):
"""Returns a tuple : the smashgg.Tournament object and a list of the top
smashgg.Player in that tournament."""
@ -605,6 +767,36 @@ def getTournamentTop(
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
@ -644,7 +836,11 @@ def getTournamentTop(
log.error("Failed to load Tournament")
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 :
return None,None
@ -653,6 +849,7 @@ def getTournamentTop(
tournament = smashgg.Tournament(
id = tournament_data["id"],
slug = tournament_data["slug"],
game = event["videogame"],
name = tournament_data["name"],
startAt = \
datetime.datetime. \
@ -663,7 +860,10 @@ def getTournamentTop(
city = tournament_data["city"],
countryCode = tournament_data["countryCode"],
hashtag = tournament_data["hashtag"],
)
) \
.clean_name(
import_options.get("name_seo_delimiter", None)
)
# Get the top players
@ -688,10 +888,10 @@ def getTournamentTop(
["authorizations"] \
[0] \
["externalUsername"]
except TypeError:
except:
twitterHandle = None
if import_options.get("use_smashgg_prefixes", True):
if "use_smashgg_prefixes" in import_options:
prefix = participant_data["prefix"]
else:
prefix = ""
@ -733,7 +933,11 @@ def getTournamentTop(
log.error("Failed to load Tournament")
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 :
return None,None
@ -753,6 +957,7 @@ def getTournamentTop(
eid = slct["entrant"]["id"]
try:
top_players[eid].add_character_selection(
game = tournament.game,
character = slct["selectionValue"],
win = (winnerId == eid),
)