323 lines
9.3 KiB
Python
Executable File
323 lines
9.3 KiB
Python
Executable File
#!/usr/bin/python3
|
|
|
|
from awpy.parser import DemoParser
|
|
from awpy.analytics.stats import player_stats
|
|
import sqlite3, os, random, time, glob
|
|
|
|
from flask import Flask, flash, request, redirect, url_for, send_from_directory, render_template, make_response
|
|
from werkzeug.utils import secure_filename
|
|
|
|
from io import StringIO
|
|
import csv
|
|
|
|
UPLOAD_FOLDER = './demos'
|
|
ALLOWED_EXTENSIONS = {'dem'}
|
|
app = Flask(__name__)
|
|
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
|
|
colors = [ '#2488bf', '#d84d3d', '#f39700', '#4caf50' ]
|
|
db = "csgo.db"
|
|
|
|
def demo_parse(demofile, db):
|
|
print(f"[Parser] Analysing demo {demofile}")
|
|
parser = DemoParser(
|
|
demofile = demofile,
|
|
parse_rate=128,
|
|
trade_time=5,
|
|
buy_style="hltv",
|
|
parse_frames=False
|
|
)
|
|
|
|
parser.parse()
|
|
data = parser.clean_rounds(remove_no_frames=True,
|
|
remove_warmups=True,
|
|
remove_knifes=True,
|
|
remove_excess_players=True)
|
|
stats = player_stats(data["gameRounds"])
|
|
ranks = data["matchmakingRanks"]
|
|
rounds = data["gameRounds"]
|
|
|
|
matchid = data['matchID']
|
|
mapname = data['mapName']
|
|
|
|
if os.path.exists(f"{matchid}.json"):
|
|
os.remove(f"{matchid}.json")
|
|
print(f"[GC] {matchid}.json have been sucessfully deleted")
|
|
|
|
con = sqlite3.connect(db)
|
|
c = con.cursor()
|
|
|
|
tables = c.execute('''SELECT name FROM sqlite_master WHERE type='table' AND name='matches';''').fetchall()
|
|
if tables == []:
|
|
c.execute('CREATE TABLE matches \
|
|
( \
|
|
matchid text, \
|
|
map text, \
|
|
accuracy real, \
|
|
adr real, \
|
|
assists integer, \
|
|
blindTime real, \
|
|
deaths integer, \
|
|
defuses integer, \
|
|
enemiesFlashed integer, \
|
|
fireThrown interger, \
|
|
firstDeaths interger, \
|
|
firstKills integer, \
|
|
flashAssists integer, \
|
|
flashesThrown integer, \
|
|
heThrown integer, \
|
|
hs integer, \
|
|
hsPercent real, \
|
|
kast real, \
|
|
kdr real, \
|
|
kills integer, \
|
|
plants integer, \
|
|
playerName text, \
|
|
rating real, \
|
|
shotsHit integer, \
|
|
smokesThrown integer, \
|
|
steamID text, \
|
|
suicides integer, \
|
|
teamKills integer, \
|
|
teamName text, \
|
|
teammatesFlashed integer, \
|
|
totalDamageGiven integer, \
|
|
totalDamageTaken integer, \
|
|
totalRounds integer, \
|
|
totalShots integer, \
|
|
totalTeamDamageGiven integer, \
|
|
tradeKills integer, \
|
|
utilityDamage integer, \
|
|
tscore integer, \
|
|
ctscore integer, \
|
|
side text \
|
|
)')
|
|
|
|
for k, v in stats.items():
|
|
for i in rounds[-1]['ctSide']['players']:
|
|
if i['steamID'] == v["steamID"]:
|
|
side = 'CT'
|
|
for i in rounds[-1]['tSide']['players']:
|
|
if i['steamID'] == v["steamID"]:
|
|
side = 'T'
|
|
if not c.execute(f'SELECT * from matches WHERE matchid LIKE "{matchid}" and steamID LIKE {v["steamID"]}').fetchall():
|
|
c.execute("INSERT INTO matches VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", \
|
|
[
|
|
matchid, \
|
|
mapname, \
|
|
v["accuracy"], \
|
|
v["adr"], \
|
|
v["assists"], \
|
|
v["blindTime"], \
|
|
v["deaths"], \
|
|
v["defuses"], \
|
|
v["enemiesFlashed"], \
|
|
v["fireThrown"], \
|
|
v["firstDeaths"], \
|
|
v["firstKills"], \
|
|
v["flashAssists"], \
|
|
v["flashesThrown"], \
|
|
v["heThrown"], \
|
|
v["hs"], \
|
|
v["hsPercent"], \
|
|
v["kast"], \
|
|
v["kdr"], \
|
|
v["kills"], \
|
|
v["plants"], \
|
|
v["playerName"], \
|
|
v["rating"], \
|
|
v["shotsHit"], \
|
|
v["smokesThrown"], \
|
|
v["steamID"], \
|
|
v["suicides"], \
|
|
v["teamKills"], \
|
|
v["teamName"], \
|
|
v["teammatesFlashed"], \
|
|
v["totalDamageGiven"], \
|
|
v["totalDamageTaken"], \
|
|
v["totalRounds"], \
|
|
v["totalShots"], \
|
|
v["totalTeamDamageGiven"], \
|
|
v["tradeKills"], \
|
|
v["utilityDamage"], \
|
|
rounds[-1]['endTScore'], \
|
|
rounds[-1]['endCTScore'], \
|
|
side
|
|
])
|
|
|
|
con.commit()
|
|
return (0)
|
|
|
|
@app.route('/<demo>')
|
|
def demo_analyze(demo):
|
|
|
|
dempath = "demos"
|
|
now = time.time()
|
|
for file in os.listdir(dempath):
|
|
if os.path.getmtime(os.path.join(dempath, file)) < now - 30 * 86400:
|
|
os.remove(os.path.join(dempath, file))
|
|
print(f"[GC] Removed old demo {file}")
|
|
|
|
con = sqlite3.connect(db)
|
|
c = con.cursor()
|
|
|
|
if not c.execute('''SELECT name FROM sqlite_master WHERE type='table' AND name='matches';''').fetchall():
|
|
demo_parse(f"demos/{demo}.dem", db)
|
|
if not c.execute(f'SELECT * FROM matches WHERE matchid LIKE "{demo}"').fetchall():
|
|
demo_parse(f"demos/{demo}.dem", db)
|
|
|
|
if not c.execute('''SELECT name FROM sqlite_master WHERE type='table' AND name='matches';''').fetchall():
|
|
flash('Error analysing demo')
|
|
return redirect("/")
|
|
|
|
c.execute(f'SELECT * FROM matches WHERE matchid LIKE "{demo}"')
|
|
stats = c.fetchall()
|
|
con.close()
|
|
|
|
team1 = []
|
|
team2 = []
|
|
for i in range(len(stats)):
|
|
if i < 5:
|
|
team1.append(stats[i])
|
|
else:
|
|
team2.append(stats[i])
|
|
|
|
if team1[0][37] == team1[0][38]:
|
|
winner = "Draw"
|
|
elif team1[0][37] > team1[0][38] and team1[0][39] == 'T':
|
|
winner = team1[0][28] if team1[0][28] else "Team1"
|
|
else:
|
|
winner = team2[0][28] if team2[0][28] else "Team2"
|
|
return (render_template('demo.html', \
|
|
team1=sorted(team1, key=lambda tup: tup[22], reverse=True), \
|
|
team2=sorted(team2, key=lambda tup: tup[22], reverse=True), \
|
|
winner=winner, color=random.choice(colors)))
|
|
|
|
@app.route('/<demo>/csv')
|
|
def download_csv(demo):
|
|
con = sqlite3.connect(db)
|
|
c = con.cursor()
|
|
try:
|
|
c.execute(f'SELECT * FROM matches WHERE matchid LIKE "{demo}"')
|
|
stats = c.fetchall()
|
|
con.close()
|
|
except:
|
|
return(f"Couldn't fetch stats for the demo {demo}")
|
|
|
|
si = StringIO()
|
|
cw = csv.writer(si)
|
|
cw.writerow(['Player',
|
|
'Team',
|
|
'Rating',
|
|
'Kast',
|
|
'HS (%)',
|
|
'ADR',
|
|
'Accuracy (%)',
|
|
'Open Attempts',
|
|
'Open Success',
|
|
'Utility damages',
|
|
'Flash used',
|
|
'Average flash time',
|
|
'Trade kills'])
|
|
|
|
for i in range(len(stats)):
|
|
cw.writerow([stats[i][21],
|
|
stats[i][28],
|
|
stats[i][22],
|
|
stats[i][17],
|
|
stats[i][16]*100,
|
|
stats[i][3],
|
|
stats[i][2]*100,
|
|
stats[i][10] + stats[i][11],
|
|
stats[i][11],
|
|
stats[i][36],
|
|
stats[i][14],
|
|
round(stats[i][5] / stats[i][8], 2),
|
|
stats[i][35]])
|
|
|
|
file = demo + ".csv"
|
|
output = make_response(si.getvalue())
|
|
output.headers["Content-Disposition"] = f"attachment; filename={demo}.csv"
|
|
output.headers["Content-type"] = "text/csv"
|
|
return output
|
|
|
|
@app.route('/<demo>/download')
|
|
def download_file(demo):
|
|
file = demo + ".dem"
|
|
return send_from_directory(app.config["UPLOAD_FOLDER"], file)
|
|
|
|
@app.route('/player/<player>')
|
|
def get_player(player):
|
|
con = sqlite3.connect(db)
|
|
c = con.cursor()
|
|
if not c.execute('''SELECT name FROM sqlite_master WHERE type='table' AND name='matches';''').fetchall():
|
|
flash('Error analysing demo')
|
|
return redirect("/")
|
|
|
|
c.execute(f'SELECT * FROM matches WHERE playerName LIKE "{player}" OR steamID LIKE "{player}"')
|
|
stats = c.fetchall()
|
|
con.close()
|
|
stlen = len(stats)
|
|
avg = list(stats[0])
|
|
for stat in stats[1:]:
|
|
for i in range(len(avg)):
|
|
if isinstance(stat[i], int) or isinstance(stat[i], float):
|
|
avg[i] += stat[i]
|
|
else:
|
|
avg[i] = stat[i]
|
|
for i in range(len(avg)):
|
|
if isinstance(avg[i], int) or isinstance(avg[i], float):
|
|
avg[i] /= stlen
|
|
if isinstance(avg[i], float):
|
|
avg[i] = round(avg[i], 2)
|
|
|
|
if avg[37] == avg[38]:
|
|
winner = 'D'
|
|
elif avg[37] > avg[38] and avg[39] == 'T':
|
|
winner = 'L'
|
|
else:
|
|
winner = 'V'
|
|
|
|
if avg[22] > 1.1:
|
|
rating = "#4caf50"
|
|
elif avg[22] > 0.9:
|
|
rating = "#f39700"
|
|
else:
|
|
rating = "#d84d3d"
|
|
|
|
return (render_template('player.html', \
|
|
player=avg, \
|
|
matches=stats, \
|
|
winner=winner, \
|
|
rating=rating))
|
|
|
|
|
|
def allowed_file(filename):
|
|
return '.' in filename and \
|
|
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
|
|
|
@app.route('/', methods=['GET', 'POST'])
|
|
def upload_file():
|
|
if request.method == 'POST':
|
|
if 'file' not in request.files:
|
|
flash('No file part')
|
|
return redirect(request.url)
|
|
|
|
file = request.files['file']
|
|
if file.filename == '':
|
|
flash('No selected file')
|
|
return redirect(request.url)
|
|
|
|
if file and allowed_file(file.filename):
|
|
filename = secure_filename(file.filename)
|
|
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
|
|
return redirect(url_for('demo_analyze', demo=filename.split('.')[0]))
|
|
|
|
files = list(filter(os.path.isfile, glob.glob("./demos/*.dem")))
|
|
files.sort(key=lambda x: os.path.getmtime(x))
|
|
demid = [x.split('/')[2][:-4] for x in files]
|
|
return (render_template('upload.html', demos=demid))
|
|
|
|
@app.route('/favicon.ico')
|
|
def favicon():
|
|
return ("Not found"), 404
|