def _connect(self): self.conn = HLLConnection() try: self.conn.connect(self.config["host"], int(self.config["port"]), self.config["password"]) except (TypeError, ValueError) as e: logger.critical("Invalid connection information") raise
def _connect(self): self.conn = HLLConnection() self.conn.connect(self.config['host'], self.config['port'], self.config['password'])
class ServerCtl: """TODO: Use string format instead of interpolation as it could be a security risk set password not implemented on purpose """ def __init__(self, config, auto_retry=1): self.config = config self._connect() self.auto_retry = auto_retry def _connect(self): self.conn = HLLConnection() self.conn.connect(self.config['host'], self.config['port'], self.config['password']) def _reconnect(self): logger.warning("reconnecting") self.conn.close() time.sleep(1) self._connect() @_auto_retry def _request(self, command: str, can_fail=True, log_info=False): if log_info: logger.info(command) else: logger.debug(command) try: self.conn.send(command.encode()) result = self.conn.receive().decode() except (RuntimeError, BrokenPipeError, socket.timeout, ConnectionResetError, UnicodeDecodeError): logger.exception("Failed request") raise HLLServerError(command) if result == 'FAIL': if can_fail: raise CommandFailedError(command) else: raise HLLServerError(f"Got FAIL for {command}") return result def _read_list(self, raw): res = raw.split('\t') try: expected_len = int(res[0]) except ValueError: raise HLLServerError("Unexpected response from server." "Unable to get list length") # Max 30 tries for i in range(30): if expected_len <= len(res) - 1: break raw += self.conn.receive().decode() res = raw.split('\t') if res[-1] == '': # There's a trailin \t res = res[:-1] if expected_len < len(res) - 1: raise HLLServerError( "Server returned incomplete list," f" expected {expected_len} got {len(res) - 1}") return res[1:] @_auto_retry def _get(self, item, is_list=False, can_fail=True): res = self._request(f"get {item}", can_fail) if not is_list: return res return self._read_list(res) def get_name(self): return self._get("name", can_fail=False) def get_map(self): # server adds a _RESTART suffix after the name when the map is # loading return self._get("map", can_fail=False) def get_maps(self): return self._get("mapsforrotation", True, can_fail=False) def get_players(self): return self._get("players", True, can_fail=False) def get_player_info(self, player): return self._request(f'playerinfo {player}') def get_admin_ids(self): return self._get("adminids", True, can_fail=False) def get_temp_bans(self): return self._get("tempbans", True, can_fail=False) def get_perma_bans(self): return self._get("permabans", True, can_fail=False) def get_team_switch_cooldown(self): return self._get("teamswitchcooldown", can_fail=False) def get_autobalance_threshold(self): return self._get("autobalancethreshold", can_fail=False) def get_map_rotation(self): return self._request('rotlist', can_fail=False).split('\n')[:-1] def get_slots(self): return self._get("slots", can_fail=False) def get_vip_ids(self): return self._get("vipids", True, can_fail=False) def get_admin_groups(self): return self._get("admingroups", True, can_fail=False) @_auto_retry def get_logs(self, since_min_ago, filter_=''): res = self._request(f'showlog {since_min_ago}') for i in range(30): if res[-1] == '\n': break res += self.conn.receive().decode() return res def get_idle_autokick_time(self): return self._get("idletime", can_fail=False) def get_max_ping_autokick(self): return self._get('highping', can_fail=False) def get_queue_length(self): return self._get("maxqueuedplayers", can_fail=False) def get_vip_slots_num(self): return self._get("numvipslots", can_fail=False) def set_autobalance(self, bool_str, log_info=True): """ String bool is on / off """ return self._request(f'setautobalanceenabled {bool_str}') def set_welcome_message(self, msg): return self._request(f"say {msg}", log_info=True) def set_map(self, map_name): return self._request(f"map {map_name}", log_info=True) def set_idle_autokick_time(self, minutes): return self._request(f"setkickidletime {minutes}", log_info=True) def set_max_ping_autokick(self, max_ms): return self._request(f"sethighping {max_ms}", log_info=True) def set_autobalance_threshold(self, max_diff: int): return self._request(f"setautobalancethreshold {max_diff}", log_info=True) def set_team_switch_cooldown(self, minutes): return self._request(f"setteamswitchcooldown {minutes}", log_info=True) def set_queue_length(self, num): return self._request(f"setmaxqueuedplayers {num}", log_info=True) def set_vip_slots_num(self, num): return self._request(f"setnumvipslots {num}", log_info=True) @_escape_params def set_broadcast(self, msg): return self._request(f'broadcast "{msg}"', log_info=True) def do_switch_player_on_death(self, player): return self._request(f'switchteamondeath {player}', log_info=True) def do_switch_player_now(self, player): return self._request(f'switchteamnow {player}', log_info=True) def do_add_map_to_rotation(self, map_name): return self._request(f"rotadd {map_name}", can_fail=False, log_info=True) def do_remove_map_from_rotation(self, map_name): return self._request(f"rotdel {map_name}", can_fail=False, log_info=True) @_escape_params def do_punish(self, player, reason): return self._request(f'punish "{player}" "{reason}"', log_info=True) @_escape_params def do_kick(self, player, reason): return self._request(f'kick "{player}" "{reason}"', log_info=True) @_escape_params def do_temp_ban(self, player, reason): return self._request(f'tempban "{player}" "{reason}"', log_info=True) @_escape_params def do_perma_ban(self, player, reason): return self._request(f'permaban "{player}" "{reason}"', log_info=True) def do_remove_temp_ban(self, ban_log): return self._request(f"pardontempban {ban_log}", log_info=True) def do_remove_perma_ban(self, ban_log): return self._request(f"pardonpermaban {ban_log}", log_info=True) @_escape_params def do_add_admin(self, steam_id_64, role, name): return self._request(f'adminadd "{steam_id_64}" "{role}" "{name}"', log_info=True) def do_remove_admin(self, steam_id_64): return self._request(f'admindel {steam_id_64}', log_info=True) @_escape_params def do_add_vip(self, steam_id_64, name): return self._request(f'vipadd {steam_id_64} "{name}"', log_info=True) def do_remove_vip(self, steam_id_64): return self._request(f'vipdel {steam_id_64}', log_info=True)
class ServerCtl: """TODO: Use string format instead of interpolation as it could be a security risk set password not implemented on purpose """ def __init__(self, config, auto_retry=1): self.config = config self.conn = None # self._connect() self.auto_retry = auto_retry def _connect(self): self.conn = HLLConnection() try: self.conn.connect(self.config["host"], int(self.config["port"]), self.config["password"]) except (TypeError, ValueError) as e: logger.critical("Invalid connection information") raise def _reconnect(self): logger.warning("reconnecting") self.conn.close() time.sleep(1) self._connect() @_auto_retry def _request(self, command: str, can_fail=True, log_info=False): if not self.conn: self._connect() if log_info: logger.info(command) else: logger.debug(command) try: self.conn.send(command.encode()) result = self.conn.receive().decode() except ( RuntimeError, BrokenPipeError, socket.timeout, ConnectionResetError, UnicodeDecodeError, ) as e: logger.exception("Failed request") raise HLLServerError(command) from e if result == "FAIL": if can_fail: raise CommandFailedError(command) else: raise HLLServerError(f"Got FAIL for {command}") return result @_auto_retry def _timed_request(self, command: str, can_fail=True, log_info=False): if log_info: logger.info(command) else: logger.debug(command) try: before_sent, after_sent, _ = self.conn.send(command.encode(), timed=True) before_received, after_received, result = self.conn.receive( timed=True) result = result.decode() except ( RuntimeError, BrokenPipeError, socket.timeout, ConnectionResetError, UnicodeDecodeError, ): logger.exception("Failed request") raise HLLServerError(command) if result == "FAIL": if can_fail: raise CommandFailedError(command) else: raise HLLServerError(f"Got FAIL for {command}") return dict( before_sent=before_sent, after_sent=after_sent, before_received=before_received, after_received=after_received, result=result, ) def _read_list(self, raw): res = raw.split("\t") try: expected_len = int(res[0]) except ValueError: raise HLLServerError("Unexpected response from server." "Unable to get list length") # Max 30 tries for i in range(30): if expected_len <= len(res) - 1: break raw += self.conn.receive().decode() res = raw.split("\t") if res[-1] == "": # There's a trailin \t res = res[:-1] if expected_len < len(res) - 1: raise HLLServerError( "Server returned incomplete list," f" expected {expected_len} got {len(res) - 1}") return res[1:] @_auto_retry def _get(self, item, is_list=False, can_fail=True): res = self._request(f"get {item}", can_fail) if not is_list: return res return self._read_list(res) def get_profanities(self): return self._get("profanity", is_list=True, can_fail=False) def do_ban_profanities(self, profanities_csv): return self._request(f"BanProfanity {profanities_csv}") def do_unban_profanities(self, profanities_csv): return self._request(f"UnbanProfanity {profanities_csv}") def get_name(self): return self._get("name", can_fail=False) def get_map(self): # server adds a _RESTART suffix after the name when the map is # loading return self._get("map", can_fail=False) def get_maps(self): return self._get("mapsforrotation", True, can_fail=False) def get_players(self): return self._get("players", True, can_fail=False) def get_playerids(self): return self._get("playerids", True, can_fail=False) def get_player_info(self, player): return self._request(f"playerinfo {player}") def get_admin_ids(self): return self._get("adminids", True, can_fail=False) def get_temp_bans(self): return self._get("tempbans", True, can_fail=False) def get_perma_bans(self): return self._get("permabans", True, can_fail=False) def get_team_switch_cooldown(self): return self._get("teamswitchcooldown", can_fail=False) def get_autobalance_threshold(self): return self._get("autobalancethreshold", can_fail=False) def get_votekick_enabled(self): return self._get("votekickenabled", can_fail=False) def get_votekick_threshold(self): return self._get("votekickthreshold", can_fail=False) def get_map_rotation(self): return self._request("rotlist", can_fail=False).split("\n")[:-1] def get_slots(self): return self._get("slots", can_fail=False) def get_vip_ids(self): return self._get("vipids", True, can_fail=False) def get_admin_groups(self): return self._get("admingroups", True, can_fail=False) def get_autobalance_enabled(self): return self._get("autobalanceenabled", can_fail=False) @_auto_retry def get_logs(self, since_min_ago, filter_=""): res = self._request(f"showlog {since_min_ago}") for i in range(30): if res[-1] == "\n": break try: res += self.conn.receive().decode() except ( RuntimeError, BrokenPipeError, socket.timeout, ConnectionResetError, UnicodeDecodeError, ): logger.exception("Failed request") raise HLLServerError(f"showlog {since_min_ago}") return res def get_timed_logs(self, since_min_ago, filter_=""): res = self._timed_request(f"showlog {since_min_ago}") for i in range(30): if res["result"][-1] == "\n": break res["result"] += self.conn.receive().decode() return res def get_idle_autokick_time(self): return self._get("idletime", can_fail=False) def get_max_ping_autokick(self): return self._get("highping", can_fail=False) def get_queue_length(self): return self._get("maxqueuedplayers", can_fail=False) def get_vip_slots_num(self): return self._get("numvipslots", can_fail=False) def set_autobalance_enabled(self, bool_str): """ String bool is on / off """ return self._request(f"setautobalanceenabled {bool_str}") def set_welcome_message(self, msg): return self._request(f"say {msg}", log_info=True) def set_map(self, map_name): return self._request(f"map {map_name}", log_info=True) def set_idle_autokick_time(self, minutes): return self._request(f"setkickidletime {minutes}", log_info=True) def set_max_ping_autokick(self, max_ms): return self._request(f"sethighping {max_ms}", log_info=True) def set_autobalance_threshold(self, max_diff: int): return self._request(f"setautobalancethreshold {max_diff}", log_info=True) def set_team_switch_cooldown(self, minutes): return self._request(f"setteamswitchcooldown {minutes}", log_info=True) def set_queue_length(self, num): return self._request(f"setmaxqueuedplayers {num}", log_info=True) def set_vip_slots_num(self, num): return self._request(f"setnumvipslots {num}", log_info=True) @_escape_params def set_broadcast(self, msg): return self._request(f'broadcast "{msg}"', log_info=True) def set_votekick_enabled(self, bool_str): """ String bool is on / off """ return self._request(f"setvotekickenabled {bool_str}") def set_votekick_threshold(self, threshold_pairs_str): """ PlayerCount,Threshold[,PlayerCount,Threshold,...] """ return self._request(f"setvotekickthreshold {threshold_pairs_str}") def do_reset_votekick_threshold(self): return self._request(f"resetvotekickthreshold", log_info=True) def do_switch_player_on_death(self, player): return self._request(f"switchteamondeath {player}", log_info=True) def do_switch_player_now(self, player): return self._request(f"switchteamnow {player}", log_info=True) def do_add_map_to_rotation(self, map_name): return self._request(f"rotadd {map_name}", can_fail=False, log_info=True) def do_remove_map_from_rotation(self, map_name): return self._request(f"rotdel {map_name}", can_fail=False, log_info=True) @_escape_params def do_punish(self, player, reason): return self._request(f'punish "{player}" "{reason}"', log_info=True) @_escape_params def do_kick(self, player, reason): return self._request(f'kick "{player}" "{reason}"', log_info=True) @_escape_params def do_temp_ban( self, player_name=None, steam_id_64=None, duration_hours=2, reason="", admin_name="", ): return self._request( f'tempban "{steam_id_64 or player_name}" {duration_hours} "{reason}" "{admin_name}"', log_info=True, ) @_escape_params def do_perma_ban(self, player_name=None, steam_id_64=None, reason="", admin_name=""): return self._request( f'permaban "{steam_id_64 or player_name}" "{reason}" "{admin_name}"', log_info=True, ) def do_remove_temp_ban(self, ban_log): return self._request(f"pardontempban {ban_log}", log_info=True) def do_remove_perma_ban(self, ban_log): return self._request(f"pardonpermaban {ban_log}", log_info=True) @_escape_params def do_add_admin(self, steam_id_64, role, name): return self._request(f'adminadd "{steam_id_64}" "{role}" "{name}"', log_info=True) def do_remove_admin(self, steam_id_64): return self._request(f"admindel {steam_id_64}", log_info=True) @_escape_params def do_add_vip(self, steam_id_64, name): return self._request(f'vipadd {steam_id_64} "{name}"', log_info=True) def do_remove_vip(self, steam_id_64): return self._request(f"vipdel {steam_id_64}", log_info=True)