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
*.swp
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)
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_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();

13
app/static/css/app.css

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

37
app/teamspeak.py

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

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

8
app/templates/layout.html

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

110
app/templates/profile.html

@ -7,14 +7,16 @@
<div class="uk-width-2-3">
<h2 class="uk-float-left"><img class="" src="{{ user.avatar }}" />&nbsp;{{ 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 %}&nbsp;<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 %}

2
app/templates/sidenav.html

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

2
app/templates/teamspeak.html

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

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