feat: persistence util, PM DRY, Discord rate limiter, missing permissions in plugin.yml
This commit is contained in:
parent
dd0fb0f10d
commit
42ebac890c
60 changed files with 418 additions and 374 deletions
|
|
@ -0,0 +1,20 @@
|
|||
package com.loki.lochat.api.service.pm;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface PrivateMessageService {
|
||||
|
||||
void setLastConversation(UUID player, UUID target);
|
||||
|
||||
Optional<UUID> getLastConversation(UUID player);
|
||||
|
||||
void removeConversation(UUID player);
|
||||
|
||||
boolean hasConversation(UUID player);
|
||||
|
||||
void sendPrivateMessage(CommandSender sender, Player target, String message);
|
||||
}
|
||||
|
|
@ -2,11 +2,10 @@ package com.loki.lochat.commands.messaging;
|
|||
|
||||
import com.loki.lochat.LoChat;
|
||||
import com.loki.lochat.api.service.MessagingService;
|
||||
import com.loki.lochat.api.service.pm.PrivateMessageService;
|
||||
import com.loki.lochat.utils.format.ChatFormatter;
|
||||
import com.loki.lochat.utils.player.PlayerUtil;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
|
@ -17,10 +16,12 @@ public class MsgCommand implements CommandExecutor {
|
|||
|
||||
private final LoChat plugin;
|
||||
private final MessagingService messagingService;
|
||||
private final PrivateMessageService pmService;
|
||||
|
||||
public MsgCommand(LoChat plugin) {
|
||||
this.plugin = plugin;
|
||||
this.messagingService = plugin.getServiceRegistry().get(MessagingService.class);
|
||||
this.pmService = plugin.getServiceRegistry().get(PrivateMessageService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -56,24 +57,7 @@ public class MsgCommand implements CommandExecutor {
|
|||
}
|
||||
|
||||
String message = String.join(" ", java.util.Arrays.copyOfRange(args, 1, args.length));
|
||||
sendPm(player, target, message);
|
||||
pmService.sendPrivateMessage(player, target, message);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void sendPm(Player sender, Player target, String message) {
|
||||
sender.sendMessage(ChatFormatter.formatPmSentNew(plugin.getMessageConfig().getPmFormatSent(), sender, target, message));
|
||||
target.sendMessage(ChatFormatter.formatPmReceivedNew(plugin.getMessageConfig().getPmFormatReceived(), sender, target, message));
|
||||
if (plugin.getConfigManager().isPmSoundEnabled()) {
|
||||
Sound sound = PlayerUtil.parseSound(plugin.getConfigManager().getPmSoundType(), null);
|
||||
if (sound != null) {
|
||||
target.playSound(target.getLocation(), sound, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
messagingService.broadcastPM(sender, target, message);
|
||||
messagingService.setLastConversation(sender.getUniqueId(), target.getUniqueId());
|
||||
messagingService.setLastConversation(target.getUniqueId(), sender.getUniqueId());
|
||||
if (plugin.getConfigManager().isPmLogEnabled()) {
|
||||
plugin.getLogger().info("[PM] " + sender.getName() + " -> " + target.getName() + ": " + message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,10 @@ package com.loki.lochat.commands.messaging;
|
|||
|
||||
import com.loki.lochat.LoChat;
|
||||
import com.loki.lochat.api.service.MessagingService;
|
||||
import com.loki.lochat.api.service.pm.PrivateMessageService;
|
||||
import com.loki.lochat.utils.format.ChatFormatter;
|
||||
import com.loki.lochat.utils.player.PlayerUtil;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
|
@ -20,10 +19,12 @@ public class ReplyCommand implements CommandExecutor {
|
|||
|
||||
private final LoChat plugin;
|
||||
private final MessagingService messagingService;
|
||||
private final PrivateMessageService pmService;
|
||||
|
||||
public ReplyCommand(LoChat plugin) {
|
||||
this.plugin = plugin;
|
||||
this.messagingService = plugin.getServiceRegistry().get(MessagingService.class);
|
||||
this.pmService = plugin.getServiceRegistry().get(PrivateMessageService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -57,19 +58,7 @@ public class ReplyCommand implements CommandExecutor {
|
|||
}
|
||||
|
||||
String message = String.join(" ", args);
|
||||
player.sendMessage(ChatFormatter.formatPmSentNew(plugin.getMessageConfig().getPmFormatSent(), player, target, message));
|
||||
target.sendMessage(ChatFormatter.formatPmReceivedNew(plugin.getMessageConfig().getPmFormatReceived(), player, target, message));
|
||||
if (plugin.getConfigManager().isPmSoundEnabled()) {
|
||||
Sound sound = PlayerUtil.parseSound(plugin.getConfigManager().getPmSoundType(), null);
|
||||
if (sound != null) {
|
||||
target.playSound(target.getLocation(), sound, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
messagingService.broadcastPM(player, target, message);
|
||||
messagingService.setLastConversation(target.getUniqueId(), player.getUniqueId());
|
||||
if (plugin.getConfigManager().isPmLogEnabled()) {
|
||||
plugin.getLogger().info("[PM] " + player.getName() + " -> " + target.getName() + ": " + message);
|
||||
}
|
||||
pmService.sendPrivateMessage(player, target, message);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.loki.lochat.api.service.MuteService;
|
|||
import com.loki.lochat.config.MessageConfig;
|
||||
import com.loki.lochat.core.service.MessagingServiceImpl;
|
||||
import com.loki.lochat.core.service.MuteServiceImpl;
|
||||
import com.loki.lochat.core.service.messaging.PrivateMessageServiceImpl;
|
||||
import com.loki.lochat.core.service.mute.MuteDataStorage;
|
||||
import com.loki.lochat.core.service.mute.MuteHistoryManager;
|
||||
import com.loki.lochat.core.service.mute.strategies.ChatMuteStrategy;
|
||||
|
|
@ -50,7 +51,8 @@ public class ServiceFactory {
|
|||
/**
|
||||
* Создать MessagingService со всеми зависимостями
|
||||
*/
|
||||
public static MessagingService createMessagingService(JavaPlugin plugin, MessageConfig messageConfig) {
|
||||
return new MessagingServiceImpl(plugin, messageConfig);
|
||||
public static MessagingService createMessagingService(
|
||||
JavaPlugin plugin, MessageConfig messageConfig, PrivateMessageServiceImpl pmService) {
|
||||
return new MessagingServiceImpl(plugin, messageConfig, pmService);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.loki.lochat.core.registry;
|
||||
|
||||
import com.loki.lochat.LoChat;
|
||||
import com.loki.lochat.api.service.ChatService;
|
||||
import com.loki.lochat.api.service.MentionService;
|
||||
import com.loki.lochat.api.service.MessageService;
|
||||
|
|
@ -8,6 +9,7 @@ import com.loki.lochat.api.service.MuteService;
|
|||
import com.loki.lochat.api.service.NickService;
|
||||
import com.loki.lochat.api.service.PlayerService;
|
||||
import com.loki.lochat.api.service.PunishmentService;
|
||||
import com.loki.lochat.api.service.pm.PrivateMessageService;
|
||||
import com.loki.lochat.config.ConfigManager;
|
||||
import com.loki.lochat.config.MessageConfig;
|
||||
import com.loki.lochat.core.factory.ServiceFactory;
|
||||
|
|
@ -17,6 +19,7 @@ import com.loki.lochat.core.service.MessageServiceImpl;
|
|||
import com.loki.lochat.core.service.NickServiceImpl;
|
||||
import com.loki.lochat.core.service.PlayerServiceImpl;
|
||||
import com.loki.lochat.core.service.PunishmentServiceImpl;
|
||||
import com.loki.lochat.core.service.messaging.PrivateMessageServiceImpl;
|
||||
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
|
|
@ -48,7 +51,10 @@ public class ServiceRegistry {
|
|||
register(MessageService.class, new MessageServiceImpl(plugin, this));
|
||||
|
||||
// ✅ NEW: Объединённый MessagingService (PM + Spy + Ignore) через фабрику
|
||||
MessagingService messagingService = ServiceFactory.createMessagingService(plugin, messageConfig);
|
||||
PrivateMessageServiceImpl pmService = new PrivateMessageServiceImpl((LoChat) plugin);
|
||||
MessagingService messagingService = ServiceFactory.createMessagingService(plugin, messageConfig, pmService);
|
||||
pmService.init(messagingService);
|
||||
register(PrivateMessageService.class, pmService);
|
||||
register(MessagingService.class, messagingService);
|
||||
|
||||
register(MentionService.class, new MentionServiceImpl(configManager));
|
||||
|
|
|
|||
|
|
@ -4,18 +4,18 @@ import com.loki.lochat.api.service.ChatService;
|
|||
import com.loki.lochat.core.registry.ServiceRegistry;
|
||||
import com.loki.lochat.renderer.EnhancedChatRenderer;
|
||||
import com.loki.lochat.utils.format.ChatFormatter;
|
||||
import com.loki.lochat.utils.persistence.FilePersistence;
|
||||
import com.loki.lochat.utils.platform.FoliaUtil;
|
||||
import com.loki.lochat.utils.player.DistanceUtil;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
|
@ -28,11 +28,9 @@ public class ChatServiceImpl implements ChatService {
|
|||
|
||||
private final JavaPlugin plugin;
|
||||
private final Set<UUID> globalChatDisabled = ConcurrentHashMap.newKeySet();
|
||||
private final File playersFile;
|
||||
|
||||
public ChatServiceImpl(JavaPlugin plugin, ServiceRegistry registry) {
|
||||
this.plugin = plugin;
|
||||
this.playersFile = new File(plugin.getDataFolder(), "data/players.yml");
|
||||
loadDisabled();
|
||||
}
|
||||
|
||||
|
|
@ -99,10 +97,7 @@ public class ChatServiceImpl implements ChatService {
|
|||
// ──────────────────────────────────────────────────────────────────────────
|
||||
|
||||
private void loadDisabled() {
|
||||
if (!playersFile.exists()) {
|
||||
return;
|
||||
}
|
||||
YamlConfiguration cfg = YamlConfiguration.loadConfiguration(playersFile);
|
||||
FileConfiguration cfg = FilePersistence.loadYaml(plugin, "data/players.yml");
|
||||
for (String uuidStr : cfg.getStringList("global-chat-disabled")) {
|
||||
try {
|
||||
globalChatDisabled.add(UUID.fromString(uuidStr));
|
||||
|
|
@ -112,25 +107,10 @@ public class ChatServiceImpl implements ChatService {
|
|||
}
|
||||
|
||||
private void saveDisabled() {
|
||||
try {
|
||||
ensureDir();
|
||||
YamlConfiguration cfg = playersFile.exists()
|
||||
? YamlConfiguration.loadConfiguration(playersFile)
|
||||
: new YamlConfiguration();
|
||||
|
||||
cfg.set("global-chat-disabled",
|
||||
globalChatDisabled.stream().map(UUID::toString).toList());
|
||||
cfg.save(playersFile);
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("[LoChat] Failed to save players.yml: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureDir() {
|
||||
File dir = playersFile.getParentFile();
|
||||
if (!dir.exists()) {
|
||||
dir.mkdirs();
|
||||
}
|
||||
YamlConfiguration cfg = new YamlConfiguration();
|
||||
cfg.set("global-chat-disabled",
|
||||
globalChatDisabled.stream().map(UUID::toString).toList());
|
||||
FilePersistence.saveYaml(plugin, "data/players.yml", cfg);
|
||||
}
|
||||
|
||||
private Component toComponent(Object message) {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.loki.lochat.core.service;
|
|||
import com.loki.lochat.api.service.MessagingService;
|
||||
import com.loki.lochat.config.MessageConfig;
|
||||
import com.loki.lochat.core.service.messaging.IgnoreService;
|
||||
import com.loki.lochat.core.service.messaging.PrivateMessageService;
|
||||
import com.loki.lochat.core.service.messaging.PrivateMessageServiceImpl;
|
||||
import com.loki.lochat.core.service.messaging.SpyService;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
|
@ -21,12 +21,12 @@ import java.util.UUID;
|
|||
*/
|
||||
public class MessagingServiceImpl implements MessagingService {
|
||||
|
||||
private final PrivateMessageService pmService;
|
||||
private final PrivateMessageServiceImpl pmService;
|
||||
private final SpyService spyService;
|
||||
private final IgnoreService ignoreService;
|
||||
|
||||
public MessagingServiceImpl(JavaPlugin plugin, MessageConfig messageConfig) {
|
||||
this.pmService = new PrivateMessageService();
|
||||
public MessagingServiceImpl(JavaPlugin plugin, MessageConfig messageConfig, PrivateMessageServiceImpl pmService) {
|
||||
this.pmService = pmService;
|
||||
this.spyService = new SpyService(messageConfig);
|
||||
this.ignoreService = new IgnoreService(plugin);
|
||||
this.ignoreService.init();
|
||||
|
|
|
|||
|
|
@ -3,18 +3,16 @@ package com.loki.lochat.core.service;
|
|||
import com.loki.lochat.api.service.NickService;
|
||||
import com.loki.lochat.config.RatConfig;
|
||||
import com.loki.lochat.utils.format.ChatFormatter;
|
||||
import com.loki.lochat.utils.persistence.FilePersistence;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
|
@ -26,26 +24,15 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
public class NickServiceImpl implements NickService {
|
||||
private final JavaPlugin plugin;
|
||||
private final Map<UUID, String> nicknames = new ConcurrentHashMap<>();
|
||||
private final File dataFile;
|
||||
private FileConfiguration data;
|
||||
|
||||
public NickServiceImpl(JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.dataFile = new File(plugin.getDataFolder(), "nicknames.yml");
|
||||
load();
|
||||
}
|
||||
|
||||
private void load() {
|
||||
if (!dataFile.exists()) {
|
||||
try {
|
||||
dataFile.getParentFile().mkdirs();
|
||||
dataFile.createNewFile();
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("Не удалось создать nicknames.yml: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
data = YamlConfiguration.loadConfiguration(dataFile);
|
||||
data = FilePersistence.loadYaml(plugin, "nicknames.yml");
|
||||
|
||||
// Загружаем ники
|
||||
if (data.contains("nicknames")) {
|
||||
|
|
@ -67,11 +54,7 @@ public class NickServiceImpl implements NickService {
|
|||
data.set("nicknames." + entry.getKey().toString(), entry.getValue());
|
||||
}
|
||||
|
||||
try {
|
||||
data.save(dataFile);
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("Не удалось сохранить nicknames.yml: " + e.getMessage());
|
||||
}
|
||||
FilePersistence.saveYaml(plugin, "nicknames.yml", data);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
package com.loki.lochat.core.service;
|
||||
|
||||
import com.loki.lochat.api.service.PlayerService;
|
||||
import com.loki.lochat.utils.persistence.FilePersistence;
|
||||
import com.loki.lochat.utils.platform.FoliaUtil;
|
||||
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
|
@ -32,12 +31,10 @@ public class PlayerServiceImpl implements PlayerService {
|
|||
private final AtomicLong pmCount = new AtomicLong(0);
|
||||
private final AtomicLong totalMessages = new AtomicLong(0);
|
||||
|
||||
private final File statsFile;
|
||||
private YamlConfiguration stats;
|
||||
|
||||
private FileConfiguration stats;
|
||||
|
||||
public PlayerServiceImpl(JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.statsFile = new File(plugin.getDataFolder(), "data/statistics.yml");
|
||||
load();
|
||||
}
|
||||
|
||||
|
|
@ -116,51 +113,33 @@ public class PlayerServiceImpl implements PlayerService {
|
|||
|
||||
@Override
|
||||
public void saveAll() {
|
||||
try {
|
||||
ensureDir();
|
||||
// Обновляем глобальные счётчики
|
||||
stats.set("global.total-messages",
|
||||
stats.getLong("global.total-messages", 0) + totalMessages.getAndSet(0));
|
||||
stats.set("by-type.global-chat",
|
||||
stats.getLong("by-type.global-chat", 0) + globalChatCount.getAndSet(0));
|
||||
stats.set("by-type.local-chat",
|
||||
stats.getLong("by-type.local-chat", 0) + localChatCount.getAndSet(0));
|
||||
stats.set("by-type.private-messages",
|
||||
stats.getLong("by-type.private-messages", 0) + pmCount.getAndSet(0));
|
||||
stats.set("last-updated", System.currentTimeMillis());
|
||||
stats.save(statsFile);
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("[LoChat] Failed to save statistics: " + e.getMessage());
|
||||
}
|
||||
stats.set("global.total-messages",
|
||||
stats.getLong("global.total-messages", 0) + totalMessages.getAndSet(0));
|
||||
stats.set("by-type.global-chat",
|
||||
stats.getLong("by-type.global-chat", 0) + globalChatCount.getAndSet(0));
|
||||
stats.set("by-type.local-chat",
|
||||
stats.getLong("by-type.local-chat", 0) + localChatCount.getAndSet(0));
|
||||
stats.set("by-type.private-messages",
|
||||
stats.getLong("by-type.private-messages", 0) + pmCount.getAndSet(0));
|
||||
stats.set("last-updated", System.currentTimeMillis());
|
||||
FilePersistence.saveYaml(plugin, "data/statistics.yml", stats);
|
||||
}
|
||||
|
||||
// ========== Private Methods ==========
|
||||
|
||||
private void persistPlayerMessages(UUID uuid, long count) {
|
||||
try {
|
||||
String path = "top-players.by-messages." + uuid;
|
||||
long prev = stats.getLong(path + ".count", 0);
|
||||
stats.set(path + ".count", prev + count);
|
||||
stats.set(path + ".last-updated", System.currentTimeMillis());
|
||||
stats.save(statsFile);
|
||||
playerMessages.remove(uuid);
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("[LoChat] Failed to persist player stats: " + e.getMessage());
|
||||
}
|
||||
String path = "top-players.by-messages." + uuid;
|
||||
long prev = stats.getLong(path + ".count", 0);
|
||||
stats.set(path + ".count", prev + count);
|
||||
stats.set(path + ".last-updated", System.currentTimeMillis());
|
||||
FilePersistence.saveYaml(plugin, "data/statistics.yml", stats);
|
||||
playerMessages.remove(uuid);
|
||||
}
|
||||
|
||||
private void load() {
|
||||
ensureDir();
|
||||
if (!statsFile.exists()) {
|
||||
if (!FilePersistence.getFile(plugin, "data/statistics.yml").exists()) {
|
||||
plugin.saveResource("data/statistics.yml", false);
|
||||
}
|
||||
stats = YamlConfiguration.loadConfiguration(statsFile);
|
||||
}
|
||||
|
||||
private void ensureDir() {
|
||||
File dir = statsFile.getParentFile();
|
||||
if (!dir.exists()) {
|
||||
dir.mkdirs();
|
||||
}
|
||||
stats = FilePersistence.loadYaml(plugin, "data/statistics.yml");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,28 +2,19 @@ package com.loki.lochat.core.service;
|
|||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.loki.lochat.api.service.PunishmentService;
|
||||
import com.loki.lochat.config.MessagesConfig;
|
||||
import com.loki.lochat.data.model.BanRecord;
|
||||
import com.loki.lochat.data.model.WarnEntry;
|
||||
import com.loki.lochat.utils.format.ChatFormatter;
|
||||
import com.loki.lochat.utils.format.TimeFormatter;
|
||||
import com.loki.lochat.utils.persistence.FilePersistence;
|
||||
import com.loki.lochat.utils.platform.FoliaUtil;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
|
@ -39,7 +30,6 @@ public class PunishmentServiceImpl implements PunishmentService {
|
|||
|
||||
private final JavaPlugin plugin;
|
||||
private final MessagesConfig messages;
|
||||
private final File dataFile;
|
||||
private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
||||
|
||||
private final Map<UUID, List<WarnEntry>> warns = new ConcurrentHashMap<>();
|
||||
|
|
@ -48,7 +38,6 @@ public class PunishmentServiceImpl implements PunishmentService {
|
|||
public PunishmentServiceImpl(JavaPlugin plugin, MessagesConfig messagesConfig) {
|
||||
this.plugin = plugin;
|
||||
this.messages = messagesConfig;
|
||||
this.dataFile = new File(plugin.getDataFolder(), "punishments.json");
|
||||
load();
|
||||
}
|
||||
|
||||
|
|
@ -141,26 +130,17 @@ public class PunishmentServiceImpl implements PunishmentService {
|
|||
|
||||
@Override
|
||||
public void save() {
|
||||
try {
|
||||
if (!dataFile.getParentFile().exists() && !dataFile.getParentFile().mkdirs()) {
|
||||
plugin.getLogger().warning("Could not create data folder for punishments");
|
||||
PunishmentSnapshot snap = new PunishmentSnapshot();
|
||||
for (Map.Entry<UUID, List<WarnEntry>> e : warns.entrySet()) {
|
||||
List<WarnEntry> list = e.getValue();
|
||||
synchronized (list) {
|
||||
snap.getWarns().put(e.getKey().toString(), new ArrayList<>(list));
|
||||
}
|
||||
PunishmentSnapshot snap = new PunishmentSnapshot();
|
||||
for (Map.Entry<UUID, List<WarnEntry>> e : warns.entrySet()) {
|
||||
List<WarnEntry> list = e.getValue();
|
||||
synchronized (list) {
|
||||
snap.getWarns().put(e.getKey().toString(), new ArrayList<>(list));
|
||||
}
|
||||
}
|
||||
for (Map.Entry<UUID, BanRecord> e : bans.entrySet()) {
|
||||
snap.getBans().put(e.getKey().toString(), e.getValue());
|
||||
}
|
||||
try (Writer w = new OutputStreamWriter(new FileOutputStream(dataFile), java.nio.charset.StandardCharsets.UTF_8)) {
|
||||
gson.toJson(snap, w);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("Failed to save punishments: " + e.getMessage());
|
||||
}
|
||||
for (Map.Entry<UUID, BanRecord> e : bans.entrySet()) {
|
||||
snap.getBans().put(e.getKey().toString(), e.getValue());
|
||||
}
|
||||
FilePersistence.saveJson(plugin, "punishments.json", snap, gson);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -175,41 +155,33 @@ public class PunishmentServiceImpl implements PunishmentService {
|
|||
}
|
||||
|
||||
private void load() {
|
||||
if (!dataFile.exists()) {
|
||||
PunishmentSnapshot snap = FilePersistence.loadJson(plugin, "punishments.json", PunishmentSnapshot.class, gson);
|
||||
if (snap == null) {
|
||||
return;
|
||||
}
|
||||
try (Reader r = new InputStreamReader(new FileInputStream(dataFile), java.nio.charset.StandardCharsets.UTF_8)) {
|
||||
Type type = new TypeToken<PunishmentSnapshot>() { }.getType();
|
||||
PunishmentSnapshot snap = gson.fromJson(r, type);
|
||||
if (snap == null) {
|
||||
return;
|
||||
}
|
||||
if (snap.getWarns() != null) {
|
||||
for (Map.Entry<String, List<WarnEntry>> e : snap.getWarns().entrySet()) {
|
||||
try {
|
||||
warns.put(UUID.fromString(e.getKey()), Collections.synchronizedList(new ArrayList<>(e.getValue())));
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
if (snap.getWarns() != null) {
|
||||
for (Map.Entry<String, List<WarnEntry>> e : snap.getWarns().entrySet()) {
|
||||
try {
|
||||
warns.put(UUID.fromString(e.getKey()), Collections.synchronizedList(new ArrayList<>(e.getValue())));
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
}
|
||||
if (snap.getBans() != null) {
|
||||
for (Map.Entry<String, BanRecord> e : snap.getBans().entrySet()) {
|
||||
try {
|
||||
UUID id = UUID.fromString(e.getKey());
|
||||
BanRecord br = e.getValue();
|
||||
if (br != null) {
|
||||
br.uuid = id;
|
||||
if (!br.isPermanent() && br.isExpired()) {
|
||||
continue;
|
||||
}
|
||||
bans.put(id, br);
|
||||
}
|
||||
if (snap.getBans() != null) {
|
||||
for (Map.Entry<String, BanRecord> e : snap.getBans().entrySet()) {
|
||||
try {
|
||||
UUID id = UUID.fromString(e.getKey());
|
||||
BanRecord br = e.getValue();
|
||||
if (br != null) {
|
||||
br.uuid = id;
|
||||
if (!br.isPermanent() && br.isExpired()) {
|
||||
continue;
|
||||
}
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
bans.put(id, br);
|
||||
}
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("Failed to load punishments: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
package com.loki.lochat.core.service.messaging;
|
||||
|
||||
import com.loki.lochat.utils.persistence.FilePersistence;
|
||||
import com.loki.lochat.utils.platform.FoliaUtil;
|
||||
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
|
@ -23,11 +23,9 @@ public class IgnoreService {
|
|||
|
||||
private final JavaPlugin plugin;
|
||||
private final Map<UUID, Set<UUID>> ignoreMap = new ConcurrentHashMap<>();
|
||||
private final File ignoreFile;
|
||||
|
||||
public IgnoreService(JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.ignoreFile = new File(plugin.getDataFolder(), "ignores.yml");
|
||||
}
|
||||
|
||||
public void init() {
|
||||
|
|
@ -75,11 +73,7 @@ public class IgnoreService {
|
|||
}
|
||||
|
||||
public void load() {
|
||||
if (!ignoreFile.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
YamlConfiguration config = YamlConfiguration.loadConfiguration(ignoreFile);
|
||||
FileConfiguration config = FilePersistence.loadYaml(plugin, "ignores.yml");
|
||||
for (String key : config.getKeys(false)) {
|
||||
try {
|
||||
UUID uuid = UUID.fromString(key);
|
||||
|
|
@ -114,11 +108,7 @@ public class IgnoreService {
|
|||
config.set(entry.getKey().toString(), ignoredList);
|
||||
}
|
||||
|
||||
try {
|
||||
config.save(ignoreFile);
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().severe("Не удалось сохранить игноры: " + e.getMessage());
|
||||
}
|
||||
FilePersistence.saveYaml(plugin, "ignores.yml", config);
|
||||
}
|
||||
|
||||
private void saveAsync() {
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
package com.loki.lochat.core.service.messaging;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Сервис личных сообщений (PM)
|
||||
* Отслеживает последние диалоги между игроками
|
||||
*/
|
||||
public class PrivateMessageService {
|
||||
|
||||
private final Map<UUID, UUID> lastConversation = new ConcurrentHashMap<>();
|
||||
|
||||
public void setLastConversation(UUID player, UUID target) {
|
||||
lastConversation.put(player, target);
|
||||
}
|
||||
|
||||
public Optional<UUID> getLastConversation(UUID player) {
|
||||
return Optional.ofNullable(lastConversation.get(player));
|
||||
}
|
||||
|
||||
public void removeConversation(UUID player) {
|
||||
lastConversation.remove(player);
|
||||
}
|
||||
|
||||
public boolean hasConversation(UUID player) {
|
||||
return lastConversation.containsKey(player);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
package com.loki.lochat.core.service.messaging;
|
||||
|
||||
import com.loki.lochat.LoChat;
|
||||
import com.loki.lochat.api.service.MessagingService;
|
||||
import com.loki.lochat.api.service.pm.PrivateMessageService;
|
||||
import com.loki.lochat.utils.format.ChatFormatter;
|
||||
import com.loki.lochat.utils.player.PlayerUtil;
|
||||
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Сервис личных сообщений (PM)
|
||||
* Отслеживает последние диалоги между игроками
|
||||
*/
|
||||
public class PrivateMessageServiceImpl implements PrivateMessageService {
|
||||
|
||||
private final Map<UUID, UUID> lastConversation = new ConcurrentHashMap<>();
|
||||
private final LoChat plugin;
|
||||
private MessagingService messagingService;
|
||||
|
||||
public PrivateMessageServiceImpl(LoChat plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public void init(MessagingService messagingService) {
|
||||
this.messagingService = messagingService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastConversation(UUID player, UUID target) {
|
||||
lastConversation.put(player, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<UUID> getLastConversation(UUID player) {
|
||||
return Optional.ofNullable(lastConversation.get(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeConversation(UUID player) {
|
||||
lastConversation.remove(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasConversation(UUID player) {
|
||||
return lastConversation.containsKey(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPrivateMessage(CommandSender sender, Player target, String message) {
|
||||
Player playerSender = (Player) sender;
|
||||
sender.sendMessage(ChatFormatter.formatPmSentNew(
|
||||
plugin.getMessageConfig().getPmFormatSent(), playerSender, target, message));
|
||||
target.sendMessage(ChatFormatter.formatPmReceivedNew(
|
||||
plugin.getMessageConfig().getPmFormatReceived(), playerSender, target, message));
|
||||
if (plugin.getConfigManager().isPmSoundEnabled()) {
|
||||
Sound sound = PlayerUtil.parseSound(plugin.getConfigManager().getPmSoundType(), null);
|
||||
if (sound != null) {
|
||||
target.playSound(target.getLocation(), sound, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
messagingService.broadcastPM(playerSender, target, message);
|
||||
messagingService.setLastConversation(playerSender.getUniqueId(), target.getUniqueId());
|
||||
messagingService.setLastConversation(target.getUniqueId(), playerSender.getUniqueId());
|
||||
if (plugin.getConfigManager().isPmLogEnabled()) {
|
||||
plugin.getLogger().info("[PM] " + playerSender.getName() + " -> " + target.getName() + ": " + message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.loki.lochat.integrations;
|
|||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.loki.lochat.integrations.discord.DiscordRateLimiter;
|
||||
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
|
|
@ -29,6 +30,7 @@ public class DiscordWebhook {
|
|||
private final int timeout;
|
||||
private final int retryAttempts;
|
||||
private final long retryDelay;
|
||||
private final DiscordRateLimiter rateLimiter;
|
||||
|
||||
// Выделенный пул — не засоряем ForkJoinPool.commonPool()
|
||||
private final ExecutorService executor = Executors.newSingleThreadExecutor(
|
||||
|
|
@ -39,8 +41,10 @@ public class DiscordWebhook {
|
|||
}
|
||||
);
|
||||
|
||||
@SuppressWarnings("checkstyle:ParameterNumber")
|
||||
public DiscordWebhook(JavaPlugin plugin, String webhookUrl, String username, String avatarUrl,
|
||||
int timeout, int retryAttempts, long retryDelay) {
|
||||
int timeout, int retryAttempts, long retryDelay,
|
||||
double maxRequestsPerSecond, int rateLimitBurst) {
|
||||
this.plugin = plugin;
|
||||
this.webhookUrl = webhookUrl != null ? webhookUrl.trim() : null;
|
||||
this.username = username;
|
||||
|
|
@ -48,6 +52,7 @@ public class DiscordWebhook {
|
|||
this.timeout = timeout * 1000;
|
||||
this.retryAttempts = retryAttempts;
|
||||
this.retryDelay = retryDelay;
|
||||
this.rateLimiter = new DiscordRateLimiter(maxRequestsPerSecond, rateLimitBurst);
|
||||
}
|
||||
|
||||
// ── Публичные методы отправки ─────────────────────────────────────────────
|
||||
|
|
@ -114,6 +119,7 @@ public class DiscordWebhook {
|
|||
private CompletableFuture<Boolean> sendWebhook(JsonObject json) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
for (int attempt = 1; attempt <= retryAttempts; attempt++) {
|
||||
rateLimiter.waitIfNeeded();
|
||||
try {
|
||||
if (sendWebhookSync(json.toString())) {
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -70,6 +70,14 @@ public class DiscordConfig {
|
|||
public boolean isAsync() {
|
||||
return config.getBoolean("performance.async", true);
|
||||
}
|
||||
|
||||
public double getMaxRequestsPerSecond() {
|
||||
return config.getDouble("performance.max-requests-per-second", 5.0);
|
||||
}
|
||||
|
||||
public int getRateLimitBurst() {
|
||||
return config.getInt("performance.rate-limit-burst", 10);
|
||||
}
|
||||
|
||||
public boolean isChatEnabled() {
|
||||
return config.getBoolean("chat.enabled", true);
|
||||
|
|
|
|||
|
|
@ -29,7 +29,9 @@ public class DiscordMessageService {
|
|||
getAvatarUrl(),
|
||||
config.getTimeout(),
|
||||
config.getRetryAttempts(),
|
||||
config.getRetryDelay()
|
||||
config.getRetryDelay(),
|
||||
config.getMaxRequestsPerSecond(),
|
||||
config.getRateLimitBurst()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
package com.loki.lochat.integrations.discord;
|
||||
|
||||
public class DiscordRateLimiter {
|
||||
|
||||
private final double refillRate;
|
||||
private final double capacity;
|
||||
private double tokens;
|
||||
private long lastRefillTime;
|
||||
|
||||
public DiscordRateLimiter(double maxRequestsPerSecond, double burst) {
|
||||
this.refillRate = maxRequestsPerSecond;
|
||||
this.capacity = burst;
|
||||
this.tokens = burst;
|
||||
this.lastRefillTime = System.nanoTime();
|
||||
}
|
||||
|
||||
public synchronized boolean tryAcquire() {
|
||||
refill();
|
||||
if (tokens >= 1.0) {
|
||||
tokens -= 1.0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void waitIfNeeded() {
|
||||
long sleepMs;
|
||||
while (true) {
|
||||
synchronized (this) {
|
||||
refill();
|
||||
if (tokens >= 1.0) {
|
||||
tokens -= 1.0;
|
||||
return;
|
||||
}
|
||||
double waitTimeMs = ((1.0 - tokens) / refillRate) * 1000.0;
|
||||
sleepMs = Math.min((long) Math.ceil(waitTimeMs), 100);
|
||||
}
|
||||
try {
|
||||
Thread.sleep(Math.max(sleepMs, 1));
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void refill() {
|
||||
long now = System.nanoTime();
|
||||
double elapsed = (now - lastRefillTime) / 1_000_000_000.0;
|
||||
tokens = Math.min(capacity, tokens + elapsed * refillRate);
|
||||
lastRefillTime = now;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package com.loki.lochat.utils.persistence;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public final class FilePersistence {
|
||||
|
||||
private FilePersistence() {}
|
||||
|
||||
public static File getFile(JavaPlugin plugin, String name) {
|
||||
return new File(plugin.getDataFolder(), name);
|
||||
}
|
||||
|
||||
public static void ensureDataFolder(JavaPlugin plugin) {
|
||||
plugin.getDataFolder().mkdirs();
|
||||
}
|
||||
|
||||
public static FileConfiguration loadYaml(JavaPlugin plugin, String name) {
|
||||
File file = getFile(plugin, name);
|
||||
return file.exists() ? YamlConfiguration.loadConfiguration(file) : new YamlConfiguration();
|
||||
}
|
||||
|
||||
public static void saveYaml(JavaPlugin plugin, String name, FileConfiguration config) {
|
||||
try {
|
||||
File file = getFile(plugin, name);
|
||||
file.getParentFile().mkdirs();
|
||||
config.save(file);
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("Failed to save " + name + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T loadJson(JavaPlugin plugin, String name, Class<T> clazz, Gson gson) {
|
||||
File file = getFile(plugin, name);
|
||||
if (!file.exists()) {
|
||||
return null;
|
||||
}
|
||||
try (Reader reader = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)) {
|
||||
return gson.fromJson(reader, clazz);
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("Failed to load " + name + ": " + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void saveJson(JavaPlugin plugin, String name, Object data, Gson gson) {
|
||||
try {
|
||||
File file = getFile(plugin, name);
|
||||
file.getParentFile().mkdirs();
|
||||
try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) {
|
||||
gson.toJson(data, writer);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("Failed to save " + name + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue