diff --git a/pom.xml b/pom.xml index 804a8c4..67a8bf8 100644 --- a/pom.xml +++ b/pom.xml @@ -138,28 +138,24 @@ 2.11.5 provided - com.github.oshi oshi-core 6.5.0 compile - com.influxdb influxdb-client-java 7.0.0 compile - com.viaversion viaversion-api 4.9.2 provided - com.vexsoftware nuvotifier-universal @@ -167,5 +163,17 @@ ${basedir}/lib/nuvotifier.jar system + + net.dv8tion + JDA + 5.0.0-beta.21 + + + club.minnced + opus-java + + + compile + \ No newline at end of file diff --git a/src/main/java/cc/fascinated/Aetheria.java b/src/main/java/cc/fascinated/Aetheria.java index 5c28962..a397fc1 100644 --- a/src/main/java/cc/fascinated/Aetheria.java +++ b/src/main/java/cc/fascinated/Aetheria.java @@ -1,6 +1,7 @@ package cc.fascinated; import cc.fascinated.account.AccountManager; +import cc.fascinated.bot.DiscordBot; import cc.fascinated.chat.ChatManager; import cc.fascinated.command.CommandManager; import cc.fascinated.commandspy.CommandSpyManager; @@ -27,11 +28,13 @@ public class Aetheria extends JavaPlugin { @Getter private static final BuildData buildData = new BuildData(); + /** * The instance of the plugin. */ public static Aetheria INSTANCE; - public static ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(2, 8, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()); + public static ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(2, 8, 0L, + TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()); public Aetheria() { INSTANCE = this; @@ -45,7 +48,6 @@ public class Aetheria extends JavaPlugin { Lang.clear(); new AccountManager(); - new EventManager(); new CommandManager(); new WorldSizeManager(); @@ -58,5 +60,6 @@ public class Aetheria extends JavaPlugin { new RenderDistanceManager(); new VoteManager(); new StaffChatManager(); + new DiscordBot(); } } \ No newline at end of file diff --git a/src/main/java/cc/fascinated/account/Account.java b/src/main/java/cc/fascinated/account/Account.java index e5a1a34..6849585 100644 --- a/src/main/java/cc/fascinated/account/Account.java +++ b/src/main/java/cc/fascinated/account/Account.java @@ -69,7 +69,7 @@ public class Account { */ private long lastLogin; - public Account(UUID uuid) { + public Account(UUID uuid, String name) { //log.info("Loading account for " + uuid); boolean newAccount = false; @@ -100,7 +100,7 @@ public class Account { this.lastLogin = config.getLong("lastLogin"); this.lastLogin = System.currentTimeMillis(); // Update last login - this.name = Bukkit.getOfflinePlayer(uuid).getName(); + this.name = name; // Update the name // Load profiles this.playerColorProfile = new PlayerColorProfile(this, this.getProfileSection(playerColorProfileId)); @@ -204,6 +204,16 @@ public class Account { return getPlayer().isOp(); } + /** + * Set if the player has the permission. + * + * @param permission the permission to check + * @return if the player has the permission + */ + public boolean hasPermission(String permission) { + return getPlayer().hasPermission(permission); + } + /** * Saves the account to disk. * diff --git a/src/main/java/cc/fascinated/account/AccountManager.java b/src/main/java/cc/fascinated/account/AccountManager.java index 803b8bb..e549f8e 100644 --- a/src/main/java/cc/fascinated/account/AccountManager.java +++ b/src/main/java/cc/fascinated/account/AccountManager.java @@ -2,16 +2,18 @@ package cc.fascinated.account; import cc.fascinated.Aetheria; import cc.fascinated.account.command.SaveAccountsCommand; +import cc.fascinated.bot.DiscordBot; +import cc.fascinated.bot.DiscordChannel; import cc.fascinated.command.CommandManager; import cc.fascinated.config.Config; import cc.fascinated.config.Lang; import cc.fascinated.playercolor.PlayerColorProfile; -import cc.fascinated.utils.DiscordWebhook; import cc.fascinated.utils.Manager; import cc.fascinated.utils.Priority; import cc.fascinated.utils.Style; import com.viaversion.viaversion.api.Via; import lombok.extern.log4j.Log4j2; +import net.dv8tion.jda.api.EmbedBuilder; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -20,7 +22,8 @@ import org.bukkit.event.player.AsyncPlayerPreLoginEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; -import java.io.IOException; +import java.awt.*; +import java.util.List; import java.util.*; import java.util.concurrent.TimeUnit; @@ -38,7 +41,7 @@ public class AccountManager extends Manager implements Listener { if (isAccountLoaded(player.getUniqueId())) { // Don't load the account if it's already loaded continue; } - loadAccount(player.getUniqueId()); + loadAccount(player.getUniqueId(), player.getName()); } Bukkit.getAsyncScheduler().runAtFixedRate(Aetheria.INSTANCE, (task) -> { @@ -46,6 +49,21 @@ public class AccountManager extends Manager implements Listener { }, SAVE_INTERVAL, SAVE_INTERVAL, TimeUnit.MINUTES); } + /** + * Gets a list of all online accounts. + * + * @return the online accounts + */ + public static List getOnlineAccounts() { + List accounts = new ArrayList<>(); + for (Account account : ACCOUNTS.values()) { + if (account.isOnline()) { + accounts.add(account); + } + } + return accounts; + } + /** * Gets the account for the specified player. * @@ -60,9 +78,10 @@ public class AccountManager extends Manager implements Listener { * Registers an account for the specified player. * * @param uuid the player's UUID + * @param name the player's name */ - private static void loadAccount(UUID uuid) { - Account account = new Account(uuid); + private static void loadAccount(UUID uuid, String name) { + Account account = new Account(uuid, name); ACCOUNTS.put(uuid, account); } @@ -105,10 +124,11 @@ public class AccountManager extends Manager implements Listener { @EventHandler public void onAsyncPlayerPreLoginEvent(AsyncPlayerPreLoginEvent event) { UUID uuid = event.getUniqueId(); + String name = event.getName(); if (isAccountLoaded(uuid)) { // Account already loaded return; } - loadAccount(uuid); // Load the account into memory + loadAccount(uuid, name); // Load the account into memory } @Override @@ -119,22 +139,13 @@ public class AccountManager extends Manager implements Listener { if (!account.getPlayer().hasPlayedBefore()) { joinMessage = Lang.FIRST_JOIN_MESSAGE.getAsString(); - // Send a notification to the discord log channel - Aetheria.EXECUTOR.execute(() -> { - // todo: re-code this it's so ugly - DiscordWebhook discordWebhook = new DiscordWebhook(Config.DISCORD_LOG_WEBHOOK.getAsString()); - DiscordWebhook.EmbedObject embed = new DiscordWebhook.EmbedObject(); - embed.setTitle("New Player Joined"); - embed.addField("Name", account.getName(), true); - embed.addField("UUID", account.getUuid().toString(), true); - - discordWebhook.addEmbed(embed); - try { - discordWebhook.execute(); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); + DiscordBot.sendEmbed(DiscordChannel.PLAYER_LOGS, new EmbedBuilder() + .setThumbnail("https://crafatar.com/avatars/" + account.getUuid()) + .setColor(Color.GREEN) + .setTitle("New Player Joined") + .addField("Player", account.getName(), true) + .addField("UUID", account.getUuid().toString(), true) + ); } event.joinMessage(Style.getMiniMessage().deserialize(joinMessage .replace("%player%", account.getName()) diff --git a/src/main/java/cc/fascinated/account/command/DeleteAccountCommand.java b/src/main/java/cc/fascinated/account/command/DeleteAccountCommand.java new file mode 100644 index 0000000..d6f9b61 --- /dev/null +++ b/src/main/java/cc/fascinated/account/command/DeleteAccountCommand.java @@ -0,0 +1,16 @@ +package cc.fascinated.account.command; + +import cc.fascinated.account.Account; +import cc.fascinated.command.Command; + +public class DeleteAccountCommand extends Command { + + public DeleteAccountCommand() { + super("deleteaccount", "aetheria.command.deleteaccount"); + } + + @Override + public void execute(Account account, String[] args) { + + } +} diff --git a/src/main/java/cc/fascinated/bot/DiscordBot.java b/src/main/java/cc/fascinated/bot/DiscordBot.java new file mode 100644 index 0000000..300ea9a --- /dev/null +++ b/src/main/java/cc/fascinated/bot/DiscordBot.java @@ -0,0 +1,105 @@ +package cc.fascinated.bot; + +import cc.fascinated.config.Config; +import cc.fascinated.utils.Manager; +import lombok.Getter; +import lombok.extern.log4j.Log4j2; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.JDABuilder; +import net.dv8tion.jda.api.OnlineStatus; +import net.dv8tion.jda.api.entities.Activity; +import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; +import net.dv8tion.jda.api.utils.cache.CacheFlag; +import org.bukkit.Bukkit; + +import java.util.Timer; +import java.util.TimerTask; + +@Getter @Log4j2 +public class DiscordBot extends Manager { + + /** + * How often we should update the Discord presence. + */ + private final int statusUpdateInterval = 1000 * 60 * 5; + + /** + * The Discord bot instance. + */ + @Getter + private static JDA jda; + + /** + * The thread that the bot runs on. + */ + private final Thread thread; + + public DiscordBot() { + this.thread = new Thread(() -> { + log.info("Starting Discord bot..."); + JDABuilder builder = JDABuilder.createDefault(Config.DISCORD_TOKEN.getAsString()); + builder.disableCache( + CacheFlag.MEMBER_OVERRIDES, + CacheFlag.VOICE_STATE, + CacheFlag.ACTIVITY + ); + + try { + jda = builder.build(); + jda.awaitReady(); + log.info("Discord bot started successfully."); + } catch (Exception ex) { + log.error("Failed to start Discord bot.", ex); + return; + } + + new Timer().scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + int playerCount = Bukkit.getOnlinePlayers().size(); + + jda.getPresence().setPresence(OnlineStatus.ONLINE, Activity.watching("over " + playerCount + " players")); + } + }, 0, statusUpdateInterval); + }); + this.thread.setName("DiscordBot"); + this.thread.start(); + } + + /** + * Sends a message to the Discord channel. + * + * @param channel the channel + * @param message the message + */ + public static void sendMessage(DiscordChannel channel, String message) { + TextChannel textChannel = jda.getTextChannelById(channel.getId()); + if (textChannel == null) { + log.error("Could not find the text channel with the ID " + channel.getId()); + return; + } + textChannel.sendMessage(message).queue(); + } + + /** + * Sends an embed to the Discord channel. + * + * @param channel the channel + * @param embed the embed + */ + public static void sendEmbed(DiscordChannel channel, EmbedBuilder embed) { + TextChannel textChannel = jda.getTextChannelById(channel.getId()); + if (textChannel == null) { + log.error("Could not find the text channel with the ID " + channel.getId()); + return; + } + textChannel.sendMessageEmbeds(embed.build()).queue(); + } + + @Override + public void onAetheriaDisable() { + log.info("Shutting down Discord bot..."); + jda.shutdownNow(); + } +} diff --git a/src/main/java/cc/fascinated/bot/DiscordChannel.java b/src/main/java/cc/fascinated/bot/DiscordChannel.java new file mode 100644 index 0000000..ecdb8c3 --- /dev/null +++ b/src/main/java/cc/fascinated/bot/DiscordChannel.java @@ -0,0 +1,16 @@ +package cc.fascinated.bot; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter @RequiredArgsConstructor +public enum DiscordChannel { + + PLAYER_LOGS("1223161810157568020"), + VOTE_LOGS("1225868584522219570"); + + /** + * The ID of the Discord channel. + */ + private final String id; +} diff --git a/src/main/java/cc/fascinated/command/Command.java b/src/main/java/cc/fascinated/command/Command.java index 1739e7f..23d18e5 100644 --- a/src/main/java/cc/fascinated/command/Command.java +++ b/src/main/java/cc/fascinated/command/Command.java @@ -54,7 +54,7 @@ public abstract class Command implements CommandExecutor, TabCompleter { } @Override - public boolean onCommand(@NotNull CommandSender commandSender, org.bukkit.command.@NotNull Command command, @NotNull String s, @NotNull String[] strings) { + public final boolean onCommand(@NotNull CommandSender commandSender, org.bukkit.command.@NotNull Command command, @NotNull String s, @NotNull String[] strings) { if (this.permission != null && !commandSender.hasPermission(permission)) { commandSender.sendMessage("§cYou do not have permission to execute this command."); return true; @@ -67,7 +67,7 @@ public abstract class Command implements CommandExecutor, TabCompleter { } @Override - public @Nullable List onTabComplete(@NotNull CommandSender commandSender, org.bukkit.command.@NotNull Command command, @NotNull String s, @NotNull String[] strings) { + public final @Nullable List onTabComplete(@NotNull CommandSender commandSender, org.bukkit.command.@NotNull Command command, @NotNull String s, @NotNull String[] strings) { if (this.permission != null && !commandSender.hasPermission(permission)) { return null; } diff --git a/src/main/java/cc/fascinated/command/CommandManager.java b/src/main/java/cc/fascinated/command/CommandManager.java index c5dc66d..689d7ad 100644 --- a/src/main/java/cc/fascinated/command/CommandManager.java +++ b/src/main/java/cc/fascinated/command/CommandManager.java @@ -14,6 +14,11 @@ public class CommandManager { registerCommand(new GitCommand()); } + /** + * Registers a command. + * + * @param command The command to register. + */ public static void registerCommand(Command command) { if (command == null) { throw new IllegalArgumentException("Command cannot be null."); diff --git a/src/main/java/cc/fascinated/config/Config.java b/src/main/java/cc/fascinated/config/Config.java index e650f0e..9e5b9ae 100644 --- a/src/main/java/cc/fascinated/config/Config.java +++ b/src/main/java/cc/fascinated/config/Config.java @@ -14,7 +14,7 @@ public enum Config { INFLUXDB_TOKEN("influxdb.token"), INFLUXDB_ORG("influxdb.org"), INFLUXDB_BUCKET("influxdb.bucket"), - DISCORD_LOG_WEBHOOK("discord.log-webhook"), + DISCORD_TOKEN("discord.token"), MOTD_HEADER("motd.header"), MOTD_FORMAT("motd.format"), MOTD_LIST("motd.motds"), diff --git a/src/main/java/cc/fascinated/playercolor/PlayerColorManager.java b/src/main/java/cc/fascinated/playercolor/PlayerColorManager.java index 0087327..391bc10 100644 --- a/src/main/java/cc/fascinated/playercolor/PlayerColorManager.java +++ b/src/main/java/cc/fascinated/playercolor/PlayerColorManager.java @@ -20,15 +20,6 @@ public class PlayerColorManager extends Manager { */ @Getter private static final List validColors = List.of( -// NamedTextColor.RED, -// NamedTextColor.GREEN, -// NamedTextColor.BLUE, -// NamedTextColor.YELLOW, -// NamedTextColor.AQUA, -// NamedTextColor.DARK_AQUA, -// NamedTextColor.GRAY, -// NamedTextColor.LIGHT_PURPLE, -// NamedTextColor.WHITE NamedTextColor.DARK_BLUE, NamedTextColor.DARK_GREEN, NamedTextColor.DARK_AQUA, diff --git a/src/main/java/cc/fascinated/staffchat/StaffChatManager.java b/src/main/java/cc/fascinated/staffchat/StaffChatManager.java index 22b6769..6d83460 100644 --- a/src/main/java/cc/fascinated/staffchat/StaffChatManager.java +++ b/src/main/java/cc/fascinated/staffchat/StaffChatManager.java @@ -1,11 +1,33 @@ package cc.fascinated.staffchat; +import cc.fascinated.account.Account; +import cc.fascinated.account.AccountManager; +import cc.fascinated.config.Lang; import cc.fascinated.staffchat.command.StaffChatCommand; import cc.fascinated.utils.Manager; +import org.bukkit.Bukkit; public class StaffChatManager extends Manager { public StaffChatManager() { registerCommand(new StaffChatCommand()); } + + /** + * Sends a message to all staff members. + * + * @param sender the account who sent the message + * @param message the message to send + */ + public static void sendMessage(Account sender, String message) { + AccountManager.getOnlineAccounts().stream() + .filter(account -> account.hasPermission("aetheria.command.staffchat")) + .forEach(account -> { + account.sendMessage(Lang.STAFF_CHAT_FORMAT.getAsString() + .replace("%player%", sender.getPlayer().getName()) + .replace("%message%", message) + .replace("player-color", sender.getPlayerColorProfile().getColor().toString()) + ); + }); + } } diff --git a/src/main/java/cc/fascinated/staffchat/command/StaffChatCommand.java b/src/main/java/cc/fascinated/staffchat/command/StaffChatCommand.java index 397440a..5cb201c 100644 --- a/src/main/java/cc/fascinated/staffchat/command/StaffChatCommand.java +++ b/src/main/java/cc/fascinated/staffchat/command/StaffChatCommand.java @@ -1,10 +1,9 @@ package cc.fascinated.staffchat.command; import cc.fascinated.account.Account; -import cc.fascinated.account.AccountManager; import cc.fascinated.command.Command; import cc.fascinated.config.Lang; -import org.bukkit.Bukkit; +import cc.fascinated.staffchat.StaffChatManager; public class StaffChatCommand extends Command { @@ -18,21 +17,6 @@ public class StaffChatCommand extends Command { account.sendMessage(Lang.STAFF_CHAT_USAGE.getAsString()); return; } - - StringBuilder message = new StringBuilder(); - for (String arg : args) { - message.append(arg).append(" "); - } - - Bukkit.getOnlinePlayers().stream() - .filter(player -> player.hasPermission("aetheria.command.staffchat")) - .forEach(player -> { - Account staffAccount = AccountManager.getAccount(player.getUniqueId()); - staffAccount.sendMessage(Lang.STAFF_CHAT_FORMAT.getAsString() - .replace("%player%", account.getPlayer().getName()) - .replace("%message%", message.toString()) - .replace("player-color", account.getPlayerColorProfile().getColor().toString()) - ); - }); + StaffChatManager.sendMessage(account, String.join(" ", args)); } } diff --git a/src/main/java/cc/fascinated/vote/VoteManager.java b/src/main/java/cc/fascinated/vote/VoteManager.java index a1edd5d..20615fd 100644 --- a/src/main/java/cc/fascinated/vote/VoteManager.java +++ b/src/main/java/cc/fascinated/vote/VoteManager.java @@ -1,6 +1,8 @@ package cc.fascinated.vote; import cc.fascinated.account.Account; +import cc.fascinated.bot.DiscordBot; +import cc.fascinated.bot.DiscordChannel; import cc.fascinated.command.CommandManager; import cc.fascinated.config.Lang; import cc.fascinated.playercolor.PlayerColorProfile; @@ -8,6 +10,9 @@ import cc.fascinated.utils.Manager; import cc.fascinated.utils.MessageUtils; import cc.fascinated.vote.command.VoteStatsCommand; import com.vexsoftware.votifier.model.VotifierEvent; +import net.dv8tion.jda.api.EmbedBuilder; + +import java.awt.*; public class VoteManager extends Manager { @@ -28,5 +33,12 @@ public class VoteManager extends Manager { account.sendMessage(Lang.VOTE_VOTED.getAsString() .replace("%votes%", voteProfile.getTotalVotes() + "") ); + + DiscordBot.sendEmbed(DiscordChannel.VOTE_LOGS, new EmbedBuilder() + .setTitle("Player Voted") + .setColor(Color.GREEN) + .addField("Player", account.getPlayer().getName(), true) + .addField("Votes", voteProfile.getTotalVotes() + "", true) + ); } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 4ea86ef..2b77a15 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -5,7 +5,7 @@ influxdb: bucket: "aetheria" discord: - log-webhook: "https://discord.com/api/webhooks/1223162714822807572/jkqXHdcf4ov7MTC4fxx8MI2rSCoQTHONua8VOJa3ie5OtHBthqd5MGM7WGd7oHF52OOm" + token: "MTEyOTEyMDY0NTk3Mjc2Njc3MA.G7VXPL.8iDzdTxScweAKByKnwY6PFcK07AehFfNvf_2Hk" version-warning: min-version: 763 # 1.20