2018-10-01 00:02:08 +08:00
#!/usr/bin/python3.7
2018-11-05 14:48:07 +02:00
import json , argparse , inspect , sys , os , base64 , aiohttp , urllib , asyncio , aiofiles , aioconsole
import logging , re , concurrent.futures
2018-10-04 11:24:24 +08:00
import blitzutils as bu
2018-10-03 23:17:27 +08:00
from blitzutils import WG
2018-11-05 14:48:07 +02:00
from blitzutils import WoTinspector
2018-10-01 00:02:08 +08:00
logging . getLogger ( "asyncio" ) . setLevel ( logging . DEBUG )
# WG account id of the uploader:
# # Find it here: https://developers.wargaming.net/reference/all/wotb/account/list/
2018-10-03 09:41:17 +08:00
WG_ID = 0
2018-10-01 00:02:08 +08:00
DEBUG = False
VERBOSE = False
SLEEP = 2
2018-10-01 12:41:23 +08:00
MAX_RETRIES = 3
REPLAY_N = 0
2018-10-02 14:43:06 +08:00
SKIPPED_N = 0
2018-10-01 12:41:23 +08:00
WIurl = 'https://wotinspector.com/api/replay/upload?'
2018-10-01 21:35:49 +08:00
WG_appID = '81381d3f45fa4aa75b78a7198eb216ad'
wg = None
2018-11-05 14:48:07 +02:00
wi = None
2018-10-01 00:02:08 +08:00
2018-10-02 14:43:06 +08:00
2018-10-01 00:02:08 +08:00
async def main ( argv ):
2018-11-05 14:48:07 +02:00
global wg , wi
2018-10-01 00:02:08 +08:00
parser = argparse . ArgumentParser ( description = 'Post replays(s) to WoTinspector.com and retrieve battle data' )
parser . add_argument ( '--output' , default = 'single' , choices = [ 'file' , 'files' , 'db' ] , help = 'Select output mode: single/multiple files or database' )
parser . add_argument ( '-id' , dest = 'accountID' , type = int , default = WG_ID , help = 'WG account_id' )
2018-10-01 21:35:49 +08:00
parser . add_argument ( '-a' , '--account' , dest = 'account' , type = str , default = None , help = 'Uploader \' s WG account name. Format: ACCOUNT_NAME@SERVER' )
2018-10-01 00:02:08 +08:00
parser . add_argument ( '-t' , '--title' , type = str , default = None , help = 'Title for replays. Use NN for continous numbering. Default is filename-based numbering' )
parser . add_argument ( '-p' , '--private' , dest = "private" , action = 'store_true' , default = False , help = 'Set replays private on WoTinspector.com' )
parser . add_argument ( '--tasks' , dest = 'N_tasks' , type = int , default = 10 , help = 'Number of worker threads' )
2018-10-04 16:50:14 +08:00
parser . add_argument ( '--tankopedia' , type = str , default = 'tanks.json' , help = 'JSON file to read Tankopedia from. Default: "tanks.json"' )
parser . add_argument ( '--mapfile' , type = str , default = 'maps.json' , help = 'JSON file to read Blitz map names from. Default: "maps.json"' )
2018-10-01 00:02:08 +08:00
parser . add_argument ( '-d' , '--debug' , action = 'store_true' , default = False , help = 'Debug mode' )
2018-10-17 11:02:39 +08:00
parser . add_argument ( '-v' , '--verbose' , action = 'store_true' , default = True , help = 'Verbose mode' )
parser . add_argument ( '-s' , '--silent' , action = 'store_true' , default = False , help = 'Silent mode' )
2018-10-01 00:02:08 +08:00
parser . add_argument ( 'files' , metavar = 'FILE1 [FILE2 ...]' , type = str , nargs = '+' , help = 'Files to read. Use \' - \' for STDIN"' )
args = parser . parse_args ()
2018-10-04 11:24:24 +08:00
bu . setVerbose ( args . verbose )
bu . setDebug ( args . debug )
2018-10-17 11:02:39 +08:00
if args . silent :
bu . setVerbose ( False )
2018-10-04 11:24:24 +08:00
wg = WG ( WG_appID , args . tankopedia , args . mapfile )
2018-11-05 14:48:07 +02:00
wi = WoTinspector ()
2018-10-01 00:02:08 +08:00
2018-10-01 21:35:49 +08:00
if args . account != None :
args . accountID = await wg . getAccountID ( args . account )
2018-10-04 11:24:24 +08:00
bu . debug ( 'WG account_id: ' + str ( args . accountID ))
2018-10-01 00:02:08 +08:00
2018-10-03 23:17:27 +08:00
if args . accountID == None :
args . accountID = 0
2018-10-01 00:02:08 +08:00
try :
2018-10-01 12:41:23 +08:00
queue = asyncio . Queue ()
2018-10-01 00:02:08 +08:00
tasks = []
2018-10-01 12:41:23 +08:00
# Make replay Queue
2018-10-01 00:02:08 +08:00
tasks . append ( asyncio . create_task ( mkReplayQ ( queue , args . files , args . title )))
2018-10-01 12:41:23 +08:00
# Start tasks to process the Queue
2018-10-01 00:02:08 +08:00
for i in range ( args . N_tasks ):
tasks . append ( asyncio . create_task ( replayWorker ( queue , i , args . accountID , args . private )))
2018-10-04 11:24:24 +08:00
bu . debug ( 'Task ' + str ( i ) + ' started' )
2018-10-01 12:41:23 +08:00
2018-10-04 11:24:24 +08:00
bu . debug ( 'Waiting for the replay scanner to finish' )
2018-10-01 00:02:08 +08:00
await asyncio . wait ([ tasks [ 0 ]])
2018-10-04 11:24:24 +08:00
bu . debug ( 'Scanner finished. Waiting for workers to finish queue' )
2018-10-01 00:02:08 +08:00
await queue . join ()
2018-10-04 11:24:24 +08:00
bu . debug ( 'Cancelling workers' )
2018-10-01 00:02:08 +08:00
for task in tasks :
task . cancel ()
2018-10-04 11:24:24 +08:00
bu . debug ( 'Waiting for workers to cancel' )
2018-10-01 00:02:08 +08:00
await asyncio . gather ( * tasks , return_exceptions = True )
2018-10-04 11:24:24 +08:00
bu . verbose ( str ( REPLAY_N ) + ' replays: ' + str ( REPLAY_N - SKIPPED_N ) + ' uploaded, ' + str ( SKIPPED_N ) + ' skipped' )
2018-10-01 21:35:49 +08:00
2018-10-01 00:02:08 +08:00
except KeyboardInterrupt :
print ( 'Ctrl-c pressed ...' )
sys . exit ( 1 )
2018-10-01 21:35:49 +08:00
finally :
## Need to close the aiohttp.session since Python destructors do not support async methods...
await wg . session . close ()
2018-11-05 14:48:07 +02:00
await wi . close ()
2018-10-01 00:02:08 +08:00
return None
async def mkReplayQ ( queue : asyncio . Queue , files : list , title : str ):
"""Create queue of replays to post"""
p_replayfile = re . compile ( '.* \\ .wotbreplay$' )
if files [ 0 ] == '-' :
2018-10-04 11:24:24 +08:00
bu . debug ( 'reading replay file list from STDIN' )
2018-10-01 00:02:08 +08:00
stdin , _ = await aioconsole . get_standard_streams ()
while True :
2018-10-04 11:24:24 +08:00
line = ( await stdin . readline ()) . decode ( 'utf-8' ) . rstrip ()
2018-10-01 00:02:08 +08:00
if not line :
break
else :
if ( p_replayfile . match ( line ) != None ):
2018-10-01 12:41:23 +08:00
await queue . put ( await mkQueueItem ( line , title ))
2018-10-01 00:02:08 +08:00
else :
for fn in files :
2018-11-05 18:15:51 +02:00
if fn . endswith ( '"' ):
2018-11-04 16:46:27 +02:00
fn = fn [: - 1 ]
2018-10-01 00:02:08 +08:00
if os . path . isfile ( fn ) and ( p_replayfile . match ( fn ) != None ):
2018-10-01 12:41:23 +08:00
await queue . put ( await mkQueueItem ( fn , title ))
2018-11-05 14:48:07 +02:00
bu . debug ( 'File added to queue: ' + fn )
2018-10-01 00:02:08 +08:00
elif os . path . isdir ( fn ):
with os . scandir ( fn ) as dirEntry :
for entry in dirEntry :
2018-11-04 13:25:58 +02:00
try :
bu . debug ( 'Found: ' + entry . name )
if entry . is_file () and ( p_replayfile . match ( entry . name ) != None ):
bu . debug ( entry . name )
await queue . put ( await mkQueueItem ( entry . path , title ))
2018-11-05 14:48:07 +02:00
bu . debug ( 'File added to queue: ' + entry . path )
2018-11-04 13:25:58 +02:00
except Exception as err :
bu . error ( str ( err ))
2018-11-05 14:48:07 +02:00
else :
bu . error ( 'File not found: ' + fn )
2018-10-04 11:24:24 +08:00
bu . debug ( 'Finished' )
2018-10-01 00:02:08 +08:00
return None
2018-10-01 12:41:23 +08:00
async def mkQueueItem ( filename : str , title : str ) -> list :
2018-10-01 00:02:08 +08:00
"""Make an item to replay queue"""
global REPLAY_N
REPLAY_N += 1
return [ filename , REPLAY_N , getTitle ( filename , title , REPLAY_N )]
async def replayWorker ( queue : asyncio . Queue , workerID : int , account_id : int , priv = False ):
2018-10-02 14:43:06 +08:00
"""Async Worker to process the replay queue"""
global SKIPPED_N
2018-11-05 14:48:07 +02:00
while True :
item = await queue . get ()
filename = item [ 0 ]
N = item [ 1 ]
title = item [ 2 ]
2018-10-02 14:43:06 +08:00
2018-11-05 14:48:07 +02:00
replay_json_fn = filename + '.json'
msg_str = 'Replay[' + str ( N ) + ']: '
2018-10-02 14:43:06 +08:00
2018-11-05 14:48:07 +02:00
#bu.debug(msg_str + replay_json_fn)
try :
#if os.path.exists(replay_json_fn) and os.path.isfile(replay_json_fn):
if os . path . isfile ( replay_json_fn ):
async with aiofiles . open ( replay_json_fn ) as fp :
replay_json = json . loads ( await fp . read ())
#if replay_json['status'] == 'ok':
if wi . chkJSONreplay ( replay_json ):
bu . verbose ( msg_str + title + ' has already been posted. Skipping.' )
SKIPPED_N += 1
queue . task_done ()
continue
2018-11-05 23:09:08 +02:00
except asyncio . CancelledError as err :
2018-11-05 14:48:07 +02:00
raise err
except Exception as err :
bu . error ( msg_str + 'Unexpected error: ' + str ( type ( err )) + ' : ' + str ( err ))
try :
#bu.debug('Opening file [' + str(N) +']: ' + filename)
2018-10-01 00:02:08 +08:00
async with aiofiles . open ( filename , 'rb' ) as fp :
filename = os . path . basename ( filename )
2018-11-05 14:48:07 +02:00
bu . debug ( msg_str + 'File: ' + filename )
json_resp = await wi . postReplay ( await fp . read (), filename , account_id , title , priv , N )
if json_resp != None :
if ( await bu . saveJSON ( replay_json_fn , json_resp )):
bu . debug ( msg_str + 'Replay saved OK: ' + filename )
else :
bu . error ( msg_str + 'Error saving replay: ' + filename )
except Exception as err :
bu . error ( msg_str + 'Unexpected Exception: ' + str ( type ( err )) + ' : ' + str ( err ) )
bu . debug ( msg_str + 'Marking task done' )
queue . task_done ()
2018-10-01 00:02:08 +08:00
return None
def getTitle ( replayfile : str , title : str , i : int ) -> str :
2018-10-01 21:35:49 +08:00
global wg
2018-10-01 00:02:08 +08:00
if title == None :
try :
filename = os . path . basename ( replayfile )
2018-11-04 13:25:58 +02:00
bu . debug ( filename )
2018-10-01 21:35:49 +08:00
map_usrStrs = wg . getMapUserStrs ()
p = re . compile ( ' \\ d {8} _ \\ d {4} _(.+)_(' + '|' . join ( map_usrStrs ) + ')(?:- \\ d)? \\ .wotbreplay$' )
2018-10-01 00:02:08 +08:00
m = p . match ( filename )
if ( m != None ):
2018-10-01 21:35:49 +08:00
if wg . tanks != None :
2018-11-04 13:25:58 +02:00
tank = m . group ( 1 )
if tank in wg . tanks [ 'userStr' ]:
tank = wg . tanks [ 'userStr' ][ tank ]
else :
bu . error ( 'Tank code: "' + tank + '" not found from Tankopedia (tanks.json)' )
2018-10-01 00:02:08 +08:00
else :
tank = m . group ( 1 )
2018-11-04 13:25:58 +02:00
blitz_map = m . group ( 2 )
if blitz_map in wg . maps :
blitz_map = wg . maps [ blitz_map ]
else :
bu . error ( 'Mapcode: "' + blitz_map + '" not found from map database (maps.json)' )
2018-10-01 00:02:08 +08:00
title = tank + ' @ ' + blitz_map
else :
title = re . sub ( ' \\ .wotbreplay$' , '' , filename )
except Exception as err :
2018-10-04 11:24:24 +08:00
bu . error ( err )
2018-10-01 00:02:08 +08:00
else :
2018-10-01 12:41:23 +08:00
title . replace ( 'NN' , str ( i ))
2018-10-01 00:02:08 +08:00
return title
2018-10-04 11:24:24 +08:00
### main() -------------------------------------------
2018-10-01 00:02:08 +08:00
if __name__ == "__main__" :
2018-10-02 14:43:06 +08:00
asyncio . run ( main ( sys . argv [ 1 :]), debug = False )