diff --git a/bukkit/pom.xml b/bukkit/pom.xml index bd8a2ce..a7a8672 100644 --- a/bukkit/pom.xml +++ b/bukkit/pom.xml @@ -10,7 +10,7 @@ io.wdsj AdvancedSensitiveWords - 1.2 + 1.3 ../pom.xml @@ -399,11 +399,19 @@ compile + + + io.github.givimad + whisper-jni + 1.6.1 + provided + + io.wdsj AdvancedSensitiveWords-common - 1.2 + 1.3 compile 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 4b39408..553d214 100644 --- a/bukkit/src/main/java/io/wdsj/asw/bukkit/AdvancedSensitiveWords.java +++ b/bukkit/src/main/java/io/wdsj/asw/bukkit/AdvancedSensitiveWords.java @@ -219,6 +219,9 @@ public void onEnable() { } if (Bukkit.getPluginManager().isPluginEnabled("voicechat") && settingsManager.getProperty(PluginSettings.HOOK_SIMPLE_VOICE_CHAT)) { + if (settingsManager.getProperty(PluginSettings.VOICE_REALTIME_TRANSCRIBING)) { + libraryService.loadWhisperJniOptional(); + } voiceChatHookService = new VoiceChatHookService(this); voiceChatHookService.register(); } diff --git a/bukkit/src/main/java/io/wdsj/asw/bukkit/integration/voicechat/VoiceChatExtension.java b/bukkit/src/main/java/io/wdsj/asw/bukkit/integration/voicechat/VoiceChatExtension.java index 9c8b5d4..45e4ba0 100644 --- a/bukkit/src/main/java/io/wdsj/asw/bukkit/integration/voicechat/VoiceChatExtension.java +++ b/bukkit/src/main/java/io/wdsj/asw/bukkit/integration/voicechat/VoiceChatExtension.java @@ -2,15 +2,30 @@ import de.maxhenkel.voicechat.api.VoicechatApi; import de.maxhenkel.voicechat.api.VoicechatPlugin; +import de.maxhenkel.voicechat.api.audio.AudioConverter; import de.maxhenkel.voicechat.api.events.EventRegistration; import de.maxhenkel.voicechat.api.events.MicrophonePacketEvent; +import de.maxhenkel.voicechat.api.events.PlayerConnectedEvent; +import de.maxhenkel.voicechat.api.events.PlayerDisconnectedEvent; +import de.maxhenkel.voicechat.api.opus.OpusDecoder; import io.wdsj.asw.bukkit.manage.punish.PlayerShadowController; +import io.wdsj.asw.bukkit.permission.PermissionsEnum; +import io.wdsj.asw.bukkit.permission.cache.CachingPermTool; +import io.wdsj.asw.bukkit.setting.PluginSettings; import org.bukkit.entity.Player; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager; + public class VoiceChatExtension implements VoicechatPlugin { + public final Map connectedPlayers; public VoiceChatExtension() { + connectedPlayers = new ConcurrentHashMap<>(); } /** @@ -39,6 +54,8 @@ public void initialize(VoicechatApi api) { @Override public void registerEvents(EventRegistration registration) { registration.registerEvent(MicrophonePacketEvent.class, this::onMicrophone); + registration.registerEvent(PlayerConnectedEvent.class, this::onConnect); + registration.registerEvent(PlayerDisconnectedEvent.class, this::onDisconnect); } /** @@ -46,7 +63,7 @@ public void registerEvents(EventRegistration registration) { * * @param event the microphone packet event */ - private void onMicrophone(MicrophonePacketEvent event) { // TODO: incomplete version, plans to add real-time voice transcribe + private void onMicrophone(MicrophonePacketEvent event) { if (event.getSenderConnection() == null) { return; } @@ -55,8 +72,48 @@ private void onMicrophone(MicrophonePacketEvent event) { // TODO: incomplete ver } Player player = (Player) event.getSenderConnection().getPlayer().getPlayer(); - if (PlayerShadowController.isShadowed(player)) { + if (PlayerShadowController.isShadowed(player) && settingsManager.getProperty(PluginSettings.VOICE_SYNC_SHADOW)) { event.cancel(); + return; + } + + if (!settingsManager.getProperty(PluginSettings.VOICE_REALTIME_TRANSCRIBING)) return; + if (event.getPacket() == null || CachingPermTool.hasPermission(PermissionsEnum.BYPASS, player)) return; + OpusDecoder decoder = event.getVoicechat().createDecoder(); + AudioConverter converter = event.getVoicechat().getAudioConverter(); + float[] newData = converter.shortsToFloats(decoder.decode(event.getPacket().getOpusEncodedData())); + if (connectedPlayers.get(player.getUniqueId()) != null) { + float[] oldData = connectedPlayers.get(player.getUniqueId()); + float[] result = new float[oldData.length + newData.length]; + System.arraycopy(oldData, 0, result, 0, oldData.length); + System.arraycopy(newData, 0, result, oldData.length, newData.length); + connectedPlayers.put(player.getUniqueId(), result); + } else { + connectedPlayers.put(player.getUniqueId(), newData); + } + decoder.close(); + } + + private void onConnect(PlayerConnectedEvent event) { + if (!settingsManager.getProperty(PluginSettings.VOICE_REALTIME_TRANSCRIBING)) return; + if (event.getConnection() == null) { + return; + } + if (!(event.getConnection().getPlayer().getPlayer() instanceof Player)) { + return; + } + Player player = (Player) event.getConnection().getPlayer().getPlayer(); + connectedPlayers.put(player.getUniqueId(), new float[]{}); + } + + private void onDisconnect(PlayerDisconnectedEvent event) { + if (!settingsManager.getProperty(PluginSettings.VOICE_REALTIME_TRANSCRIBING)) { + if (!connectedPlayers.isEmpty()) connectedPlayers.clear(); + return; + } + if (event.getPlayerUuid() == null) { + return; } + connectedPlayers.remove(event.getPlayerUuid()); } } diff --git a/bukkit/src/main/java/io/wdsj/asw/bukkit/integration/voicechat/WhisperVoiceTranscribeTool.java b/bukkit/src/main/java/io/wdsj/asw/bukkit/integration/voicechat/WhisperVoiceTranscribeTool.java new file mode 100644 index 0000000..5ff174e --- /dev/null +++ b/bukkit/src/main/java/io/wdsj/asw/bukkit/integration/voicechat/WhisperVoiceTranscribeTool.java @@ -0,0 +1,81 @@ +package io.wdsj.asw.bukkit.integration.voicechat; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import io.github.givimad.whisperjni.WhisperContext; +import io.github.givimad.whisperjni.WhisperFullParams; +import io.github.givimad.whisperjni.WhisperJNI; +import io.wdsj.asw.bukkit.AdvancedSensitiveWords; +import io.wdsj.asw.bukkit.setting.PluginSettings; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.concurrent.*; + +import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.LOGGER; +import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager; + +public class WhisperVoiceTranscribeTool { + private final WhisperJNI whisper; + private final WhisperContext whisperCtx; + private final ThreadPoolExecutor threadPool; + + public WhisperVoiceTranscribeTool() { + try { + WhisperJNI.loadLibrary(); + if (settingsManager.getProperty(PluginSettings.VOICE_DEBUG)) { + WhisperJNI.LibraryLogger logger = log -> LOGGER.info("[WhisperJNI] " + log); + WhisperJNI.setLibraryLogger(logger); + LOGGER.info("WhisperJNI debug logger enabled"); + } else { + WhisperJNI.setLibraryLogger(null); + } + whisper = new WhisperJNI(); + File dataFolder = Paths.get(AdvancedSensitiveWords.getInstance().getDataFolder().getPath(), "whisper", "model").toFile(); + if (Files.notExists(dataFolder.toPath())) { + Files.createDirectories(dataFolder.toPath()); + } + int coreCount = Runtime.getRuntime().availableProcessors(); + int maxThread = settingsManager.getProperty(PluginSettings.VOICE_REALTIME_TRANSCRIBING_MAX_THREAD); + if (maxThread <= -1) { + maxThread = coreCount; + } else if (maxThread == 0) { + maxThread = coreCount * 2; + } + LOGGER.info("Using " + maxThread + " thread(s) for voice transcription"); + whisperCtx = whisper.init(Paths.get(dataFolder.getPath(), settingsManager.getProperty(PluginSettings.VOICE_MODEL_NAME))); + RejectedExecutionHandler handler = (r, executor) -> LOGGER.info("Rejected execution of transcription task, thread pool is full"); + threadPool = new ThreadPoolExecutor(Math.min(coreCount, maxThread), + maxThread, + settingsManager.getProperty(PluginSettings.VOICE_REALTIME_TRANSCRIBING_THREAD_KEEP_ALIVE), + TimeUnit.SECONDS, + new LinkedBlockingQueue<>(100), + new ThreadFactoryBuilder() + .setNameFormat("ASW Whisper Transcribe Thread-%d") + .setDaemon(true) + .build(), + handler); + threadPool.allowCoreThreadTimeOut(true); + threadPool.prestartAllCoreThreads(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + public CompletableFuture transcribe(float[] data) { + return CompletableFuture.supplyAsync(() -> { + WhisperFullParams params = new WhisperFullParams(); + int result = whisper.full(whisperCtx, params, data, data.length); + if (result != 0) { + throw new RuntimeException("Transcription failed with code " + result); + } + whisper.fullNSegments(whisperCtx); + return whisper.fullGetSegmentText(whisperCtx,0); + }, threadPool); + } + + public void shutdown() { + threadPool.shutdownNow(); + whisperCtx.close(); + } +} diff --git a/bukkit/src/main/java/io/wdsj/asw/bukkit/service/BukkitLibraryService.java b/bukkit/src/main/java/io/wdsj/asw/bukkit/service/BukkitLibraryService.java index f32834e..c6d78b0 100644 --- a/bukkit/src/main/java/io/wdsj/asw/bukkit/service/BukkitLibraryService.java +++ b/bukkit/src/main/java/io/wdsj/asw/bukkit/service/BukkitLibraryService.java @@ -45,4 +45,12 @@ public BukkitLibraryService(AdvancedSensitiveWords plugin) { public void loadRequired() { libraryManager.loadLibraries(openai4j, caffeine, ollama4j); } + + public void loadWhisperJniOptional() { + libraryManager.loadLibraries(Library.builder() + .groupId("io{}github{}givimad") + .artifactId("whisper-jni") + .version("1.6.1") + .build()); + } } diff --git a/bukkit/src/main/java/io/wdsj/asw/bukkit/service/hook/VoiceChatHookService.java b/bukkit/src/main/java/io/wdsj/asw/bukkit/service/hook/VoiceChatHookService.java index 10cfdf9..04e923d 100644 --- a/bukkit/src/main/java/io/wdsj/asw/bukkit/service/hook/VoiceChatHookService.java +++ b/bukkit/src/main/java/io/wdsj/asw/bukkit/service/hook/VoiceChatHookService.java @@ -1,14 +1,22 @@ package io.wdsj.asw.bukkit.service.hook; +import com.github.Anon8281.universalScheduler.scheduling.tasks.MyScheduledTask; import de.maxhenkel.voicechat.api.BukkitVoicechatService; import io.wdsj.asw.bukkit.AdvancedSensitiveWords; import io.wdsj.asw.bukkit.integration.voicechat.VoiceChatExtension; +import io.wdsj.asw.bukkit.integration.voicechat.WhisperVoiceTranscribeTool; +import io.wdsj.asw.bukkit.setting.PluginSettings; +import io.wdsj.asw.bukkit.task.voicechat.VoiceChatTranscribeTask; +import io.wdsj.asw.bukkit.util.SchedulingUtils; import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.LOGGER; +import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager; public class VoiceChatHookService { private final AdvancedSensitiveWords plugin; private VoiceChatExtension voiceChatExtension; + private MyScheduledTask transcribeTask; + private WhisperVoiceTranscribeTool transcribeTool; public VoiceChatHookService(AdvancedSensitiveWords plugin) { this.plugin = plugin; } @@ -20,6 +28,11 @@ public void register() { voiceChatExtension = new VoiceChatExtension(); service.registerPlugin(voiceChatExtension); LOGGER.info("Successfully hooked into voicechat."); + if (settingsManager.getProperty(PluginSettings.VOICE_REALTIME_TRANSCRIBING)) { + transcribeTool = new WhisperVoiceTranscribeTool(); + long interval = settingsManager.getProperty(PluginSettings.VOICE_CHECK_INTERVAL); + transcribeTask = new VoiceChatTranscribeTask(voiceChatExtension, transcribeTool).runTaskTimerAsynchronously(plugin, interval * 20L, interval * 20L); + } } catch (Exception e) { LOGGER.warning("Failed to register voicechat listener." + " This should not happen, please report to the author: " + e.getMessage()); @@ -33,5 +46,9 @@ public void unregister() { if (voiceChatExtension != null) { plugin.getServer().getServicesManager().unregister(voiceChatExtension); } + SchedulingUtils.cancelTaskSafely(transcribeTask); + if (transcribeTool != null) { + transcribeTool.shutdown(); + } } } diff --git a/bukkit/src/main/java/io/wdsj/asw/bukkit/setting/PluginMessages.java b/bukkit/src/main/java/io/wdsj/asw/bukkit/setting/PluginMessages.java index 1293539..551b938 100644 --- a/bukkit/src/main/java/io/wdsj/asw/bukkit/setting/PluginMessages.java +++ b/bukkit/src/main/java/io/wdsj/asw/bukkit/setting/PluginMessages.java @@ -20,6 +20,8 @@ public class PluginMessages implements SettingsHolder { public static final Property MESSAGE_ON_NAME = newProperty("Name.messageOnName", "&c您的用户名包含敏感词,请修改您的用户名或联系管理员."); @Comment("玩家物品包含敏感词时的消息") public static final Property MESSAGE_ON_ITEM = newProperty("Item.messageOnItem", "&c您的物品包含敏感词."); + @Comment("玩家发送违规语音消息时的提示") + public static final Property MESSAGE_ON_VOICE = newProperty("Voice.messageOnVoice", "&c请勿发送违规语音."); @Comment("插件重载消息") public static final Property MESSAGE_ON_COMMAND_RELOAD = newProperty("Plugin.messageOnCommandReload", "&aAdvancedSensitiveWords 已重新加载."); @Comment("违规次数重置消息") @@ -74,6 +76,7 @@ public void registerComments(CommentsConfiguration conf) { conf.setComment("Sign", "告示牌检测消息"); conf.setComment("Anvil", "铁砧重命名检测消息"); conf.setComment("Name", "玩家名检测消息"); + conf.setComment("Voice", "玩家语音检测消息"); } private PluginMessages() { diff --git a/bukkit/src/main/java/io/wdsj/asw/bukkit/setting/PluginSettings.java b/bukkit/src/main/java/io/wdsj/asw/bukkit/setting/PluginSettings.java index b4e4cfa..41b8b0f 100644 --- a/bukkit/src/main/java/io/wdsj/asw/bukkit/setting/PluginSettings.java +++ b/bukkit/src/main/java/io/wdsj/asw/bukkit/setting/PluginSettings.java @@ -412,6 +412,32 @@ public class PluginSettings implements SettingsHolder { "Whether to enable punishment"}) public static final Property ITEM_PUNISH = newProperty("Item.punish", true); + @Comment({"*是否启用实时语音转录检测(Java 11+)", + "*Whether to enable real-time voice transcribing(Java 11+)"}) + public static final Property VOICE_REALTIME_TRANSCRIBING = newProperty("Voice.realtimeTranscribing", false); + @Comment({"*实时语音转录检测最大线程数", + "*Maximum threads for real-time voice transcribing"}) + public static final Property VOICE_REALTIME_TRANSCRIBING_MAX_THREAD = newProperty("Voice.realtimeTranscribingMaxThread", -1); + public static final Property VOICE_REALTIME_TRANSCRIBING_THREAD_KEEP_ALIVE = newProperty("Voice.realtimeTranscribingThreadKeepAlive", 60L); + @Comment({"*模型文件名", + "*Model file name"}) + public static final Property VOICE_MODEL_NAME = newProperty("Voice.modelName", "ggml-tiny.bin"); + @Comment({"*是否启用调试日志", + "*Whether to enable debug logging"}) + public static final Property VOICE_DEBUG = newProperty("Voice.debug", false); + @Comment({"*检测间隔(单位: 秒)", + "*Check interval(in seconds)"}) + public static final Property VOICE_CHECK_INTERVAL = newProperty("Voice.checkInterval", 40L); + @Comment({"*是否在违规时通知玩家", + "*Whether to notify the player when violated"}) + public static final Property VOICE_SEND_MESSAGE = newProperty("Voice.sendMessage", true); + @Comment({"是否启用惩罚", + "Whether to enable the punish"}) + public static final Property VOICE_PUNISH = newProperty("Voice.punish", true); + @Comment({"是否在Shadow惩罚时同步至语音", + "Whether to sync Shadow punishment to voice chat"}) + public static final Property VOICE_SYNC_SHADOW = newProperty("Voice.syncShadow", true); + @Override public void registerComments(CommentsConfiguration conf) { @@ -426,6 +452,7 @@ public void registerComments(CommentsConfiguration conf) { conf.setComment("Anvil", "Anvil rename detection"); conf.setComment("Name", "Player name detection"); conf.setComment("Item", "Item detection"); + conf.setComment("Voice", "Voice detection (Requires hookVoiceChat enabled)"); } // Do not instantiate. diff --git a/bukkit/src/main/java/io/wdsj/asw/bukkit/task/voicechat/VoiceChatTranscribeTask.java b/bukkit/src/main/java/io/wdsj/asw/bukkit/task/voicechat/VoiceChatTranscribeTask.java new file mode 100644 index 0000000..8429aac --- /dev/null +++ b/bukkit/src/main/java/io/wdsj/asw/bukkit/task/voicechat/VoiceChatTranscribeTask.java @@ -0,0 +1,81 @@ +package io.wdsj.asw.bukkit.task.voicechat; + +import com.github.Anon8281.universalScheduler.UniversalRunnable; +import io.wdsj.asw.bukkit.integration.voicechat.VoiceChatExtension; +import io.wdsj.asw.bukkit.integration.voicechat.WhisperVoiceTranscribeTool; +import io.wdsj.asw.bukkit.manage.notice.Notifier; +import io.wdsj.asw.bukkit.manage.punish.Punishment; +import io.wdsj.asw.bukkit.manage.punish.ViolationCounter; +import io.wdsj.asw.bukkit.proxy.bungee.BungeeSender; +import io.wdsj.asw.bukkit.proxy.velocity.VelocitySender; +import io.wdsj.asw.bukkit.setting.PluginMessages; +import io.wdsj.asw.bukkit.setting.PluginSettings; +import io.wdsj.asw.bukkit.type.ModuleType; +import io.wdsj.asw.bukkit.util.LoggingUtils; +import io.wdsj.asw.bukkit.util.SchedulingUtils; +import io.wdsj.asw.bukkit.util.TimingUtils; +import io.wdsj.asw.bukkit.util.Utils; +import io.wdsj.asw.bukkit.util.message.MessageUtils; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.*; + +public class VoiceChatTranscribeTask extends UniversalRunnable { + private final VoiceChatExtension extension; + private final WhisperVoiceTranscribeTool transcribeTool; + public VoiceChatTranscribeTask(VoiceChatExtension extension, WhisperVoiceTranscribeTool transcribeTool) { + this.extension = extension; + this.transcribeTool = transcribeTool; + } + @Override + public void run() { + if (!settingsManager.getProperty(PluginSettings.VOICE_REALTIME_TRANSCRIBING)) { + if (!extension.connectedPlayers.isEmpty()) extension.connectedPlayers.clear(); + return; + } + if (extension.connectedPlayers.isEmpty()) return; + Set> entries = extension.connectedPlayers.entrySet(); + for (Map.Entry entry : entries) { + if (!isInitialized) continue; + UUID uuid = entry.getKey(); + float[] data = entry.getValue(); + Player player = SchedulingUtils.callSyncMethod(() -> Bukkit.getPlayer(uuid)); + extension.connectedPlayers.remove(uuid); // Early remove before transcribing + if (player == null) continue; + transcribeTool.transcribe(data) + .thenAccept(text -> { + if (text.isEmpty()) return; + List censoredWordList = sensitiveWordBs.findAll(text); + long startTime = System.currentTimeMillis(); + if (!censoredWordList.isEmpty()) { + Utils.messagesFilteredNum.getAndIncrement(); + if (settingsManager.getProperty(PluginSettings.VOICE_SEND_MESSAGE)) { + MessageUtils.sendMessage(player, PluginMessages.MESSAGE_ON_VOICE); + } + if (settingsManager.getProperty(PluginSettings.LOG_VIOLATION)) { + LoggingUtils.logViolation(player.getName() + "(IP: " + Utils.getPlayerIp(player) + ")(Voice)", text + censoredWordList); + } + ViolationCounter.incrementViolationCount(player); + if (settingsManager.getProperty(PluginSettings.HOOK_VELOCITY)) { + VelocitySender.sendNotifyMessage(player, ModuleType.VOICE, text, censoredWordList); + } + if (settingsManager.getProperty(PluginSettings.HOOK_BUNGEECORD)) { + BungeeSender.sendNotifyMessage(player, ModuleType.VOICE, text, censoredWordList); + } + long endTime = System.currentTimeMillis(); + TimingUtils.addProcessStatistic(endTime, startTime); + if (settingsManager.getProperty(PluginSettings.NOTICE_OPERATOR)) Notifier.notice(player, ModuleType.VOICE, text, censoredWordList); + if (settingsManager.getProperty(PluginSettings.VOICE_PUNISH)) { + SchedulingUtils.runSyncIfNotOnMainThread(() -> Punishment.punish(player)); + } + } + }); + } + } +} diff --git a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/type/ModuleType.kt b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/type/ModuleType.kt index b0d7268..09e3c75 100644 --- a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/type/ModuleType.kt +++ b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/type/ModuleType.kt @@ -10,5 +10,6 @@ enum class ModuleType { ANVIL, BOOK, NAME, - ITEM + ITEM, + VOICE } \ No newline at end of file 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 index 9626cc8..83a15aa 100644 --- a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/LoggingUtils.kt +++ b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/LoggingUtils.kt @@ -17,6 +17,7 @@ import java.util.concurrent.Executors object LoggingUtils { private val dateFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd-HH:mm:ss") private lateinit var loggingThreadPool: ExecutorService + @JvmStatic fun logViolation(playerName: String, violationReason: String) { loggingThreadPool.submit { val formattedDate = LocalDateTime.now().format(dateFormatter) 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 index b7ca885..7d8d485 100644 --- a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/TimingUtils.kt +++ b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/TimingUtils.kt @@ -10,6 +10,7 @@ object TimingUtils { @JvmStatic val jvmVersion: String = System.getProperties().getProperty("java.version") ?: "Unknown" + @JvmStatic fun addProcessStatistic(endTime: Long, startTime: Long) { val processTime = endTime - startTime while (processStatistic.size >= 20) { 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 index 2944061..c4f2923 100644 --- a/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/VirtualThreadUtils.kt +++ b/bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/VirtualThreadUtils.kt @@ -1,5 +1,6 @@ package io.wdsj.asw.bukkit.util +import java.lang.reflect.Method import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.concurrent.ThreadFactory @@ -8,52 +9,68 @@ import java.util.concurrent.ThreadFactory * Utility class for virtual threads which are introduced in Java 21 */ object VirtualThreadUtils { - private var virtualThreadFactory: ThreadFactory? + private var methodVirtualThreadFactory: Method? + private var methodThreadOfVirtual: Method? - private var virtualThreadPerTaskExecutor: ExecutorService? + private var methodVirtualThreadPerTaskExecutor: Method? init { try { - val ofVirtual = Thread::class.java.getMethod("ofVirtual") + methodThreadOfVirtual = 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 + methodVirtualThreadFactory= threadBuilder.getMethod("factory") + methodThreadOfVirtual?.isAccessible = true + methodVirtualThreadFactory?.isAccessible = true } catch (e: Exception) { - virtualThreadFactory = null + methodThreadOfVirtual = null + methodVirtualThreadFactory = null } try { - val method = Executors::class.java.getMethod("newVirtualThreadPerTaskExecutor") - method.isAccessible = true - virtualThreadPerTaskExecutor = method.invoke(null) as ExecutorService + methodVirtualThreadPerTaskExecutor = Executors::class.java.getMethod("newVirtualThreadPerTaskExecutor") + methodVirtualThreadPerTaskExecutor?.isAccessible = true } catch (e: Exception) { - virtualThreadPerTaskExecutor = null + methodVirtualThreadPerTaskExecutor = null } } @JvmStatic fun newVirtualThreadFactory(): ThreadFactory? { - return virtualThreadFactory + return invokeOfVirtualFactory() } @JvmStatic fun newVirtualThreadPerTaskExecutor(): ExecutorService? { - return virtualThreadPerTaskExecutor + return invokeNewVirtualThreadPerTaskExecutor() } @JvmStatic fun newVirtualThreadFactoryOrProvided(threadFactory: ThreadFactory): ThreadFactory { - return virtualThreadFactory ?: threadFactory + return invokeOfVirtualFactory() ?: threadFactory } @JvmStatic fun newVirtualThreadFactoryOrDefault(): ThreadFactory { - return virtualThreadFactory ?: Executors.defaultThreadFactory() + return invokeOfVirtualFactory() ?: Executors.defaultThreadFactory() } @JvmStatic fun newVirtualThreadPerTaskExecutorOrProvided(executorService: ExecutorService): ExecutorService { - return virtualThreadPerTaskExecutor ?: executorService + return invokeNewVirtualThreadPerTaskExecutor() ?: executorService + } + + private fun invokeNewVirtualThreadPerTaskExecutor(): ExecutorService? { + return try { + methodVirtualThreadPerTaskExecutor?.invoke(null) as ExecutorService + } catch (e: Exception) { + null + } + } + + private fun invokeOfVirtualFactory(): ThreadFactory? { + return try { + methodVirtualThreadFactory?.invoke(methodThreadOfVirtual?.invoke(null)) as ThreadFactory + } catch (e: Exception) { + null + } } } diff --git a/bukkit/src/main/resources/messages_en.yml b/bukkit/src/main/resources/messages_en.yml index f7cec57..469c265 100644 --- a/bukkit/src/main/resources/messages_en.yml +++ b/bukkit/src/main/resources/messages_en.yml @@ -23,6 +23,10 @@ Name: Item: #Messages on Player item messageOnItem: '&cThe item you held contains sensitive words.' +#Player voice chat related messages +Voice: + #Messages on Voice chat + messageOnVoice: '&cDo not send inappropriate voice.' #Plugin related messages Plugin: #Reload command diff --git a/bukkit/src/main/resources/messages_zhcn.yml b/bukkit/src/main/resources/messages_zhcn.yml index 4158915..675ee5b 100644 --- a/bukkit/src/main/resources/messages_zhcn.yml +++ b/bukkit/src/main/resources/messages_zhcn.yml @@ -23,6 +23,10 @@ Name: Item: # 玩家物品包含敏感词时的消息 messageOnItem: '&c您的物品包含敏感词.' +# 玩家语音检测消息 +Voice: + # 玩家发送违规语音消息时的提示 + messageOnVoice: '&c请勿发送违规语音.' # 插件消息 Plugin: # 插件重载消息 diff --git a/bungee/pom.xml b/bungee/pom.xml index e611876..bd20638 100644 --- a/bungee/pom.xml +++ b/bungee/pom.xml @@ -10,7 +10,7 @@ io.wdsj AdvancedSensitiveWords - 1.2 + 1.3 ../pom.xml @@ -102,7 +102,7 @@ io.wdsj AdvancedSensitiveWords-common - 1.2 + 1.3 compile diff --git a/common/pom.xml b/common/pom.xml index 05c67db..8cc5af8 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -7,7 +7,7 @@ io.wdsj AdvancedSensitiveWords - 1.2 + 1.3 ../pom.xml diff --git a/pom.xml b/pom.xml index 67a66ad..7e97a20 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ io.wdsj AdvancedSensitiveWords - 1.2 + 1.3 pom AdvancedSensitiveWords @@ -14,7 +14,7 @@ 1.8 UTF-8 - release + dev diff --git a/velocity/pom.xml b/velocity/pom.xml index 631f060..afbf468 100644 --- a/velocity/pom.xml +++ b/velocity/pom.xml @@ -10,7 +10,7 @@ io.wdsj AdvancedSensitiveWords - 1.2 + 1.3 ../pom.xml @@ -116,7 +116,7 @@ io.wdsj AdvancedSensitiveWords-common - 1.2 + 1.3 compile