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