diff --git a/pom.xml b/pom.xml
index e38fc71..ccd2e20 100644
--- a/pom.xml
+++ b/pom.xml
@@ -55,22 +55,6 @@
1.20.5-R0.1-SNAPSHOT
provided
-
-
- org.spigotmc
- spigot
- 1.20.5-R0.1-SNAPSHOT
- true
- provided
-
org.bstats
@@ -194,6 +178,7 @@
org.apache.maven.plugins
maven-jar-plugin
+ 3.4.1
diff --git a/src/main/java/me/danjono/inventoryrollback/config/ConfigData.java b/src/main/java/me/danjono/inventoryrollback/config/ConfigData.java
index d8ac15c..fd0442d 100644
--- a/src/main/java/me/danjono/inventoryrollback/config/ConfigData.java
+++ b/src/main/java/me/danjono/inventoryrollback/config/ConfigData.java
@@ -101,7 +101,26 @@ public String getName() {
private static boolean bStatsEnabled;
private static boolean debugEnabled;
- public void setVariables() {
+ // Discord webhook configuration
+ private static boolean discordEnabled;
+ private static String discordWebhookUrl;
+ private static boolean discordBackupCreated;
+ private static boolean discordInventoryRestored;
+ private static boolean discordEnderChestRestored;
+ private static boolean discordHealthRestored;
+ private static boolean discordHungerRestored;
+ private static boolean discordExperienceRestored;
+ private static boolean discordPlayerDeath;
+ private static boolean discordForceBackup;
+ private static boolean discordIncludeServerName;
+ private static String discordServerName;
+ private static boolean discordUseEmbeds;
+ private static String discordColorBackup;
+ private static String discordColorRestore;
+ private static String discordColorDeath;
+ private static String discordColorWarning;
+
+ public void setVariables() {
setEnabled((boolean) getDefaultValue("enabled", true));
String folder = (String) getDefaultValue("folder-location", "DEFAULT");
@@ -149,6 +168,25 @@ public void setVariables() {
setbStatsEnabled((boolean) getDefaultValue("bStats", true));
setDebugEnabled((boolean) getDefaultValue("debug", false));
+ // Discord webhook settings
+ setDiscordEnabled((boolean) getDefaultValue("discord.enabled", false));
+ setDiscordWebhookUrl((String) getDefaultValue("discord.webhook-url", ""));
+ setDiscordBackupCreated((boolean) getDefaultValue("discord.events.backup-created", true));
+ setDiscordInventoryRestored((boolean) getDefaultValue("discord.events.inventory-restored", true));
+ setDiscordEnderChestRestored((boolean) getDefaultValue("discord.events.ender-chest-restored", true));
+ setDiscordHealthRestored((boolean) getDefaultValue("discord.events.health-restored", true));
+ setDiscordHungerRestored((boolean) getDefaultValue("discord.events.hunger-restored", true));
+ setDiscordExperienceRestored((boolean) getDefaultValue("discord.events.experience-restored", true));
+ setDiscordPlayerDeath((boolean) getDefaultValue("discord.events.player-death", true));
+ setDiscordForceBackup((boolean) getDefaultValue("discord.events.force-backup", true));
+ setDiscordIncludeServerName((boolean) getDefaultValue("discord.settings.include-server-name", true));
+ setDiscordServerName((String) getDefaultValue("discord.settings.server-name", "My Server"));
+ setDiscordUseEmbeds((boolean) getDefaultValue("discord.settings.use-embeds", true));
+ setDiscordColorBackup((String) getDefaultValue("discord.settings.colors.backup", "#00ff00"));
+ setDiscordColorRestore((String) getDefaultValue("discord.settings.colors.restore", "#0099ff"));
+ setDiscordColorDeath((String) getDefaultValue("discord.settings.colors.death", "#ff3300"));
+ setDiscordColorWarning((String) getDefaultValue("discord.settings.colors.warning", "#ffcc00"));
+
if (saveChanges())
saveConfig();
}
@@ -281,6 +319,74 @@ public static void setDebugEnabled(boolean enabled) {
debugEnabled = enabled;
}
+ public static void setDiscordEnabled(boolean enabled) {
+ discordEnabled = enabled;
+ }
+
+ public static void setDiscordWebhookUrl(String url) {
+ discordWebhookUrl = url;
+ }
+
+ public static void setDiscordBackupCreated(boolean enabled) {
+ discordBackupCreated = enabled;
+ }
+
+ public static void setDiscordInventoryRestored(boolean enabled) {
+ discordInventoryRestored = enabled;
+ }
+
+ public static void setDiscordEnderChestRestored(boolean enabled) {
+ discordEnderChestRestored = enabled;
+ }
+
+ public static void setDiscordHealthRestored(boolean enabled) {
+ discordHealthRestored = enabled;
+ }
+
+ public static void setDiscordHungerRestored(boolean enabled) {
+ discordHungerRestored = enabled;
+ }
+
+ public static void setDiscordExperienceRestored(boolean enabled) {
+ discordExperienceRestored = enabled;
+ }
+
+ public static void setDiscordPlayerDeath(boolean enabled) {
+ discordPlayerDeath = enabled;
+ }
+
+ public static void setDiscordForceBackup(boolean enabled) {
+ discordForceBackup = enabled;
+ }
+
+ public static void setDiscordIncludeServerName(boolean enabled) {
+ discordIncludeServerName = enabled;
+ }
+
+ public static void setDiscordServerName(String name) {
+ discordServerName = name;
+ }
+
+ public static void setDiscordUseEmbeds(boolean enabled) {
+ discordUseEmbeds = enabled;
+ }
+
+ public static void setDiscordColorBackup(String color) {
+ discordColorBackup = color;
+ }
+
+ public static void setDiscordColorRestore(String color) {
+ discordColorRestore = color;
+ }
+
+ public static void setDiscordColorDeath(String color) {
+ discordColorDeath = color;
+ }
+
+ public static void setDiscordColorWarning(String color) {
+ discordColorWarning = color;
+ }
+
public static boolean isEnabled() {
return pluginEnabled;
}
@@ -389,6 +495,74 @@ public static boolean isDebugEnabled() {
return debugEnabled;
}
+ public static boolean isDiscordEnabled() {
+ return discordEnabled;
+ }
+
+ public static String getDiscordWebhookUrl() {
+ return discordWebhookUrl;
+ }
+
+ public static boolean isDiscordBackupCreated() {
+ return discordBackupCreated;
+ }
+
+ public static boolean isDiscordInventoryRestored() {
+ return discordInventoryRestored;
+ }
+
+ public static boolean isDiscordEnderChestRestored() {
+ return discordEnderChestRestored;
+ }
+
+ public static boolean isDiscordHealthRestored() {
+ return discordHealthRestored;
+ }
+
+ public static boolean isDiscordHungerRestored() {
+ return discordHungerRestored;
+ }
+
+ public static boolean isDiscordExperienceRestored() {
+ return discordExperienceRestored;
+ }
+
+ public static boolean isDiscordPlayerDeath() {
+ return discordPlayerDeath;
+ }
+
+ public static boolean isDiscordForceBackup() {
+ return discordForceBackup;
+ }
+
+ public static boolean isDiscordIncludeServerName() {
+ return discordIncludeServerName;
+ }
+
+ public static String getDiscordServerName() {
+ return discordServerName;
+ }
+
+ public static boolean isDiscordUseEmbeds() {
+ return discordUseEmbeds;
+ }
+
+ public static String getDiscordColorBackup() {
+ return discordColorBackup;
+ }
+
+ public static String getDiscordColorRestore() {
+ return discordColorRestore;
+ }
+
+ public static String getDiscordColorDeath() {
+ return discordColorDeath;
+ }
+
+ public static String getDiscordColorWarning() {
+ return discordColorWarning;
+ }
+
private boolean saveChanges = false;
public Object getDefaultValue(String path, Object defaultValue) {
Object obj = configuration.get(path);
diff --git a/src/main/java/me/danjono/inventoryrollback/config/MessageData.java b/src/main/java/me/danjono/inventoryrollback/config/MessageData.java
index 07c44d2..47ab983 100644
--- a/src/main/java/me/danjono/inventoryrollback/config/MessageData.java
+++ b/src/main/java/me/danjono/inventoryrollback/config/MessageData.java
@@ -124,6 +124,38 @@ public boolean saveConfig() {
private static String previousPageButton;
private static String backButton;
+ // Discord webhook messages
+ private static String discordTitleBackupCreated;
+ private static String discordTitleInventoryRestored;
+ private static String discordTitleEnderChestRestored;
+ private static String discordTitleHealthRestored;
+ private static String discordTitleHungerRestored;
+ private static String discordTitleExperienceRestored;
+ private static String discordTitlePlayerDeath;
+ private static String discordTitleForceBackup;
+
+ private static String discordDescBackupCreated;
+ private static String discordDescInventoryRestored;
+ private static String discordDescEnderChestRestored;
+ private static String discordDescHealthRestored;
+ private static String discordDescHungerRestored;
+ private static String discordDescExperienceRestored;
+ private static String discordDescPlayerDeath;
+ private static String discordDescForceBackup;
+
+ private static String discordMsgBackupCreated;
+ private static String discordMsgInventoryRestored;
+ private static String discordMsgEnderChestRestored;
+ private static String discordMsgHealthRestored;
+ private static String discordMsgHungerRestored;
+ private static String discordMsgExperienceRestored;
+ private static String discordMsgPlayerDeath;
+ private static String discordMsgForceBackup;
+
+ private static String discordErrorWebhookFailed;
+ private static String discordErrorInvalidWebhook;
+ private static String discordErrorConnectionFailed;
+
public void setMessages() {
setPluginPrefix(convertColorCodes((String) getDefaultValue("general.prefix", "&f[&bInventoryRollbackPlus&f]&r ")));
@@ -194,6 +226,38 @@ public void setMessages() {
setPreviousPageButton(convertColorCodes((String) getDefaultValue("menu-buttons.previous-page", "&fPrevious Page")));
setBackButton(convertColorCodes((String) getDefaultValue("menu-buttons.back-page", "&fBack")));
+ // Discord webhook messages
+ setDiscordTitleBackupCreated((String) getDefaultValue("discord.titles.backup-created", "📦 Backup Created"));
+ setDiscordTitleInventoryRestored((String) getDefaultValue("discord.titles.inventory-restored", "🎒 Inventory Restored"));
+ setDiscordTitleEnderChestRestored((String) getDefaultValue("discord.titles.ender-chest-restored", "📦 Ender Chest Restored"));
+ setDiscordTitleHealthRestored((String) getDefaultValue("discord.titles.health-restored", "❤️ Health Restored"));
+ setDiscordTitleHungerRestored((String) getDefaultValue("discord.titles.hunger-restored", "🍖 Hunger Restored"));
+ setDiscordTitleExperienceRestored((String) getDefaultValue("discord.titles.experience-restored", "✨ Experience Restored"));
+ setDiscordTitlePlayerDeath((String) getDefaultValue("discord.titles.player-death", "💀 Player Death"));
+ setDiscordTitleForceBackup((String) getDefaultValue("discord.titles.force-backup", "🔧 Force Backup"));
+
+ setDiscordDescBackupCreated((String) getDefaultValue("discord.descriptions.backup-created", "Player **%PLAYER%** backup created\n**Type:** %TYPE%\n**Time:** %TIME%"));
+ setDiscordDescInventoryRestored((String) getDefaultValue("discord.descriptions.inventory-restored", "Player **%PLAYER%** inventory restored by **%ADMIN%**\n**From backup:** %TIME%"));
+ setDiscordDescEnderChestRestored((String) getDefaultValue("discord.descriptions.ender-chest-restored", "Player **%PLAYER%** ender chest restored by **%ADMIN%**\n**From backup:** %TIME%"));
+ setDiscordDescHealthRestored((String) getDefaultValue("discord.descriptions.health-restored", "Player **%PLAYER%** health restored by **%ADMIN%**\n**Health:** %HEALTH%\n**From backup:** %TIME%"));
+ setDiscordDescHungerRestored((String) getDefaultValue("discord.descriptions.hunger-restored", "Player **%PLAYER%** hunger restored by **%ADMIN%**\n**Hunger:** %HUNGER%\n**From backup:** %TIME%"));
+ setDiscordDescExperienceRestored((String) getDefaultValue("discord.descriptions.experience-restored", "Player **%PLAYER%** experience restored by **%ADMIN%**\n**Level:** %LEVEL%\n**From backup:** %TIME%"));
+ setDiscordDescPlayerDeath((String) getDefaultValue("discord.descriptions.player-death", "Player **%PLAYER%** died\n**Location:** %WORLD% (%X%, %Y%, %Z%)\n**Cause:** %CAUSE%\n**Time:** %TIME%"));
+ setDiscordDescForceBackup((String) getDefaultValue("discord.descriptions.force-backup", "Force backup created for **%PLAYER%** by **%ADMIN%**\n**Time:** %TIME%"));
+
+ setDiscordMsgBackupCreated((String) getDefaultValue("discord.messages.backup-created", "📦 Backup created for %PLAYER% (%TYPE%) at %TIME%"));
+ setDiscordMsgInventoryRestored((String) getDefaultValue("discord.messages.inventory-restored", "🎒 %PLAYER% inventory restored by %ADMIN% from backup %TIME%"));
+ setDiscordMsgEnderChestRestored((String) getDefaultValue("discord.messages.ender-chest-restored", "📦 %PLAYER% ender chest restored by %ADMIN% from backup %TIME%"));
+ setDiscordMsgHealthRestored((String) getDefaultValue("discord.messages.health-restored", "❤️ %PLAYER% health restored by %ADMIN% (Health: %HEALTH%) from backup %TIME%"));
+ setDiscordMsgHungerRestored((String) getDefaultValue("discord.messages.hunger-restored", "🍖 %PLAYER% hunger restored by %ADMIN% (Hunger: %HUNGER%) from backup %TIME%"));
+ setDiscordMsgExperienceRestored((String) getDefaultValue("discord.messages.experience-restored", "✨ %PLAYER% experience restored by %ADMIN% (Level: %LEVEL%) from backup %TIME%"));
+ setDiscordMsgPlayerDeath((String) getDefaultValue("discord.messages.player-death", "💀 %PLAYER% died at %WORLD% (%X%, %Y%, %Z%) - %CAUSE% at %TIME%"));
+ setDiscordMsgForceBackup((String) getDefaultValue("discord.messages.force-backup", "🔧 Force backup created for %PLAYER% by %ADMIN% at %TIME%"));
+
+ setDiscordErrorWebhookFailed(convertColorCodes((String) getDefaultValue("discord.errors.webhook-failed", "&cFailed to send Discord webhook message")));
+ setDiscordErrorInvalidWebhook(convertColorCodes((String) getDefaultValue("discord.errors.invalid-webhook", "&cInvalid Discord webhook URL configured")));
+ setDiscordErrorConnectionFailed(convertColorCodes((String) getDefaultValue("discord.errors.connection-failed", "&cFailed to connect to Discord webhook")));
+
if (saveChanges())
saveConfig();
}
@@ -398,6 +462,113 @@ public static void setBackButton(String message) {
backButton = message;
}
+ public static void setDiscordTitleBackupCreated(String message) {
+ discordTitleBackupCreated = message;
+ }
+
+ public static void setDiscordTitleInventoryRestored(String message) {
+ discordTitleInventoryRestored = message;
+ }
+
+ public static void setDiscordTitleEnderChestRestored(String message) {
+ discordTitleEnderChestRestored = message;
+ }
+
+ public static void setDiscordTitleHealthRestored(String message) {
+ discordTitleHealthRestored = message;
+ }
+
+ public static void setDiscordTitleHungerRestored(String message) {
+ discordTitleHungerRestored = message;
+ }
+
+ public static void setDiscordTitleExperienceRestored(String message) {
+ discordTitleExperienceRestored = message;
+ }
+
+ public static void setDiscordTitlePlayerDeath(String message) {
+ discordTitlePlayerDeath = message;
+ }
+
+ public static void setDiscordTitleForceBackup(String message) {
+ discordTitleForceBackup = message;
+ }
+
+ public static void setDiscordDescBackupCreated(String message) {
+ discordDescBackupCreated = message;
+ }
+
+ public static void setDiscordDescInventoryRestored(String message) {
+ discordDescInventoryRestored = message;
+ }
+
+ public static void setDiscordDescEnderChestRestored(String message) {
+ discordDescEnderChestRestored = message;
+ }
+
+ public static void setDiscordDescHealthRestored(String message) {
+ discordDescHealthRestored = message;
+ }
+
+ public static void setDiscordDescHungerRestored(String message) {
+ discordDescHungerRestored = message;
+ }
+
+ public static void setDiscordDescExperienceRestored(String message) {
+ discordDescExperienceRestored = message;
+ }
+
+ public static void setDiscordDescPlayerDeath(String message) {
+ discordDescPlayerDeath = message;
+ }
+
+ public static void setDiscordDescForceBackup(String message) {
+ discordDescForceBackup = message;
+ }
+
+ public static void setDiscordMsgBackupCreated(String message) {
+ discordMsgBackupCreated = message;
+ }
+
+ public static void setDiscordMsgInventoryRestored(String message) {
+ discordMsgInventoryRestored = message;
+ }
+
+ public static void setDiscordMsgEnderChestRestored(String message) {
+ discordMsgEnderChestRestored = message;
+ }
+
+ public static void setDiscordMsgHealthRestored(String message) {
+ discordMsgHealthRestored = message;
+ }
+
+ public static void setDiscordMsgHungerRestored(String message) {
+ discordMsgHungerRestored = message;
+ }
+
+ public static void setDiscordMsgExperienceRestored(String message) {
+ discordMsgExperienceRestored = message;
+ }
+
+ public static void setDiscordMsgPlayerDeath(String message) {
+ discordMsgPlayerDeath = message;
+ }
+
+ public static void setDiscordMsgForceBackup(String message) {
+ discordMsgForceBackup = message;
+ }
+
+ public static void setDiscordErrorWebhookFailed(String message) {
+ discordErrorWebhookFailed = message;
+ }
+
+ public static void setDiscordErrorInvalidWebhook(String message) {
+ discordErrorInvalidWebhook = message;
+ }
+
+ public static void setDiscordErrorConnectionFailed(String message) {
+ discordErrorConnectionFailed = message;
+ }
// GETTERS
@@ -452,7 +623,7 @@ public static String getNotOnlineError(String name) {
public static String getForceBackupPlayer(String name) {
return forceSavedPlayer.replaceAll(nameVariable, name);
}
-
+
public static String getForceBackupAll() {
return forceSavedAll;
}
@@ -460,7 +631,7 @@ public static String getForceBackupAll() {
public static String getForceBackupError(String name) {
return notForcedSaved.replaceAll(nameVariable, name);
}
-
+
public static String getMainInventoryRestored(String name) {
return mainInventoryRestored.replaceAll(nameVariable, name);
}
@@ -472,7 +643,7 @@ public static String getMainInventoryRestoredPlayer(String name) {
public static String getMainInventoryNotOnline(String name) {
return mainInventoryNotOnline.replaceAll(nameVariable, name);
}
-
+
public static String getMainInventoryRestoreButton() {
return mainInventoryButton;
}
@@ -492,7 +663,7 @@ public static String getEnderChestRestoredPlayer(String name) {
public static String getEnderChestNotOnline(String name) {
return enderChestNotOnline.replaceAll(nameVariable, name);
}
-
+
public static String getEnderChestRestoreButton() {
return enderChestButton;
}
@@ -508,7 +679,7 @@ public static String getHealthRestoredPlayer(String name) {
public static String getHealthNotOnline(String name) {
return healthNotOnline.replaceAll(nameVariable, name);
}
-
+
public static String getHealthRestoreButton() {
return healthButton;
}
@@ -524,7 +695,7 @@ public static String getHungerRestoredPlayer(String name) {
public static String getHungerNotOnline(String name) {
return hungerNotOnline.replaceAll(nameVariable, name);
}
-
+
public static String getHungerRestoreButton() {
return hungerButton;
}
@@ -540,11 +711,11 @@ public static String getExperienceRestoredPlayer(String name, int xp) {
public static String getExperienceNotOnlinePlayer(String name) {
return experienceNotOnline.replaceAll(nameVariable, name);
}
-
+
public static String getExperienceRestoreButton() {
return experienceButton;
}
-
+
public static String getExperienceRestoreLevel(int xp) {
return experienceButtonLore.replaceAll(xpVariable, xp + "");
}
@@ -572,11 +743,11 @@ public static String getDeathReason(String reason) {
public static String getDeathTime(String time) {
return deathTime.replace("%TIME%", time);
}
-
+
public static String getDeathLocation() {
return deathLocationTeleportTo;
}
-
+
public static String getDeathLocationTeleport(Location location) {
return deathLocationTeleport.replace("%LOCATION%", "X:" + (int) location.getX() + " Y:" + (int) location.getY() + " Z:" + (int) location.getZ());
}
@@ -600,7 +771,116 @@ public static String getPreviousPageButton() {
public static String getBackButton() {
return backButton;
}
-
+
+ // Discord webhook message getters
+ public static String getDiscordTitleBackupCreated() {
+ return discordTitleBackupCreated;
+ }
+
+ public static String getDiscordTitleInventoryRestored() {
+ return discordTitleInventoryRestored;
+ }
+
+ public static String getDiscordTitleEnderChestRestored() {
+ return discordTitleEnderChestRestored;
+ }
+
+ public static String getDiscordTitleHealthRestored() {
+ return discordTitleHealthRestored;
+ }
+
+ public static String getDiscordTitleHungerRestored() {
+ return discordTitleHungerRestored;
+ }
+
+ public static String getDiscordTitleExperienceRestored() {
+ return discordTitleExperienceRestored;
+ }
+
+ public static String getDiscordTitlePlayerDeath() {
+ return discordTitlePlayerDeath;
+ }
+
+ public static String getDiscordTitleForceBackup() {
+ return discordTitleForceBackup;
+ }
+
+ public static String getDiscordDescBackupCreated() {
+ return discordDescBackupCreated;
+ }
+
+ public static String getDiscordDescInventoryRestored() {
+ return discordDescInventoryRestored;
+ }
+
+ public static String getDiscordDescEnderChestRestored() {
+ return discordDescEnderChestRestored;
+ }
+
+ public static String getDiscordDescHealthRestored() {
+ return discordDescHealthRestored;
+ }
+
+ public static String getDiscordDescHungerRestored() {
+ return discordDescHungerRestored;
+ }
+
+ public static String getDiscordDescExperienceRestored() {
+ return discordDescExperienceRestored;
+ }
+
+ public static String getDiscordDescPlayerDeath() {
+ return discordDescPlayerDeath;
+ }
+
+ public static String getDiscordDescForceBackup() {
+ return discordDescForceBackup;
+ }
+
+ public static String getDiscordMsgBackupCreated() {
+ return discordMsgBackupCreated;
+ }
+
+ public static String getDiscordMsgInventoryRestored() {
+ return discordMsgInventoryRestored;
+ }
+
+ public static String getDiscordMsgEnderChestRestored() {
+ return discordMsgEnderChestRestored;
+ }
+
+ public static String getDiscordMsgHealthRestored() {
+ return discordMsgHealthRestored;
+ }
+
+ public static String getDiscordMsgHungerRestored() {
+ return discordMsgHungerRestored;
+ }
+
+ public static String getDiscordMsgExperienceRestored() {
+ return discordMsgExperienceRestored;
+ }
+
+ public static String getDiscordMsgPlayerDeath() {
+ return discordMsgPlayerDeath;
+ }
+
+ public static String getDiscordMsgForceBackup() {
+ return discordMsgForceBackup;
+ }
+
+ public static String getDiscordErrorWebhookFailed() {
+ return discordErrorWebhookFailed;
+ }
+
+ public static String getDiscordErrorInvalidWebhook() {
+ return discordErrorInvalidWebhook;
+ }
+
+ public static String getDiscordErrorConnectionFailed() {
+ return discordErrorConnectionFailed;
+ }
+
private static String convertColorCodes(String text) {
return ChatColor.translateAlternateColorCodes('&', text);
}
@@ -615,6 +895,14 @@ public Object getDefaultValue(String path, Object defaultValue) {
saveChanges = true;
}
+ // Process newline characters for Discord messages
+ if (obj instanceof String && path.startsWith("discord.")) {
+ String str = (String) obj;
+ // Convert literal \n sequences to actual newlines
+ str = str.replace("\\n", "\n");
+ obj = str;
+ }
+
return obj;
}
diff --git a/src/main/java/me/danjono/inventoryrollback/discord/DiscordWebhook.java b/src/main/java/me/danjono/inventoryrollback/discord/DiscordWebhook.java
new file mode 100644
index 0000000..9df6bed
--- /dev/null
+++ b/src/main/java/me/danjono/inventoryrollback/discord/DiscordWebhook.java
@@ -0,0 +1,469 @@
+package me.danjono.inventoryrollback.discord;
+
+import me.danjono.inventoryrollback.InventoryRollback;
+import me.danjono.inventoryrollback.config.ConfigData;
+import me.danjono.inventoryrollback.config.MessageData;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.entity.Player;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.CompletableFuture;
+import java.util.logging.Level;
+
+public class DiscordWebhook {
+
+ public enum EventType {
+ BACKUP_CREATED,
+ INVENTORY_RESTORED,
+ ENDER_CHEST_RESTORED,
+ HEALTH_RESTORED,
+ HUNGER_RESTORED,
+ EXPERIENCE_RESTORED,
+ PLAYER_DEATH,
+ FORCE_BACKUP
+ }
+
+ /**
+ * Sends a Discord webhook message for a backup creation event
+ */
+ public static void sendBackupCreated(String playerName, String backupType, String timestamp) {
+ if (!ConfigData.isDiscordEnabled() || !ConfigData.isDiscordBackupCreated()) {
+ return;
+ }
+
+ String message, title, description;
+ if (ConfigData.isDiscordUseEmbeds()) {
+ title = MessageData.getDiscordTitleBackupCreated();
+ description = MessageData.getDiscordDescBackupCreated()
+ .replace("%PLAYER%", playerName)
+ .replace("%TYPE%", backupType)
+ .replace("%TIME%", timestamp);
+ message = null;
+ } else {
+ message = MessageData.getDiscordMsgBackupCreated()
+ .replace("%PLAYER%", playerName)
+ .replace("%TYPE%", backupType)
+ .replace("%TIME%", timestamp);
+ title = null;
+ description = null;
+ }
+
+ sendWebhookMessage(EventType.BACKUP_CREATED, message, title, description);
+ }
+
+ /**
+ * Sends a Discord webhook message for an inventory restoration event
+ */
+ public static void sendInventoryRestored(String playerName, String adminName, String timestamp) {
+ if (!ConfigData.isDiscordEnabled() || !ConfigData.isDiscordInventoryRestored()) {
+ return;
+ }
+
+ String message, title, description;
+ if (ConfigData.isDiscordUseEmbeds()) {
+ title = MessageData.getDiscordTitleInventoryRestored();
+ description = MessageData.getDiscordDescInventoryRestored()
+ .replace("%PLAYER%", playerName)
+ .replace("%ADMIN%", adminName)
+ .replace("%TIME%", timestamp);
+ message = null;
+ } else {
+ message = MessageData.getDiscordMsgInventoryRestored()
+ .replace("%PLAYER%", playerName)
+ .replace("%ADMIN%", adminName)
+ .replace("%TIME%", timestamp);
+ title = null;
+ description = null;
+ }
+
+ sendWebhookMessage(EventType.INVENTORY_RESTORED, message, title, description);
+ }
+
+ /**
+ * Sends a Discord webhook message for an ender chest restoration event
+ */
+ public static void sendEnderChestRestored(String playerName, String adminName, String timestamp) {
+ if (!ConfigData.isDiscordEnabled() || !ConfigData.isDiscordEnderChestRestored()) {
+ return;
+ }
+
+ String message, title, description;
+ if (ConfigData.isDiscordUseEmbeds()) {
+ title = MessageData.getDiscordTitleEnderChestRestored();
+ description = MessageData.getDiscordDescEnderChestRestored()
+ .replace("%PLAYER%", playerName)
+ .replace("%ADMIN%", adminName)
+ .replace("%TIME%", timestamp);
+ message = null;
+ } else {
+ message = MessageData.getDiscordMsgEnderChestRestored()
+ .replace("%PLAYER%", playerName)
+ .replace("%ADMIN%", adminName)
+ .replace("%TIME%", timestamp);
+ title = null;
+ description = null;
+ }
+
+ sendWebhookMessage(EventType.ENDER_CHEST_RESTORED, message, title, description);
+ }
+
+ /**
+ * Sends a Discord webhook message for a health restoration event
+ */
+ public static void sendHealthRestored(String playerName, String adminName, double health, String timestamp) {
+ if (!ConfigData.isDiscordEnabled() || !ConfigData.isDiscordHealthRestored()) {
+ return;
+ }
+
+ String message, title, description;
+ if (ConfigData.isDiscordUseEmbeds()) {
+ title = MessageData.getDiscordTitleHealthRestored();
+ description = MessageData.getDiscordDescHealthRestored()
+ .replace("%PLAYER%", playerName)
+ .replace("%ADMIN%", adminName)
+ .replace("%HEALTH%", String.format("%.1f", health))
+ .replace("%TIME%", timestamp);
+ message = null;
+ } else {
+ message = MessageData.getDiscordMsgHealthRestored()
+ .replace("%PLAYER%", playerName)
+ .replace("%ADMIN%", adminName)
+ .replace("%HEALTH%", String.format("%.1f", health))
+ .replace("%TIME%", timestamp);
+ title = null;
+ description = null;
+ }
+
+ sendWebhookMessage(EventType.HEALTH_RESTORED, message, title, description);
+ }
+
+ /**
+ * Sends a Discord webhook message for a hunger restoration event
+ */
+ public static void sendHungerRestored(String playerName, String adminName, int hunger, String timestamp) {
+ if (!ConfigData.isDiscordEnabled() || !ConfigData.isDiscordHungerRestored()) {
+ return;
+ }
+
+ String message, title, description;
+ if (ConfigData.isDiscordUseEmbeds()) {
+ title = MessageData.getDiscordTitleHungerRestored();
+ description = MessageData.getDiscordDescHungerRestored()
+ .replace("%PLAYER%", playerName)
+ .replace("%ADMIN%", adminName)
+ .replace("%HUNGER%", String.valueOf(hunger))
+ .replace("%TIME%", timestamp);
+ message = null;
+ } else {
+ message = MessageData.getDiscordMsgHungerRestored()
+ .replace("%PLAYER%", playerName)
+ .replace("%ADMIN%", adminName)
+ .replace("%HUNGER%", String.valueOf(hunger))
+ .replace("%TIME%", timestamp);
+ title = null;
+ description = null;
+ }
+
+ sendWebhookMessage(EventType.HUNGER_RESTORED, message, title, description);
+ }
+
+ /**
+ * Sends a Discord webhook message for an experience restoration event
+ */
+ public static void sendExperienceRestored(String playerName, String adminName, int level, String timestamp) {
+ if (!ConfigData.isDiscordEnabled() || !ConfigData.isDiscordExperienceRestored()) {
+ return;
+ }
+
+ String message, title, description;
+ if (ConfigData.isDiscordUseEmbeds()) {
+ title = MessageData.getDiscordTitleExperienceRestored();
+ description = MessageData.getDiscordDescExperienceRestored()
+ .replace("%PLAYER%", playerName)
+ .replace("%ADMIN%", adminName)
+ .replace("%LEVEL%", String.valueOf(level))
+ .replace("%TIME%", timestamp);
+ message = null;
+ } else {
+ message = MessageData.getDiscordMsgExperienceRestored()
+ .replace("%PLAYER%", playerName)
+ .replace("%ADMIN%", adminName)
+ .replace("%LEVEL%", String.valueOf(level))
+ .replace("%TIME%", timestamp);
+ title = null;
+ description = null;
+ }
+
+ sendWebhookMessage(EventType.EXPERIENCE_RESTORED, message, title, description);
+ }
+
+ /**
+ * Sends a Discord webhook message for a player death event
+ */
+ public static void sendPlayerDeath(String playerName, Location location, String deathCause, String timestamp) {
+ if (!ConfigData.isDiscordEnabled() || !ConfigData.isDiscordPlayerDeath()) {
+ return;
+ }
+
+ String worldName = location.getWorld() != null ? location.getWorld().getName() : "unknown";
+ String x = String.valueOf((int) location.getX());
+ String y = String.valueOf((int) location.getY());
+ String z = String.valueOf((int) location.getZ());
+
+ String message, title, description;
+ if (ConfigData.isDiscordUseEmbeds()) {
+ title = MessageData.getDiscordTitlePlayerDeath();
+ description = MessageData.getDiscordDescPlayerDeath()
+ .replace("%PLAYER%", playerName)
+ .replace("%WORLD%", worldName)
+ .replace("%X%", x)
+ .replace("%Y%", y)
+ .replace("%Z%", z)
+ .replace("%CAUSE%", deathCause)
+ .replace("%TIME%", timestamp);
+ message = null;
+ } else {
+ message = MessageData.getDiscordMsgPlayerDeath()
+ .replace("%PLAYER%", playerName)
+ .replace("%WORLD%", worldName)
+ .replace("%X%", x)
+ .replace("%Y%", y)
+ .replace("%Z%", z)
+ .replace("%CAUSE%", deathCause)
+ .replace("%TIME%", timestamp);
+ title = null;
+ description = null;
+ }
+
+ sendWebhookMessage(EventType.PLAYER_DEATH, message, title, description);
+ }
+
+ /**
+ * Sends a Discord webhook message for a force backup event
+ */
+ public static void sendForceBackup(String playerName, String adminName, String timestamp) {
+ if (!ConfigData.isDiscordEnabled() || !ConfigData.isDiscordForceBackup()) {
+ return;
+ }
+
+ String message, title, description;
+ if (ConfigData.isDiscordUseEmbeds()) {
+ title = MessageData.getDiscordTitleForceBackup();
+ description = MessageData.getDiscordDescForceBackup()
+ .replace("%PLAYER%", playerName)
+ .replace("%ADMIN%", adminName)
+ .replace("%TIME%", timestamp);
+ message = null;
+ } else {
+ message = MessageData.getDiscordMsgForceBackup()
+ .replace("%PLAYER%", playerName)
+ .replace("%ADMIN%", adminName)
+ .replace("%TIME%", timestamp);
+ title = null;
+ description = null;
+ }
+
+ sendWebhookMessage(EventType.FORCE_BACKUP, message, title, description);
+ }
+
+ /**
+ * Sends the actual webhook message to Discord
+ */
+ private static void sendWebhookMessage(EventType eventType, String message, String title, String description) {
+ String webhookUrl = ConfigData.getDiscordWebhookUrl();
+
+ if (webhookUrl == null || webhookUrl.isEmpty()) {
+ if (ConfigData.isDebugEnabled()) {
+ InventoryRollback.getInstance().getLogger().warning("Discord webhook URL is not configured");
+ }
+ return;
+ }
+
+ // Validate webhook URL
+ if (!isValidWebhookUrl(webhookUrl)) {
+ InventoryRollback.getInstance().getLogger().warning(MessageData.getDiscordErrorInvalidWebhook());
+ return;
+ }
+
+ // Send webhook asynchronously to avoid blocking the main thread
+ CompletableFuture.runAsync(() -> {
+ try {
+ String jsonPayload = buildJsonPayload(eventType, message, title, description);
+ sendHttpRequest(webhookUrl, jsonPayload);
+
+ if (ConfigData.isDebugEnabled()) {
+ InventoryRollback.getInstance().getLogger().info("Discord webhook sent successfully for event: " + eventType);
+ }
+ } catch (Exception e) {
+ InventoryRollback.getInstance().getLogger().log(Level.WARNING, MessageData.getDiscordErrorWebhookFailed() + ": " + e.getMessage(), e);
+ }
+ });
+ }
+
+ /**
+ * Builds the JSON payload for the Discord webhook
+ */
+ private static String buildJsonPayload(EventType eventType, String message, String title, String description) {
+ StringBuilder json = new StringBuilder();
+ json.append("{");
+
+ // Add server name if enabled
+ if (ConfigData.isDiscordIncludeServerName()) {
+ json.append("\"username\":\"").append(escapeJson(ConfigData.getDiscordServerName())).append("\",");
+ }
+
+ if (ConfigData.isDiscordUseEmbeds() && title != null && description != null) {
+ // Use embeds - Discord embeds support newlines differently
+ json.append("\"embeds\":[{");
+ json.append("\"title\":\"").append(escapeJson(title)).append("\",");
+ json.append("\"description\":\"").append(escapeJsonForEmbed(description)).append("\",");
+ json.append("\"color\":").append(getColorForEventType(eventType)).append(",");
+ json.append("\"timestamp\":\"").append(java.time.Instant.now().toString()).append("\"");
+ json.append("}]");
+ } else {
+ // Use simple content message - Discord content supports newlines natively
+ json.append("\"content\":\"").append(escapeJsonForContent(message != null ? message : "Unknown event")).append("\"");
+ }
+
+ json.append("}");
+ return json.toString();
+ }
+
+ /**
+ * Gets the color code for the event type
+ */
+ private static int getColorForEventType(EventType eventType) {
+ String colorHex;
+ switch (eventType) {
+ case BACKUP_CREATED:
+ case FORCE_BACKUP:
+ colorHex = ConfigData.getDiscordColorBackup();
+ break;
+ case INVENTORY_RESTORED:
+ case ENDER_CHEST_RESTORED:
+ case HEALTH_RESTORED:
+ case HUNGER_RESTORED:
+ case EXPERIENCE_RESTORED:
+ colorHex = ConfigData.getDiscordColorRestore();
+ break;
+ case PLAYER_DEATH:
+ colorHex = ConfigData.getDiscordColorDeath();
+ break;
+ default:
+ colorHex = ConfigData.getDiscordColorWarning();
+ break;
+ }
+
+ // Convert hex color to decimal
+ try {
+ if (colorHex.startsWith("#")) {
+ colorHex = colorHex.substring(1);
+ }
+ return Integer.parseInt(colorHex, 16);
+ } catch (NumberFormatException e) {
+ return 0x0099ff; // Default blue color
+ }
+ }
+
+ /**
+ * Sends the HTTP request to Discord
+ */
+ private static void sendHttpRequest(String webhookUrl, String jsonPayload) throws IOException {
+ // Log the JSON payload for debugging when debug is enabled
+ if (ConfigData.isDebugEnabled()) {
+ InventoryRollback.getInstance().getLogger().info("Sending Discord webhook payload: " + jsonPayload);
+ }
+
+ URL url = new URL(webhookUrl);
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+
+ connection.setRequestMethod("POST");
+ connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
+ connection.setRequestProperty("User-Agent", "InventoryRollbackPlus/1.7.6");
+ connection.setDoOutput(true);
+
+ try (OutputStream outputStream = connection.getOutputStream()) {
+ byte[] input = jsonPayload.getBytes(StandardCharsets.UTF_8);
+ outputStream.write(input, 0, input.length);
+ }
+
+ int responseCode = connection.getResponseCode();
+ if (responseCode < 200 || responseCode >= 300) {
+ // Try to read error response for debugging
+ String errorResponse = "";
+ try {
+ if (connection.getErrorStream() != null) {
+ java.util.Scanner scanner = new java.util.Scanner(connection.getErrorStream()).useDelimiter("\\A");
+ errorResponse = scanner.hasNext() ? scanner.next() : "";
+ }
+ } catch (Exception e) {
+ // Ignore error reading response
+ }
+
+ String errorMessage = "Discord webhook returned HTTP " + responseCode;
+ if (!errorResponse.isEmpty() && ConfigData.isDebugEnabled()) {
+ errorMessage += " - Response: " + errorResponse;
+ }
+ throw new IOException(errorMessage);
+ }
+ }
+
+ /**
+ * Validates if the webhook URL is a valid Discord webhook URL
+ */
+ private static boolean isValidWebhookUrl(String url) {
+ try {
+ new URL(url);
+ return url.contains("discord.com/api/webhooks/") || url.contains("discordapp.com/api/webhooks/");
+ } catch (MalformedURLException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Escapes JSON special characters
+ */
+ private static String escapeJson(String text) {
+ if (text == null) return "";
+ return text.replace("\\", "\\\\")
+ .replace("\"", "\\\"")
+ .replace("\r", "") // Remove carriage returns
+ .replace("\t", " "); // Replace tabs with spaces, keep newlines as-is
+ }
+
+ /**
+ * Escapes JSON special characters for content messages
+ * Content messages support newlines natively but need proper JSON escaping
+ */
+ private static String escapeJsonForContent(String text) {
+ if (text == null) return "";
+ return text.replace("\\", "\\\\")
+ .replace("\"", "\\\"")
+ .replace("\b", "\\b")
+ .replace("\f", "\\f")
+ .replace("\n", "\\n") // Properly escape newlines for JSON
+ .replace("\r", "\\r")
+ .replace("\t", "\\t");
+ }
+
+ /**
+ * Escapes JSON special characters for embed descriptions
+ * Embed descriptions support newlines but need proper JSON escaping
+ */
+ private static String escapeJsonForEmbed(String text) {
+ if (text == null) return "";
+ return text.replace("\\", "\\\\")
+ .replace("\"", "\\\"")
+ .replace("\b", "\\b")
+ .replace("\f", "\\f")
+ .replace("\n", "\\n") // Properly escape newlines for JSON
+ .replace("\r", "\\r")
+ .replace("\t", "\\t");
+ }
+}
diff --git a/src/main/java/me/danjono/inventoryrollback/listeners/ClickGUI.java b/src/main/java/me/danjono/inventoryrollback/listeners/ClickGUI.java
index 2480efc..3366ac0 100644
--- a/src/main/java/me/danjono/inventoryrollback/listeners/ClickGUI.java
+++ b/src/main/java/me/danjono/inventoryrollback/listeners/ClickGUI.java
@@ -10,6 +10,7 @@
import me.danjono.inventoryrollback.config.SoundData;
import me.danjono.inventoryrollback.data.LogType;
import me.danjono.inventoryrollback.data.PlayerData;
+import me.danjono.inventoryrollback.discord.DiscordWebhook;
import me.danjono.inventoryrollback.gui.Buttons;
import me.danjono.inventoryrollback.gui.InventoryName;
import me.danjono.inventoryrollback.gui.menu.*;
@@ -338,6 +339,16 @@ public void run() {
player.sendMessage(MessageData.getPluginPrefix() + MessageData.getMainInventoryRestoredPlayer(staff.getName()));
if (!staff.getUniqueId().equals(player.getUniqueId()))
staff.sendMessage(MessageData.getPluginPrefix() + MessageData.getMainInventoryRestored(offlinePlayer.getName()));
+
+ // Send Discord webhook for inventory restoration
+ try {
+ String timestamp = ConfigData.getTimeFormat().format(System.currentTimeMillis());
+ DiscordWebhook.sendInventoryRestored(offlinePlayer.getName(), staff.getName(), timestamp);
+ } catch (Exception ex) {
+ if (ConfigData.isDebugEnabled()) {
+ InventoryRollback.getInstance().getLogger().warning("Failed to send Discord webhook for inventory restore: " + ex.getMessage());
+ }
+ }
}
}.runTaskAsynchronously(main);
@@ -440,6 +451,16 @@ else if (icon.getType().equals(Buttons.getHealthIcon())) {
player.sendMessage(MessageData.getPluginPrefix() + MessageData.getHealthRestoredPlayer(staff.getName()));
if (!staff.getUniqueId().equals(player.getUniqueId()))
staff.sendMessage(MessageData.getPluginPrefix() + MessageData.getHealthRestored(player.getName()));
+
+ // Send Discord webhook for health restoration
+ try {
+ String healthTimestamp = ConfigData.getTimeFormat().format(System.currentTimeMillis());
+ DiscordWebhook.sendHealthRestored(offlinePlayer.getName(), staff.getName(), health, healthTimestamp);
+ } catch (Exception ex) {
+ if (ConfigData.isDebugEnabled()) {
+ InventoryRollback.getInstance().getLogger().warning("Failed to send Discord webhook for health restore: " + ex.getMessage());
+ }
+ }
} else {
staff.sendMessage(MessageData.getPluginPrefix() + MessageData.getHealthNotOnline(offlinePlayer.getName()));
}
@@ -467,6 +488,16 @@ else if (icon.getType().equals(Buttons.getHungerIcon())) {
player.sendMessage(MessageData.getPluginPrefix() + MessageData.getHungerRestoredPlayer(staff.getName()));
if (!staff.getUniqueId().equals(player.getUniqueId()))
staff.sendMessage(MessageData.getPluginPrefix() + MessageData.getHungerRestored(player.getName()));
+
+ // Send Discord webhook for hunger restoration
+ try {
+ String hungerTimestamp = ConfigData.getTimeFormat().format(System.currentTimeMillis());
+ DiscordWebhook.sendHungerRestored(offlinePlayer.getName(), staff.getName(), hunger, hungerTimestamp);
+ } catch (Exception ex) {
+ if (ConfigData.isDebugEnabled()) {
+ InventoryRollback.getInstance().getLogger().warning("Failed to send Discord webhook for hunger restore: " + ex.getMessage());
+ }
+ }
} else {
staff.sendMessage(MessageData.getPluginPrefix() + MessageData.getHungerNotOnline(offlinePlayer.getName()));
}
@@ -493,7 +524,17 @@ else if (icon.getType().equals(Buttons.getExperienceIcon())) {
player.sendMessage(MessageData.getPluginPrefix() + MessageData.getExperienceRestoredPlayer(staff.getName(), level));
if (!staff.getUniqueId().equals(player.getUniqueId()))
staff.sendMessage(MessageData.getPluginPrefix() + MessageData.getExperienceRestored(player.getName(), level));
- } else {
+
+ // Send Discord webhook for experience restoration
+ try {
+ String experienceTimestamp = ConfigData.getTimeFormat().format(System.currentTimeMillis());
+ DiscordWebhook.sendExperienceRestored(offlinePlayer.getName(), staff.getName(), level, experienceTimestamp);
+ } catch (Exception ex) {
+ if (ConfigData.isDebugEnabled()) {
+ InventoryRollback.getInstance().getLogger().warning("Failed to send Discord webhook for experience restore: " + ex.getMessage());
+ }
+ }
+ } else {
staff.sendMessage(MessageData.getPluginPrefix() + MessageData.getExperienceNotOnlinePlayer(offlinePlayer.getName()));
}
}
@@ -692,4 +733,4 @@ public void run() {
}
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/me/danjono/inventoryrollback/listeners/EventLogs.java b/src/main/java/me/danjono/inventoryrollback/listeners/EventLogs.java
index 170ddc2..e8dc565 100644
--- a/src/main/java/me/danjono/inventoryrollback/listeners/EventLogs.java
+++ b/src/main/java/me/danjono/inventoryrollback/listeners/EventLogs.java
@@ -4,6 +4,7 @@
import com.tcoded.lightlibs.bukkitversion.BukkitVersion;
import me.danjono.inventoryrollback.config.ConfigData;
import me.danjono.inventoryrollback.data.LogType;
+import me.danjono.inventoryrollback.discord.DiscordWebhook;
import me.danjono.inventoryrollback.inventory.SaveInventory;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
@@ -71,6 +72,16 @@ private void playerJoin(PlayerJoinEvent e) {
if (player.hasPermission("inventoryrollbackplus.joinsave")) {
new SaveInventory(e.getPlayer(), LogType.JOIN, null, null)
.snapshotAndSave(player.getInventory(), player.getEnderChest(), true);
+
+ // Send Discord webhook for backup creation
+ try {
+ String timestamp = ConfigData.getTimeFormat().format(System.currentTimeMillis());
+ DiscordWebhook.sendBackupCreated(player.getName(), "JOIN", timestamp);
+ } catch (Exception ex) {
+ if (ConfigData.isDebugEnabled()) {
+ main.getLogger().warning("Failed to send Discord webhook for join backup: " + ex.getMessage());
+ }
+ }
}
if (player.hasPermission("inventoryrollbackplus.adminalerts")) {
// can send info to admins here
@@ -86,6 +97,16 @@ private void playerQuit(PlayerQuitEvent e) {
if (player.hasPermission("inventoryrollbackplus.leavesave")) {
new SaveInventory(e.getPlayer(), LogType.QUIT, null, null)
.snapshotAndSave(player.getInventory(), player.getEnderChest(), true);
+
+ // Send Discord webhook for backup creation
+ try {
+ String timestamp = ConfigData.getTimeFormat().format(System.currentTimeMillis());
+ DiscordWebhook.sendBackupCreated(player.getName(), "QUIT", timestamp);
+ } catch (Exception ex) {
+ if (ConfigData.isDebugEnabled()) {
+ main.getLogger().warning("Failed to send Discord webhook for quit backup: " + ex.getMessage());
+ }
+ }
}
UUID uuid = player.getUniqueId();
@@ -214,6 +235,22 @@ public void playerDeathHandle(PlayerDeathEvent event) {
// Remove the snapshot from the cache
this.inventoryCache.remove(uuid);
}
+
+ // Send Discord webhook for player death
+ try {
+ String deathCause = detailedReason.reason != null ? detailedReason.reason : detailedReason.damageCause.name();
+ String timestamp = ConfigData.getTimeFormat().format(System.currentTimeMillis());
+ DiscordWebhook.sendPlayerDeath(
+ player.getName(),
+ player.getLocation(),
+ deathCause,
+ timestamp
+ );
+ } catch (Exception e) {
+ if (ConfigData.isDebugEnabled()) {
+ main.getLogger().warning("Failed to send Discord webhook for player death: " + e.getMessage());
+ }
+ }
}
}
@@ -226,6 +263,16 @@ private void playerChangeWorld(PlayerChangedWorldEvent e) {
if (player.hasPermission("inventoryrollbackplus.worldchangesave")) {
new SaveInventory(e.getPlayer(), LogType.WORLD_CHANGE, null, null)
.snapshotAndSave(player.getInventory(), player.getEnderChest(), true);
+
+ // Send Discord webhook for backup creation
+ try {
+ String timestamp = ConfigData.getTimeFormat().format(System.currentTimeMillis());
+ DiscordWebhook.sendBackupCreated(player.getName(), "WORLD_CHANGE", timestamp);
+ } catch (Exception ex) {
+ if (ConfigData.isDebugEnabled()) {
+ main.getLogger().warning("Failed to send Discord webhook for world change backup: " + ex.getMessage());
+ }
+ }
}
}
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index 82f82fc..3c13b6d 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -30,6 +30,35 @@ mysql:
verifyCertificate: true
allowPubKeyRetrieval: false
+## Discord webhook logging configuration
+## Allows sending backup and restore events to a Discord channel via webhook
+discord:
+ enabled: false
+ webhook-url: ''
+ # Events to log to Discord (set to false to disable specific events)
+ events:
+ backup-created: true
+ inventory-restored: true
+ ender-chest-restored: true
+ health-restored: true
+ hunger-restored: true
+ experience-restored: true
+ player-death: true
+ force-backup: true
+ # Additional settings
+ settings:
+ # Include server name in Discord messages
+ include-server-name: true
+ server-name: 'My Server'
+ # Use embeds for richer formatting
+ use-embeds: true
+ # Color for different event types (hex colors)
+ colors:
+ backup: '#00ff00' # Green for backups
+ restore: '#0099ff' # Blue for restores
+ death: '#ff3300' # Red for deaths
+ warning: '#ffcc00' # Yellow for warnings
+
## Sounds will play to the player when parts of their player is restored.
sounds:
teleport:
diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml
index ab8c408..7d04647 100644
--- a/src/main/resources/messages.yml
+++ b/src/main/resources/messages.yml
@@ -17,6 +17,47 @@ backup:
force-saved-all: 'All online player inventories have been force saved.'
not-forced-saved: 'There was an issue with saving %NAME%''s inventory.'
+# Discord webhook messages
+discord:
+ # Event titles for Discord embeds
+ titles:
+ backup-created: '📦 Backup Created'
+ inventory-restored: '🎒 Inventory Restored'
+ ender-chest-restored: '📦 Ender Chest Restored'
+ health-restored: '❤️ Health Restored'
+ hunger-restored: '🍖 Hunger Restored'
+ experience-restored: '✨ Experience Restored'
+ player-death: '💀 Player Death'
+ force-backup: '🔧 Force Backup'
+
+ # Event descriptions for Discord embeds
+ descriptions:
+ backup-created: 'Player **%PLAYER%** backup created\n**Type:** %TYPE%\n**Time:** %TIME%'
+ inventory-restored: 'Player **%PLAYER%** inventory restored by **%ADMIN%**\n**From backup:** %TIME%'
+ ender-chest-restored: 'Player **%PLAYER%** ender chest restored by **%ADMIN%**\n**From backup:** %TIME%'
+ health-restored: 'Player **%PLAYER%** health restored by **%ADMIN%**\n**Health:** %HEALTH%\n**From backup:** %TIME%'
+ hunger-restored: 'Player **%PLAYER%** hunger restored by **%ADMIN%**\n**Hunger:** %HUNGER%\n**From backup:** %TIME%'
+ experience-restored: 'Player **%PLAYER%** experience restored by **%ADMIN%**\n**Level:** %LEVEL%\n**From backup:** %TIME%'
+ player-death: 'Player **%PLAYER%** died\n**Location:** %WORLD% (%X%, %Y%, %Z%)\n**Cause:** %CAUSE%\n**Time:** %TIME%'
+ force-backup: 'Force backup created for **%PLAYER%** by **%ADMIN%**\n**Time:** %TIME%'
+
+ # Simple text messages (when embeds are disabled)
+ messages:
+ backup-created: '📦 Backup created for %PLAYER% (%TYPE%) at %TIME%'
+ inventory-restored: '🎒 %PLAYER% inventory restored by %ADMIN% from backup %TIME%'
+ ender-chest-restored: '📦 %PLAYER% ender chest restored by %ADMIN% from backup %TIME%'
+ health-restored: '❤️ %PLAYER% health restored by %ADMIN% (Health: %HEALTH%) from backup %TIME%'
+ hunger-restored: '🍖 %PLAYER% hunger restored by %ADMIN% (Hunger: %HUNGER%) from backup %TIME%'
+ experience-restored: '✨ %PLAYER% experience restored by %ADMIN% (Level: %LEVEL%) from backup %TIME%'
+ player-death: '💀 %PLAYER% died at %WORLD% (%X%, %Y%, %Z%) - %CAUSE% at %TIME%'
+ force-backup: '🔧 Force backup created for %PLAYER% by %ADMIN% at %TIME%'
+
+ # Error messages
+ errors:
+ webhook-failed: '&cFailed to send Discord webhook message'
+ invalid-webhook: '&cInvalid Discord webhook URL configured'
+ connection-failed: '&cFailed to connect to Discord webhook'
+
attribute-restore:
main-inventory:
restored: '%NAME%''s main inventory has been restored.'