2020-01-18 19:47:17 +02:00
#!/usr/bin/env python3.8
2018-10-01 00:02:08 +08:00
2018-11-05 14:48:07 +02:00
import json , argparse , inspect , sys , os , base64 , aiohttp , urllib , asyncio , aiofiles , aioconsole
2019-11-23 01:44:21 +02:00
import logging , re , concurrent.futures , configparser , motor.motor_asyncio , ssl , pymongo
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 )
2019-09-20 19:38:40 +03:00
FILE_CONFIG = 'blitzstats.ini'
2020-02-11 21:22:18 +02:00
# DB_C_REPLAYS = 'Replays'
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
2019-08-16 22:53:06 +03:00
REPLAY_N = 0
SKIPPED_N = 0
ERROR_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
async def main ( argv ):
2018-11-05 14:48:07 +02:00
global wg , wi
2019-12-17 21:25:54 +02:00
# set the directory for the script
os . chdir ( os . path . dirname ( sys . argv [ 0 ]))
2018-10-01 00:02:08 +08:00
2019-09-20 19:38:40 +03:00
## Read config
config = configparser . ConfigParser ()
config . read ( FILE_CONFIG )
2020-02-11 21:22:18 +02:00
# configDB = config['DATABASE']
# DB_SERVER = configDB.get('db_server', 'localhost')
# DB_PORT = configDB.getint('db_port', 27017)
# DB_SSL = configDB.getboolean('db_ssl', False)
# DB_CERT_REQ = configDB.getint('db_ssl_req', ssl.CERT_NONE)
# DB_AUTH = configDB.get('db_auth', 'admin')
# DB_NAME = configDB.get('db_name', 'BlitzStats')
# DB_USER = configDB.get('db_user', 'mongouser')
# DB_PASSWD = configDB.get('db_password', "PASSWORD")
# DB_CERT = configDB.get('db_ssl_cert_file', None)
# DB_CA = configDB.get('db_ssl_ca_file', None)
2019-11-23 01:44:21 +02:00
2020-02-11 21:22:18 +02:00
# bu.debug('DB_SERVER: ' + DB_SERVER)
# bu.debug('DB_PORT: ' + str(DB_PORT))
# bu.debug('DB_SSL: ' + "True" if DB_SSL else "False")
# bu.debug('DB_AUTH: ' + DB_AUTH)
# bu.debug('DB_NAME: ' + DB_NAME)
2019-09-09 23:30:58 +03:00
2019-09-20 19:38:40 +03:00
configGeneral = config [ 'GENERAL' ]
# WG account id of the uploader:
# # Find it here: https://developers.wargaming.net/reference/all/wotb/account/list/
WG_ID = configGeneral . getint ( 'wg_id' , 0 )
2020-02-11 21:22:18 +02:00
# USE_DB = configGeneral.getint('use_DB', False)
2019-09-09 23:30:58 +03:00
2018-10-01 00:02:08 +08:00
parser = argparse . ArgumentParser ( description = 'Post replays(s) to WoTinspector.com and retrieve battle data' )
2020-02-08 02:43:49 +02:00
#parser.add_argument('--output', default='file', choices=['file', 'files', 'db'] , help='Select output mode: single/multiple files or database')
2018-10-01 00:02:08 +08:00
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' )
2019-08-16 22:53:06 +03:00
parser . add_argument ( '--tasks' , dest = 'N_tasks' , type = int , default = 5 , 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"' )
2020-02-11 21:22:18 +02:00
# parser.add_argument('--db', action='store_true', default=USE_DB, help='Use DB - You are unlikely to have it')
2018-10-01 00:02:08 +08:00
parser . add_argument ( '-d' , '--debug' , action = 'store_true' , default = False , help = 'Debug mode' )
2019-12-24 12:30:09 +02:00
parser . add_argument ( '-v' , '--verbose' , action = 'store_true' , default = False , 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 ()
2019-12-24 02:52:56 +02:00
bu . set_verbose ( args . verbose )
2019-12-24 12:30:09 +02:00
bu . set_log_level ( args . silent , args . verbose , args . debug )
2018-10-17 11:02:39 +08:00
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
2019-12-24 02:52:56 +02:00
#### Connect to MongoDB (TBD)
2020-02-11 21:22:18 +02:00
# client = None
# db = None
# if args.db:
# try:
# client = motor.motor_asyncio.AsyncIOMotorClient(DB_SERVER,DB_PORT, authSource=DB_AUTH, username=DB_USER, password=DB_PASSWD, ssl=DB_SSL, ssl_cert_reqs=DB_CERT_REQ, ssl_certfile=DB_CERT, tlsCAFile=DB_CA)
# db = client[DB_NAME]
# bu.debug('Database connection initiated')
# except Exception as err:
# bu.error("Could no initiate DB connection: Disabling DB", err)
# args.db = False
# pass
# else:
# bu.debug('No DB in use')
2019-12-18 00:19:21 +02:00
2018-10-01 21:35:49 +08:00
if args . account != None :
2019-12-24 02:52:56 +02:00
args . accountID = await wg . get_account_id ( 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 ):
2020-02-11 21:22:18 +02:00
#tasks.append(asyncio.create_task(replayWorker(queue, db, i, args.accountID, args.private)))
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 )
2019-08-16 22:53:06 +03:00
bu . verbose ( str ( REPLAY_N ) + ' replays: ' + str ( REPLAY_N - SKIPPED_N - ERROR_N ) + ' uploaded, ' + str ( SKIPPED_N ) + ' skipped, ' + str ( ERROR_N ) + ' errors' )
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 )]
2020-02-11 21:22:18 +02:00
#async def replayWorker(queue: asyncio.Queue, db: motor.motor_asyncio.AsyncIOMotorDatabase, workerID: int, account_id: int, priv = False):
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
2019-08-16 22:53:06 +03:00
global ERROR_N
2018-10-02 14:43:06 +08:00
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':
2019-12-24 02:52:56 +02:00
if wi . chk_JSON_replay ( replay_json ):
2018-11-05 14:48:07 +02:00
bu . verbose ( msg_str + title + ' has already been posted. Skipping.' )
SKIPPED_N += 1
2020-02-11 21:22:18 +02:00
#await saveReplay2DB(db, replay_json)
2018-11-05 14:48:07 +02:00
queue . task_done ()
continue
2019-08-16 22:53:06 +03:00
else :
os . remove ( replay_json_fn )
bu . debug ( "Replay JSON not valid: Deleting " + replay_json_fn )
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 )
2019-12-24 02:52:56 +02:00
json_resp = await wi . post_replay ( await fp . read (), filename , account_id , title , priv , N )
2018-11-05 14:48:07 +02:00
if json_resp != None :
2019-12-24 02:52:56 +02:00
if ( await bu . save_JSON ( replay_json_fn , json_resp )):
2018-11-05 14:48:07 +02:00
bu . debug ( msg_str + 'Replay saved OK: ' + filename )
else :
bu . error ( msg_str + 'Error saving replay: ' + filename )
2019-08-16 22:53:06 +03:00
ERROR_N += 1
2020-02-11 21:22:18 +02:00
# await saveReplay2DB(db, json_resp)
2018-11-05 14:48:07 +02:00
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
2019-09-20 19:38:40 +03:00
2020-02-11 21:22:18 +02:00
# async def saveReplay2DB(db: motor.motor_asyncio.AsyncIOMotorDatabase, replay: dict) -> bool:
# """Save retriever replay JSON into DB (mongo)"""
# try:
# if db == None:
# return False
# dbc = db[DB_C_REPLAYS]
# replay_link = replay['data']['download_url']
# replay_id = wi.get_replay_id(replay_link)
# replay['_id'] = replay_id
# await dbc.insert_one(replay)
# bu.debug('Replay added to database')
# except pymongo.errors.DuplicateKeyError as err:
# bu.verbose_std('Replay already in the DB: ' + replay['data']['summary']['title'] + ' : id=' + replay_id)
# return False
# except Exception as err:
# bu.error('Unexpected Exception: ' + str(type(err)) + ' : ' + str(err))
# return False
# return True
2019-09-20 19:38:40 +03:00
2018-10-01 00:02:08 +08:00
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 )
2019-12-24 02:52:56 +02:00
map_usrStrs = wg . get_map_user_strs ()
tank_userStrs = wg . get_tank_user_strs ()
2019-08-16 22:53:06 +03:00
#p = re.compile('\\d{8}_\\d{4}_(.+)_(' + '|'.join(map_usrStrs) + ')(?:-\\d)?\\.wotbreplay$')
2019-09-09 23:30:58 +03:00
# update 6.2 changed the file name format. Bug fixed 2019-09-09 Jylpah
p = re . compile ( ' \\ d {8} _ \\ d {4} _.*?(' + '|' . join ( tank_userStrs ) + ')_(' + '|' . join ( map_usrStrs ) + ')(?:- \\ d)? \\ .wotbreplay$' )
2019-08-16 22:53:06 +03:00
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__" :
2019-11-23 01:44:21 +02:00
asyncio . run ( main ( sys . argv [ 1 :]), debug = False )