# -*- coding: utf-8 -*- import datetime import logging from collections.abc import MutableMapping logger = logging.getLogger('tsstats') class Clients(MutableMapping): ''' A high-level-interface to multiple Client-objects ''' def __init__(self, ident_map=None, *args, **kwargs): ''' Initialize a new Client-collection :param ident_map: Identity-map (see :doc:`identmap`) :type ident_map: dict ''' self.ident_map = ident_map or {} self.store = dict() self.update(dict(*args, **kwargs)) def apply_events(self, events): ''' Apply events to this Client-collection :param events: list of events to apply :type events: list ''' for event in events: # find corresponding client client = self.setdefault( event.identifier, Client(self.ident_map.get(event.identifier, event.identifier)) ) if event.action == 'set_nick': client.nick = event.arg continue if event.arg_is_client: # if arg is client, replace identifier with Client-obj event = event._replace( arg=self.setdefault(event.arg, Client(event.arg)) ) client.__getattribute__(event.action)(event.arg) def __add__(self, client): ''' Add a Client to the collection :param client: Client to add to the collection :type id_or_uid: Client ''' identifier = client.identifier self.store[self.ident_map.get(identifier, identifier)] = client return self def __iter__(self): ''' Yield all Client-objects from the collection ''' return iter(self.store.keys()) def __getitem__(self, key): return self.store[self.ident_map.get(key, key)] def __delitem__(self, key): del self.store[key] def __len__(self): return len(self.store) def __setitem__(self, key, value): self.store[self.ident_map.get(key, key)] = value def __str__(self): return str(list(map(str, self))) class Client(object): ''' Client provides high-level-access to a Teamspeak-Client ''' def __init__(self, identifier, nick=None): ''' Initialize a new Client :param identifier: Identifier of the client :type identifier: int or str ''' # public self.identifier = identifier self._nick = nick self.nick_history = set() self.connected = 0 self.onlinetime = datetime.timedelta() self.kicks = 0 self.pkicks = 0 self.bans = 0 self.pbans = 0 self.last_seen = None # private self._last_connect = 0 @property def nick(self): return self._nick @nick.setter def nick(self, new_nick): if self._nick and new_nick != self._nick: # add old nick to history self.nick_history.add(self._nick) # set new nick self._nick = new_nick def connect(self, timestamp): ''' Connect client at `timestamp` :param timestamp: time of connect :type timestamp: int ''' logger.debug('[%s] CONNECT %s', timestamp, self) self.connected += 1 self._last_connect = timestamp def disconnect(self, timestamp): ''' Disconnect client at `timestamp` :param timestamp: time of disconnect :type timestamp: int ''' logger.debug('[%s] DISCONNECT %s', timestamp, self) if not self.connected: logger.debug('^ disconnect before connect') return self.connected -= 1 session_time = timestamp - self._last_connect logger.debug('Session lasted %s', session_time) self.onlinetime += session_time self.last_seen = timestamp def kick(self, target): ''' Let client kick `target` :param target: client to kick :type target: Client ''' logger.debug('KICK %s -> %s', self, target) target.pkicks += 1 self.kicks += 1 def ban(self, target): ''' Let client ban `target` :param target: client to ban :type target: Client ''' logger.debug('BAN %s -> %s', self, target) target.pbans += 1 self.bans += 1 def __str__(self): return u'<{}, {}>'.format(self.identifier, self.nick) def __repr__(self): return self.__str__()