From a46b968a6700bc01486bd96b0a66249704c4a4b2 Mon Sep 17 00:00:00 2001 From: dkoz Date: Mon, 13 Nov 2023 23:25:42 -0500 Subject: [PATCH 1/4] Support Ark SA Adding support for Ark: Survival Ascended --- discordgsm/games.csv | 1 + discordgsm/protocols/__init__.py | 1 + discordgsm/protocols/asa.py | 87 ++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 discordgsm/protocols/asa.py diff --git a/discordgsm/games.csv b/discordgsm/games.csv index 41330eb..635c84f 100644 --- a/discordgsm/games.csv +++ b/discordgsm/games.csv @@ -11,6 +11,7 @@ americasarmy3,America's Army 3 (2009),source,port=8777;port_query=27020 americasarmypg,America's Army: Proving Grounds (2015),source,port=8777;port_query=27020 aoe2,Age of Empires 2 (1999),ase,port_query=27224 arkse,Ark: Survival Evolved (2017),source,port=7777;port_query=27015 +asa,Ark: Survival Ascended (2023),asa,port=7777 arma,ARMA: Armed Assault (2007),gamespy2,port=2302 arma2,ARMA 2 (2009),source,port=2302;port_query_offset=1 arma2oa,ARMA 2: Operation Arrowhead (2010),source,port=2302;port_query_offset=1 diff --git a/discordgsm/protocols/__init__.py b/discordgsm/protocols/__init__.py index 2eed280..58d17fe 100644 --- a/discordgsm/protocols/__init__.py +++ b/discordgsm/protocols/__init__.py @@ -32,5 +32,6 @@ from .ut3 import UT3 from .vcmp import Vcmp from .won import WON +from .asa import Asa protocols = {str(protocol.name): protocol for protocol in Protocol.__subclasses__()} diff --git a/discordgsm/protocols/asa.py b/discordgsm/protocols/asa.py new file mode 100644 index 0000000..6543a1d --- /dev/null +++ b/discordgsm/protocols/asa.py @@ -0,0 +1,87 @@ +import asyncio +import time +from discordgsm.protocols.protocol import Protocol +import requests +import base64 + +class Asa(Protocol): + name = 'asa' + + def __init__(self, kv): + super().__init__(kv) + self.client_id = 'xyza7891muomRmynIIHaJB9COBKkwj6n' + self.client_secret = 'PP5UGxysEieNfSrEicaD1N2Bb3TdXuD7xHYcsdUHZ7s' + self.deployment_id = 'ad9a8feffb3b4b2ca315546f038c3ae2' + self.epic_api = 'https://api.epicgames.dev' + + async def query(self): + host, port = str(self.kv['host']), int(str(self.kv['port'])) + try: + access_token = self.get_access_token() + server_info = self.query_server_info(access_token, host, port) + + sessions = server_info.get('sessions', []) + if not sessions: + raise Exception("No sessions found") + + desired_session = sessions[0] + + attributes = desired_session.get('attributes', {}) + settings = desired_session.get('settings', {}) + + result = { + 'name': attributes.get('CUSTOMSERVERNAME_s', 'Unknown Server'), + 'map': attributes.get('MAPNAME_s', 'Unknown Map'), + 'password': attributes.get('SERVERPASSWORD_b', False), + 'numplayers': desired_session.get('totalPlayers', 0), + 'maxplayers': settings.get('maxPublicPlayers', 0), + 'players': [], + 'bots': [], + 'connect': attributes.get('ADDRESS_s', '') + ':' + str(port), + 'ping': 0, + 'raw': desired_session + } + except Exception as e: + result = { + 'raw': {'error': str(e)}, + 'name': 'Unknown Server', + 'map': 'Unknown Map', + 'password': False, + 'numplayers': 0, + 'maxplayers': 0, + 'players': [], + 'bots': [], + 'connect': f"{host}:{port}", + 'ping': 0 + } + + return result + + def get_access_token(self): + url = f"{self.epic_api}/auth/v1/oauth/token" + auth = base64.b64encode(f"{self.client_id}:{self.client_secret}".encode()).decode() + headers = { + "Authorization": f"Basic {auth}", + "Content-Type": "application/x-www-form-urlencoded" + } + body = f"grant_type=client_credentials&deployment_id={self.deployment_id}" + response = requests.post(url, headers=headers, data=body) + response.raise_for_status() + return response.json()['access_token'] + + def query_server_info(self, access_token, host, port): + url = f"{self.epic_api}/matchmaking/v1/{self.deployment_id}/filter" + headers = { + "Authorization": f"Bearer {access_token}", + "Content-Type": "application/json", + "Accept": "application/json" + } + body = { + "criteria": [ + {"key": "attributes.ADDRESS_s", "op": "EQUAL", "value": host}, + {"key": "attributes.ADDRESSBOUND_s", "op": "EQUAL", "value": f"{host}:{port}"} + ] + } + response = requests.post(url, headers=headers, json=body) + response.raise_for_status() + return response.json() From b339cb57bd0740b05918025899e2ff369d5bda96 Mon Sep 17 00:00:00 2001 From: dkoz Date: Mon, 13 Nov 2023 23:26:33 -0500 Subject: [PATCH 2/4] Update asa.py --- discordgsm/protocols/asa.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/discordgsm/protocols/asa.py b/discordgsm/protocols/asa.py index 6543a1d..43d61c2 100644 --- a/discordgsm/protocols/asa.py +++ b/discordgsm/protocols/asa.py @@ -1,5 +1,3 @@ -import asyncio -import time from discordgsm.protocols.protocol import Protocol import requests import base64 @@ -79,7 +77,7 @@ def query_server_info(self, access_token, host, port): body = { "criteria": [ {"key": "attributes.ADDRESS_s", "op": "EQUAL", "value": host}, - {"key": "attributes.ADDRESSBOUND_s", "op": "EQUAL", "value": f"{host}:{port}"} + {"key": "attributes.ADDRESSBOUND_s", "op": "EQUAL", "value": f"0.0.0.0:{port}"} ] } response = requests.post(url, headers=headers, json=body) From 9f33a13a777fdd11dac8a64339ba7d77c017cd04 Mon Sep 17 00:00:00 2001 From: dkoz Date: Tue, 14 Nov 2023 00:04:49 -0500 Subject: [PATCH 3/4] Update asa.py Server can't be queried based on port and JSON output from EOS API varies. Needs more testing. --- discordgsm/protocols/asa.py | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/discordgsm/protocols/asa.py b/discordgsm/protocols/asa.py index 43d61c2..1f1bcd1 100644 --- a/discordgsm/protocols/asa.py +++ b/discordgsm/protocols/asa.py @@ -1,6 +1,7 @@ from discordgsm.protocols.protocol import Protocol -import requests +import aiohttp import base64 +import asyncio class Asa(Protocol): name = 'asa' @@ -15,8 +16,8 @@ def __init__(self, kv): async def query(self): host, port = str(self.kv['host']), int(str(self.kv['port'])) try: - access_token = self.get_access_token() - server_info = self.query_server_info(access_token, host, port) + access_token = await self.get_access_token() + server_info = await self.query_server_info(access_token, host, port) sessions = server_info.get('sessions', []) if not sessions: @@ -55,7 +56,7 @@ async def query(self): return result - def get_access_token(self): + async def get_access_token(self): url = f"{self.epic_api}/auth/v1/oauth/token" auth = base64.b64encode(f"{self.client_id}:{self.client_secret}".encode()).decode() headers = { @@ -63,11 +64,14 @@ def get_access_token(self): "Content-Type": "application/x-www-form-urlencoded" } body = f"grant_type=client_credentials&deployment_id={self.deployment_id}" - response = requests.post(url, headers=headers, data=body) - response.raise_for_status() - return response.json()['access_token'] + + async with aiohttp.ClientSession() as session: + async with session.post(url, headers=headers, data=body) as response: + response.raise_for_status() + data = await response.json() + return data['access_token'] - def query_server_info(self, access_token, host, port): + async def query_server_info(self, access_token, host, port): url = f"{self.epic_api}/matchmaking/v1/{self.deployment_id}/filter" headers = { "Authorization": f"Bearer {access_token}", @@ -77,9 +81,11 @@ def query_server_info(self, access_token, host, port): body = { "criteria": [ {"key": "attributes.ADDRESS_s", "op": "EQUAL", "value": host}, - {"key": "attributes.ADDRESSBOUND_s", "op": "EQUAL", "value": f"0.0.0.0:{port}"} + {"key": "attributes.ADDRESSBOUND_s", "op": "EQUAL", "value": f"{host}:{port}"} ] } - response = requests.post(url, headers=headers, json=body) - response.raise_for_status() - return response.json() + + async with aiohttp.ClientSession() as session: + async with session.post(url, headers=headers, json=body) as response: + response.raise_for_status() + return await response.json() \ No newline at end of file From 40f0c26d715810d1ab771c182ab47cec7d79f996 Mon Sep 17 00:00:00 2001 From: dkoz Date: Tue, 14 Nov 2023 09:33:45 -0500 Subject: [PATCH 4/4] Update asa.py Fixed filtering issue with EOS API. --- discordgsm/protocols/asa.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/discordgsm/protocols/asa.py b/discordgsm/protocols/asa.py index 1f1bcd1..5680b0a 100644 --- a/discordgsm/protocols/asa.py +++ b/discordgsm/protocols/asa.py @@ -78,12 +78,13 @@ async def query_server_info(self, access_token, host, port): "Content-Type": "application/json", "Accept": "application/json" } - body = { - "criteria": [ - {"key": "attributes.ADDRESS_s", "op": "EQUAL", "value": host}, - {"key": "attributes.ADDRESSBOUND_s", "op": "EQUAL", "value": f"{host}:{port}"} - ] - } + + criteria = [ + {"key": "attributes.ADDRESS_s", "op": "EQUAL", "value": host}, + {"key": "attributes.ADDRESSBOUND_s", "op": "CONTAINS", "value": f":{port}"} + ] + + body = {"criteria": criteria} async with aiohttp.ClientSession() as session: async with session.post(url, headers=headers, json=body) as response: