diff --git a/src/main/java/com/loki/lochat/LoChat.java b/src/main/java/com/loki/lochat/LoChat.java index 009d357..1b0d5a5 100644 --- a/src/main/java/com/loki/lochat/LoChat.java +++ b/src/main/java/com/loki/lochat/LoChat.java @@ -8,9 +8,11 @@ import com.loki.lochat.config.ConfigManager; import com.loki.lochat.config.MessageConfig; import com.loki.lochat.core.registry.ServiceRegistry; import com.loki.lochat.gradient.GradientModule; +import com.loki.lochat.integrations.DiscordIntegration; import com.loki.lochat.integrations.LibertyBansHook; import com.loki.lochat.integrations.PlaceholderAPIHook; import com.loki.lochat.listener.ChatEventListener; +import com.loki.lochat.listener.DiscordEventListener; import com.loki.lochat.listener.PlayerEventListener; import com.loki.lochat.managers.AutoMessageManager; import com.loki.lochat.managers.CustomCommandManager; @@ -30,6 +32,7 @@ public final class LoChat extends JavaPlugin { private GradientModule gradientModule; private AutoMessageManager autoMessageManager; private CustomCommandManager customCommandManager; + private DiscordIntegration discordIntegration; // Геттеры public static LoChat getInstance() { @@ -86,6 +89,9 @@ public final class LoChat extends JavaPlugin { } private void initIntegrations() { + // Discord интеграция + discordIntegration = new DiscordIntegration(this); + // Gradient модуль gradientModule = new GradientModule(this); if (gradientModule.init()) { @@ -144,11 +150,23 @@ public final class LoChat extends JavaPlugin { ClearChatConfigCommand clearCfgCmd = new ClearChatConfigCommand(this); getCommand("clearchatconfig").setExecutor(clearCfgCmd); getCommand("clearchatconfig").setTabCompleter(clearCfgCmd); + + // Discord команда + DiscordCommand discordCmd = new DiscordCommand(this, discordIntegration); + getCommand("discord").setExecutor(discordCmd); + getCommand("discord").setTabCompleter(discordCmd); } private void registerListeners() { getServer().getPluginManager().registerEvents(new ChatEventListener(this, serviceRegistry), this); getServer().getPluginManager().registerEvents(new PlayerEventListener(serviceRegistry), this); + + // Discord listener + if (discordIntegration.isEnabled()) { + getServer().getPluginManager().registerEvents(new DiscordEventListener(discordIntegration), this); + getLogger().info("Discord event listener registered"); + } + getLogger().info("Слушатели зарегистрированы (новая архитектура)"); } @@ -163,6 +181,7 @@ public final class LoChat extends JavaPlugin { autoMessageManager.reload(); if (gradientModule != null) gradientModule.reload(); if (customCommandManager != null) customCommandManager.reload(); + if (discordIntegration != null) discordIntegration.reload(); } public ServiceRegistry getServiceRegistry() { @@ -184,4 +203,8 @@ public final class LoChat extends JavaPlugin { public CustomCommandManager getCustomCommandManager() { return customCommandManager; } + + public DiscordIntegration getDiscordIntegration() { + return discordIntegration; + } } diff --git a/src/main/java/com/loki/lochat/commands/DiscordCommand.java b/src/main/java/com/loki/lochat/commands/DiscordCommand.java new file mode 100644 index 0000000..566ca22 --- /dev/null +++ b/src/main/java/com/loki/lochat/commands/DiscordCommand.java @@ -0,0 +1,120 @@ +package com.loki.lochat.commands; + +import com.loki.lochat.LoChat; +import com.loki.lochat.integrations.DiscordIntegration; +import com.loki.lochat.utils.ChatFormatter; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.List; + +/** + * Команда для управления Discord интеграцией + */ +public class DiscordCommand implements CommandExecutor, TabCompleter { + private final LoChat plugin; + private final DiscordIntegration discord; + + public DiscordCommand(LoChat plugin, DiscordIntegration discord) { + this.plugin = plugin; + this.discord = discord; + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + if (!sender.hasPermission("lochat.discord.admin")) { + sender.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("errors.no-permission"))); + return true; + } + + if (args.length == 0) { + sendHelp(sender); + return true; + } + + switch (args[0].toLowerCase()) { + case "status": + handleStatus(sender); + break; + case "reload": + handleReload(sender); + break; + case "test": + handleTest(sender, args); + break; + default: + sendHelp(sender); + break; + } + + return true; + } + + private void sendHelp(CommandSender sender) { + sender.sendMessage(ChatFormatter.parse("&6&l=== Discord Integration ===")); + sender.sendMessage(ChatFormatter.parse("&e/discord status &7- Показать статус интеграции")); + sender.sendMessage(ChatFormatter.parse("&e/discord reload &7- Перезагрузить конфигурацию")); + sender.sendMessage(ChatFormatter.parse("&e/discord test [message] &7- Отправить тестовое сообщение")); + } + + private void handleStatus(CommandSender sender) { + if (discord.isEnabled()) { + sender.sendMessage(ChatFormatter.parse("&a✓ Discord интеграция включена")); + } else { + sender.sendMessage(ChatFormatter.parse("&c✗ Discord интеграция отключена")); + } + } + + private void handleReload(CommandSender sender) { + try { + discord.reload(); + sender.sendMessage(ChatFormatter.parse("&a✓ Discord конфигурация перезагружена")); + } catch (Exception e) { + sender.sendMessage(ChatFormatter.parse("&c✗ Ошибка при перезагрузке: " + e.getMessage())); + plugin.getLogger().severe("Error reloading Discord config: " + e.getMessage()); + } + } + + private void handleTest(CommandSender sender, String[] args) { + if (!discord.isEnabled()) { + sender.sendMessage(ChatFormatter.parse("&c✗ Discord интеграция отключена")); + return; + } + + String message = args.length > 1 ? String.join(" ", Arrays.copyOfRange(args, 1, args.length)) : "Тестовое сообщение от " + sender.getName(); + + sender.sendMessage(ChatFormatter.parse("&e⏳ Отправка тестового сообщения...")); + + // Отправляем тестовое сообщение + plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> { + try { + discord.sendTestMessage(message, sender.getName()); + plugin.getServer().getScheduler().runTask(plugin, () -> + sender.sendMessage(ChatFormatter.parse("&a✓ Тестовое сообщение отправлено в Discord")) + ); + } catch (Exception e) { + plugin.getServer().getScheduler().runTask(plugin, () -> + sender.sendMessage(ChatFormatter.parse("&c✗ Ошибка отправки: " + e.getMessage())) + ); + } + }); + } + + @Override + public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { + if (!sender.hasPermission("lochat.discord.admin")) { + return null; + } + + if (args.length == 1) { + return Arrays.asList("status", "reload", "test"); + } + + return null; + } +} \ No newline at end of file diff --git a/src/main/java/com/loki/lochat/integrations/DiscordIntegration.java b/src/main/java/com/loki/lochat/integrations/DiscordIntegration.java new file mode 100644 index 0000000..ea27ffb --- /dev/null +++ b/src/main/java/com/loki/lochat/integrations/DiscordIntegration.java @@ -0,0 +1,244 @@ +package com.loki.lochat.integrations; + +import com.loki.lochat.util.FoliaUtil; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; +import java.util.regex.Pattern; + +/** + * Интеграция с Discord через вебхуки + */ +public class DiscordIntegration { + private final JavaPlugin plugin; + private YamlConfiguration config; + private DiscordWebhook webhook; + private boolean enabled = false; + + // Паттерны для очистки сообщений + private static final Pattern MENTION_PATTERN = Pattern.compile("@(everyone|here)", Pattern.CASE_INSENSITIVE); + private static final Pattern COLOR_CODE_PATTERN = Pattern.compile("§[0-9a-fk-or]", Pattern.CASE_INSENSITIVE); + + public DiscordIntegration(JavaPlugin plugin) { + this.plugin = plugin; + loadConfig(); + initWebhook(); + } + + /** + * Загрузить конфигурацию + */ + private void loadConfig() { + File configFile = new File(plugin.getDataFolder(), "config/discord.yml"); + if (!configFile.exists()) { + plugin.saveResource("config/discord.yml", false); + } + + config = YamlConfiguration.loadConfiguration(configFile); + enabled = config.getBoolean("enabled", false); + + if (!enabled) { + plugin.getLogger().info("Discord integration disabled in config"); + return; + } + } + + /** + * Инициализировать вебхук + */ + private void initWebhook() { + if (!enabled) return; + + String webhookUrl = config.getString("webhook.url", ""); + String username = config.getString("webhook.username", "Minecraft Server"); + String avatarUrl = config.getString("webhook.avatar_url", "https://mc-heads.net/avatar/minecraft/64"); + int timeout = config.getInt("performance.timeout", 10); + int retryAttempts = config.getInt("performance.retry_attempts", 3); + long retryDelay = config.getLong("performance.retry_delay", 1000); + + webhook = new DiscordWebhook(plugin, webhookUrl, username, avatarUrl, timeout, retryAttempts, retryDelay); + + if (!webhook.isValid()) { + plugin.getLogger().severe("Invalid Discord webhook URL! Please check config/discord.yml"); + enabled = false; + return; + } + + plugin.getLogger().info("Discord integration initialized successfully"); + } + + /** + * Отправить сообщение чата в Discord + */ + public void sendChatMessage(Player player, String message, boolean isGlobal) { + if (!enabled || !config.getBoolean("chat.enabled", true)) return; + + // Проверяем фильтр глобального чата + if (config.getBoolean("chat.global_only", true) && !isGlobal) { + return; + } + + // Фильтрация сообщения + String cleanMessage = sanitizeMessage(message); + if (cleanMessage.length() < config.getInt("filter.min_message_length", 1)) { + return; + } + + // Обрезаем если слишком длинное + int maxLength = config.getInt("filter.max_message_length", 2000); + if (cleanMessage.length() > maxLength) { + cleanMessage = cleanMessage.substring(0, maxLength - 3) + "..."; + } + + String playerName = player.getName(); + String format = config.getString("chat.format", "**{player}**: {message}"); + String formattedMessage = format + .replace("{player}", playerName) + .replace("{message}", cleanMessage); + + // Отправляем асинхронно + if (config.getBoolean("performance.async", true)) { + FoliaUtil.runAsync(plugin, () -> sendChatMessageInternal(formattedMessage, playerName, isGlobal)); + } else { + sendChatMessageInternal(formattedMessage, playerName, isGlobal); + } + } + + /** + * Внутренний метод отправки сообщения чата + */ + private void sendChatMessageInternal(String message, String playerName, boolean isGlobal) { + if (config.getBoolean("chat.use_embed", true)) { + String title = isGlobal ? "🌍 Глобальный чат" : "📍 Локальный чат"; + String color = config.getString("chat.embed_color", "5865F2"); + String thumbnail = "https://mc-heads.net/avatar/" + playerName + "/64"; + + webhook.sendEmbed(title, message, color, thumbnail); + } else { + webhook.sendMessage(message); + } + } + + /** + * Отправить событие входа игрока + */ + public void sendPlayerJoin(Player player) { + if (!enabled || !config.getBoolean("events.enabled", true) || + !config.getBoolean("events.join.enabled", true)) return; + + String playerName = player.getName(); + String title = config.getString("events.join.title", "🟢 Игрок зашел на сервер"); + String description = config.getString("events.join.description", "**{player}** присоединился к серверу") + .replace("{player}", playerName); + String color = config.getString("events.join.color", "00FF00"); + String thumbnail = config.getString("events.join.thumbnail", "https://mc-heads.net/avatar/{player}/64") + .replace("{player}", playerName); + + if (config.getBoolean("performance.async", true)) { + FoliaUtil.runAsync(plugin, () -> webhook.sendEmbed(title, description, color, thumbnail)); + } else { + webhook.sendEmbed(title, description, color, thumbnail); + } + } + + /** + * Отправить событие выхода игрока + */ + public void sendPlayerQuit(Player player) { + if (!enabled || !config.getBoolean("events.enabled", true) || + !config.getBoolean("events.quit.enabled", true)) return; + + String playerName = player.getName(); + String title = config.getString("events.quit.title", "🔴 Игрок покинул сервер"); + String description = config.getString("events.quit.description", "**{player}** покинул сервер") + .replace("{player}", playerName); + String color = config.getString("events.quit.color", "FF0000"); + String thumbnail = config.getString("events.quit.thumbnail", "https://mc-heads.net/avatar/{player}/64") + .replace("{player}", playerName); + + if (config.getBoolean("performance.async", true)) { + FoliaUtil.runAsync(plugin, () -> webhook.sendEmbed(title, description, color, thumbnail)); + } else { + webhook.sendEmbed(title, description, color, thumbnail); + } + } + + /** + * Отправить событие смерти игрока + */ + public void sendPlayerDeath(Player player, String deathMessage) { + if (!enabled || !config.getBoolean("events.enabled", true) || + !config.getBoolean("events.death.enabled", true)) return; + + String playerName = player.getName(); + String title = config.getString("events.death.title", "💀 Смерть игрока"); + String description = config.getString("events.death.description", "**{player}** {death_message}") + .replace("{player}", playerName) + .replace("{death_message}", sanitizeMessage(deathMessage)); + String color = config.getString("events.death.color", "800080"); + String thumbnail = config.getString("events.death.thumbnail", "https://mc-heads.net/avatar/{player}/64") + .replace("{player}", playerName); + + if (config.getBoolean("performance.async", true)) { + FoliaUtil.runAsync(plugin, () -> webhook.sendEmbed(title, description, color, thumbnail)); + } else { + webhook.sendEmbed(title, description, color, thumbnail); + } + } + + /** + * Очистить сообщение от нежелательных элементов + */ + private String sanitizeMessage(String message) { + if (message == null) return ""; + + // Удаляем цветовые коды Minecraft + message = COLOR_CODE_PATTERN.matcher(message).replaceAll(""); + + // Заменяем опасные упоминания + if (config.getBoolean("filter.sanitize_mentions", true)) { + message = MENTION_PATTERN.matcher(message).replaceAll("@\u200B$1"); // Добавляем невидимый символ + } + + // Экранируем Discord markdown + message = message.replace("*", "\\*") + .replace("_", "\\_") + .replace("`", "\\`") + .replace("~", "\\~"); + + return message.trim(); + } + + /** + * Перезагрузить конфигурацию + */ + public void reload() { + loadConfig(); + initWebhook(); + plugin.getLogger().info("Discord integration reloaded"); + } + + /** + * Отправить тестовое сообщение + */ + public void sendTestMessage(String message, String senderName) { + if (!enabled) return; + + String title = "🧪 Тестовое сообщение"; + String description = "**Отправитель:** " + senderName + "\n**Сообщение:** " + message; + String color = "FFD700"; // золотой цвет + String thumbnail = "https://mc-heads.net/avatar/" + senderName + "/64"; + + webhook.sendEmbed(title, description, color, thumbnail); + } + + /** + * Проверить включена ли интеграция + */ + public boolean isEnabled() { + return enabled; + } +} \ No newline at end of file diff --git a/src/main/java/com/loki/lochat/integrations/DiscordWebhook.java b/src/main/java/com/loki/lochat/integrations/DiscordWebhook.java new file mode 100644 index 0000000..ec57896 --- /dev/null +++ b/src/main/java/com/loki/lochat/integrations/DiscordWebhook.java @@ -0,0 +1,186 @@ +package com.loki.lochat.integrations; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.loki.lochat.util.FoliaUtil; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.CompletableFuture; + +/** + * Класс для отправки сообщений в Discord через вебхуки + */ +public class DiscordWebhook { + private final JavaPlugin plugin; + private final String webhookUrl; + private final String username; + private final String avatarUrl; + private final int timeout; + private final int retryAttempts; + private final long retryDelay; + + public DiscordWebhook(JavaPlugin plugin, String webhookUrl, String username, String avatarUrl, + int timeout, int retryAttempts, long retryDelay) { + this.plugin = plugin; + this.webhookUrl = webhookUrl; + this.username = username; + this.avatarUrl = avatarUrl; + this.timeout = timeout * 1000; // конвертируем в миллисекунды + this.retryAttempts = retryAttempts; + this.retryDelay = retryDelay; + } + + /** + * Отправить простое текстовое сообщение + */ + public CompletableFuture sendMessage(String content) { + JsonObject json = new JsonObject(); + json.addProperty("content", content); + json.addProperty("username", username); + json.addProperty("avatar_url", avatarUrl); + + return sendWebhook(json); + } + + /** + * Отправить embed сообщение + */ + public CompletableFuture sendEmbed(String title, String description, String color, String thumbnailUrl) { + JsonObject embed = new JsonObject(); + if (title != null) embed.addProperty("title", title); + if (description != null) embed.addProperty("description", description); + if (color != null) embed.addProperty("color", Integer.parseInt(color, 16)); + + if (thumbnailUrl != null) { + JsonObject thumbnail = new JsonObject(); + thumbnail.addProperty("url", thumbnailUrl); + embed.add("thumbnail", thumbnail); + } + + // Добавляем timestamp + embed.addProperty("timestamp", java.time.Instant.now().toString()); + + JsonArray embeds = new JsonArray(); + embeds.add(embed); + + JsonObject json = new JsonObject(); + json.add("embeds", embeds); + json.addProperty("username", username); + json.addProperty("avatar_url", avatarUrl); + + return sendWebhook(json); + } + + /** + * Отправить embed с кастомными полями + */ + public CompletableFuture sendEmbedWithFields(String title, String description, String color, + String thumbnailUrl, JsonArray fields) { + JsonObject embed = new JsonObject(); + if (title != null) embed.addProperty("title", title); + if (description != null) embed.addProperty("description", description); + if (color != null) embed.addProperty("color", Integer.parseInt(color, 16)); + if (fields != null) embed.add("fields", fields); + + if (thumbnailUrl != null) { + JsonObject thumbnail = new JsonObject(); + thumbnail.addProperty("url", thumbnailUrl); + embed.add("thumbnail", thumbnail); + } + + embed.addProperty("timestamp", java.time.Instant.now().toString()); + + JsonArray embeds = new JsonArray(); + embeds.add(embed); + + JsonObject json = new JsonObject(); + json.add("embeds", embeds); + json.addProperty("username", username); + json.addProperty("avatar_url", avatarUrl); + + return sendWebhook(json); + } + + /** + * Отправить JSON в вебхук с повторными попытками + */ + private CompletableFuture sendWebhook(JsonObject json) { + return CompletableFuture.supplyAsync(() -> { + for (int attempt = 1; attempt <= retryAttempts; attempt++) { + try { + boolean success = sendWebhookSync(json.toString()); + if (success) { + return true; + } + } catch (Exception e) { + plugin.getLogger().warning("Discord webhook attempt " + attempt + " failed: " + e.getMessage()); + } + + // Ждем перед следующей попыткой (кроме последней) + if (attempt < retryAttempts) { + try { + Thread.sleep(retryDelay); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + } + + plugin.getLogger().severe("Failed to send Discord webhook after " + retryAttempts + " attempts"); + return false; + }); + } + + /** + * Синхронная отправка в вебхук + */ + private boolean sendWebhookSync(String jsonPayload) throws IOException { + URL url = new URL(webhookUrl); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + + try { + connection.setRequestMethod("POST"); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setRequestProperty("User-Agent", "LoChat-Discord-Integration/1.0"); + connection.setDoOutput(true); + connection.setConnectTimeout(timeout); + connection.setReadTimeout(timeout); + + // Отправляем JSON + try (OutputStream os = connection.getOutputStream()) { + byte[] input = jsonPayload.getBytes(StandardCharsets.UTF_8); + os.write(input, 0, input.length); + } + + int responseCode = connection.getResponseCode(); + + if (responseCode == 204) { + return true; // Успешная отправка + } else if (responseCode == 429) { + plugin.getLogger().warning("Discord webhook rate limited (429)"); + return false; + } else { + plugin.getLogger().warning("Discord webhook returned code: " + responseCode); + return false; + } + + } finally { + connection.disconnect(); + } + } + + /** + * Проверить валидность вебхука + */ + public boolean isValid() { + return webhookUrl != null && + webhookUrl.startsWith("https://discord.com/api/webhooks/") && + webhookUrl.length() > 50; // Минимальная длина валидного URL + } +} \ No newline at end of file diff --git a/src/main/java/com/loki/lochat/listener/ChatEventListener.java b/src/main/java/com/loki/lochat/listener/ChatEventListener.java index 53e5eb8..6919221 100644 --- a/src/main/java/com/loki/lochat/listener/ChatEventListener.java +++ b/src/main/java/com/loki/lochat/listener/ChatEventListener.java @@ -49,6 +49,11 @@ public class ChatEventListener implements Listener { String filteredMessage = filterResult.filteredMessage(); + // Убеждаемся что ! удален из отображаемого сообщения + if (isGlobal && filteredMessage.startsWith("!")) { + filteredMessage = filteredMessage.substring(1).stripLeading(); + } + event.message(com.loki.lochat.utils.ChatFormatter.parse(filteredMessage)); // Фильтруем получателей для локального чата diff --git a/src/main/java/com/loki/lochat/listener/DiscordEventListener.java b/src/main/java/com/loki/lochat/listener/DiscordEventListener.java new file mode 100644 index 0000000..cb1c478 --- /dev/null +++ b/src/main/java/com/loki/lochat/listener/DiscordEventListener.java @@ -0,0 +1,81 @@ +package com.loki.lochat.listener; + +import com.loki.lochat.integrations.DiscordIntegration; +import io.papermc.paper.event.player.AsyncChatEvent; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +/** + * Слушатель событий для Discord интеграции + */ +public class DiscordEventListener implements Listener { + private final DiscordIntegration discord; + + public DiscordEventListener(DiscordIntegration discord) { + this.discord = discord; + } + + /** + * Обработка сообщений чата для Discord + */ + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onChat(AsyncChatEvent event) { + if (!discord.isEnabled()) return; + + Player player = event.getPlayer(); + String message = PlainTextComponentSerializer.plainText().serialize(event.message()); + + // Определяем тип чата (глобальный если начинается с !) + boolean isGlobal = message.startsWith("!"); + + // Очищаем сообщение от ! если это глобальный чат + if (isGlobal) { + message = message.substring(1).stripLeading(); + } + + discord.sendChatMessage(player, message, isGlobal); + } + + /** + * Обработка входа игрока + */ + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerJoin(PlayerJoinEvent event) { + if (!discord.isEnabled()) return; + + Player player = event.getPlayer(); + discord.sendPlayerJoin(player); + } + + /** + * Обработка выхода игрока + */ + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerQuit(PlayerQuitEvent event) { + if (!discord.isEnabled()) return; + + Player player = event.getPlayer(); + discord.sendPlayerQuit(player); + } + + /** + * Обработка смерти игрока + */ + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerDeath(PlayerDeathEvent event) { + if (!discord.isEnabled()) return; + + Player player = event.getEntity(); + String deathMessage = event.getDeathMessage() != null ? + PlainTextComponentSerializer.plainText().serialize(event.getDeathMessage()) : + "умер"; + + discord.sendPlayerDeath(player, deathMessage); + } +} \ No newline at end of file diff --git a/src/main/resources/config/discord.yml b/src/main/resources/config/discord.yml new file mode 100644 index 0000000..d0ca13b --- /dev/null +++ b/src/main/resources/config/discord.yml @@ -0,0 +1,92 @@ +# ╔══════════════════════════════════════════════════════════════════════════════╗ +# ║ Discord Integration ║ +# ║ Интеграция с Discord ║ +# ╚══════════════════════════════════════════════════════════════════════════════╝ + +# Включить интеграцию с Discord +enabled: true + +# Настройки вебхука +webhook: + # URL вебхука Discord (получить в настройках канала) + url: "https://discord.com/api/webhooks/YOUR_WEBHOOK_ID/YOUR_WEBHOOK_TOKEN" + + # Имя бота в Discord + username: "Minecraft Server" + + # Аватар бота (URL изображения) + avatar_url: "https://mc-heads.net/avatar/minecraft/64" + +# Настройки чата +chat: + # Отправлять сообщения чата в Discord + enabled: true + + # Отправлять только глобальный чат (true) или весь чат (false) + global_only: true + + # Формат сообщения чата + format: "**{player}**: {message}" + + # Цвет embed для сообщений чата (hex без #) + embed_color: "5865F2" + + # Использовать embed или обычное сообщение + use_embed: true + +# Настройки событий игроков +events: + # Отправлять события входа/выхода + enabled: true + + # Настройки входа на сервер + join: + enabled: true + title: "🟢 Игрок зашел на сервер" + description: "**{player}** присоединился к серверу" + color: "00FF00" + thumbnail: "https://mc-heads.net/avatar/{player}/64" + + # Настройки выхода с сервера + quit: + enabled: true + title: "🔴 Игрок покинул сервер" + description: "**{player}** покинул сервер" + color: "FF0000" + thumbnail: "https://mc-heads.net/avatar/{player}/64" + + # Настройки смерти игрока + death: + enabled: true + title: "💀 Смерть игрока" + description: "**{player}** {death_message}" + color: "800080" + thumbnail: "https://mc-heads.net/avatar/{player}/64" + +# Настройки фильтрации +filter: + # Игнорировать сообщения от ботов + ignore_bots: true + + # Минимальная длина сообщения для отправки + min_message_length: 1 + + # Максимальная длина сообщения (обрезать если больше) + max_message_length: 2000 + + # Заменять @everyone и @here на безопасные варианты + sanitize_mentions: true + +# Настройки производительности +performance: + # Асинхронная отправка (рекомендуется) + async: true + + # Таймаут подключения (секунды) + timeout: 10 + + # Повторные попытки при ошибке + retry_attempts: 3 + + # Задержка между попытками (миллисекунды) + retry_delay: 1000 \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 8fccfeb..691d868 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -166,8 +166,9 @@ commands: aliases: [ правила, rule ] discord: - description: Show Discord invite - usage: /discord + description: Discord integration management + usage: /discord [status|reload|test] + permission: lochat.discord.admin aliases: [ дискорд, ds ] website: @@ -442,3 +443,6 @@ permissions: lochat.ping.others: description: Can view other players' ping default: op + lochat.discord.admin: + description: Can manage Discord integration + default: op