mv periodic back to cron, add winrate graphs

This commit is contained in:
Brandon Cornejo 2014-07-13 15:10:18 -05:00
parent 5d81c4fbe4
commit 73f7f9e567
14 changed files with 352 additions and 92 deletions

1
.gitignore vendored
View File

@ -12,3 +12,4 @@ venv/
*.db *.db
*.swp *.swp
clientlist.txt clientlist.txt
*.wsgi

24
app.py
View File

@ -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()

View File

@ -10,29 +10,4 @@ db = SQLAlchemy(app)
oid = OpenID(app) oid = OpenID(app)
cache = Cache(app, config={'CACHE_TYPE': app.config['CACHE_TYPE']}) cache = Cache(app, config={'CACHE_TYPE': app.config['CACHE_TYPE']})
import ts3 from app import views
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

76
app/analytics.py Normal file
View 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

View File

@ -81,11 +81,14 @@ class User(db.Model):
points_from_events = db.Column(db.Integer) points_from_events = db.Column(db.Integer)
points_from_ts3 = db.Column(db.Integer) points_from_ts3 = db.Column(db.Integer)
points_from_forum = db.Column(db.Integer) points_from_forum = db.Column(db.Integer)
ts3_starttime = db.Column(db.DateTime) ts3_starttime = db.Column(db.DateTime)
ts3_endtime = db.Column(db.DateTime) ts3_endtime = db.Column(db.DateTime)
ts3_rewardtime = db.Column(db.DateTime) ts3_rewardtime = db.Column(db.DateTime)
ts3_connections = db.Column(MutableDict.as_mutable(Json)) ts3_connections = db.Column(MutableDict.as_mutable(Json))
last_post_reward = db.Column(db.Integer) last_post_reward = db.Column(db.Integer)
winrate_data = db.Column(MutableDict.as_mutable(Json))
@classmethod @classmethod
@ -96,6 +99,7 @@ class User(db.Model):
self.steam_id = steam_id self.steam_id = steam_id
self.random_heroes = {'current':None, 'completed':[]} self.random_heroes = {'current':None, 'completed':[]}
self.az_completions = 0 self.az_completions = 0
self.ts3_connections = {'list':[]}
self.created = datetime.utcnow() self.created = datetime.utcnow()
self.last_seen = datetime.utcnow() self.last_seen = datetime.utcnow()
self.bio_text = None self.bio_text = None
@ -161,7 +165,7 @@ class User(db.Model):
db.session.commit(); db.session.commit();
def finalize_connection(self): 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_startime = None
self.ts3_endtime = None self.ts3_endtime = None
db.session.commit(); db.session.commit();

View File

@ -95,6 +95,15 @@ footer {
float:right; float:right;
} }
#profile_links > a { #winrate_graph {
font-size:14px; margin: 5em;
}
.pipemenu > li {
display: inline-block;
list-style: none;
}
.pipemenu > li + li::before {
content: " | ";
} }

View File

