mirror of
https://github.com/frozenpandaman/s3s.git
synced 2026-09-28 20:26:28 -05:00
fix merge conflicts
This commit is contained in:
463
s3s.py
463
s3s.py
@@ -6,11 +6,12 @@
|
||||
|
||||
import argparse, base64, datetime, json, os, shutil, re, requests, sys, time, uuid
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
import mmh3, msgpack
|
||||
from subprocess import call
|
||||
import msgpack
|
||||
from packaging import version
|
||||
import iksm, utils
|
||||
|
||||
A_VERSION = "0.1.17"
|
||||
A_VERSION = "0.2.0"
|
||||
|
||||
DEBUG = False
|
||||
|
||||
@@ -85,19 +86,26 @@ def write_config(tokens):
|
||||
config_file.close()
|
||||
|
||||
|
||||
def headbutt():
|
||||
def headbutt(forcelang=None):
|
||||
'''Returns a (dynamic!) header used for GraphQL requests.'''
|
||||
|
||||
if forcelang:
|
||||
lang = forcelang
|
||||
country = forcelang[-2:]
|
||||
else:
|
||||
lang = USER_LANG
|
||||
country = USER_COUNTRY
|
||||
|
||||
graphql_head = {
|
||||
'Authorization': f'Bearer {BULLETTOKEN}', # update every time it's called with current global var
|
||||
'Accept-Language': USER_LANG,
|
||||
'Accept-Language': lang,
|
||||
'User-Agent': APP_USER_AGENT,
|
||||
'X-Web-View-Ver': iksm.get_web_view_ver(),
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': '*/*',
|
||||
'Origin': iksm.SPLATNET3_URL,
|
||||
'X-Requested-With': 'com.nintendo.znca',
|
||||
'Referer': f'{iksm.SPLATNET3_URL}?lang={USER_LANG}&na_country={USER_COUNTRY}&na_lang={USER_LANG}',
|
||||
'Referer': f'{iksm.SPLATNET3_URL}?lang={lang}&na_country={country}&na_lang={lang}',
|
||||
'Accept-Encoding': 'gzip, deflate'
|
||||
}
|
||||
return graphql_head
|
||||
@@ -228,10 +236,14 @@ def fetch_json(which, separate=False, exportall=False, specific=False, numbers_o
|
||||
if sha is not None:
|
||||
if DEBUG:
|
||||
print(f"* making query1 to {sha}")
|
||||
lang = 'en-US' if sha == "CoopHistoryQuery" else None
|
||||
sha = utils.translate_rid[sha]
|
||||
battle_ids, job_ids = [], []
|
||||
|
||||
query1 = requests.post(utils.GRAPHQL_URL, data=utils.gen_graphql_body(sha), headers=headbutt(), cookies=dict(_gtoken=GTOKEN))
|
||||
query1 = requests.post(utils.GRAPHQL_URL,
|
||||
data=utils.gen_graphql_body(sha),
|
||||
headers=headbutt(forcelang=lang),
|
||||
cookies=dict(_gtoken=GTOKEN))
|
||||
query1_resp = json.loads(query1.text)
|
||||
swim()
|
||||
|
||||
@@ -270,7 +282,6 @@ def fetch_json(which, separate=False, exportall=False, specific=False, numbers_o
|
||||
ink_list.extend(battle_ids)
|
||||
salmon_list.extend(job_ids)
|
||||
else: # ALL DATA - TAKES A LONG TIME
|
||||
|
||||
ink_list.extend(thread_pool.map(fetch_detailed_result, [True]*len(battle_ids), battle_ids, [swim]*len(battle_ids)))
|
||||
|
||||
salmon_list.extend(thread_pool.map(fetch_detailed_result, [False]*len(job_ids), job_ids, [swim]*len(job_ids)))
|
||||
@@ -300,26 +311,33 @@ def fetch_json(which, separate=False, exportall=False, specific=False, numbers_o
|
||||
return combined
|
||||
|
||||
def fetch_detailed_result(is_vs_history, history_id, swim):
|
||||
'''Helper function for fetch_json().'''
|
||||
|
||||
sha = "VsHistoryDetailQuery" if is_vs_history else "CoopHistoryDetailQuery"
|
||||
varname = "vsResultId" if is_vs_history else "coopHistoryDetailId"
|
||||
lang = None if is_vs_history else 'en-US'
|
||||
|
||||
query2 = requests.post(utils.GRAPHQL_URL,
|
||||
data=utils.gen_graphql_body(utils.translate_rid[sha], varname, history_id),
|
||||
headers=headbutt(),
|
||||
headers=headbutt(forcelang=lang),
|
||||
cookies=dict(_gtoken=GTOKEN))
|
||||
query2_resp = json.loads(query2.text)
|
||||
|
||||
swim()
|
||||
return query2_resp
|
||||
|
||||
|
||||
def update_salmon_profile():
|
||||
''' Updates stat.ink Salmon Run stats/profile.'''
|
||||
|
||||
pass
|
||||
pass # SR TODO
|
||||
|
||||
# prefetch_checks()
|
||||
|
||||
# old code - need stat.ink s3 support - TODO
|
||||
# url = nintendo's api/coop_results...
|
||||
# results_list = requests.get(url, headers=headbutt(), cookies=dict(_gtoken=GTOKEN))
|
||||
# results_list = requests.post(utils.GRAPHQL_URL,
|
||||
# data=utils.gen_graphql_body(utils.translate_rid["CoopHistoryQuery"]),
|
||||
# headers=headbutt(forcelang='en-US'),
|
||||
# cookies=dict(_gtoken=GTOKEN))
|
||||
# data = json.loads(results_list.text)
|
||||
# profile = data["summary"]
|
||||
|
||||
@@ -329,10 +347,10 @@ def update_salmon_profile():
|
||||
# "total_eggs": profile["card"]["ikura_total"],
|
||||
# "total_rescued": profile["card"]["help_total"],
|
||||
# "total_point": profile["card"]["kuma_point_total"]
|
||||
# # TODO - other new things - fish scales, xtrawave stuff?, etc.
|
||||
# # other new things - total fish scales, kings defeated, current & total points?
|
||||
# }
|
||||
|
||||
# url = "https://stat.ink/api/v2/salmon-stats" # TODO - need stat.ink s3 support
|
||||
# url = "https://stat.ink/api/v2/salmon-stats"
|
||||
# auth = {'Authorization': f'Bearer {API_KEY}'}
|
||||
# updateprofile = requests.post(url, headers=auth, data=payload)
|
||||
|
||||
@@ -598,7 +616,9 @@ def prepare_battle_result(battle, ismonitoring, isblackout, overview_data=None):
|
||||
payload["knockout"] = "no" if battle["knockout"] is None or battle["knockout"] == "NEITHER" else "yes"
|
||||
payload["rank_exp_change"] = battle["bankaraMatch"]["earnedUdemaePoint"]
|
||||
|
||||
battle_id = base64.b64decode(battle["id"]).decode('utf-8')
|
||||
battle_id = base64.b64decode(battle["id"]).decode('utf-8')
|
||||
battle_id_mutated = battle_id.replace("BANKARA", "RECENT") # normalize the ID, make work with -M and -r
|
||||
|
||||
if overview_data is None: # no passed in file with -i
|
||||
overview_post = requests.post(utils.GRAPHQL_URL,
|
||||
data=utils.gen_graphql_body(utils.translate_rid["BankaraBattleHistoriesQuery"]),
|
||||
@@ -620,13 +640,14 @@ def prepare_battle_result(battle, ismonitoring, isblackout, overview_data=None):
|
||||
for parent in ranked_list: # groups in overview (ranked) JSON/screen
|
||||
for idx, child in enumerate(parent["historyDetails"]["nodes"]):
|
||||
|
||||
overview_battle_id = base64.b64decode(child["id"]).decode('utf-8')
|
||||
overview_battle_id = base64.b64decode(child["id"]).decode('utf-8')
|
||||
overview_battle_id_mutated = overview_battle_id.replace("BANKARA", "RECENT") # same battle, different screens
|
||||
if overview_battle_id_mutated == battle_id: # found the battle ID in the other file
|
||||
|
||||
if overview_battle_id_mutated == battle_id_mutated: # found the battle ID in the other file
|
||||
|
||||
full_rank = re.split('([0-9]+)', child["udemae"].lower())
|
||||
was_s_plus_before = len(full_rank) > 1 # true if "before" rank is s+
|
||||
|
||||
|
||||
payload["rank_before"] = full_rank[0]
|
||||
if was_s_plus_before:
|
||||
payload["rank_before_s_plus"] = int(full_rank[1])
|
||||
@@ -643,13 +664,11 @@ def prepare_battle_result(battle, ismonitoring, isblackout, overview_data=None):
|
||||
else:
|
||||
payload["rank_up_battle"] = "no"
|
||||
|
||||
if parent["bankaraMatchChallenge"]["udemaeAfter"] is None:
|
||||
payload["rank_after"] = payload["rank_before"]
|
||||
if was_s_plus_before:
|
||||
payload["rank_after_s_plus"] = payload["rank_before_s_plus"] # also s+ after
|
||||
else:
|
||||
if parent["bankaraMatchChallenge"]["udemaeAfter"] is not None:
|
||||
if idx != 0:
|
||||
payload["rank_after"] = payload["rank_before"]
|
||||
if was_s_plus_before: # not a rank-up battle, so must be the same
|
||||
payload["rank_after_s_plus"] = payload["rank_before_s_plus"]
|
||||
else: # the battle where we actually ranked up
|
||||
full_rank_after = re.split('([0-9]+)', parent["bankaraMatchChallenge"]["udemaeAfter"].lower())
|
||||
payload["rank_after"] = full_rank_after[0]
|
||||
@@ -729,13 +748,233 @@ def prepare_battle_result(battle, ismonitoring, isblackout, overview_data=None):
|
||||
return payload
|
||||
|
||||
|
||||
def prepare_job_result(battle, ismonitoring, isblackout, overview_data=None):
|
||||
def prepare_job_result(job, ismonitoring, isblackout, overview_data=None):
|
||||
'''Converts the Nintendo JSON format for a Salmon Run job to the stat.ink one.'''
|
||||
|
||||
pass # stat.ink doesn't support SR yet
|
||||
# combo of set_teammates() + salmon_post_shift() - mirrors prepare_battle_result()
|
||||
# specify if private or not; also ensure SR results are formatted the same in post_result()!
|
||||
# set payload["splatnet_json"]
|
||||
# https://github.com/fetus-hina/stat.ink/wiki/Spl3-API:-Salmon-%EF%BC%8D-Post
|
||||
payload = {}
|
||||
job = job["coopHistoryDetail"]
|
||||
|
||||
full_id = utils.b64d(job["id"])
|
||||
payload["uuid"] = str(uuid.uuid5(utils.SALMON_NAMESPACE, full_id[-52:]))
|
||||
|
||||
payload["stage"] = utils.b64d(job["coopStage"]["id"])
|
||||
|
||||
payload["danger_rate"] = job["dangerRate"] * 100
|
||||
payload["king_smell"] = job["smellMeter"]
|
||||
|
||||
waves_cleared = job["resultWave"] - 1 # resultWave = 0 if all normal waves cleared
|
||||
payload["clear_waves"] = 3 if waves_cleared == -1 else waves_cleared
|
||||
|
||||
if payload["clear_waves"] < 0: # player dc'd
|
||||
payload["clear_waves"] = None
|
||||
|
||||
elif payload["clear_waves"] != 3: # job defeat only
|
||||
underperformed = False
|
||||
for wave in job["waveResults"]:
|
||||
if wave["teamDeliverCount"] < wave["deliverNorm"]:
|
||||
underperformed = True
|
||||
payload["fail_reason"] = "time_limit" if underperformed else "wipe_out"
|
||||
|
||||
# xtrawave only
|
||||
# https://stat.ink/api-info/boss-salmonid3
|
||||
if job["bossResult"]:
|
||||
king_id = utils.b64d(job["bossResult"]["boss"]["id"])
|
||||
if king_id == 23: # cohozuna
|
||||
payload["king_salmonid"] = "yokozuna"
|
||||
|
||||
payload["clear_extra"] = "yes" if job["bossResult"]["hasDefeatBoss"] else "no"
|
||||
|
||||
# https://stat.ink/api-info/salmon-title3
|
||||
payload["title_after"] = utils.b64d(job["afterGrade"]["id"])
|
||||
payload["title_exp_after"] = job["afterGradePoint"]
|
||||
|
||||
point_diff = 20 if payload["clear_waves"] == 3 else -30 + (10 * job["resultWave"]) # +20 for win or -(30-10w) for loss
|
||||
if payload["title_exp_after"] - point_diff >= 0: # before exp isn't negative, i.e. no title change
|
||||
payload["title_before"] = payload["title_after"]
|
||||
payload["title_exp_before"] = payload["title_exp_after"] - point_diff
|
||||
else: # ranked up
|
||||
payload["title_before"] = payload["title_after"] - 1
|
||||
# exp...
|
||||
|
||||
geggs = job["myResult"]["goldenDeliverCount"]
|
||||
peggs = job["myResult"]["deliverCount"]
|
||||
for player in job["memberResults"]:
|
||||
geggs += player["goldenDeliverCount"]
|
||||
peggs += player["deliverCount"]
|
||||
payload["golden_eggs"] = geggs
|
||||
payload["power_eggs"] = peggs
|
||||
|
||||
if job["scale"]:
|
||||
payload["gold_scale"] = job["scale"]["gold"]
|
||||
payload["silver_scale"] = job["scale"]["silver"]
|
||||
payload["bronze_scale"] = job["scale"]["bronze"]
|
||||
|
||||
payload["job_score"] = job["jobScore"] # job score
|
||||
payload["job_rate"] = job["jobRate"] # pay grade
|
||||
payload["job_bonus"] = job["jobBonus"] # clear bonus
|
||||
payload["job_point"] = job["jobPoint"] # your points = floor((score x rate) + bonus)
|
||||
# note the current bug with "bonus" lol... https://github.com/frozenpandaman/s3s/wiki/%7C-splatnet-bugs
|
||||
|
||||
waves = []
|
||||
for wave in job["waveResults"]:
|
||||
wave_info = {}
|
||||
wave_info["tide"] = "low" if wave["waterLevel"] == 0 else "high" if wave["waterLevel"] == 2 else "normal"
|
||||
wave_info["golden_quota"] = wave["deliverNorm"]
|
||||
wave_info["golden_delivered"] = wave["teamDeliverCount"]
|
||||
wave_info["golden_appearances"] = wave["goldenPopCount"]
|
||||
|
||||
if wave["eventWave"]:
|
||||
event_id = utils.b64d(wave["eventWave"]["id"])
|
||||
translate_occurrence = {
|
||||
1: "rush",
|
||||
2: "goldie_seeking",
|
||||
3: "the_griller",
|
||||
4: "the_mothership",
|
||||
5: "fog",
|
||||
6: "cohock_charge",
|
||||
7: "giant_tornado",
|
||||
8: "mudmouth_eruption"
|
||||
}
|
||||
wave_info["event"] = translate_occurrence[event_id]
|
||||
|
||||
translate_special = {
|
||||
20006: "nicedama",
|
||||
20007: "hopsonar",
|
||||
20009: "megaphone51",
|
||||
20010: "jetpack",
|
||||
20012: "kanitank",
|
||||
20013: "sameride",
|
||||
20014: "tripletornado"
|
||||
}
|
||||
special_uses = {
|
||||
"nicedama": 0,
|
||||
"hopsonar": 0,
|
||||
"megaphone51": 0,
|
||||
"jetpack": 0,
|
||||
"kanitank": 0,
|
||||
"sameride": 0,
|
||||
"tripletornado": 0,
|
||||
"unknown": 0
|
||||
}
|
||||
for wep_use in wave["specialWeapons"]:
|
||||
special_id = utils.b64d(wep_use["id"])
|
||||
special_key = translate_special.get(special_id, "unknown")
|
||||
special_uses[special_key] += 1 # increment value of the key each time it's found
|
||||
wave_info["special_uses"] = special_uses
|
||||
|
||||
waves.append(wave_info)
|
||||
payload["waves"] = waves
|
||||
|
||||
players = []
|
||||
players_json = [job["myResult"]]
|
||||
for teammate in job["memberResults"]:
|
||||
players_json.append(teammate)
|
||||
|
||||
for player in players_json:
|
||||
player_info = {}
|
||||
player_info["me"] = "yes" if player["player"]["isMyself"] else "no"
|
||||
player_info["name"] = player["player"]["name"]
|
||||
player_info["number"] = player["player"]["nameId"]
|
||||
player_info["splashtag_title"] = player["player"]["byname"]
|
||||
player_info["golden_eggs"] = player["goldenDeliverCount"]
|
||||
player_info["golden_assist"] = player["goldenAssistCount"]
|
||||
player_info["power_eggs"] = player["deliverCount"]
|
||||
player_info["rescue"] = player["rescueCount"]
|
||||
player_info["rescued"] = player["rescuedCount"]
|
||||
player_info["defeat_boss"] = player["defeatEnemyCount"]
|
||||
|
||||
dc_indicators = [
|
||||
player_info["golden_eggs"],
|
||||
player_info["power_eggs"],
|
||||
player_info["rescue"],
|
||||
player_info["rescued"],
|
||||
player_info["defeat_boss"]
|
||||
]
|
||||
player_info["disconnected"] = "yes" if all(value == 0 for value in dc_indicators) else "no"
|
||||
|
||||
slop_num = utils.b64d(player["player"]["uniform"]["id"])
|
||||
translate_slop = {
|
||||
1: "orange",
|
||||
2: "green",
|
||||
3: "yellow",
|
||||
4: "pink",
|
||||
5: "blue",
|
||||
6: "black",
|
||||
7: "white"
|
||||
}
|
||||
player_info["uniform"] = translate_slop[slop_num]
|
||||
|
||||
try:
|
||||
special_id = utils.b64d(player["specialWeapon"]["id"])
|
||||
player_info["special"] = translate_special[special_id]
|
||||
except TypeError: # player.specialWeapon is null - player dc'd
|
||||
pass
|
||||
except KeyError: # invalid special weapon - likely defaulted to '1' before it could be assigned
|
||||
pass
|
||||
|
||||
weapons = []
|
||||
for weapon in player["weapons"]: # should always be returned in in english due to headbutt() using forcelang
|
||||
wep_string = weapon["name"].lower().replace(" ", "_").replace("-", "_").replace(".", "").replace("'", "")
|
||||
if wep_string == "random": # NINTENDOOOOOOO
|
||||
wep_string = None
|
||||
weapons.append(wep_string)
|
||||
player_info["weapons"] = weapons
|
||||
|
||||
players.append(player_info)
|
||||
payload["players"] = players
|
||||
|
||||
# https://stat.ink/api-info/boss-salmonid3
|
||||
bosses = {}
|
||||
translate_boss = {
|
||||
4: "bakudan",
|
||||
5: "katapad",
|
||||
6: "teppan",
|
||||
7: "hebi",
|
||||
8: "tower",
|
||||
9: "mogura",
|
||||
10: "koumori",
|
||||
11: "hashira",
|
||||
12: "diver",
|
||||
13: "tekkyu",
|
||||
14: "nabebuta",
|
||||
15: "kin_shake",
|
||||
17: "grill",
|
||||
20: "doro_shake"
|
||||
}
|
||||
for boss in job["enemyResults"]:
|
||||
boss_id = utils.b64d(boss["enemy"]["id"])
|
||||
boss_key = translate_boss[boss_id]
|
||||
bosses[boss_key] = {
|
||||
"appearances": boss["popCount"],
|
||||
"defeated": boss["teamDefeatCount"],
|
||||
"defeated_by_me": boss["defeatCount"]
|
||||
}
|
||||
payload["bosses"] = bosses
|
||||
|
||||
payload["start_at"] = utils.epoch_time(job["playedTime"])
|
||||
payload["private"] = "yes" if not job["jobPoint"] else "no"
|
||||
|
||||
payload["big_run"] = "no" # SR TODO
|
||||
|
||||
if isblackout:
|
||||
# fix payload
|
||||
for player in payload["players"]:
|
||||
if player["me"] == "no":
|
||||
player["name"] = None
|
||||
player["number"] = None
|
||||
player["splashtag_title"] = None
|
||||
|
||||
# fix job json
|
||||
for player in job["memberResults"]:
|
||||
player["player"]["name"] = None
|
||||
player["player"]["nameId"] = None
|
||||
player["player"]["byname"] = None
|
||||
|
||||
payload["splatnet_json"] = json.dumps(job)
|
||||
payload["automated"] = "yes"
|
||||
|
||||
return payload
|
||||
|
||||
|
||||
def post_result(data, ismonitoring, isblackout, istestrun, overview_data=None):
|
||||
@@ -747,12 +986,12 @@ def post_result(data, ismonitoring, isblackout, istestrun, overview_data=None):
|
||||
|
||||
if isinstance(data, list): # -o export format
|
||||
try:
|
||||
data = [x for x in data if x['data']['vsHistoryDetail'] is not None] # avoid {'data': {'vsHistoryDetail': None}} error
|
||||
results = sorted(data, key=lambda d: d['data']['vsHistoryDetail']['playedTime'])
|
||||
data = [x for x in data if x["data"]["vsHistoryDetail"] is not None] # avoid {"data": {"vsHistoryDetail": None}} error
|
||||
results = sorted(data, key=lambda d: d["data"]["vsHistoryDetail"]["playedTime"])
|
||||
except KeyError:
|
||||
try:
|
||||
data = [x for x in data if x['coopHistoryDetail'] is not None]
|
||||
results = sorted(data, key=lambda d: d['data']['coopHistoryDetail']['playedTime'])
|
||||
data = [x for x in data if x["data"]["coopHistoryDetail"] is not None]
|
||||
results = sorted(data, key=lambda d: d["data"]["coopHistoryDetail"]["playedTime"])
|
||||
except KeyError: # unsorted - shouldn't happen
|
||||
print("(!) Uploading without chronologically sorting results")
|
||||
results = data
|
||||
@@ -766,8 +1005,10 @@ def post_result(data, ismonitoring, isblackout, istestrun, overview_data=None):
|
||||
for i in range(len(results)):
|
||||
if "vsHistoryDetail" in results[i]["data"]: # ink battle
|
||||
payload = prepare_battle_result(results[i]["data"], ismonitoring, isblackout, overview_data)
|
||||
which = "ink"
|
||||
elif "coopHistoryDetail" in results[i]["data"]: # salmon run job
|
||||
payload = prepare_job_result(results[i]["data"], ismonitoring, isblackout, overview_data)
|
||||
which = "salmon"
|
||||
else: # shouldn't happen
|
||||
print("Ill-formatted JSON while uploading. Exiting.")
|
||||
print('\nDebug info:')
|
||||
@@ -778,7 +1019,8 @@ def post_result(data, ismonitoring, isblackout, istestrun, overview_data=None):
|
||||
continue
|
||||
|
||||
# should have been taken care of in fetch_json() but just in case...
|
||||
if payload["lobby"] == "private" and utils.custom_key_exists("ignore_private", CONFIG_DATA):
|
||||
if payload.get("lobby") == "private" and utils.custom_key_exists("ignore_private", CONFIG_DATA) or \
|
||||
payload.get("private") == "yes" and utils.custom_key_exists("ignore_private", CONFIG_DATA): # SR version
|
||||
continue
|
||||
|
||||
s3s_values = {'agent': '\u0073\u0033\u0073', 'agent_version': f'v{A_VERSION}'} # lol
|
||||
@@ -792,8 +1034,12 @@ def post_result(data, ismonitoring, isblackout, istestrun, overview_data=None):
|
||||
if istestrun:
|
||||
payload["test"] = "yes"
|
||||
|
||||
# post
|
||||
url = "https://stat.ink/api/v3/battle"
|
||||
# POST
|
||||
url = "https://stat.ink/api/v3"
|
||||
if which == "ink":
|
||||
url += "/battle"
|
||||
elif which == "salmon":
|
||||
url += "/salmon"
|
||||
auth = {'Authorization': f'Bearer {API_KEY}', 'Content-Type': 'application/x-msgpack'}
|
||||
postbattle = requests.post(url, headers=auth, data=msgpack.packb(payload), allow_redirects=False)
|
||||
|
||||
@@ -817,12 +1063,12 @@ def post_result(data, ismonitoring, isblackout, istestrun, overview_data=None):
|
||||
print(f"* time uploaded: {time_uploaded}; time now: {time_now}")
|
||||
|
||||
if postbattle.status_code != 201: # Created (or already exists)
|
||||
print("Error uploading battle. Message from server:")
|
||||
print(f"Error uploading {utils.set_noun(which)[:-1]}. Message from server:")
|
||||
print(postbattle.content.decode('utf-8'))
|
||||
elif time_uploaded <= time_now - 5: # give some leeway
|
||||
print(f"Battle already uploaded - {headerloc}")
|
||||
print(f"{utils.set_noun(which)[:-1].capitalize()} already uploaded - {headerloc}")
|
||||
else: # 200 OK
|
||||
print(f"Battle uploaded to {headerloc}")
|
||||
print(f"{utils.set_noun(which)[:-1].capitalize()} uploaded to {headerloc}")
|
||||
|
||||
|
||||
def check_for_updates():
|
||||
@@ -849,7 +1095,7 @@ def check_for_updates():
|
||||
"` as soon as possible.\n")
|
||||
else: # no git directory
|
||||
print(" Visit the site below to update:\nhttps://github.com/frozenpandaman/s3s\n")
|
||||
except: # if there's a problem connecting to github
|
||||
except Exception as e: # if there's a problem connecting to github
|
||||
print('\033[3m' + "» Couldn't connect to GitHub. Please update the script manually via " \
|
||||
'`\033[91m' + "git pull" + '\033[0m' + "`." + '\033[0m' + "\n")
|
||||
# print('\033[3m' + "» While s3s is in beta, please update the script regularly via " \
|
||||
@@ -928,24 +1174,26 @@ def get_num_results(which):
|
||||
|
||||
|
||||
def fetch_and_upload_single_result(hash, noun, isblackout, istestrun):
|
||||
'''Performs a GraphQL request for a single vsResultId/coopResultId and call post_result().'''
|
||||
'''Performs a GraphQL request for a single vsResultId/coopHistoryDetailId and call post_result().'''
|
||||
|
||||
if noun in ("battles", "battle"):
|
||||
dict_key = "VsHistoryDetailQuery"
|
||||
dict_key2 = "vsResultId"
|
||||
lang = None
|
||||
else: # noun == "jobs" or "job"
|
||||
dict_key = "CoopHistoryQuery"
|
||||
dict_key2 = "coopResultId"
|
||||
dict_key = "CoopHistoryDetailQuery"
|
||||
dict_key2 = "coopHistoryDetailId"
|
||||
lang = 'en-US'
|
||||
|
||||
result_post = requests.post(utils.GRAPHQL_URL,
|
||||
data=utils.gen_graphql_body(utils.translate_rid[dict_key], dict_key2, hash),
|
||||
headers=headbutt(),
|
||||
headers=headbutt(forcelang=lang),
|
||||
cookies=dict(_gtoken=GTOKEN))
|
||||
result = json.loads(result_post.text)
|
||||
post_result(result, False, isblackout, istestrun) # not monitoring mode
|
||||
|
||||
|
||||
def check_if_missing(which, isblackout, istestrun):
|
||||
def check_if_missing(which, isblackout, istestrun, skipprefetch):
|
||||
'''Checks for unuploaded battles and uploads any that are found (-r flag).'''
|
||||
|
||||
noun = utils.set_noun(which)
|
||||
@@ -958,10 +1206,10 @@ def check_if_missing(which, isblackout, istestrun):
|
||||
urls.append("https://stat.ink/api/v3/s3s/uuid-list") # max 200 entries
|
||||
else:
|
||||
urls.append(None)
|
||||
# if which == "both" or which == "salmon":
|
||||
# urls.append("...")
|
||||
# else:
|
||||
# urls.append(None)
|
||||
if which == "both" or which == "salmon":
|
||||
urls.append("https://stat.ink/api/v3/salmon/uuid-list")
|
||||
else:
|
||||
urls.append(None)
|
||||
|
||||
noun = "battles" # first (and maybe only)
|
||||
which = "ink"
|
||||
@@ -978,19 +1226,27 @@ def check_if_missing(which, isblackout, istestrun):
|
||||
|
||||
# ! fetch from online
|
||||
# specific - check ALL possible battles; printout - to show tokens are being checked at program start
|
||||
splatnet_ids = fetch_json(which, specific=True, numbers_only=True, printout=True)
|
||||
splatnet_ids = fetch_json(which, specific=True, numbers_only=True, printout=True, skipprefetch=skipprefetch)
|
||||
|
||||
# same as code in -i section below...
|
||||
for id in reversed(splatnet_ids):
|
||||
full_id = utils.b64d(id)
|
||||
old_uuid = full_id[-36:]
|
||||
new_uuid = str(uuid.uuid5(utils.S3S_NAMESPACE, full_id[-52:]))
|
||||
|
||||
if new_uuid in statink_uploads:
|
||||
continue
|
||||
if old_uuid in statink_uploads:
|
||||
if not utils.custom_key_exists("force_uploads", CONFIG_DATA):
|
||||
if which == "ink":
|
||||
old_battle_uuid = full_id[-36:]
|
||||
new_battle_uuid = str(uuid.uuid5(utils.S3S_NAMESPACE, full_id[-52:]))
|
||||
|
||||
if new_battle_uuid in statink_uploads:
|
||||
continue
|
||||
if old_battle_uuid in statink_uploads:
|
||||
if not utils.custom_key_exists("force_uploads", CONFIG_DATA):
|
||||
continue
|
||||
|
||||
elif which == "salmon":
|
||||
job_uuid = str(uuid.uuid5(utils.SALMON_NAMESPACE, full_id[-52:]))
|
||||
if job_uuid in statink_uploads:
|
||||
continue
|
||||
|
||||
if not printed:
|
||||
printed = True
|
||||
print(f"Previously-unuploaded {noun} detected. Uploading now...")
|
||||
@@ -1003,25 +1259,9 @@ def check_if_missing(which, isblackout, istestrun):
|
||||
noun = "jobs" # for second run through the loop
|
||||
which = "salmon"
|
||||
|
||||
def check_for_new_results(input_params):
|
||||
def check_for_new_results(which, cached_battles, cached_jobs, battle_wins, battle_losses, battle_draws, splatfest_wins, splatfest_losses, splatfest_draws, mirror_matches, job_successes, job_failures, isblackout, istestrun):
|
||||
'''Helper function for monitor_battles(), called every N seconds or when exiting.'''
|
||||
|
||||
# INPUT PARAMETERS, IN ORDER:
|
||||
which = input_params[0] # "ink", "salmon", or "both"
|
||||
cached_battles = input_params[1] # lists of battles/jobs monitoring mode already knows about
|
||||
cached_jobs = input_params[2]
|
||||
battle_wins = input_params[3] # ink battle counters
|
||||
battle_losses = input_params[4]
|
||||
battle_draws = input_params[5]
|
||||
splatfest_wins = input_params[6] # splatfest counters
|
||||
splatfest_losses = input_params[7]
|
||||
splatfest_draws = input_params[8]
|
||||
mirror_matches = input_params[9]
|
||||
job_successes = input_params[10] # salmon run counters
|
||||
job_failures = input_params[11]
|
||||
isblackout = input_params[12] # type of run
|
||||
istestrun = input_params[13]
|
||||
|
||||
# ! fetch from online
|
||||
# check only numbers (quicker); specific=False since checks recent (latest) only
|
||||
ink_results, salmon_results = fetch_json(which, separate=True, numbers_only=True)
|
||||
@@ -1085,8 +1325,8 @@ def check_for_new_results(input_params):
|
||||
if num not in cached_jobs:
|
||||
# get the full job data
|
||||
result_post = requests.post(utils.GRAPHQL_URL,
|
||||
data=utils.gen_graphql_body(utils.translate_rid["CoopHistoryDetailQuery"], "coopResultId", num),
|
||||
headers=headbutt(),
|
||||
data=utils.gen_graphql_body(utils.translate_rid["CoopHistoryDetailQuery"], "coopHistoryDetailId", num),
|
||||
headers=headbutt(forcelang='en-US'),
|
||||
cookies=dict(_gtoken=GTOKEN))
|
||||
result = json.loads(result_post.text)
|
||||
|
||||
@@ -1103,8 +1343,7 @@ def check_for_new_results(input_params):
|
||||
|
||||
stagename = result["data"]["coopHistoryDetail"]["coopStage"]["name"]
|
||||
shortname = stagename.split(" ")[-1] # fine for salmon run stage names too
|
||||
endtime = utils.epoch_time(result["data"]["coopHistoryDetail"]["playedTime"]) + \
|
||||
result["data"]["coopHistoryDetail"]["duration"]
|
||||
endtime = utils.epoch_time(result["data"]["coopHistoryDetail"]["playedTime"])
|
||||
|
||||
dt = datetime.datetime.fromtimestamp(endtime).strftime('%I:%M:%S %p').lstrip("0")
|
||||
print(f"New job result detected at {dt}! ({shortname}, {outcome})")
|
||||
@@ -1153,7 +1392,7 @@ def monitor_battles(which, secs, isblackout, istestrun, skipprefetch):
|
||||
job_successes, job_failures,
|
||||
isblackout, istestrun
|
||||
]
|
||||
which, cached_battles, cached_jobs, battle_wins, battle_losses, battle_draws, splatfest_wins, splatfest_losses, splatfest_draws, mirror_matches, job_successes, job_failures, foundany = check_for_new_results(input_params)
|
||||
which, cached_battles, cached_jobs, battle_wins, battle_losses, battle_draws, splatfest_wins, splatfest_losses, splatfest_draws, mirror_matches, job_successes, job_failures, foundany = check_for_new_results(*input_params)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print(f"\n\nChecking to see if there are unuploaded {utils.set_noun(which)} before exiting...")
|
||||
@@ -1166,7 +1405,7 @@ def monitor_battles(which, secs, isblackout, istestrun, skipprefetch):
|
||||
job_successes, job_failures,
|
||||
isblackout, istestrun
|
||||
]
|
||||
which, cached_battles, cached_jobs, battle_wins, battle_losses, battle_draws, splatfest_wins, splatfest_losses, splatfest_draws, mirror_matches, job_successes, job_failures, foundany = check_for_new_results(input_params)
|
||||
which, cached_battles, cached_jobs, battle_wins, battle_losses, battle_draws, splatfest_wins, splatfest_losses, splatfest_draws, mirror_matches, job_successes, job_failures, foundany = check_for_new_results(*input_params)
|
||||
|
||||
noun = utils.set_noun(which)
|
||||
if foundany:
|
||||
@@ -1197,10 +1436,9 @@ def monitor_battles(which, secs, isblackout, istestrun, skipprefetch):
|
||||
|
||||
print(f"{mirror_matches} mirror match{'' if mirror_matches == 1 else 'es'} against your Splatfest team.")
|
||||
|
||||
# SR TODO
|
||||
# if which in ("salmon", "both"):
|
||||
# print(f"Salmon Run: {job_successes} success{'' if job_successes == 1 else 'es'} and " \
|
||||
# f"{job_failures} failure{'' if job_failures == 1 else 's'}.")
|
||||
if which in ("salmon", "both"):
|
||||
print(f"Salmon Run: {job_successes} success{'' if job_successes == 1 else 'es'} and " \
|
||||
f"{job_failures} failure{'' if job_failures == 1 else 's'}.")
|
||||
|
||||
print("Bye!")
|
||||
|
||||
@@ -1228,6 +1466,13 @@ class SquidProgress:
|
||||
def export_seed_json(skipprefetch=False):
|
||||
'''Export a JSON file for use with Lean's seed checker at https://leanny.github.io/splat3seedchecker/.'''
|
||||
|
||||
try:
|
||||
import pymmh3 as mmh3
|
||||
except ModuleNotFoundError:
|
||||
print("This function requires a Python module you don't have installed. " \
|
||||
"Please run " + '`\033[91m' + "pip install -r requirements.txt" + '\033[0m`' + " and try again.")
|
||||
sys.exit(1)
|
||||
|
||||
if not skipprefetch:
|
||||
prefetch_checks(printout=True)
|
||||
|
||||
@@ -1304,7 +1549,7 @@ def parse_arguments():
|
||||
parser.add_argument("-o", required=False, action="store_true",
|
||||
help="export all possible results to local files")
|
||||
parser.add_argument("-i", dest="file", nargs=2, required=False,
|
||||
help="upload local results; use `-i results.json overview.json`")
|
||||
help="upload local results: `-i (coop_)results.json overview.json`")
|
||||
parser.add_argument("-t", required=False, action="store_true",
|
||||
help="dry run for testing (won't post to stat.ink)")
|
||||
parser.add_argument("--getseed", required=False, action="store_true",
|
||||
@@ -1410,9 +1655,11 @@ def main():
|
||||
# manual json upload: -i flag
|
||||
#############################
|
||||
if filenames: # 2 files in list
|
||||
if os.path.basename(filenames[0]) != "results.json" or os.path.basename(filenames[1]) != "overview.json":
|
||||
if (os.path.basename(filenames[0]) != "results.json" \
|
||||
and os.path.basename(filenames[0]) != "coop_results.json") \
|
||||
or os.path.basename(filenames[1]) != "overview.json":
|
||||
print("Must use the format " \
|
||||
'\033[91m' + "-i path/to/results.json path/to/overview.json" + '\033[0m' + ". Exiting.")
|
||||
'\033[91m' + "-i path/to/(coop_)results.json path/to/overview.json" + '\033[0m' + ". Exiting.")
|
||||
sys.exit(1)
|
||||
for filename in filenames:
|
||||
if not os.path.exists(filename):
|
||||
@@ -1422,7 +1669,7 @@ def main():
|
||||
try:
|
||||
data = json.load(data_file)
|
||||
except ValueError:
|
||||
print("Could not decode JSON object in results.json.")
|
||||
print(f"Could not decode JSON object in {os.path.basename(filenames[0])}.")
|
||||
sys.exit(1)
|
||||
with open(filenames[1]) as data_file:
|
||||
try:
|
||||
@@ -1434,28 +1681,41 @@ def main():
|
||||
|
||||
# only upload unuploaded results
|
||||
auth = {'Authorization': f'Bearer {API_KEY}'}
|
||||
resp = requests.get("https://stat.ink/api/v3/s3s/uuid-list", headers=auth)
|
||||
resp_b = requests.get("https://stat.ink/api/v3/s3s/uuid-list", headers=auth)
|
||||
resp_j = requests.get("https://stat.ink/api/v3/salmon/uuid-list", headers=auth)
|
||||
try:
|
||||
statink_uploads = json.loads(resp.text)
|
||||
statink_uploads = json.loads(resp_b.text)
|
||||
statink_uploads.extend(json.loads(resp_j.text))
|
||||
except:
|
||||
print(f"Encountered an error while checking recently-uploaded data. Is stat.ink down?")
|
||||
sys.exit(1)
|
||||
|
||||
to_upload = []
|
||||
for battle in data:
|
||||
if battle["data"]["vsHistoryDetail"] is not None:
|
||||
full_id = utils.b64d(battle["data"]["vsHistoryDetail"]["id"])
|
||||
old_uuid = full_id[-36:] # not unique because nintendo hates us
|
||||
new_uuid = str(uuid.uuid5(utils.S3S_NAMESPACE, full_id[-52:]))
|
||||
for result in data:
|
||||
try: # ink battle
|
||||
if result["data"]["vsHistoryDetail"] is not None:
|
||||
full_id = utils.b64d(result["data"]["vsHistoryDetail"]["id"])
|
||||
old_uuid = full_id[-36:] # not unique because nintendo hates us
|
||||
new_uuid = str(uuid.uuid5(utils.S3S_NAMESPACE, full_id[-52:]))
|
||||
|
||||
if new_uuid in statink_uploads:
|
||||
print("Skipping already-uploaded battle.")
|
||||
continue
|
||||
if old_uuid in statink_uploads:
|
||||
if not utils.custom_key_exists("force_uploads", CONFIG_DATA):
|
||||
if new_uuid in statink_uploads:
|
||||
print("Skipping already-uploaded battle.")
|
||||
continue
|
||||
to_upload.append(battle)
|
||||
if old_uuid in statink_uploads:
|
||||
if not utils.custom_key_exists("force_uploads", CONFIG_DATA):
|
||||
print("Skipping already-uploaded battle.")
|
||||
continue
|
||||
to_upload.append(result)
|
||||
|
||||
except KeyError: # salmon run job
|
||||
if result["data"]["coopHistoryDetail"] is not None:
|
||||
full_id = utils.b64d(result["data"]["coopHistoryDetail"]["id"])
|
||||
the_uuid = str(uuid.uuid5(utils.SALMON_NAMESPACE, full_id[-52:]))
|
||||
|
||||
if the_uuid in statink_uploads:
|
||||
print("Skipping already-uploaded job.")
|
||||
continue
|
||||
to_upload.append(result)
|
||||
|
||||
post_result(to_upload, False, blackout, test_run, overview_data=overview_file) # one or multiple; monitoring mode = False
|
||||
sys.exit(0)
|
||||
@@ -1468,7 +1728,10 @@ def main():
|
||||
update_salmon_profile()
|
||||
|
||||
if check_old:
|
||||
check_if_missing(which, blackout, test_run) # monitoring mode hasn't begun yet
|
||||
if which == "both":
|
||||
prefetch_checks(printout=True)
|
||||
skipprefetch = True
|
||||
check_if_missing(which, blackout, test_run, skipprefetch) # monitoring mode hasn't begun yet
|
||||
print()
|
||||
|
||||
if secs != -1: # monitoring mode
|
||||
|
||||
Reference in New Issue
Block a user