diff --git a/pom.xml b/pom.xml
index 150a23b..19bd24b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -75,6 +75,11 @@
jitpack.io
https://jitpack.io
+
+ fascinated-repo-public
+ Fascinated's Repository
+ https://repo.fascinated.cc/public
+
@@ -181,5 +186,10 @@
commons-text
1.12.0
+
+ xyz.mcutils
+ mcutils-java-library
+ 1.2.4
+
diff --git a/src/main/java/cc/fascinated/bat/features/Feature.java b/src/main/java/cc/fascinated/bat/features/Feature.java
index d5b1607..1ff4316 100644
--- a/src/main/java/cc/fascinated/bat/features/Feature.java
+++ b/src/main/java/cc/fascinated/bat/features/Feature.java
@@ -7,6 +7,8 @@ import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
+import java.lang.reflect.Member;
+
/**
* @author Fascinated (fascinated7)
*/
diff --git a/src/main/java/cc/fascinated/bat/features/minecraft/MinecraftFeature.java b/src/main/java/cc/fascinated/bat/features/minecraft/MinecraftFeature.java
new file mode 100644
index 0000000..dba7acd
--- /dev/null
+++ b/src/main/java/cc/fascinated/bat/features/minecraft/MinecraftFeature.java
@@ -0,0 +1,23 @@
+package cc.fascinated.bat.features.minecraft;
+
+import cc.fascinated.bat.features.Feature;
+import cc.fascinated.bat.features.FeatureProfile;
+import cc.fascinated.bat.features.minecraft.command.MinecraftCommand;
+import cc.fascinated.bat.service.CommandService;
+import lombok.NonNull;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Fascinated (fascinated7)
+ */
+@Component
+public class MinecraftFeature extends Feature {
+ @Autowired
+ public MinecraftFeature(@NonNull ApplicationContext context, @NonNull CommandService commandService) {
+ super("Minecraft", FeatureProfile.FeatureState.DISABLED, true);
+
+ super.registerCommand(commandService, context.getBean(MinecraftCommand.class));
+ }
+}
diff --git a/src/main/java/cc/fascinated/bat/features/minecraft/command/LookupPlayerCommand.java b/src/main/java/cc/fascinated/bat/features/minecraft/command/LookupPlayerCommand.java
new file mode 100644
index 0000000..5ba6f36
--- /dev/null
+++ b/src/main/java/cc/fascinated/bat/features/minecraft/command/LookupPlayerCommand.java
@@ -0,0 +1,65 @@
+package cc.fascinated.bat.features.minecraft.command;
+
+import cc.fascinated.bat.command.BatCommand;
+import cc.fascinated.bat.command.CommandInfo;
+import cc.fascinated.bat.common.EmbedDescriptionBuilder;
+import cc.fascinated.bat.common.EmbedUtils;
+import cc.fascinated.bat.model.BatGuild;
+import cc.fascinated.bat.model.BatUser;
+import lombok.NonNull;
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
+import net.dv8tion.jda.api.interactions.commands.OptionMapping;
+import net.dv8tion.jda.api.interactions.commands.OptionType;
+import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
+import net.dv8tion.jda.api.interactions.commands.build.OptionData;
+import org.springframework.stereotype.Component;
+import xyz.mcutils.McUtilsAPI;
+import xyz.mcutils.models.cache.CachedPlayer;
+import xyz.mcutils.models.player.Skin;
+
+/**
+ * @author Fascinated (fascinated7)
+ */
+@Component
+@CommandInfo(
+ name = "lookup-player",
+ description = "Lookup a Minecraft player"
+)
+public class LookupPlayerCommand extends BatCommand {
+ public LookupPlayerCommand() {
+ super.addOptions(
+ new OptionData(OptionType.STRING, "player", "The player to lookup", true)
+ );
+ }
+
+ @Override
+ public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
+ OptionMapping playerOption = event.getOption("player");
+ if (playerOption == null) {
+ return;
+ }
+ String player = playerOption.getAsString();
+
+ CachedPlayer cachedPlayer = McUtilsAPI.getPlayer(player);
+ if (cachedPlayer == null) {
+ event.reply("The player `%s` could not be found".formatted(player)).queue();
+ return;
+ }
+
+ String headUrl = cachedPlayer.getSkin() != null ? cachedPlayer.getSkin().getParts().get(Skin.SkinPart.HEAD.getName()) : null;
+ EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Player Lookup")
+ .appendLine("Username: `%s`".formatted(cachedPlayer.getUsername()), true)
+ .appendLine("UUID: `%s`".formatted(cachedPlayer.getUniqueId().toString()), true);
+ if (cachedPlayer.getSkin() != null) {
+ description.appendLine("Skin: [Click Here](%s)".formatted(headUrl), true);
+ }
+ if (cachedPlayer.getCape() != null) {
+ description.appendLine("Cape: [Click Here](%s)".formatted(cachedPlayer.getCape().getUrl()), true);
+ }
+ event.replyEmbeds(EmbedUtils.successEmbed()
+ .setDescription(description.build())
+ .setThumbnail(headUrl)
+ .build()).queue();
+ }
+}
diff --git a/src/main/java/cc/fascinated/bat/features/minecraft/command/LookupServerCommand.java b/src/main/java/cc/fascinated/bat/features/minecraft/command/LookupServerCommand.java
new file mode 100644
index 0000000..2ca253c
--- /dev/null
+++ b/src/main/java/cc/fascinated/bat/features/minecraft/command/LookupServerCommand.java
@@ -0,0 +1,104 @@
+package cc.fascinated.bat.features.minecraft.command;
+
+import cc.fascinated.bat.command.BatCommand;
+import cc.fascinated.bat.command.CommandInfo;
+import cc.fascinated.bat.common.EmbedDescriptionBuilder;
+import cc.fascinated.bat.common.EmbedUtils;
+import cc.fascinated.bat.model.BatGuild;
+import cc.fascinated.bat.model.BatUser;
+import lombok.NonNull;
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
+import net.dv8tion.jda.api.interactions.commands.OptionMapping;
+import net.dv8tion.jda.api.interactions.commands.OptionType;
+import net.dv8tion.jda.api.interactions.commands.SlashCommandInteraction;
+import net.dv8tion.jda.api.interactions.commands.build.OptionData;
+import org.springframework.stereotype.Component;
+import xyz.mcutils.McUtilsAPI;
+import xyz.mcutils.models.cache.CachedBedrockMinecraftServer;
+import xyz.mcutils.models.cache.CachedJavaMinecraftServer;
+import xyz.mcutils.models.server.MinecraftServer;
+
+/**
+ * @author Fascinated (fascinated7)
+ */
+@Component
+@CommandInfo(
+ name = "lookup-server",
+ description = "Lookup a Minecraft server"
+)
+public class LookupServerCommand extends BatCommand {
+ public LookupServerCommand() {
+ super.addOptions(
+ new OptionData(OptionType.STRING, "platform", "The platform of the server to lookup", true)
+ .addChoice("Java", "java")
+ .addChoice("Bedrock", "bedrock"),
+ new OptionData(OptionType.STRING, "host", "The host/ip of the server to lookup", true)
+ );
+ }
+
+ @Override
+ public void execute(BatGuild guild, @NonNull BatUser user, @NonNull MessageChannel channel, Member member, @NonNull SlashCommandInteraction event) {
+ OptionMapping platformOption = event.getOption("platform");
+ OptionMapping hostOption = event.getOption("host");
+ if (platformOption == null || hostOption == null) {
+ return;
+ }
+ String platform = platformOption.getAsString();
+ String host = hostOption.getAsString();
+
+ MinecraftServer server;
+ try {
+ if (platform.equalsIgnoreCase("java")) {
+ server = McUtilsAPI.getJavaServer(host);
+ } else {
+ server = McUtilsAPI.getBedrockServer(host);
+ }
+ } catch (Exception ex) {
+ event.replyEmbeds(EmbedUtils.errorEmbed()
+ .setDescription("The server `%s` is invalid or offline".formatted(host))
+ .build()).queue();
+ return;
+ }
+
+ int platformDefaultPort = platform.equalsIgnoreCase("java") ? 25565 : 19132;
+ String hostname = server.getHostname() + (server.getPort() != platformDefaultPort ? ":" + server.getPort() : "");
+ EmbedDescriptionBuilder description = new EmbedDescriptionBuilder("Server Lookup [(Raw Data)](%s)".formatted(
+ "https://api.mcutils.xyz/server/%s/%s".formatted(platform, hostname)
+ ));
+ description.appendLine("Host: `%s`".formatted(hostname), true);
+
+ MinecraftServer.GeoLocation location = server.getLocation();
+ if (server instanceof CachedJavaMinecraftServer javaServer) {
+ description.appendLine("Version: `%s`".formatted(javaServer.getVersion().getName()), true);
+ if (javaServer.getForgeData() != null) {
+ description.appendLine("Forge Mods: `%s`".formatted(javaServer.getForgeData().getMods().length), true);
+ }
+ if (javaServer.isMojangBlocked()) {
+ description.appendLine("Mojang Blocked: `Yes`", true);
+ }
+ if (javaServer.isEnforcesSecureChat()) {
+ description.appendLine("Enforces Secure Chat: `Yes`", true);
+ }
+ if (javaServer.isPreventsChatReports()) {
+ description.appendLine("Prevents Chat Reports: `Yes`", true);
+ }
+ }
+ if (server instanceof CachedBedrockMinecraftServer bedrockServer) {
+ description.appendLine("Version: `%s`".formatted(bedrockServer.getVersion().getName()), true);
+ }
+ if (location != null) {
+ description.appendLine("Location: [%s](%s)".formatted(
+ (location.getCity() == null ? "" : location.getCity() + ", ") + location.getCountry(),
+ "https://www.google.com/maps/search/?api=1&query=%s,%s".formatted(location.getLatitude(), location.getLongitude())
+ ), true);
+ }
+
+ event.replyEmbeds(EmbedUtils.successEmbed()
+ .setDescription(description.build())
+ .setThumbnail("https://api.mcutils.xyz/server/icon/%s".formatted(hostname))
+ .setImage(server.getMotd().getPreview())
+ .setFooter("Powered by mcutils.xyz")
+ .build()).queue();
+ }
+}
diff --git a/src/main/java/cc/fascinated/bat/features/minecraft/command/MinecraftCommand.java b/src/main/java/cc/fascinated/bat/features/minecraft/command/MinecraftCommand.java
new file mode 100644
index 0000000..f7d6a07
--- /dev/null
+++ b/src/main/java/cc/fascinated/bat/features/minecraft/command/MinecraftCommand.java
@@ -0,0 +1,26 @@
+package cc.fascinated.bat.features.minecraft.command;
+
+import cc.fascinated.bat.command.BatCommand;
+import cc.fascinated.bat.command.CommandInfo;
+import lombok.NonNull;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Fascinated (fascinated7)
+ */
+@Component
+@CommandInfo(
+ name = "minecraft",
+ description = "Minecraft related commands"
+)
+public class MinecraftCommand extends BatCommand {
+ @Autowired
+ public MinecraftCommand(@NonNull ApplicationContext context) {
+ super.addSubCommands(
+ context.getBean(LookupPlayerCommand.class),
+ context.getBean(LookupServerCommand.class)
+ );
+ }
+}
diff --git a/src/main/java/cc/fascinated/bat/service/EventService.java b/src/main/java/cc/fascinated/bat/service/EventService.java
index c9e3e55..40c2c4d 100644
--- a/src/main/java/cc/fascinated/bat/service/EventService.java
+++ b/src/main/java/cc/fascinated/bat/service/EventService.java
@@ -18,6 +18,7 @@ import net.dv8tion.jda.api.events.emoji.EmojiRemovedEvent;
import net.dv8tion.jda.api.events.emoji.update.EmojiUpdateNameEvent;
import net.dv8tion.jda.api.events.guild.GuildBanEvent;
import net.dv8tion.jda.api.events.guild.GuildUnbanEvent;
+import net.dv8tion.jda.api.events.guild.invite.GenericGuildInviteEvent;
import net.dv8tion.jda.api.events.guild.invite.GuildInviteCreateEvent;
import net.dv8tion.jda.api.events.guild.member.GuildMemberJoinEvent;
import net.dv8tion.jda.api.events.guild.member.GuildMemberRemoveEvent;