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
|
### Architecture (SOLID/KISS/DRY)
|
||||||
- [x] AutoMessageManager: save task reference, cancel in `stop()`
|
- [x] `AppearanceConfig` — fixed double-call bug
|
||||||
- [x] Event listeners: `HandlerList.unregisterAll(this)` in `onDisable()`
|
- [x] `config/` — 9→3 files per folder (subfolders: chat/, filter/, mute/, manager/)
|
||||||
- [x] MuteHistoryManager: MAX_HISTORY_PER_PLAYER = 50
|
- [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
|
### Encapsulation
|
||||||
- [x] AsyncChatEvent: try-catch for Location in async
|
- [x] `ChatEventListener` — removed instanceof cast to `PlayerServiceImpl`
|
||||||
- [x] ConfigManager: `volatile` + `synchronized reload()`
|
- [x] `PunishmentSnapshot` — mutable fields → private + getters
|
||||||
|
- [x] `BaseConfig` — protected fields → private + protected getters
|
||||||
### Compilation Fixes
|
- [x] 29 files with `import.*` → explicit imports
|
||||||
- [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
|
|
||||||
|
|
||||||
### Checkstyle
|
### Checkstyle
|
||||||
- [x] Google Java Style config (2-space indent, 100-char lines, Javadoc)
|
- [x] Config: 4-space indent, 140-char lines, Google-style naming/quality
|
||||||
- [x] Checkstyle plugin wired into build.gradle.kts
|
- [x] Warnings: **520 → 0** (both main and test)
|
||||||
|
- [x] Wired into `build.gradle.kts`
|
||||||
|
|
||||||
### Cleanup
|
### Configs
|
||||||
- [x] Removed `libs/lolib-3.0.0.jar` (unused)
|
- [x] All rewritten: clean, English comments, no Spacelegacy palette
|
||||||
- [x] Removed CODE_OF_CONDUCT.md, SECURITY.md
|
|
||||||
- [x] Added `bin/` to .gitignore
|
|
||||||
- [x] Configs rewritten: clean, English, no Russian/Spacelegacy
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🟡 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 |
|
**Fix:** Extract `FilePersistenceUtil` or base class
|
||||||
|--------|-------|-----|
|
|
||||||
| `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 |
|
|
||||||
|
|
||||||
### 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`
|
### 3. DIP: Service Locator Anti-Pattern
|
||||||
- `init()` calls `super.init()` which calls `onLoad()` which calls `loadEmojiCache()`
|
`ServiceRegistry.get(Xxx.class)` used everywhere instead of constructor DI. Big refactor.
|
||||||
- Then `init()` calls `loadEmojiCache()` again
|
|
||||||
- **Fix:** Remove the duplicate call in `init()`
|
|
||||||
|
|
||||||
### 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:
|
### 5. PM Send Logic Duplicated Between MsgCommand and ReplyCommand
|
||||||
- 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
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔵 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`
|
### 8. Discord Rate Limiter
|
||||||
|
Prevent rate limiting when sending many webhook messages.
|
||||||
### 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
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
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[\\/].*"/>
|
<property name="fileNamePattern" value=".*[\\/]src[\\/]test[\\/].*"/>
|
||||||
</module>
|
</module>
|
||||||
|
|
||||||
|
<module name="SuppressWarningsFilter"/>
|
||||||
|
|
||||||
<module name="FileLength">
|
<module name="FileLength">
|
||||||
<property name="max" value="600"/>
|
<property name="max" value="600"/>
|
||||||
</module>
|
</module>
|
||||||
|
|
@ -23,6 +25,7 @@
|
||||||
</module>
|
</module>
|
||||||
|
|
||||||
<module name="TreeWalker">
|
<module name="TreeWalker">
|
||||||
|
<module name="SuppressWarningsHolder"/>
|
||||||
|
|
||||||
<module name="Indentation">
|
<module name="Indentation">
|
||||||
<property name="basicOffset" value="4"/>
|
<property name="basicOffset" value="4"/>
|
||||||
|
|
@ -103,6 +106,5 @@
|
||||||
<module name="ArrayTypeStyle"/>
|
<module name="ArrayTypeStyle"/>
|
||||||
<module name="UnnecessaryParentheses"/>
|
<module name="UnnecessaryParentheses"/>
|
||||||
<module name="MissingSwitchDefault"/>
|
<module name="MissingSwitchDefault"/>
|
||||||
|
|
||||||
</module>
|
</module>
|
||||||
</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.LoChat;
|
||||||
import com.loki.lochat.api.service.MessagingService;
|
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.format.ChatFormatter;
|
||||||
import com.loki.lochat.utils.player.PlayerUtil;
|
|
||||||
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Sound;
|
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.command.CommandExecutor;
|
import org.bukkit.command.CommandExecutor;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
|
|
@ -17,10 +16,12 @@ public class MsgCommand implements CommandExecutor {
|
||||||
|
|
||||||
private final LoChat plugin;
|
private final LoChat plugin;
|
||||||
private final MessagingService messagingService;
|
private final MessagingService messagingService;
|
||||||
|
private final PrivateMessageService pmService;
|
||||||
|
|
||||||
public MsgCommand(LoChat plugin) {
|
public MsgCommand(LoChat plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.messagingService = plugin.getServiceRegistry().get(MessagingService.class);
|
this.messagingService = plugin.getServiceRegistry().get(MessagingService.class);
|
||||||
|
this.pmService = plugin.getServiceRegistry().get(PrivateMessageService.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -56,24 +57,7 @@ public class MsgCommand implements CommandExecutor {
|
||||||
}
|
}
|
||||||
|
|
||||||
String message = String.join(" ", java.util.Arrays.copyOfRange(args, 1, args.length));
|
String message = String.join(" ", java.util.Arrays.copyOfRange(args, 1, args.length));
|
||||||
sendPm(player, target, message);
|
pmService.sendPrivateMessage(player, target, message);
|
||||||
return true;
|
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.LoChat;
|
||||||
import com.loki.lochat.api.service.MessagingService;
|
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.format.ChatFormatter;
|
||||||
import com.loki.lochat.utils.player.PlayerUtil;
|
|
||||||
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Sound;
|
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.command.CommandExecutor;
|
import org.bukkit.command.CommandExecutor;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
|
|
@ -20,10 +19,12 @@ public class ReplyCommand implements CommandExecutor {
|
||||||
|
|
||||||
private final LoChat plugin;
|
private final LoChat plugin;
|
||||||
private final MessagingService messagingService;
|
private final MessagingService messagingService;
|
||||||
|
private final PrivateMessageService pmService;
|
||||||
|
|
||||||
public ReplyCommand(LoChat plugin) {
|
public ReplyCommand(LoChat plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.messagingService = plugin.getServiceRegistry().get(MessagingService.class);
|
this.messagingService = plugin.getServiceRegistry().get(MessagingService.class);
|
||||||
|
this.pmService = plugin.getServiceRegistry().get(PrivateMessageService.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -57,19 +58,7 @@ public class ReplyCommand implements CommandExecutor {
|
||||||
}
|
}
|
||||||
|
|
||||||
String message = String.join(" ", args);
|
String message = String.join(" ", args);
|
||||||
player.sendMessage(ChatFormatter.formatPmSentNew(plugin.getMessageConfig().getPmFormatSent(), player, target, message));
|
pmService.sendPrivateMessage(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);
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import com.loki.lochat.api.service.MuteService;
|
||||||
import com.loki.lochat.config.MessageConfig;
|
import com.loki.lochat.config.MessageConfig;
|
||||||
import com.loki.lochat.core.service.MessagingServiceImpl;
|
import com.loki.lochat.core.service.MessagingServiceImpl;
|
||||||
import com.loki.lochat.core.service.MuteServiceImpl;
|
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.MuteDataStorage;
|
||||||
import com.loki.lochat.core.service.mute.MuteHistoryManager;
|
import com.loki.lochat.core.service.mute.MuteHistoryManager;
|
||||||
import com.loki.lochat.core.service.mute.strategies.ChatMuteStrategy;
|
import com.loki.lochat.core.service.mute.strategies.ChatMuteStrategy;
|
||||||
|
|
@ -50,7 +51,8 @@ public class ServiceFactory {
|
||||||
/**
|
/**
|
||||||
* Создать MessagingService со всеми зависимостями
|
* Создать MessagingService со всеми зависимостями
|
||||||
*/
|
*/
|
||||||
public static MessagingService createMessagingService(JavaPlugin plugin, MessageConfig messageConfig) {
|
public static MessagingService createMessagingService(
|
||||||
return new MessagingServiceImpl(plugin, messageConfig);
|
JavaPlugin plugin, MessageConfig messageConfig, PrivateMessageServiceImpl pmService) {
|
||||||
|
return new MessagingServiceImpl(plugin, messageConfig, pmService);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.loki.lochat.core.registry;
|
package com.loki.lochat.core.registry;
|
||||||
|
|
||||||
|
import com.loki.lochat.LoChat;
|
||||||
import com.loki.lochat.api.service.ChatService;
|
import com.loki.lochat.api.service.ChatService;
|
||||||
import com.loki.lochat.api.service.MentionService;
|
import com.loki.lochat.api.service.MentionService;
|
||||||
import com.loki.lochat.api.service.MessageService;
|
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.NickService;
|
||||||
import com.loki.lochat.api.service.PlayerService;
|
import com.loki.lochat.api.service.PlayerService;
|
||||||
import com.loki.lochat.api.service.PunishmentService;
|
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.ConfigManager;
|
||||||
import com.loki.lochat.config.MessageConfig;
|
import com.loki.lochat.config.MessageConfig;
|
||||||
import com.loki.lochat.core.factory.ServiceFactory;
|
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.NickServiceImpl;
|
||||||
import com.loki.lochat.core.service.PlayerServiceImpl;
|
import com.loki.lochat.core.service.PlayerServiceImpl;
|
||||||
import com.loki.lochat.core.service.PunishmentServiceImpl;
|
import com.loki.lochat.core.service.PunishmentServiceImpl;
|
||||||
|
import com.loki.lochat.core.service.messaging.PrivateMessageServiceImpl;
|
||||||
|
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
|
@ -48,7 +51,10 @@ public class ServiceRegistry {
|
||||||
register(MessageService.class, new MessageServiceImpl(plugin, this));
|
register(MessageService.class, new MessageServiceImpl(plugin, this));
|
||||||
|
|
||||||
// ✅ NEW: Объединённый MessagingService (PM + Spy + Ignore) через фабрику
|
// ✅ 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(MessagingService.class, messagingService);
|
||||||
|
|
||||||
register(MentionService.class, new MentionServiceImpl(configManager));
|
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.core.registry.ServiceRegistry;
|
||||||
import com.loki.lochat.renderer.EnhancedChatRenderer;
|
import com.loki.lochat.renderer.EnhancedChatRenderer;
|
||||||
import com.loki.lochat.utils.format.ChatFormatter;
|
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.platform.FoliaUtil;
|
||||||
import com.loki.lochat.utils.player.DistanceUtil;
|
import com.loki.lochat.utils.player.DistanceUtil;
|
||||||
|
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
import org.bukkit.configuration.file.YamlConfiguration;
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
@ -28,11 +28,9 @@ public class ChatServiceImpl implements ChatService {
|
||||||
|
|
||||||
private final JavaPlugin plugin;
|
private final JavaPlugin plugin;
|
||||||
private final Set<UUID> globalChatDisabled = ConcurrentHashMap.newKeySet();
|
private final Set<UUID> globalChatDisabled = ConcurrentHashMap.newKeySet();
|
||||||
private final File playersFile;
|
|
||||||
|
|
||||||
public ChatServiceImpl(JavaPlugin plugin, ServiceRegistry registry) {
|
public ChatServiceImpl(JavaPlugin plugin, ServiceRegistry registry) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.playersFile = new File(plugin.getDataFolder(), "data/players.yml");
|
|
||||||
loadDisabled();
|
loadDisabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,10 +97,7 @@ public class ChatServiceImpl implements ChatService {
|
||||||
// ──────────────────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
private void loadDisabled() {
|
private void loadDisabled() {
|
||||||
if (!playersFile.exists()) {
|
FileConfiguration cfg = FilePersistence.loadYaml(plugin, "data/players.yml");
|
||||||
return;
|
|
||||||
}
|
|
||||||
YamlConfiguration cfg = YamlConfiguration.loadConfiguration(playersFile);
|
|
||||||
for (String uuidStr : cfg.getStringList("global-chat-disabled")) {
|
for (String uuidStr : cfg.getStringList("global-chat-disabled")) {
|
||||||
try {
|
try {
|
||||||
globalChatDisabled.add(UUID.fromString(uuidStr));
|
globalChatDisabled.add(UUID.fromString(uuidStr));
|
||||||
|
|
@ -112,25 +107,10 @@ public class ChatServiceImpl implements ChatService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveDisabled() {
|
private void saveDisabled() {
|
||||||
try {
|
YamlConfiguration cfg = new YamlConfiguration();
|
||||||
ensureDir();
|
|
||||||
YamlConfiguration cfg = playersFile.exists()
|
|
||||||
? YamlConfiguration.loadConfiguration(playersFile)
|
|
||||||
: new YamlConfiguration();
|
|
||||||
|
|
||||||
cfg.set("global-chat-disabled",
|
cfg.set("global-chat-disabled",
|
||||||
globalChatDisabled.stream().map(UUID::toString).toList());
|
globalChatDisabled.stream().map(UUID::toString).toList());
|
||||||
cfg.save(playersFile);
|
FilePersistence.saveYaml(plugin, "data/players.yml", cfg);
|
||||||
} 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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Component toComponent(Object message) {
|
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.api.service.MessagingService;
|
||||||
import com.loki.lochat.config.MessageConfig;
|
import com.loki.lochat.config.MessageConfig;
|
||||||
import com.loki.lochat.core.service.messaging.IgnoreService;
|
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 com.loki.lochat.core.service.messaging.SpyService;
|
||||||
|
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
|
|
@ -21,12 +21,12 @@ import java.util.UUID;
|
||||||
*/
|
*/
|
||||||
public class MessagingServiceImpl implements MessagingService {
|
public class MessagingServiceImpl implements MessagingService {
|
||||||
|
|
||||||
private final PrivateMessageService pmService;
|
private final PrivateMessageServiceImpl pmService;
|
||||||
private final SpyService spyService;
|
private final SpyService spyService;
|
||||||
private final IgnoreService ignoreService;
|
private final IgnoreService ignoreService;
|
||||||
|
|
||||||
public MessagingServiceImpl(JavaPlugin plugin, MessageConfig messageConfig) {
|
public MessagingServiceImpl(JavaPlugin plugin, MessageConfig messageConfig, PrivateMessageServiceImpl pmService) {
|
||||||
this.pmService = new PrivateMessageService();
|
this.pmService = pmService;
|
||||||
this.spyService = new SpyService(messageConfig);
|
this.spyService = new SpyService(messageConfig);
|
||||||
this.ignoreService = new IgnoreService(plugin);
|
this.ignoreService = new IgnoreService(plugin);
|
||||||
this.ignoreService.init();
|
this.ignoreService.init();
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,16 @@ package com.loki.lochat.core.service;
|
||||||
import com.loki.lochat.api.service.NickService;
|
import com.loki.lochat.api.service.NickService;
|
||||||
import com.loki.lochat.config.RatConfig;
|
import com.loki.lochat.config.RatConfig;
|
||||||
import com.loki.lochat.utils.format.ChatFormatter;
|
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.Component;
|
||||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||||
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.configuration.file.FileConfiguration;
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
import org.bukkit.configuration.file.YamlConfiguration;
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
@ -26,26 +24,15 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||||
public class NickServiceImpl implements NickService {
|
public class NickServiceImpl implements NickService {
|
||||||
private final JavaPlugin plugin;
|
private final JavaPlugin plugin;
|
||||||
private final Map<UUID, String> nicknames = new ConcurrentHashMap<>();
|
private final Map<UUID, String> nicknames = new ConcurrentHashMap<>();
|
||||||
private final File dataFile;
|
|
||||||
private FileConfiguration data;
|
private FileConfiguration data;
|
||||||
|
|
||||||
public NickServiceImpl(JavaPlugin plugin) {
|
public NickServiceImpl(JavaPlugin plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.dataFile = new File(plugin.getDataFolder(), "nicknames.yml");
|
|
||||||
load();
|
load();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void load() {
|
private void load() {
|
||||||
if (!dataFile.exists()) {
|
data = FilePersistence.loadYaml(plugin, "nicknames.yml");
|
||||||
try {
|
|
||||||
dataFile.getParentFile().mkdirs();
|
|
||||||
dataFile.createNewFile();
|
|
||||||
} catch (IOException e) {
|
|
||||||
plugin.getLogger().warning("Не удалось создать nicknames.yml: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data = YamlConfiguration.loadConfiguration(dataFile);
|
|
||||||
|
|
||||||
// Загружаем ники
|
// Загружаем ники
|
||||||
if (data.contains("nicknames")) {
|
if (data.contains("nicknames")) {
|
||||||
|
|
@ -67,11 +54,7 @@ public class NickServiceImpl implements NickService {
|
||||||
data.set("nicknames." + entry.getKey().toString(), entry.getValue());
|
data.set("nicknames." + entry.getKey().toString(), entry.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
FilePersistence.saveYaml(plugin, "nicknames.yml", data);
|
||||||
data.save(dataFile);
|
|
||||||
} catch (IOException e) {
|
|
||||||
plugin.getLogger().warning("Не удалось сохранить nicknames.yml: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
package com.loki.lochat.core.service;
|
package com.loki.lochat.core.service;
|
||||||
|
|
||||||
import com.loki.lochat.api.service.PlayerService;
|
import com.loki.lochat.api.service.PlayerService;
|
||||||
|
import com.loki.lochat.utils.persistence.FilePersistence;
|
||||||
import com.loki.lochat.utils.platform.FoliaUtil;
|
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 org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
@ -32,12 +31,10 @@ public class PlayerServiceImpl implements PlayerService {
|
||||||
private final AtomicLong pmCount = new AtomicLong(0);
|
private final AtomicLong pmCount = new AtomicLong(0);
|
||||||
private final AtomicLong totalMessages = new AtomicLong(0);
|
private final AtomicLong totalMessages = new AtomicLong(0);
|
||||||
|
|
||||||
private final File statsFile;
|
private FileConfiguration stats;
|
||||||
private YamlConfiguration stats;
|
|
||||||
|
|
||||||
public PlayerServiceImpl(JavaPlugin plugin) {
|
public PlayerServiceImpl(JavaPlugin plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.statsFile = new File(plugin.getDataFolder(), "data/statistics.yml");
|
|
||||||
load();
|
load();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -116,9 +113,6 @@ public class PlayerServiceImpl implements PlayerService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void saveAll() {
|
public void saveAll() {
|
||||||
try {
|
|
||||||
ensureDir();
|
|
||||||
// Обновляем глобальные счётчики
|
|
||||||
stats.set("global.total-messages",
|
stats.set("global.total-messages",
|
||||||
stats.getLong("global.total-messages", 0) + totalMessages.getAndSet(0));
|
stats.getLong("global.total-messages", 0) + totalMessages.getAndSet(0));
|
||||||
stats.set("by-type.global-chat",
|
stats.set("by-type.global-chat",
|
||||||
|
|
@ -128,39 +122,24 @@ public class PlayerServiceImpl implements PlayerService {
|
||||||
stats.set("by-type.private-messages",
|
stats.set("by-type.private-messages",
|
||||||
stats.getLong("by-type.private-messages", 0) + pmCount.getAndSet(0));
|
stats.getLong("by-type.private-messages", 0) + pmCount.getAndSet(0));
|
||||||
stats.set("last-updated", System.currentTimeMillis());
|
stats.set("last-updated", System.currentTimeMillis());
|
||||||
stats.save(statsFile);
|
FilePersistence.saveYaml(plugin, "data/statistics.yml", stats);
|
||||||
} catch (IOException e) {
|
|
||||||
plugin.getLogger().warning("[LoChat] Failed to save statistics: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Private Methods ==========
|
// ========== Private Methods ==========
|
||||||
|
|
||||||
private void persistPlayerMessages(UUID uuid, long count) {
|
private void persistPlayerMessages(UUID uuid, long count) {
|
||||||
try {
|
|
||||||
String path = "top-players.by-messages." + uuid;
|
String path = "top-players.by-messages." + uuid;
|
||||||
long prev = stats.getLong(path + ".count", 0);
|
long prev = stats.getLong(path + ".count", 0);
|
||||||
stats.set(path + ".count", prev + count);
|
stats.set(path + ".count", prev + count);
|
||||||
stats.set(path + ".last-updated", System.currentTimeMillis());
|
stats.set(path + ".last-updated", System.currentTimeMillis());
|
||||||
stats.save(statsFile);
|
FilePersistence.saveYaml(plugin, "data/statistics.yml", stats);
|
||||||
playerMessages.remove(uuid);
|
playerMessages.remove(uuid);
|
||||||
} catch (IOException e) {
|
|
||||||
plugin.getLogger().warning("[LoChat] Failed to persist player stats: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void load() {
|
private void load() {
|
||||||
ensureDir();
|
if (!FilePersistence.getFile(plugin, "data/statistics.yml").exists()) {
|
||||||
if (!statsFile.exists()) {
|
|
||||||
plugin.saveResource("data/statistics.yml", false);
|
plugin.saveResource("data/statistics.yml", false);
|
||||||
}
|
}
|
||||||
stats = YamlConfiguration.loadConfiguration(statsFile);
|
stats = FilePersistence.loadYaml(plugin, "data/statistics.yml");
|
||||||
}
|
|
||||||
|
|
||||||
private void ensureDir() {
|
|
||||||
File dir = statsFile.getParentFile();
|
|
||||||
if (!dir.exists()) {
|
|
||||||
dir.mkdirs();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,28 +2,19 @@ package com.loki.lochat.core.service;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
import com.loki.lochat.api.service.PunishmentService;
|
import com.loki.lochat.api.service.PunishmentService;
|
||||||
import com.loki.lochat.config.MessagesConfig;
|
import com.loki.lochat.config.MessagesConfig;
|
||||||
import com.loki.lochat.data.model.BanRecord;
|
import com.loki.lochat.data.model.BanRecord;
|
||||||
import com.loki.lochat.data.model.WarnEntry;
|
import com.loki.lochat.data.model.WarnEntry;
|
||||||
import com.loki.lochat.utils.format.ChatFormatter;
|
import com.loki.lochat.utils.format.ChatFormatter;
|
||||||
import com.loki.lochat.utils.format.TimeFormatter;
|
import com.loki.lochat.utils.format.TimeFormatter;
|
||||||
|
import com.loki.lochat.utils.persistence.FilePersistence;
|
||||||
import com.loki.lochat.utils.platform.FoliaUtil;
|
import com.loki.lochat.utils.platform.FoliaUtil;
|
||||||
|
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
|
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
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.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
@ -39,7 +30,6 @@ public class PunishmentServiceImpl implements PunishmentService {
|
||||||
|
|
||||||
private final JavaPlugin plugin;
|
private final JavaPlugin plugin;
|
||||||
private final MessagesConfig messages;
|
private final MessagesConfig messages;
|
||||||
private final File dataFile;
|
|
||||||
private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
||||||
|
|
||||||
private final Map<UUID, List<WarnEntry>> warns = new ConcurrentHashMap<>();
|
private final Map<UUID, List<WarnEntry>> warns = new ConcurrentHashMap<>();
|
||||||
|
|
@ -48,7 +38,6 @@ public class PunishmentServiceImpl implements PunishmentService {
|
||||||
public PunishmentServiceImpl(JavaPlugin plugin, MessagesConfig messagesConfig) {
|
public PunishmentServiceImpl(JavaPlugin plugin, MessagesConfig messagesConfig) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.messages = messagesConfig;
|
this.messages = messagesConfig;
|
||||||
this.dataFile = new File(plugin.getDataFolder(), "punishments.json");
|
|
||||||
load();
|
load();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,10 +130,6 @@ public class PunishmentServiceImpl implements PunishmentService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void save() {
|
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();
|
PunishmentSnapshot snap = new PunishmentSnapshot();
|
||||||
for (Map.Entry<UUID, List<WarnEntry>> e : warns.entrySet()) {
|
for (Map.Entry<UUID, List<WarnEntry>> e : warns.entrySet()) {
|
||||||
List<WarnEntry> list = e.getValue();
|
List<WarnEntry> list = e.getValue();
|
||||||
|
|
@ -155,12 +140,7 @@ public class PunishmentServiceImpl implements PunishmentService {
|
||||||
for (Map.Entry<UUID, BanRecord> e : bans.entrySet()) {
|
for (Map.Entry<UUID, BanRecord> e : bans.entrySet()) {
|
||||||
snap.getBans().put(e.getKey().toString(), e.getValue());
|
snap.getBans().put(e.getKey().toString(), e.getValue());
|
||||||
}
|
}
|
||||||
try (Writer w = new OutputStreamWriter(new FileOutputStream(dataFile), java.nio.charset.StandardCharsets.UTF_8)) {
|
FilePersistence.saveJson(plugin, "punishments.json", snap, gson);
|
||||||
gson.toJson(snap, w);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
plugin.getLogger().warning("Failed to save punishments: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -175,12 +155,7 @@ public class PunishmentServiceImpl implements PunishmentService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void load() {
|
private void load() {
|
||||||
if (!dataFile.exists()) {
|
PunishmentSnapshot snap = FilePersistence.loadJson(plugin, "punishments.json", PunishmentSnapshot.class, gson);
|
||||||
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) {
|
if (snap == null) {
|
||||||
return;
|
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 {
|
private static class PunishmentSnapshot {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
package com.loki.lochat.core.service.messaging;
|
package com.loki.lochat.core.service.messaging;
|
||||||
|
|
||||||
|
import com.loki.lochat.utils.persistence.FilePersistence;
|
||||||
import com.loki.lochat.utils.platform.FoliaUtil;
|
import com.loki.lochat.utils.platform.FoliaUtil;
|
||||||
|
|
||||||
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
import org.bukkit.configuration.file.YamlConfiguration;
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -23,11 +23,9 @@ public class IgnoreService {
|
||||||
|
|
||||||
private final JavaPlugin plugin;
|
private final JavaPlugin plugin;
|
||||||
private final Map<UUID, Set<UUID>> ignoreMap = new ConcurrentHashMap<>();
|
private final Map<UUID, Set<UUID>> ignoreMap = new ConcurrentHashMap<>();
|
||||||
private final File ignoreFile;
|
|
||||||
|
|
||||||
public IgnoreService(JavaPlugin plugin) {
|
public IgnoreService(JavaPlugin plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.ignoreFile = new File(plugin.getDataFolder(), "ignores.yml");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init() {
|
public void init() {
|
||||||
|
|
@ -75,11 +73,7 @@ public class IgnoreService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void load() {
|
public void load() {
|
||||||
if (!ignoreFile.exists()) {
|
FileConfiguration config = FilePersistence.loadYaml(plugin, "ignores.yml");
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
YamlConfiguration config = YamlConfiguration.loadConfiguration(ignoreFile);
|
|
||||||
for (String key : config.getKeys(false)) {
|
for (String key : config.getKeys(false)) {
|
||||||
try {
|
try {
|
||||||
UUID uuid = UUID.fromString(key);
|
UUID uuid = UUID.fromString(key);
|
||||||
|
|
@ -114,11 +108,7 @@ public class IgnoreService {
|
||||||
config.set(entry.getKey().toString(), ignoredList);
|
config.set(entry.getKey().toString(), ignoredList);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
FilePersistence.saveYaml(plugin, "ignores.yml", config);
|
||||||
config.save(ignoreFile);
|
|
||||||
} catch (IOException e) {
|
|
||||||
plugin.getLogger().severe("Не удалось сохранить игноры: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveAsync() {
|
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.JsonArray;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
import com.loki.lochat.integrations.discord.DiscordRateLimiter;
|
||||||
|
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
|
@ -29,6 +30,7 @@ public class DiscordWebhook {
|
||||||
private final int timeout;
|
private final int timeout;
|
||||||
private final int retryAttempts;
|
private final int retryAttempts;
|
||||||
private final long retryDelay;
|
private final long retryDelay;
|
||||||
|
private final DiscordRateLimiter rateLimiter;
|
||||||
|
|
||||||
// Выделенный пул — не засоряем ForkJoinPool.commonPool()
|
// Выделенный пул — не засоряем ForkJoinPool.commonPool()
|
||||||
private final ExecutorService executor = Executors.newSingleThreadExecutor(
|
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,
|
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.plugin = plugin;
|
||||||
this.webhookUrl = webhookUrl != null ? webhookUrl.trim() : null;
|
this.webhookUrl = webhookUrl != null ? webhookUrl.trim() : null;
|
||||||
this.username = username;
|
this.username = username;
|
||||||
|
|
@ -48,6 +52,7 @@ public class DiscordWebhook {
|
||||||
this.timeout = timeout * 1000;
|
this.timeout = timeout * 1000;
|
||||||
this.retryAttempts = retryAttempts;
|
this.retryAttempts = retryAttempts;
|
||||||
this.retryDelay = retryDelay;
|
this.retryDelay = retryDelay;
|
||||||
|
this.rateLimiter = new DiscordRateLimiter(maxRequestsPerSecond, rateLimitBurst);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Публичные методы отправки ─────────────────────────────────────────────
|
// ── Публичные методы отправки ─────────────────────────────────────────────
|
||||||
|
|
@ -114,6 +119,7 @@ public class DiscordWebhook {
|
||||||
private CompletableFuture<Boolean> sendWebhook(JsonObject json) {
|
private CompletableFuture<Boolean> sendWebhook(JsonObject json) {
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
for (int attempt = 1; attempt <= retryAttempts; attempt++) {
|
for (int attempt = 1; attempt <= retryAttempts; attempt++) {
|
||||||
|
rateLimiter.waitIfNeeded();
|
||||||
try {
|
try {
|
||||||
if (sendWebhookSync(json.toString())) {
|
if (sendWebhookSync(json.toString())) {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,14 @@ public class DiscordConfig {
|
||||||
return config.getBoolean("performance.async", true);
|
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() {
|
public boolean isChatEnabled() {
|
||||||
return config.getBoolean("chat.enabled", true);
|
return config.getBoolean("chat.enabled", true);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,9 @@ public class DiscordMessageService {
|
||||||
getAvatarUrl(),
|
getAvatarUrl(),
|
||||||
config.getTimeout(),
|
config.getTimeout(),
|
||||||
config.getRetryAttempts(),
|
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
|
timeout: 10
|
||||||
retry-attempts: 3
|
retry-attempts: 3
|
||||||
retry-delay: 1000
|
retry-delay: 1000
|
||||||
|
max-requests-per-second: 5
|
||||||
|
rate-limit-burst: 10
|
||||||
|
|
|
||||||
|
|
@ -281,6 +281,9 @@ permissions:
|
||||||
lochat.mute.see-silent:
|
lochat.mute.see-silent:
|
||||||
description: Can see silent mute notifications
|
description: Can see silent mute notifications
|
||||||
default: op
|
default: op
|
||||||
|
lochat.mute.dur.5m:
|
||||||
|
description: Can mute for 5 minutes
|
||||||
|
default: false
|
||||||
lochat.mute.dur.10m:
|
lochat.mute.dur.10m:
|
||||||
description: Can mute for 10 minutes
|
description: Can mute for 10 minutes
|
||||||
default: false
|
default: false
|
||||||
|
|
@ -290,6 +293,9 @@ permissions:
|
||||||
lochat.mute.dur.1h:
|
lochat.mute.dur.1h:
|
||||||
description: Can mute for 1 hour
|
description: Can mute for 1 hour
|
||||||
default: false
|
default: false
|
||||||
|
lochat.mute.dur.3h:
|
||||||
|
description: Can mute for 3 hours
|
||||||
|
default: false
|
||||||
lochat.mute.dur.6h:
|
lochat.mute.dur.6h:
|
||||||
description: Can mute for 6 hours
|
description: Can mute for 6 hours
|
||||||
default: false
|
default: false
|
||||||
|
|
@ -315,6 +321,46 @@ permissions:
|
||||||
description: Can mute permanently
|
description: Can mute permanently
|
||||||
default: op
|
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:
|
lochat.warn:
|
||||||
description: Issue a visible warning
|
description: Issue a visible warning
|
||||||
default: op
|
default: op
|
||||||
|
|
@ -348,6 +394,9 @@ permissions:
|
||||||
lochat.heademoji.use:
|
lochat.heademoji.use:
|
||||||
description: Can use head emojis in chat
|
description: Can use head emojis in chat
|
||||||
default: true
|
default: true
|
||||||
|
chat.emoji.use:
|
||||||
|
description: Can use emojis in chat
|
||||||
|
default: true
|
||||||
lochat.nick:
|
lochat.nick:
|
||||||
description: Can use custom nicknames
|
description: Can use custom nicknames
|
||||||
default: true
|
default: true
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue