-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #30 from mylesagray/feature/server-queries
Initial implementation of server monitoring and querying logic
- Loading branch information
Showing
10 changed files
with
715 additions
and
91 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
""" | ||
Queries SteamQuery, DCS and SpaceEngineers instances | ||
""" | ||
import traceback | ||
import logging | ||
from steam import SteamQuery | ||
import network | ||
from servers import Server, ServerType, list_servers, get_server | ||
|
||
|
||
def is_anyone_active() -> bool: | ||
""" | ||
Checks all known servers for users currently logged in | ||
returns a bool | ||
""" | ||
try: | ||
player_count = 0 | ||
for server in list_servers(): | ||
server = get_server(server) | ||
player_count += get_players(server).get('current_players') | ||
if player_count > 0: | ||
return True | ||
else: | ||
return False | ||
except: | ||
logging.error("Couldn't query servers for active players") | ||
traceback.print_exc() | ||
raise | ||
|
||
|
||
def get_players(server: Server) -> dict: | ||
""" | ||
Returns a dict with the current number of players connected | ||
to the server as well as the max players supported | ||
""" | ||
if server['server_type'] is ServerType.STEAM: | ||
try: | ||
steamquery = _steam_server_connection( | ||
server_ip=str(server['ip_address']), port=server['port']) | ||
server_state = _lint_steamquery_output( | ||
steamquery.query_server_info()) | ||
return {"current_players": server_state["players"], | ||
"max_players": server_state["max_players"]} | ||
|
||
except Exception: | ||
print("Could not get server info") | ||
traceback.print_exc() | ||
raise | ||
elif server['server_type'] is ServerType.SPACE_ENGINEERS: | ||
return {"current_players": 0, | ||
"max_players": 0} | ||
|
||
elif server['server_type'] is ServerType.DCS: | ||
return {"current_players": 0, | ||
"max_players": 0} | ||
|
||
else: | ||
print(f'Cannot query unrecognised server type {server_type}') | ||
|
||
|
||
def get_players_details(server: Server) -> list: | ||
""" | ||
Returns a list with all current player objects containing | ||
names, scores and durations on the server | ||
""" | ||
if server['server_type'] is ServerType.STEAM: | ||
try: | ||
steamquery = _steam_server_connection( | ||
server_ip=str(server['ip_address']), port=server['port']) | ||
player_info = _lint_steamquery_output( | ||
steamquery.query_player_info()) | ||
return player_info | ||
|
||
except Exception: | ||
print("Could not get player info") | ||
traceback.print_exc() | ||
raise | ||
elif server['server_type'] is ServerType.SPACE_ENGINEERS: | ||
pass | ||
|
||
elif server['server_type'] is ServerType.DCS: | ||
pass | ||
else: | ||
print(f'Cannot query unrecognised server type {server_type}') | ||
|
||
|
||
# Creates and returns server connection object | ||
|
||
|
||
def _steam_server_connection(server_ip: str, port: int) -> object: | ||
""" | ||
Creates a steam query server connection object and passes it back. | ||
""" | ||
try: | ||
# Check if IP address is valid | ||
if not network.valid_ip_address(server_ip): | ||
raise ValueError("IP Address Invalid") | ||
|
||
# Check if port is valid | ||
if not network.valid_port(port): | ||
raise ValueError("PORT environment variable is invalid") | ||
|
||
# Construct SteamQuery session | ||
print(f'Connecting to {server_ip}:{port}') | ||
server = SteamQuery(server_ip, port) | ||
return server | ||
|
||
except Exception: | ||
print("Unable to connect to server") | ||
traceback.print_exc() | ||
raise | ||
|
||
|
||
def _lint_steamquery_output(query) -> object: | ||
""" | ||
Checks if SteamQuery output should have been an exception | ||
and if so raises one, kill me | ||
""" | ||
# SteamQuery lib returns errors as strings, so need to | ||
# check if "error" key is present to detect exceptions | ||
# when errored, it is always passed back as a dict | ||
# | ||
# If the query is a list, then it is a valid response | ||
# in any case | ||
if isinstance(query, list): | ||
return query | ||
else: | ||
try: | ||
if "error" in query.keys(): | ||
raise ConnectionError(str(query)) | ||
else: | ||
return query | ||
except Exception: | ||
print("Error passed back from SteamQuery") | ||
traceback.print_exc() | ||
raise |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
""" | ||
Provides functions for establishing network locations and | ||
communications, port and IP verification | ||
""" | ||
import traceback | ||
from ipaddress import ip_address, IPv6Address, IPv4Address | ||
|
||
import requests | ||
|
||
# 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. | ||
# | ||
# Unknown how this will behave in IPv6 environments, but the | ||
# assumption is that it will work just the same as no NAT is | ||
# used in IPv6 and the external IPv6 address will be the | ||
# global address of the machine | ||
|
||
|
||
def get_external_ip() -> str: | ||
""" | ||
Gets the current external IP of where the app is running. | ||
This uses ifconfig.me and assumes it is not blocked or down. | ||
""" | ||
try: | ||
response = requests.get('https://ifconfig.me/ip', timeout=5) | ||
server_ip = response.content.decode() | ||
print(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") | ||
traceback.print_exc() | ||
raise | ||
|
||
# Validates if the IP address given is valid | ||
|
||
|
||
def valid_ip_address(ipaddress: int) -> int: | ||
""" | ||
Checks if the IP address passed is a Valid IPv4 or IPv6 address | ||
""" | ||
try: | ||
if isinstance(ip_address(ipaddress), (IPv4Address, IPv6Address)): | ||
return True | ||
else: | ||
return False | ||
|
||
except ValueError: | ||
print("IP address is invalid") | ||
traceback.print_exc() | ||
raise | ||
|
||
# Validates if the given port is in valid range | ||
|
||
|
||
def valid_port(port: int) -> bool: | ||
""" | ||
Checks if a given port is in the valid list of ranges for UDP ports | ||
""" | ||
try: | ||
port = int(port) | ||
if port > 0 and port <= 65535: | ||
print(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") | ||
traceback.print_exc() | ||
raise |
Oops, something went wrong.