diff --git a/bukkit/src/main/java/io/wdsj/asw/bukkit/AdvancedSensitiveWords.java b/bukkit/src/main/java/io/wdsj/asw/bukkit/AdvancedSensitiveWords.java index 2c79262..e165402 100644 --- a/bukkit/src/main/java/io/wdsj/asw/bukkit/AdvancedSensitiveWords.java +++ b/bukkit/src/main/java/io/wdsj/asw/bukkit/AdvancedSensitiveWords.java @@ -57,7 +57,7 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Logger; -import static io.wdsj.asw.bukkit.util.TimingUtils.cleanStatisticCache; +import static io.wdsj.asw.bukkit.util.TimingUtils.resetStatistics; import static io.wdsj.asw.bukkit.util.Utils.*; @@ -124,7 +124,7 @@ public void onEnable() { long startTime = System.currentTimeMillis(); libraryService.loadRequired(); LOGGER.info("Initializing DFA system..."); - cleanStatisticCache(); + resetStatistics(); scheduler = UniversalScheduler.getScheduler(this); permCache = CachingPermTool.enable(this); BookCache.initialize(); @@ -279,7 +279,7 @@ public void onDisable() { getServer().getMessenger().unregisterOutgoingPluginChannel(this); getServer().getMessenger().unregisterIncomingPluginChannel(this); HandlerList.unregisterAll(this); - TimingUtils.cleanStatisticCache(); + TimingUtils.resetStatistics(); ChatContext.forceClearContext(); SignContext.forceClearContext(); PlayerShadowController.clear(); diff --git a/bukkit/src/main/java/io/wdsj/asw/bukkit/manage/punish/Punishment.java b/bukkit/src/main/java/io/wdsj/asw/bukkit/manage/punish/Punishment.java index d8ff904..33f1b44 100644 --- a/bukkit/src/main/java/io/wdsj/asw/bukkit/manage/punish/Punishment.java +++ b/bukkit/src/main/java/io/wdsj/asw/bukkit/manage/punish/Punishment.java @@ -37,7 +37,10 @@ public static void punish(Player player) { public static void processSinglePunish(Player player, String method) throws IllegalArgumentException { String[] normalPunish = method.split("\\|"); - PunishmentType punishMethod = PunishmentType.valueOf(normalPunish[0].toUpperCase(Locale.ROOT)); + PunishmentType punishMethod = PunishmentType.getType(normalPunish[0].toUpperCase(Locale.ROOT)); + if (punishMethod == null) { + throw new IllegalArgumentException("Invalid punishment method"); + } long violationCount = ViolationCounter.getViolationCount(player); if (normalPunish.length > 2 && normalPunish[normalPunish.length - 1].toUpperCase(Locale.ROOT).startsWith("VL") && normalPunish[normalPunish.length - 1].length() > 2) { String vlCondition = normalPunish[normalPunish.length - 1].substring(2); @@ -107,8 +110,7 @@ public static void processSinglePunish(Player player, String method) throws Ille } break; default: - LOGGER.warning("Unknown punishment type"); - break; + throw new IllegalArgumentException("Invalid punishment method"); } } diff --git a/bukkit/src/main/java/io/wdsj/asw/bukkit/manage/punish/PunishmentType.java b/bukkit/src/main/java/io/wdsj/asw/bukkit/manage/punish/PunishmentType.java index 1a096ce..ec6efcb 100644 --- a/bukkit/src/main/java/io/wdsj/asw/bukkit/manage/punish/PunishmentType.java +++ b/bukkit/src/main/java/io/wdsj/asw/bukkit/manage/punish/PunishmentType.java @@ -1,5 +1,9 @@ package io.wdsj.asw.bukkit.manage.punish; +import org.jetbrains.annotations.Nullable; + +import java.util.Locale; + public enum PunishmentType { COMMAND, @@ -11,5 +15,14 @@ public enum PunishmentType { EFFECT, - SHADOW + SHADOW; + + @Nullable + public static PunishmentType getType(String type) { + try { + return valueOf(type.trim().toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException e) { + return null; + } + } } diff --git a/bukkit/src/main/java/io/wdsj/asw/bukkit/util/LoggingUtils.java b/bukkit/src/main/java/io/wdsj/asw/bukkit/util/LoggingUtils.java deleted file mode 100644 index beadec5..0000000 --- a/bukkit/src/main/java/io/wdsj/asw/bukkit/util/LoggingUtils.java +++ /dev/null @@ -1,48 +0,0 @@ -package io.wdsj.asw.bukkit.util; - -import com.google.common.util.concurrent.ThreadFactoryBuilder; -import io.wdsj.asw.bukkit.AdvancedSensitiveWords; - -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.LOGGER; - -public class LoggingUtils { - private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd-HH:mm:ss"); - private static ExecutorService loggingThreadPool; - public static void logViolation(String playerName, String violationReason) { - loggingThreadPool.submit(() -> { - String formattedDate = dateFormat.format(new Date()); - String logMessage = "[" + formattedDate + "] " + playerName + " " + violationReason; - File logFile = new File(AdvancedSensitiveWords.getInstance().getDataFolder(), "violations.log"); - if (!logFile.exists()) { - try { - logFile.createNewFile(); - } catch (IOException e) { - LOGGER.warning("Failed to create violations.log file: " + e.getMessage()); - return; - } - } - try (Writer writer = new OutputStreamWriter(new FileOutputStream(logFile, true), StandardCharsets.UTF_8)) { - writer.write(logMessage + System.lineSeparator()); - } catch (IOException e) { - LOGGER.warning("Failed to write to violations.log file: " + e.getMessage()); - } - }); - } - - public static void start() { - loggingThreadPool = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("ASW Logging Thread-%d").setDaemon(true).build()); - } - public static void stop() { - if (loggingThreadPool != null) { - loggingThreadPool.shutdown(); - loggingThreadPool = null; - } - } -} diff --git a/bukkit/src/main/java/io/wdsj/asw/bukkit/util/PlayerUtils.java b/bukkit/src/main/java/io/wdsj/asw/bukkit/util/PlayerUtils.java deleted file mode 100644 index 74f5136..0000000 --- a/bukkit/src/main/java/io/wdsj/asw/bukkit/util/PlayerUtils.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.wdsj.asw.bukkit.util; - -import org.bukkit.entity.Player; - -import static io.wdsj.asw.bukkit.util.Utils.isAnyClassLoaded; - -public class PlayerUtils { - private static final boolean isLeavesServer = isAnyClassLoaded("top.leavesmc.leaves.LeavesConfig", "org.leavesmc.leaves.LeavesConfig"); - - public static boolean isNpc(Player player) { - if (isLeavesServer) { - return player.getAddress() == null || player.hasMetadata("NPC"); - } else { - return player.hasMetadata("NPC"); - } - } - - private PlayerUtils() {} -} diff --git a/bukkit/src/main/java/io/wdsj/asw/bukkit/util/SchedulingUtils.java b/bukkit/src/main/java/io/wdsj/asw/bukkit/util/SchedulingUtils.java deleted file mode 100644 index b86647d..0000000 --- a/bukkit/src/main/java/io/wdsj/asw/bukkit/util/SchedulingUtils.java +++ /dev/null @@ -1,73 +0,0 @@ -package io.wdsj.asw.bukkit.util; - - -import com.github.Anon8281.universalScheduler.UniversalScheduler; -import com.github.Anon8281.universalScheduler.scheduling.tasks.MyScheduledTask; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.entity.Entity; -import org.bukkit.event.Event; - -import java.util.concurrent.Callable; - -import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.getScheduler; - -public class SchedulingUtils { - private SchedulingUtils() { - } - private static final boolean isFolia = UniversalScheduler.isFolia; - - public static void runSyncIfFolia(Runnable runnable) { - if (isFolia) { - getScheduler().runTask(runnable); - } else { - runnable.run(); - } - } - - public static void runSyncAtEntityIfFolia(Entity entity, Runnable runnable) { - if (isFolia) { - getScheduler().runTask(entity, runnable); - } else { - runnable.run(); - } - } - - public static void runSyncAtLocationIfFolia(Location location, Runnable runnable) { - if (isFolia) { - getScheduler().runTask(location, runnable); - } else { - runnable.run(); - } - } - - public static void runSyncIfEventAsync(Runnable runnable, Event event) { - if (event.isAsynchronous()) { - getScheduler().runTask(runnable); - } else { - runnable.run(); - } - } - - public static void runSyncIfNotOnMainThread(Runnable runnable) { - if (Bukkit.isPrimaryThread()) { - runnable.run(); - } else { - getScheduler().runTask(runnable); - } - } - - public static void cancelTaskSafely(MyScheduledTask task) { - if (task == null) return; - task.cancel(); - } - - public static T callSyncMethod(Callable callable) { - try { - return getScheduler().callSyncMethod(callable).get(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - -} diff --git a/bukkit/src/main/java/io/wdsj/asw/bukkit/util/TimingUtils.java b/bukkit/src/main/java/io/wdsj/asw/bukkit/util/TimingUtils.java deleted file mode 100644 index 8d549d7..0000000 --- a/bukkit/src/main/java/io/wdsj/asw/bukkit/util/TimingUtils.java +++ /dev/null @@ -1,46 +0,0 @@ -package io.wdsj.asw.bukkit.util; - - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import static io.wdsj.asw.bukkit.util.Utils.messagesFilteredNum; - -public class TimingUtils { - private static final List processStatistic = Collections.synchronizedList(new ArrayList<>()); - private static final String vendor = Utils.checkNotNullWithFallback(System.getProperties().getProperty("java.vendor"), "Unknown"); - private static final String javaVersion = Utils.checkNotNullWithFallback(System.getProperties().getProperty("java.version"), "Unknown"); - - public static void addProcessStatistic(long endTime, long startTime) { - long processTime = endTime - startTime; - while (processStatistic.size() >= 20) { - processStatistic.remove(0); - } - processStatistic.add(processTime); - } - - public static String getJvmVersion() { - return javaVersion; - } - - public static String getJvmVendor() { - return vendor; - } - - public static long getProcessAverage() { - long sum = 0L; - for (long l : processStatistic) { - sum += l; - } - return !processStatistic.isEmpty() ? sum / processStatistic.size() : 0L; - } - - public static void cleanStatisticCache() { - processStatistic.clear(); - messagesFilteredNum.set(0L); - } - private TimingUtils() { - } - -} diff --git a/bukkit/src/main/java/io/wdsj/asw/bukkit/util/Utils.java b/bukkit/src/main/java/io/wdsj/asw/bukkit/util/Utils.java deleted file mode 100644 index 08cef66..0000000 --- a/bukkit/src/main/java/io/wdsj/asw/bukkit/util/Utils.java +++ /dev/null @@ -1,117 +0,0 @@ -package io.wdsj.asw.bukkit.util; - -import com.github.houbb.heaven.util.io.FileUtil; -import com.github.houbb.heaven.util.lang.StringUtil; -import io.wdsj.asw.bukkit.AdvancedSensitiveWords; -import io.wdsj.asw.bukkit.setting.PluginSettings; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; - -import java.io.File; -import java.net.InetSocketAddress; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.atomic.AtomicLong; - -import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.LOGGER; -import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager; - -public class Utils { - public static AtomicLong messagesFilteredNum = new AtomicLong(0); - - public static String getPlayerIp(Player player) { - InetSocketAddress address = player.getAddress(); - if (address != null && address.getAddress() != null) return address.getAddress().getHostAddress(); - throw new IllegalStateException("Player address is null"); - } - - public static boolean isClassLoaded(String className) { - try { - Class.forName(className); - return true; - } catch (ClassNotFoundException ignored) { - return false; - } - } - public static boolean isAnyClassLoaded(String... classNames) { - for (String className : classNames) { - if (isClassLoaded(className)) return true; - } - return false; - } - public static boolean canUsePE() { - Plugin protocolLib = Bukkit.getPluginManager().getPlugin("ProtocolLib"); - Plugin packetevents = Bukkit.getPluginManager().getPlugin("packetevents"); - if (packetevents == null) return false; - if (protocolLib != null && protocolLib.isEnabled()) { - // ProtocolLib is loaded - try { - return StringUtil.toInt(String.valueOf(protocolLib.getDescription().getVersion().charAt(0))) >= 5; - } catch (Exception e) { - return true; - } - } - return true; - } - - public static void purgeLog() { - File logFile = new File(AdvancedSensitiveWords.getInstance().getDataFolder(), "violations.log"); - if (!logFile.exists()) return; - FileUtil.deleteFile(logFile); - LOGGER.info("Successfully purged violations"); - } - public static boolean isCommand(String command) { - return command.startsWith("/"); - } - public static String getSplitCommandArgs(String command) { - String[] splitCommand = command.split(" "); - if (splitCommand.length <= 1) return ""; - return String.join(" ", Arrays.copyOfRange(splitCommand, 1, splitCommand.length)); - } - - public static String getSplitCommandHeaders(String command) { - String[] splitCommand = command.split(" "); - if (splitCommand.length < 1) return ""; - return splitCommand[0]; - } - public static String getPreProcessRegex() { - return settingsManager.getProperty(PluginSettings.PRE_PROCESS_REGEX); - } - - - public static boolean isCommandAndWhiteListed(String command) { - if (!command.startsWith("/")) return false; - List whitelist = settingsManager.getProperty(PluginSettings.CHAT_COMMAND_WHITE_LIST); - String[] splitCommand = command.split(" "); - for (String s : whitelist) { - if (splitCommand[0].equalsIgnoreCase(s)) { - return !settingsManager.getProperty(PluginSettings.CHAT_INVERT_WHITELIST); - } - } - return settingsManager.getProperty(PluginSettings.CHAT_INVERT_WHITELIST); - } - - public static String getMinecraftVersion() { - return Bukkit.getBukkitVersion().split("-")[0]; - } - - public static boolean isNotCommand(String command) { - return !command.startsWith("/"); - } - - /** - * Checks if the given object is null, and returns the fallback value if it is. - * @param obj The object to check - * @param fallback The fallback value to return if the object is null - */ - public static T checkNotNullWithFallback(T obj, T fallback) { - if (obj == null) { - return fallback; - } - return obj; - } - - private Utils() { - } -} diff --git a/bukkit/src/main/java/io/wdsj/asw/bukkit/util/VirtualThreadUtils.java b/bukkit/src/main/java/io/wdsj/asw/bukkit/util/VirtualThreadUtils.java deleted file mode 100644 index 514e65a..0000000 --- a/bukkit/src/main/java/io/wdsj/asw/bukkit/util/VirtualThreadUtils.java +++ /dev/null @@ -1,65 +0,0 @@ -package io.wdsj.asw.bukkit.util; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.lang.reflect.Method; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; - -/** - * Utility class for virtual threads which are introduced in Java 21 - */ -public class VirtualThreadUtils { - private VirtualThreadUtils() {} - - private static ThreadFactory virtualThreadFactory; - - private static ExecutorService virtualThreadPerTaskExecutor; - - static { - try { - Method ofVirtual = Thread.class.getMethod("ofVirtual"); - Class ThreadBuilder = Class.forName("java.lang.Thread$Builder"); - Method factory = ThreadBuilder.getMethod("factory"); - ofVirtual.setAccessible(true); - factory.setAccessible(true); - virtualThreadFactory = (ThreadFactory) factory.invoke(ofVirtual.invoke(null)); - } catch (Exception e) { - virtualThreadFactory = null; - } - try { - Method method = Executors.class.getMethod("newVirtualThreadPerTaskExecutor"); - method.setAccessible(true); - virtualThreadPerTaskExecutor = (ExecutorService) method.invoke(null); - } catch (Exception e) { - virtualThreadPerTaskExecutor = null; - } - } - - @Nullable - public static ThreadFactory newVirtualThreadFactory() { - return virtualThreadFactory; - } - - @Nullable - public static ExecutorService newVirtualThreadPerTaskExecutor() { - return virtualThreadPerTaskExecutor; - } - - @NotNull - public static ThreadFactory newVirtualThreadFactoryOrProvided(ThreadFactory threadFactory) { - return Utils.checkNotNullWithFallback(virtualThreadFactory, threadFactory); - } - - @NotNull - public static ThreadFactory newVirtualThreadFactoryOrDefault() { - return Utils.checkNotNullWithFallback(virtualThreadFactory, Executors.defaultThreadFactory()); - } - - @NotNull - public static ExecutorService newVirtualThreadPerTaskExecutorOrProvided(ExecutorService executorService) { - return Utils.checkNotNullWithFallback(virtualThreadPerTaskExecutor, executorService); - } -} diff --git a/bukkit/src/main/java/io/wdsj/asw/bukkit/util/cache/BookCache.java b/bukkit/src/main/java/io/wdsj/asw/bukkit/util/cache/BookCache.java deleted file mode 100644 index ea6eddd..0000000 --- a/bukkit/src/main/java/io/wdsj/asw/bukkit/util/cache/BookCache.java +++ /dev/null @@ -1,90 +0,0 @@ -package io.wdsj.asw.bukkit.util.cache; - -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; -import io.wdsj.asw.bukkit.setting.PluginSettings; - -import java.util.List; -import java.util.NoSuchElementException; -import java.util.concurrent.TimeUnit; - -import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager; - -/** - * Cache utilities class for Book detections. - * @author HaHaWTH & HeyWTF_IS_That and 0D00_0721 - */ -public class BookCache { - private static Cache cache; - public static boolean isBookCached(String content) { - return cache.getIfPresent(content) != null; - } - - public static void addToBookCache(String content, String processedContent, List sensitiveWordList) { - cache.put(content, new BookCacheEntry(processedContent, sensitiveWordList)); - } - - /** - * Retrieves the processed book content from the cache. - * Warning: This method is only safely called after checking if the book is cached. - * @param content The content of the book. - * @return The processed book content. - */ - public static String getCachedProcessedBookContent(String content) throws NoSuchElementException { - final BookCacheEntry entry = cache.getIfPresent(content); - if (entry == null) { - throw new NoSuchElementException("Book not found in cache"); - } - return entry.getProcessedContent(); - } - - /** - * Retrieves the list of sensitive words from the cache. - * Warning: This method is only safely called after checking if the book is cached. - * @param content The content of the book. - * @return The list of sensitive words. - */ - public static List getCachedBookSensitiveWordList(String content) throws NoSuchElementException { - final BookCacheEntry entry = cache.getIfPresent(content); - if (entry == null) { - throw new NoSuchElementException("Book not found in cache"); - } - return entry.getSensitiveWordList(); - } - - public static void invalidateAll() { - cache.invalidateAll(); - } - - public static void initialize() { - cache = Caffeine.newBuilder() - .maximumSize(settingsManager.getProperty(PluginSettings.BOOK_MAXIMUM_CACHE_SIZE)) - .expireAfterWrite(settingsManager.getProperty(PluginSettings.BOOK_CACHE_EXPIRE_TIME), TimeUnit.MINUTES) - .build(); - } - - - private BookCache() { - } - - /** - * Inner class to encapsulate the processed book content and its list of sensitive words. - */ - private static class BookCacheEntry { - private final String processedContent; - private final List sensitiveWordList; - - public BookCacheEntry(String processedContent, List sensitiveWordList) { - this.processedContent = processedContent; - this.sensitiveWordList = sensitiveWordList; - } - - public String getProcessedContent() { - return processedContent; - } - - public List getSensitiveWordList() { - return sensitiveWordList; - } - } -} \ No newline at end of file diff --git a/bukkit/src/main/java/io/wdsj/asw/bukkit/util/context/ChatContext.java b/bukkit/src/main/java/io/wdsj/asw/bukkit/util/context/ChatContext.java deleted file mode 100644 index c98d5cc..0000000 --- a/bukkit/src/main/java/io/wdsj/asw/bukkit/util/context/ChatContext.java +++ /dev/null @@ -1,56 +0,0 @@ -package io.wdsj.asw.bukkit.util.context; - -import io.wdsj.asw.bukkit.setting.PluginSettings; -import io.wdsj.asw.common.datatype.TimedString; -import org.bukkit.entity.Player; - -import java.util.Deque; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedDeque; - -import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager; - -public class ChatContext { - private static final ConcurrentHashMap> chatHistory = new ConcurrentHashMap<>(); - /** - * Add player message to history - */ - public static void addMessage(Player player, String message) { - final UUID uuid = player.getUniqueId(); - chatHistory.computeIfAbsent(uuid, k -> new ConcurrentLinkedDeque<>()); - Deque history = chatHistory.get(uuid); - while (history.size() >= settingsManager.getProperty(PluginSettings.CHAT_CONTEXT_MAX_SIZE)) { - history.pollFirst(); - } - if (message.trim().isEmpty()) return; - history.offerLast(TimedString.of(message.trim())); - } - - public static Deque getHistory(Player player) { - final UUID uuid = player.getUniqueId(); - Deque tsHistory = chatHistory.getOrDefault(uuid, new ConcurrentLinkedDeque<>()); - if (tsHistory.isEmpty()) return new ConcurrentLinkedDeque<>(); - tsHistory.removeIf(timedString -> (System.currentTimeMillis() - timedString.getTime()) / 1000 > settingsManager.getProperty(PluginSettings.CHAT_CONTEXT_TIME_LIMIT)); - return tsHistory.stream() - .map(TimedString::getString) - .collect(ConcurrentLinkedDeque::new, ConcurrentLinkedDeque::offerLast, ConcurrentLinkedDeque::addAll); - } - - public static void clearPlayerContext(Player player) { - final UUID uuid = player.getUniqueId(); - if (chatHistory.get(uuid) == null) return; - chatHistory.remove(uuid); - } - - public static void pollPlayerContext(Player player) { - final UUID uuid = player.getUniqueId(); - Deque history = chatHistory.get(uuid); - if (history != null) history.pollLast(); - } - public static void forceClearContext() { - chatHistory.clear(); - } - - private ChatContext() {} -} diff --git a/bukkit/src/main/java/io/wdsj/asw/bukkit/util/context/SignContext.java b/bukkit/src/main/java/io/wdsj/asw/bukkit/util/context/SignContext.java deleted file mode 100644 index 2f86416..0000000 --- a/bukkit/src/main/java/io/wdsj/asw/bukkit/util/context/SignContext.java +++ /dev/null @@ -1,56 +0,0 @@ -package io.wdsj.asw.bukkit.util.context; - -import io.wdsj.asw.bukkit.setting.PluginSettings; -import io.wdsj.asw.common.datatype.TimedString; -import org.bukkit.entity.Player; - -import java.util.Deque; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedDeque; - -import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager; - -public class SignContext { - private static final ConcurrentHashMap> signEditHistory = new ConcurrentHashMap<>(); - /** - * Add player message to history - */ - public static void addMessage(Player player, String message) { - final UUID uuid = player.getUniqueId(); - signEditHistory.computeIfAbsent(uuid, k -> new ConcurrentLinkedDeque<>()); - Deque history = signEditHistory.get(uuid); - while (history.size() >= settingsManager.getProperty(PluginSettings.SIGN_CONTEXT_MAX_SIZE)) { - history.pollFirst(); - } - if (message.trim().isEmpty()) return; - history.offerLast(TimedString.of(message.trim())); - } - - public static Deque getHistory(Player player) { - final UUID uuid = player.getUniqueId(); - Deque tsHistory = signEditHistory.getOrDefault(uuid, new ConcurrentLinkedDeque<>()); - if (tsHistory.isEmpty()) return new ConcurrentLinkedDeque<>(); - tsHistory.removeIf(timedString -> (System.currentTimeMillis() - timedString.getTime()) / 1000 > settingsManager.getProperty(PluginSettings.SIGN_CONTEXT_TIME_LIMIT)); - return tsHistory.stream() - .map(TimedString::getString) - .collect(ConcurrentLinkedDeque::new, ConcurrentLinkedDeque::offerLast, ConcurrentLinkedDeque::addAll); - } - - public static void clearPlayerContext(Player player) { - final UUID uuid = player.getUniqueId(); - if (signEditHistory.get(uuid) == null) return; - signEditHistory.remove(uuid); - } - - public static void pollPlayerContext(Player player) { - final UUID uuid = player.getUniqueId(); - Deque history = signEditHistory.get(uuid); - if (history != null) history.pollLast(); - } - public static void forceClearContext() { - signEditHistory.clear(); - } - - private SignContext() {} -} diff --git a/bukkit/src/main/java/io/wdsj/asw/bukkit/util/message/MessageUtils.java b/bukkit/src/main/java/io/wdsj/asw/bukkit/util/message/MessageUtils.java deleted file mode 100644 index 628ee0e..0000000 --- a/bukkit/src/main/java/io/wdsj/asw/bukkit/util/message/MessageUtils.java +++ /dev/null @@ -1,31 +0,0 @@ -package io.wdsj.asw.bukkit.util.message; - -import ch.jalu.configme.properties.Property; -import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; - -import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.messagesManager; - -public class MessageUtils { - private MessageUtils() { - } - - public static final char AMPERSAND_CHAR = '&'; - - public static String retrieveMessage(Property property) { - return ChatColor.translateAlternateColorCodes(AMPERSAND_CHAR, messagesManager.getProperty(property)); - } - - public static void sendMessage(CommandSender sender, Property property) { - String msg = retrieveMessage(property); - if (!msg.isEmpty()) { - sender.sendMessage(msg); - } - } - - public static void sendMessage(CommandSender sender, String message) { - if (!message.isEmpty()) { - sender.sendMessage(ChatColor.translateAlternateColorCodes(AMPERSAND_CHAR, message)); - } - } -} diff --git a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/AnvilListener.kt b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/AnvilListener.kt index 34f6f0b..907ac96 100644 --- a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/AnvilListener.kt +++ b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/AnvilListener.kt @@ -42,7 +42,7 @@ class AnvilListener : Listener { var originalItemName = itemMeta.displayName if (settingsManager.getProperty(PluginSettings.PRE_PROCESS)) originalItemName = originalItemName.replace( - Utils.getPreProcessRegex().toRegex(), "" + Utils.preProcessRegex.toRegex(), "" ) val censoredWords = AdvancedSensitiveWords.sensitiveWordBs.findAll(originalItemName) if (censoredWords.isNotEmpty()) { diff --git a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/BookListener.kt b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/BookListener.kt index 813f93e..81df534 100644 --- a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/BookListener.kt +++ b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/BookListener.kt @@ -45,7 +45,7 @@ class BookListener : Listener { if (skipReturnLine) originalPage = originalPage.replace("\n", "").replace("§0", "") if (settingsManager.getProperty(PluginSettings.PRE_PROCESS)) originalPage = originalPage.replace( - Utils.getPreProcessRegex().toRegex(), "" + Utils.preProcessRegex.toRegex(), "" ) val isBookCached = BookCache.isBookCached(originalPage) val censoredWordList = @@ -83,7 +83,7 @@ class BookListener : Listener { if (settingsManager.getProperty(PluginSettings.PRE_PROCESS)) { originalPageCrossed = originalPageCrossed.replace( - Utils.getPreProcessRegex().toRegex(), "" + Utils.preProcessRegex.toRegex(), "" ) } val censoredWordListCrossed = AdvancedSensitiveWords.sensitiveWordBs.findAll(originalPageCrossed) @@ -101,7 +101,7 @@ class BookListener : Listener { if (originalAuthor != null) { if (settingsManager.getProperty(PluginSettings.PRE_PROCESS)) originalAuthor = originalAuthor.replace( - Utils.getPreProcessRegex().toRegex(), "" + Utils.preProcessRegex.toRegex(), "" ) val censoredWordListAuthor = AdvancedSensitiveWords.sensitiveWordBs.findAll(originalAuthor) if (censoredWordListAuthor.isNotEmpty()) { @@ -122,7 +122,7 @@ class BookListener : Listener { if (originalTitle != null) { if (settingsManager.getProperty(PluginSettings.PRE_PROCESS)) originalTitle = originalTitle.replace( - Utils.getPreProcessRegex().toRegex(), "" + Utils.preProcessRegex.toRegex(), "" ) val censoredWordListTitle = AdvancedSensitiveWords.sensitiveWordBs.findAll(originalTitle) if (censoredWordListTitle.isNotEmpty()) { diff --git a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/BroadCastListener.kt b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/BroadCastListener.kt index 2c21658..a233ea7 100644 --- a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/BroadCastListener.kt +++ b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/BroadCastListener.kt @@ -18,7 +18,7 @@ class BroadCastListener : Listener { var originalMessage = event.message if (settingsManager.getProperty(PluginSettings.PRE_PROCESS)) originalMessage = originalMessage.replace( - Utils.getPreProcessRegex().toRegex(), "" + Utils.preProcessRegex.toRegex(), "" ) val startTime = System.currentTimeMillis() val censoredWordList = AdvancedSensitiveWords.sensitiveWordBs.findAll(originalMessage) diff --git a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/ChatListener.kt b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/ChatListener.kt index fbc4bfa..35bafc5 100644 --- a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/ChatListener.kt +++ b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/ChatListener.kt @@ -35,7 +35,7 @@ class ChatListener : Listener { val player = event.player if (shouldNotProcess(player)) return val isCancelMode = settingsManager.getProperty(PluginSettings.CHAT_METHOD).equals("cancel", ignoreCase = true) - val originalMessage = if (settingsManager.getProperty(PluginSettings.PRE_PROCESS)) event.message.replace(Utils.getPreProcessRegex().toRegex(), "") else event.message + val originalMessage = if (settingsManager.getProperty(PluginSettings.PRE_PROCESS)) event.message.replace(Utils.preProcessRegex.toRegex(), "") else event.message val censoredWordList = sensitiveWordBs.findAll(originalMessage) val startTime = System.currentTimeMillis() if (censoredWordList.isNotEmpty()) { diff --git a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/CommandListener.kt b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/CommandListener.kt index d635674..6e226ea 100644 --- a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/CommandListener.kt +++ b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/CommandListener.kt @@ -30,7 +30,7 @@ class CommandListener : Listener { val player = event.player val originalCommand = if (settingsManager.getProperty(PluginSettings.PRE_PROCESS)) event.message.replace( - Utils.getPreProcessRegex().toRegex(), "" + Utils.preProcessRegex.toRegex(), "" ) else event.message if (shouldNotProcess(player, originalCommand)) return val censoredWordList = sensitiveWordBs.findAll(originalCommand) diff --git a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/ItemSpawnListener.kt b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/ItemSpawnListener.kt index 2d97122..ff1273a 100644 --- a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/ItemSpawnListener.kt +++ b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/ItemSpawnListener.kt @@ -20,7 +20,7 @@ class ItemSpawnListener : Listener { var originalName = it if (settingsManager.getProperty(PluginSettings.PRE_PROCESS)) originalName = originalName.replace( - Utils.getPreProcessRegex().toRegex(), "" + Utils.preProcessRegex.toRegex(), "" ) val startTime = System.currentTimeMillis() val censoredWordList = sensitiveWordBs.findAll(originalName) diff --git a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/PlayerItemListener.kt b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/PlayerItemListener.kt index 3f25b1c..08a844d 100644 --- a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/PlayerItemListener.kt +++ b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/PlayerItemListener.kt @@ -36,7 +36,7 @@ class PlayerItemListener : Listener { val startTime = System.currentTimeMillis() if (settingsManager.getProperty(PluginSettings.PRE_PROCESS)) originalName = originalName.replace( - Utils.getPreProcessRegex().toRegex(), "" + Utils.preProcessRegex.toRegex(), "" ) val censoredWordList = AdvancedSensitiveWords.sensitiveWordBs.findAll(originalName) if (censoredWordList.isNotEmpty()) { @@ -95,7 +95,7 @@ class PlayerItemListener : Listener { val startTime = System.currentTimeMillis() if (settingsManager.getProperty(PluginSettings.PRE_PROCESS)) originalName = originalName.replace( - Utils.getPreProcessRegex().toRegex(), "" + Utils.preProcessRegex.toRegex(), "" ) val censoredWordList = AdvancedSensitiveWords.sensitiveWordBs.findAll(originalName) if (censoredWordList.isNotEmpty()) { diff --git a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/SignListener.kt b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/SignListener.kt index d1dde73..d5984f0 100644 --- a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/SignListener.kt +++ b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/SignListener.kt @@ -41,7 +41,7 @@ class SignListener : Listener { var originalMessage = event.getLine(line) if (settingsManager.getProperty(PluginSettings.PRE_PROCESS) && originalMessage != null) originalMessage = originalMessage.replace( - Utils.getPreProcessRegex().toRegex(), "" + Utils.preProcessRegex.toRegex(), "" ) assert(originalMessage != null) val censoredWordList = sensitiveWordBs.findAll(originalMessage) diff --git a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/packet/ASWBookPacketListener.kt b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/packet/ASWBookPacketListener.kt index c3886f5..826957b 100644 --- a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/packet/ASWBookPacketListener.kt +++ b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/packet/ASWBookPacketListener.kt @@ -51,7 +51,7 @@ class ASWBookPacketListener : PacketListenerAbstract(PacketListenerPriority.LOW) if (skipReturnLine) { originalPage = originalPage.replace("\n", "").replace("§0", "") } - if (settingsManager.getProperty(PluginSettings.PRE_PROCESS)) originalPage = originalPage.replace(Utils.getPreProcessRegex().toRegex(), "") + if (settingsManager.getProperty(PluginSettings.PRE_PROCESS)) originalPage = originalPage.replace(Utils.preProcessRegex.toRegex(), "") val isBookCached = BookCache.isBookCached(originalPage) val censoredWordList = if (isBookCached && isCacheEnabled) BookCache.getCachedBookSensitiveWordList(originalPage) else AdvancedSensitiveWords.sensitiveWordBs.findAll(originalPage) if (censoredWordList.isNotEmpty()) { @@ -81,7 +81,7 @@ class ASWBookPacketListener : PacketListenerAbstract(PacketListenerPriority.LOW) if (settingsManager.getProperty(PluginSettings.BOOK_CROSS_PAGE) && !shouldSendMessage) { var crossPageListString = originalPages.joinToString("").replace("\n", "").replace("§0", "") if (settingsManager.getProperty(PluginSettings.PRE_PROCESS)) { - crossPageListString = crossPageListString.replace(Utils.getPreProcessRegex().toRegex(), "") + crossPageListString = crossPageListString.replace(Utils.preProcessRegex.toRegex(), "") } val censoredWordListCrossPage = AdvancedSensitiveWords.sensitiveWordBs.findAll(crossPageListString) if (censoredWordListCrossPage.isNotEmpty()) { @@ -95,7 +95,7 @@ class ASWBookPacketListener : PacketListenerAbstract(PacketListenerPriority.LOW) // Book title check var originalTitle = wrapper.title if (originalTitle != null) { - if (settingsManager.getProperty(PluginSettings.PRE_PROCESS)) originalTitle = originalTitle.replace(Utils.getPreProcessRegex().toRegex(), "") + if (settingsManager.getProperty(PluginSettings.PRE_PROCESS)) originalTitle = originalTitle.replace(Utils.preProcessRegex.toRegex(), "") val censoredWordListTitle = AdvancedSensitiveWords.sensitiveWordBs.findAll(originalTitle) if (censoredWordListTitle.isNotEmpty()) { val processedTitle = AdvancedSensitiveWords.sensitiveWordBs.replace(originalTitle) diff --git a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/packet/ASWChatPacketListener.kt b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/packet/ASWChatPacketListener.kt index f89f1c4..5c62901 100644 --- a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/packet/ASWChatPacketListener.kt +++ b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/packet/ASWChatPacketListener.kt @@ -44,7 +44,7 @@ class ASWChatPacketListener : PacketListenerAbstract(PacketListenerPriority.LOW) if (packetType === PacketType.Play.Client.CHAT_MESSAGE) { val player = event.getPlayer() as Player val wrapperPlayClientChatMessage = WrapperPlayClientChatMessage(event) - val originalMessage = if (settingsManager.getProperty(PluginSettings.PRE_PROCESS)) wrapperPlayClientChatMessage.message.replace(Utils.getPreProcessRegex().toRegex(), "") else wrapperPlayClientChatMessage.message + val originalMessage = if (settingsManager.getProperty(PluginSettings.PRE_PROCESS)) wrapperPlayClientChatMessage.message.replace(Utils.preProcessRegex.toRegex(), "") else wrapperPlayClientChatMessage.message if (shouldNotProcess(player, originalMessage)) return val startTime = System.currentTimeMillis() // Word check @@ -249,7 +249,7 @@ class ASWChatPacketListener : PacketListenerAbstract(PacketListenerPriority.LOW) } else if (packetType === PacketType.Play.Client.CHAT_COMMAND) { val player = event.getPlayer() as Player val wrapperPlayClientChatCommand = WrapperPlayClientChatCommand(event) - val originalCommand = if (settingsManager.getProperty(PluginSettings.PRE_PROCESS)) wrapperPlayClientChatCommand.command.replace(Utils.getPreProcessRegex().toRegex(), "") else wrapperPlayClientChatCommand.command + val originalCommand = if (settingsManager.getProperty(PluginSettings.PRE_PROCESS)) wrapperPlayClientChatCommand.command.replace(Utils.preProcessRegex.toRegex(), "") else wrapperPlayClientChatCommand.command if (shouldNotProcess(player, "/$originalCommand")) return val startTime = System.currentTimeMillis() val censoredWords = sensitiveWordBs.findAll(originalCommand) diff --git a/bukkit/src/main/java/io/wdsj/asw/bukkit/type/ModuleType.java b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/type/ModuleType.kt similarity index 68% rename from bukkit/src/main/java/io/wdsj/asw/bukkit/type/ModuleType.java rename to bukkit/src/main/kotlin/io/wdsj/asw/bukkit/type/ModuleType.kt index e5dac65..b0d7268 100644 --- a/bukkit/src/main/java/io/wdsj/asw/bukkit/type/ModuleType.java +++ b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/type/ModuleType.kt @@ -1,9 +1,9 @@ -package io.wdsj.asw.bukkit.type; +package io.wdsj.asw.bukkit.type /** * Types for different detection modules. */ -public enum ModuleType { +enum class ModuleType { CHAT, CHAT_AI, SIGN, diff --git a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/LoggingUtils.kt b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/LoggingUtils.kt new file mode 100644 index 0000000..0dd6bda --- /dev/null +++ b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/LoggingUtils.kt @@ -0,0 +1,53 @@ +package io.wdsj.asw.bukkit.util + +import com.google.common.util.concurrent.ThreadFactoryBuilder +import io.wdsj.asw.bukkit.AdvancedSensitiveWords +import io.wdsj.asw.bukkit.AdvancedSensitiveWords.LOGGER +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.OutputStreamWriter +import java.nio.charset.StandardCharsets +import java.text.SimpleDateFormat +import java.util.* +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +object LoggingUtils { + private val dateFormat = SimpleDateFormat("yyyy/MM/dd-HH:mm:ss") + private lateinit var loggingThreadPool: ExecutorService + fun logViolation(playerName: String, violationReason: String) { + loggingThreadPool.submit { + val formattedDate = dateFormat.format(Date()) + val logMessage = "[$formattedDate] $playerName $violationReason" + val logFile = File(AdvancedSensitiveWords.getInstance().dataFolder, "violations.log") + if (!logFile.exists()) { + try { + logFile.createNewFile() + } catch (e: IOException) { + LOGGER.warning("Failed to create violations.log file: " + e.message) + return@submit + } + } + try { + OutputStreamWriter(FileOutputStream(logFile, true), StandardCharsets.UTF_8).use { writer -> + writer.write(logMessage + System.lineSeparator()) + } + } catch (e: IOException) { + LOGGER.warning("Failed to write to violations.log file: " + e.message) + } + } + } + + @JvmStatic + fun start() { + loggingThreadPool = Executors.newSingleThreadExecutor( + ThreadFactoryBuilder().setNameFormat("ASW Logging Thread-%d").setDaemon(true).build() + ) + } + + @JvmStatic + fun stop() { + loggingThreadPool.shutdown() + } +} diff --git a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/PlayerUtils.kt b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/PlayerUtils.kt new file mode 100644 index 0000000..65911ca --- /dev/null +++ b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/PlayerUtils.kt @@ -0,0 +1,16 @@ +package io.wdsj.asw.bukkit.util + +import org.bukkit.entity.Player + +object PlayerUtils { + private val isLeavesServer = + Utils.isAnyClassLoaded("top.leavesmc.leaves.LeavesConfig", "org.leavesmc.leaves.LeavesConfig") + + fun isNpc(player: Player): Boolean { + return if (isLeavesServer) { + player.address == null || player.hasMetadata("NPC") + } else { + player.hasMetadata("NPC") + } + } +} diff --git a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/SchedulingUtils.kt b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/SchedulingUtils.kt new file mode 100644 index 0000000..2f81f96 --- /dev/null +++ b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/SchedulingUtils.kt @@ -0,0 +1,75 @@ +package io.wdsj.asw.bukkit.util + +import com.github.Anon8281.universalScheduler.UniversalScheduler +import com.github.Anon8281.universalScheduler.scheduling.tasks.MyScheduledTask +import io.wdsj.asw.bukkit.AdvancedSensitiveWords +import org.bukkit.Bukkit +import org.bukkit.Location +import org.bukkit.entity.Entity +import org.bukkit.event.Event +import java.util.concurrent.Callable + + +object SchedulingUtils { + private val isFolia = UniversalScheduler.isFolia + + @JvmStatic + fun runSyncIfFolia(runnable: Runnable) { + if (isFolia) { + AdvancedSensitiveWords.getScheduler().runTask(runnable) + } else { + runnable.run() + } + } + + @JvmStatic + fun runSyncAtEntityIfFolia(entity: Entity, runnable: Runnable) { + if (isFolia) { + AdvancedSensitiveWords.getScheduler().runTask(entity, runnable) + } else { + runnable.run() + } + } + + @JvmStatic + fun runSyncAtLocationIfFolia(location: Location, runnable: Runnable) { + if (isFolia) { + AdvancedSensitiveWords.getScheduler().runTask(location, runnable) + } else { + runnable.run() + } + } + + @JvmStatic + fun runSyncIfEventAsync(runnable: Runnable, event: Event) { + if (event.isAsynchronous) { + AdvancedSensitiveWords.getScheduler().runTask(runnable) + } else { + runnable.run() + } + } + + @JvmStatic + fun runSyncIfNotOnMainThread(runnable: Runnable) { + if (Bukkit.isPrimaryThread()) { + runnable.run() + } else { + AdvancedSensitiveWords.getScheduler().runTask(runnable) + } + } + + @JvmStatic + fun cancelTaskSafely(task: MyScheduledTask?) { + if (task == null) return + task.cancel() + } + + @JvmStatic + fun callSyncMethod(callable: Callable): T { + try { + return AdvancedSensitiveWords.getScheduler().callSyncMethod(callable).get() + } catch (e: Exception) { + throw RuntimeException(e) + } + } +} diff --git a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/TimingUtils.kt b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/TimingUtils.kt new file mode 100644 index 0000000..b7ca885 --- /dev/null +++ b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/TimingUtils.kt @@ -0,0 +1,36 @@ +package io.wdsj.asw.bukkit.util + +import java.util.* + + +object TimingUtils { + private val processStatistic: MutableList = Collections.synchronizedList(ArrayList()) + @JvmStatic + val jvmVendor: String = System.getProperties().getProperty("java.vendor") ?: "Unknown" + @JvmStatic + val jvmVersion: String = System.getProperties().getProperty("java.version") ?: "Unknown" + + fun addProcessStatistic(endTime: Long, startTime: Long) { + val processTime = endTime - startTime + while (processStatistic.size >= 20) { + processStatistic.removeAt(0) + } + processStatistic.add(processTime) + } + + @JvmStatic + val processAverage: Long + get() { + var sum = 0L + for (l in processStatistic) { + sum += l + } + return if (processStatistic.isNotEmpty()) sum / processStatistic.size else 0L + } + + @JvmStatic + fun resetStatistics() { + processStatistic.clear() + Utils.messagesFilteredNum.set(0L) + } +} diff --git a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/Utils.kt b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/Utils.kt new file mode 100644 index 0000000..3e5e204 --- /dev/null +++ b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/Utils.kt @@ -0,0 +1,115 @@ +package io.wdsj.asw.bukkit.util + +import com.github.houbb.heaven.util.io.FileUtil +import com.github.houbb.heaven.util.lang.StringUtil +import io.wdsj.asw.bukkit.AdvancedSensitiveWords +import io.wdsj.asw.bukkit.setting.PluginSettings +import org.bukkit.Bukkit +import org.bukkit.entity.Player +import java.io.File +import java.util.* +import java.util.concurrent.atomic.AtomicLong + +object Utils { + @JvmField + val messagesFilteredNum: AtomicLong = AtomicLong(0) + + @JvmStatic + fun getPlayerIp(player: Player): String { + val address = player.address + if (address != null && address.address != null) return address.address.hostAddress + throw IllegalStateException("Player address is null") + } + + @JvmStatic + fun isClassLoaded(className: String): Boolean { + try { + Class.forName(className) + return true + } catch (ignored: ClassNotFoundException) { + return false + } + } + + fun isAnyClassLoaded(vararg classNames: String): Boolean { + for (className in classNames) { + if (isClassLoaded(className)) return true + } + return false + } + + @JvmStatic + fun canUsePE(): Boolean { + val protocolLib = Bukkit.getPluginManager().getPlugin("ProtocolLib") + Bukkit.getPluginManager().getPlugin("packetevents") ?: return false + if (protocolLib != null && protocolLib.isEnabled) { + // ProtocolLib is loaded + return try { + StringUtil.toInt(protocolLib.description.version[0].toString()) >= 5 + } catch (e: Exception) { + true + } + } + return true + } + + @JvmStatic + fun purgeLog() { + val logFile = File(AdvancedSensitiveWords.getInstance().dataFolder, "violations.log") + if (!logFile.exists()) return + FileUtil.deleteFile(logFile) + AdvancedSensitiveWords.LOGGER.info("Successfully purged violations") + } + + fun isCommand(command: String): Boolean { + return command.startsWith("/") + } + + fun getSplitCommandArgs(command: String): String { + val splitCommand = command.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + if (splitCommand.size <= 1) return "" + return java.lang.String.join(" ", *Arrays.copyOfRange(splitCommand, 1, splitCommand.size)) + } + + fun getSplitCommandHeaders(command: String): String { + val splitCommand = command.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + if (splitCommand.isEmpty()) return "" + return splitCommand[0] + } + + val preProcessRegex: String + get() = AdvancedSensitiveWords.settingsManager.getProperty(PluginSettings.PRE_PROCESS_REGEX) + + + fun isCommandAndWhiteListed(command: String): Boolean { + if (!command.startsWith("/")) return false + val whitelist = AdvancedSensitiveWords.settingsManager.getProperty(PluginSettings.CHAT_COMMAND_WHITE_LIST) + val splitCommand = command.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + for (s in whitelist) { + if (splitCommand[0].equals(s, ignoreCase = true)) { + return !AdvancedSensitiveWords.settingsManager.getProperty(PluginSettings.CHAT_INVERT_WHITELIST) + } + } + return AdvancedSensitiveWords.settingsManager.getProperty(PluginSettings.CHAT_INVERT_WHITELIST) + } + + @JvmStatic + val minecraftVersion: String + get() = Bukkit.getBukkitVersion().split("-".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0] + + fun isNotCommand(command: String): Boolean { + return !command.startsWith("/") + } + + /** + * Checks if the given object is null, and returns the fallback value if it is. + * @param obj The object to check + * @param fallback The fallback value to return if the object is null + */ + fun checkNotNullWithFallback(obj: T?, fallback: T): T { + if (obj == null) { + return fallback + } + return obj + } +} diff --git a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/VirtualThreadUtils.kt b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/VirtualThreadUtils.kt new file mode 100644 index 0000000..7da1b60 --- /dev/null +++ b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/VirtualThreadUtils.kt @@ -0,0 +1,59 @@ +package io.wdsj.asw.bukkit.util + +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors +import java.util.concurrent.ThreadFactory + +/** + * Utility class for virtual threads which are introduced in Java 21 + */ +object VirtualThreadUtils { + private var virtualThreadFactory: ThreadFactory? + + private var virtualThreadPerTaskExecutor: ExecutorService? + + init { + try { + val ofVirtual = Thread::class.java.getMethod("ofVirtual") + val threadBuilder = Class.forName("java.lang.Thread\$Builder") + val factory = threadBuilder.getMethod("factory") + ofVirtual.isAccessible = true + factory.isAccessible = true + virtualThreadFactory = factory.invoke(ofVirtual.invoke(null)) as ThreadFactory + } catch (e: Exception) { + virtualThreadFactory = null + } + try { + val method = Executors::class.java.getMethod("newVirtualThreadPerTaskExecutor") + method.isAccessible = true + virtualThreadPerTaskExecutor = method.invoke(null) as ExecutorService + } catch (e: Exception) { + virtualThreadPerTaskExecutor = null + } + } + + @JvmStatic + fun newVirtualThreadFactory(): ThreadFactory? { + return virtualThreadFactory + } + + @JvmStatic + fun newVirtualThreadPerTaskExecutor(): ExecutorService? { + return virtualThreadPerTaskExecutor + } + + @JvmStatic + fun newVirtualThreadFactoryOrProvided(threadFactory: ThreadFactory?): ThreadFactory { + return Utils.checkNotNullWithFallback(virtualThreadFactory, threadFactory)!! + } + + @JvmStatic + fun newVirtualThreadFactoryOrDefault(): ThreadFactory { + return Utils.checkNotNullWithFallback(virtualThreadFactory, Executors.defaultThreadFactory())!! + } + + @JvmStatic + fun newVirtualThreadPerTaskExecutorOrProvided(executorService: ExecutorService?): ExecutorService { + return Utils.checkNotNullWithFallback(virtualThreadPerTaskExecutor, executorService)!! + } +} diff --git a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/cache/BookCache.kt b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/cache/BookCache.kt new file mode 100644 index 0000000..6dce648 --- /dev/null +++ b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/cache/BookCache.kt @@ -0,0 +1,70 @@ +package io.wdsj.asw.bukkit.util.cache + +import com.github.benmanes.caffeine.cache.Cache +import com.github.benmanes.caffeine.cache.Caffeine +import io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager +import io.wdsj.asw.bukkit.setting.PluginSettings +import java.util.concurrent.TimeUnit + +/** + * Cache utilities class for Book detections. + * @author HaHaWTH & HeyWTF_IS_That and 0D00_0721 + */ +object BookCache { + private lateinit var cache: Cache + fun isBookCached(content: String): Boolean { + return cache.getIfPresent(content) != null + } + + fun addToBookCache(content: String, processedContent: String, sensitiveWordList: List) { + cache.put(content, BookCacheEntry(processedContent, sensitiveWordList)) + } + + /** + * Retrieves the processed book content from the cache. + * Warning: This method is only safely called after checking if the book is cached. + * @param content The content of the book. + * @return The processed book content. + */ + @Throws(NoSuchElementException::class) + fun getCachedProcessedBookContent(content: String): String { + val entry = cache.getIfPresent(content) ?: throw NoSuchElementException("Book not found in cache") + return entry.processedContent + } + + /** + * Retrieves the list of sensitive words from the cache. + * Warning: This method is only safely called after checking if the book is cached. + * @param content The content of the book. + * @return The list of sensitive words. + */ + @Throws(NoSuchElementException::class) + fun getCachedBookSensitiveWordList(content: String): List { + val entry = cache.getIfPresent(content) ?: throw NoSuchElementException("Book not found in cache") + return entry.sensitiveWordList + } + + @JvmStatic + fun invalidateAll() { + cache.invalidateAll() + } + + @JvmStatic + fun initialize() { + cache = Caffeine.newBuilder() + .maximumSize( + settingsManager.getProperty(PluginSettings.BOOK_MAXIMUM_CACHE_SIZE).toLong() + ) + .expireAfterWrite( + settingsManager.getProperty(PluginSettings.BOOK_CACHE_EXPIRE_TIME).toLong(), + TimeUnit.MINUTES + ) + .build() + } + + + /** + * Inner class to encapsulate the processed book content and its list of sensitive words. + */ + private class BookCacheEntry(val processedContent: String, val sensitiveWordList: List) +} \ No newline at end of file diff --git a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/context/ChatContext.kt b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/context/ChatContext.kt new file mode 100644 index 0000000..92c6239 --- /dev/null +++ b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/context/ChatContext.kt @@ -0,0 +1,54 @@ +package io.wdsj.asw.bukkit.util.context + +import io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager +import io.wdsj.asw.bukkit.setting.PluginSettings +import io.wdsj.asw.common.datatype.TimedString +import org.bukkit.entity.Player +import java.util.* +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentLinkedDeque + +object ChatContext { + private val chatHistory = ConcurrentHashMap>() + + /** + * Add player message to history + */ + fun addMessage(player: Player, message: String) { + val uuid = player.uniqueId + val history = chatHistory.getOrPut(uuid) { ConcurrentLinkedDeque() } + while (history.size >= settingsManager.getProperty(PluginSettings.CHAT_CONTEXT_MAX_SIZE)) { + history.pollFirst() + } + if (message.trim().isEmpty()) return + history.offerLast(TimedString.of(message.trim())) + } + + fun getHistory(player: Player): Deque { + val uuid = player.uniqueId + val tsHistory = chatHistory.getOrPut(uuid) { ConcurrentLinkedDeque() } + if (tsHistory.isEmpty()) return ConcurrentLinkedDeque() + tsHistory.removeIf { + (System.currentTimeMillis() - it.time) / 1000 > settingsManager.getProperty( + PluginSettings.CHAT_CONTEXT_TIME_LIMIT + ) + } + return tsHistory.mapTo(ConcurrentLinkedDeque()) { it.string } + } + + fun clearPlayerContext(player: Player) { + val uuid = player.uniqueId + chatHistory.remove(uuid) + } + + fun pollPlayerContext(player: Player) { + val uuid = player.uniqueId + val history = chatHistory[uuid] + history?.pollLast() + } + + @JvmStatic + fun forceClearContext() { + chatHistory.clear() + } +} diff --git a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/context/SignContext.kt b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/context/SignContext.kt new file mode 100644 index 0000000..6304055 --- /dev/null +++ b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/context/SignContext.kt @@ -0,0 +1,55 @@ +package io.wdsj.asw.bukkit.util.context + +import io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager +import io.wdsj.asw.bukkit.setting.PluginSettings +import io.wdsj.asw.common.datatype.TimedString +import org.bukkit.entity.Player +import java.util.* +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentLinkedDeque + +object SignContext { + private val signEditHistory = ConcurrentHashMap>() + + /** + * Add player message to history + */ + fun addMessage(player: Player, message: String) { + val uuid = player.uniqueId + val history = signEditHistory.getOrPut(uuid) { ConcurrentLinkedDeque() } + while (history.size >= settingsManager.getProperty(PluginSettings.SIGN_CONTEXT_MAX_SIZE)) { + history.pollFirst() + } + if (message.trim().isEmpty()) return + history.offerLast(TimedString.of(message.trim())) + } + + fun getHistory(player: Player): Deque { + val uuid = player.uniqueId + val tsHistory = signEditHistory.getOrPut(uuid) { ConcurrentLinkedDeque() } + if (tsHistory.isEmpty()) return ConcurrentLinkedDeque() + tsHistory.removeIf { + (System.currentTimeMillis() - it.time) / 1000 > settingsManager.getProperty( + PluginSettings.SIGN_CONTEXT_TIME_LIMIT + ) + } + return tsHistory.mapTo(ConcurrentLinkedDeque()) { it.string } + } + + fun clearPlayerContext(player: Player) { + val uuid = player.uniqueId + signEditHistory.remove(uuid) + } + + @JvmStatic + fun pollPlayerContext(player: Player) { + val uuid = player.uniqueId + val history = signEditHistory[uuid] + history?.pollLast() + } + + @JvmStatic + fun forceClearContext() { + signEditHistory.clear() + } +} diff --git a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/message/MessageUtils.kt b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/message/MessageUtils.kt new file mode 100644 index 0000000..715d8df --- /dev/null +++ b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/message/MessageUtils.kt @@ -0,0 +1,33 @@ +package io.wdsj.asw.bukkit.util.message + +import ch.jalu.configme.properties.Property +import io.wdsj.asw.bukkit.AdvancedSensitiveWords +import org.bukkit.ChatColor +import org.bukkit.command.CommandSender + +object MessageUtils { + private const val AMPERSAND_CHAR: Char = '&' + + @JvmStatic + fun retrieveMessage(property: Property): String { + return ChatColor.translateAlternateColorCodes( + AMPERSAND_CHAR, + AdvancedSensitiveWords.messagesManager.getProperty(property) + ) + } + + @JvmStatic + fun sendMessage(sender: CommandSender, property: Property) { + val msg = retrieveMessage(property) + if (msg.isNotEmpty()) { + sender.sendMessage(msg) + } + } + + @JvmStatic + fun sendMessage(sender: CommandSender, message: String) { + if (message.isNotEmpty()) { + sender.sendMessage(ChatColor.translateAlternateColorCodes(AMPERSAND_CHAR, message)) + } + } +}