Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
5046ddc
Velocity get CorePlayer directly & debug log identity check
EarthCow May 30, 2026
8551395
Bungee get CorePlayer directly & debug log identity check
EarthCow May 30, 2026
790bfd8
Remove unused CorePlugin#getCore
EarthCow May 30, 2026
8ab4be6
Remove unused CorePlugin#getServerType & ServerType enum
EarthCow May 30, 2026
0aa5d3e
Merge pull request #68 from RagingTech/improvement/core-plugin-remove…
EarthCow May 30, 2026
172cde1
Merge pull request #66 from RagingTech/improvement/proxy-disconnect-i…
EarthCow May 30, 2026
52671d8
Add publish to Modrinth workflow
EarthCow May 31, 2026
d95bd50
Disallow persist-credentials on checkout
EarthCow May 31, 2026
27be75d
Use vars instead of secrets for MODRINTH_PROJECT_ID
EarthCow May 31, 2026
5fc126a
Set read only permissions
EarthCow May 31, 2026
84af437
Merge pull request #70 from RagingTech/chore/add-modrinth-publish-wor…
EarthCow May 31, 2026
4af3167
Move connected state into CorePlayer class
EarthCow Jun 13, 2026
970bf26
Merge pull request #71 from RagingTech/improvement/move-connected-sta…
EarthCow Jun 13, 2026
91308ea
Make CorePlayer connecting & disconnecting volatile
EarthCow Jun 13, 2026
9424faf
Make CorePlayer disconnecting atomic
EarthCow Jun 13, 2026
6fcf026
Make CorePlayer connected atomic
EarthCow Jun 13, 2026
3f7e183
Add discord leave message cache
EarthCow Aug 12, 2026
0e12d28
Remove leave message cache debug line
EarthCow Aug 12, 2026
1078c4e
Strip all formatting from discord messages
EarthCow Aug 12, 2026
967090f
Merge pull request #73 from RagingTech/bugfix/discord-leave-message-p…
EarthCow Aug 12, 2026
e59cf8f
v3.6.2
EarthCow Aug 12, 2026
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
40 changes: 40 additions & 0 deletions .github/workflows/publish-modrinth.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Publish to Modrinth

on:
release:
types: [published]

permissions:
contents: read

jobs:
publish:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
with:
persist-credentials: false

- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'

- name: Build with Gradle
run: ./gradlew build

