mirror of
https://github.com/FAUSheppy/open-web-leaderboard.git
synced 2025-12-06 15:11:35 +01:00
Compare commits
12 Commits
ese-custom
...
6491afc272
| Author | SHA1 | Date | |
|---|---|---|---|
| 6491afc272 | |||
| 756b24a447 | |||
| 5f7713daaf | |||
| ee14c3fd7e | |||
|
|
c32155fd40 | ||
|
|
defcf5671d | ||
| 2133249947 | |||
| db3d2bb57e | |||
| c5b7963fff | |||
| 23fa7f9862 | |||
| 8f7b1b47ac | |||
| bdd7d9bf01 |
34
.github/workflows/main.yaml
vendored
Normal file
34
.github/workflows/main.yaml
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
name: Container Build for open-web-leaderboard
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "master"
|
||||||
|
schedule:
|
||||||
|
- cron: "0 2 * * 0"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment:
|
||||||
|
name: prod
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
-
|
||||||
|
name: Login to Docker Registry
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ${{ secrets.REGISTRY }}
|
||||||
|
username: ${{ secrets.REGISTRY_USER }}
|
||||||
|
password: ${{ secrets.REGISTRY_PASS }}
|
||||||
|
-
|
||||||
|
name: open-web-leaderboard
|
||||||
|
uses: docker/build-push-action@v3
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: "${{ secrets.REGISTRY }}/atlantishq/open-web-leaderboard:latest"
|
||||||
13
Dockerfile
Normal file
13
Dockerfile
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
FROM alpine
|
||||||
|
|
||||||
|
RUN apk add --no-cache py3-pip
|
||||||
|
RUN python3 -m pip install --no-cache-dir --break-system-packages waitress
|
||||||
|
COPY req.txt .
|
||||||
|
RUN python3 -m pip install --no-cache-dir --break-system-packages -r req.txt
|
||||||
|
|
||||||
|
RUN mkdir /app
|
||||||
|
WORKDIR /app
|
||||||
|
COPY ./ .
|
||||||
|
|
||||||
|
ENTRYPOINT ["waitress-serve"]
|
||||||
|
CMD ["--host", "0.0.0.0", "--port", "5000", "--call", "app:createApp"]
|
||||||
@@ -33,6 +33,7 @@ Players can be blacklisted by name via a *blacklist.json* file in the project ro
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Adding servers for player count live info
|
# Adding servers for player count live info
|
||||||
|
**THIS FEATURE IS DISABLED BECAUSE py-valve DOES NOT SUPPORT PYTHON>3.9**
|
||||||
Source-Servers can be added via the *servers.json*-file:
|
Source-Servers can be added via the *servers.json*-file:
|
||||||
|
|
||||||
[
|
[
|
||||||
|
|||||||
2
app.py
2
app.py
@@ -1,3 +1,5 @@
|
|||||||
import server
|
import server
|
||||||
def createApp(envivorment=None, start_response=None):
|
def createApp(envivorment=None, start_response=None):
|
||||||
|
with server.app.app_context():
|
||||||
|
server.create_app()
|
||||||
return server.app
|
return server.app
|
||||||
|
|||||||
93
medals.py
Normal file
93
medals.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import flask
|
||||||
|
|
||||||
|
medalDict = { "games-played-1" : { "name" : "Tourist",
|
||||||
|
"text" : "Played {} games on this server",
|
||||||
|
"color" : "white",
|
||||||
|
"text-color" : "black" },
|
||||||
|
|
||||||
|
"games-played-2" : { "name" : "Enlisted",
|
||||||
|
"text" : "Played {} games on this server",
|
||||||
|
"color": "green",
|
||||||
|
"text-color" : "white" },
|
||||||
|
|
||||||
|
"games-played-3" : { "name" : "Veteran",
|
||||||
|
"text" : "Played {} games on this server",
|
||||||
|
"color" : "yellow",
|
||||||
|
"text-color" : "black" },
|
||||||
|
|
||||||
|
"rating-cur-1" : { "name" : "Slightly skilled",
|
||||||
|
"text" : "Rated above 1500",
|
||||||
|
"color" : "beige",
|
||||||
|
"text-color" : "black" },
|
||||||
|
|
||||||
|
"rating-2k-1" : { "name" : "Contender",
|
||||||
|
"text" : "Played {} games above 2000 rating",
|
||||||
|
"color" : "coral",
|
||||||
|
"text-color" : "black" },
|
||||||
|
"rating-2k-2" : { "name" : "Known Contender",
|
||||||
|
"text" : "Played {} games above 2000 rating",
|
||||||
|
"color" : "lightgreen",
|
||||||
|
"text-color" : "black" },
|
||||||
|
|
||||||
|
"rating-3k-1" : { "name" : "Epic",
|
||||||
|
"text" : "Played {} games above 3000 rating",
|
||||||
|
"color" : "lightblue",
|
||||||
|
"text-color" : "black" },
|
||||||
|
"rating-3k-2" : { "name" : "Legend",
|
||||||
|
"text" : "Played {} games above 3000 rating",
|
||||||
|
"color" : "orange",
|
||||||
|
"text-color" : "black" },
|
||||||
|
"rating-3k-3" : { "name" : "All Along The Watchtower",
|
||||||
|
"text" : "Played {} games above 3000 rating",
|
||||||
|
"color" : "red",
|
||||||
|
"text-color" : "black" },
|
||||||
|
}
|
||||||
|
|
||||||
|
def medalGen(medal, formatInsert=None):
|
||||||
|
'''Gen HTML for metal'''
|
||||||
|
html = '\
|
||||||
|
<div style="background-color: {bg} !important; color: {color} !important;"\
|
||||||
|
class="btn btn-secondary"\
|
||||||
|
data-toggle="tooltip" data-placement="top" title="{tooltip}">\
|
||||||
|
{text}\
|
||||||
|
</div>\
|
||||||
|
'
|
||||||
|
tmp = html.format(bg=medal["color"], tooltip=medal["text"], text=medal["name"],
|
||||||
|
color=medal["text-color"])
|
||||||
|
if formatInsert:
|
||||||
|
tmp = tmp.format(formatInsert)
|
||||||
|
return flask.Markup(tmp)
|
||||||
|
|
||||||
|
def getMedals(ratingList, gamesPlayed, currentRating):
|
||||||
|
'''Get Medals this player should have'''
|
||||||
|
medals = []
|
||||||
|
|
||||||
|
if gamesPlayed > 500:
|
||||||
|
medals += [medalGen(medalDict["games-played-3"], gamesPlayed)]
|
||||||
|
elif gamesPlayed > 100:
|
||||||
|
medals += [medalGen(medalDict["games-played-2"], gamesPlayed)]
|
||||||
|
elif gamesPlayed > 50:
|
||||||
|
medals += [medalGen(medalDict["games-played-1"], gamesPlayed)]
|
||||||
|
|
||||||
|
games2k = len(list(filter(lambda x: x > 2000, ratingList)))
|
||||||
|
games3k = len(list(filter(lambda x: x > 3000, ratingList)))
|
||||||
|
|
||||||
|
if games2k > 100:
|
||||||
|
medals += [medalGen(medalDict["rating-2k-2"], games2k)]
|
||||||
|
elif games2k > 0:
|
||||||
|
medals += [medalGen(medalDict["rating-2k-1"], games2k)]
|
||||||
|
|
||||||
|
if games3k > 200:
|
||||||
|
medals += [medalGen(medalDict["rating-3k-3"], games3k)]
|
||||||
|
|
||||||
|
if games3k > 50:
|
||||||
|
medals += [medalGen(medalDict["rating-3k-2"], games3k)]
|
||||||
|
elif games3k > 5:
|
||||||
|
medals += [medalGen(medalDict["rating-3k-1"], games3k)]
|
||||||
|
|
||||||
|
if currentRating > 1500:
|
||||||
|
medals += [medalGen(medalDict["rating-cur-1"])]
|
||||||
|
|
||||||
|
return medals
|
||||||
|
|
||||||
|
|
||||||
72
server.py
72
server.py
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
|
import medals
|
||||||
import flask
|
import flask
|
||||||
import requests
|
import requests
|
||||||
import argparse
|
import argparse
|
||||||
@@ -9,6 +10,8 @@ import os
|
|||||||
import MapSummary
|
import MapSummary
|
||||||
|
|
||||||
from database import DatabaseConnection
|
from database import DatabaseConnection
|
||||||
|
#import valve.source.a2s
|
||||||
|
#from valve.source import NoResponseError
|
||||||
|
|
||||||
|
|
||||||
app = flask.Flask("open-leaderboard")
|
app = flask.Flask("open-leaderboard")
|
||||||
@@ -39,8 +42,9 @@ def playersOnline():
|
|||||||
|
|
||||||
for s in SERVERS:
|
for s in SERVERS:
|
||||||
try:
|
try:
|
||||||
with valve.source.a2s.ServerQuerier((args.host, args.port)) as server:
|
pass
|
||||||
playerTotal += int(server.info()["player_count"])
|
#with valve.source.a2s.ServerQuerier((s["host"], s["port"])) as server:
|
||||||
|
# playerTotal += int(server.info()["player_count"])
|
||||||
except NoResponseError:
|
except NoResponseError:
|
||||||
error = "Server Unreachable"
|
error = "Server Unreachable"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -157,16 +161,29 @@ def player():
|
|||||||
minRating = 3000
|
minRating = 3000
|
||||||
maxRating = 0
|
maxRating = 0
|
||||||
|
|
||||||
|
# data for medals #
|
||||||
|
medalsRatingList = []
|
||||||
|
|
||||||
if histData:
|
if histData:
|
||||||
datapoints = histData[playerId]
|
datapoints = histData[playerId]
|
||||||
if datapoints:
|
if datapoints:
|
||||||
|
|
||||||
tickCounter = 10
|
tickCounter = 10
|
||||||
for dpk in datapoints.keys():
|
for dpk in datapoints.keys():
|
||||||
|
|
||||||
|
# timestamp #
|
||||||
t = datetime.datetime.fromtimestamp(int(float(dpk)))
|
t = datetime.datetime.fromtimestamp(int(float(dpk)))
|
||||||
tsMs = str(int(t.timestamp() * 1000))
|
tsMs = str(int(t.timestamp() * 1000))
|
||||||
ratingString = str(int(datapoints[dpk]["mu"]) - 2*int(datapoints[dpk]["sigma"]))
|
|
||||||
|
computedRating = int(datapoints[dpk]["mu"]) - 2*int(datapoints[dpk]["sigma"])
|
||||||
|
|
||||||
|
# for medals #
|
||||||
|
medalsRatingList += [computedRating]
|
||||||
|
|
||||||
|
# for moment js #
|
||||||
|
ratingString = str(computedRating)
|
||||||
ratingAmored = '{ x : ' + tsMs + ', y : ' + ratingString + '}'
|
ratingAmored = '{ x : ' + tsMs + ', y : ' + ratingString + '}'
|
||||||
|
|
||||||
csv_timestamps += [str(tsMs)]
|
csv_timestamps += [str(tsMs)]
|
||||||
csv_ratings += [ratingAmored]
|
csv_ratings += [ratingAmored]
|
||||||
|
|
||||||
@@ -179,6 +196,8 @@ def player():
|
|||||||
maxRating = max(maxRating, int(ratingString))
|
maxRating = max(maxRating, int(ratingString))
|
||||||
|
|
||||||
yMin, yMax = prettifyMinMaxY(minRating, maxRating)
|
yMin, yMax = prettifyMinMaxY(minRating, maxRating)
|
||||||
|
|
||||||
|
medalsList = medals.getMedals(medalsRatingList, player.games, player.rating)
|
||||||
|
|
||||||
# change displayed rank to start from 1 :)
|
# change displayed rank to start from 1 :)
|
||||||
player.rank += 1
|
player.rank += 1
|
||||||
@@ -186,7 +205,7 @@ def player():
|
|||||||
return flask.render_template("player.html", player=player, CSV_RATINGS=",".join(csv_ratings),
|
return flask.render_template("player.html", player=player, CSV_RATINGS=",".join(csv_ratings),
|
||||||
CSV_MONTH_YEAR_OF_RATINGS=",".join(csv_month_year),
|
CSV_MONTH_YEAR_OF_RATINGS=",".join(csv_month_year),
|
||||||
CSV_TIMESTAMPS=csv_timestamps,
|
CSV_TIMESTAMPS=csv_timestamps,
|
||||||
Y_MIN=yMin, Y_MAX=yMax)
|
Y_MIN=yMin, Y_MAX=yMax, medals=medalsList)
|
||||||
|
|
||||||
@app.route('/leaderboard')
|
@app.route('/leaderboard')
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
@@ -258,11 +277,11 @@ def leaderboard():
|
|||||||
if maxEntry <= 100:
|
if maxEntry <= 100:
|
||||||
start = max(start, 0)
|
start = max(start, 0)
|
||||||
|
|
||||||
finalResponse = flask.render_template("base.html", playerList=playerList, \
|
finalResponse = flask.render_template("base.html", playerList=playerList,
|
||||||
doNotComputeRank=doNotComputeRank, \
|
doNotComputeRank=doNotComputeRank,
|
||||||
start=start, \
|
start=start,
|
||||||
endOfBoardIndicator=endOfBoardIndicator, \
|
endOfBoardIndicator=endOfBoardIndicator,
|
||||||
findPlayer=cannotFindPlayer, \
|
findPlayer=cannotFindPlayer,
|
||||||
searchName=searchName,
|
searchName=searchName,
|
||||||
nextPageNumber=int(pageInt)+1,
|
nextPageNumber=int(pageInt)+1,
|
||||||
prevPageNumber=int(pageInt)-1)
|
prevPageNumber=int(pageInt)-1)
|
||||||
@@ -270,28 +289,35 @@ def leaderboard():
|
|||||||
|
|
||||||
@app.route('/static/<path:path>')
|
@app.route('/static/<path:path>')
|
||||||
def send_js(path):
|
def send_js(path):
|
||||||
return send_from_directory('static', path)
|
|
||||||
|
|
||||||
@app.before_first_request
|
response = send_from_directory('static', path)
|
||||||
def init():
|
response.headers['Cache-Control'] = "max-age=2592000"
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def create_app():
|
||||||
|
|
||||||
|
global SERVERS
|
||||||
|
|
||||||
SERVERS_FILE = "servers.json"
|
SERVERS_FILE = "servers.json"
|
||||||
if os.path.isfile(SERVERS_FILE):
|
if os.path.isfile(SERVERS_FILE):
|
||||||
import valve.source.a2s
|
|
||||||
from valve.source import NoResponseError
|
|
||||||
with open(SERVERS_FILE) as f:
|
with open(SERVERS_FILE) as f:
|
||||||
SERVERS = json.load(f)
|
SERVERS = json.load(f)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser(description='Start open-leaderboard', \
|
|
||||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
|
||||||
parser.add_argument('--interface', default="localhost", \
|
|
||||||
help='Interface on which flask (this server) will take requests on')
|
|
||||||
parser.add_argument('--port', default="5002", \
|
|
||||||
help='Port on which flask (this server) will take requests on')
|
|
||||||
|
|
||||||
parser.add_argument('--skillbird-db', required=True, help='skillbird database (overrides web connection if set)')
|
parser = argparse.ArgumentParser(description='Start open-leaderboard',
|
||||||
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||||
|
parser.add_argument('--interface', default="localhost",
|
||||||
|
help='Interface on which flask (this server) will take requests on')
|
||||||
|
parser.add_argument('--port', default="5002",
|
||||||
|
help='Port on which flask (this server) will take requests on')
|
||||||
|
parser.add_argument('--skillbird-db', required=True,
|
||||||
|
help='skillbird database (overrides web connection if set)')
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
|
create_app()
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
app.config["DB_PATH"] = args.skillbird_db
|
app.config["DB_PATH"] = args.skillbird_db
|
||||||
app.run(host=args.interface, port=args.port)
|
app.run(host=args.interface, port=args.port)
|
||||||
|
|||||||
@@ -64,14 +64,14 @@ body{
|
|||||||
font-size: 5vw;
|
font-size: 5vw;
|
||||||
margin-right: 2.5%;
|
margin-right: 2.5%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-field{
|
.input-field{
|
||||||
margin-top: 2vw;
|
margin-top: 2vw;
|
||||||
float: left;
|
float: left;
|
||||||
font-size: 6vw;
|
font-size: 6vw;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-field-number{
|
.input-field-number{
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,7 @@ body{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ############# PLAYER INFORMATION IN LINES ############# */
|
/* ############# PLAYER INFORMATION IN LINES ############# */
|
||||||
.playerRank{
|
.playerRank{
|
||||||
margin-left:1%;
|
margin-left:1%;
|
||||||
float: left;
|
float: left;
|
||||||
@@ -143,7 +143,7 @@ body{
|
|||||||
}
|
}
|
||||||
|
|
||||||
.line-odd{
|
.line-odd{
|
||||||
width: 100%;
|
width: 100%;
|
||||||
color: black;
|
color: black;
|
||||||
background-color: lightgrey !important;
|
background-color: lightgrey !important;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -167,7 +167,7 @@ body{
|
|||||||
font-size: 4.5vw;
|
font-size: 4.5vw;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.colum-names{
|
.colum-names{
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -199,14 +199,14 @@ body{
|
|||||||
width: 15%;
|
width: 15%;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.playerWinratio{
|
.playerWinratio{
|
||||||
/* 19% is just enought to cut the last letter */
|
/* 19% is just enought to cut the last letter */
|
||||||
width: 19%;
|
width: 19%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ######################## FOOTER ####################### */
|
/* ######################## FOOTER ####################### */
|
||||||
.footer{
|
.footer{
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -217,7 +217,6 @@ body{
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.footerLink{
|
.footerLink{
|
||||||
@@ -241,7 +240,6 @@ body{
|
|||||||
.footerLink{
|
.footerLink{
|
||||||
font-size: 4vw;
|
font-size: 4vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mid{
|
.mid{
|
||||||
margin-left: 2.5%;
|
margin-left: 2.5%;
|
||||||
margin-right: 2.5%;
|
margin-right: 2.5%;
|
||||||
@@ -258,3 +256,11 @@ body{
|
|||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0.3; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-flicker {
|
||||||
|
animation: fadeIn 2s alternate;
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,12 +13,16 @@
|
|||||||
<div id="playerDisplay" class="playerDisplay mb-3 mt-2">
|
<div id="playerDisplay" class="playerDisplay mb-3 mt-2">
|
||||||
<script>
|
<script>
|
||||||
function players(){
|
function players(){
|
||||||
|
//document.getElementById("playerDisplay").classList.remove("animate-flicker")
|
||||||
fetch("/players-online").then(
|
fetch("/players-online").then(
|
||||||
response => response.json()
|
response => response.json()
|
||||||
).then(
|
).then(
|
||||||
data => {
|
data => {
|
||||||
if(data["error"] == ""){
|
if(data["error"] == ""){
|
||||||
document.getElementById("playerDisplay").innerHTML = "Players Online: " + data["player_total"]
|
document.getElementById("playerDisplay").innerHTML = "Players Online: " + data["player_total"]
|
||||||
|
if(parseInt(data["player_total"]) == 0){
|
||||||
|
//document.getElementById("playerDisplay").classList.add("animate-flicker")
|
||||||
|
}
|
||||||
}else{
|
}else{
|
||||||
document.getElementById("playerDisplay").innerHTML = "Players Online: (error)" + data["error"]
|
document.getElementById("playerDisplay").innerHTML = "Players Online: (error)" + data["error"]
|
||||||
}
|
}
|
||||||
@@ -26,7 +30,7 @@
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
players()
|
players()
|
||||||
setInterval(players, 2000)
|
setInterval(players, 10000)
|
||||||
</script>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
<table id="tableMain" class="table table-striped table-bordered table-sm"
|
<table id="tableMain" class="table table-striped table-bordered table-sm"
|
||||||
|
|||||||
@@ -27,10 +27,15 @@
|
|||||||
<canvas id="lineChart">
|
<canvas id="lineChart">
|
||||||
</canvas>
|
</canvas>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mt-3 medal-container">
|
||||||
|
{% for m in medals %}
|
||||||
|
{{ m }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
<p class="mt-5 mb-3">
|
<p class="mt-5 mb-3">
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{% include 'footer.html' %}
|
<!-- {% include 'footer.html' %}-->
|
||||||
<script defer>
|
<script defer>
|
||||||
var canvas = document.getElementById("lineChart")
|
var canvas = document.getElementById("lineChart")
|
||||||
var ctx = canvas.getContext('2d');
|
var ctx = canvas.getContext('2d');
|
||||||
|
|||||||
Reference in New Issue
Block a user