diff --git a/ext/python-a2s-rcon-api/Dockerfile b/ext/python-a2s-rcon-api/Dockerfile index 4908797..6dcb2f9 100644 --- a/ext/python-a2s-rcon-api/Dockerfile +++ b/ext/python-a2s-rcon-api/Dockerfile @@ -1,5 +1,5 @@ FROM python:3.10 -RUN python -m pip install git+https://github.com/Yepoleb/python-a2s git+https://github.com/conqp/rcon git+https://github.com/tiangolo/fastapi "uvicorn[standard]" && mkdir /app +RUN python -m pip install git+https://github.com/Yepoleb/python-a2s git+https://github.com/conqp/rcon git+https://github.com/tiangolo/fastapi "uvicorn[standard]" git+https://github.com/ValvePython/steam && mkdir /app WORKDIR /app COPY ./ ./ ENV PYTHONUNBUFFERED 1 diff --git a/ext/python-a2s-rcon-api/RCONPlayerModel.py b/ext/python-a2s-rcon-api/RCONPlayerModel.py index 3e0c000..655d981 100644 --- a/ext/python-a2s-rcon-api/RCONPlayerModel.py +++ b/ext/python-a2s-rcon-api/RCONPlayerModel.py @@ -1,15 +1,26 @@ from pydantic import BaseModel +from steam.steamid import SteamID import re class BotPlayer(Exception): pass +def GetSteam(steamid): + steam_obj = SteamID(steamid) + return { + "steam3":steam_obj.as_steam3, + "steam2":steam_obj.as_steam2_zero, + "steam64":steam_obj.as_64, + "account_id":steam_obj.as_32, + "community_url":steam_obj.community_url + } + class RCONPlayer: #DefaultPlayer name: str score: int id: int = 0 - steam = None + steam: dict #RCONPlayer duration: str ip: str @@ -22,13 +33,18 @@ class RCONPlayer: def __init__(self, status_line:str): splited = re.split(r"\s+", status_line) len = splited.__len__() - self.id = int(splited[1]) - self.ip = splited[len - 1] - self.state = splited[len - 2] - if self.state == "BOT": - raise BotPlayer() - self.loss = int(splited[len - 3]) - self.ping = int(splited[len - 4]) - self.duration = splited[len - 5] - self.steam2 = splited[len - 6] - self.name = " ".join(splited[2:len-6])[1:-1] \ No newline at end of file + try: + self.id = int(splited[1]) + self.ip = splited[len - 1] + self.state = splited[len - 2] + if self.state == "BOT": + raise BotPlayer() + self.loss = int(splited[len - 3]) + self.ping = int(splited[len - 4]) + self.duration = splited[len - 5] + self.steam2 = splited[len - 6] + self.name = " ".join(splited[2:len-6])[1:-1] + self.steam = GetSteam(self.steam2) + except ValueError as err: + print(splited) + raise err \ No newline at end of file diff --git a/ext/python-a2s-rcon-api/docker-compose.yaml b/ext/python-a2s-rcon-api/docker-compose.yaml index 0db6b05..bef689a 100644 --- a/ext/python-a2s-rcon-api/docker-compose.yaml +++ b/ext/python-a2s-rcon-api/docker-compose.yaml @@ -1,5 +1,7 @@ services: a2s_backend: build: ./ + extra_hosts: + - "tf2.pblr-nyk.pro:192.168.3.3" ports: - - 8082:8082 \ No newline at end of file + - 8085:8082 \ No newline at end of file diff --git a/ext/python-a2s-rcon-api/service.py b/ext/python-a2s-rcon-api/service.py index ecd31ea..f1f988e 100644 --- a/ext/python-a2s-rcon-api/service.py +++ b/ext/python-a2s-rcon-api/service.py @@ -50,6 +50,7 @@ class SourceBackend: @self.app.post("/api/rcon") async def execute_rcon(request: RCON_Request): + #print(request.__dict__) if not request.fulled_data: return Response(status_code=409) try: @@ -64,6 +65,7 @@ class SourceBackend: @self.app.post("/api/a2s/info") async def get_a2s_info(request: A2S_request): + #print(request.__dict__) if not request.address: return Response(status_code=409) try: @@ -74,6 +76,7 @@ class SourceBackend: @self.app.post("/api/a2s/players") async def get_a2s_players(request: A2S_request): + #print(request.__dict__) if not request.address: return Response(status_code=409) try: @@ -84,7 +87,11 @@ class SourceBackend: @self.app.post("/api/players") async def get_players(request: RCON_Request): + #print(request.__dict__) a2s_players = await VALVE_SERVER_PLAYERS(request.tuple_address) + if(request.password == None): + return Response(status_code = 403) + status_lines:str = await VALVE_SERVER_RCON( "status", host = request.address, diff --git a/src/main/java/app/entities/Stats.java b/src/main/java/app/entities/Stats.java index 3bc48f7..cca6090 100644 --- a/src/main/java/app/entities/Stats.java +++ b/src/main/java/app/entities/Stats.java @@ -77,43 +77,4 @@ public class Stats { uniq.merge(key, value, (x, y) -> y); } - public void RefreshServerA2SData(String server_name) { - //try (SourceQueryClient sourceQueryClient = context.getBean(SourceQueryClient.class)) { - try (SourceQueryClient sourceQueryClient = getServers().get(server_name).GetSourceQueryClient()) { - sourceQueryClient.getInfo(getServers().get(server_name).getInetAddress()).whenComplete((info, error) -> { - if (!sourceQueryClient.getExecutor().isShutdown()) sourceQueryClient.getExecutor().shutdown(); - if (error != null) { - getServers().get(server_name).SetDownStatus(); - return; - } - getServers().get(server_name).UpdateStatusFromA2S(info); - }).join(); - } catch (CompletionException | IOException err) { - } - - if (!getServers().get(server_name).isStatus() || getServers().get(server_name).getPlayer_count() < 1) { - return; - } - //////////////////////////////////////////////////////////////////////// - //If player count > 0 make base player request - //////////////////////////////////////////////////////////////////////// - //try (SourceQueryClient sourceQueryClient = context.getBean(SourceQueryClient.class)) { - try (SourceQueryClient sourceQueryClient = getServers().get(server_name).GetSourceQueryClient()) { - sourceQueryClient.getPlayers(getServers().get(server_name).getInetAddress()).whenComplete((players, error) -> { - if (!sourceQueryClient.getExecutor().isShutdown()) sourceQueryClient.getExecutor().shutdown(); - if (error != null) return; - getServers().get(server_name).UpdatePlayersFromA2S(players); - }).join(); - } catch (CompletionException | IOException err) {} - /////////////////////////////////////////////////////////////////////// - //Extend current players of rcon result - ////////////////////////////////////////////////////////////////////// - try { - String response = getServers().get(server_name).ExecuteRCON("status"); - getServers().get(server_name).UpdatePlayersFromRCON(response); - } catch (RconException | CompletionException err) { - return; - } - } - } diff --git a/src/main/java/app/entities/a2s/external/ExternalValveClient.java b/src/main/java/app/entities/a2s/external/ExternalValveClient.java new file mode 100644 index 0000000..e762989 --- /dev/null +++ b/src/main/java/app/entities/a2s/external/ExternalValveClient.java @@ -0,0 +1,74 @@ +package app.entities.a2s.external; + +import app.entities.a2s.requests.A2SRequest; +import app.entities.a2s.requests.RCONRequest; +import app.entities.server.players.RCONPlayer; +import app.utils.SteamIDConverter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import jakarta.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.client.RestTemplate; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public abstract class ExternalValveClient { + @JsonIgnore + RestTemplate restTemplate; + @JsonIgnore + boolean enabled = true; + @JsonIgnore + public String gateway = System.getenv("A2S_BACKEND_URL"); + + public ExternalValveClient(){ + restTemplate = new RestTemplate(); + CheckApi(); + } + + public void CheckApi(){ + System.out.printf("Check status: %s/api/ping\n", gateway); + try { + enabled = restTemplate.getForEntity("%s/api/ping".formatted(gateway), HashMap.class).getBody().containsKey("pong"); + } catch (Exception err) { + System.out.print("A2S Backend not respond\n"); + } + } + @JsonIgnore + public String ExecuteRCON(RCONRequest request){ + if(!enabled) { + System.out.printf("External client not enabled, cannot get rcon on %s\n", gateway); + return "not enabled"; + } + try { + return restTemplate.postForEntity("%s/api/rcon".formatted(gateway), request, String.class).getBody(); + } catch (Exception err) { + return "backend error"; + } + } + @JsonIgnore + public HashMap GetA2SInfo(A2SRequest request){ + if(!enabled) { + System.out.printf("External client not enabled, cannot get a2s on %s\n", gateway); + return null; + } + try { + return restTemplate.postForEntity("%s/api/a2s/info".formatted(gateway), request, HashMap.class).getBody(); + } catch (Exception err) { + return null; + } + } + + public List GetRCONPlayers(RCONRequest request){ + if(!enabled) { + System.out.printf("External client not enabled, cannot get rcon players on %s\n", gateway); + return null; + } + try { + return restTemplate.postForEntity("%s/api/players".formatted(gateway), request, List.class).getBody(); + } catch (Exception err) { + err.printStackTrace(); + return List.of(); + } + } +} diff --git a/src/main/java/app/entities/a2s/internal/InternalValveClient.java b/src/main/java/app/entities/a2s/internal/InternalValveClient.java new file mode 100644 index 0000000..55a41cf --- /dev/null +++ b/src/main/java/app/entities/a2s/internal/InternalValveClient.java @@ -0,0 +1,162 @@ +package app.entities.a2s.internal; + +import app.entities.a2s.requests.RCONRequest; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.ibasco.agql.core.enums.RateLimitType; +import com.ibasco.agql.core.util.FailsafeOptions; +import com.ibasco.agql.core.util.GeneralOptions; +import com.ibasco.agql.protocols.valve.source.query.SourceQueryClient; +import com.ibasco.agql.protocols.valve.source.query.SourceQueryOptions; +import com.ibasco.agql.protocols.valve.source.query.rcon.SourceRconClient; +import com.ibasco.agql.protocols.valve.source.query.rcon.SourceRconOptions; +import com.ibasco.agql.protocols.valve.source.query.rcon.message.SourceRconAuthResponse; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public abstract class InternalValveClient { + @JsonIgnore + private ExecutorService executorServices_query; + @JsonIgnore + private ExecutorService executorServices_rcon; + @JsonIgnore + private SourceRconClient sourceRconClient; + @JsonIgnore + private SourceQueryClient sourceQueryClient; + + @JsonIgnore + public SourceRconClient GetSourceRconClient() { + if (executorServices_rcon == null) executorServices_rcon = Executors.newCachedThreadPool(); + if (sourceRconClient == null) { + SourceRconOptions options = SourceRconOptions.builder() + .option(FailsafeOptions.FAILSAFE_RATELIMIT_TYPE, RateLimitType.SMOOTH) + .option(GeneralOptions.THREAD_EXECUTOR_SERVICE, executorServices_rcon) + .build(); + sourceRconClient = new SourceRconClient(options); + } + return sourceRconClient; + } + + + @JsonIgnore + public SourceQueryClient GetSourceQueryClient() { + if (executorServices_query == null) executorServices_query = Executors.newCachedThreadPool(); + if (sourceQueryClient == null) { + SourceQueryOptions options = SourceQueryOptions.builder() + .option(FailsafeOptions.FAILSAFE_RATELIMIT_TYPE, RateLimitType.SMOOTH) + .option(GeneralOptions.THREAD_EXECUTOR_SERVICE, executorServices_query) + .build(); + sourceQueryClient = new SourceQueryClient(options); + } + return sourceQueryClient; + } + + @JsonIgnore + public String ExecuteRCON(RCONRequest request) { + try (SourceRconClient rconClient = GetSourceRconClient()) { + SourceRconAuthResponse response = rconClient.authenticate(request.getInetAddress(), request.getPassword().getBytes()).join(); + if (!response.isAuthenticated()) { + if (!rconClient.getExecutor().isShutdown()) rconClient.getExecutor().shutdown(); + return null; + } + return rconClient.execute(request.getInetAddress(), request.getCommand()) + .thenApplyAsync(out -> { + rconClient.cleanup(true); + return out.getResult(); + }) + .join(); + } catch (Exception err) { + return ""; + } + } + + /* + * public void UpdateStatusFromA2S(SourceQueryInfoResponse response) { + SetDownStatus(); + if (response == null) return; + + setMax_players(response.getResult().getMaxPlayers()); + setPlayer_count(response.getResult().getNumOfPlayers()); + setMap(response.getResult().getMapName()); + setStatus(true); + }*/ + /* + * public void RefreshServerA2SData(String server_name) { + //try (SourceQueryClient sourceQueryClient = context.getBean(SourceQueryClient.class)) { + try (SourceQueryClient sourceQueryClient = getServers().get(server_name).GetSourceQueryClient()) { + sourceQueryClient.getInfo(getServers().get(server_name).getInetAddress()).whenComplete((info, error) -> { + if (!sourceQueryClient.getExecutor().isShutdown()) sourceQueryClient.getExecutor().shutdown(); + if (error != null) { + getServers().get(server_name).SetDownStatus(); + return; + } + getServers().get(server_name).UpdateStatusFromA2S(info); + }).join(); + } catch (CompletionException | IOException err) { + } + + if (!getServers().get(server_name).isStatus() || getServers().get(server_name).getPlayer_count() < 1) { + return; + } + //////////////////////////////////////////////////////////////////////// + //If player count > 0 make base player request + //////////////////////////////////////////////////////////////////////// + //try (SourceQueryClient sourceQueryClient = context.getBean(SourceQueryClient.class)) { + try (SourceQueryClient sourceQueryClient = getServers().get(server_name).GetSourceQueryClient()) { + sourceQueryClient.getPlayers(getServers().get(server_name).getInetAddress()).whenComplete((players, error) -> { + if (!sourceQueryClient.getExecutor().isShutdown()) sourceQueryClient.getExecutor().shutdown(); + if (error != null) return; + getServers().get(server_name).UpdatePlayersFromA2S(players); + }).join(); + } catch (CompletionException | IOException err) {} + /////////////////////////////////////////////////////////////////////// + //Extend current players of rcon result + ////////////////////////////////////////////////////////////////////// + try { + String response = getServers().get(server_name).ExecuteRCON("status"); + getServers().get(server_name).UpdatePlayersFromRCON(response); + } catch (RconException | CompletionException err) { + return; + } + }*/ + /* + * public void UpdatePlayersFromA2S(SourceQueryPlayerResponse response) { + a2s_players.clear(); + if (response != null) { + response.getResult().stream().map(app.entities.server.players.SourcePlayer::new).forEach(a2s_players::add); + } + } + + public void UpdatePlayersFromRCON(String response) { + players.clear(); + int start_index = response.indexOf("# userid"); + if (start_index == -1) return; + List players_list = Arrays.stream(response.substring(start_index, response.length()).split("\n")).toList(); + boolean skip_table_header = true; + for(String player_text: players_list) { + if (skip_table_header || player_text.length() < 1) { + skip_table_header = false; + continue; + } + ///////////////////////////////////////////////////// + List player_line = Arrays.stream(player_text.split("\\s+")).toList(); + RCONPlayer player; + try { + player = new RCONPlayer(player_line); + } catch (Exception parse_err) { + System.out.println("Cannot parse: " + player_line); + continue; + } + + for (SourcePlayer sourcePlayer: a2s_players) { + if (sourcePlayer.getName().equals(player.getName())) { + player.setScore(sourcePlayer.getScore()); + a2s_players.remove(sourcePlayer); + players.add(player); + break; + } + } + } + a2s_players.clear(); + }*/ +} diff --git a/src/main/java/app/entities/a2s/requests/A2SRequest.java b/src/main/java/app/entities/a2s/requests/A2SRequest.java new file mode 100644 index 0000000..c58984f --- /dev/null +++ b/src/main/java/app/entities/a2s/requests/A2SRequest.java @@ -0,0 +1,17 @@ +package app.entities.a2s.requests; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import lombok.AllArgsConstructor; +import lombok.Data; + +@JsonSerialize +@Data +public class A2SRequest { + String server; + + public A2SRequest(){} + + public A2SRequest(String server){ + this.server = server; + } +} diff --git a/src/main/java/app/entities/a2s/requests/RCONRequest.java b/src/main/java/app/entities/a2s/requests/RCONRequest.java new file mode 100644 index 0000000..e9f0cc4 --- /dev/null +++ b/src/main/java/app/entities/a2s/requests/RCONRequest.java @@ -0,0 +1,23 @@ +package app.entities.a2s.requests; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.net.InetSocketAddress; + +@JsonSerialize +@AllArgsConstructor +@Getter +public class RCONRequest extends A2SRequest{ + String server; + String password; + String command; + + @JsonIgnore + public InetSocketAddress getInetAddress() { + String[] splitted_address = server.split(":", 2); + return new InetSocketAddress(splitted_address[0], Integer.parseInt(splitted_address[1])); + } +} diff --git a/src/main/java/app/entities/server/BaseServer.java b/src/main/java/app/entities/server/BaseServer.java deleted file mode 100644 index ab54e97..0000000 --- a/src/main/java/app/entities/server/BaseServer.java +++ /dev/null @@ -1,51 +0,0 @@ -package app.entities.server; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.ibasco.agql.core.enums.RateLimitType; -import com.ibasco.agql.core.util.FailsafeOptions; -import com.ibasco.agql.core.util.GeneralOptions; -import com.ibasco.agql.protocols.valve.source.query.SourceQueryClient; -import com.ibasco.agql.protocols.valve.source.query.SourceQueryOptions; -import com.ibasco.agql.protocols.valve.source.query.rcon.SourceRconClient; -import com.ibasco.agql.protocols.valve.source.query.rcon.SourceRconOptions; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -public abstract class BaseServer { - @JsonIgnore - private ExecutorService executorServices_query; - @JsonIgnore - private ExecutorService executorServices_rcon; - @JsonIgnore - private SourceRconClient sourceRconClient; - @JsonIgnore - private SourceQueryClient sourceQueryClient; - - @JsonIgnore - public SourceRconClient GetSourceRconClient() { - if (executorServices_rcon == null) executorServices_rcon = Executors.newCachedThreadPool(); - if (sourceRconClient == null) { - SourceRconOptions options = SourceRconOptions.builder() - .option(FailsafeOptions.FAILSAFE_RATELIMIT_TYPE, RateLimitType.SMOOTH) - .option(GeneralOptions.THREAD_EXECUTOR_SERVICE, executorServices_rcon) - .build(); - sourceRconClient = new SourceRconClient(options); - } - return sourceRconClient; - } - - - @JsonIgnore - public SourceQueryClient GetSourceQueryClient() { - if (executorServices_query == null) executorServices_query = Executors.newCachedThreadPool(); - if (sourceQueryClient == null) { - SourceQueryOptions options = SourceQueryOptions.builder() - .option(FailsafeOptions.FAILSAFE_RATELIMIT_TYPE, RateLimitType.SMOOTH) - .option(GeneralOptions.THREAD_EXECUTOR_SERVICE, executorServices_query) - .build(); - sourceQueryClient = new SourceQueryClient(options); - } - return sourceQueryClient; - } -} diff --git a/src/main/java/app/entities/server/Server.java b/src/main/java/app/entities/server/Server.java index 6db08a0..fb17bab 100644 --- a/src/main/java/app/entities/server/Server.java +++ b/src/main/java/app/entities/server/Server.java @@ -1,37 +1,24 @@ package app.entities.server; +import app.entities.a2s.requests.A2SRequest; import app.entities.other.SteamID; -import app.entities.server.players.DefaultPlayer; +import app.entities.a2s.external.ExternalValveClient; +import app.entities.a2s.requests.RCONRequest; import app.entities.server.players.RCONPlayer; import app.entities.server.players.SourcePlayer; import com.fasterxml.jackson.annotation.*; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.ibasco.agql.core.enums.RateLimitType; -import com.ibasco.agql.core.util.FailsafeOptions; -import com.ibasco.agql.core.util.GeneralOptions; -import com.ibasco.agql.protocols.valve.source.query.SourceQueryClient; -import com.ibasco.agql.protocols.valve.source.query.SourceQueryOptions; import com.ibasco.agql.protocols.valve.source.query.info.SourceQueryInfoResponse; import com.ibasco.agql.protocols.valve.source.query.players.SourceQueryPlayerResponse; -import com.ibasco.agql.protocols.valve.source.query.rcon.SourceRconClient; -import com.ibasco.agql.protocols.valve.source.query.rcon.SourceRconOptions; -import com.ibasco.agql.protocols.valve.source.query.rcon.message.SourceRconAuthResponse; -import com.ibasco.agql.protocols.valve.source.query.rcon.message.SourceRconCmdResponse; -import jakarta.persistence.criteria.CriteriaBuilder; import lombok.Data; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; @Data -public class Server extends BaseServer { +public class Server extends ExternalValveClient { String name; String description; String address; @@ -45,8 +32,6 @@ public class Server extends BaseServer { List naming; HashMap uniq = new HashMap<>(); List players = new ArrayList<>(); - @JsonIgnore - List a2s_players = new ArrayList<>(); @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) private String db; @@ -55,83 +40,45 @@ public class Server extends BaseServer { @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) private String rcon_password; + @Override + public String toString() { + return "Address: %s\nPlayers: %d/%d\nMap: %s".formatted(address, player_count, max_players, map); + } + public void UpdateUniq(String key, Long count) { uniq.merge(key, count, (x,y) -> y); } - @JsonIgnore - public InetSocketAddress getInetAddress() { - String[] splitted_address = address.split(":", 2); - return new InetSocketAddress(splitted_address[0], Integer.parseInt(splitted_address[1])); + public void RefreshServerA2SData() { + UpdateStatusFromA2S(); + /////////////////////////////////////////// + if (!isStatus() || getPlayer_count() < 1) { + return; + } + /////////////////////////////////////////// + UpdatePlayers(); } - public void UpdateStatusFromA2S(SourceQueryInfoResponse response) { + public void UpdateStatusFromA2S(){ SetDownStatus(); - if (response == null) return; - - setMax_players(response.getResult().getMaxPlayers()); - setPlayer_count(response.getResult().getNumOfPlayers()); - setMap(response.getResult().getMapName()); - setStatus(true); - } - - public void UpdatePlayersFromA2S(SourceQueryPlayerResponse response) { - a2s_players.clear(); + HashMap response = super.GetA2SInfo(new A2SRequest(getAddress())); if (response != null) { - response.getResult().stream().map(app.entities.server.players.SourcePlayer::new).forEach(a2s_players::add); + setMax_players((int) response.get("max_players")); + setPlayer_count((int) response.get("player_count")); + setMap((String) response.get("map_name")); + setStatus(true); } } - public void UpdatePlayersFromRCON(String response) { + public void UpdatePlayers(){ players.clear(); - int start_index = response.indexOf("# userid"); - if (start_index == -1) return; - List players_list = Arrays.stream(response.substring(start_index, response.length()).split("\n")).toList(); - boolean skip_table_header = true; - for(String player_text: players_list) { - if (skip_table_header || player_text.length() < 1) { - skip_table_header = false; - continue; - } - ///////////////////////////////////////////////////// - List player_line = Arrays.stream(player_text.split("\\s+")).toList(); - RCONPlayer player; - try { - player = new RCONPlayer(player_line); - } catch (Exception parse_err) { - System.out.println("Cannot parse: " + player_line); - continue; - } - - for (SourcePlayer sourcePlayer: a2s_players) { - if (sourcePlayer.getName().equals(player.getName())) { - player.setScore(sourcePlayer.getScore()); - a2s_players.remove(sourcePlayer); - players.add(player); - break; - } - } - } - a2s_players.clear(); + players = GetRCONPlayers( + new RCONRequest(getAddress(), getRcon_password(), "status") + ); } - @JsonIgnore - public String ExecuteRCON(String command) { - try (SourceRconClient rconClient = GetSourceRconClient()) { - SourceRconAuthResponse response = rconClient.authenticate(getInetAddress(), getRcon_password().getBytes()).join(); - if (!response.isAuthenticated()) { - if (!rconClient.getExecutor().isShutdown()) rconClient.getExecutor().shutdown(); - return null; - } - return rconClient.execute(getInetAddress(), command) - .thenApplyAsync(out -> { - rconClient.cleanup(true); - return out.getResult(); - }) - .join(); - } catch (Exception err) { - return ""; - } + public String ExecuteRCON(String command){ + return super.ExecuteRCON(new RCONRequest(getAddress(), getRcon_password(), command)); } public void SetDownStatus() { @@ -144,7 +91,7 @@ public class Server extends BaseServer { @JsonIgnore public RCONPlayer searchPlayer(SteamID steamID){ - return (RCONPlayer) players.stream().filter(player -> player.getSteam().is(steamID)).findFirst().orElse(null); + return players.stream().filter(player -> player.getSteam().is(steamID)).findFirst().orElse(null); } @JsonIgnore diff --git a/src/main/java/app/entities/server/players/DefaultPlayer.java b/src/main/java/app/entities/server/players/DefaultPlayer.java index 0b135ba..5fa64f5 100644 --- a/src/main/java/app/entities/server/players/DefaultPlayer.java +++ b/src/main/java/app/entities/server/players/DefaultPlayer.java @@ -8,5 +8,4 @@ public class DefaultPlayer { String name; int score; int id = 0; - SteamID steam = new SteamID(); } diff --git a/src/main/java/app/entities/server/players/RCONPlayer.java b/src/main/java/app/entities/server/players/RCONPlayer.java index bac52aa..1ae6a64 100644 --- a/src/main/java/app/entities/server/players/RCONPlayer.java +++ b/src/main/java/app/entities/server/players/RCONPlayer.java @@ -3,8 +3,13 @@ package app.entities.server.players; import app.entities.other.SteamID; import app.entities.server.players.DefaultPlayer; import app.utils.SteamIDConverter; +import com.fasterxml.jackson.annotation.JsonGetter; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import lombok.Data; +import lombok.Getter; import java.util.List; @@ -18,6 +23,13 @@ public class RCONPlayer extends DefaultPlayer { int ping; String state; String steam2; + SteamID steam; + + @JsonGetter("steam") + public SteamID getSteam() { + if (steam == null) steam = SteamIDConverter.getSteamID(steam2); + return steam; + } public RCONPlayer(List status_line) { id = Integer.parseInt(status_line.get(1)); diff --git a/src/main/java/app/updates/PlayersUpdater.java b/src/main/java/app/updates/PlayersUpdater.java index 5524843..02288e8 100644 --- a/src/main/java/app/updates/PlayersUpdater.java +++ b/src/main/java/app/updates/PlayersUpdater.java @@ -29,7 +29,7 @@ public class PlayersUpdater extends BaseUpdater{ if (update) { stats.getServers().forEach((server_name, server) -> { CreateTaskUpdater(() -> { - stats.RefreshServerA2SData(server_name); + server.RefreshServerA2SData(); stats.getUpdates().merge(server_name, Instant.now().getEpochSecond(), (x, y) -> y); return null; }, timeout); diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index d8296f7..9cb9f29 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -45,6 +45,8 @@ backend: kd: 300 vip: discord: ${VIP_DISCORD_WEBHOOK} + a2s: + backend_url: ${A2S_BACKEND_URL} logging: level: diff --git a/src/test/java/app/servers/TestExternalA2S.java b/src/test/java/app/servers/TestExternalA2S.java new file mode 100644 index 0000000..37b23e6 --- /dev/null +++ b/src/test/java/app/servers/TestExternalA2S.java @@ -0,0 +1,29 @@ +package app.servers; + +import app.entities.a2s.external.ExternalValveClient; +import app.entities.server.Server; +import org.junit.Test; + +public class TestExternalA2S { + Server server = new Server(); + + public TestExternalA2S(){ + server.gateway = System.getenv("A2S_BACKEND_URL"); + server.setAddress(System.getenv("TEST_SERVER")); + } + + @Test + public void CheckA2SInfo(){ + server.UpdateStatusFromA2S(); + System.out.printf("%s\n", server); + } + + @Test + public void CheckPlayers(){ + server.setRcon_password(System.getenv("TEST_SERVER_PASSWORD")); + server.UpdatePlayers(); + System.out.print(server.getPlayers()); + } + + +}