Browse Source

sourcepawn integration

master
gsd 1 year ago
parent
commit
74eff9f728
  1. 1
      .gitignore
  2. 221
      ext/sourcepawn-client/Facti13BackendIntegration.sp
  3. 38
      src/main/java/app/controllers/server/ServerUpdaterController.java
  4. 5
      src/main/java/app/entities/a2s/external/ExternalValveClient.java
  5. 44
      src/main/java/app/entities/server/Server.java
  6. 11
      src/main/java/app/entities/server/request/PlayerOnServer.java
  7. 12
      src/main/java/app/entities/server/request/ServerRequestBody.java
  8. 2
      src/main/java/app/updates/BaseUpdater.java
  9. 18
      src/main/java/app/updates/PlayersUpdater.java
  10. 2
      src/main/resources/application.yaml

1
.gitignore

@ -1,2 +1,3 @@
/.idea/
/target/
/ext/plugins/

221
ext/sourcepawn-client/Facti13BackendIntegration.sp

@ -0,0 +1,221 @@
#include <sourcemod>
#include <ripext>
#define PLUGIN_VERSION "1.0"
public Plugin myinfo = {
name = "Facti13 Backend Integration",
author = "gsd",
description = "Update server info directly without RCON",
version = PLUGIN_VERSION,
url = "https://tf2.pblr-nyk.pro"
}
/* CONVAR */
Handle g_server_id_convar = INVALID_HANDLE;
Handle g_api_secret_key_convar = INVALID_HANDLE;
Handle g_api_gateway_convar = INVALID_HANDLE;
char g_server_id[32];
char g_secretkey[64];
char g_gateway[128];
char g_cookie[128];
char g_url[128];
Handle g_timer = INVALID_HANDLE;
int g_lastupdate = 0;
bool g_warned = true;
bool g_setuped = false;
bool g_updating = false;
stock SetupConVar() {
g_server_id_convar = CreateConVar("sm_fbi_server_id", "srv8", "fbi server id", FCVAR_PROTECTED);
g_api_secret_key_convar = CreateConVar("sm_fbi_secretkey", "123456789", "fbi secret key", FCVAR_PROTECTED);
g_api_gateway_convar = CreateConVar("sm_fbi_gateway", "http://192.168.3.50:8080/api/server/%s", "fbi gateway", FCVAR_PROTECTED);
//
HookConVarChange(g_server_id_convar, OnServerIdChanged);
HookConVarChange(g_api_secret_key_convar, OnSecretKeyChanged);
HookConVarChange(g_api_gateway_convar, OnGatewayChanged);
//
GetConVarString(g_server_id_convar, g_server_id, sizeof(g_server_id));
GetConVarString(g_api_secret_key_convar, g_secretkey, sizeof(g_secretkey));
GetConVarString(g_api_gateway_convar, g_gateway, sizeof(g_gateway));
//
UpdateUrlData();
}
stock UnSetupConvar() {
UnhookConVarChange(g_server_id_convar, OnServerIdChanged);
UnhookConVarChange(g_api_secret_key_convar, OnSecretKeyChanged);
UnhookConVarChange(g_api_gateway_convar, OnGatewayChanged);
}
public OnServerIdChanged(Handle:cvar, const String:oldVal[], const String:newVal[]) {
strcopy(g_server_id, sizeof(g_server_id), newVal);
LogMessage("[FBI] Server id now: %s", g_server_id);
UpdateUrlData();
}
public OnSecretKeyChanged(Handle:cvar, const String:oldVal[], const String:newVal[]) {
strcopy(g_secretkey, sizeof(g_secretkey), newVal);
LogMessage("[FBI] Secret key now lenght: %d", strlen(g_secretkey));
UpdateUrlData();
}
public OnGatewayChanged(Handle:cvar, const String:oldVal[], const String:newVal[]) {
strcopy(g_gateway, sizeof(g_gateway), newVal);
LogMessage("[FBI] Gateway now: %s", g_gateway);
UpdateUrlData();
}
stock UpdateUrlData(){
g_setuped = false;
if (strlen(g_server_id)>0 && strlen(g_secretkey)>0){
Format(g_url, sizeof(g_url), g_gateway, g_server_id);
Format(g_cookie, sizeof(g_cookie), "secretkey=%s", g_secretkey);
g_setuped = true;
LogMessage("[FBI] Successful set URL and SecretKey");
} else
LogError("[FBI] Failed URL and SecretKey");
}
stock HTTPRequest createRequest() {
HTTPRequest client = INVALID_HANDLE;
if (strlen(g_url)>0) {
client = new HTTPRequest(g_url);
client.SetHeader("Cookie", g_cookie);
client.Timeout = 3;
} else {
LogMessage("[FBI] Client not builded");
}
return client;
}
stock JSONObject createPayload() {
JSONObject payload = new JSONObject();
payload.SetBool("status", true);
payload.SetInt("player_count", GetClientCount(true));
payload.SetInt("max_players", MaxClients);
char map_name[128];
GetCurrentMap(map_name, sizeof(map_name));
payload.SetString("map_name", map_name);
JSONArray players = new JSONArray();
for(int client = 0; client <= MAXPLAYERS; client++) {
if (IsValidClient(client)) {
JSONObject player = new JSONObject();
/* Name */
char name[64];
GetClientName(client, name, sizeof(name));
player.SetString("name", name);
/* Score */
player.SetInt("score", GetClientFrags(client))
/* Duration */
char duration[16];
int ct = RoundFloat(GetClientTime(client));
int h = ct / 3600;
int m = ct % 3600 / 60;
int s = ct % 60;
Format(duration, sizeof(duration), "%02d:%02d:%02d", h, m, s);
player.SetString("duration", duration);
/* Id */
player.SetInt("id", GetClientUserId(client));
/* Ip */
char ip[32];
GetClientIP(client, ip, sizeof(ip), false);
player.SetString("ip", ip);
/* Loss */
player.SetInt("loss",RoundFloat(GetClientAvgLoss(client, NetFlow_Both)*1000.0));
/* Ping */
player.SetInt("ping",RoundFloat(GetClientLatency(client, NetFlow_Both)*1000.0));
/* State */
player.SetString("state", "active")
/* Steam 2 так надо, не надо спрашивать почему*/
char steam2[32];
GetClientAuthId(client, AuthId_Steam3, steam2, sizeof(steam2));
player.SetString("steam2", steam2);
players.Push(player);
}
}
payload.Set("players", players);
return payload;
}
stock UpdateStatus(){
if (!g_setuped) return;
if (g_updating) return;
if (GetTime() - g_lastupdate < 10) return;
g_updating = true;
JSONObject payload = createPayload();
createRequest().Post(payload, Request_Callback);
g_lastupdate = GetTime();
}
static void Request_Callback(HTTPResponse response, any value){
g_updating = false;
if (response.Status == HTTPStatus_OK){
if(!g_warned) {
LogMessage("Success send payload, after error");
g_warned = true;
}
return;
}
else{
if(g_warned) {
LogMessage("Failed response! Code: %i", response.Status);
g_warned = false;
}
return;
}
}
stock IsValidClient(int client){
if(client > 4096){
client = EntRefToEntIndex(client);
}
if(client < 1 || client > MaxClients) return false;
if(!IsClientInGame(client)) return false;
if(IsFakeClient(client)) return false;
return true;
}
public OnPluginStart() {
SetupConVar();
RegAdminCmd("fbi_test", TestUpdate, ADMFLAG_ROOT);
g_timer = CreateTimer(15, timerCall, 0, TIMER_REPEAT | TIMER_FLAG_NO_MAPCHANGE);
}
public Action timerCall(Handle:t, any:d) {
UpdateStatus();
}
public OnPluginEnd() {
UnSetupConvar();
if (g_timer != INVALID_HANDLE) {
KillTimer(g_timer);
}
}
public Action TestUpdate(int client, int args){
UpdateStatus();
ReplyToCommand(client, "OK!")
return Plugin_Handled;
}
public OnClientDisconnect(int client) {
UpdateStatus();
}
public OnClientAuthorized(int client) {
UpdateStatus();
}
public OnMapStart() {
UpdateStatus();
}
public OnMapEnd() {
UpdateStatus();
}

