diff --git a/src/main/java/pw/chew/mlb/MLBBot.java b/src/main/java/pw/chew/mlb/MLBBot.java index 7ad7c6a..b2001dc 100644 --- a/src/main/java/pw/chew/mlb/MLBBot.java +++ b/src/main/java/pw/chew/mlb/MLBBot.java @@ -16,6 +16,7 @@ import pw.chew.mlb.commands.ConfigCommand; import pw.chew.mlb.commands.GameInfoCommand; import pw.chew.mlb.commands.PlanGameCommand; +import pw.chew.mlb.commands.PlayerCommand; import pw.chew.mlb.commands.ScoreCommand; import pw.chew.mlb.commands.SetInfoCommand; import pw.chew.mlb.commands.StandingsCommand; @@ -66,7 +67,7 @@ public static void main(String[] args) throws IOException { new StartGameCommand(), new StopGameCommand(), new ScoreCommand(), new SetInfoCommand(), new ConfigCommand(), new PlanGameCommand() , // Stats Commands - new StandingsCommand(), new GameInfoCommand() + new StandingsCommand(), new GameInfoCommand(), new PlayerCommand() ); //client.forceGuildOnly("148195924567392257"); diff --git a/src/main/java/pw/chew/mlb/commands/PlayerCommand.java b/src/main/java/pw/chew/mlb/commands/PlayerCommand.java new file mode 100644 index 0000000..a37b07d --- /dev/null +++ b/src/main/java/pw/chew/mlb/commands/PlayerCommand.java @@ -0,0 +1,391 @@ +package pw.chew.mlb.commands; + +import com.jagrosh.jdautilities.command.SlashCommand; +import com.jagrosh.jdautilities.command.SlashCommandEvent; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.events.interaction.command.CommandAutoCompleteInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; +import net.dv8tion.jda.api.interactions.InteractionContextType; +import net.dv8tion.jda.api.interactions.commands.Command; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import net.dv8tion.jda.api.interactions.commands.build.OptionData; +import net.dv8tion.jda.api.interactions.components.ActionRow; +import net.dv8tion.jda.api.interactions.components.buttons.Button; +import net.dv8tion.jda.api.interactions.components.selections.SelectOption; +import net.dv8tion.jda.api.interactions.components.selections.StringSelectMenu; +import org.jetbrains.annotations.Nullable; +import org.json.JSONArray; +import org.json.JSONObject; +import pw.chew.chewbotcca.util.MiscUtil; +import pw.chew.chewbotcca.util.RestClient; +import pw.chew.mlb.util.EmbedUtil; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +public class PlayerCommand extends SlashCommand { + public PlayerCommand() { + this.name = "player"; + this.help = "Show information for a provided player."; + this.contexts = new InteractionContextType[]{InteractionContextType.GUILD, InteractionContextType.BOT_DM, InteractionContextType.PRIVATE_CHANNEL}; + this.options = List.of( + new OptionData(OptionType.INTEGER, "player", "The player to show information for.", true, true), + new OptionData(OptionType.INTEGER, "season", "The season to show information for.", false, true), + new OptionData(OptionType.STRING, "type", "Type of information to show.", false) + .addChoice("Batting", "batting") + .addChoice("Fielding", "fielding") + .addChoice("Pitching", "pitching") + ); + } + + @Override + protected void execute(SlashCommandEvent event) { + long playerId = event.optLong("player", 0); + + if (playerId == 0) { + event.replyEmbeds(EmbedUtil.failure("Invalid player returned. Try searching again!")).setEphemeral(true).queue(); + return; + } + + event.replyEmbeds(buildInfoEmbed(playerId)) + .addComponents(buildButtons("info", playerId)) + .setEphemeral(true) + .queue(); + } + + public static MessageEmbed buildInfoEmbed(long playerId) { + JSONObject player = RestClient.get("https://statsapi.mlb.com/api/v1/people/%s?hydrate=currentTeam,team,stats(type=[yearByYear,yearByYearAdvanced,careerRegularSeason,careerAdvanced,availableStats](team(league)),leagueListId=mlb_hist)" + .formatted(playerId)) + .asJSONObject() + .getJSONArray("people") + .getJSONObject(0); + + DateTimeFormatter inputFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + DateTimeFormatter outputFormatter = DateTimeFormatter.ofPattern("MMMM d, yyyy"); + + EmbedBuilder embed = new EmbedBuilder() + .setTitle("%s (#%s)".formatted(player.getString("fullName"), player.getString("primaryNumber"))) + .setThumbnail("https://img.mlbstatic.com/mlb-photos/image/upload/d_people:generic:headshot:67:current.png/w_426,q_auto:best/v1/people/%s/headshot/67/current".formatted(playerId)) + .setDescription(""" + %s | B/T: %s/%s | %s %s | Age: %s + """.formatted( + player.getJSONObject("primaryPosition").getString("abbreviation"), + player.getJSONObject("batSide").getString("code"), + player.getJSONObject("pitchHand").getString("code"), + player.getString("height"), + player.getInt("weight"), + player.getInt("currentAge") + ) + ).addField("Born", "%s\n%s, %s, %s".formatted( + LocalDate.parse(player.getString("birthDate"), inputFormatter).format(outputFormatter), + player.getString("birthCity"), + player.optString("birthStateProvince"), + player.getString("birthCountry") + ), true) + .addField("MLB Debut", LocalDate.parse(player.getString("mlbDebutDate"), inputFormatter).format(outputFormatter), true) + .addField("Status", player.getBoolean("active") ? "Active" : "Inactive", true); + + return embed.build(); + } + + public static MessageEmbed buildHittingStatsEmbed(long playerId) { + // get stats + JSONObject player = RestClient.get("https://statsapi.mlb.com/api/v1/people/%s?hydrate=currentTeam,team,stats(type=[yearByYear,yearByYearAdvanced,careerRegularSeason,careerAdvanced,availableStats](team(league)),leagueListId=mlb_hist),draft,awards,education" + .formatted(playerId)) + .asJSONObject() + .getJSONArray("people") + .getJSONObject(0); + + // get hitting stats + JSONObject stats = MiscUtil.toList(player.getJSONArray("stats"), JSONObject.class) + .stream() + .filter(stat -> stat.getJSONObject("type").getString("displayName").equals("yearByYear")) + .filter(stat -> stat.getJSONObject("group").getString("displayName").equals("hitting")) + .findFirst() + .orElse(null); + + if (stats == null) { + return EmbedUtil.failure("Failed to find hitting stats for this player."); + } + + // get the stats + JSONObject splits = stats.getJSONArray("splits").getJSONObject(stats.getJSONArray("splits").length() - 1); + JSONObject stat = splits.getJSONObject("stat"); + + String name = player.getString("fullName"); + + EmbedBuilder embed = new EmbedBuilder() + .setTitle("Hitting stats for %s".formatted(name)) + .addField("Overall", String.join("\n", List.of( + "Games: %s".formatted(stat.getInt("gamesPlayed")), + "At Bats: %s".formatted(stat.getInt("atBats")), + "Runs: %s".formatted(stat.getInt("runs")), + "Runs Batted In: %s".formatted(stat.getInt("rbi")), + "Walks: %s".formatted(stat.getInt("baseOnBalls")), + "Intentional Walks: %s".formatted(stat.getInt("intentionalWalks")), + "Strikeouts: %s".formatted(stat.getInt("strikeOuts")), + "Stolen Bases: %s".formatted(stat.getInt("stolenBases")), + "Caught Stealing: %s".formatted(stat.getInt("caughtStealing")) + )), true) + .addField("Hits", String.join("\n", List.of( + "Hits: %s".formatted(stat.getInt("hits")), + "Total Bases: %s".formatted(stat.getInt("totalBases")), + "Doubles: %s".formatted(stat.getInt("doubles")), + "Triples: %s".formatted(stat.getInt("triples")), + "Home Runs: %s".formatted(stat.getInt("homeRuns")) + )), true) + .addField("Percentages", String.join("\n", List.of( + "Average: %s".formatted(stat.getString("avg")), + "On Base: %s".formatted(stat.getString("obp")), + "Slugging: %s".formatted(stat.getString("slg")), + "On Base + Slugging: %s".formatted(stat.getString("ops")), + "Ground Outs/Air Outs: %s".formatted(stat.getString("groundOutsToAirouts")) + )), true); + + return embed.build(); + } + + public static List buildButtons(String current, long playerId) { + ActionRow statButtons = ActionRow.of( + Button.primary("player:info:%s".formatted(playerId), "Info").withDisabled(current.equals("info")), + Button.primary("player:hitting:%s".formatted(playerId), "Hitting").withDisabled(current.equals("hitting")), + Button.primary("player:fielding:%s".formatted(playerId), "Fielding").withDisabled(current.equals("fielding")), + Button.primary("player:pitching:%s".formatted(playerId), "Pitching").withDisabled(current.equals("pitching")) + ); + + ActionRow otherButtons = ActionRow.of( + Button.secondary("player:%s:%s:refresh".formatted(current, playerId), "Refresh"), + Button.secondary("player:sites:%s".formatted(playerId), "View Online") + ); + + JSONArray splits = RestClient.get("https://statsapi.mlb.com/api/v1/people/%s?hydrate=stats(type=[yearByYear])&fields=people,stats,splits,season".formatted(playerId)) + .asJSONObject() + .getJSONArray("people") + .getJSONObject(0) + .getJSONArray("stats") + .getJSONObject(0) + .getJSONArray("splits"); + List seasons = MiscUtil.toList(splits, JSONObject.class) + .stream() + .map(split -> split.getString("season")) + .distinct() + .map(season -> SelectOption.of(season, "player:season:%s:%s".formatted(playerId, season))) + .toList(); + + StringSelectMenu menu = StringSelectMenu.create("player:season:%s".formatted(playerId)) + .setPlaceholder("Select a season") + .addOptions(seasons) + .setDefaultOptions(seasons.get(seasons.size() - 1)) + .build(); + + return List.of( + statButtons, + ActionRow.of(menu), + otherButtons + ); + } + + public static List updateButtons(List rows, String updated) { + // first row is all we are changing + List rowsArray = new ArrayList<>(rows); + ActionRow row = rowsArray.remove(0); + + List newRows = new ArrayList<>(); + List