Add the server lookup command

This commit is contained in:
Braydon 2024-04-24 19:01:07 -04:00
parent 1474598a1a
commit 6c8d486052
5 changed files with 200 additions and 3 deletions

@ -2,6 +2,7 @@ package cc.restfulmc.bot.command;
import cc.restfulmc.bot.DiscordBot;
import cc.restfulmc.bot.command.impl.PlayerCommand;
import cc.restfulmc.bot.command.impl.ServerCommand;
import cc.restfulmc.sdk.client.RESTfulMCClient;
import lombok.NonNull;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
@ -20,7 +21,9 @@ public final class CommandManager extends ListenerAdapter {
private final List<SlashCommand> commands = Collections.synchronizedList(new ArrayList<>());
public CommandManager(@NonNull DiscordBot bot, @NonNull RESTfulMCClient apiClient) {
// Register commands
registerCommand(new PlayerCommand(apiClient));
registerCommand(new ServerCommand(apiClient));
// Update the commands on Discord
CommandListUpdateAction updateCommands = bot.getJda().updateCommands();

@ -53,10 +53,21 @@ public abstract class SlashCommand {
* @param apiError the api error to reply with
*/
protected final void replyWithApiError(@NonNull SlashCommandInteractionEvent event, @NonNull RESTfulMCAPIException apiError) {
replyWithGenericError(event, apiError.getCode() + " | API Error", apiError.getLocalizedMessage());
}
/**
* Reply to an interaction with a generic error.
*
* @param event the event to reply to
* @param title the title of the error
* @param description the description of the error
*/
protected final void replyWithGenericError(@NonNull SlashCommandInteractionEvent event, @NonNull String title, @NonNull String description) {
event.getHook().sendMessageEmbeds(new EmbedBuilder()
.setColor(0xAA0000)
.setTitle(apiError.getCode() + " | API Error")
.setDescription(apiError.getMessage())
.setTitle(title)
.setDescription(description)
.build()).queue();
}
}

@ -5,6 +5,7 @@ import cc.restfulmc.sdk.client.RESTfulMCClient;
import cc.restfulmc.sdk.exception.RESTfulMCAPIException;
import cc.restfulmc.sdk.response.Player;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
@ -16,6 +17,7 @@ import net.dv8tion.jda.api.interactions.commands.build.OptionData;
/**
* @author Braydon
*/
@Slf4j(topic = "Server Lookup Command")
public final class PlayerCommand extends SlashCommand {
/**
* The API client to use for lookups.
@ -49,7 +51,7 @@ public final class PlayerCommand extends SlashCommand {
if (ex.getCause() instanceof RESTfulMCAPIException apiError) {
replyWithApiError(event, apiError);
} else { // Only print real errors
ex.printStackTrace();
log.error("Failed fetching player:", ex);
}
return;
}

@ -0,0 +1,160 @@
package cc.restfulmc.bot.command.impl;
import cc.restfulmc.bot.command.SlashCommand;
import cc.restfulmc.bot.common.StringUtils;
import cc.restfulmc.sdk.client.RESTfulMCClient;
import cc.restfulmc.sdk.exception.RESTfulMCAPIException;
import cc.restfulmc.sdk.response.server.BedrockMinecraftServer;
import cc.restfulmc.sdk.response.server.JavaMinecraftServer;
import cc.restfulmc.sdk.response.server.MinecraftServer;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Map;
/**
* @author Braydon
*/
@Slf4j(topic = "Server Lookup Command")
public final class ServerCommand extends SlashCommand {
private static final Map<MinecraftServer.Platform, String> MAPPED_PLATFORM_EMOJIS = new HashMap<>() {{
put(MinecraftServer.Platform.JAVA, "<:grass_block:1232798337300828181>");
put(MinecraftServer.Platform.BEDROCK, "<:bedrock_block:1232798365427830928>");
}};
private static final DecimalFormat PLAYERS_FORMAT = new DecimalFormat("#,##0");
/**
* The API client to use for lookups.
*/
@NonNull private final RESTfulMCClient apiClient;
public ServerCommand(@NonNull RESTfulMCClient apiClient) {
super("server", "Lookup a server by its platform and hostname",
new OptionData(OptionType.STRING, "platform", "The platform: JAVA | BEDROCK").setRequired(true),
new OptionData(OptionType.STRING, "hostname", "The hostname of the server").setRequired(true)
);
this.apiClient = apiClient;
}
/**
* Invoked when this command is executed.
*
* @param user the executing user
* @param member the executing member, null if in dms
* @param event the event that triggered this command
*/
@Override
public void onExecute(@NonNull User user, Member member, @NonNull SlashCommandInteractionEvent event) {
OptionMapping platformOption = event.getOption("platform"); // The platform to query
assert platformOption != null;
String platformValue = platformOption.getAsString();
MinecraftServer.Platform platform; // The platform to lookup
try {
platform = MinecraftServer.Platform.valueOf(platformValue.toUpperCase());
} catch (IllegalArgumentException ex) {
replyWithGenericError(event, "Invalid Platform", "You must specify either **Java** or **Bedrock** as the platform.");
return;
}
OptionMapping hostname = event.getOption("hostname"); // Get the hostname to query
assert hostname != null;
String hostnameValue = hostname.getAsString(); // Get the hostname value
// Lookup the requested server by the given platform and hostname
apiClient.async().getMinecraftServer(platform, hostnameValue).whenComplete((server, ex) -> {
// Failed to lookup the server, handle the error
if (ex != null) {
if (ex.getCause() instanceof RESTfulMCAPIException apiError) {
replyWithApiError(event, apiError);
} else { // Only print real errors
log.error("Failed fetching Minecraft Server:", ex);
}
return;
}
// Respond with the server
String ip = server.getIp(); // The resolved IP of the server
MinecraftServer.GeoLocation geo = server.getGeo(); // The geo location of the server
MinecraftServer.Players players = server.getPlayers(); // The players on the server
long cached = server.getCached(); // The timestamp the server was cached
EmbedBuilder embed = new EmbedBuilder()
.setColor(0x55FF55)
.setTitle(
MAPPED_PLATFORM_EMOJIS.get(platform) + " " + StringUtils.capitalize(platform.name()) + " Server Response: " + server.getHostname(),
"https://api.restfulmc.cc/server/" + platformValue + "/" + hostnameValue
).addField("IP", (ip == null ? server.getHostname() : ip) + ":" + server.getPort(), true);
// Show Geo location if resolved
if (geo != null) {
MinecraftServer.GeoLocation.LocationData country = geo.getCountry(); // The server's country
embed.addField("Located", ":flag_" + country.getCode().toLowerCase() + ": " + country.getName(), true);
}
// Append platform specific details to the embed
if (server instanceof JavaMinecraftServer javaServer) {
appendJavaEmbed(embed, javaServer);
} else {
appendBedrockEmbed(embed, (BedrockMinecraftServer) server);
}
event.getHook().sendMessageEmbeds(embed
.addField("Players", PLAYERS_FORMAT.format(players.getOnline()) + "/" + PLAYERS_FORMAT.format(players.getMax()), true)
.addField("Cached", cached == -1L ? "No" : "Yes, <t:" + (cached / 1000L) + ":R>", true)
.setThumbnail(server instanceof JavaMinecraftServer javaServer ? javaServer.getFavicon().getUrl() : "https://api.restfulmc.cc/server/icon/invalid")
.setFooter("Requested by " + user.getName() + " | " + user.getId(), user.getEffectiveAvatarUrl())
.build()).queue();
});
}
/**
* Append Java specific details to an embed.
*
* @param embed the embed to append to
* @param javaServer the java server
*/
private void appendJavaEmbed(@NonNull EmbedBuilder embed, @NonNull JavaMinecraftServer javaServer) {
JavaMinecraftServer.Version version = javaServer.getVersion(); // The version of the server
String protocolName = version.getProtocolName(); // The name of the server's version protocol
String platform = version.getPlatform(); // The server's platform
String world = javaServer.getWorld(); // The server's main world
JavaMinecraftServer.Plugin[] plugins = javaServer.getPlugins(); // The server's plugins
embed.addField("Version", version.getProtocol() + (protocolName == null ? "" : " (" + protocolName + ")"), true);
if (platform != null) {
embed.addField("Platform", platform, true);
}
if (world != null) {
embed.addField("World", world, true);
}
if (plugins != null) {
embed.addField("Plugins", String.valueOf(plugins.length), true);
}
embed.addField("Query", javaServer.isQueryEnabled() ? "Enabled" : "Disabled", true);
embed.addField("Mojang Banned", javaServer.isMojangBanned() ? "Yes" : "No", true);
}
/**
* Append Bedrock specific details to an embed.
*
* @param embed the embed to append to
* @param bedrockServer the bedrock server
*/
private void appendBedrockEmbed(@NonNull EmbedBuilder embed, @NonNull BedrockMinecraftServer bedrockServer) {
BedrockMinecraftServer.Version version = bedrockServer.getVersion(); // The version of the server
BedrockMinecraftServer.GameMode gamemode = bedrockServer.getGamemode(); // The game mode of the server
embed.addField("Version", version.getProtocol() + " (" + version.getName() + ")", true);
embed.addField("Edition", bedrockServer.getEdition().name(), true);
embed.addField("GameMode", gamemode.getName() + " (" + gamemode.getNumericId() + ")", true);
}
}

@ -0,0 +1,21 @@
package cc.restfulmc.bot.common;
import lombok.NonNull;
import lombok.experimental.UtilityClass;
/**
* @author Braydon
*/
@UtilityClass
public final class StringUtils {
/**
* Capitalize the first character in the given string.
*
* @param input the input to capitalize
* @return the capitalized string
*/
@NonNull
public static String capitalize(@NonNull String input) {
return Character.toUpperCase(input.charAt(0)) + input.substring(1).toLowerCase();
}
}