38
src/main/java/app/controllers/server/ServerUpdaterController.java

@ -0,0 +1,38 @@
package app.controllers.server;
import app.annotations.enums.AuthMethod;
import app.annotations.interfaces.CheckWebAccess;
import app.entities.Stats;
import app.entities.server.request.ServerRequestBody;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("api/server")
public class ServerUpdaterController {
private Stats stats;
@Autowired
ServerUpdaterController(Stats stats) {
this.stats = stats;
}
@PostMapping(value = "/{srv}")
@CheckWebAccess(auth_method = AuthMethod.SECRET_KEY)
public ResponseEntity updateServer(HttpServletRequest request, @PathVariable String srv, @RequestBody ServerRequestBody serverRequestBody) {
if (!stats.getServers().containsKey(srv)) return new ResponseEntity<>(HttpStatus.NOT_FOUND);
stats.getServers().get(srv).RefreshServerFromRequest(serverRequestBody);
return new ResponseEntity(HttpStatus.OK);
}
@DeleteMapping(value = "/{srv}")
@CheckWebAccess(auth_method = AuthMethod.SECRET_KEY)
public ResponseEntity downServer(HttpServletRequest request, @PathVariable String srv) {
if (!stats.getServers().containsKey(srv)) return new ResponseEntity<>(HttpStatus.NOT_FOUND);
stats.getServers().get(srv).RefreshServerFromRequest(null);
return new ResponseEntity(HttpStatus.OK);
}
}

