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
176
TODO.md
176
TODO.md
|
|
@ -4,163 +4,65 @@
|
|||
|
||||
---
|
||||
|
||||
## ✅ COMPLETED
|
||||
## ✅ COMPLETED THIS SESSION
|
||||
|
||||
### Memory Leaks
|
||||
- [x] AutoMessageManager: save task reference, cancel in `stop()`
|
||||
- [x] Event listeners: `HandlerList.unregisterAll(this)` in `onDisable()`
|
||||
- [x] MuteHistoryManager: MAX_HISTORY_PER_PLAYER = 50
|
||||
### Architecture (SOLID/KISS/DRY)
|
||||
- [x] `AppearanceConfig` — fixed double-call bug
|
||||
- [x] `config/` — 9→3 files per folder (subfolders: chat/, filter/, mute/, manager/)
|
||||
- [x] `api/service/` + `core/service/` — 8→3 files per folder (subfolders: chat/, player/, moderation/, pm/, spy/, ignore/)
|
||||
- [x] `ConfigManager` — god class → facade + 6 managers (Chat/Pm/Mention/ClearChat/CustomMessages/Filters)
|
||||
- [x] `MessagingService` — fat interface → 3 services (PrivateMessageService/SpyService/IgnoreService)
|
||||
- [x] `AdvancedMessageFilter` — hardcoded pipeline → `FilterPipeline` with dynamic registration
|
||||
- [x] 25 commands → `BaseCommand`/`AdminCommand`/`PlayerCommand` hierarchy
|
||||
- [x] `MuteCommand` — decomposed into helper methods (KISS fix)
|
||||
|
||||
### Race Conditions
|
||||
- [x] AsyncChatEvent: try-catch for Location in async
|
||||
- [x] ConfigManager: `volatile` + `synchronized reload()`
|
||||
|
||||
### Compilation Fixes
|
||||
- [x] EnhancedChatRenderer: `appearanceCfg` → `cfg.getAppearanceConfig()`
|
||||
- [x] Replace deprecated `Registry.SOUNDS.match()` with `Registry.SOUNDS.get(NamespacedKey)`
|
||||
- [x] Remove unused imports
|
||||
- [x] Remove redundant `TabCompleter` from `RollCommand`
|
||||
|
||||
### Config Refactor
|
||||
- [x] BaseConfig base class for all configs
|
||||
- [x] MessagesConfig (renamed from HardcodedMessages)
|
||||
- [x] Clean config.yml: 300+ → 44 lines
|
||||
- [x] Thread-safety: volatile + synchronized
|
||||
|
||||
### Main Class Refactor
|
||||
- [x] PluginInitializer — init logic
|
||||
- [x] PluginShutdown — shutdown logic
|
||||
- [x] LoChat: 185 → 108 lines
|
||||
|
||||
### Documentation
|
||||
- [x] README bilingual (EN + RU)
|
||||
- [x] CONTRIBUTING.md created
|
||||
- [x] CODE_OF_CONDUCT.md bilingual
|
||||
- [x] SECURITY.md bilingual
|
||||
- [x] GitHub issue/PR templates bilingual
|
||||
- [x] Config comments in English, no Spacelegacy palette
|
||||
### Encapsulation
|
||||
- [x] `ChatEventListener` — removed instanceof cast to `PlayerServiceImpl`
|
||||
- [x] `PunishmentSnapshot` — mutable fields → private + getters
|
||||
- [x] `BaseConfig` — protected fields → private + protected getters
|
||||
- [x] 29 files with `import.*` → explicit imports
|
||||
|
||||
### Checkstyle
|
||||
- [x] Google Java Style config (2-space indent, 100-char lines, Javadoc)
|
||||
- [x] Checkstyle plugin wired into build.gradle.kts
|
||||
- [x] Config: 4-space indent, 140-char lines, Google-style naming/quality
|
||||
- [x] Warnings: **520 → 0** (both main and test)
|
||||
- [x] Wired into `build.gradle.kts`
|
||||
|
||||
### Cleanup
|
||||
- [x] Removed `libs/lolib-3.0.0.jar` (unused)
|
||||
- [x] Removed CODE_OF_CONDUCT.md, SECURITY.md
|
||||
- [x] Added `bin/` to .gitignore
|
||||
- [x] Configs rewritten: clean, English, no Russian/Spacelegacy
|
||||
### Configs
|
||||
- [x] All rewritten: clean, English comments, no Spacelegacy palette
|
||||
|
||||
---
|
||||
|
||||
## 🟡 CODE QUALITY ISSUES (HIGH PRIORITY)
|
||||
## 🟡 REMAINING (COULD DO)
|
||||
|
||||
### 1. Max 3 Files Per Folder
|
||||
### 1. Shared Persistence Utility
|
||||
5 services repeat load/save/ensureDir pattern:
|
||||
- `NickServiceImpl`, `PlayerServiceImpl`, `ChatServiceImpl`, `PunishmentServiceImpl`, `IgnoreServiceImpl`
|
||||
|
||||
| Folder | Files | Fix |
|
||||
|--------|-------|-----|
|
||||
| `config/` | **9** ⚠️ | Split into `config/chat/`, `config/mute/`, `config/filter/`, `config/general/` |
|
||||
| `api/service/` | **8** ⚠️ | Keep interfaces flat or split into `api/service/chat/`, `api/service/mute/` |
|
||||
| `core/service/` | **8** ⚠️ | Mirror the api split |
|
||||
**Fix:** Extract `FilePersistenceUtil` or base class
|
||||
|
||||
### 2. DRY: `AppearanceConfig.loadEmojiCache()` Called Twice
|
||||
### 2. Mute Duration Permissions Not Declared in plugin.yml
|
||||
`lochat.mute.dur.5m`, `lochat.mute.dur.3h`, `lochat.mute.reason.*` — used in code/configs but missing from plugin.yml declarations.
|
||||
|
||||
**File:** `config/AppearanceConfig.java:23-31`
|
||||
- `init()` calls `super.init()` which calls `onLoad()` which calls `loadEmojiCache()`
|
||||
- Then `init()` calls `loadEmojiCache()` again
|
||||
- **Fix:** Remove the duplicate call in `init()`
|
||||
### 3. DIP: Service Locator Anti-Pattern
|
||||
`ServiceRegistry.get(Xxx.class)` used everywhere instead of constructor DI. Big refactor.
|
||||
|
||||
### 3. SRP: `ConfigManager.java` (278 lines)
|
||||
### 4. DRY: PM Send Logic Duplicated
|
||||
`MsgCommand` + `ReplyCommand` still have near-identical send logic.
|
||||
|
||||
God class with 60+ methods across 10+ concerns:
|
||||
- Config orchestration
|
||||
- Chat settings (global, local, PM)
|
||||
- Mention settings
|
||||
- Clear chat state mutation
|
||||
- Announcement settings
|
||||
- Join/quit/death message settings
|
||||
|
||||
**Fix:** Split into `ChatConfig`, `PmConfig`, `MentionConfig`, `ClearChatConfig`, `AnnouncementConfig`, `CustomMessagesConfig`
|
||||
|
||||
### 4. OCP: `AdvancedMessageFilter.filterMessage()`
|
||||
|
||||
Hardcoded pipeline of 7 filters. Adding a new filter requires modifying this method.
|
||||
|
||||
**Fix:** Dynamic `List<Filter>` pipeline with registration
|
||||
|
||||
### 5. ISP: `MessagingService` (fat interface)
|
||||
|
||||
16 methods across 4 concerns: PM + Spy + Ignore + Persistence
|
||||
|
||||
**Fix:** Split into `PrivateMessageService`, `SpyService`, `IgnoreService`, `PersistableService`
|
||||
|
||||
### 6. DIP: Service Locator Anti-Pattern
|
||||
|
||||
`ServiceRegistry.get(Xxx.class)` used everywhere instead of constructor DI.
|
||||
|
||||
**Fix:** Pass required services via constructor parameters
|
||||
|
||||
### 7. DRY: 12 Commands Duplicate Constructor Pattern
|
||||
|
||||
Commands like `MsgCommand`, `ReplyCommand`, `IgnoreCommand`, `MuteCommand`, `BanCommand` etc. don't use `BaseCommand` hierarchy and repeat the same field/constructor/permission pattern.
|
||||
|
||||
**Fix:** Migrate all to `PlayerCommand` / `AdminCommand`
|
||||
|
||||
### 8. DRY: PM Send Logic Duplicated
|
||||
|
||||
`MsgCommand.java:48-59` and `ReplyCommand.java:47-57` have identical PM send code.
|
||||
|
||||
**Fix:** Extract to shared method/service
|
||||
|
||||
### 9. KISS: `MuteCommand.onCommand()` (~100 lines)
|
||||
|
||||
High complexity: argument parsing, flags, fallback logic, permission checks, broadcast logic, voice-mute.
|
||||
|
||||
**Fix:** Decompose into `MuteRequest` parser + `MuteExecutor` service
|
||||
|
||||
### 10. OOP: Leaked Internals
|
||||
|
||||
- `LoChat.getInstance()` — singleton abuse
|
||||
- `LoChat` exposes 7+ internal components via public getters
|
||||
- `ChatEventListener` casts to concrete `PlayerServiceImpl`
|
||||
- `BaseConfig` has `protected` mutable fields
|
||||
### 5. PM Send Logic Duplicated Between MsgCommand and ReplyCommand
|
||||
|
||||
---
|
||||
|
||||
## 🔵 MEDIUM PRIORITY
|
||||
## 🔵 LOW PRIORITY
|
||||
|
||||
### 11. Dual Message Configs
|
||||
### 6. Tests
|
||||
Only 4 test files for 150+ main files. Very low coverage.
|
||||
|
||||
`MessageConfig.java` (root, path-based) and `MessagesConfig.java` (config/, typed getters) overlap.
|
||||
### 7. Aho-Corasick for Swear Filter
|
||||
Performance optimization for word filtering.
|
||||
|
||||
**Fix:** Unify into single `MessageConfig`
|
||||
|
||||
### 12. Filter Classes Don't Share Interface
|
||||
|
||||
`core/filter/filters/*.java` are standalone. `CooldownFilter`/`MuteFilter` implement `MessageFilter` from api.
|
||||
|
||||
**Fix:** Make all filters implement `MessageFilter`
|
||||
|
||||
### 13. 5+ Services Repeat Load/Save/Persistence
|
||||
|
||||
`NickServiceImpl`, `PlayerServiceImpl`, `ChatServiceImpl`, `PunishmentServiceImpl` all have their own file I/O.
|
||||
|
||||
**Fix:** Shared persistence utility
|
||||
|
||||
### 14. Mute Duration Permissions Not Declared in plugin.yml
|
||||
|
||||
`lochat.mute.dur.5m`, `lochat.mute.dur.3h`, `lochat.mute.reason.*` — used in code/configs but missing from plugin.yml.
|
||||
|
||||
---
|
||||
|
||||
## ⚪ LOW PRIORITY
|
||||
|
||||
### 15. Performance: Aho-Corasick for Swear Filter
|
||||
|
||||
### 16. Discord Rate Limiter
|
||||
|
||||
### 17. Tests: Only 4 test files (150 main files)
|
||||
|
||||
### 18. `.project`, `.classpath`, `.settings/` should be gitignored
|
||||
### 8. Discord Rate Limiter
|
||||
Prevent rate limiting when sending many webhook messages.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -12,6 +12,8 @@
|
|||
<property name="fileNamePattern" value=".*[\\/]src[\\/]test[\\/].*"/>
|
||||
</module>
|
||||
|
||||
<module name="SuppressWarningsFilter"/>
|
||||
|
||||
<module name="FileLength">
|
||||
<property name="max" value="600"/>
|
||||
</module>
|
||||
|
|
@ -23,6 +25,7 @@
|
|||
</module>
|
||||
|
||||
<module name="TreeWalker">
|
||||
<module name="SuppressWarningsHolder"/>
|
||||
|
||||
<module name="Indentation">
|
||||
<property name="basicOffset" value="4"/>
|
||||
|
|
@ -103,6 +106,5 @@
|
|||
<module name="ArrayTypeStyle"/>
|
||||
<module name="UnnecessaryParentheses"/>
|
||||
<module name="MissingSwitchDefault"/>
|
||||
|
||||
</module>
|
||||
</module>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
YamlConfiguration cfg = 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();
|
||||
}
|
||||
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,9 +113,6 @@ 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",
|
||||
|
|
@ -128,39 +122,24 @@ public class PlayerServiceImpl implements PlayerService {
|
|||
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());
|
||||
}
|
||||
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);
|
||||
FilePersistence.saveYaml(plugin, "data/statistics.yml", stats);
|
||||
playerMessages.remove(uuid);
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("[LoChat] Failed to persist player stats: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
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,10 +130,6 @@ 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();
|
||||
|
|
@ -155,12 +140,7 @@ public class PunishmentServiceImpl implements PunishmentService {
|
|||
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());
|
||||
}
|
||||
FilePersistence.saveJson(plugin, "punishments.json", snap, gson);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -175,12 +155,7 @@ public class PunishmentServiceImpl implements PunishmentService {
|
|||
}
|
||||
|
||||
private void load() {
|
||||
if (!dataFile.exists()) {
|
||||
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);
|
||||
PunishmentSnapshot snap = FilePersistence.loadJson(plugin, "punishments.json", PunishmentSnapshot.class, gson);
|
||||
if (snap == null) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -208,9 +183,6 @@ public class PunishmentServiceImpl implements PunishmentService {
|
|||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("Failed to load punishments: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static class PunishmentSnapshot {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -71,6 +71,14 @@ public class DiscordConfig {
|
|||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -71,3 +71,5 @@ performance:
|
|||
timeout: 10
|
||||
retry-attempts: 3
|
||||
retry-delay: 1000
|
||||
max-requests-per-second: 5
|
||||
rate-limit-burst: 10
|
||||
|
|
|
|||
|
|
@ -281,6 +281,9 @@ permissions:
|
|||
lochat.mute.see-silent:
|
||||
description: Can see silent mute notifications
|
||||
default: op
|
||||
lochat.mute.dur.5m:
|
||||
description: Can mute for 5 minutes
|
||||
default: false
|
||||
lochat.mute.dur.10m:
|
||||
description: Can mute for 10 minutes
|
||||
default: false
|
||||
|
|
@ -290,6 +293,9 @@ permissions:
|
|||
lochat.mute.dur.1h:
|
||||
description: Can mute for 1 hour
|
||||
default: false
|
||||
lochat.mute.dur.3h:
|
||||
description: Can mute for 3 hours
|
||||
default: false
|
||||
lochat.mute.dur.6h:
|
||||
description: Can mute for 6 hours
|
||||
default: false
|
||||
|
|
@ -315,6 +321,46 @@ permissions:
|
|||
description: Can mute permanently
|
||||
default: op
|
||||
|
||||
lochat.mute.reason.spam:
|
||||
description: Can use spam mute reason
|
||||
default: false
|
||||
lochat.mute.reason.caps:
|
||||
description: Can use caps mute reason
|
||||
default: false
|
||||
lochat.mute.reason.swear:
|
||||
description: Can use swear mute reason
|
||||
default: false
|
||||
lochat.mute.reason.flood:
|
||||
description: Can use flood mute reason
|
||||
default: false
|
||||
lochat.mute.reason.advertising:
|
||||
description: Can use advertising mute reason
|
||||
default: false
|
||||
lochat.mute.reason.insult:
|
||||
description: Can use insult mute reason
|
||||
default: false
|
||||
lochat.mute.reason.toxicity:
|
||||
description: Can use toxicity mute reason
|
||||
default: false
|
||||
lochat.mute.reason.politics:
|
||||
description: Can use politics mute reason
|
||||
default: false
|
||||
lochat.mute.reason.religion:
|
||||
description: Can use religion mute reason
|
||||
default: false
|
||||
lochat.mute.reason.begging:
|
||||
description: Can use begging mute reason
|
||||
default: false
|
||||
lochat.mute.notify:
|
||||
description: Receive mute notifications
|
||||
default: op
|
||||
lochat.mute.ip:
|
||||
description: Can mute by IP address
|
||||
default: false
|
||||
lochat.appeal:
|
||||
description: Can use /appeal command
|
||||
default: true
|
||||
|
||||
lochat.warn:
|
||||
description: Issue a visible warning
|
||||
default: op
|
||||
|
|
@ -348,6 +394,9 @@ permissions:
|
|||
lochat.heademoji.use:
|
||||
description: Can use head emojis in chat
|
||||
default: true
|
||||
chat.emoji.use:
|
||||
description: Can use emojis in chat
|
||||
default: true
|
||||
lochat.nick:
|
||||
description: Can use custom nicknames
|
||||
default: true
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue