TeamspeakStats/tsstats.py

216 lines
7.5 KiB
Python
Raw Normal View History

2015-03-05 12:48:10 -05:00
import re
2015-06-02 09:13:08 -04:00
import glob
2015-05-12 15:38:34 -04:00
import json
2015-06-08 15:25:37 -04:00
import logging
import datetime
2015-03-05 12:48:10 -05:00
import configparser
2015-06-08 15:25:37 -04:00
from sys import argv
from time import mktime
from os.path import exists, abspath
from jinja2 import Environment, FileSystemLoader
2015-06-08 15:25:37 -04:00
# logging
log = logging.getLogger()
log.setLevel(logging.DEBUG)
# create handler
file_handler = logging.FileHandler('debug.txt', 'w', 'UTF-8')
2015-06-21 15:28:14 -04:00
file_handler.setFormatter(logging.Formatter('%(message)s'))
2015-06-08 15:25:37 -04:00
file_handler.setLevel(logging.DEBUG)
stream_handler = logging.StreamHandler()
stream_handler.setLevel(logging.INFO)
# add handler
log.addHandler(file_handler)
log.addHandler(stream_handler)
class Clients:
def __init__(self):
self.clients_by_id = {}
self.clients_by_uid = {}
def is_id(self, id_or_uid):
try:
int(id_or_uid)
return True
except ValueError:
return False
def __add__(self, id_or_uid):
if self.is_id(id_or_uid):
if id_or_uid not in self.clients_by_id:
self.clients_by_id[id_or_uid] = Client(id_or_uid)
else:
if id_or_uid not in self.clients_by_uid:
self.clients_by_uid[id_or_uid] = Client(id_or_uid)
return self
def __getitem__(self, id_or_uid):
if self.is_id(id_or_uid):
if id_or_uid not in self.clients_by_id:
self += id_or_uid
return self.clients_by_id[id_or_uid]
else:
if id_or_uid not in self.clients_by_uid:
self += id_or_uid
return self.clients_by_uid[id_or_uid]
clients = Clients()
class Client:
def __init__(self, identifier):
# public
self.identifier = identifier
self.nick = None
self.connected = False
self.onlinetime = datetime.timedelta()
self.kicks = 0
self.pkicks = 0
self.bans = 0
self.pbans = 0
# private
self._last_connect = 0
def connect(self, timestamp):
'''
client connects at "timestamp"
'''
logging.debug('CONNECT {}'.format(str(self)))
self.connected = True
self._last_connect = timestamp
def disconnect(self, timestamp):
'''
client disconnects at "timestamp"
'''
logging.debug('DISCONNECT {}'.format(str(self)))
if not self.connected:
raise Exception('WTF did just happen?! A client disconnected before connecting!')
self.connected = False
session_time = timestamp - self._last_connect
self.onlinetime += session_time
def kick(self, target):
'''
client kicks "target" (Client-obj)
'''
logging.debug('KICK {} -> {}'.format(str(self), str(target)))
target.pkicks += 1
self.kicks += 1
def ban(self, target):
'''
client bans "target" (Client-obj)
'''
logging.debug('BAN {} -> {}'.format(str(self), str(target)))
target.pbans += 1
self.bans += 1
def __str__(self):
return '<{},{}>'.format(self.identifier, self.nick)
def __format__(self):
return self.__str__()
# check cmdline-args
config_path = argv[1] if len(argv) >= 2 else 'config.ini'
config_path = abspath(config_path)
id_map_path = argv[2] if len(argv) >= 3 else 'id_map.json'
id_map_path = abspath(id_map_path)
2015-06-08 15:25:37 -04:00
if not exists(config_path):
raise Exception('Couldn\'t find config-file at {}'.format(config_path))
2015-06-08 15:25:37 -04:00
if exists(id_map_path):
# read id_map
id_map = json.load(open(path + 'id_map.json'))
2015-04-16 13:30:03 -04:00
else:
2015-06-08 15:25:37 -04:00
id_map = {}
2015-04-16 13:30:03 -04:00
# parse config
2015-03-05 12:48:10 -05:00
config = configparser.ConfigParser()
config.read(config_path)
2015-05-12 15:38:34 -04:00
# check keys
2015-06-08 15:25:37 -04:00
if 'General' not in config:
raise Exception('Invalid config! Section "General" missing!')
2015-05-12 15:38:34 -04:00
general = config['General']
2015-06-08 15:25:37 -04:00
html = config['HTML'] if 'HTML' in config.sections() else {}
if not ('logfile' in general or 'outputfile' in general):
raise Exception('Invalid config! "logfile" and/or "outputfile" missing!')
2015-05-12 15:38:34 -04:00
log_path = general['logfile']
if not exists(log_path):
2015-06-08 15:25:37 -04:00
raise Exception('Couldn\'t access log-file!')
2015-05-12 15:38:34 -04:00
output_path = general['outputfile']
2015-06-21 15:28:14 -04:00
debug = general.get('debug', 'true') in ['true', 'True']
2015-06-08 15:25:37 -04:00
title = html.get('title', 'TeamspeakStats')
2015-06-21 15:28:14 -04:00
if not debug:
logging.disable(logging.DEBUG)
2015-03-05 12:48:10 -05:00
2015-06-08 15:25:37 -04:00
generation_start = datetime.datetime.now()
2015-03-05 12:48:10 -05:00
2015-06-08 15:25:37 -04:00
re_dis_connect = re.compile(r"'(.*)'\(id:(\d*)\)")
re_disconnect_invoker = re.compile(r"invokername=(.*)\ invokeruid=(.*)\ reasonmsg")
2015-03-05 12:48:10 -05:00
2015-06-08 15:25:37 -04:00
# find all log-files and collect lines
2015-06-02 09:13:08 -04:00
log_files = [file_name for file_name in glob.glob(log_path)]
log_lines = []
for log_file in log_files:
for line in open(log_file, 'r'):
log_lines.append(line)
2015-06-08 15:25:37 -04:00
# process lines
2015-06-02 09:13:08 -04:00
for line in log_lines:
parts = line.split('|')
2015-06-08 15:25:37 -04:00
logdatetime = datetime.datetime.strptime(parts[0], '%Y-%m-%d %H:%M:%S.%f')
data = parts[4].strip()
2015-06-02 09:13:08 -04:00
if data.startswith('client'):
2015-06-08 15:25:37 -04:00
nick, clid = re_dis_connect.findall(data)[0]
2015-06-02 09:13:08 -04:00
if clid in id_map:
clid = id_map[clid]
2015-06-08 15:25:37 -04:00
client = clients[clid]
client.nick = nick
2015-06-02 09:13:08 -04:00
if data.startswith('client connected'):
2015-06-08 15:25:37 -04:00
client.connect(logdatetime)
2015-06-02 09:13:08 -04:00
elif data.startswith('client disconnected'):
2015-06-08 15:25:37 -04:00
client.disconnect(logdatetime)
if 'invokeruid' in data:
re_disconnect_data = re_disconnect_invoker.findall(data)
invokernick, invokeruid = re_disconnect_data[0]
invoker = clients[invokeruid]
invoker.nick = invokernick
if 'bantime' in data:
invoker.ban(client)
else:
invoker.kick(client)
generation_end = datetime.datetime.now()
generation_delta = generation_end - generation_start
2015-06-08 15:25:37 -04:00
# render template
template = Environment(loader=FileSystemLoader(abspath('.'))).get_template('template.html')
# sort all values desc
cl_by_id = clients.clients_by_id
cl_by_uid = clients.clients_by_uid
clients_onlinetime_ = sorted([(client, client.onlinetime) for client in cl_by_id.values()], key=lambda data: data[1], reverse=True)
clients_onlinetime = []
for client, onlinetime in clients_onlinetime_:
minutes, seconds = divmod(client.onlinetime.seconds, 60)
hours, minutes = divmod(minutes, 60)
hours = str(hours) + 'h ' if hours != 0 else ''
minutes = str(minutes) + 'm ' if minutes != 0 else ''
seconds = str(seconds) + 's' if seconds != 0 else ''
clients_onlinetime.append((client, hours + minutes + seconds))
clients_kicks = sorted([(client, client.kicks) for client in cl_by_uid.values() if client.kicks > 0], key=lambda data: data[1], reverse=True)
clients_pkicks = sorted([(client, client.pkicks) for client in cl_by_id.values() if client.pkicks > 0], key=lambda data: data[1], reverse=True)
clients_bans = sorted([(client, client.bans) for client in cl_by_uid.values() if client.bans > 0], key=lambda data: data[1], reverse=True)
clients_pbans = sorted([(client, client.pbans) for client in cl_by_id.values() if client.pbans > 0], key=lambda data: data[1], reverse=True)
objs = [('Onlinetime', clients_onlinetime), ('Kicks', clients_kicks),
('passive Kicks', clients_pkicks),
('Bans', clients_bans), ('passive Bans', clients_pbans)] # (headline, list)
with open(output_path, 'w') as f:
2015-06-21 15:28:14 -04:00
f.write(template.render(title=title, objs=objs, generation_time='{}.{}'.format(generation_delta.seconds, generation_delta.microseconds), time=generation_end.strftime('%d.%m.%Y %H:%M'), debug=debug))