Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/workflows/build-and-test.yaml
Original file line number Diff line number Diff line change
@@ -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
45 changes: 45 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -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 }}
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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/
98 changes: 98 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>dev.oxs.staffchat</groupId>
<artifactId>StaffChat</artifactId>
<version>2.0.1</version>
<description>A simple staff chat plugin for Poseidon Minecraft servers.</description>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<repositories>
<!-- Repository for resolving release dependencies -->
<repository>
<id>johnymuffin-nexus-releases</id>
<url>https://repository.johnymuffin.com/repository/maven-public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<!-- Repository for resolving snapshot dependencies -->
<repository>
<id>johnymuffin-nexus-snapshots</id>
<url>https://repository.johnymuffin.com/repository/maven-snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>dv8tion</id>
<url>https://m2.dv8tion.net/releases</url>
</repository>
</repositories>

<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>


<dependencies>

<dependency>
<groupId>com.legacyminecraft.poseidon</groupId>
<artifactId>poseidon-craftbukkit</artifactId>
<version>1.1.8</version>
</dependency>

<dependency>
<groupId>com.johnymuffin</groupId>
<artifactId>discordcore</artifactId>
<version>1.1.3</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>net.dv8tion</groupId>
<artifactId>JDA</artifactId>
<version>4.4.0_350</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>club.minnced</groupId>
<artifactId>opus-java</artifactId>
</exclusion>
</exclusions>
</dependency>

</dependencies>

</project>
100 changes: 100 additions & 0 deletions src/main/java/dev/oxs/staffchat/DiscordBridge.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
52 changes: 45 additions & 7 deletions src/main/java/dev/oxs/staffchat/StaffChat.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -26,6 +28,9 @@ public class StaffChat extends JavaPlugin implements Listener {

private HashMap<UUID, Boolean> staffChatToggled = new HashMap<>();

private DiscordBridge discordBridge;
//TODO upgrade to Discord Core 5 when support is EOL

@Override
public void onEnable() {
plugin = this;
Expand All @@ -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);
}
}

Expand Down
Loading