diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml new file mode 100644 index 0000000..d60b654 --- /dev/null +++ b/.github/workflows/build-and-test.yaml @@ -0,0 +1,41 @@ +name: build-and-test +on: + pull_request: + types: + - opened + - synchronize + - reopened + push: + branches: + - master + - main + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up JDK 1.8 + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: 8 + + - name: Set up Maven + uses: stCarolas/setup-maven@v4.5 + with: + maven-version: 3.9.1 + + - name: build application + shell: bash + run: | + mvn clean install + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ github.event.repository.name }}-artifact + path: target/*.jar \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..cbdb0d7 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,45 @@ +name: Release Workflow + +on: + push: + branches: + - main + - master + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up JDK 1.8 + uses: actions/setup-java@v2 + with: + distribution: 'temurin' + java-version: 8 + + - name: Get the version from pom.xml + id: get_version + run: echo "PROJECT_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV + + - name: Fail if snapshot version + run: | + if [[ $PROJECT_VERSION == *"-SNAPSHOT"* ]]; then + echo "Snapshot versions are not releasable" + exit 0 + fi + + - name: Build with Maven + run: mvn clean package + + - name: Create GitHub Release + if: ${{ !endsWith(env.PROJECT_VERSION, '-SNAPSHOT') }} + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ env.PROJECT_VERSION }} + files: | + target/*.jar + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 524f096..20eae6f 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,7 @@ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* replay_pid* + +*.iml + +.idea/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..7853bcf --- /dev/null +++ b/pom.xml @@ -0,0 +1,98 @@ + + + 4.0.0 + + dev.oxs.staffchat + StaffChat + 2.0.1 + A simple staff chat plugin for Poseidon Minecraft servers. + + + 8 + 8 + UTF-8 + + + + + + johnymuffin-nexus-releases + https://repository.johnymuffin.com/repository/maven-public/ + + true + + + false + + + + + johnymuffin-nexus-snapshots + https://repository.johnymuffin.com/repository/maven-snapshots/ + + false + + + true + + + + dv8tion + https://m2.dv8tion.net/releases + + + + + + + src/main/resources + true + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + + + + + + + + + + com.legacyminecraft.poseidon + poseidon-craftbukkit + 1.1.8 + + + + com.johnymuffin + discordcore + 1.1.3 + provided + + + + net.dv8tion + JDA + 4.4.0_350 + provided + + + club.minnced + opus-java + + + + + + + \ No newline at end of file diff --git a/src/main/java/dev/oxs/staffchat/DiscordBridge.java b/src/main/java/dev/oxs/staffchat/DiscordBridge.java new file mode 100644 index 0000000..260458b --- /dev/null +++ b/src/main/java/dev/oxs/staffchat/DiscordBridge.java @@ -0,0 +1,100 @@ +package dev.oxs.staffchat; + +import com.johnymuffin.discordcore.DiscordCore; +import com.johnymuffin.discordcore.DiscordBot; +import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import org.bukkit.Bukkit; + +import java.util.logging.Logger; + +/** + * Bridges StaffChat ↔ Discord via DiscordCore-4. + * + * The JDA listener anonymous class (DiscordBridge$1) is a separate .class file + * compiled by javac. It is only loaded by the JVM when start() executes the + * "new ListenerAdapter(){}" expression — by which point DiscordCore (and its + * shaded JDA) are guaranteed to be in the plugin classloader graph. + * + * DiscordBridge itself has NO JDA references in its own class definition, so + * it loads cleanly even if DiscordCore is absent. + */ +public class DiscordBridge { + + private final StaffChat plugin; + private final Logger log; + private DiscordBot discordBot; + private String channelId; + private Object jdaListener; // typed as Object so this class has no JDA dependency + + public DiscordBridge(StaffChat plugin) { + this.plugin = plugin; + this.log = plugin.getServer().getLogger(); + } + + public void start() { + if (!plugin.getConfig().getConfigBoolean("discord.enabled")) return; + + channelId = plugin.getConfig().getConfigString("discord.channel-id"); + if (channelId == null || channelId.isEmpty() || channelId.equals("none")) { + log.warning("[StaffChat] discord.enabled is true but discord.channel-id is not set."); + return; + } + + org.bukkit.plugin.Plugin dc = Bukkit.getPluginManager().getPlugin("DiscordCore"); + if (dc == null || !dc.isEnabled()) { + log.warning("[StaffChat] DiscordCore not found — Discord bridge disabled."); + return; + } + + try { + discordBot = ((DiscordCore) dc).getDiscordBot(); + registerJdaListener(); + log.info("[StaffChat] Discord bridge active on channel " + channelId); + } catch (Exception | NoClassDefFoundError e) { + log.severe("[StaffChat] Failed to attach JDA listener: " + e.getMessage()); + } + } + + /** + * Separated into its own method so the anonymous ListenerAdapter subclass + * (which lives in DiscordBridge$1.class) is only resolved when this method + * is called, not when DiscordBridge.class is first loaded. + */ + private void registerJdaListener() { + final String targetChannel = this.channelId; + ListenerAdapter listener = new ListenerAdapter() { + @Override + public void onMessageReceived(MessageReceivedEvent event) { + if (!event.getChannel().getId().equals(targetChannel)) return; + if (event.getAuthor().isBot() || event.isWebhookMessage()) return; + + final String username = event.getAuthor().getName(); + final String message = event.getMessage().getContentRaw(); + + Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() { + @Override + public void run() { + plugin.staffChatMessageFromDiscord(username, message); + } + }); + } + }; + discordBot.getJda().addEventListener(listener); + jdaListener = listener; + } + + public void stop() { + if (discordBot != null && jdaListener != null) { + try { + discordBot.getJda().removeEventListener(jdaListener); + } catch (Exception | NoClassDefFoundError ignored) {} + } + } + + /** Forward an in-game staff chat message to the Discord channel. */ + public void sendToDiscord(String playerName, String message) { + if (discordBot == null) return; + discordBot.discordSendToChannel(channelId, "**[StaffChat]** " + playerName + ": " + message); + } +} diff --git a/src/main/java/dev/oxs/staffchat/StaffChat.java b/src/main/java/dev/oxs/staffchat/StaffChat.java index 52aaeb1..7428883 100755 --- a/src/main/java/dev/oxs/staffchat/StaffChat.java +++ b/src/main/java/dev/oxs/staffchat/StaffChat.java @@ -3,8 +3,10 @@ import dev.oxs.staffchat.commands.*; import org.bukkit.Bukkit; import org.bukkit.ChatColor; +import org.bukkit.craftbukkit.CraftServer; import org.bukkit.entity.Player; import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerChatEvent; import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.java.JavaPlugin; @@ -26,6 +28,9 @@ public class StaffChat extends JavaPlugin implements Listener { private HashMap staffChatToggled = new HashMap<>(); + private DiscordBridge discordBridge; + //TODO upgrade to Discord Core 5 when support is EOL + @Override public void onEnable() { plugin = this; @@ -49,32 +54,65 @@ public void onEnable() { pluginName = pdf.getName(); + discordBridge = new DiscordBridge(plugin); + discordBridge.start(); + log.info("[" + pluginName + "] Is Loading, Version: " + pdf.getVersion()); } @Override public void onDisable() { + if (discordBridge != null) discordBridge.stop(); log.info(pluginName + " has been disabled."); } public void StaffChatMessage(Player sender, String message) { + String name = StaffChatSettings.getInstance(plugin).getConfigBoolean("settings.staffchat-use-displayNamesStaffChat") + ? sender.getDisplayName() : sender.getName(); + String formatted = plugin.getPluginPrefix() + " " + ChatColor.WHITE + name + ": " + ChatColor.WHITE + printColours(message); for (Player onlinePlayer : getServer().getOnlinePlayers()) { if (onlinePlayer.hasPermission("staffchat.see") || onlinePlayer.isOp()) { - Boolean useDisplayName = StaffChatSettings.getInstance(plugin).getConfigBoolean("settings.staffchat-use-displayNamesStaffChat"); - onlinePlayer.sendMessage(plugin.getPluginPrefix() + " " + ChatColor.WHITE + (useDisplayName ? sender.getDisplayName() : sender.getName()) + ": " + ChatColor.WHITE + printColours(message)); + onlinePlayer.sendMessage(formatted); } } + if (discordBridge != null) discordBridge.sendToDiscord(name, message); } - public void PublicChatMessage(Player sender, String message) { + public void staffChatMessageFromDiscord(String discordUsername, String message) { + String discordPrefix = printColours(StaffChatSettings.getInstance(plugin).getConfigString("discord.discord-prefix")); + String formatted = discordPrefix + " " + ChatColor.WHITE + discordUsername + ": " + ChatColor.WHITE + printColours(message); for (Player onlinePlayer : getServer().getOnlinePlayers()) { + if (onlinePlayer.hasPermission("staffchat.see") || onlinePlayer.isOp()) { + onlinePlayer.sendMessage(formatted); + } + } + log.info("[StaffChat][Discord] " + discordUsername + ": " + message); + } - String playerPrefix = StaffChatSettings.getInstance(plugin).getConfigString("settings.staffchat-publicChatPrefix"); - Boolean useDisplay = StaffChatSettings.getInstance(plugin).getConfigBoolean("settings.staffchat-use-displayNamesPublicChat"); + public void PublicChatMessage(Player sender, String message) { +// for (Player onlinePlayer : getServer().getOnlinePlayers()) { +// +// String playerPrefix = StaffChatSettings.getInstance(plugin).getConfigString("settings.staffchat-publicChatPrefix"); +// Boolean useDisplay = StaffChatSettings.getInstance(plugin).getConfigBoolean("settings.staffchat-use-displayNamesPublicChat"); +// +// String replacedString = playerPrefix.replace("%player%", (useDisplay ? sender.getDisplayName() : sender.getName())); +// onlinePlayer.sendMessage(plugin.printColours(replacedString) + ChatColor.WHITE + plugin.printColours(message)); +// } + + // Send public chat message the same way as the server does + + PlayerChatEvent event = new PlayerChatEvent(sender, message); + Bukkit.getPluginManager().callEvent(event); + + if(event.isCancelled()) { + return; + } - String replacedString = playerPrefix.replace("%player%", (useDisplay ? sender.getDisplayName() : sender.getName())); - onlinePlayer.sendMessage(plugin.printColours(replacedString) + ChatColor.WHITE + plugin.printColours(message)); + String formattedMessage = String.format(event.getFormat(), event.getPlayer().getDisplayName(), event.getMessage()); + ((CraftServer) Bukkit.getServer()).getServer().console.sendMessage(formattedMessage); // Log to console + for (Player recipient : event.getRecipients()) { + recipient.sendMessage(formattedMessage); } } diff --git a/src/main/java/dev/oxs/staffchat/StaffChatSettings.java b/src/main/java/dev/oxs/staffchat/StaffChatSettings.java index 5e13b3c..02b25fc 100644 --- a/src/main/java/dev/oxs/staffchat/StaffChatSettings.java +++ b/src/main/java/dev/oxs/staffchat/StaffChatSettings.java @@ -23,6 +23,10 @@ private void write() { generateConfigOption("settings.staffchat-use-displayNamesStaffChat", false); generateConfigOption("settings.staffchat-use-displayNamesPublicChat", true); generateConfigOption("settings.staffchat-publicChatPrefix", "&f<%player%&f> "); + //Discord Bridge (requires DiscordCore-4) + generateConfigOption("discord.enabled", false); + generateConfigOption("discord.channel-id", "none"); + generateConfigOption("discord.discord-prefix", "&d[&9D&dStaffChat]&f"); } public void generateConfigOption(String key, Object defaultValue) { diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 5122f7e..ff99655 100755 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,10 +1,11 @@ -name: StaffChat -description: Staff Chat beta plugin +name: ${project.name} +description: ${project.description} main: dev.oxs.staffchat.StaffChat -version: 2.0.0 +version: ${project.version} authors: - oxs depend: [ ] +softdepend: [ DiscordCore ] commands: StaffChatCommand: