From 0d084c818541b0356b6802ab7c08c16afdac9ce2 Mon Sep 17 00:00:00 2001 From: Janes Thomas Date: Wed, 26 Aug 2026 13:34:10 +0200 Subject: [PATCH 1/9] removed non-existing case "premier" and "wingman" and refactored code --- src/main/java/ch/yoinc/tasks/LeetifyTask.java | 126 ++++++------------ 1 file changed, 39 insertions(+), 87 deletions(-) diff --git a/src/main/java/ch/yoinc/tasks/LeetifyTask.java b/src/main/java/ch/yoinc/tasks/LeetifyTask.java index 5980a26..bb0f8fc 100644 --- a/src/main/java/ch/yoinc/tasks/LeetifyTask.java +++ b/src/main/java/ch/yoinc/tasks/LeetifyTask.java @@ -24,12 +24,6 @@ public void execute(JDA jda, Properties properties) { dataService.setBotID(jda.getSelfUser().getId()); leetifyConnection = new LeetifyConnection(properties); - 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"; - List internalUsers = dataService.getAllSteamUsers(); //this map contains all newly played matches found during the next run of the @@ -41,66 +35,36 @@ public void execute(JDA jda, Properties properties) { for (String matchID : newlyPlayedMatches.keySet()) { List matches = newlyPlayedMatches.get(matchID); + EmbedBuilder matchEmbed = new EmbedBuilder(); if (matches.size() > 1) { LeetifyMatchResponse match = matches.getFirst(); - String title = ""; - Color color = Color.BLUE; - String imageUrl = ""; - String thumbnailUrl = ""; - String footer = ""; - List playerNames = new ArrayList<>(); for (LeetifyMatchResponse playerMatch : matches) { playerNames.add(playerMatch.stats.getFirst().name); } String players = String.join(", ", playerNames); - String description = ""; switch (match.data_source) { case "faceit": - title = "New Faceit Match"; - color = Color.ORANGE; - description = players + " played a new Faceit match together."; - imageUrl = DEFAULT_MAP_URL.replace("%s", match.map_name); - thumbnailUrl = DEFAULT_MAP_LOGO_URL.replace("%s", match.map_name); - footer = "Finished at " + match.finished_at; + matchEmbed = returnFilledEmbed("New Faceit Match", + Color.ORANGE, players + " played a new Faceit match together.", + match.map_name, matchID, "Finished at " + match.finished_at); break; - case "wingman": case "matchmaking_wingman": - title = "New Wingman Match"; - color = Color.GREEN; - description = players + " played a new Wingman match together."; - imageUrl = DEFAULT_MAP_URL.replace("%s", match.map_name); - thumbnailUrl = DEFAULT_MAP_LOGO_URL.replace("%s", match.map_name); - footer = "Finished at " + match.finished_at; - break; - case "premier": - title = "New Premier Match"; - color = Color.YELLOW; - description = players + " played a new Premier match together."; - imageUrl = DEFAULT_MAP_URL.replace("%s", match.map_name); - thumbnailUrl = DEFAULT_MAP_LOGO_URL.replace("%s", match.map_name); - footer = "Finished at " + match.finished_at; + matchEmbed = returnFilledEmbed("New Wingman Match", + Color.GREEN, players + " played a new Wingman match together.", + match.map_name, matchID, "Finished at " + match.finished_at); break; case "matchmaking": - title = "New Competitive Match"; - color = Color.RED; - description = players + " played a new Competitive match together."; - imageUrl = DEFAULT_MAP_URL.replace("%s", match.map_name); - thumbnailUrl = DEFAULT_MAP_LOGO_URL.replace("%s", match.map_name); - footer = "Finished at " + match.finished_at; + matchEmbed = returnFilledEmbed("New Competitive Match", + Color.YELLOW, players + " played a new Competitive match together.", + match.map_name, matchID, "Finished at " + match.finished_at); break; } - EmbedBuilder updateMessage = discordService.createEmbedBuilder(title, description, imageUrl, footer); - updateMessage - .setTitle(title, DEFAULT_LEETIFY_URL.replace("%s", matchID)) - .setColor(color) - .setThumbnail(thumbnailUrl); - for (LeetifyMatchResponse playerMatch : matches) { LeetifyPlayerStatsResponse stats = playerMatch.stats.getFirst(); - updateMessage.addField( + matchEmbed.addField( stats.name, "Kills: " + stats.total_kills + "\nDeaths: " + stats.total_deaths + @@ -111,67 +75,55 @@ public void execute(JDA jda, Properties properties) { true ); } - Objects.requireNonNull(jda.getTextChannelById(properties.getProperty("discord.channelID"))).sendMessageEmbeds(updateMessage.build()).queue(); + Objects.requireNonNull(jda.getTextChannelById(properties.getProperty("discord.channelID"))).sendMessageEmbeds(matchEmbed.build()).queue(); } else { LeetifyMatchResponse match = matches.getFirst(); - String title = ""; - Color color = Color.BLUE; - String description = ""; - String imageUrl = ""; - String thumbnailUrl = ""; - String footer = ""; switch (match.data_source) { case "faceit": - title = "New Faceit Match"; - color = Color.ORANGE; - description = match.stats.getFirst().name + " played a new Faceit match."; - imageUrl = DEFAULT_MAP_URL.replace("%s", match.map_name); - thumbnailUrl = DEFAULT_MAP_LOGO_URL.replace("%s", match.map_name); - footer = "Finished at " + match.finished_at; + matchEmbed = returnFilledEmbed("New Faceit Match", Color.ORANGE, + match.stats.getFirst().name + " played a new Faceit match.", + match.map_name, matchID, "Finished at " + match.finished_at); break; - case "wingman": case "matchmaking_wingman": - title = "New Wingman Match"; - color = Color.GREEN; - description = match.stats.getFirst().name + " played a new Wingman match."; - imageUrl = DEFAULT_MAP_URL.replace("%s", match.map_name); - thumbnailUrl = DEFAULT_MAP_LOGO_URL.replace("%s", match.map_name); - footer = "Finished at " + match.finished_at; - break; - case "premier": - title = "New Premier Match"; - color = Color.YELLOW; - description = match.stats.getFirst().name + " played a new Premier match."; - imageUrl = DEFAULT_MAP_URL.replace("%s", match.map_name); - thumbnailUrl = DEFAULT_MAP_LOGO_URL.replace("%s", match.map_name); - footer = "Finished at " + match.finished_at; + matchEmbed = returnFilledEmbed("New Wingman Match", Color.GREEN, + match.stats.getFirst().name + " played a new Wingman match.", + match.map_name, matchID, "Finished at " + match.finished_at); break; case "matchmaking": - title = "New Competitive Match"; - color = Color.RED; - description = match.stats.getFirst().name + " played a new Competitive match."; - imageUrl = DEFAULT_MAP_URL.replace("%s", match.map_name); - thumbnailUrl = DEFAULT_MAP_LOGO_URL.replace("%s", match.map_name); - footer = "Finished at " + match.finished_at; + matchEmbed = returnFilledEmbed("New Competitive Match", Color.YELLOW, + match.stats.getFirst().name + " played a new Competitive match.", + match.map_name, matchID, "Finished at " + match.finished_at); break; } - EmbedBuilder updateMessage = discordService.createEmbedBuilder(title, description, imageUrl, footer); - updateMessage - .setTitle(title, DEFAULT_LEETIFY_URL.replace("%s", matchID)) - .setColor(color) - .setThumbnail(thumbnailUrl) + 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", Double.toString(match.stats.getFirst().leetify_rating), true) .addField("CT Rating", Double.toString(match.stats.getFirst().ct_leetify_rating), true) .addField("T Rating", Double.toString(match.stats.getFirst().t_leetify_rating), true); - Objects.requireNonNull(jda.getTextChannelById(properties.getProperty("discord.channelID"))).sendMessageEmbeds(updateMessage.build()).queue(); + Objects.requireNonNull(jda.getTextChannelById(properties.getProperty("discord.channelID"))).sendMessageEmbeds(matchEmbed.build()).queue(); } } } + 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"; + + EmbedBuilder returnEmbed = discordService.createEmbedBuilder(title, description, DEFAULT_MAP_URL.replace("%s", map_name), footer); + returnEmbed + .setTitle(title, DEFAULT_LEETIFY_URL.replace("%s", matchID)) + .setColor(color) + .setThumbnail(DEFAULT_MAP_LOGO_URL.replace("%s", map_name)); + + return returnEmbed; + } + private HashMap> setNewlyPlayedMatches(List steamInternalUsers) { HashMap> results = new HashMap<>(); From 45d427bd1e1b50cf39d27af6bbe454340b508fb6 Mon Sep 17 00:00:00 2001 From: Janes Thomas Date: Wed, 26 Aug 2026 13:50:08 +0200 Subject: [PATCH 2/9] created unit tests for leetify tasks --- pom.xml | 3 +- .../java/ch/yoinc/tasks/LeetifyTaskTest.java | 227 ++++++++++++++++++ 2 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 src/test/java/ch/yoinc/tasks/LeetifyTaskTest.java diff --git a/pom.xml b/pom.xml index 64d5f5a..9170021 100644 --- a/pom.xml +++ b/pom.xml @@ -67,7 +67,8 @@ org.mockito mockito-core - 5.12.0 + 5.23.0 + test net.kronos diff --git a/src/test/java/ch/yoinc/tasks/LeetifyTaskTest.java b/src/test/java/ch/yoinc/tasks/LeetifyTaskTest.java new file mode 100644 index 0000000..942ba66 --- /dev/null +++ b/src/test/java/ch/yoinc/tasks/LeetifyTaskTest.java @@ -0,0 +1,227 @@ +package ch.yoinc.tasks; + +import ch.yoinc.http.LeetifyConnection; +import ch.yoinc.model.internal.InternalUser; +import ch.yoinc.model.leetify.LeetifyMatchResponse; +import ch.yoinc.model.leetify.LeetifyPlayerStatsResponse; +import ch.yoinc.services.DataService; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.MessageEmbed; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import java.awt.Color; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.time.Instant; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class LeetifyTaskTest { + + private LeetifyTask task; + + @BeforeEach + void setUp() { + task = new LeetifyTask(); + } + + // --------------------------------------------------------------------- + // returnFilledEmbed + // --------------------------------------------------------------------- + + @Test + void returnFilledEmbed_setsTitleAndMatchUrl() throws Exception { + MessageEmbed embed = invokeReturnFilledEmbed("New Faceit Match", Color.ORANGE, + "players played a match", "de_dust2", "match-123", "Finished at 2026-01-01"); + + assertEquals("New Faceit Match", embed.getTitle()); + assertEquals("https://leetify.com/app/match-details/match-123/overview", embed.getUrl()); + } + + @Test + void returnFilledEmbed_setsColorDescriptionAndFooter() throws Exception { + MessageEmbed embed = invokeReturnFilledEmbed("New Competitive Match", Color.RED, + "some description", "de_mirage", "match-456", "Finished at 2026-01-02"); + + assertEquals(Color.RED, embed.getColor()); + assertEquals("some description", embed.getDescription()); + assertEquals("Finished at 2026-01-02", embed.getFooter().getText()); + } + + @Test + void returnFilledEmbed_buildsMapImageAndThumbnailUrlsFromMapName() throws Exception { + MessageEmbed embed = invokeReturnFilledEmbed("New Wingman Match", Color.GREEN, + "desc", "de_inferno", "match-789", "footer"); + + assertEquals("https://raw.githubusercontent.com/MurkyYT/cs2-map-icons/main/images/thumbs/de_inferno_1_png.png", + embed.getImage().getUrl()); + assertEquals("https://raw.githubusercontent.com/MurkyYT/cs2-map-icons/main/images/de_inferno.png", + embed.getThumbnail().getUrl()); + } + + @Test + void returnFilledEmbed_appliesYoincBranding() throws Exception { + MessageEmbed embed = invokeReturnFilledEmbed("New Premier Match", Color.YELLOW, + "desc", "de_ancient", "match-321", "footer"); + + assertNotNull(embed.getAuthor()); + assertEquals("Powered by YOINC.", embed.getAuthor().getName()); + } + + // --------------------------------------------------------------------- + // setNewlyPlayedMatches + // --------------------------------------------------------------------- + + @Test + @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); + + // the method sleeps 10s (real) after every user; pre-interrupting this thread makes + // that Thread.sleep() throw immediately instead of actually waiting + Thread.currentThread().interrupt(); + + HashMap> results = invokeSetNewlyPlayedMatches(List.of(user(1, "STEAM1"))); + + assertTrue(results.isEmpty()); + verify(dataService, never()).insertAndGetNewMatches(any(), any()); + assertTrue(Thread.interrupted(), "interrupt flag should have been restored by the method"); + } + + @Test + @Timeout(value = 5, unit = TimeUnit.SECONDS) + void setNewlyPlayedMatches_onlyIncludesMatchesReturnedAsNew() throws Exception { + DataService dataService = mock(DataService.class); + LeetifyConnection leetifyConnection = mock(LeetifyConnection.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(dataService.insertAndGetNewMatches(history, 1)).thenReturn(List.of("new-match")); + injectDependencies(dataService, leetifyConnection); + + Thread.currentThread().interrupt(); + + HashMap> results = invokeSetNewlyPlayedMatches(List.of(user(1, "STEAM1"))); + + assertEquals(1, results.size()); + assertFalse(results.containsKey("old-match")); + assertEquals(List.of(newMatch), results.get("new-match")); + Thread.interrupted(); + } + + @Test + @Timeout(value = 12, unit = TimeUnit.SECONDS) + void setNewlyPlayedMatches_groupsMatchesFromDifferentUsersUnderSameMatchId() throws Exception { + DataService dataService = mock(DataService.class); + LeetifyConnection leetifyConnection = mock(LeetifyConnection.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(dataService.insertAndGetNewMatches(aliceHistory, 1)).thenReturn(List.of("shared-match")); + when(dataService.insertAndGetNewMatches(bobHistory, 2)).thenReturn(List.of("shared-match")); + injectDependencies(dataService, leetifyConnection); + + // 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 + // the worker to skip its trailing sleep instead of waiting a second 10s + AtomicReference>> resultRef = new AtomicReference<>(); + Thread worker = new Thread(() -> { + try { + resultRef.set(invokeSetNewlyPlayedMatches(List.of(user(1, "STEAM1"), user(2, "STEAM2")))); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + worker.start(); + worker.join(10_500); + if (worker.isAlive()) { + worker.interrupt(); + worker.join(2_000); + } + + HashMap> results = resultRef.get(); + assertNotNull(results, "worker did not finish in time"); + assertEquals(1, results.size()); + assertEquals(List.of(aliceMatch, bobMatch), results.get("shared-match")); + } + + // --------------------------------------------------------------------- + // helpers + // --------------------------------------------------------------------- + + private MessageEmbed invokeReturnFilledEmbed(String title, Color color, String description, + String mapName, String matchId, String footer) throws Exception { + Method method = LeetifyTask.class.getDeclaredMethod("returnFilledEmbed", + String.class, Color.class, String.class, String.class, String.class, String.class); + method.setAccessible(true); + EmbedBuilder builder = (EmbedBuilder) method.invoke(task, title, color, description, mapName, matchId, footer); + return builder.build(); + } + + @SuppressWarnings("unchecked") + private HashMap> invokeSetNewlyPlayedMatches(List users) { + try { + Method method = LeetifyTask.class.getDeclaredMethod("setNewlyPlayedMatches", List.class); + method.setAccessible(true); + return (HashMap>) method.invoke(task, users); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private void injectDependencies(DataService dataService, LeetifyConnection leetifyConnection) throws Exception { + setPrivateField("dataService", dataService); + setPrivateField("leetifyConnection", leetifyConnection); + } + + private void setPrivateField(String name, Object value) throws Exception { + Field field = LeetifyTask.class.getDeclaredField(name); + field.setAccessible(true); + field.set(task, value); + } + + private static InternalUser user(int id, String steamId) { + InternalUser user = new InternalUser(); + user.userID = id; + user.steamID = steamId; + return user; + } + + private static LeetifyMatchResponse match(String matchId, String playerName) { + LeetifyMatchResponse match = new LeetifyMatchResponse(); + match.id = matchId; + match.data_source = "matchmaking"; + match.map_name = "de_dust2"; + match.finished_at = Instant.now(); + LeetifyPlayerStatsResponse stats = new LeetifyPlayerStatsResponse(); + stats.name = playerName; + match.stats = List.of(stats); + return match; + } +} From 763772e7918686088b067a243a96c20724c12c49 Mon Sep 17 00:00:00 2001 From: Aatha Sivas Date: Wed, 26 Aug 2026 22:56:38 +0200 Subject: [PATCH 3/9] formatted leetify ratings --- src/main/java/ch/yoinc/tasks/LeetifyTask.java | 20 ++++++++++++----- .../java/ch/yoinc/tasks/LeetifyTaskTest.java | 22 +++++++++++++++++++ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/main/java/ch/yoinc/tasks/LeetifyTask.java b/src/main/java/ch/yoinc/tasks/LeetifyTask.java index bb0f8fc..8ca2af4 100644 --- a/src/main/java/ch/yoinc/tasks/LeetifyTask.java +++ b/src/main/java/ch/yoinc/tasks/LeetifyTask.java @@ -12,6 +12,7 @@ import java.awt.*; import java.util.*; import java.util.List; +import java.util.Locale; public class LeetifyTask implements ScheduledTask { @@ -69,9 +70,9 @@ public void execute(JDA jda, Properties properties) { "Kills: " + stats.total_kills + "\nDeaths: " + stats.total_deaths + "\nADR: " + stats.dpr + - "\nRating: " + stats.leetify_rating + - "\nCT Rating: " + stats.ct_leetify_rating + - "\nT Rating: " + stats.t_leetify_rating, + "\nRating: " + formatRating(stats.leetify_rating) + + "\nCT Rating: " + formatRating(stats.ct_leetify_rating) + + "\nT Rating: " + formatRating(stats.t_leetify_rating), true ); } @@ -100,9 +101,9 @@ public void execute(JDA jda, Properties properties) { .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", Double.toString(match.stats.getFirst().leetify_rating), true) - .addField("CT Rating", Double.toString(match.stats.getFirst().ct_leetify_rating), true) - .addField("T Rating", Double.toString(match.stats.getFirst().t_leetify_rating), 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(); } } @@ -156,4 +157,11 @@ private HashMap> setNewlyPlayedMatches(List> invokeSetNewlyPlayedMatches(List users) { try { From dada792daa34bd3c6db16c4facf90e92776dcbca Mon Sep 17 00:00:00 2001 From: Janes Thomas Date: Thu, 27 Aug 2026 08:34:25 +0200 Subject: [PATCH 4/9] only log non-404 status codes from leetify --- src/main/java/ch/yoinc/http/LeetifyConnection.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/yoinc/http/LeetifyConnection.java b/src/main/java/ch/yoinc/http/LeetifyConnection.java index 4120103..ba84869 100644 --- a/src/main/java/ch/yoinc/http/LeetifyConnection.java +++ b/src/main/java/ch/yoinc/http/LeetifyConnection.java @@ -88,7 +88,9 @@ public List getPlayerMatchHistory(String steam64ID, String return gson.fromJson(response.body(), new TypeToken>() { }.getType()); } - 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()); + 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()); } From 9bd2c8ff94b58f7b06b361698a099b264c62a6a3 Mon Sep 17 00:00:00 2001 From: Janes Thomas Date: Thu, 27 Aug 2026 19:49:43 +0200 Subject: [PATCH 5/9] switch cleanup and victory/loss notice --- src/main/java/ch/yoinc/tasks/LeetifyTask.java | 66 ++++++++----------- 1 file changed, 27 insertions(+), 39 deletions(-) diff --git a/src/main/java/ch/yoinc/tasks/LeetifyTask.java b/src/main/java/ch/yoinc/tasks/LeetifyTask.java index 8ca2af4..e835fdf 100644 --- a/src/main/java/ch/yoinc/tasks/LeetifyTask.java +++ b/src/main/java/ch/yoinc/tasks/LeetifyTask.java @@ -37,31 +37,27 @@ public void execute(JDA jda, Properties properties) { 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) { - LeetifyMatchResponse match = matches.getFirst(); List playerNames = new ArrayList<>(); for (LeetifyMatchResponse playerMatch : matches) { playerNames.add(playerMatch.stats.getFirst().name); } String players = String.join(", ", playerNames); - switch (match.data_source) { - case "faceit": - matchEmbed = returnFilledEmbed("New Faceit Match", - Color.ORANGE, players + " played a new Faceit match together.", - match.map_name, matchID, "Finished at " + match.finished_at); - break; - case "matchmaking_wingman": - matchEmbed = returnFilledEmbed("New Wingman Match", - Color.GREEN, players + " played a new Wingman match together.", - match.map_name, matchID, "Finished at " + match.finished_at); - break; - case "matchmaking": - matchEmbed = returnFilledEmbed("New Competitive Match", - Color.YELLOW, players + " played a new Competitive match together.", - match.map_name, matchID, "Finished at " + match.finished_at); - break; - } + 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(); @@ -76,27 +72,19 @@ public void execute(JDA jda, Properties properties) { true ); } - Objects.requireNonNull(jda.getTextChannelById(properties.getProperty("discord.channelID"))).sendMessageEmbeds(matchEmbed.build()).queue(); } else { - LeetifyMatchResponse match = matches.getFirst(); - - switch (match.data_source) { - case "faceit": - matchEmbed = returnFilledEmbed("New Faceit Match", Color.ORANGE, - match.stats.getFirst().name + " played a new Faceit match.", - match.map_name, matchID, "Finished at " + match.finished_at); - break; - case "matchmaking_wingman": - matchEmbed = returnFilledEmbed("New Wingman Match", Color.GREEN, - match.stats.getFirst().name + " played a new Wingman match.", - match.map_name, matchID, "Finished at " + match.finished_at); - break; - case "matchmaking": - matchEmbed = returnFilledEmbed("New Competitive Match", Color.YELLOW, - match.stats.getFirst().name + " played a new Competitive match.", - match.map_name, matchID, "Finished at " + match.finished_at); - break; - } + 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) @@ -104,8 +92,8 @@ public void execute(JDA jda, Properties properties) { .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(); } } From d6810ecf690b9f30b06764f41bc70f79d425c266 Mon Sep 17 00:00:00 2001 From: Janes Thomas Date: Thu, 27 Aug 2026 19:51:38 +0200 Subject: [PATCH 6/9] set timer to fixed rate to prevent schedule drift-off --- src/main/java/ch/yoinc/tasks/TaskScheduler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/yoinc/tasks/TaskScheduler.java b/src/main/java/ch/yoinc/tasks/TaskScheduler.java index 3ab5cb9..539e277 100644 --- a/src/main/java/ch/yoinc/tasks/TaskScheduler.java +++ b/src/main/java/ch/yoinc/tasks/TaskScheduler.java @@ -67,6 +67,6 @@ public void run() { } }; - timer.schedule(timerTask, initialDelay, interval); + timer.scheduleAtFixedRate(timerTask, initialDelay, interval); } } \ No newline at end of file From 1ffdb5cf1ef96edcc4903efb4c409dbc339ccedc Mon Sep 17 00:00:00 2001 From: Janes Thomas Date: Thu, 27 Aug 2026 19:52:43 +0200 Subject: [PATCH 7/9] prevent httpclient thread pool leak --- src/main/java/ch/yoinc/tasks/LeetifyTask.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/yoinc/tasks/LeetifyTask.java b/src/main/java/ch/yoinc/tasks/LeetifyTask.java index e835fdf..51cb963 100644 --- a/src/main/java/ch/yoinc/tasks/LeetifyTask.java +++ b/src/main/java/ch/yoinc/tasks/LeetifyTask.java @@ -21,9 +21,13 @@ public class LeetifyTask implements ScheduledTask { @Override public void execute(JDA jda, Properties properties) { - dataService = new DataService(properties); + if (dataService == null) { + dataService = new DataService(properties); + } + if (leetifyConnection == null) { + leetifyConnection = new LeetifyConnection(properties); + } dataService.setBotID(jda.getSelfUser().getId()); - leetifyConnection = new LeetifyConnection(properties); List internalUsers = dataService.getAllSteamUsers(); From d1e002a23b84f766612fdaaa95e0b88c1b6178ab Mon Sep 17 00:00:00 2001 From: Janes Thomas Date: Thu, 27 Aug 2026 22:12:03 +0200 Subject: [PATCH 8/9] created github actions --- .github/ci-config.properties | 17 ++++++++++ .github/workflows/ci.yml | 60 ++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 .github/ci-config.properties create mode 100644 .github/workflows/ci.yml diff --git a/.github/ci-config.properties b/.github/ci-config.properties new file mode 100644 index 0000000..f9fddc5 --- /dev/null +++ b/.github/ci-config.properties @@ -0,0 +1,17 @@ +# Placeholder config used only by CI (see .github/workflows/ci.yml). +# The real config-local.properties is gitignored and never committed. +# These values are dummies so the Maven build's copy-config step has a file to copy. + +## Discord Settings +discord.apiToken=ci-placeholder +discord.guildID=0 +discord.channelID=0 + +## Steam Settings +steam.api=ci-placeholder + +## Leetify API +leetify.apiToken=ci-placeholder + +## Carthage settings +carthage.url=http://localhost/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..7db739f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,60 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + workflow_dispatch: + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + name: Unit tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '21' + cache: maven + + # config-local.properties is gitignored and holds secrets, so it is not in + # the repo. The build's copy-config step only needs the file to exist, so a + # placeholder is enough to compile and run the tests in CI. + - name: Generate placeholder config + run: cp .github/ci-config.properties src/main/resources/config-local.properties + + - name: Run tests + run: mvn -B -ntp -P local test + + build: + name: Build package + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '21' + cache: maven + + - name: Generate placeholder config + run: cp .github/ci-config.properties src/main/resources/config-local.properties + + - name: Build shaded jar + run: mvn -B -ntp -P local -DskipTests package + + - name: Upload jar + uses: actions/upload-artifact@v4 + with: + name: CounterStrikeBot-jar + path: target/CounterStrikeBot-*.jar + if-no-files-found: error From e4d60ad1753ed8402dfe7aea76b2f5c93113123b Mon Sep 17 00:00:00 2001 From: Janes Thomas Date: Thu, 27 Aug 2026 22:15:34 +0200 Subject: [PATCH 9/9] bump version to 3.7 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9170021..1f1c642 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ ch.yoinc CounterStrikeBot - 3.6 + 3.7 CounterStrikeBot https://github.com/yoinc-development/CounterStrikeBot