Browse Source

mv periodic back to cron, add winrate graphs

master
Brandon Cornejo 10 years ago
parent
commit
73f7f9e567
  1. 1
      .gitignore
  2. 24
      app.py
  3. 27
      app/__init__.py
  4. 76
      app/analytics.py
  5. 6
      app/models.py
  6. 13
      app/static/css/app.css
  7. 37
      app/teamspeak.py
  8. 10
      app/templates/edit_event.html
  9. 8
      app/templates/layout.html
  10. 110
      app/templates/profile.html
  11. 2
      app/templates/sidenav.html
  12. 2
      app/templates/teamspeak.html
  13. 26
      migrations/versions/1c90e0fd276a_.py
  14. 102
      run.py

1
.gitignore

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

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

27
app/__init__.py

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

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

6
app/models.py

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

13
app/static/css/app.css

@ -95,6 +95,15 @@ footer {
float:right; 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: " | ";
} }

37
app/teamspeak.py

@ -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 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): 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:
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 # 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:
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: if doob:
doob.update_connection() doob.update_connection()
active_users.add(doob) active_users.add(doob)

10
app/templates/edit_event.html

@ -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");

8
app/templates/layout.html

@ -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 %}

110
app/templates/profile.html

@ -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 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 %} {% 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">
<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 %} {% 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 %}

2
app/templates/sidenav.html

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

2
app/templates/teamspeak.html

@ -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) }}");

26
migrations/versions/1c90e0fd276a_.py

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

@ -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…
Cancel
Save