#!/usr/bin/env python3 # -*- coding: utf-8 -*- import datetime as dt import logging import sys import time from contextlib import suppress import ts3 from .exceptions import DateException __all__ = ['GroupAssigner'] class GroupAssigner: def __init__(self, host: str, port: (str, int), user: str, password: str, sid: (str, int), gid: (str, int), date: dt.date, delta: dt.timedelta): # host config self.host = host self.port = port self.user = user self.pw = password # server and group id self.sid = sid self.gid = gid # start date and delta self.sleepstart = date - dt.timedelta(days=1) self.startdate = date self.enddate = date + delta # init connection handler self.conn = None def __connect(self): """ establish query connection and return connection handler """ # connect to the telnet interface self.conn = ts3.query.TS3Connection(self.host, self.port) # login self.conn.login(client_login_name=self.user, client_login_password=self.pw) # select specified sid self.conn.use(sid=self.sid) # assign friendly nickname self.conn.clientupdate(client_nickname=__name__) def __disconnect(self): """ gracefully logout and disconnect the connection, this should only be called if the exit is intentional """ try: self.conn.logout() self.conn.quit() # attribute error if disconnect is called prior to the connection being established except AttributeError: pass # broad exception if something unexpected happens except ts3.TS3Error as err: # log exception logging.error(err) # exit sys.exit() def __checkdate(self): """ method to check if the current date is still in the configured date range """ now = dt.date.today() # check if still in the configured range if not self.startdate <= now <= self.enddate: # if date range is exceeded shutdown gracefully logging.info('the current date exceeds the configured date range -- exiting') self.__disconnect() # else continue logging.debug('heartbeat - target date within configured date range') def __start_sleepstart(self): """ method to check if the process is eligible to sleepstart """ now = dt.date.today() # start date already reached proceed if self.startdate <= now: logging.debug('start date is already reached -- not eligible to sleepstart continue') # if startdate within the next 24h proceed to sleepstart elif now >= self.startdate - dt.timedelta(days=1): # convert both dates to datetime starttime = dt.datetime.combine(self.startdate, dt.time(hour=0, minute=0, second=0)) now = dt.datetime.now() # calculate remaining time delta remaindelta = starttime - now logging.debug(f'target date will be reached in {remaindelta.seconds} seconds -- sleeping') time.sleep(remaindelta.seconds + 1) else: # if the date is too far back raise DateException raise DateException('target date is too far in the future') def __notifycliententerview(self, data: dict): """ event thrown if a client connects to the server :param data: dictionary containing users info """ # return all non voice clients without reasonid 0 if data['client_type'] != '0' or data['reasonid'] != '0': return cldbid = data['client_database_id'] user_grps = data['client_servergroups'].split(sep=',') msg = '{client_nickname}:{client_database_id} connected - member of {client_servergroups}' logging.debug(msg.format(**data)) # only try to add nonmembers to group if str(self.gid) not in user_grps: logging.debug(f'{data["client_nickname"]} is not member of {self.gid}') try: # Usage: servergroupaddclient sgid={groupID} cldbid={clientDBID} cmd = self.conn.servergroupaddclient(sgid=self.gid, cldbid=cldbid) if cmd.error['id'] != '0': logging.error(cmd.data[0].decode("utf-8")) # log process logging.info('add {client_nickname}:{client_database_id} to {gid}'.format(**data, gid=self.gid)) # log possible key errors while the teamspeak 5 client is not fully released except KeyError as err: logging.error([err, data]) def __handle_event(self, event: str, data: dict): """ event handler which separates events to their specific handlers """ # check if event is still eligible self.__checkdate() # client enter events if event == "notifycliententerview": self.__notifycliententerview(data) def start(self): """ main entry point to start the bot """ # eol to start process ahead of time self.__start_sleepstart() # proceed only if target date is inside the date range self.__checkdate() try: # init connection self.__connect() # break if credentials are invalid except ts3.query.TS3QueryError as err: # log error and disconnect logging.error(err) self.__disconnect() # start processing self.__main() def __main(self): """ main loop """ # register for "server" notify event self.conn.servernotifyregister(event="server") # start listening and processing while True: self.conn.send_keepalive() # suppress TimeoutError exceptions with suppress(ts3.query.TS3TimeoutError): # wait for events event = self.conn.wait_for_event(timeout=60) # handover event to eventhandler self.__handle_event(event.event, event.parsed[0])