@ -6,12 +6,12 @@ from datetime import datetime, timedelta
from xml.etree import ElementTree from xml.etree import ElementTree
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
import models
from app import app, db from app import app, db
def getTeamspeakWindow(window=timedelta(weeks=1)): def getTeamspeakWindow(window=timedelta(weeks=1)):
current_time = datetime.utcnow() current_time = datetime.utcnow()
from models import TeamspeakData return models.TeamspeakData.query.filter(models.TeamspeakData.time < current_time, models.TeamspeakData.time > current_time-window).order_by(models.TeamspeakData.time).all()
return TeamspeakData.query.filter(TeamspeakData.time < current_time, TeamspeakData.time > current_time-window).order_by(TeamspeakData.time).all()
def registerUserTeamspeakId(user, tsid): def registerUserTeamspeakId(user, tsid):
server = ts3.TS3Server(app.config['TS3_HOST'], app.config['TS3_PORT']) server = ts3.TS3Server(app.config['TS3_HOST'], app.config['TS3_PORT'])
@ -173,7 +173,7 @@ def create_teamspeak_viewer():
return "error: %s" % inst return "error: %s" % inst
def get_ISO3166_mapping(): 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() data = d.read()
xml = ElementTree.fromstring(data) xml = ElementTree.fromstring(data)
d = dict() d = dict()
@ -187,10 +187,10 @@ ISO3166_MAPPING = get_ISO3166_mapping()
# Scheduled functions for TeamspeakServer # 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. """ """ 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 = [] exempt_cids = []
permid_response = server.send_command('permidgetbyname', keys={'permsid': 'i_channel_needed_join_power'}) permid_response = server.send_command('permidgetbyname', keys={'permsid': 'i_channel_needed_join_power'})
if permid_response.is_successful: if permid_response.is_successful:
@ -220,10 +220,8 @@ def idle_mover(voice):
clientlist = server.send_command('clientlist', opts=['times']).data clientlist = server.send_command('clientlist', opts=['times']).data
for client in clientlist: for client in clientlist:
clientinfo = server.send_command('clientinfo', {'clid':client['clid']}) clientinfo = server.send_command('clientinfo', {'clid':client['clid']})
if clientinfo.is_successful: #if clientinfo.is_successful:
client['client_unique_identifier'] = clientinfo.data[0]['client_unique_identifier'] #client['client_unique_identifier'] = clientinfo.data[0]['client_unique_identifier']
else:
raise UserWarning('Could not find the clientinfo for %s' % client['clid'])
# move idlers to afk channel # move idlers to afk channel
for client in clientlist: for client in clientlist:
@ -232,10 +230,10 @@ def idle_mover(voice):
# Have TeamSpeak move AFK user to appropriate channel # Have TeamSpeak move AFK user to appropriate channel
server.send_command('clientmove', keys={'clid': client['clid'], 'cid': afk_channel['cid']}) 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 """ """ 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) # Get exempt channels (AFK, passworded, join powers)
exempt_cids = [] exempt_cids = []
permid_response = server.send_command('permidgetbyname', keys={'permsid': 'i_channel_needed_join_power'}) 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.add(tsdata)
db.session.commit() 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 """ """ 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 # Get list of clients
clientlist = server.clientlist() clientlist = server.clientlist()
for clid, client in clientlist.iteritems(): for clid, client in clientlist.iteritems():
clientinfo = server.send_command('clientinfo', {'clid':clid}) clientinfo = server.send_command('clientinfo', {'clid':clid})
if clientinfo.is_successful: if clientinfo.is_successful:
client['client_unique_identifier'] = clientinfo.data[0]['client_unique_identifier'] client['client_unique_identifier'] = clientinfo.data[0]['client_unique_identifier']
else:
raise UserWarning('Could not find clientinfo for %s' % clid)
# Process any active events # Process any active events
for clid, client in clientlist.iteritems(): for clid, client in clientlist.iteritems():
@ -327,10 +323,10 @@ def process_ts3_events(voice):
server.clientpoke(client['clid'], message) 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. """ """ 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) # Get exempt channels (AFK, passwords, join power)
exempt_cids = [] exempt_cids = []
permid_response = server.send_command('permidgetbyname', keys={'permsid': 'i_channel_needed_join_power'}) 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}) clientinfo = server.send_command('clientinfo', {'clid': clid})
if clientinfo.is_successful: if clientinfo.is_successful:
client['client_unique_identifier'] = clientinfo.data[0]['client_unique_identifier'] client['client_unique_identifier'] = clientinfo.data[0]['client_unique_identifier']
else:
raise UserWarning('Could not find the clientinfo for %s' % clid)
# Update the data # Update the data
active_users = set() active_users = set()
for client in clientlist.values(): for client in clientlist.values():
if client['cid'] not in exempt_cids: if client['cid'] not in exempt_cids:
try:
doob = models.User.query.filter_by(teamspeak_id=client['client_unique_identifier']).first() doob = models.User.query.filter_by(teamspeak_id=client['client_unique_identifier']).first()
except KeyError:
pass
if doob: if doob:
doob.update_connection() doob.update_connection()
active_users.add(doob) active_users.add(doob)

View File

