diff --git a/app/bot.py b/app/bot.py index 029a761..9ac4dc3 100644 --- a/app/bot.py +++ b/app/bot.py @@ -174,7 +174,9 @@ async def _shutdown(ctx): the function not being called via sudo """ try: - if gamequery.is_anyone_active() and not ctx.author == 'sudo': + #TODO:myles - Add failed server query feedback to user + is_anyone_active = gamequery.is_anyone_active() + if is_anyone_active[0] and not ctx.author == 'sudo': await ctx.respond('Server can\'t be shut down, someone is online!') else: response = requests.get(SHUTDOWN_URL, timeout=2) @@ -199,7 +201,9 @@ async def _reboot(ctx): the function not being called via sudo """ try: - if gamequery.is_anyone_active() and not ctx.author == 'sudo': + #TODO:myles - Add failed server query feedback to user + is_anyone_active = gamequery.is_anyone_active() + if is_anyone_active[0] and not ctx.author == 'sudo': await ctx.respond('Server can\'t be rebooted, someone is online!') else: response = requests.get(REBOOT_URL, timeout=2) diff --git a/app/gamequery.py b/app/gamequery.py index 2c17376..c28f22c 100644 --- a/app/gamequery.py +++ b/app/gamequery.py @@ -10,25 +10,35 @@ import network from servers import Server, ServerType, list_servers, load_servers, get_server +logger = logging.getLogger(__name__) +logging.basicConfig(filename='gamequery.log', level=logging.INFO) +logger.setLevel(logging.DEBUG) -def is_anyone_active() -> bool: +def is_anyone_active() -> tuple[bool, list]: """ Checks all known servers for users currently logged in - returns a bool + returns a tuple of a bool and a list """ try: player_count = 0 if list_servers() == {}: load_servers() + failed_queries = [] for server in list_servers(): - server = get_server(server) - player_count += get_players(server).get('current_players') + try: + server = get_server(server) + player_count += get_players(server).get('current_players') + except AttributeError: + logger.warning("No result, couldn't connect to %s, moving on...", server['name']) + failed_queries.append(server['name']) + except Exception: + traceback.print_exc() if player_count > 0: - return True + return True, failed_queries else: - return False + return False, failed_queries except: - logging.error("Couldn't query servers for active players") + logger.error("Couldn't query servers for active players") traceback.print_exc() raise @@ -39,29 +49,30 @@ def get_players(server: Server) -> dict: to the server as well as the max players supported """ if server['server_type'] is ServerType.STEAM: + logger.info("Querying ArmA server: %s", server['name']) try: steamquery = _steam_server_connection( server_ip=str(server['ip_address']), port=server['port']) server_state = _lint_steamquery_output( steamquery.query_server_info()) + logger.info("%s has %s/%s players active", server['name'], server_state['players'], server_state['max_players']) return {"current_players": server_state["players"], "max_players": server_state["max_players"]} except ConnectionError: - print("Could not connect to ArmA server, connection error") - traceback.print_exc() - pass + logger.error("Could not connect to %s, connection error", server['name']) except Exception: - print("Could not get ArmA server info") + logger.error("Could not get %s player info", server['name']) traceback.print_exc() raise elif server['server_type'] is ServerType.SPACE_ENGINEERS: + logger.info("Querying Space Engineers server: %s", server['name']) try: server_api_address = "http://" + \ str(server['ip_address']) + ":" + str(server['port']) api = VRageAPI(url=server_api_address, token=server['password']) - players = api.get_players() server_ping = api.get_server_ping() + players = api.get_players() server_info = api.get_server_info() if server_ping["data"]["Result"] == "Pong": @@ -78,53 +89,53 @@ def get_players(server: Server) -> dict: for player in players["data"]["Players"]: print(player["SteamID"], player["DisplayName"]) + logger.info("%s has %s/%s players active", str(server['name']), player_count, "99") return {"current_players": player_count, "max_players": 99} except ConnectionError: - print("Could not connect to SE server, connection error") - traceback.print_exc() - pass + logger.error("Could not connect to %s, connection error", server['name']) except Exception: - print("Could not get Space Engineers server info") + logger.error("Could not get %s player info", server['name']) traceback.print_exc() raise elif server['server_type'] is ServerType.MINECRAFT_JAVA: + logger.info("Querying Minecraft Java server: %s", server['name']) try: - server = JavaServer(str(server['ip_address']), server['port']) - status = server.status() + serverinstance = JavaServer(str(server['ip_address']), server['port']) + status = serverinstance.status() + logger.info("%s has %s/%s players active", str(server['name']), status.players.online, status.players.max) return {"current_players": status.players.online, "max_players": status.players.max} except ConnectionError: - print("Could not connect to Minecraft Java server, connection error") - traceback.print_exc() - pass + logger.error("Could not connect to %s, connection error", server['name']) except Exception: - print("Could not get Minecraft JE server info") + logger.error("Could not get %s player info", server['name']) traceback.print_exc() raise elif server['server_type'] is ServerType.MINECRAFT_BEDROCK: + logger.info("Querying Minecraft Bedrock server: %s", server['name']) try: - server = BedrockServer(str(server['ip_address']), server['port']) - status = server.status() + serverinstance = BedrockServer(str(server['ip_address']), server['port']) + status = serverinstance.status() + logger.info("%s has %s/%s players active", str(server['name']), status.players.online, status.players.max) return {"current_players": status.players.online, "max_players": status.players.max} except ConnectionError: - print("Could not connect to Minecraft Bedrock server, connection error") - traceback.print_exc() - pass + logger.error("Could not connect to %s, connection error", server['name']) except Exception: - print("Could not get Minecraft Bedrock server info") + logger.error("Could not get %s player info", server['name']) traceback.print_exc() raise elif server['server_type'] is ServerType.DCS: ##TODO myles + logger.info("%s has 0/0 players active", server['name']) return {"current_players": 0, "max_players": 0} else: - print(f'Cannot query unrecognised server type {server_type}') + logger.error('Cannot query unrecognised server type %s', server_type) def get_players_details(server: Server) -> list: @@ -132,6 +143,7 @@ def get_players_details(server: Server) -> list: Returns a list with all current player objects containing names, scores and durations on the server """ + logger.info("Getting player details for %s", server['name']) if server['server_type'] is ServerType.STEAM: try: steamquery = _steam_server_connection( @@ -139,11 +151,11 @@ def get_players_details(server: Server) -> list: player_info = _lint_steamquery_output( steamquery.query_player_info()) return player_info - except Exception: - print("Could not get player info") + logger.warning("Could not get player info") traceback.print_exc() raise + elif server['server_type'] is ServerType.SPACE_ENGINEERS: pass @@ -159,15 +171,57 @@ def get_players_details(server: Server) -> list: pass else: - print(f'Cannot query unrecognised server type {server_type}') + logger.error('Cannot query unrecognised server type %s', server_type) + +def get_server_details(server: Server) -> list: + """ + Returns a list with all relevant server config + """ + logger.info("Getting server details for %s", server['name']) + if server['server_type'] is ServerType.STEAM: + try: + steamquery = _steam_server_connection( + server_ip=str(server['ip_address']), port=server['port']) + server_info = _lint_steamquery_output( + steamquery.query_server_info()) + return server_info + + except Exception: + logger.warning("Could not get server info for %s", server['name']) + traceback.print_exc() + raise + elif server['server_type'] is ServerType.SPACE_ENGINEERS: + pass -# Creates and returns server connection object + elif server['server_type'] is ServerType.DCS: + pass + + elif server['server_type'] is ServerType.MINECRAFT_JAVA: + #https://mcstatus.readthedocs.io/en/stable/api/basic/#mcstatus.status_response.JavaStatusPlayer + #https://mcstatus.readthedocs.io/en/stable/api/basic/#mcstatus.querier.QueryResponse + try: + serverinstance = JavaServer(str(server['ip_address']), server['port']) + query = serverinstance.query() + print(query) + except TimeoutError: + logger.error("Could not query server info for %s, timed out", server['name']) + except Exception as error: + logger.error("Could not query server info for %s, %s", server['name'], error) + traceback.print_exc() + raise + + elif server['server_type'] is ServerType.MINECRAFT_BEDROCK: + pass + + else: + logger.error('Cannot query unrecognised server type %s', server_type) def _steam_server_connection(server_ip: str, port: int) -> object: """ Creates a steam query server connection object and passes it back. """ + logger.info("Connecting to SteamQuery...") try: # Check if IP address is valid if not network.valid_ip_address(server_ip): @@ -178,12 +232,12 @@ def _steam_server_connection(server_ip: str, port: int) -> object: raise ValueError("PORT environment variable is invalid") # Construct SteamQuery session - print(f'Connecting to {server_ip}:{port}') + logger.info('Connecting to %s:%s',server_ip , port) server = SteamQuery(server_ip, port) return server except Exception: - print("Unable to connect to server") + logger.warning("Unable to connect to server") traceback.print_exc() raise @@ -199,6 +253,7 @@ def _lint_steamquery_output(query) -> object: # # If the query is a list, then it is a valid response # in any case + logger.info("Linting SteamQuery ouput") if isinstance(query, list): return query else: @@ -207,7 +262,10 @@ def _lint_steamquery_output(query) -> object: raise ConnectionError(str(query)) else: return query + except ConnectionError: + logger.error("Connection error: server may be down") + raise except Exception: - print("Error passed back from SteamQuery") + logger.error("Error passed back from SteamQuery") traceback.print_exc() raise diff --git a/app/network.py b/app/network.py index 8d1a8ef..e8eac42 100644 --- a/app/network.py +++ b/app/network.py @@ -3,10 +3,14 @@ communications, port and IP verification """ import traceback +import logging from ipaddress import ip_address, IPv6Address, IPv4Address import requests +logger = logging.getLogger(__name__) +logging.basicConfig(filename='network.log', level=logging.INFO) + # Gets the external IP address where the server is running # this assumes that the outbound IP after NAT and inbound IP # before NAT are the same IP address. @@ -25,11 +29,11 @@ def get_external_ip() -> str: try: response = requests.get('https://ifconfig.me/ip', timeout=5) server_ip = response.content.decode() - print(f'Discovered IP address is {server_ip}') + logger.info(f'Discovered IP address is {server_ip}') return str(server_ip) except Exception: - print("External IP could not be found, ifconfig.me may be down or blocked") + logger.error("External IP could not be found, ifconfig.me may be down or blocked") traceback.print_exc() raise @@ -47,7 +51,7 @@ def valid_ip_address(ipaddress: int) -> int: return False except ValueError: - print("IP address is invalid") + logger.error("IP address is invalid") traceback.print_exc() raise @@ -61,11 +65,11 @@ def valid_port(port: int) -> bool: try: port = int(port) if port > 0 and port <= 65535: - print(f'PORT {port} is valid') + logger.info(f'PORT {port} is valid') return True raise ValueError(f'PORT {port} is not in valid range 1-65535') except Exception: - print("PORT is not valid") + logger.warning("PORT is not valid") traceback.print_exc() raise diff --git a/app/servers.json b/app/servers.json.example similarity index 73% rename from app/servers.json rename to app/servers.json.example index fdfe272..f5f7b38 100644 --- a/app/servers.json +++ b/app/servers.json.example @@ -19,5 +19,12 @@ "password": "", "port": 10308, "server_type": "DCS" + }, + { + "ip_address": "minecraft.example.com", + "name": "Minecraft Server", + "password": "", + "port": 25565, + "server_type": "MINECRAFT_JAVA" } ] \ No newline at end of file