mv periodic back to cron, add winrate graphs
This commit is contained in:
parent
5d81c4fbe4
commit
73f7f9e567
1
.gitignore
vendored
1
.gitignore
vendored
@ -12,3 +12,4 @@ venv/
|
||||
*.db
|
||||
*.swp
|
||||
clientlist.txt
|
||||
*.wsgi
|
||||
|
24
app.py
24
app.py
@ -1,24 +0,0 @@
|
||||
#!venv/bin/python
|
||||
from flask import Flask
|
||||
from flask.ext.script import Manager, Server
|
||||
from flask.ext.migrate import Migrate, MigrateCommand
|
||||
|
||||
from app import *
|
||||
|
||||
SQLALCHEMY_DATABASE_URI = 'mysql://root:$perwePP@localhost/dotanoobs'
|
||||
|
||||
migrate = Migrate(app, db)
|
||||
manager = Manager(app)
|
||||
manager.add_command('db', MigrateCommand)
|
||||
|
||||
|
||||
@manager.command
|
||||
def admin(name):
|
||||
u = models.User.query.filter_by(nickname=name).first()
|
||||
if u and not u.admin:
|
||||
u.admin = True
|
||||
db.session.commit()
|
||||
print "User {} has been granted admin access.".format(name)
|
||||
|
||||
if __name__ == '__main__':
|
||||
manager.run()
|
@ -10,29 +10,4 @@ db = SQLAlchemy(app)
|
||||
oid = OpenID(app)
|
||||
cache = Cache(app, config={'CACHE_TYPE': app.config['CACHE_TYPE']})
|
||||
|
||||
import ts3
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from teamspeak import idle_mover, store_active_data, \
|
||||
process_ts3_events, award_idle_ts3_points
|
||||
|
||||
def set_voice_server():
|
||||
ts3Server = ts3.TS3Server(app.config['TS3_HOST'], app.config['TS3_PORT'])
|
||||
ts3Server.login(app.config['TS3_USERNAME'], app.config['TS3_PASSWORD'])
|
||||
ts3Server.use(1)
|
||||
return ts3Server
|
||||
|
||||
voice = set_voice_server()
|
||||
|
||||
def refresh_voice_server():
|
||||
app.logger.info("Refreshing TS3 connection...")
|
||||
voice = set_voice_server()
|
||||
|
||||
scheduler = BackgroundScheduler(logger=app.logger)
|
||||
scheduler.add_job(refresh_voice_server, 'interval', hours=6)
|
||||
scheduler.add_job(idle_mover, 'interval', [voice], minutes=30)
|
||||
scheduler.add_job(store_active_data, 'interval', [voice], minutes=30)
|
||||
scheduler.add_job(award_idle_ts3_points, 'interval', [voice], minutes=30)
|
||||
scheduler.add_job(process_ts3_events, 'interval', [voice], hours=1)
|
||||
scheduler.start()
|
||||
|
||||
from app import views, models
|
||||
from app import views
|
||||
|
76
app/analytics.py
Normal file
76
app/analytics.py
Normal file
@ -0,0 +1,76 @@
|
||||
import requests
|
||||
from time import sleep, mktime
|
||||
from bs4 import BeautifulSoup
|
||||
from datetime import datetime
|
||||
|
||||
from app import app, db, models
|
||||
|
||||
MODES_TO_SKIP = ['Ability Draft', 'Greeviling', 'Diretide']
|
||||
|
||||
def collect_match_results(dotabuff_id, num_matches):
|
||||
results = []
|
||||
page = 0
|
||||
while True:
|
||||
page += 1
|
||||
url = "http://dotabuff.com/players/{}/matches/?page={}".format(dotabuff_id, page)
|
||||
data = requests.get(url).text
|
||||
soup = BeautifulSoup(data).article.table.tbody
|
||||
# Catch last page
|
||||
if 'sorry' in soup.tr.td.text.lower():
|
||||
break
|
||||
# Parse the matches on current page
|
||||
for row in soup.find_all('tr'):
|
||||
# Pass over bot matches and other 'inactive' games
|
||||
if 'inactive' in row.get('class', ''): continue
|
||||
cells = row.find_all('td')
|
||||
result_cell = cells[2]
|
||||
match_cell = cells[3]
|
||||
match_id = int(result_cell.a['href'].split('/')[-1])
|
||||
match_type = match_cell.div.text
|
||||
if match_type in MODES_TO_SKIP: continue
|
||||
result = True if 'won' in result_cell.a['class'] else False
|
||||
dt = datetime.strptime(result_cell.time['datetime'], '%Y-%m-%dT%H:%M:%S+00:00')
|
||||
results.append({'match_id':match_id, 'win':result, 'datetime':dt, 'game_mode':match_type})
|
||||
if len(results) > num_matches:
|
||||
break
|
||||
if len(results) > num_matches:
|
||||
break
|
||||
sleep(60)
|
||||
results.reverse()
|
||||
return results
|
||||
|
||||
def apply_window(results, window_size=50):
|
||||
windows = []
|
||||
# Compute the initial window
|
||||
win_rate = 0.00
|
||||
for idx in range(0, window_size):
|
||||
win_rate += 1 if results[idx]['win'] else 0
|
||||
win_rate /= window_size
|
||||
windows.append(win_rate)
|
||||
# From here on, modify based on leave/enter data points
|
||||
fractional_change = 1. / window_size
|
||||
for idx in range(window_size, len(results)):
|
||||
if results[idx-window_size]['win'] == results[idx]['win']:
|
||||
pass
|
||||
elif results[idx]['win']:
|
||||
win_rate += fractional_change
|
||||
else:
|
||||
win_rate -= fractional_change
|
||||
windows.append(win_rate)
|
||||
return windows
|
||||
|
||||
def calculate_winrates():
|
||||
users_analyzed = 0
|
||||
for user in models.User.query.all():
|
||||
db_id = requests.get("http://dotabuff.com/search?q="+user.steam_id).url.split("/")[-1]
|
||||
result = collect_match_results(db_id, app.config['ANALYTICS_WINRATE_NUM_MATCHES'])
|
||||
windowed = apply_window(result, app.config['ANALYTICS_WINRATE_WINDOW'])
|
||||
date_nums = map(lambda x: mktime(x['datetime'].timetuple()),\
|
||||
result[app.config['ANALYTICS_WINRATE_WINDOW']-1:])
|
||||
winrate = {'total_games': len(result), 'data': zip(date_nums, windowed) }
|
||||
user.winrate_data = winrate
|
||||
db.session.commit()
|
||||
users_analyzed += 1
|
||||
sleep(60)
|
||||
app.logger.info("Calculated win rate numbers for {} doobs.".format(users_analyzed))
|
||||
return users_analyzed
|
@ -81,11 +81,14 @@ class User(db.Model):
|
||||
points_from_events = db.Column(db.Integer)
|
||||
points_from_ts3 = db.Column(db.Integer)
|
||||
points_from_forum = db.Column(db.Integer)
|
||||
|
||||
ts3_starttime = db.Column(db.DateTime)
|
||||
ts3_endtime = db.Column(db.DateTime)
|
||||
ts3_rewardtime = db.Column(db.DateTime)
|
||||
ts3_connections = db.Column(MutableDict.as_mutable(Json))
|
||||
|
||||
last_post_reward = db.Column(db.Integer)
|
||||
winrate_data = db.Column(MutableDict.as_mutable(Json))
|
||||
|
||||
|
||||
@classmethod
|
||||
@ -96,6 +99,7 @@ class User(db.Model):
|
||||
self.steam_id = steam_id
|
||||
self.random_heroes = {'current':None, 'completed':[]}
|
||||
self.az_completions = 0
|
||||
self.ts3_connections = {'list':[]}
|
||||
self.created = datetime.utcnow()
|
||||
self.last_seen = datetime.utcnow()
|
||||
self.bio_text = None
|
||||
@ -161,7 +165,7 @@ class User(db.Model):
|
||||
db.session.commit();
|
||||
|
||||
def finalize_connection(self):
|
||||
self.ts3_connections.append({'starttime': self.ts3_starttime, 'endtime': self.ts3_endtime})
|
||||
self.ts3_connections['list'].append({'starttime': self.ts3_starttime, 'endtime': self.ts3_endtime})
|
||||
self.ts3_startime = None
|
||||
self.ts3_endtime = None
|
||||
db.session.commit();
|
||||
|
@ -95,6 +95,15 @@ footer {
|
||||
float:right;
|
||||
}
|
||||
|
||||
#profile_links > a {
|
||||
font-size:14px;
|
||||
#winrate_graph {
|
||||
margin: 5em;
|
||||
}
|
||||
|
||||
.pipemenu > li {
|
||||
display: inline-block;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.pipemenu > li + li::before {
|
||||
content: " | ";
|
||||
}
|
||||
|
@ -6,12 +6,12 @@ from datetime import datetime, timedelta
|
||||
from xml.etree import ElementTree
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
import models
|
||||
from app import app, db
|
||||
|
||||
def getTeamspeakWindow(window=timedelta(weeks=1)):
|
||||
current_time = datetime.utcnow()
|
||||
from models import TeamspeakData
|
||||
return TeamspeakData.query.filter(TeamspeakData.time < current_time, TeamspeakData.time > current_time-window).order_by(TeamspeakData.time).all()
|
||||
return models.TeamspeakData.query.filter(models.TeamspeakData.time < current_time, models.TeamspeakData.time > current_time-window).order_by(models.TeamspeakData.time).all()
|
||||
|
||||
def registerUserTeamspeakId(user, tsid):
|
||||
server = ts3.TS3Server(app.config['TS3_HOST'], app.config['TS3_PORT'])
|
||||
@ -173,7 +173,7 @@ def create_teamspeak_viewer():
|
||||
return "error: %s" % inst
|
||||
|
||||
def get_ISO3166_mapping():
|
||||
with open('app/static/country_codes.xml', mode='r') as d:
|
||||
with open(path.join(path.dirname(__file__), 'static/country_codes.xml'), mode='r') as d:
|
||||
data = d.read()
|
||||
xml = ElementTree.fromstring(data)
|
||||
d = dict()
|
||||
@ -187,10 +187,10 @@ ISO3166_MAPPING = get_ISO3166_mapping()
|
||||
# Scheduled functions for TeamspeakServer
|
||||
#
|
||||
|
||||
def idle_mover(voice):
|
||||
def idle_mover(server):
|
||||
""" Checks connected clients idle_time, moving to AFK if over TS3_MAX_IDLETIME. """
|
||||
|
||||
app.logger.info("Running TS3 AFK mover...")
|
||||
app.logger.debug("Running TS3 AFK mover...")
|
||||
exempt_cids = []
|
||||
permid_response = server.send_command('permidgetbyname', keys={'permsid': 'i_channel_needed_join_power'})
|
||||
if permid_response.is_successful:
|
||||
@ -220,10 +220,8 @@ def idle_mover(voice):
|
||||
clientlist = server.send_command('clientlist', opts=['times']).data
|
||||
for client in clientlist:
|
||||
clientinfo = server.send_command('clientinfo', {'clid':client['clid']})
|
||||
if clientinfo.is_successful:
|
||||
client['client_unique_identifier'] = clientinfo.data[0]['client_unique_identifier']
|
||||
else:
|
||||
raise UserWarning('Could not find the clientinfo for %s' % client['clid'])
|
||||
#if clientinfo.is_successful:
|
||||
#client['client_unique_identifier'] = clientinfo.data[0]['client_unique_identifier']
|
||||
|
||||
# move idlers to afk channel
|
||||
for client in clientlist:
|
||||
@ -232,10 +230,10 @@ def idle_mover(voice):
|
||||
# Have TeamSpeak move AFK user to appropriate channel
|
||||
server.send_command('clientmove', keys={'clid': client['clid'], 'cid': afk_channel['cid']})
|
||||
|
||||
def store_active_data(voice):
|
||||
def store_active_data(server):
|
||||
""" Take a snapshot of Teamspeak (clients, countries, etc) to feed the ts3_stats page """
|
||||
|
||||
app.logger.info("Taking Teamspeak snapshot...")
|
||||
app.logger.debug("Taking Teamspeak snapshot...")
|
||||
# Get exempt channels (AFK, passworded, join powers)
|
||||
exempt_cids = []
|
||||
permid_response = server.send_command('permidgetbyname', keys={'permsid': 'i_channel_needed_join_power'})
|
||||
@ -276,18 +274,16 @@ def store_active_data(voice):
|
||||
db.session.add(tsdata)
|
||||
db.session.commit()
|
||||
|
||||
def process_ts3_events(voice):
|
||||
def process_ts3_events(server):
|
||||
""" Create Teamspeak channels for upcoming events, delete empty event channels that have expired """
|
||||
|
||||
app.logger.info("Processing Teamspeak events...")
|
||||
app.logger.debug("Processing Teamspeak events...")
|
||||
# Get list of clients
|
||||
clientlist = server.clientlist()
|
||||
for clid, client in clientlist.iteritems():
|
||||
clientinfo = server.send_command('clientinfo', {'clid':clid})
|
||||
if clientinfo.is_successful:
|
||||
client['client_unique_identifier'] = clientinfo.data[0]['client_unique_identifier']
|
||||
else:
|
||||
raise UserWarning('Could not find clientinfo for %s' % clid)
|
||||
|
||||
# Process any active events
|
||||
for clid, client in clientlist.iteritems():
|
||||
@ -327,10 +323,10 @@ def process_ts3_events(voice):
|
||||
server.clientpoke(client['clid'], message)
|
||||
|
||||
|
||||
def award_idle_ts3_points(voice):
|
||||
def award_idle_ts3_points(server):
|
||||
""" Award points for active time spent in the Teamspeak server. """
|
||||
|
||||
app.logger.info("Awarding Teamspeak idle points")
|
||||
app.logger.debug("Awarding Teamspeak idle points")
|
||||
# Get exempt channels (AFK, passwords, join power)
|
||||
exempt_cids = []
|
||||
permid_response = server.send_command('permidgetbyname', keys={'permsid': 'i_channel_needed_join_power'})
|
||||
@ -358,14 +354,15 @@ def award_idle_ts3_points(voice):
|
||||
clientinfo = server.send_command('clientinfo', {'clid': clid})
|
||||
if clientinfo.is_successful:
|
||||
client['client_unique_identifier'] = clientinfo.data[0]['client_unique_identifier']
|
||||
else:
|
||||
raise UserWarning('Could not find the clientinfo for %s' % clid)
|
||||
|
||||
# Update the data
|
||||
active_users = set()
|
||||
for client in clientlist.values():
|
||||
if client['cid'] not in exempt_cids:
|
||||
doob = models.User.query.filter_by(teamspeak_id=client['client_unique_identifier']).first()
|
||||
try:
|
||||
doob = models.User.query.filter_by(teamspeak_id=client['client_unique_identifier']).first()
|
||||
except KeyError:
|
||||
pass
|
||||
if doob:
|
||||
doob.update_connection()
|
||||
active_users.add(doob)
|
||||
|
@ -64,6 +64,16 @@
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/uikit/2.4.0/addons/timepicker/timepicker.min.js"></script>
|
||||
<link rel=stylesheet href="//cdnjs.cloudflare.com/ajax/libs/uikit/2.4.0/addons/datepicker/datepicker.gradient.css"></link>
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
var s = $("#start_time").val().split(" ");
|
||||
var e = $("#end_time").val().split(" ");
|
||||
|
||||
$("#start_d").val(s[0]);
|
||||
$("#start_t").val(s[1]);
|
||||
$("#end_d").val(e[0]);
|
||||
$("#end_t").val(e[1]);
|
||||
});
|
||||
|
||||
$(".uk-form").submit(function(event) {
|
||||
var s = moment($("#start_d").val() + ' ' + $("#start_t").val(), "DD.MM.YYYY HH:mm");
|
||||
var e = moment($("#end_d").val() + ' ' + $("#end_t").val(), "DD.MM.YYYY HH:mm");
|
||||
|
@ -10,19 +10,21 @@
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
|
||||
<!-- CSS includes -->
|
||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/uikit/2.4.0/css/uikit.gradient.min.css" />
|
||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/uikit/2.8.0/css/uikit.gradient.min.css" />
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/app.css') }}" />
|
||||
|
||||
<!-- Javascript includes -->
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/uikit/2.4.0/js/uikit.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/uikit/2.8.0/js/uikit.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.5.1/moment.min.js"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<!-- Navigation bar -->
|
||||
<nav class="uk-navbar uk-navbar-attached" data-uk-navbar>
|
||||
<a href="#offcanvas" class="uk-navbar-brand uk-hidden-large" data-uk-offcanvas><img src="{{ url_for('static', filename='img/navlogo.png') }}" /><i class="uk-icon-double-angle-down"></i></a>
|
||||
<a href="#offcanvas" class="uk-navbar-brand uk-hidden-large" data-uk-offcanvas>
|
||||
<img src="{{ url_for('static', filename='img/navlogo.png') }}" class="uk-visible-large" /><i class="uk-icon-navicon uk-hidden-large"></i>
|
||||
</a>
|
||||
<a href="" class="uk-navbar-brand uk-visible-large" data-uk-offcanvas><img src="{{ url_for('static', filename='img/navlogo.png') }}" /></a>
|
||||
<!-- Check if user is logged in -->
|
||||
{% if g.user %}
|
||||
|
@ -7,14 +7,16 @@
|
||||
<div class="uk-width-2-3">
|
||||
<h2 class="uk-float-left"><img class="" src="{{ user.avatar }}" /> {{ user.nickname }}</h2>
|
||||
</div>
|
||||
<div id="profile_links" class="uk-width-1-3 uk-text-center">
|
||||
<div class="uk-panel">
|
||||
{% if user.public %}
|
||||
<a href="http://steamcommunity.com/profiles/{{ user.steam_id | safe }}">Steam</a> |
|
||||
<a href="http://board.dotanoobs.com/?page=profile&id={{ user.id | safe }}">Forum Profile</a> |
|
||||
<a href="http://dotabuff.com/search?q={{ user.steam_id }}">Dotabuff</a>
|
||||
<div id="profile_links" class="uk-width-1-3 uk-hidden-small uk-text-center">
|
||||
{% if user.public %}
|
||||
<div class="uk-button-group">
|
||||
<a class="uk-button" href="http://steamcommunity.com/profiles/{{ user.steam_id | safe }}">Steam</a>
|
||||
{% if user.forum_id %}
|
||||
<a class="uk-button" href="http://board.dotanoobs.com/?page=profile&id={{ user.forum_id | safe }}">Forum Profile</a>
|
||||
{% endif %}
|
||||
<a class="uk-button" href="http://dotabuff.com/search?q={{ user.steam_id }}">Dotabuff</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<!--Main content area -->
|
||||
<div class="uk-width-large-2-3 uk-width-medium-1-1 uk-panel">
|
||||
@ -29,24 +31,104 @@
|
||||
{% endif %}
|
||||
{% if user.id == g.user.id %} <a href="{{ url_for('user_settings')}}"><i class="uk-icon-edit"></i></a>{% endif %}
|
||||
</div>
|
||||
<div id="profile_links" class="uk-width-1-3 uk-visible-small uk-text-center">
|
||||
{% if user.public %}
|
||||
<div class="uk-button-group">
|
||||
<a class="uk-button" href="http://steamcommunity.com/profiles/{{ user.steam_id | safe }}">Steam</a>
|
||||
{% if user.forum_id %}
|
||||
<a class="uk-button" href="http://board.dotanoobs.com/?page=profile&id={{ user.forum_id | safe }}">Forum Profile</a>
|
||||
{% endif %}
|
||||
<a class="uk-button" href="http://dotabuff.com/search?q={{ user.steam_id }}">Dotabuff</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<!-- Side bar -->
|
||||
<div class="uk-width-large-1-3 uk-width-medium-1-1 uk-panel uk-panel-box uk-panel-box-secondary">
|
||||
<div class="uk-width-large-1-3 uk-width-medium-1-1 uk-panel">
|
||||
{% if user.public %}
|
||||
<div class="uk-container-center uk-text-center">
|
||||
<span class="uk-text-bold">Current Hero</span><br/>
|
||||
<span>{{ user.random_hero['localized_name'] }}</span><br/>
|
||||
<span class="uk-text-success uk-text-bold">
|
||||
{{ user.random_hero['localized_name'] }}
|
||||
({{ user.random_heroes.completed | length + 1 }}
|
||||
/
|
||||
{{ total_hero_pool() - user.random_heroes.completed|length }})
|
||||
</span><br/>
|
||||
<a href={{ url_for('user_random_hero', userid=user.id) }}>
|
||||
<img src="{{ url_for('static', filename=hero_image_large(user.random_hero)) }}" class="dn-hero-icon" /><br/>
|
||||
<span>View A-Z Progress</span>
|
||||
</a>
|
||||
</div>
|
||||
<ul class="uk-list uk-list-space uk-list-striped uk-text-center uk-text-small">
|
||||
<li>Completed <span id='rands'>{{ user.random_heroes.completed | length }}</span> heroes in A-Z</li>
|
||||
<li>Has <span id='points_total'>0</span> doobs points</li>
|
||||
<li>Last seen at <span class='date'> {{ user.last_seen | js_datetime }}</span></li>
|
||||
<li>Doob since <span class='date'> {{ user.created | js_datetime }}</span></li>
|
||||
</ul>
|
||||
<table class="uk-table uk-table-hover uk-table-condensed">
|
||||
<caption>{{ user.nickname }}</caption>
|
||||
<tbody class="uk-text-small">
|
||||
<tr>
|
||||
<td class="uk-width-4-10">TS Points</td>
|
||||
<td class="uk-width-6-10 uk-text-right">0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Events Points</td>
|
||||
<td class="uk-text-right">0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Forum Points</td>
|
||||
<td class="uk-text-right">0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Last Seen</td>
|
||||
<td class="date uk-text-right">{{ user.last_seen | js_datetime }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Member Since</td>
|
||||
<td class="date uk-text-right">{{ user.created | js_datetime }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<button class="uk-button uk-button-success uk-align-center" data-uk-modal="{target: '#winrate_modal'}">View Winrate</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<div id="winrate_modal" class="uk-modal">
|
||||
<div class="uk-modal-dialog uk-modal-dialog-frameless uk-modal-dialog-large">
|
||||
<a class="uk-modal-close uk-close uk-close-alt"></a>
|
||||
</div>
|
||||
<div id="winrate_graph" class="uk-overflow-container"></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block pagescripts %}
|
||||
<script src="http://code.highcharts.com/highcharts.js"></script>
|
||||
{% cache 60*700 %}
|
||||
<script>
|
||||
$('#winrate_modal').on({
|
||||
'uk.modal.show': function(){
|
||||
Highcharts.charts[0].reflow();
|
||||
},
|
||||
});
|
||||
$(function () {
|
||||
$('#winrate_graph').highcharts({
|
||||
chart: { reflow: true },
|
||||
title: { text: "Win rate for {{ user.nickname }}" },
|
||||
subtitle: { text: "Over last {{ user.winrate_data['total_games'] }} games" },
|
||||
xAxis: {type: 'datetime', dateTimeLabelFormats:{
|
||||
month:'%m'
|
||||
}
|
||||
},
|
||||
yAxis: {min: 0.35, max: 0.90, plotLines: [{value:0, width:2, color:'#808080'}]},
|
||||
legend: {enabled: false},
|
||||
series: [
|
||||
{
|
||||
name: '{{ user.nickname }}',
|
||||
data: [
|
||||
{% for date_nums, windowed in user.winrate_data['data'] %}
|
||||
[({{ date_nums }} * 1000), parseFloat({{ windowed }}.toFixed(3))],
|
||||
{% endfor %}
|
||||
]
|
||||
},
|
||||
]
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endcache %}
|
||||
{% endblock %}
|
||||
|
@ -12,7 +12,7 @@
|
||||
<a href="#">Stream Stats</a>
|
||||
<ul class="uk-nav-sub">
|
||||
<li><a href="http://potatr.dotanoobs.com">Potato_Bot</a></li>
|
||||
<li><a href="http://potatr.dotanoobs.com">Cider_Bot</a></li>
|
||||
<li><a href="http://cidr.dotanoobs.com">Cider_Bot</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="uk-nav-divider"></li>
|
||||
|
@ -36,12 +36,12 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block pagescripts %}
|
||||
{% cache 60*5 %}
|
||||
{% set teamspeak_data = get_teamspeak_window() %}
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highcharts/3.0.7/highcharts.js"></script>
|
||||
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/highcharts/3.0.7/modules/map.src.js"></script>
|
||||
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/highcharts/3.0.7/modules/data.src.js"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', filename='js/world-map-shapes.js') }}"></script>
|
||||
{% cache 60*5 %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('#unique_clients').html("{{ ts3_active_clients(teamspeak_data) }}");
|
||||
|
26
migrations/versions/1c90e0fd276a_.py
Normal file
26
migrations/versions/1c90e0fd276a_.py
Normal file
@ -0,0 +1,26 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: 1c90e0fd276a
|
||||
Revises: a6f7dd522b7
|
||||
Create Date: 2014-06-24 19:01:39.358682
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '1c90e0fd276a'
|
||||
down_revision = 'a6f7dd522b7'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade():
|
||||
### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('user', sa.Column('winrate_data', sa.Json(), nullable=True))
|
||||
### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('user', 'winrate_data')
|
||||
### end Alembic commands ###
|
102
run.py
Executable file
102
run.py
Executable file
@ -0,0 +1,102 @@
|
||||
#!venv/bin/python
|
||||
from flask import Flask
|
||||
from flask.ext.script import Manager, Server
|
||||
from flask.ext.migrate import Migrate, MigrateCommand
|
||||
|
||||
from app import app, db, models
|
||||
|
||||
#SQLALCHEMY_DATABASE_URI = 'mysql://root:$perwePP@localhost/dotanoobs'
|
||||
|
||||
migrate = Migrate(app, db)
|
||||
manager = Manager(app)
|
||||
manager.add_command('db', MigrateCommand)
|
||||
|
||||
def createTeamspeakInstance():
|
||||
import ts3
|
||||
s = ts3.TS3Server(app.config['TS3_HOST'], app.config['TS3_PORT'])
|
||||
s.login(app.config['TS3_USERNAME'], app.config['TS3_PASSWORD'])
|
||||
s.use(1)
|
||||
return s
|
||||
|
||||
@manager.command
|
||||
def install_cronjobs():
|
||||
from os import path
|
||||
from crontab import CronTab
|
||||
cron = CronTab(user=True)
|
||||
|
||||
# Clear out existing jobs
|
||||
cron.remove_all(comment='DOOBSAUTO')
|
||||
|
||||
def make_job(job):
|
||||
p = path.realpath(__file__)
|
||||
c = cron.new(command='{}/venv/bin/python {} {}'.format(path.split(p)[0],\
|
||||
p, job), comment='DOOBSAUTO')
|
||||
return c
|
||||
|
||||
# Create the jobs
|
||||
winrate = make_job('calc_winrates')
|
||||
ts3_move_afk = make_job('ts3_move_afk')
|
||||
ts3_snapshot = make_job('ts3_snapshot')
|
||||
ts3_award_points = make_job('ts3_award_points')
|
||||
ts3_process_events = make_job('ts3_process_events')
|
||||
|
||||
# Set their frequency to run
|
||||
winrate.every(1).day()
|
||||
ts3_move_afk.every(app.config['MOVE_AFK_FREQUENCY']).minute()
|
||||
ts3_snapshot.every(app.config['SNAPSHOT_FREQUENCY']).hour()
|
||||
ts3_award_points.every(app.config['AWARD_POINTS_FREQUENCY']).minute()
|
||||
ts3_process_events.every(app.config['PROCESS_EVENTS_FREQUENCY']).hour()
|
||||
|
||||
try:
|
||||
assert True == winrate.is_valid()
|
||||
assert True == ts3_move_afk.is_valid()
|
||||
assert True == ts3_snapshot.is_valid()
|
||||
assert True == ts3_award_points.is_valid()
|
||||
assert True == ts3_process_events.is_valid()
|
||||
except AssertionError as e:
|
||||
print "Problem installing cronjobs: {}".format(e)
|
||||
else:
|
||||
cron.write()
|
||||
print "Cron jobs written succesfully"
|
||||
|
||||
@manager.command
|
||||
def admin(name):
|
||||
u = models.User.query.filter_by(nickname=name).first()
|
||||
if u and not u.admin:
|
||||
u.admin = True
|
||||
db.session.commit()
|
||||
print "User {} has been granted admin access.".format(name)
|
||||
|
||||
@manager.command
|
||||
def calc_winrates():
|
||||
from app.analytics import calculate_winrates
|
||||
tsServer = createTeamspeakInstance()
|
||||
calculate_winrates()
|
||||
|
||||
@manager.command
|
||||
def ts3_move_afk():
|
||||
from app.teamspeak import idle_mover
|
||||
tsServer = createTeamspeakInstance()
|
||||
idle_mover(tsServer)
|
||||
|
||||
@manager.command
|
||||
def ts3_snapshot():
|
||||
from app.teamspeak import store_active_data
|
||||
tsServer = createTeamspeakInstance()
|
||||
store_active_data(tsServer)
|
||||
|
||||
@manager.command
|
||||
def ts3_award_points():
|
||||
from app.teamspeak import award_idle_ts3_points
|
||||
tsServer = createTeamspeakInstance()
|
||||
award_idle_ts3_points(tsServer)
|
||||
|
||||
@manager.command
|
||||
def ts3_process_events():
|
||||
from app.teamspeak import process_ts3_events
|
||||
tsServer = createTeamspeakInstance()
|
||||
process_ts3_events(tsServer)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
manager.run()
|
Loading…
x
Reference in New Issue
Block a user