- name: Publish to Modrinth
uses: cloudnode-pro/modrinth-publish@v2
with:
token: ${{ secrets.MODRINTH_TOKEN }}
project: ${{ vars.MODRINTH_PROJECT_ID }}
version: ${{ github.event.release.tag_name }}
changelog: ${{ github.event.release.body }}
loaders: |-
velocity
bungeecord
game-versions: ">=1.8"
files: build/libs/*.jar
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Project metadata
group=xyz.earthcow.networkjoinmessages
version=3.6.1
version=3.6.2
description=A plugin handling join, leave and swap messages for proxy servers.

# Plugin.yml metadata
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public void onEnable() {

getProxy()
.getPluginManager()
.registerListener(this, new PlayerListener(core.getCorePlayerListener()));
.registerListener(this, new PlayerListener(core.getCorePlayerListener(), bungeeLogger));

getProxy()
.getPluginManager()
Expand Down Expand Up @@ -124,16 +124,6 @@ public CoreCommandSender getConsole() {
return console;
}

@Override
public Core getCore() {
return core;
}

@Override
public ServerType getServerType() {
return ServerType.BUNGEE;
}

@Override
public CoreLogger getCoreLogger() {
return bungeeLogger;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import xyz.earthcow.networkjoinmessages.bungee.BungeeMain;
import xyz.earthcow.networkjoinmessages.bungee.abstraction.BungeeLogger;
import xyz.earthcow.networkjoinmessages.bungee.abstraction.BungeePlayer;
import xyz.earthcow.networkjoinmessages.bungee.abstraction.BungeeServer;
import xyz.earthcow.networkjoinmessages.common.abstraction.CorePlayer;
Expand All @@ -16,9 +17,11 @@
public class PlayerListener implements Listener {

private final CorePlayerListener corePlayerListener;
private final BungeeLogger logger;

public PlayerListener(CorePlayerListener corePlayerListener) {
public PlayerListener(CorePlayerListener corePlayerListener, BungeeLogger logger) {
this.corePlayerListener = corePlayerListener;
this.logger = logger;
}

@EventHandler
Expand All @@ -42,8 +45,10 @@ public void onServerConnected(ServerConnectedEvent e) {
@EventHandler
public void onDisconnect(PlayerDisconnectEvent event) {
// Check that the player disconnected is not a duplicate user session (the same account tries to join the server while already joined)
CorePlayer corePlayer = BungeeMain.getInstance().getOrPutPlayer(new BungeePlayer(event.getPlayer()));
if (corePlayer.getConnectionIdentity() != System.identityHashCode(event.getPlayer())) {
CorePlayer corePlayer = BungeeMain.getInstance().getPlayerManager().getPlayer(event.getPlayer().getUniqueId());
if (corePlayer == null || corePlayer.getConnectionIdentity() != System.identityHashCode(event.getPlayer())) {
logger.debug("Ignoring disconnect event for " + (corePlayer == null ? "null" : corePlayer.getName())
+ " (" + event.getPlayer().getName() + ") - null or duplicate player identity");
return;
}

Expand Down
10 changes: 7 additions & 3 deletions src/main/java/xyz/earthcow/networkjoinmessages/common/Core.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,18 @@ public Core(CorePlugin plugin, PremiumVanish premiumVanish) {
ReceiverResolver receiverResolver = new ReceiverResolver(plugin, config, sayanVanishHook != null, premiumVanish != null);
MessageHandler messageHandler = new MessageHandler(plugin, config, stateStore, placeholderResolver, receiverResolver);

// Discord webhook builder, early for leave message cache usage
DiscordWebhookBuilder webhookBuilder = new DiscordWebhookBuilder(plugin, configManager.getDiscordConfig());

// Player event helpers
SilenceChecker silenceChecker = new SilenceChecker(plugin, config, stateStore, sayanVanishHook, premiumVanish);
LeaveMessageCache leaveMessageCache = new LeaveMessageCache(plugin, config, messageFormatter, placeholderResolver);
LeaveMessageCache leaveMessageCache = new LeaveMessageCache(
plugin, config, messageFormatter, placeholderResolver, webhookBuilder, configManager.getDiscordConfig());
LeaveJoinBufferManager leaveJoinBuffer = new LeaveJoinBufferManager(plugin, config);

// Discord integration
DiscordWebhookBuilder webhookBuilder = new DiscordWebhookBuilder(plugin, configManager.getDiscordConfig());
DiscordIntegration discordIntegration = new DiscordIntegration(plugin, placeholderResolver, messageFormatter, webhookBuilder, configManager.getDiscordConfig());
DiscordIntegration discordIntegration = new DiscordIntegration(
plugin, placeholderResolver, messageFormatter, webhookBuilder, configManager.getDiscordConfig());

// Spoof
SpoofManager spoofManager = new SpoofManager(plugin, config, messageHandler, messageFormatter, placeholderResolver);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
package xyz.earthcow.networkjoinmessages.common.abstraction;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import net.kyori.adventure.audience.Audience;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;

@Getter @Setter
public abstract class CorePlayer implements CoreCommandSender {
// Fields
@Getter(AccessLevel.NONE) @Setter(AccessLevel.NONE)
private AtomicBoolean connected = new AtomicBoolean(false);
private CoreBackendServer lastKnownConnectedServer;
private boolean disconnecting = false;
@Getter(AccessLevel.NONE) @Setter(AccessLevel.NONE)
private AtomicBoolean disconnecting = new AtomicBoolean(false);
private String cachedLeaveMessage;
private String cachedDiscordLeavePayload;
private Audience audience;
private boolean premiumVanishHidden = false;
private int premiumVanishUseLevel = 0;
Expand All @@ -24,6 +30,18 @@ public CorePlayer(CoreBackendServer lastKnownConnectedServer, Audience audience)
this.audience = audience;
}

public boolean isConnected() {
return connected.get();
}

public void setConnected(boolean value) {
connected.set(value);
}

public boolean markDisconnecting() {
return disconnecting.compareAndSet(false, true);
}

// Abstract
@NotNull
public abstract UUID getUniqueId();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
package xyz.earthcow.networkjoinmessages.common.abstraction;

import xyz.earthcow.networkjoinmessages.common.Core;
import xyz.earthcow.networkjoinmessages.common.modules.DiscordIntegration;

import java.io.File;
import java.util.List;
import java.util.UUID;

public interface CorePlugin {
Core getCore();

void disable();

ServerType getServerType();
File getDataFolder();
CoreLogger getCoreLogger();

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,11 @@ public void onPreConnect(@NotNull CorePlayer player, @Nullable String previousSe
public void onServerConnected(@NotNull CorePlayer player, @NotNull CoreBackendServer server,
@Nullable CoreBackendServer previousServer) {
plugin.runTaskAsync(() -> {
if (!stateStore.isConnected(player)) {
handleJoin(player, server);
} else {
if (player.isConnected()) {
boolean fromLimbo = plugin.hasLimbo() && previousServer == null;
handleSwap(player, server, fromLimbo);
} else {
handleJoin(player, server);
}
});
}
Expand All @@ -100,11 +100,10 @@ public void onServerConnected(@NotNull CorePlayer player, @NotNull CoreBackendSe
* Called when a player disconnects from the network.
*/
public void onDisconnect(@NotNull CorePlayer player) {
if (player.isDisconnecting()) {
if (!player.markDisconnecting()) {
plugin.getCoreLogger().debug("Duplicate disconnect ignored for " + player.getName());
return;
}
player.setDisconnecting(true);

if (shouldSkipLeave(player)) {
cleanup(player);
Expand All @@ -119,7 +118,7 @@ public void onDisconnect(@NotNull CorePlayer player) {

private void handleJoin(@NotNull CorePlayer player, @NotNull CoreBackendServer server) {
stateStore.loadData(player.getUniqueId(), player.getName());
stateStore.setConnected(player, true);
player.setConnected(true);
player.setLastKnownConnectedServer(server);

PremiumVanish pv = plugin.getVanishAPI();
Expand Down Expand Up @@ -243,7 +242,7 @@ private boolean shouldSkipSwap(CorePlayer player, String from, String to, boolea

private boolean shouldSkipLeave(CorePlayer player) {
if (player.getCurrentServer() == null) return true;
if (!stateStore.isConnected(player)) {
if (!player.isConnected()) {
plugin.getCoreLogger().debug("Skipping leave for " + player.getName() + " — not marked as connected");
return true;
}
Expand Down Expand Up @@ -306,7 +305,6 @@ private void fireLeaveEvent(CorePlayer player, String serverName, String message

private void cleanup(CorePlayer player) {
plugin.getPlayerManager().removePlayer(player.getUniqueId());
stateStore.setConnected(player, false);
leaveMessageCache.stopFor(player);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import xyz.earthcow.networkjoinmessages.common.events.NetworkJoinEvent;
import xyz.earthcow.networkjoinmessages.common.events.NetworkLeaveEvent;
import xyz.earthcow.networkjoinmessages.common.events.SwapServerEvent;
import xyz.earthcow.networkjoinmessages.common.util.Formatter;
import xyz.earthcow.networkjoinmessages.common.util.PlaceholderResolver;

import java.io.FileNotFoundException;
Expand Down Expand Up @@ -71,7 +72,7 @@ public void onSwapServer(SwapServerEvent event) {
DiscordWebhook webhook = webhookBuilder.buildSwapWebhook(webhookUrl);
if (webhook == null) return;

String avatarUrl = resolveAvatarUrl(player);
String avatarUrl = webhookBuilder.resolveAvatarUrl(player);
String preparedJson = messageFormatter.prepareDiscordSwapTemplate(
webhook.getJsonString(), player, event.serverFrom(), event.serverTo(), avatarUrl);
executeWebhook(webhook, preparedJson, player);
Expand All @@ -84,7 +85,7 @@ public void onNetworkJoin(NetworkJoinEvent event) {
DiscordWebhook webhook = webhookBuilder.buildJoinWebhook(webhookUrl, key);
if (webhook == null) return;

String avatarUrl = resolveAvatarUrl(player);
String avatarUrl = webhookBuilder.resolveAvatarUrl(player);
String preparedJson = messageFormatter.prepareDiscordJoinLeaveTemplate(
webhook.getJsonString(), player, false, avatarUrl);
executeWebhook(webhook, preparedJson, player);
Expand All @@ -96,10 +97,13 @@ public void onNetworkLeave(NetworkLeaveEvent event) {
DiscordWebhook webhook = webhookBuilder.buildLeaveWebhook(webhookUrl);
if (webhook == null) return;

String avatarUrl = resolveAvatarUrl(player);
String preparedJson = messageFormatter.prepareDiscordJoinLeaveTemplate(
webhook.getJsonString(), player, true, avatarUrl);
executeWebhook(webhook, preparedJson, player);
String cachedPayload = player.getCachedDiscordLeavePayload();
if (cachedPayload == null) {
plugin.getCoreLogger().warn(
"No cached Discord leave payload for " + player.getName() + ", skipping webhook.");
return;
}
sendWebhook(webhook, cachedPayload);
}

// --- Webhook execution ---
Expand All @@ -109,27 +113,21 @@ public void onNetworkLeave(NetworkLeaveEvent event) {
* string, then executes the webhook asynchronously.
*/
private void executeWebhook(DiscordWebhook webhook, String preparedJson, CorePlayer parseTarget) {
placeholderResolver.resolve(preparedJson, parseTarget, fullyResolved ->
plugin.runTaskAsync(() -> {
try {
webhook.execute(fullyResolved);
} catch (IOException e) {
plugin.getCoreLogger().severe("[DiscordIntegration] " + describeHttpError(e));
plugin.getCoreLogger().debug("Exception: " + e);
plugin.getCoreLogger().debug("Webhook payload: " + preparedJson);
}
})
);
placeholderResolver.resolve(preparedJson, parseTarget, fullyResolved -> sendWebhook(webhook, fullyResolved));
}

/**
* Resolves the avatar URL template from config, substituting {@code %uuid%} and
* {@code %player%} for the given player.
*/
private String resolveAvatarUrl(CorePlayer player) {
return discordConfig.getString("EmbedAvatarUrl")
.replace("%uuid%", player.getUniqueId().toString())
.replace("%player%", player.getName());
/** Sends an already fully-resolved JSON payload to Discord asynchronously. */
private void sendWebhook(DiscordWebhook webhook, String fullyResolved) {
plugin.runTaskAsync(() -> {
String cleanFullyResolved = Formatter.sanitize(fullyResolved);
try {
webhook.execute(cleanFullyResolved);
} catch (IOException e) {
plugin.getCoreLogger().severe("[DiscordIntegration] " + describeHttpError(e));
plugin.getCoreLogger().debug("Exception: " + e);
plugin.getCoreLogger().debug("Webhook payload: " + cleanFullyResolved);
}
});
}

/** Produces a human-readable error description for a failed webhook HTTP request. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import dev.dejvokep.boostedyaml.YamlDocument;
import org.jetbrains.annotations.Nullable;
import xyz.earthcow.discordwebhook.DiscordWebhook;
import xyz.earthcow.networkjoinmessages.common.abstraction.CorePlayer;
import xyz.earthcow.networkjoinmessages.common.abstraction.CorePlugin;

import java.awt.*;
Expand Down Expand Up @@ -54,6 +55,16 @@ public DiscordWebhook buildLeaveWebhook(String webhookUrl) {
return buildWebhook(webhookUrl, "Messages.LeaveNetwork");
}

/**
* Resolves the avatar URL template from config, substituting {@code %uuid%} and
* {@code %player%} for the given player.
*/
public String resolveAvatarUrl(CorePlayer player) {
return discordConfig.getString("EmbedAvatarUrl")
.replace("%uuid%", player.getUniqueId().toString())
.replace("%player%", player.getName());
}

// --- Internal builder ---

/**
Expand Down
Loading
Loading