diff --git a/.gitignore b/.gitignore index 3dff92f..8d7990d 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,5 @@ out/ ## Project-specific ignores ############################## /src/main/resources/config-local.properties -/src/main/resources/config-prod.properties \ No newline at end of file +/src/main/resources/config-prod.properties +/logs/ \ No newline at end of file diff --git a/pom.xml b/pom.xml index 1f1c642..4b0d9a1 100644 --- a/pom.xml +++ b/pom.xml @@ -75,6 +75,12 @@ rkon-core 1.1.2 + + ch.qos.logback + logback-classic + 1.5.18 + runtime + @@ -149,6 +155,7 @@ StartUp + diff --git a/src/main/java/StartUp.java b/src/main/java/StartUp.java index 17c59ee..738a152 100644 --- a/src/main/java/StartUp.java +++ b/src/main/java/StartUp.java @@ -7,22 +7,21 @@ import net.dv8tion.jda.api.requests.GatewayIntent; import net.dv8tion.jda.api.utils.ChunkingFilter; import net.dv8tion.jda.api.utils.MemberCachePolicy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; import java.util.Locale; import java.util.Properties; import java.util.ResourceBundle; public class StartUp { + private static final Logger log = LoggerFactory.getLogger(StartUp.class); + public static void main(String[] args) { try { - - System.out.println("[CSBot - StartUp - " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd.MM.yyyy - HH:mm:ss")) + "] - Application Started"); - InputStream inputStream = StartUp.class.getClassLoader().getResourceAsStream("config.properties"); Properties properties = new Properties(); properties.load(inputStream); @@ -44,10 +43,8 @@ public static void main(String[] args) { .queue(); jda.awaitReady(); - } catch (InterruptedException ex) { - System.out.println("[CSBot - StartUp - " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd.MM.yyyy - HH:mm:ss")) + "] InterruptedException thrown: " + ex.getMessage()); - } catch (IOException ex) { - System.out.println("[CSBot - StartUp - " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd.MM.yyyy - HH:mm:ss")) + "] IOException thrown: " + ex.getMessage()); + } catch (InterruptedException | IOException ex) { + log.error(ex.getMessage(), ex); } } } diff --git a/src/main/java/ch/yoinc/http/CarthageConnection.java b/src/main/java/ch/yoinc/http/CarthageConnection.java index f22bcef..3d42f3d 100644 --- a/src/main/java/ch/yoinc/http/CarthageConnection.java +++ b/src/main/java/ch/yoinc/http/CarthageConnection.java @@ -101,7 +101,7 @@ private String extractString(JsonObject responseBody, String field) { if (responseBody.has(field) && !responseBody.get(field).isJsonNull()) { return responseBody.get(field).getAsString(); } - return null; + return ""; } private List extractList(JsonObject responseBody, String field, Type type) { diff --git a/src/main/java/ch/yoinc/http/ConnectionBuilder.java b/src/main/java/ch/yoinc/http/ConnectionBuilder.java deleted file mode 100644 index 4c1a849..0000000 --- a/src/main/java/ch/yoinc/http/ConnectionBuilder.java +++ /dev/null @@ -1,45 +0,0 @@ -package ch.yoinc.http; - -import com.google.gson.*; -import ch.yoinc.model.steam.ResponseData; - -import java.io.IOException; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.util.Properties; - -public class ConnectionBuilder { - - Properties properties; - - private final String STEAM_API = "https://api.steampowered.com"; - - public ConnectionBuilder(Properties properties) { - this.properties = properties; - } - - public ResponseData fetchSteamUserStats(String steamID) throws InterruptedException, IOException { - - HttpClient client = HttpClient.newHttpClient(); - - HttpRequest request; - ResponseData responseData; - - request = HttpRequest.newBuilder() - .uri(URI.create(STEAM_API + "/ISteamUser/GetPlayerSummaries/v0002/?key=" + properties.get("steam.api") + "&steamids=" + steamID)) - .build(); - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - - responseData = new Gson().fromJson(response.body(), ResponseData.class); - - request = HttpRequest.newBuilder() - .uri(URI.create(STEAM_API + "/ISteamUserStats/GetUserStatsForGame/v0002/?key=" + properties.get("steam.api") + "&appid=730&steamid=" + steamID)) - .build(); - response = client.send(request, HttpResponse.BodyHandlers.ofString()); - responseData.setPlayerstats(new Gson().fromJson(response.body(), ResponseData.class).getPlayerstats()); - - return responseData; - } -} diff --git a/src/main/java/ch/yoinc/http/ExternalApiConnection.java b/src/main/java/ch/yoinc/http/ExternalApiConnection.java new file mode 100644 index 0000000..a2c6c4c --- /dev/null +++ b/src/main/java/ch/yoinc/http/ExternalApiConnection.java @@ -0,0 +1,127 @@ +package ch.yoinc.http; + +import ch.yoinc.model.leetify.LeetifyMatchResponse; +import ch.yoinc.model.leetify.LeetifyProfileResponse; +import com.google.gson.*; +import ch.yoinc.model.steam.ResponseData; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Instant; +import java.util.List; +import java.util.Properties; + +public class ExternalApiConnection { + + private static final Logger log = LoggerFactory.getLogger(ExternalApiConnection.class); + + private final String apiKey; + private final HttpClient client; + private final Gson gson; + private final Properties properties; + private final String LEETIFY_API = "https://api-public.cs-prod.leetify.com"; + private final String STEAM_API = "https://api.steampowered.com"; + + public ExternalApiConnection(Properties properties) { + this.properties = properties; + this.apiKey = properties.getProperty("leetify.apiToken"); + gson = new GsonBuilder() + .registerTypeAdapter(Instant.class, new TypeAdapter() { + @Override + public void write(JsonWriter out, Instant value) throws IOException { + if (value == null) { + out.nullValue(); + } else { + out.value(value.toString()); + } + } + + @Override + public Instant read(JsonReader in) throws IOException { + if (in.peek() == JsonToken.NULL) { + in.nextNull(); + return null; + } + return Instant.parse(in.nextString()); + } + }) + .create(); + this.client = HttpClient.newHttpClient(); + } + + public ResponseData fetchSteamUserStats(String steamID) throws InterruptedException, IOException { + + HttpClient client = HttpClient.newHttpClient(); + + HttpRequest request; + ResponseData responseData; + + request = HttpRequest.newBuilder() + .uri(URI.create(STEAM_API + "/ISteamUser/GetPlayerSummaries/v0002/?key=" + properties.get("steam.api") + "&steamids=" + steamID)) + .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + responseData = new Gson().fromJson(response.body(), ResponseData.class); + + request = HttpRequest.newBuilder() + .uri(URI.create(STEAM_API + "/ISteamUserStats/GetUserStatsForGame/v0002/?key=" + properties.get("steam.api") + "&appid=730&steamid=" + steamID)) + .build(); + response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() == 200) { + responseData.setPlayerstats(new Gson().fromJson(response.body(), ResponseData.class).getPlayerstats()); + } + if (response.statusCode() != 404) { + log.warn("fetchSteamUserStats for {} returned status {}, body: {}", steamID, response.statusCode(), response.body()); + } + + return responseData; + } + + public LeetifyProfileResponse getPlayerProfile(String steam64ID, String leetifyID) throws InterruptedException, IOException { + String parameter = steam64ID == null ? "id=" + leetifyID : "steam64_id=" + steam64ID; + HttpRequest request; + request = HttpRequest.newBuilder() + .uri(URI.create(LEETIFY_API + "/v3/profile?" + parameter)) + .header("Authorization", "Bearer " + apiKey) + .GET() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() == 200) { + return gson.fromJson(response.body(), LeetifyProfileResponse.class); + } + if (response.statusCode() != 404) { + log.warn("getPlayerProfile for {} returned status {}, body: {}", parameter, response.statusCode(), response.body()); + } + return null; + } + + public List getPlayerMatchHistory(String steam64ID, String leetifyID) throws InterruptedException, IOException { + String parameter = steam64ID == null ? "id=" + leetifyID : "steam64_id=" + steam64ID; + HttpRequest request; + request = HttpRequest.newBuilder() + .uri(URI.create(LEETIFY_API + "/v3/profile/matches?" + parameter)) + .header("Authorization", "Bearer " + apiKey) + .GET() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() == 200) { + return gson.fromJson(response.body(), new TypeToken>() { + }.getType()); + } + if (response.statusCode() != 404) { + log.warn("getPlayerMatchHistory for {} returned status {}, body: {}", parameter, response.statusCode(), response.body()); + } + return null; + } +} diff --git a/src/main/java/ch/yoinc/http/LeetifyConnection.java b/src/main/java/ch/yoinc/http/LeetifyConnection.java deleted file mode 100644 index ba84869..0000000 --- a/src/main/java/ch/yoinc/http/LeetifyConnection.java +++ /dev/null @@ -1,99 +0,0 @@ -package ch.yoinc.http; - -import ch.yoinc.model.leetify.LeetifyMatchResponse; -import ch.yoinc.model.leetify.LeetifyProfileResponse; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.TypeAdapter; -import com.google.gson.reflect.TypeToken; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import com.google.gson.stream.JsonWriter; - -import java.io.IOException; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.List; -import java.util.Properties; - -public class LeetifyConnection { - - private final String apiKey; - private final HttpClient client; - private final Gson gson; - private final String LEETIFY_API = "https://api-public.cs-prod.leetify.com"; - - - public LeetifyConnection(Properties properties) { - this.apiKey = properties.getProperty("leetify.apiToken"); - gson = new GsonBuilder() - .registerTypeAdapter(Instant.class, new TypeAdapter() { - @Override - public void write(JsonWriter out, Instant value) throws IOException { - if (value == null) { - out.nullValue(); - } else { - out.value(value.toString()); - } - } - - @Override - public Instant read(JsonReader in) throws IOException { - if (in.peek() == JsonToken.NULL) { - in.nextNull(); - return null; - } - return Instant.parse(in.nextString()); - } - }) - .create(); - this.client = HttpClient.newHttpClient(); - } - - public LeetifyProfileResponse getPlayerProfile(String steam64ID, String leetifyID) { - String parameter = steam64ID == null ? "id=" + leetifyID : "steam64_id=" + steam64ID; - HttpRequest request; - request = HttpRequest.newBuilder() - .uri(URI.create(LEETIFY_API + "/v3/profile?" + parameter)) - .header("Authorization", "Bearer " + apiKey) - .GET() - .build(); - - try { - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - return gson.fromJson(response.body(), LeetifyProfileResponse.class); - } catch (IOException | InterruptedException ex) { - System.out.println("[CSBot - LeetifyConnection - " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd.MM.yyyy - HH:mm:ss")) + "] IOException / InterruptedException thrown: " + ex.getMessage()); - } - return null; - } - - public List getPlayerMatchHistory(String steam64ID, String leetifyID) { - String parameter = steam64ID == null ? "id=" + leetifyID : "steam64_id=" + steam64ID; - HttpRequest request; - request = HttpRequest.newBuilder() - .uri(URI.create(LEETIFY_API + "/v3/profile/matches?" + parameter)) - .header("Authorization", "Bearer " + apiKey) - .GET() - .build(); - - try { - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - if (response.statusCode() == 200) { - return gson.fromJson(response.body(), new TypeToken>() { - }.getType()); - } - if(response.statusCode() != 404) { - System.out.println("[CSBot - LeetifyConnection - " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd.MM.yyyy - HH:mm:ss")) + "] getPlayerMatchHistory for " + parameter + " returned status " + response.statusCode() + ", body: " + response.body()); - } - } catch (IOException | InterruptedException ex) { - System.out.println("[CSBot - LeetifyConnection - " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd.MM.yyyy - HH:mm:ss")) + "] IOException / InterruptedException thrown: " + ex.getMessage()); - } - return null; - } -} diff --git a/src/main/java/ch/yoinc/services/CsStatsService.java b/src/main/java/ch/yoinc/services/CsStatsService.java index a55e93d..4381565 100644 --- a/src/main/java/ch/yoinc/services/CsStatsService.java +++ b/src/main/java/ch/yoinc/services/CsStatsService.java @@ -1,29 +1,32 @@ package ch.yoinc.services; -import com.google.gson.JsonSyntaxException; import ch.yoinc.http.CarthageException; -import ch.yoinc.http.ConnectionBuilder; +import ch.yoinc.http.ExternalApiConnection; import ch.yoinc.model.steam.ResponseData; import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import org.codehaus.plexus.util.StringUtils; import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; import java.util.*; public class CsStatsService { - ResourceBundle resourceBundle; - ConnectionBuilder connectionBuilder; + private static final Logger log = LoggerFactory.getLogger(CsStatsService.class); + + ResourceBundle resourceBundle; + ExternalApiConnection connection; DataService dataService; + DiscordService discordService; public CsStatsService(Properties properties, DataService dataService) { this.dataService = dataService; - connectionBuilder = new ConnectionBuilder(properties); + connection = new ExternalApiConnection(properties); + discordService = new DiscordService(); } public EmbedBuilder handleStatsEvent(SlashCommandInteractionEvent event) { @@ -32,14 +35,14 @@ public EmbedBuilder handleStatsEvent(SlashCommandInteractionEvent event) { try { ResponseData responseData = getUserResponseData(Objects.requireNonNull(event.getOption("player")).getAsMentionable().getId()); return responseData.getBasicInfo(resourceBundle); - } catch (InterruptedException | IOException ex) { - System.out.println("[CSBot - CsStatsService - " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd.MM.yyyy - HH:mm:ss")) + "] InterruptedException / IOException thrown: " + ex.getMessage()); - return new EmbedBuilder().setTitle(resourceBundle.getString("error.interruptedexception")); - } catch (NullPointerException | JsonSyntaxException ex) { - System.out.println("[CSBot - CsStatsService - " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd.MM.yyyy - HH:mm:ss")) + "] NullPointerException / JSonSyntaxException thrown: " + ex.getMessage()); + } catch (IOException | InterruptedException ex) { + log.error(ex.getMessage(), ex); + return new EmbedBuilder().setTitle(resourceBundle.getString("error.connectionerror")); + } catch (NullPointerException ex) { + log.error(ex.getMessage(), ex); return new EmbedBuilder().setTitle(resourceBundle.getString("error.privacysettings")); } catch (CarthageException ex) { - System.out.println("[CSBot - CsStatsService - " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd.MM.yyyy - HH:mm:ss")) + "] CarthageException thrown: " + ex.getMessage()); + log.error(ex.getMessage(), ex); return new EmbedBuilder().setTitle(resourceBundle.getString("error.majorerror")); } } @@ -47,17 +50,18 @@ public EmbedBuilder handleStatsEvent(SlashCommandInteractionEvent event) { public EmbedBuilder handleCompareEvent(SlashCommandInteractionEvent event) { resourceBundle = ResourceBundle.getBundle("localization", Locale.of("en")); try { - String requestedUserOneID = Objects.requireNonNull(event.getOption("playerone")).getAsMentionable().getId(); - String requestedUserTwoID = Objects.requireNonNull(event.getOption("playertwo")).getAsMentionable().getId(); - return comparePlayers(getUserResponseData(requestedUserOneID), getUserResponseData(requestedUserTwoID)); - } catch (NullPointerException ex) { - System.out.println("[CSBot - CsStatsService - " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd.MM.yyyy - HH:mm:ss")) + "] NullPointerException thrown: " + ex.getMessage()); - return new EmbedBuilder().setTitle(resourceBundle.getString("error.wrongqueryparameters")); + ResponseData userDataOne = getUserResponseData(Objects.requireNonNull(event.getOption("playerone")).getAsMentionable().getId()); + ResponseData userDataTwo = getUserResponseData(Objects.requireNonNull(event.getOption("playertwo")).getAsMentionable().getId()); + + if(userDataOne == null || userDataTwo == null) { + return new EmbedBuilder().setTitle(resourceBundle.getString("error.privacysettings")); + } + return comparePlayers(userDataOne, userDataTwo); } catch (InterruptedException | IOException ex) { - System.out.println("[CSBot - CsStatsService - " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd.MM.yyyy - HH:mm:ss")) + "] InterruptedException / IOException thrown: " + ex.getMessage()); - return new EmbedBuilder().setTitle(resourceBundle.getString("error.interruptedexception")); + log.error(ex.getMessage(), ex); + return new EmbedBuilder().setTitle(resourceBundle.getString("error.connectionerror")); } catch (CarthageException ex) { - System.out.println("[CSBot - CsStatsService - " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd.MM.yyyy - HH:mm:ss")) + "] CarthageException thrown: " + ex.getMessage()); + log.error(ex.getMessage(), ex); return new EmbedBuilder().setTitle(resourceBundle.getString("error.majorerror")); } } @@ -106,12 +110,12 @@ private String getString(long playerOneLong, long playerTwoLong, boolean playerO } } - private ResponseData getUserResponseData(String discordID) throws NullPointerException, InterruptedException, IOException, CarthageException { + private ResponseData getUserResponseData(String discordID) throws IOException, InterruptedException, CarthageException { ResponseData responseData = null; String steamID = dataService.getSteamIDForDiscordID(discordID); if (StringUtils.isNotEmpty(steamID)) { - responseData = connectionBuilder.fetchSteamUserStats(steamID); + responseData = connection.fetchSteamUserStats(steamID); } return responseData; } diff --git a/src/main/java/ch/yoinc/services/DataService.java b/src/main/java/ch/yoinc/services/DataService.java index 87206d2..b8071fa 100644 --- a/src/main/java/ch/yoinc/services/DataService.java +++ b/src/main/java/ch/yoinc/services/DataService.java @@ -6,8 +6,6 @@ import ch.yoinc.model.leetify.LeetifyMatchResponse; import java.io.IOException; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Properties; @@ -23,31 +21,18 @@ public void setBotID(String botID) { this.botID = botID; } - public String getSteamIDForDiscordID(String discordID) throws CarthageException { - try { - return carthageConnection.getSteamIDForDiscordID(botID, discordID); - } catch (IOException | InterruptedException ex) { - throw new CarthageException("Failed to fetch steamID for discordID " + discordID + " from carthage: " + ex.getMessage()); - } + public String getSteamIDForDiscordID(String discordID) throws IOException, InterruptedException, CarthageException { + return carthageConnection.getSteamIDForDiscordID(botID, discordID); } - public List getAllSteamUsers() { - try { - return carthageConnection.getAllSteamUsers(botID); - } catch (IOException | InterruptedException | CarthageException ex) { - System.out.println("[CSBot - DataService - " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd.MM.yyyy - HH:mm:ss")) + "] InterruptedException / IOException / CarthageException thrown: " + ex.getMessage()); - } - return List.of(); + public List getAllSteamUsers() throws IOException, InterruptedException, CarthageException { + return carthageConnection.getAllSteamUsers(botID); } - public List insertAndGetNewMatches(List matches, Integer userID) { - try { - if (matches != null && !matches.isEmpty()) { - List matchIDs = matches.stream().map(match -> match.id).toList(); - return carthageConnection.insertAndGetNewMatches(matchIDs, userID, botID); - } - } catch (IOException | InterruptedException | CarthageException ex) { - System.out.println("[CSBot - DataService - " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd.MM.yyyy - HH:mm:ss")) + "] InterruptedException / IOException / CarthageException thrown: " + ex.getMessage()); + public List insertAndGetNewMatches(List matches, Integer userID) throws IOException, InterruptedException, CarthageException { + if (matches != null && !matches.isEmpty()) { + List matchIDs = matches.stream().map(match -> match.id).toList(); + return carthageConnection.insertAndGetNewMatches(matchIDs, userID, botID); } return List.of(); } diff --git a/src/main/java/ch/yoinc/services/DiscordService.java b/src/main/java/ch/yoinc/services/DiscordService.java index 5fabdfe..1afef30 100644 --- a/src/main/java/ch/yoinc/services/DiscordService.java +++ b/src/main/java/ch/yoinc/services/DiscordService.java @@ -2,6 +2,8 @@ import net.dv8tion.jda.api.EmbedBuilder; +import java.util.Locale; + public class DiscordService { public EmbedBuilder createEmbedBuilder(String title, String description, String imageUrl, String footer) { @@ -12,6 +14,13 @@ public EmbedBuilder createEmbedBuilder(String title, String description, String .setFooter(footer); } + public String formatRating(Double rating) { + if (rating == null) { + return "n/a"; + } + return String.format(Locale.US, "%.2f", rating * 100.0); + } + public static class YoincEmbedBuilder extends EmbedBuilder { public YoincEmbedBuilder() { super(); diff --git a/src/main/java/ch/yoinc/tasks/LeetifyTask.java b/src/main/java/ch/yoinc/tasks/LeetifyTask.java index 51cb963..a6e6db4 100644 --- a/src/main/java/ch/yoinc/tasks/LeetifyTask.java +++ b/src/main/java/ch/yoinc/tasks/LeetifyTask.java @@ -1,6 +1,7 @@ package ch.yoinc.tasks; -import ch.yoinc.http.LeetifyConnection; +import ch.yoinc.http.CarthageException; +import ch.yoinc.http.ExternalApiConnection; import ch.yoinc.model.internal.InternalUser; import ch.yoinc.model.leetify.LeetifyMatchResponse; import ch.yoinc.model.leetify.LeetifyPlayerStatsResponse; @@ -8,102 +9,112 @@ import ch.yoinc.services.DiscordService; import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.JDA; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.awt.*; +import java.io.IOException; import java.util.*; import java.util.List; -import java.util.Locale; public class LeetifyTask implements ScheduledTask { + private static final Logger log = LoggerFactory.getLogger(LeetifyTask.class); + private DataService dataService; - private LeetifyConnection leetifyConnection; + private DiscordService discordService; + private ExternalApiConnection connection; @Override public void execute(JDA jda, Properties properties) { if (dataService == null) { dataService = new DataService(properties); } - if (leetifyConnection == null) { - leetifyConnection = new LeetifyConnection(properties); + if (discordService == null) { + discordService = new DiscordService(); + } + if (connection == null) { + connection = new ExternalApiConnection(properties); } dataService.setBotID(jda.getSelfUser().getId()); - List internalUsers = dataService.getAllSteamUsers(); - - //this map contains all newly played matches found during the next run of the - //leetify task. its identifier is the match ID and the list contains all - //match responses with this id. why? because that way it's easier to see if - //for example a group has played in the same match. this would allow a different message - //to be sent to the discord instead of the generic "person x has played a match." - HashMap> newlyPlayedMatches = setNewlyPlayedMatches(internalUsers); - - for (String matchID : newlyPlayedMatches.keySet()) { - List matches = newlyPlayedMatches.get(matchID); - EmbedBuilder matchEmbed = new EmbedBuilder(); - LeetifyMatchResponse match = matches.getFirst(); - boolean hasWon = match.stats.getFirst().rounds_won >= match.stats.getFirst().rounds_lost; //tie is a victory, change my mind - if (matches.size() > 1) { - List playerNames = new ArrayList<>(); - for (LeetifyMatchResponse playerMatch : matches) { - playerNames.add(playerMatch.stats.getFirst().name); - } - String players = String.join(", ", playerNames); - - matchEmbed = switch (match.data_source) { - case "faceit" -> returnFilledEmbed("New Faceit Match", - Color.ORANGE, players + " played a Faceit match together and " + ((hasWon) ? "**won**." : "**lost**."), - match.map_name, matchID, "Finished at " + match.finished_at); - case "matchmaking_wingman" -> returnFilledEmbed("New Wingman Match", - Color.GREEN, players + " played a Wingman match together and " + ((hasWon) ? "**won**." : "**lost**."), - match.map_name, matchID, "Finished at " + match.finished_at); - case "matchmaking" -> returnFilledEmbed("New Competitive Match", - Color.YELLOW, players + " played a Competitive match together and " + ((hasWon) ? "**won**." : "**lost**."), - match.map_name, matchID, "Finished at " + match.finished_at); - default -> matchEmbed; - }; - - for (LeetifyMatchResponse playerMatch : matches) { - LeetifyPlayerStatsResponse stats = playerMatch.stats.getFirst(); - matchEmbed.addField( - stats.name, - "Kills: " + stats.total_kills + - "\nDeaths: " + stats.total_deaths + - "\nADR: " + stats.dpr + - "\nRating: " + formatRating(stats.leetify_rating) + - "\nCT Rating: " + formatRating(stats.ct_leetify_rating) + - "\nT Rating: " + formatRating(stats.t_leetify_rating), - true - ); + try { + List internalUsers = dataService.getAllSteamUsers(); + + //this map contains all newly played matches found during the next run of the + //leetify task. its identifier is the match ID and the list contains all + //match responses with this id. why? because that way it's easier to see if + //for example a group has played in the same match. this would allow a different message + //to be sent to the discord instead of the generic "person x has played a match." + HashMap> newlyPlayedMatches = setNewlyPlayedMatches(internalUsers); + + for (String matchID : newlyPlayedMatches.keySet()) { + List matches = newlyPlayedMatches.get(matchID); + EmbedBuilder matchEmbed = new EmbedBuilder(); + LeetifyMatchResponse match = matches.getFirst(); + boolean hasWon = match.stats.getFirst().rounds_won >= match.stats.getFirst().rounds_lost; //tie is a victory, change my mind + if (matches.size() > 1) { + List playerNames = new ArrayList<>(); + for (LeetifyMatchResponse playerMatch : matches) { + playerNames.add(playerMatch.stats.getFirst().name); + } + String players = String.join(", ", playerNames); + + matchEmbed = switch (match.data_source) { + case "faceit" -> returnFilledEmbed("New Faceit Match", + Color.ORANGE, players + " played a Faceit match together and " + ((hasWon) ? "**won**." : "**lost**."), + match.map_name, matchID, "Finished at " + match.finished_at); + case "matchmaking_wingman" -> returnFilledEmbed("New Wingman Match", + Color.GREEN, players + " played a Wingman match together and " + ((hasWon) ? "**won**." : "**lost**."), + match.map_name, matchID, "Finished at " + match.finished_at); + case "matchmaking" -> returnFilledEmbed("New Competitive Match", + Color.YELLOW, players + " played a Competitive match together and " + ((hasWon) ? "**won**." : "**lost**."), + match.map_name, matchID, "Finished at " + match.finished_at); + default -> matchEmbed; + }; + + for (LeetifyMatchResponse playerMatch : matches) { + LeetifyPlayerStatsResponse stats = playerMatch.stats.getFirst(); + matchEmbed.addField( + stats.name, + "Kills: " + stats.total_kills + + "\nDeaths: " + stats.total_deaths + + "\nADR: " + stats.dpr + + "\nRating: " + discordService.formatRating(stats.leetify_rating) + + "\nCT Rating: " + discordService.formatRating(stats.ct_leetify_rating) + + "\nT Rating: " + discordService.formatRating(stats.t_leetify_rating), + true + ); + } + } else { + matchEmbed = switch (match.data_source) { + case "faceit" -> returnFilledEmbed("New Faceit Match", Color.ORANGE, + match.stats.getFirst().name + " played a Faceit match and " + ((hasWon) ? "**won**." : "**lost**."), + match.map_name, matchID, "Finished at " + match.finished_at); + case "matchmaking_wingman" -> returnFilledEmbed("New Wingman Match", Color.GREEN, + match.stats.getFirst().name + " played a Wingman match and " + ((hasWon) ? "**won**." : "**lost**."), + match.map_name, matchID, "Finished at " + match.finished_at); + case "matchmaking" -> returnFilledEmbed("New Competitive Match", Color.YELLOW, + match.stats.getFirst().name + " played a Competitive match and " + ((hasWon) ? "**won**." : "**lost**."), + match.map_name, matchID, "Finished at " + match.finished_at); + default -> matchEmbed; + }; + matchEmbed + .addField("Kills", Integer.toString(match.stats.getFirst().total_kills), true) + .addField("Deaths", Integer.toString(match.stats.getFirst().total_deaths), true) + .addField("ADR", Double.toString(match.stats.getFirst().dpr), true) + .addField("Rating", discordService.formatRating(match.stats.getFirst().leetify_rating), true) + .addField("CT Rating", discordService.formatRating(match.stats.getFirst().ct_leetify_rating), true) + .addField("T Rating", discordService.formatRating(match.stats.getFirst().t_leetify_rating), true); } - } else { - matchEmbed = switch (match.data_source) { - case "faceit" -> returnFilledEmbed("New Faceit Match", Color.ORANGE, - match.stats.getFirst().name + " played a Faceit match and " + ((hasWon) ? "**won**." : "**lost**."), - match.map_name, matchID, "Finished at " + match.finished_at); - case "matchmaking_wingman" -> returnFilledEmbed("New Wingman Match", Color.GREEN, - match.stats.getFirst().name + " played a Wingman match and " + ((hasWon) ? "**won**." : "**lost**."), - match.map_name, matchID, "Finished at " + match.finished_at); - case "matchmaking" -> returnFilledEmbed("New Competitive Match", Color.YELLOW, - match.stats.getFirst().name + " played a Competitive match and " + ((hasWon) ? "**won**." : "**lost**."), - match.map_name, matchID, "Finished at " + match.finished_at); - default -> matchEmbed; - }; - matchEmbed - .addField("Kills", Integer.toString(match.stats.getFirst().total_kills), true) - .addField("Deaths", Integer.toString(match.stats.getFirst().total_deaths), true) - .addField("ADR", Double.toString(match.stats.getFirst().dpr), true) - .addField("Rating", formatRating(match.stats.getFirst().leetify_rating), true) - .addField("CT Rating", formatRating(match.stats.getFirst().ct_leetify_rating), true) - .addField("T Rating", formatRating(match.stats.getFirst().t_leetify_rating), true); + Objects.requireNonNull(jda.getTextChannelById(properties.getProperty("discord.channelID"))).sendMessageEmbeds(matchEmbed.build()).queue(); } - Objects.requireNonNull(jda.getTextChannelById(properties.getProperty("discord.channelID"))).sendMessageEmbeds(matchEmbed.build()).queue(); + } catch (InterruptedException | IOException | CarthageException ex) { + log.error(ex.getMessage(), ex); } } private EmbedBuilder returnFilledEmbed(String title, Color color, String description, String map_name, String matchID, String footer) { - DiscordService discordService = new DiscordService(); - String DEFAULT_LEETIFY_URL = "https://leetify.com/app/match-details/%s/overview"; String DEFAULT_MAP_LOGO_URL = "https://raw.githubusercontent.com/MurkyYT/cs2-map-icons/main/images/%s.png"; String DEFAULT_MAP_URL = "https://raw.githubusercontent.com/MurkyYT/cs2-map-icons/main/images/thumbs/%s_1_png.png"; @@ -120,27 +131,31 @@ private EmbedBuilder returnFilledEmbed(String title, Color color, String descrip private HashMap> setNewlyPlayedMatches(List steamInternalUsers) { HashMap> results = new HashMap<>(); - for (InternalUser user : steamInternalUsers) { - List matches = leetifyConnection.getPlayerMatchHistory(user.steamID, null); + try { + for (InternalUser user : steamInternalUsers) { + List matches = connection.getPlayerMatchHistory(user.steamID, null); - if (matches != null && !matches.isEmpty()) { - List newMatches = dataService.insertAndGetNewMatches(matches, user.userID); + if (matches != null && !matches.isEmpty()) { + List newMatches = dataService.insertAndGetNewMatches(matches, user.userID); - for (String matchID : newMatches) { - for (LeetifyMatchResponse match : matches) { - if (match.id.equals(matchID)) { - results.computeIfAbsent(matchID, k -> new ArrayList<>()).add(match); + for (String matchID : newMatches) { + for (LeetifyMatchResponse match : matches) { + if (match.id.equals(matchID)) { + results.computeIfAbsent(matchID, k -> new ArrayList<>()).add(match); + } } } } - } - try { - Thread.sleep(10000); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - return results; + try { + Thread.sleep(10000); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + return results; + } } + } catch (InterruptedException | IOException | CarthageException ex) { + log.error(ex.getMessage(), ex); } return results; } @@ -149,11 +164,4 @@ private HashMap> setNewlyPlayedMatches(List + + + %d{dd.MM.yyyy - HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n + + + + + logs/csbot.log + + logs/csbot.%d{yyyy-MM-dd}.%i.log + 10MB + 14 + 200MB + + + %d{dd.MM.yyyy - HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/ch/yoinc/tasks/LeetifyTaskTest.java b/src/test/java/ch/yoinc/tasks/LeetifyTaskTest.java index c2fdf80..3671a99 100644 --- a/src/test/java/ch/yoinc/tasks/LeetifyTaskTest.java +++ b/src/test/java/ch/yoinc/tasks/LeetifyTaskTest.java @@ -1,10 +1,11 @@ package ch.yoinc.tasks; -import ch.yoinc.http.LeetifyConnection; +import ch.yoinc.http.ExternalApiConnection; import ch.yoinc.model.internal.InternalUser; import ch.yoinc.model.leetify.LeetifyMatchResponse; import ch.yoinc.model.leetify.LeetifyPlayerStatsResponse; import ch.yoinc.services.DataService; +import ch.yoinc.services.DiscordService; import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.entities.MessageEmbed; import org.junit.jupiter.api.BeforeEach; @@ -37,8 +38,9 @@ class LeetifyTaskTest { private LeetifyTask task; @BeforeEach - void setUp() { + void setUp() throws Exception { task = new LeetifyTask(); + setPrivateField("discordService", new DiscordService()); } // --------------------------------------------------------------------- @@ -108,9 +110,9 @@ void formatRating_returnsPlaceholder_whenRatingIsMissing() throws Exception { @Timeout(value = 5, unit = TimeUnit.SECONDS) void setNewlyPlayedMatches_returnsEmptyMap_whenUserHasNoMatchHistory() throws Exception { DataService dataService = mock(DataService.class); - LeetifyConnection leetifyConnection = mock(LeetifyConnection.class); - when(leetifyConnection.getPlayerMatchHistory(eq("STEAM1"), isNull())).thenReturn(null); - injectDependencies(dataService, leetifyConnection); + ExternalApiConnection connection = mock(ExternalApiConnection.class); + when(connection.getPlayerMatchHistory(eq("STEAM1"), isNull())).thenReturn(null); + injectDependencies(dataService, connection); // the method sleeps 10s (real) after every user; pre-interrupting this thread makes // that Thread.sleep() throw immediately instead of actually waiting @@ -127,14 +129,14 @@ void setNewlyPlayedMatches_returnsEmptyMap_whenUserHasNoMatchHistory() throws Ex @Timeout(value = 5, unit = TimeUnit.SECONDS) void setNewlyPlayedMatches_onlyIncludesMatchesReturnedAsNew() throws Exception { DataService dataService = mock(DataService.class); - LeetifyConnection leetifyConnection = mock(LeetifyConnection.class); + ExternalApiConnection connection = mock(ExternalApiConnection.class); LeetifyMatchResponse oldMatch = match("old-match", "Alice"); LeetifyMatchResponse newMatch = match("new-match", "Alice"); List history = List.of(oldMatch, newMatch); - when(leetifyConnection.getPlayerMatchHistory(eq("STEAM1"), isNull())).thenReturn(history); + when(connection.getPlayerMatchHistory(eq("STEAM1"), isNull())).thenReturn(history); when(dataService.insertAndGetNewMatches(history, 1)).thenReturn(List.of("new-match")); - injectDependencies(dataService, leetifyConnection); + injectDependencies(dataService, connection); Thread.currentThread().interrupt(); @@ -150,18 +152,18 @@ void setNewlyPlayedMatches_onlyIncludesMatchesReturnedAsNew() throws Exception { @Timeout(value = 12, unit = TimeUnit.SECONDS) void setNewlyPlayedMatches_groupsMatchesFromDifferentUsersUnderSameMatchId() throws Exception { DataService dataService = mock(DataService.class); - LeetifyConnection leetifyConnection = mock(LeetifyConnection.class); + ExternalApiConnection connection = mock(ExternalApiConnection.class); LeetifyMatchResponse aliceMatch = match("shared-match", "Alice"); LeetifyMatchResponse bobMatch = match("shared-match", "Bob"); List aliceHistory = List.of(aliceMatch); List bobHistory = List.of(bobMatch); - when(leetifyConnection.getPlayerMatchHistory(eq("STEAM1"), isNull())).thenReturn(aliceHistory); - when(leetifyConnection.getPlayerMatchHistory(eq("STEAM2"), isNull())).thenReturn(bobHistory); + when(connection.getPlayerMatchHistory(eq("STEAM1"), isNull())).thenReturn(aliceHistory); + when(connection.getPlayerMatchHistory(eq("STEAM2"), isNull())).thenReturn(bobHistory); when(dataService.insertAndGetNewMatches(aliceHistory, 1)).thenReturn(List.of("shared-match")); when(dataService.insertAndGetNewMatches(bobHistory, 2)).thenReturn(List.of("shared-match")); - injectDependencies(dataService, leetifyConnection); + injectDependencies(dataService, connection); // the production loop sleeps 10s (real) between users, so the first sleep has to be // waited out for the second user to be processed at all; once it's back we interrupt @@ -200,10 +202,8 @@ private MessageEmbed invokeReturnFilledEmbed(String title, Color color, String d return builder.build(); } - private String invokeFormatRating(Double rating) throws Exception { - Method method = LeetifyTask.class.getDeclaredMethod("formatRating", Double.class); - method.setAccessible(true); - return (String) method.invoke(null, rating); + private String invokeFormatRating(Double rating) { + return new DiscordService().formatRating(rating); } @SuppressWarnings("unchecked") @@ -217,9 +217,9 @@ private HashMap> invokeSetNewlyPlayedMatches( } } - private void injectDependencies(DataService dataService, LeetifyConnection leetifyConnection) throws Exception { + private void injectDependencies(DataService dataService, ExternalApiConnection connection) throws Exception { setPrivateField("dataService", dataService); - setPrivateField("leetifyConnection", leetifyConnection); + setPrivateField("connection", connection); } private void setPrivateField(String name, Object value) throws Exception { diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml new file mode 100644 index 0000000..8781576 --- /dev/null +++ b/src/test/resources/logback-test.xml @@ -0,0 +1,4 @@ + + + +