You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
373 lines
21 KiB
373 lines
21 KiB
from flask import url_for
|
|
from operator import itemgetter
|
|
from os import path
|
|
import ts3
|
|
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()
|
|
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'])
|
|
server.login(app.config['TS3_USERNAME'], app.config['TS3_PASSWORD'])
|
|
server.use(1)
|
|
|
|
response = server.send_command('clientdbfind', {'pattern':tsid.encode('utf-8')}, ('uid',))
|
|
if response.is_successful:
|
|
cdbid = response.data[0]['cldbid']
|
|
user.teamspeak_id = tsid
|
|
sgid = [entry['sgid'] for entry in server.send_command('servergrouplist').data if entry['name'] == 'Normal' and entry['type'] == '1'][0]
|
|
server.send_command('servergroupaddclient', {'sgid': sgid, 'cldbid': cdbid})
|
|
db.session.commit()
|
|
return True
|
|
return False
|
|
|
|
def create_teamspeak_viewer():
|
|
try:
|
|
server = ts3.TS3Server(app.config['TS3_HOST'], app.config['TS3_PORT'])
|
|
server.login(app.config['TS3_USERNAME'], app.config['TS3_PASSWORD'])
|
|
server.use(1)
|
|
|
|
serverinfo = server.send_command('serverinfo').data
|
|
channellist = server.send_command('channellist', opts=("limits", "flags", "voice", "icon")).data
|
|
clientlist = server.send_command('clientlist', opts=("away", "voice", "info", "icon", "groups", "country")).data
|
|
servergrouplist = server.send_command('servergrouplist').data
|
|
channelgrouplist = server.send_command('channelgrouplist').data
|
|
|
|
soup = BeautifulSoup()
|
|
div_tag = soup.new_tag('div')
|
|
div_tag['class'] ='devmx-webviewer'
|
|
soup.append(div_tag)
|
|
|
|
def construct_channels(parent_tag, cid):
|
|
num_clients = 0
|
|
for channel in channellist:
|
|
if int(channel['pid']) == int(cid):
|
|
# Construct the channel
|
|
channel_tag = soup.new_tag('div')
|
|
channel_tag['class'] = 'tswv-channel'
|
|
# Channel image
|
|
image_tag = soup.new_tag('span')
|
|
image_tag['class'] = 'tswv-image tswv-image-right'
|
|
if int(channel['channel_flag_password']) == 1:
|
|
image_tag['class'] += ' tswv-channel-password-right'
|
|
if int(channel['channel_flag_default']) == 1:
|
|
image_tag['class'] += ' tswv-channel-home'
|
|
if int(channel['channel_needed_talk_power']) > 0:
|
|
image_tag['class'] += ' tswv-channel-moderated'
|
|
if int(channel['channel_icon_id']) != 0:
|
|
pass
|
|
image_tag.append(' ')
|
|
channel_tag.append(image_tag)
|
|
# Status image
|
|
status_tag = soup.new_tag('span')
|
|
status_tag['class'] = 'tswv-image'
|
|
if int(channel['channel_flag_password']) == 1:
|
|
status_tag['class'] += ' tswv-channel-password'
|
|
elif int(channel['total_clients']) == int(channel['channel_maxclients']):
|
|
status_tag['class'] += ' tswv-channel-full'
|
|
else:
|
|
status_tag['class'] += ' tswv-channel-normal'
|
|
status_tag.append(' ')
|
|
channel_tag.append(status_tag)
|
|
# Label
|
|
label_tag = soup.new_tag('span')
|
|
label_tag['class'] = 'tswv-label'
|
|
label_tag.append(channel['channel_name'])
|
|
channel_tag.append(label_tag)
|
|
# Clients
|
|
channel_tag, channel_clients = construct_clients(channel_tag, channel['cid'])
|
|
# Recurse through sub-channels, collecting total number of clients as we go
|
|
channel_tag, sub_clients = construct_channels(channel_tag, channel['cid'])
|
|
channel_clients += sub_clients
|
|
# Only show non-empty channels
|
|
if channel_clients > 0:
|
|
parent_tag.append(channel_tag)
|
|
num_clients += channel_clients
|
|
return parent_tag, num_clients
|
|
|
|
def construct_clients(parent_tag, cid):
|
|
num_clients = 0
|
|
for client in clientlist:
|
|
if int(client['cid']) == int(cid):
|
|
# Skip ServerQuery clients
|
|
if int(client['client_type']) == 1: continue
|
|
num_clients += 1
|
|
client_tag = soup.new_tag('div')
|
|
client_tag['class'] = 'tswv-client'
|
|
# Status image
|
|
status_tag = soup.new_tag('span')
|
|
status_tag['class'] = 'tswv-image'
|
|
if int(client['client_type']) == 1:
|
|
status_tag['class'] += ' tswv-client-query'
|
|
elif int(client['client_away']) == 1:
|
|
status_tag['class'] += " tswv-client-away"
|
|
elif int(client['client_input_muted']) == 1:
|
|
status_tag['class'] += " tswv-client-input-muted"
|
|
elif int(client['client_output_muted']) == 1:
|
|
status_tag['class'] += " tswv-client-output-muted"
|
|
elif int(client['client_input_hardware']) == 0:
|
|
status_tag['class'] += " tswv-client-input-muted-hardware"
|
|
elif int(client['client_output_hardware']) == 0:
|
|
status_tag['class'] += " tswv-client-output-muted-hardware"
|
|
elif (int(client['client_flag_talking']) == 1) and (int(client['client_is_channel_commander']) == 1):
|
|
status_tag['class'] += " tswv-client-channel-commander-talking"
|
|
elif int(client['client_is_channel_commander']) == 1:
|
|
status_tag['class'] += " tswv-client-channel-commander"
|
|
elif int(client['client_flag_talking']) == 1:
|
|
status_tag['class'] += " tswv-client-talking"
|
|
else:
|
|
status_tag['class'] += " tswv-client-normal"
|
|
status_tag.append(' ')
|
|
client_tag.append(status_tag)
|
|
# Country image
|
|
if client['client_country']:
|
|
country_tag = soup.new_tag('span')
|
|
country_tag['class'] = 'tswv-image tswv-image-right'
|
|
country_tag['title'] = ' '.join([word.capitalize() for word in ISO3166_MAPPING[client['client_country']].split(' ')])
|
|
country_tag['style'] = 'background: url("%s") center center no-repeat;' % url_for('static', filename='img/ts3_viewer/countries/%s.png' % client['client_country'].lower())
|
|
country_tag.append(' ')
|
|
client_tag.append(country_tag)
|
|
# Server group images
|
|
sgids = [int(sg) for sg in client['client_servergroups'].split(',')]
|
|
servergroups = [servergroup for servergroup in servergrouplist if int(servergroup['sgid']) in sgids]
|
|
servergroups.sort(key=itemgetter('sortid'))
|
|
for servergroup in servergroups:
|
|
if not servergroup['iconid']: continue
|
|
img_fname = 'img/ts3_viewer/%s.png' % servergroup['iconid']
|
|
if not path.exists(path.join(app.static_folder, img_fname)):
|
|
continue
|
|
image_tag = soup.new_tag('span')
|
|
image_tag['class'] = 'tswv-image tswv-image-right'
|
|
image_tag['title'] = servergroup['name']
|
|
image_tag['style'] = 'background-image: url("%s")' % url_for('static', filename=img_fname)
|
|
image_tag.append(' ')
|
|
client_tag.append(image_tag)
|
|
# Check if client is in a moderated channel
|
|
channel = [channel for channel in channellist if int(channel['cid']) == int(client['cid'])][0]
|
|
if int(channel['channel_needed_talk_power']) > 0:
|
|
status_tag = soup.new_tag('span')
|
|
status_tag['class'] = 'tswv-image tswv-image-right'
|
|
if int(client['client_is_talker']) == 0:
|
|
status_tag['class'] += ' tswv-client-input-muted'
|
|
else:
|
|
status_tag['class'] += ' tswv-client-talkpower-granted'
|
|
status_tag.append(' ')
|
|
client_tag.append(status_tag)
|
|
# Label
|
|
label_tag = soup.new_tag('span')
|
|
label_tag['class'] = 'tswv-label'
|
|
label_tag.append(client['client_nickname'])
|
|
client_tag.append(label_tag)
|
|
parent_tag.append(client_tag)
|
|
return parent_tag, num_clients
|
|
div_tag, num_clients = construct_channels(div_tag, 0)
|
|
return (soup.prettify(), num_clients)
|
|
except Exception as inst:
|
|
return "error: %s" % inst
|
|
|
|
def get_ISO3166_mapping():
|
|
with open(path.join(path.dirname(__file__), 'static/country_codes.xml'), mode='r') as d:
|
|
data = d.read()
|
|
xml = ElementTree.fromstring(data)
|
|
d = dict()
|
|
for entry in xml.findall('ISO_3166-1_Entry'):
|
|
d[entry.find('ISO_3166-1_Alpha-2_Code_element').text] = entry.find('ISO_3166-1_Country_name').text
|
|
return d
|
|
|
|
ISO3166_MAPPING = get_ISO3166_mapping()
|
|
|
|
#
|
|
# Scheduled functions for TeamspeakServer
|
|
#
|
|
|
|
def idle_mover(server):
|
|
""" Checks connected clients idle_time, moving to AFK if over TS3_MAX_IDLETIME. """
|
|
|
|
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:
|
|
join_permid = permid_response.data[0]['permid']
|
|
def exempt_check(cid):
|
|
# check flags
|
|
flag_response = server.send_command('channelinfo', keys={'cid': cid})
|
|
if flag_response.is_successful:
|
|
if flag_response.data[0]['channel_needed_talk_power'] != '0': return True
|
|
permid_response = server.send_command('channelpermlist', keys={'cid': cid})
|
|
if permid_response.is_successful:
|
|
for perm in permid_response.data:
|
|
if perm['permid'] == join_permid and perm['permvalue'] != '0': return True
|
|
return False
|
|
list_response = server.send_command('channellist')
|
|
if list_response.is_successful:
|
|
for channel in list_response.data:
|
|
if exempt_check(channel['cid']):
|
|
exempt_cids.append(channel['cid'])
|
|
|
|
# get destination
|
|
response = server.send_command('channelfind', keys={'pattern': 'AFK'})
|
|
if response.is_successful:
|
|
afk_channel = response.data[0]
|
|
|
|
# Get list of clients
|
|
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']
|
|
|
|
# move idlers to afk channel
|
|
for client in clientlist:
|
|
if( int(client['client_idle_time']) > app.config['TS3_MAX_IDLETIME']):
|
|
if client['cid'] not in exempt_cids:
|
|
# Have TeamSpeak move AFK user to appropriate channel
|
|
server.send_command('clientmove', keys={'clid': client['clid'], 'cid': afk_channel['cid']})
|
|
|
|
def store_active_data(server):
|
|
""" Take a snapshot of Teamspeak (clients, countries, etc) to feed the ts3_stats page """
|
|
|
|
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'})
|
|
if permid_response.is_successful:
|
|
join_permid = permid_response.data[0]['permid']
|
|
def exempt_check(cid):
|
|
# Check flags
|
|
flag_response = server.send_command('channelinfo', keys={'cid': cid})
|
|
if flag_response.is_successful:
|
|
if flag_response.data[0]['channel_flag_password'] != '0': return True
|
|
if flag_response.data[0]['channel_needed_talk_power'] != '0': return True
|
|
permid_response = server.send_command('channelpermlist', keys={'cid': cid})
|
|
if permid_response.is_successful:
|
|
for perm in permid_response.data:
|
|
if perm['permid'] == join_permid and perm['permvalue'] != '0': return True
|
|
return False
|
|
list_response = server.send_command('channellist')
|
|
if list_response.is_successful:
|
|
for channel in list_response.data:
|
|
if exempt_check(channel['cid']):
|
|
exempt_cids.append(channel['cid'])
|
|
|
|
# Get list of clients
|
|
clientlist = server.send_command('clientlist', opts=("country",)).data
|
|
# Remove the server_query and afk/moderated clients
|
|
clientlist = filter(lambda client: client['client_type'] == '0' and client['cid'] not in exempt_cids, clientlist)
|
|
# Compile the important information
|
|
clients = {}
|
|
for client in clientlist:
|
|
clientinfo = server.send_command('clientdbinfo', {'cldbid': client['client_database_id']})
|
|
if clientinfo.is_successful:
|
|
clients[clientinfo.data[0]['client_unique_identifier']] = {'country': client['client_country']}
|
|
else:
|
|
raise UserWarning('Could not find the clientdbinfo for %s' % client['client_database_id'])
|
|
|
|
# Update the data
|
|
tsdata = models.TeamspeakData(clients)
|
|
db.session.add(tsdata)
|
|
db.session.commit()
|
|
|
|
def process_ts3_events(server):
|
|
""" Create Teamspeak channels for upcoming events, delete empty event channels that have expired """
|
|
|
|
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']
|
|
|
|
# Process any active events
|
|
for clid, client in clientlist.iteritems():
|
|
u = models.User.query.filter_by(teamspeak_id=client['client_unique_identifier']).first()
|
|
e = models.Event.query.filter(models.Event.start_time <= datetime.utcnow(), models.Event.end_time > datetime.utcnow()).all()
|
|
if u and e:
|
|
for event in e:
|
|
if client['cid'] in event.cids:
|
|
event.add_participant(u)
|
|
|
|
# Add channels for upcoming events
|
|
e = models.Event.query.filter(models.Event.start_time >= datetime.utcnow(), \
|
|
models.Event.start_time <= (datetime.utcnow() + timedelta(minutes=60))).all()
|
|
for event in e:
|
|
if not event.cids:
|
|
print("Adding channels for event {}".format(event.name))
|
|
event.create_channels()
|
|
|
|
# Remove channels for expired events
|
|
e = models.Event.query.filter(models.Event.start_time > (datetime.utcnow() - timedelta(hours=24)), \
|
|
models.Event.end_time < (datetime.utcnow() - timedelta(minutes=60))).all()
|
|
for event in e:
|
|
current_time = datetime.utcnow()
|
|
remove_time = event.end_time + timedelta(minutes=60)
|
|
warn_time = event.end_time + timedelta(minutes=30)
|
|
time_left = remove_time - current_time
|
|
message = "This event channel is temporary and will be removed in {} minutes.".format(divmod(time_left.days * 86400 + time_left.seconds, 60)[0])
|
|
if event.cids:
|
|
if current_time > remove_time:
|
|
print("Removing channels for event: {}".format(event.name))
|
|
event.remove_channels()
|
|
elif current_time > warn_time:
|
|
for cid in event.cids:
|
|
clients = [client for client in clientlist.values() if int(client['cid']) == int(cid)]
|
|
for client in clients:
|
|
print("Warning {} about expired event {}".format(client['client_nickname'], event.name))
|
|
server.clientpoke(client['clid'], message)
|
|
|
|
|
|
def award_idle_ts3_points(server):
|
|
""" Award points for active time spent in the Teamspeak server. """
|
|
|
|
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'})
|
|
if permid_response.is_successful:
|
|
join_permid = permid_response.data[0]['permid']
|
|
def exempt_check(cid):
|
|
# Check flags
|
|
flag_response = server.send_command('channelinfo', keys={'cid':cid})
|
|
if flag_response.is_successful:
|
|
if flag_response.data[0]['channel_flag_password'] != '0': return True
|
|
if flag_response.data[0]['channel_needed_talk_power'] !='0': return True
|
|
permid_response = server.send_command('channelpermlist', keys={'cid': cid})
|
|
if permid_response.is_successful:
|
|
for perm in permid_response.data:
|
|
if perm['permid'] == join_permid and perm['permvalue'] != '0': return True
|
|
return False
|
|
list_response = server.send_command('channellist')
|
|
if list_response.is_successful:
|
|
for channel in list_response.data:
|
|
if exempt_check(channel['cid']):
|
|
exempt_cids.append(channel['cid'])
|
|
|
|
# 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']
|
|
|
|
# Update the data
|
|
active_users = set()
|
|
for client in clientlist.values():
|
|
if client['cid'] not in exempt_cids:
|
|
try:
|
|
doob = models.User.query.filter_by(teamspeak_id=client['client_unique_identifier']).first()
|
|
if doob:
|
|
doob.update_connection()
|
|
active_users.add(doob)
|
|
except KeyError:
|
|
pass
|
|
|
|
doobs = set(models.User.query.filter(models.User.ts3_starttime != None).all())
|
|
for doob in doobs.difference(active_users):
|
|
doob.finalize_connection()
|