@ -64,6 +64,16 @@
<script src="//cdnjs.cloudflare.com/ajax/libs/uikit/2.4.0/addons/timepicker/timepicker.min.js"></script> <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> <link rel=stylesheet href="//cdnjs.cloudflare.com/ajax/libs/uikit/2.4.0/addons/datepicker/datepicker.gradient.css"></link>
<script> <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) { $(".uk-form").submit(function(event) {
var s = moment($("#start_d").val() + ' ' + $("#start_t").val(), "DD.MM.YYYY HH:mm"); 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"); var e = moment($("#end_d").val() + ' ' + $("#end_t").val(), "DD.MM.YYYY HH:mm");

View File

@ -10,19 +10,21 @@
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<!-- CSS includes --> <!-- 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') }}" /> <link rel="stylesheet" href="{{ url_for('static', filename='css/app.css') }}" />
<!-- Javascript includes --> <!-- Javascript includes -->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script> <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> <script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.5.1/moment.min.js"></script>
</head> </head>
<body> <body>
<!-- Navigation bar --> <!-- Navigation bar -->
<nav class="uk-navbar uk-navbar-attached" data-uk-navbar> <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> <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 --> <!-- Check if user is logged in -->
{% if g.user %} {% if g.user %}

View File

@ -7,14 +7,16 @@
<div class="uk-width-2-3"> <div class="uk-width-2-3">
<h2 class="uk-float-left"><img class="" src="{{ user.avatar }}" />&nbsp;{{ user.nickname }}</h2> <h2 class="uk-float-left"><img class="" src="{{ user.avatar }}" />&nbsp;{{ user.nickname }}</h2>
</div> </div>
<div id="profile_links" class="uk-width-1-3 uk-text-center"> <div id="profile_links" class="uk-width-1-3 uk-hidden-small uk-text-center">
<div class="uk-panel">
{% if user.public %} {% if user.public %}
<a href="http://steamcommunity.com/profiles/{{ user.steam_id | safe }}">Steam</a> | <div class="uk-button-group">
<a href="http://board.dotanoobs.com/?page=profile&id={{ user.id | safe }}">Forum Profile</a> | <a class="uk-button" href="http://steamcommunity.com/profiles/{{ user.steam_id | safe }}">Steam</a>
<a href="http://dotabuff.com/search?q={{ user.steam_id }}">Dotabuff</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 %} {% endif %}
<a class="uk-button" href="http://dotabuff.com/search?q={{ user.steam_id }}">Dotabuff</a>
</div> </div>
{% endif %}
</div> </div>
<!--Main content area --> <!--Main content area -->
<div class="uk-width-large-2-3 uk-width-medium-1-1 uk-panel"> <div class="uk-width-large-2-3 uk-width-medium-1-1 uk-panel">
@ -29,24 +31,104 @@
{% endif %} {% endif %}
{% if user.id == g.user.id %}&nbsp;<a href="{{ url_for('user_settings')}}"><i class="uk-icon-edit"></i></a>{% endif %} {% if user.id == g.user.id %}&nbsp;<a href="{{ url_for('user_settings')}}"><i class="uk-icon-edit"></i></a>{% endif %}
</div> </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 --> <!-- 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 %} {% if user.public %}
<div class="uk-container-center uk-text-center"> <div class="uk-container-center uk-text-center">
<span class="uk-text-bold">Current Hero</span><br/> <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) }}> <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/> <img src="{{ url_for('static', filename=hero_image_large(user.random_hero)) }}" class="dn-hero-icon" /><br/>
<span>View A-Z Progress</span> <span>View A-Z Progress</span>
</a> </a>
</div> </div>
<ul class="uk-list uk-list-space uk-list-striped uk-text-center uk-text-small"> <table class="uk-table uk-table-hover uk-table-condensed">
<li>Completed <span id='rands'>{{ user.random_heroes.completed | length }}</span> heroes in A-Z</li> <caption>{{ user.nickname }}</caption>
<li>Has <span id='points_total'>0</span> doobs points</li> <tbody class="uk-text-small">
<li>Last seen at <span class='date'> {{ user.last_seen | js_datetime }}</span></li> <tr>
<li>Doob since <span class='date'> {{ user.created | js_datetime }}</span></li> <td class="uk-width-4-10">TS Points</td>
</ul> <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 %} {% endif %}
</div> </div>
</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 %} {% endblock %}

View File

@ -12,7 +12,7 @@
<a href="#">Stream Stats</a> <a href="#">Stream Stats</a>
<ul class="uk-nav-sub"> <ul class="uk-nav-sub">
<li><a href="http://potatr.dotanoobs.com">Potato_Bot</a></li> <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> </ul>
</li> </li>
<li class="uk-nav-divider"></li> <li class="uk-nav-divider"></li>

View File

@ -36,12 +36,12 @@
{% endblock %} {% endblock %}
{% block pagescripts %} {% block pagescripts %}
{% cache 60*5 %}
{% set teamspeak_data = get_teamspeak_window() %} {% set teamspeak_data = get_teamspeak_window() %}
<script src="//cdnjs.cloudflare.com/ajax/libs/highcharts/3.0.7/highcharts.js"></script> <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/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="//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> <script type="text/javascript" src="{{ url_for('static', filename='js/world-map-shapes.js') }}"></script>
{% cache 60*5 %}
<script> <script>
$(document).ready(function() { $(document).ready(function() {
$('#unique_clients').html("{{ ts3_active_clients(teamspeak_data) }}"); $('#unique_clients').html("{{ ts3_active_clients(teamspeak_data) }}");

View 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
View 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()