5
src/main/java/app/entities/a2s/external/ExternalValveClient.java

@ -11,7 +11,7 @@ import org.springframework.web.client.RestTemplate;
import java.util.*;
public abstract class ExternalValveClient {
public abstract class ExternalValveClient extends BaseUpdater {
@JsonIgnore
RestTemplate restTemplate;
@JsonIgnore
@ -19,7 +19,8 @@ public abstract class ExternalValveClient {
@JsonIgnore
public String gateway = System.getenv("A2S_BACKEND_URL");
private final Logger logger = LoggerFactory.getLogger(ExternalValveClient.class);
@JsonIgnore
public final Logger logger = LoggerFactory.getLogger(ExternalValveClient.class);
public ExternalValveClient(){
restTemplate = new RestTemplate();

44
src/main/java/app/entities/server/Server.java

@ -5,9 +5,11 @@ import app.entities.other.SteamID;
import app.entities.a2s.external.ExternalValveClient;
import app.entities.a2s.requests.RCONRequest;
import app.entities.server.players.RCONPlayer;
import app.entities.server.request.ServerRequestBody;
import com.fasterxml.jackson.annotation.*;
import lombok.Data;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@ -28,6 +30,7 @@ public class Server extends ExternalValveClient {
List<String> naming;
HashMap<String, Long> uniq = new HashMap<>();
List<RCONPlayer> players = new ArrayList<>();
long last_update = 0;
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String db;
@ -40,6 +43,26 @@ public class Server extends ExternalValveClient {
double[] city_pos = {0.0, 0.0};
public Server() {
if (System.getenv("A2S") != null && System.getenv("A2S").equals("false")) {
logger.warn("Create cache last time updater: {} server", name);
CreateTaskUpdater(() -> {
logger.info("Update players cache from: {} server", name);
RefreshLastCheck(60);
return null;
}, 15000);
} else if (System.getenv("A2S") != null && System.getenv("A2S").equals("true")) {
logger.warn("Create a2s updater: {} server", name);
CreateTaskUpdater(() -> {
logger.info("Update players from: {} server", name);
RefreshServerA2SData();
return null;
}, 60000);
} else {
logger.warn("{} server not be update", name);
}
}
@Override
public String toString() {
return "SERVER> %s | %s\nSTATS > %d/%d | %s\nCONFIG> grab limit: %d".formatted(
@ -61,6 +84,27 @@ public class Server extends ExternalValveClient {
}
///////////////////////////////////////////
UpdatePlayers();
last_update = Instant.now().getEpochSecond();
}
public void RefreshLastCheck(int sec) {
if (Instant.now().getEpochSecond() - last_update > sec) {
SetDownStatus();
}
}
public void RefreshServerFromRequest(ServerRequestBody serverRequestBody) {
SetDownStatus();
if (serverRequestBody != null) {
setMax_players(serverRequestBody.getMax_players());
setPlayer_count(serverRequestBody.getPlayer_count());
setMap(serverRequestBody.getMap_name());
setStatus(serverRequestBody.isStatus());
//
players.clear();
players = new ArrayList<>(List.of(serverRequestBody.getPlayers()));
}
last_update = Instant.now().getEpochSecond();
}
public void UpdateStatusFromA2S(){

11
src/main/java/app/entities/server/request/PlayerOnServer.java

@ -0,0 +1,11 @@
package app.entities.server.request;
import app.entities.server.players.RCONPlayer;
import com.fasterxml.jackson.annotation.JsonGetter;
import lombok.Data;
@Data
public class PlayerOnServer extends RCONPlayer {
float[] pos = {};
//int duration_seconds = 0;
}

12
src/main/java/app/entities/server/request/ServerRequestBody.java

@ -0,0 +1,12 @@
package app.entities.server.request;
import lombok.Data;
@Data
public class ServerRequestBody {
int max_players = 0;
int player_count = 0;
String map_name = "";
boolean status = false;
PlayerOnServer[] players = {};
}

2
src/main/java/app/updates/BaseUpdater.java

@ -3,6 +3,8 @@ package app.updates;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Supplier;

18
src/main/java/app/updates/PlayersUpdater.java

@ -7,6 +7,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.io.IOException;
@ -31,10 +32,11 @@ public class PlayersUpdater extends BaseUpdater{
this.stats = stats;
}
@PostConstruct
/*
public void updateValues() {
if (update) {
logger.warn("Updater enabled");
if (stats.getServers().size()==0) logger.error("Not found servers to update");
stats.getServers().forEach((server_name, server) -> {
CreateTaskUpdater(() -> {
logger.info("Update players from: {} server", server_name);
@ -43,10 +45,22 @@ public class PlayersUpdater extends BaseUpdater{
return null;
}, timeout);
});
} else {
logger.warn("A2S Refresh disabled! Enable last timecheck");
if (stats.getServers().size()==0) logger.error("Not found servers to update");
stats.getServers().forEach((server_name, server) -> {
CreateTaskUpdater(() -> {
logger.info("Update players from: {} server", server_name);
server.RefreshLastCheck(60);
return null;
}, 15000);
});
}
}
}*/
public void burstUpdater() {
if (!update) return;
ExecutorService executor = Executors.newCachedThreadPool();
List tasks = new ArrayList<>();
stats.getServers().forEach((server_name, server) -> {

2
src/main/resources/application.yaml

@ -68,6 +68,6 @@ backend:
logging:
level:
com.ibasco.agql.core.util.*: OFF
app.updates.PlayersUpdater: ERROR
app.entities.a2s.external.ExternalValveClient: WARN
app.utils.SaltedCookie: INFO
app.annotations.impl.WebAccessAspect: WARN
Loading…
Cancel
Save