├── .github ├── dependabot.yml └── workflows │ └── maven.yml ├── .gitignore ├── LICENSE ├── README.md ├── bukkit ├── pom.xml └── src │ └── main │ ├── java │ └── io │ │ └── wdsj │ │ └── asw │ │ └── bukkit │ │ ├── AdvancedSensitiveWords.java │ │ ├── ai │ │ ├── AIProcessor.java │ │ ├── OllamaProcessor.java │ │ └── OpenAIProcessor.java │ │ ├── annotation │ │ └── PaperEventHandler.java │ │ ├── command │ │ ├── ConstructCommandExecutor.java │ │ └── ConstructTabCompleter.java │ │ ├── core │ │ └── condition │ │ │ └── WordResultConditionNumMatch.java │ │ ├── integration │ │ ├── placeholder │ │ │ └── ASWExpansion.java │ │ └── voicechat │ │ │ ├── VoiceChatExtension.java │ │ │ └── WhisperVoiceTranscribeTool.java │ │ ├── manage │ │ ├── notice │ │ │ └── Notifier.java │ │ └── punish │ │ │ ├── PlayerAltController.java │ │ │ ├── PlayerShadowController.java │ │ │ ├── Punishment.java │ │ │ ├── PunishmentType.java │ │ │ └── ViolationCounter.java │ │ ├── method │ │ ├── CharIgnore.java │ │ ├── ExternalWordAllow.java │ │ ├── ExternalWordDeny.java │ │ ├── OnlineWordDeny.java │ │ ├── WildCardLineResolver.java │ │ ├── WordAllow.java │ │ ├── WordDeny.java │ │ └── WordReplace.java │ │ ├── permission │ │ ├── PermissionsEnum.java │ │ └── cache │ │ │ └── CachingPermTool.java │ │ ├── proxy │ │ ├── bungee │ │ │ ├── BungeeCordChannel.java │ │ │ ├── BungeeReceiver.java │ │ │ └── BungeeSender.java │ │ └── velocity │ │ │ ├── VelocityChannel.java │ │ │ ├── VelocityReceiver.java │ │ │ └── VelocitySender.java │ │ ├── service │ │ ├── BukkitLibraryService.java │ │ ├── ListenerService.java │ │ └── hook │ │ │ └── VoiceChatHookService.java │ │ ├── setting │ │ ├── PluginMessages.java │ │ └── PluginSettings.java │ │ └── task │ │ ├── punish │ │ └── ViolationResetTask.java │ │ └── voicechat │ │ └── VoiceChatTranscribeTask.java │ ├── kotlin │ └── io │ │ └── wdsj │ │ └── asw │ │ └── bukkit │ │ ├── listener │ │ ├── AltsListener.kt │ │ ├── AnvilListener.kt │ │ ├── BookListener.kt │ │ ├── BroadCastListener.kt │ │ ├── ChatListener.kt │ │ ├── CommandListener.kt │ │ ├── FakeMessageExecutor.kt │ │ ├── JoinUpdateNotifier.kt │ │ ├── PlayerItemListener.kt │ │ ├── PlayerLoginListener.kt │ │ ├── QuitDataCleaner.kt │ │ ├── ShadowListener.kt │ │ ├── SignListener.kt │ │ ├── abstraction │ │ │ └── AbstractFakeMessageExecutor.kt │ │ ├── packet │ │ │ ├── ASWBookPacketListener.kt │ │ │ └── ASWChatPacketListener.kt │ │ └── paper │ │ │ ├── PaperChatListener.kt │ │ │ └── PaperFakeMessageExecutor.kt │ │ ├── type │ │ └── ModuleType.kt │ │ └── util │ │ ├── LoggingUtils.kt │ │ ├── PlayerUtils.kt │ │ ├── SchedulingUtils.kt │ │ ├── TimingUtils.kt │ │ ├── Utils.kt │ │ ├── VirtualThreadUtils.kt │ │ ├── cache │ │ └── BookCache.kt │ │ ├── context │ │ ├── ChatContext.kt │ │ └── SignContext.kt │ │ └── message │ │ └── MessageUtils.kt │ └── resources │ ├── messages_en.yml │ ├── messages_zhcn.yml │ ├── plugin.yml │ ├── sensitive_word_dict.txt │ └── sensitive_word_dict_en.txt ├── bungee ├── pom.xml └── src │ └── main │ ├── java │ └── io │ │ └── wdsj │ │ └── asw │ │ └── bungee │ │ ├── AdvancedSensitiveWords.java │ │ ├── config │ │ └── Config.java │ │ └── listener │ │ └── PluginMessageListener.java │ └── resources │ └── bungee.yml ├── common ├── pom.xml └── src │ └── main │ └── java │ └── io │ └── wdsj │ └── asw │ └── common │ ├── constant │ └── networking │ │ └── ChannelDataConstant.java │ ├── datatype │ ├── TimedString.java │ └── io │ │ └── LimitedByteArrayDataOutput.java │ ├── template │ └── PluginVersionTemplate.java │ └── update │ └── Updater.java ├── extended_words └── zh_cn │ └── 偏旁部首变换.txt ├── pom.xml └── velocity ├── pom.xml └── src └── main └── java └── io └── wdsj └── asw └── velocity ├── AdvancedSensitiveWords.java ├── config └── Config.java └── subscriber └── PluginMessageForwarder.java /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "maven" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | ignore: 13 | - dependency-name: "com.comphenix.protocol:ProtocolLib" 14 | versions: ">=4.8.0" 15 | - dependency-name: "com.zaxxer:HikariCP" 16 | versions: ">=5.0.0" 17 | 18 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | name: AdvancedSensitiveWords CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | branches: 7 | - pro 8 | 9 | jobs: 10 | Build: 11 | strategy: 12 | matrix: 13 | jdkversion: [ 21 ] 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: actions/setup-java@v4 18 | with: 19 | distribution: 'temurin' 20 | java-version: ${{ matrix.jdkversion }} 21 | cache: 'maven' 22 | - name: Build 23 | run: mvn -V -B clean package --file pom.xml 24 | - name: Upload Bukkit Artifacts 25 | uses: actions/upload-artifact@v4 26 | with: 27 | name: Download-Bukkit 28 | path: ./bukkit/target/AdvancedSensitiveWords-bukkit.jar 29 | - name: Upload Velocity Artifacts 30 | uses: actions/upload-artifact@v4 31 | with: 32 | name: Download-Velocity 33 | path: ./velocity/target/AdvancedSensitiveWords-velocity.jar 34 | - name: Upload BungeeCord Artifacts 35 | uses: actions/upload-artifact@v4 36 | with: 37 | name: Download-BungeeCord 38 | path: ./bungee/target/AdvancedSensitiveWords-bungee.jar 39 | runtime-test: 40 | name: Plugin Runtime Test 41 | needs: [Build] 42 | runs-on: ubuntu-latest 43 | strategy: 44 | matrix: 45 | include: 46 | - mcVersion: '1.8.8' 47 | javaVersion: '8' 48 | - mcVersion: '1.12.2' 49 | javaVersion: '8' 50 | - mcVersion: '1.20.4' 51 | javaVersion: '17' 52 | - mcVersion: '1.21.1' 53 | javaVersion: '21' 54 | steps: 55 | - uses: HaHaWTH/minecraft-plugin-runtime-test@paper 56 | with: 57 | server-version: ${{ matrix.mcVersion }} 58 | java-version: ${{ matrix.javaVersion }} 59 | artifact-name: Download-Bukkit 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HaHaWTH/AdvancedSensitiveWords/b8a7cf3331dffbc9f9d898b142ea87710a7079bd/.gitignore -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AdvancedSensitiveWords 2 | If you trigger the sensitive word test you'll be penalised. 3 | One-stop-shop **ultimate** anti-swear solution for your Minecraft server! 4 | 5 | ![](https://socialify.git.ci/HaHaWTH/AdvancedSensitiveWords/image?description=1&descriptionEditable=One-stop-shop%20ultimate%20anti-swear%20solution%20for%20Minecraft&font=Inter&language=1&name=1&stargazers=1&theme=Auto) 6 | 7 | [![Available on SpigotMC](https://img.shields.io/badge/Available%20on%20SpigotMC-orange?style=for-the-badge&logo=SpigotMC&logoColor=FFFFFF)](https://www.spigotmc.org/resources/advancedsensitivewords.115484/) 8 | 9 | [![CodeFactor](https://www.codefactor.io/repository/github/hahawth/advancedsensitivewords/badge)](https://www.codefactor.io/repository/github/hahawth/advancedsensitivewords) 10 | [![QQ](https://img.shields.io/badge/QQ-361581545-blue)](https://qm.qq.com/q/sC52yJDrGi) 11 | 12 | ## Features 13 | 1. Using DFA(Deterministic Finite Automata) algorithm 14 | 2. Plug-and-play 15 | 3. Huge and high-quality default dictionary (Over 60,000 words) 16 | 4. Performance first, handling 1,000+ messages per second 17 | 5. 100% compatibility with chat plugins (Tested over 30+ plugins) 18 | 6. Full-customizable 19 | 7. Sign check support 20 | 8. Anvil check support 21 | 9. Book check support 22 | 10. Player name check support 23 | 11. **Chat context check** 24 | 12. **Sign multiple lines check** 25 | 13. **Book check with ignore lines support and cache** 26 | 14. Bedrock player compatibility 27 | 15. Compatibility with main stream login plugins (AuthMe, CatSeedLogin etc.) 28 | 16. Emoji and other unicode support 29 | 17. Chinese support 30 | 18. Fast processing depending on our custom data structure 31 | 19. Online sensitive word list support ([Repository here](https://github.com/HaHaWTH/ASW-OnlineWordList)) 32 | 20. Folia supported 33 | 21. OP notifications on player swore 34 | 22. Custom punishments (Effect, command, hostile, etc.) 35 | 23. Fake message support (Inspired by [Bilibili Avalon System](https://github.com/freedom-introvert/Research-on-Avalon-System-in-Bilibili-Comment-Area)) 36 | 24. PlaceHolder API expansion support 37 | 25. AI powered moderation system 38 | 39 | **Features above make us unique in the anti-swear plugins!** 40 | 41 | ## How does this plugin work? 42 | 43 | ```mermaid 44 | graph TD 45 | A[Player Interaction] --> B[Event Listeners] 46 | C[Player Packet] --> D[Packet Listeners] 47 | B --> E[Regex Preprocess] 48 | D --> E[Regex Preprocess] 49 | E --> F[DFA Match] 50 | F -->|Matched| G[Result] 51 | G --> J[Replace] 52 | G --> K[Cancel] 53 | F -->|Not Matched| H[AI Processor] 54 | H -->|LLMs| I[Rating] 55 | I --> L[Punish] 56 | ``` 57 | 58 | ## Commands 59 | 60 | `/asw help` - Show help message 61 | 62 | `/asw reload` - Re-initialize the DFA dict and reload configurations 63 | 64 | `/asw status` - Show the status of the AdvancedSensitiveWords 65 | 66 | `/asw test ` - Test the AdvancedSensitiveWords filter with given text 67 | 68 | `/asw info ` - Check total violations of specific player from database 69 | 70 | `/asw punish [type]` - Punish a player with a specific type 71 | 72 | ## Permissions 73 | 74 | `advancedsensitivewords.bypass` - Bypass the AdvancedSensitiveWords message filter 75 | 76 | `advancedsensitivewords.reload` - Allows you to use the /asw reload command 77 | 78 | `advancedsensitivewords.status` - Allows you to use the /asw status command 79 | 80 | `advancedsensitivewords.test` - Allows you to use the /asw test command 81 | 82 | `advancedsensitivewords.help` - Allows you to use the /asw help command 83 | 84 | `advancedsensitivewords.notice` - Retrieve the notification when players swore 85 | 86 | `advancedsensitivewords.info` - Ability to use /asw info command 87 | 88 | `advancedsensitivewords.update` - Receive update notifications 89 | 90 | `advancedsensitivewords.punish` - Allows you to use the /asw punish command 91 | 92 | **For more info, please visit [our Wiki](https://github.com/HaHaWTH/AdvancedSensitiveWords/wiki)** 93 | 94 | ## Supported Platforms 95 | - Spigot(stable) 96 | - Velocity(stable) 97 | - BungeeCord(stable) 98 | - Sponge(WIP) 99 | - Fabric(Planned) 100 | - Forge/NeoForge(Coming s∞n) 101 | 102 | ## Known Incompatibilities 103 | - [HuskChat (Only proxy mode)](https://github.com/WiIIiam278/HuskChat) 104 | 105 | ## Statistics 106 | [![](https://img.shields.io/bstats/servers/20661?label=Spigot%20Servers&style=for-the-badge)](https://bstats.org/plugin/bukkit/AdvancedSensitiveWords/20661) 107 | 108 | [![](https://img.shields.io/bstats/players/20661?label=Online%20Players&style=for-the-badge)](https://bstats.org/plugin/bukkit/AdvancedSensitiveWords/20661) 109 | 110 | ## Open-source projects used 111 | - **[Ollama4j(Modified to support Java 8)](https://github.com/ollama4j/ollama4j)** 112 | - **[OpenAI4j](https://github.com/ai-for-java/openai4j)** 113 | - **[packetevents(Used for handling chat & book packets)](https://github.com/retrooper/packetevents)** 114 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/ai/AIProcessor.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.ai; 2 | 3 | import com.google.common.util.concurrent.ThreadFactoryBuilder; 4 | import io.wdsj.asw.bukkit.util.VirtualThreadUtils; 5 | 6 | import java.util.concurrent.ExecutorService; 7 | import java.util.concurrent.Executors; 8 | 9 | /** 10 | * Interface for AI processors (Maybe more in the future?) 11 | */ 12 | public interface AIProcessor { 13 | /** 14 | * Shared thread pool for AI processors 15 | */ 16 | ExecutorService THREAD_POOL = VirtualThreadUtils.newVirtualThreadPerTaskExecutorOrProvided(Executors.newCachedThreadPool(new ThreadFactoryBuilder().setNameFormat("ASW AIProcessor Thread-%d").setDaemon(true).build())); 17 | 18 | default boolean isInitialized() { 19 | return false; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/ai/OllamaProcessor.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.ai; 2 | 3 | import com.google.common.base.Preconditions; 4 | import io.github.amithkoujalgi.ollama4j.core.OllamaAPI; 5 | import io.github.amithkoujalgi.ollama4j.core.models.OllamaResult; 6 | import io.github.amithkoujalgi.ollama4j.core.utils.OptionsBuilder; 7 | import io.github.amithkoujalgi.ollama4j.core.utils.PromptBuilder; 8 | import io.wdsj.asw.bukkit.setting.PluginSettings; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | import java.util.concurrent.CompletableFuture; 12 | 13 | import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.LOGGER; 14 | import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager; 15 | 16 | /** 17 | * Ollama AI processor 18 | */ 19 | public enum OllamaProcessor implements AIProcessor { 20 | INSTANCE; 21 | private static boolean isOllamaInit = false; 22 | private static PromptBuilder promptBuilder; 23 | private static OllamaAPI api; 24 | private static String modelName; 25 | 26 | /** 27 | * Initialize the Ollama service 28 | * @param modelAddress Ollama server address 29 | * @param name Model name to use 30 | * @param timeOut Timeout in seconds 31 | * @param debug Whether to enable debug logging 32 | */ 33 | public void initService(String modelAddress, String name, int timeOut, boolean debug) { 34 | modelName = name; 35 | api = new OllamaAPI(modelAddress); 36 | api.setRequestTimeoutSeconds(timeOut); 37 | try { 38 | if (!api.ping()) { 39 | LOGGER.warning("Ollama ping failed, please check the api address"); 40 | isOllamaInit = false; 41 | return; 42 | } 43 | } catch (Exception e) { 44 | LOGGER.warning("Ollama ping failed, please check the api address"); 45 | isOllamaInit = false; 46 | return; 47 | } 48 | LOGGER.info("Successfully connect to ollama server"); 49 | if (debug) { 50 | LOGGER.info("Ollama debug logging enabled"); 51 | } 52 | api.setVerbose(debug); 53 | isOllamaInit = true; 54 | } 55 | 56 | @Override 57 | public boolean isInitialized() { 58 | return isOllamaInit; 59 | } 60 | 61 | /** 62 | * Process the input message using OllamaProcessor 63 | * @param inputMessage The message to process 64 | * @return A future that will contain the returned message from Ollama server, the future inside may be null 65 | */ 66 | @NotNull 67 | public static CompletableFuture process(String inputMessage) { 68 | Preconditions.checkState(isOllamaInit, "Ollama service is not initialized"); 69 | return CompletableFuture.supplyAsync(() -> { 70 | try { 71 | promptBuilder = new PromptBuilder().addLine(settingsManager.getProperty(PluginSettings.AI_MODEL_PROMPT)).addSeparator() 72 | .add(inputMessage); 73 | OllamaResult response = api.generate(modelName, promptBuilder.build(), 74 | new OptionsBuilder().build()); 75 | return response.getResponse(); 76 | } catch (Exception e) { 77 | LOGGER.warning("Error occurred while communicating with Ollama server: " + e.getMessage()); 78 | return null; 79 | } 80 | }, THREAD_POOL); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/ai/OpenAIProcessor.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.ai; 2 | 3 | import com.github.houbb.heaven.support.tuple.impl.Pair; 4 | import com.google.common.base.Preconditions; 5 | import dev.ai4j.openai4j.OpenAiClient; 6 | import dev.ai4j.openai4j.moderation.Categories; 7 | import dev.ai4j.openai4j.moderation.ModerationRequest; 8 | import dev.ai4j.openai4j.moderation.ModerationResponse; 9 | import dev.ai4j.openai4j.moderation.ModerationResult; 10 | import io.wdsj.asw.bukkit.setting.PluginSettings; 11 | 12 | import java.net.Proxy; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.concurrent.CompletableFuture; 16 | 17 | import static dev.ai4j.openai4j.moderation.ModerationModel.TEXT_MODERATION_LATEST; 18 | import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.LOGGER; 19 | import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager; 20 | 21 | /** 22 | * OpenAI Moderation Processor. 23 | */ 24 | public enum OpenAIProcessor implements AIProcessor { 25 | INSTANCE; 26 | private static boolean isOpenAiInit = false; 27 | private static OpenAiClient client; 28 | 29 | /** 30 | * Initialize the OpenAI moderation service. 31 | * @param apikey the openai key 32 | * @param debug whether to enable debug logging 33 | */ 34 | public void initService(String apikey, boolean debug) { 35 | @SuppressWarnings("rawtypes") 36 | OpenAiClient.Builder builder = OpenAiClient.builder() 37 | .openAiApiKey(apikey); 38 | if (settingsManager.getProperty(PluginSettings.OPENAI_ENABLE_HTTP_PROXY)) { 39 | builder.proxy(Proxy.Type.HTTP, settingsManager.getProperty(PluginSettings.OPENAI_HTTP_PROXY_ADDRESS), settingsManager.getProperty(PluginSettings.OPENAI_HTTP_PROXY_PORT)); 40 | } 41 | if (debug) { 42 | builder.logResponses(true) 43 | .logRequests(true); 44 | } 45 | client = builder.build(); 46 | isOpenAiInit = true; 47 | } 48 | 49 | @Override 50 | public boolean isInitialized() { 51 | return isOpenAiInit; 52 | } 53 | 54 | /** 55 | * Process the input message using OpenAI moderation. 56 | * @param inputMessage the input message 57 | * @return A future contains the moderation response 58 | */ 59 | public static CompletableFuture process(String inputMessage) { 60 | Preconditions.checkState(isOpenAiInit, "OpenAI Moderation Processor is not initialized"); 61 | ModerationRequest request = ModerationRequest.builder() 62 | .input(inputMessage) 63 | .model(TEXT_MODERATION_LATEST) 64 | .build(); 65 | return CompletableFuture.supplyAsync(() -> { 66 | try { 67 | ModerationResponse resp = client.moderation(request) 68 | .execute(); 69 | if (resp != null) { 70 | for (ModerationResult result : resp.results()) { 71 | if (result.isFlagged()) { 72 | Categories categories = result.categories(); 73 | List> categoryChecks = new ArrayList<>(); 74 | categoryChecks.add(Pair.of(categories.hateThreatening(), settingsManager.getProperty(PluginSettings.OPENAI_ENABLE_HATE_THREATENING_CHECK))); 75 | categoryChecks.add(Pair.of(categories.hate(), settingsManager.getProperty(PluginSettings.OPENAI_ENABLE_HATE_CHECK))); 76 | categoryChecks.add(Pair.of(categories.selfHarm(), settingsManager.getProperty(PluginSettings.OPENAI_ENABLE_SELF_HARM_CHECK))); 77 | categoryChecks.add(Pair.of(categories.sexual(), settingsManager.getProperty(PluginSettings.OPENAI_ENABLE_SEXUAL_CONTENT_CHECK))); 78 | categoryChecks.add(Pair.of(categories.sexualMinors(), settingsManager.getProperty(PluginSettings.OPENAI_ENABLE_SEXUAL_MINORS_CHECK))); 79 | categoryChecks.add(Pair.of(categories.violence(), settingsManager.getProperty(PluginSettings.OPENAI_ENABLE_VIOLENCE_CHECK))); 80 | return categoryChecks.stream() 81 | .anyMatch(pair -> pair.getValueOne() && pair.getValueTwo()); 82 | } 83 | } 84 | } 85 | return null; 86 | } catch (Exception e) { 87 | LOGGER.warning("OpenAI Moderation error: " + e.getMessage()); 88 | return null; 89 | } 90 | }, THREAD_POOL); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/annotation/PaperEventHandler.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | @Documented 6 | @Retention(RetentionPolicy.RUNTIME) 7 | @Target({ElementType.TYPE}) 8 | public @interface PaperEventHandler { 9 | } 10 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/command/ConstructTabCompleter.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.command; 2 | 3 | import io.wdsj.asw.bukkit.permission.PermissionsEnum; 4 | import org.bukkit.command.Command; 5 | import org.bukkit.command.CommandSender; 6 | import org.bukkit.command.ConsoleCommandSender; 7 | import org.bukkit.command.TabCompleter; 8 | import org.bukkit.entity.Player; 9 | import org.jetbrains.annotations.NotNull; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | import java.util.ArrayList; 13 | import java.util.Collections; 14 | import java.util.List; 15 | import java.util.stream.Collectors; 16 | 17 | public class ConstructTabCompleter implements TabCompleter { 18 | @Nullable 19 | @Override 20 | public List onTabComplete(@NotNull CommandSender sender, @NotNull Command cmd, @NotNull String s, @NotNull String[] args) { 21 | if (args.length == 1) { 22 | List tabComplete = new ArrayList<>(); 23 | if (sender.hasPermission(PermissionsEnum.RELOAD.getPermission()) && args[0].startsWith("rel")) { 24 | tabComplete.add("reload"); 25 | tabComplete.add("reloadconfig"); 26 | } else if (sender.hasPermission(PermissionsEnum.ADD.getPermission()) && args[0].startsWith("a")) { 27 | tabComplete.add("add"); 28 | tabComplete.add("addallow"); 29 | } else if (sender.hasPermission(PermissionsEnum.REMOVE.getPermission()) && args[0].startsWith("rem")) { 30 | tabComplete.add("remove"); 31 | tabComplete.add("removeallow"); 32 | } else if (sender.hasPermission(PermissionsEnum.RESET.getPermission()) && args[0].startsWith("res")) { 33 | tabComplete.add("reset"); 34 | } else if (sender.hasPermission(PermissionsEnum.STATUS.getPermission()) && args[0].startsWith("s")) { 35 | tabComplete.add("status"); 36 | } else if (sender.hasPermission(PermissionsEnum.TEST.getPermission()) && args[0].startsWith("t")) { 37 | tabComplete.add("test"); 38 | } else if (sender.hasPermission(PermissionsEnum.HELP.getPermission()) && args[0].startsWith("h")) { 39 | tabComplete.add("help"); 40 | } else if (sender.hasPermission(PermissionsEnum.INFO.getPermission()) && args[0].startsWith("i")) { 41 | tabComplete.add("info"); 42 | } else if (sender.hasPermission(PermissionsEnum.PUNISH.getPermission()) && args[0].startsWith("p")) { 43 | tabComplete.add("punish"); 44 | } else if (sender.hasPermission(PermissionsEnum.RELOAD.getPermission()) || 45 | sender.hasPermission(PermissionsEnum.STATUS.getPermission()) || sender.hasPermission(PermissionsEnum.TEST.getPermission()) || 46 | sender.hasPermission(PermissionsEnum.HELP.getPermission()) || sender.hasPermission(PermissionsEnum.INFO.getPermission()) || 47 | sender.hasPermission(PermissionsEnum.PUNISH.getPermission()) || sender.hasPermission(PermissionsEnum.ADD.getPermission()) || 48 | sender.hasPermission(PermissionsEnum.REMOVE.getPermission())) { 49 | tabComplete.add("help"); 50 | tabComplete.add("reload"); 51 | tabComplete.add("reloadconfig"); 52 | tabComplete.add("add"); 53 | tabComplete.add("remove"); 54 | tabComplete.add("status"); 55 | tabComplete.add("test"); 56 | tabComplete.add("punish"); 57 | tabComplete.add("info"); 58 | tabComplete.add("reset"); 59 | tabComplete.add("addallow"); 60 | tabComplete.add("removeallow"); 61 | } 62 | return tabComplete; 63 | } 64 | if (args.length == 2) { 65 | if (args[0].equalsIgnoreCase("info") && (sender.hasPermission(PermissionsEnum.INFO.getPermission()) || sender instanceof ConsoleCommandSender)) { 66 | return sender.getServer().getOnlinePlayers().stream() 67 | .map(Player::getName) 68 | .collect(Collectors.toList()); 69 | } 70 | if (args[0].equalsIgnoreCase("reset") && (sender.hasPermission(PermissionsEnum.RESET.getPermission()) || sender instanceof ConsoleCommandSender)) { 71 | return sender.getServer().getOnlinePlayers().stream() 72 | .map(Player::getName) 73 | .collect(Collectors.toList()); 74 | } 75 | if (args[0].equalsIgnoreCase("punish") && (sender.hasPermission(PermissionsEnum.PUNISH.getPermission()) || sender instanceof ConsoleCommandSender)) { 76 | return sender.getServer().getOnlinePlayers().stream() 77 | .map(Player::getName) 78 | .collect(Collectors.toList()); 79 | } 80 | } 81 | return Collections.emptyList(); // Must return empty list, if null paper will supply player names 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/core/condition/WordResultConditionNumMatch.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.core.condition; 2 | 3 | import com.github.houbb.heaven.util.lang.CharUtil; 4 | import com.github.houbb.sensitive.word.api.IWordContext; 5 | import com.github.houbb.sensitive.word.api.IWordResult; 6 | import com.github.houbb.sensitive.word.constant.enums.WordValidModeEnum; 7 | import com.github.houbb.sensitive.word.support.resultcondition.AbstractWordResultCondition; 8 | 9 | public class WordResultConditionNumMatch extends AbstractWordResultCondition { 10 | @Override 11 | protected boolean doMatch(IWordResult wordResult, String text, WordValidModeEnum modeEnum, IWordContext context) { 12 | final int startIndex = wordResult.startIndex(); 13 | final int endIndex = wordResult.endIndex(); 14 | if (startIndex > 0) { 15 | char preC = text.charAt(startIndex - 1); 16 | if (CharUtil.isDigit(preC)) { 17 | return false; 18 | } 19 | } 20 | if (endIndex < text.length()) { 21 | char afterC = text.charAt(endIndex); 22 | if (CharUtil.isDigit(afterC)) { 23 | return false; 24 | } 25 | } 26 | for (int i = startIndex; i < endIndex; i++) { 27 | char c = text.charAt(i); 28 | if (!CharUtil.isDigit(c)) { 29 | return true; 30 | } 31 | } 32 | return true; 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/integration/placeholder/ASWExpansion.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.integration.placeholder; 2 | 3 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords; 4 | import io.wdsj.asw.bukkit.manage.punish.PlayerShadowController; 5 | import io.wdsj.asw.bukkit.manage.punish.ViolationCounter; 6 | import me.clip.placeholderapi.expansion.PlaceholderExpansion; 7 | import org.bukkit.OfflinePlayer; 8 | import org.bukkit.entity.Player; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | import static io.wdsj.asw.bukkit.util.Utils.messagesFilteredNum; 12 | 13 | public class ASWExpansion extends PlaceholderExpansion { 14 | @Override 15 | public @NotNull String getIdentifier() { 16 | return "asw"; 17 | } 18 | 19 | @Override 20 | public @NotNull String getAuthor() { 21 | return "HaHaWTH"; 22 | } 23 | 24 | @Override 25 | public @NotNull String getVersion() { 26 | return AdvancedSensitiveWords.PLUGIN_VERSION; 27 | } 28 | 29 | @Override 30 | public boolean persist() { 31 | return true; 32 | } 33 | 34 | @Override 35 | public String onRequest(OfflinePlayer player, @NotNull String params) { 36 | if (params.equalsIgnoreCase("version")) { 37 | return getVersion(); 38 | } 39 | if (params.equalsIgnoreCase("total_filtered")) { 40 | return String.valueOf(messagesFilteredNum.get()); 41 | } 42 | if (params.equalsIgnoreCase("is_shadow")) { 43 | if (player != null) { 44 | Player onlinePlayer = player.getPlayer(); 45 | if (onlinePlayer != null) { 46 | return String.valueOf(PlayerShadowController.isShadowed(onlinePlayer)); 47 | } 48 | } 49 | } 50 | if (params.equalsIgnoreCase("violation_count")) { 51 | if (player != null) { 52 | Player onlinePlayer = player.getPlayer(); 53 | if (onlinePlayer != null) { 54 | return String.valueOf(ViolationCounter.INSTANCE.getViolationCount(onlinePlayer)); 55 | } 56 | } 57 | } 58 | return null; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/integration/voicechat/VoiceChatExtension.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.integration.voicechat; 2 | 3 | import de.maxhenkel.voicechat.api.VoicechatApi; 4 | import de.maxhenkel.voicechat.api.VoicechatPlugin; 5 | import de.maxhenkel.voicechat.api.audio.AudioConverter; 6 | import de.maxhenkel.voicechat.api.events.EventRegistration; 7 | import de.maxhenkel.voicechat.api.events.MicrophonePacketEvent; 8 | import de.maxhenkel.voicechat.api.events.PlayerConnectedEvent; 9 | import de.maxhenkel.voicechat.api.events.PlayerDisconnectedEvent; 10 | import de.maxhenkel.voicechat.api.opus.OpusDecoder; 11 | import io.wdsj.asw.bukkit.manage.punish.PlayerShadowController; 12 | import io.wdsj.asw.bukkit.permission.PermissionsEnum; 13 | import io.wdsj.asw.bukkit.permission.cache.CachingPermTool; 14 | import io.wdsj.asw.bukkit.setting.PluginSettings; 15 | import org.bukkit.entity.Player; 16 | 17 | import java.util.Map; 18 | import java.util.UUID; 19 | import java.util.concurrent.ConcurrentHashMap; 20 | 21 | import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager; 22 | 23 | public class VoiceChatExtension implements VoicechatPlugin { 24 | public final Map connectedPlayers; 25 | 26 | 27 | public VoiceChatExtension() { 28 | connectedPlayers = new ConcurrentHashMap<>(); 29 | } 30 | 31 | /** 32 | * @return the unique ID for this voice chat plugin 33 | */ 34 | @Override 35 | public String getPluginId() { 36 | return "asw_voicechat"; 37 | } 38 | 39 | /** 40 | * Called when the voice chat initializes the plugin. 41 | * 42 | * @param api the voice chat API 43 | */ 44 | @Override 45 | public void initialize(VoicechatApi api) { 46 | 47 | } 48 | 49 | /** 50 | * Called once by the voice chat to register all events. 51 | * 52 | * @param registration the event registration 53 | */ 54 | @Override 55 | public void registerEvents(EventRegistration registration) { 56 | registration.registerEvent(MicrophonePacketEvent.class, this::onMicrophone); 57 | registration.registerEvent(PlayerConnectedEvent.class, this::onConnect); 58 | registration.registerEvent(PlayerDisconnectedEvent.class, this::onDisconnect); 59 | } 60 | 61 | /** 62 | * This method is called whenever a player sends audio to the server via the voice chat. 63 | * 64 | * @param event the microphone packet event 65 | */ 66 | private void onMicrophone(MicrophonePacketEvent event) { 67 | if (event.getSenderConnection() == null) { 68 | return; 69 | } 70 | if (!(event.getSenderConnection().getPlayer().getPlayer() instanceof Player)) { 71 | return; 72 | } 73 | 74 | Player player = (Player) event.getSenderConnection().getPlayer().getPlayer(); 75 | if (PlayerShadowController.isShadowed(player) && settingsManager.getProperty(PluginSettings.VOICE_SYNC_SHADOW)) { 76 | event.cancel(); 77 | return; 78 | } 79 | 80 | if (!settingsManager.getProperty(PluginSettings.VOICE_REALTIME_TRANSCRIBING)) return; 81 | if (event.getPacket() == null || CachingPermTool.hasPermission(PermissionsEnum.BYPASS, player)) return; 82 | OpusDecoder decoder = event.getVoicechat().createDecoder(); 83 | AudioConverter converter = event.getVoicechat().getAudioConverter(); 84 | float[] newData = converter.shortsToFloats(decoder.decode(event.getPacket().getOpusEncodedData())); 85 | if (connectedPlayers.get(player.getUniqueId()) != null) { 86 | float[] oldData = connectedPlayers.get(player.getUniqueId()); 87 | float[] result = new float[oldData.length + newData.length]; 88 | System.arraycopy(oldData, 0, result, 0, oldData.length); 89 | System.arraycopy(newData, 0, result, oldData.length, newData.length); 90 | connectedPlayers.put(player.getUniqueId(), result); 91 | } else { 92 | connectedPlayers.put(player.getUniqueId(), newData); 93 | } 94 | decoder.close(); 95 | } 96 | 97 | private void onConnect(PlayerConnectedEvent event) { 98 | if (!settingsManager.getProperty(PluginSettings.VOICE_REALTIME_TRANSCRIBING)) return; 99 | if (event.getConnection() == null) { 100 | return; 101 | } 102 | if (!(event.getConnection().getPlayer().getPlayer() instanceof Player)) { 103 | return; 104 | } 105 | Player player = (Player) event.getConnection().getPlayer().getPlayer(); 106 | connectedPlayers.put(player.getUniqueId(), new float[]{}); 107 | } 108 | 109 | private void onDisconnect(PlayerDisconnectedEvent event) { 110 | if (!settingsManager.getProperty(PluginSettings.VOICE_REALTIME_TRANSCRIBING)) { 111 | if (!connectedPlayers.isEmpty()) connectedPlayers.clear(); 112 | return; 113 | } 114 | if (event.getPlayerUuid() == null) { 115 | return; 116 | } 117 | connectedPlayers.remove(event.getPlayerUuid()); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/integration/voicechat/WhisperVoiceTranscribeTool.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.integration.voicechat; 2 | 3 | import com.google.common.util.concurrent.ThreadFactoryBuilder; 4 | import io.github.givimad.whisperjni.WhisperContext; 5 | import io.github.givimad.whisperjni.WhisperFullParams; 6 | import io.github.givimad.whisperjni.WhisperJNI; 7 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords; 8 | import io.wdsj.asw.bukkit.setting.PluginSettings; 9 | import io.wdsj.asw.bukkit.util.VirtualThreadUtils; 10 | 11 | import java.io.File; 12 | import java.io.IOException; 13 | import java.nio.file.Files; 14 | import java.nio.file.Paths; 15 | import java.util.concurrent.*; 16 | 17 | import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.LOGGER; 18 | import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager; 19 | 20 | public class WhisperVoiceTranscribeTool { 21 | private final WhisperJNI whisper; 22 | private final WhisperContext whisperCtx; 23 | private final ThreadPoolExecutor threadPool; 24 | 25 | public WhisperVoiceTranscribeTool() { 26 | try { 27 | WhisperJNI.loadLibrary(); 28 | if (settingsManager.getProperty(PluginSettings.VOICE_DEBUG)) { 29 | WhisperJNI.LibraryLogger logger = log -> LOGGER.info("[WhisperJNI] " + log); 30 | WhisperJNI.setLibraryLogger(logger); 31 | LOGGER.info("WhisperJNI debug logger enabled"); 32 | } else { 33 | WhisperJNI.setLibraryLogger(null); 34 | } 35 | whisper = new WhisperJNI(); 36 | File dataFolder = Paths.get(AdvancedSensitiveWords.getInstance().getDataFolder().getPath(), "whisper", "model").toFile(); 37 | if (Files.notExists(dataFolder.toPath())) { 38 | Files.createDirectories(dataFolder.toPath()); 39 | } 40 | int coreCount = Runtime.getRuntime().availableProcessors(); 41 | int maxThread = settingsManager.getProperty(PluginSettings.VOICE_REALTIME_TRANSCRIBING_MAX_THREAD); 42 | if (maxThread <= -1) { 43 | maxThread = coreCount; 44 | } else if (maxThread == 0) { 45 | maxThread = coreCount * 2; 46 | } 47 | LOGGER.info("Using " + maxThread + " thread(s) for voice transcription"); 48 | whisperCtx = whisper.init(Paths.get(dataFolder.getPath(), settingsManager.getProperty(PluginSettings.VOICE_MODEL_NAME))); 49 | RejectedExecutionHandler handler = (r, executor) -> LOGGER.info("Rejected execution of transcription task, thread pool is full"); 50 | threadPool = new ThreadPoolExecutor( 51 | maxThread / 2, 52 | maxThread, 53 | settingsManager.getProperty(PluginSettings.VOICE_REALTIME_TRANSCRIBING_THREAD_KEEP_ALIVE), 54 | TimeUnit.SECONDS, 55 | new LinkedBlockingQueue<>(maxThread * 2), 56 | new ThreadFactoryBuilder() 57 | .setNameFormat("ASW Whisper Transcribe Thread-%d") 58 | .setDaemon(true) 59 | .setThreadFactory(VirtualThreadUtils.newVirtualThreadFactoryOrDefault()) 60 | .build(), 61 | handler); 62 | threadPool.allowCoreThreadTimeOut(true); 63 | threadPool.prestartAllCoreThreads(); 64 | } catch (IOException e) { 65 | throw new RuntimeException(e); 66 | } 67 | } 68 | public CompletableFuture transcribe(float[] data) { 69 | return CompletableFuture.supplyAsync(() -> { 70 | WhisperFullParams params = new WhisperFullParams(); 71 | int result = whisper.full(whisperCtx, params, data, data.length); 72 | if (result != 0) { 73 | LOGGER.warning("Transcription failed with code " + result); 74 | } 75 | whisper.fullNSegments(whisperCtx); 76 | return whisper.fullGetSegmentText(whisperCtx,0); 77 | }, threadPool); 78 | } 79 | 80 | public void shutdown() { 81 | threadPool.shutdownNow(); 82 | whisperCtx.close(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/manage/notice/Notifier.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.manage.notice; 2 | 3 | import io.wdsj.asw.bukkit.manage.punish.ViolationCounter; 4 | import io.wdsj.asw.bukkit.permission.PermissionsEnum; 5 | import io.wdsj.asw.bukkit.permission.cache.CachingPermTool; 6 | import io.wdsj.asw.bukkit.setting.PluginMessages; 7 | import io.wdsj.asw.bukkit.type.ModuleType; 8 | import io.wdsj.asw.bukkit.util.message.MessageUtils; 9 | import org.bukkit.Bukkit; 10 | import org.bukkit.ChatColor; 11 | import org.bukkit.entity.Player; 12 | 13 | import java.util.Collection; 14 | import java.util.List; 15 | 16 | public class Notifier { 17 | /** 18 | * Notice the operators 19 | * @param violatedPlayer the player who violated the rules 20 | * @param moduleType the detection module type 21 | * @param originalMessage original message sent by the player 22 | * @param censoredList censored list 23 | */ 24 | public static void notice(Player violatedPlayer, ModuleType moduleType, String originalMessage, List censoredList) { 25 | Collection players = Bukkit.getOnlinePlayers(); 26 | String message = MessageUtils.retrieveMessage(PluginMessages.ADMIN_REMINDER).replace("%player%", violatedPlayer.getName()).replace("%type%", moduleType.toString()).replace("%message%", ChatColor.stripColor(originalMessage)).replace("%censored_list%", censoredList.toString()).replace("%violation%", String.valueOf(ViolationCounter.INSTANCE.getViolationCount(violatedPlayer))); 27 | for (Player player : players) { 28 | if (CachingPermTool.hasPermission(PermissionsEnum.NOTICE, player)) { 29 | MessageUtils.sendMessage(player, message); 30 | } 31 | } 32 | } 33 | 34 | public static void normalNotice(String message) { 35 | Collection players = Bukkit.getOnlinePlayers(); 36 | for (Player player : players) { 37 | if (CachingPermTool.hasPermission(PermissionsEnum.NOTICE, player)) { 38 | MessageUtils.sendMessage(player, message); 39 | } 40 | } 41 | } 42 | 43 | /** 44 | * Notice Operator method used by the proxy receivers 45 | * @param violatedPlayer the player who violated the rules, with server name 46 | * @param eventType the type 47 | * @param originalMessage the original message sent by the player 48 | * @param censoredList censored list 49 | */ 50 | public static void noticeFromProxy(String violatedPlayer, String serverName, String eventType, String violationCount, String originalMessage, String censoredList) { 51 | Collection players = Bukkit.getOnlinePlayers(); 52 | String message = MessageUtils.retrieveMessage(PluginMessages.ADMIN_REMINDER_PROXY).replace("%player%", violatedPlayer).replace("%type%", eventType).replace("%message%", ChatColor.stripColor(originalMessage)).replace("%censored_list%", censoredList).replace("%server_name%", serverName).replace("%violation%", violationCount); 53 | for (Player player : players) { 54 | if (CachingPermTool.hasPermission(PermissionsEnum.NOTICE, player)) { 55 | player.sendMessage(message); 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/manage/punish/PlayerAltController.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.manage.punish; 2 | 3 | import com.google.common.collect.HashMultimap; 4 | import com.google.common.collect.Multimap; 5 | import io.wdsj.asw.bukkit.util.Utils; 6 | import org.bukkit.entity.Player; 7 | 8 | import java.util.Collection; 9 | import java.util.Collections; 10 | import java.util.UUID; 11 | import java.util.stream.Collectors; 12 | 13 | public class PlayerAltController { 14 | private static final Multimap PLAYER_ALTS = HashMultimap.create(); 15 | 16 | /** 17 | * Adds a player to the alts list. 18 | * @param ip The player's IP address. 19 | * @param player The player 20 | */ 21 | public static void addToAlts(String ip, Player player) { 22 | PLAYER_ALTS.put(ip, player.getUniqueId()); 23 | } 24 | 25 | /** 26 | * Removes a player from the alts list. 27 | * @param ip The player's IP address. 28 | * @param player The player 29 | */ 30 | public static void removeFromAlts(String ip, Player player) { 31 | PLAYER_ALTS.remove(ip, player.getUniqueId()); 32 | } 33 | 34 | /** 35 | * Checks if a player has an alt. 36 | * @param player The player 37 | * @return True if the player has an alt, false otherwise. 38 | */ 39 | public static boolean hasAlt(Player player) { 40 | String ip = Utils.getPlayerIp(player); 41 | return PLAYER_ALTS.get(ip).size() > 1; 42 | } 43 | 44 | /** 45 | * Gets a list of alts for a player(everyone except the player itself). 46 | * @param player The player 47 | * @return A list of alts for the player. 48 | */ 49 | public static Collection getAlts(Player player) { 50 | String ip = Utils.getPlayerIp(player); 51 | synchronized (PLAYER_ALTS) { 52 | if (!PLAYER_ALTS.containsKey(ip)) { 53 | return Collections.emptyList(); 54 | } 55 | return PLAYER_ALTS.get(ip).stream() 56 | .filter(uuid -> !uuid.equals(player.getUniqueId())) 57 | .collect(Collectors.toList()); 58 | } 59 | } 60 | 61 | /** 62 | * Checks if a player is in the alts list. 63 | * @param ip The player's IP address. 64 | * @param player The player 65 | * @return true if the player is in the alts list, false otherwise. 66 | */ 67 | public static boolean contains(String ip, Player player) { 68 | return PLAYER_ALTS.get(ip).contains(player.getUniqueId()); 69 | } 70 | 71 | /** 72 | * Clears the alts list. 73 | */ 74 | public static void clear() { 75 | PLAYER_ALTS.clear(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/manage/punish/PlayerShadowController.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.manage.punish; 2 | 3 | import org.bukkit.entity.Player; 4 | 5 | import java.util.Map; 6 | import java.util.UUID; 7 | import java.util.concurrent.ConcurrentHashMap; 8 | 9 | /** 10 | * Player shadow controller 11 | */ 12 | public class PlayerShadowController { 13 | private static final Map SHADOWED_PLAYERS = new ConcurrentHashMap<>(); 14 | 15 | /** 16 | * Add player to shadowed players 17 | * @param player to shadow 18 | * @param start Start time, in milliseconds 19 | * @param duration Duration, in seconds 20 | */ 21 | public static void shadowPlayer(Player player, long start, long duration) { 22 | SHADOWED_PLAYERS.put(player.getUniqueId(), new StartAndDuration(start, duration)); 23 | } 24 | 25 | /** 26 | * Remove player from shadowed players 27 | * @param player to unshadow 28 | */ 29 | public static void unshadowPlayer(Player player) { 30 | SHADOWED_PLAYERS.remove(player.getUniqueId()); 31 | } 32 | 33 | private static void unshadowPlayer(UUID uuid) { 34 | SHADOWED_PLAYERS.remove(uuid); 35 | } 36 | 37 | /** 38 | * Check if player is shadowed 39 | * @param player to check 40 | * @return true if player is shadowed, false otherwise 41 | */ 42 | public static boolean isShadowed(Player player) { 43 | UUID uuid = player.getUniqueId(); 44 | if (!SHADOWED_PLAYERS.containsKey(uuid)) return false; 45 | StartAndDuration startAndDuration = SHADOWED_PLAYERS.get(uuid); 46 | long currentTime = System.currentTimeMillis(); 47 | if (currentTime - startAndDuration.getStart() > startAndDuration.getDuration() * 1000) { 48 | unshadowPlayer(uuid); 49 | return false; 50 | } else { 51 | return true; 52 | } 53 | } 54 | 55 | /** 56 | * Clear all shadowed players 57 | */ 58 | public static void clear() { 59 | SHADOWED_PLAYERS.clear(); 60 | } 61 | 62 | /** 63 | * Class to store start time and duration 64 | */ 65 | private static class StartAndDuration { 66 | private final long start; 67 | private final long duration; 68 | 69 | /** 70 | * Constructor 71 | * @param start Start time, in milliseconds 72 | * @param duration Duration, in seconds 73 | */ 74 | public StartAndDuration(long start, long duration) { 75 | this.start = start; 76 | this.duration = duration; 77 | } 78 | 79 | /** 80 | * Get start time 81 | * @return Start time, in milliseconds 82 | */ 83 | public long getStart() { 84 | return start; 85 | } 86 | 87 | /** 88 | * Get duration 89 | * @return Duration, in seconds 90 | */ 91 | public long getDuration() { 92 | return duration; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/manage/punish/PunishmentType.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.manage.punish; 2 | 3 | import org.jetbrains.annotations.Nullable; 4 | 5 | import java.util.Locale; 6 | 7 | public enum PunishmentType { 8 | COMMAND, 9 | 10 | COMMAND_PROXY, 11 | 12 | HOSTILE, 13 | 14 | DAMAGE, 15 | 16 | EFFECT, 17 | 18 | SHADOW; 19 | 20 | @Nullable 21 | public static PunishmentType getType(String type) { 22 | try { 23 | return valueOf(type.trim().toUpperCase(Locale.ROOT)); 24 | } catch (IllegalArgumentException e) { 25 | return null; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/manage/punish/ViolationCounter.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.manage.punish; 2 | 3 | import org.bukkit.entity.Player; 4 | 5 | import java.util.Map; 6 | import java.util.UUID; 7 | import java.util.concurrent.ConcurrentHashMap; 8 | 9 | public enum ViolationCounter { 10 | INSTANCE; 11 | private final Map violationCountMap = new ConcurrentHashMap<>(); 12 | 13 | public long getViolationCount(Player player) { 14 | return violationCountMap.getOrDefault(player.getUniqueId(), 0L); 15 | } 16 | 17 | public void incrementViolationCount(Player player, long count) { 18 | violationCountMap.merge(player.getUniqueId(), count, Long::sum); 19 | } 20 | 21 | public void incrementViolationCount(Player player) { 22 | incrementViolationCount(player, 1); 23 | } 24 | 25 | public void resetViolationCount(Player player) { 26 | violationCountMap.remove(player.getUniqueId()); 27 | } 28 | 29 | public boolean hasViolation(Player player) { 30 | return violationCountMap.containsKey(player.getUniqueId()) && violationCountMap.get(player.getUniqueId()) > 0L; 31 | } 32 | 33 | public void resetAllViolations() { 34 | violationCountMap.clear(); 35 | } 36 | } -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/method/CharIgnore.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.method; 2 | 3 | import com.github.houbb.heaven.util.lang.StringUtil; 4 | import com.github.houbb.sensitive.word.api.ISensitiveWordCharIgnore; 5 | import com.github.houbb.sensitive.word.api.context.InnerSensitiveWordContext; 6 | import io.wdsj.asw.bukkit.setting.PluginSettings; 7 | 8 | import java.util.Set; 9 | 10 | import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager; 11 | 12 | public class CharIgnore implements ISensitiveWordCharIgnore { 13 | @Override 14 | public boolean ignore(int i, char[] chars, InnerSensitiveWordContext innerSensitiveWordContext) { 15 | Set SET = StringUtil.toCharSet(settingsManager.getProperty(PluginSettings.IGNORE_CHAR)); 16 | char c = chars[i]; 17 | return SET.contains(c); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/method/ExternalWordAllow.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.method; 2 | 3 | import com.github.houbb.sensitive.word.api.IWordAllow; 4 | import org.bukkit.plugin.Plugin; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.File; 8 | import java.io.IOException; 9 | import java.nio.file.Files; 10 | import java.nio.file.Path; 11 | import java.nio.file.Paths; 12 | import java.util.ArrayList; 13 | import java.util.Collections; 14 | import java.util.List; 15 | import java.util.Locale; 16 | import java.util.stream.Collectors; 17 | import java.util.stream.Stream; 18 | 19 | import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.LOGGER; 20 | 21 | public class ExternalWordAllow implements IWordAllow { 22 | private final File dataFolder; 23 | 24 | public ExternalWordAllow(Plugin plugin) { 25 | this.dataFolder = Paths.get(plugin.getDataFolder().getPath(),"external","allow").toFile(); 26 | } 27 | @Override 28 | public List allow() { 29 | final List totalList = new ArrayList<>(); 30 | 31 | if (Files.notExists(dataFolder.toPath())) { 32 | try { 33 | Files.createDirectories(dataFolder.toPath()); 34 | } catch (IOException e) { 35 | LOGGER.severe("Error occurred while creating external allow directory: " + e.getMessage()); 36 | } 37 | } 38 | try (Stream paths = Files.walk(dataFolder.toPath())) { 39 | List files = paths 40 | .filter(Files::isRegularFile) 41 | .map(Path::toFile) 42 | .collect(Collectors.toList()); 43 | if (files.isEmpty()) return Collections.emptyList(); 44 | 45 | files.parallelStream() 46 | .forEach(file -> { 47 | final boolean isWildCard = file.getName().toLowerCase(Locale.ROOT).contains("wildcard"); 48 | try (BufferedReader reader = Files.newBufferedReader(file.toPath())) { 49 | synchronized (totalList) { 50 | if (isWildCard) { 51 | final WildCardLineResolver parser = new WildCardLineResolver(); 52 | reader.lines().forEach(line -> totalList.addAll(parser.resolveWildCardLine(line))); 53 | } else reader.lines().forEach(totalList::add); 54 | } 55 | } catch (IOException e) { 56 | LOGGER.severe("Error reading file: " + file.getName()); 57 | } 58 | }); 59 | LOGGER.info("Loaded " + files.size() + " external allow file(s). " + "Total words: " + totalList.size()); 60 | } catch (IOException e) { 61 | LOGGER.severe("Error occurred while loading external allow files: " + e.getMessage()); 62 | return Collections.emptyList(); 63 | } 64 | return totalList; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/method/ExternalWordDeny.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.method; 2 | 3 | import com.github.houbb.sensitive.word.api.IWordDeny; 4 | import org.bukkit.plugin.Plugin; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.File; 8 | import java.io.IOException; 9 | import java.nio.file.Files; 10 | import java.nio.file.Path; 11 | import java.nio.file.Paths; 12 | import java.util.ArrayList; 13 | import java.util.Collections; 14 | import java.util.List; 15 | import java.util.Locale; 16 | import java.util.stream.Collectors; 17 | import java.util.stream.Stream; 18 | 19 | import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.LOGGER; 20 | 21 | public class ExternalWordDeny implements IWordDeny { 22 | private final File dataFolder; 23 | 24 | public ExternalWordDeny(Plugin plugin) { 25 | this.dataFolder = Paths.get(plugin.getDataFolder().getPath(),"external","deny").toFile(); 26 | } 27 | @Override 28 | public List deny() { 29 | final List totalList = new ArrayList<>(); 30 | 31 | if (Files.notExists(dataFolder.toPath())) { 32 | try { 33 | Files.createDirectories(dataFolder.toPath()); 34 | } catch (IOException e) { 35 | LOGGER.severe("Error occurred while creating external deny directory: " + e.getMessage()); 36 | } 37 | } 38 | try (Stream paths = Files.walk(dataFolder.toPath())) { 39 | List files = paths 40 | .filter(Files::isRegularFile) 41 | .map(Path::toFile) 42 | .collect(Collectors.toList()); 43 | if (files.isEmpty()) return Collections.emptyList(); 44 | 45 | files.parallelStream() 46 | .forEach(file -> { 47 | final boolean isWildCard = file.getName().toLowerCase(Locale.ROOT).contains("wildcard"); 48 | try (BufferedReader reader = Files.newBufferedReader(file.toPath())) { 49 | synchronized (totalList) { 50 | if (isWildCard) { 51 | final WildCardLineResolver parser = new WildCardLineResolver(); 52 | reader.lines().forEach(line -> totalList.addAll(parser.resolveWildCardLine(line))); 53 | } else reader.lines().forEach(totalList::add); 54 | } 55 | } catch (IOException e) { 56 | LOGGER.severe("Error reading file: " + file.getName()); 57 | } 58 | }); 59 | LOGGER.info("Loaded " + files.size() + " external deny file(s). " + "Total words: " + totalList.size()); 60 | } catch (IOException e) { 61 | LOGGER.severe("Error occurred while loading external deny files: " + e.getMessage()); 62 | return Collections.emptyList(); 63 | } 64 | return totalList; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/method/OnlineWordDeny.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.method; 2 | 3 | import com.github.houbb.sensitive.word.api.IWordDeny; 4 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords; 5 | import io.wdsj.asw.bukkit.setting.PluginSettings; 6 | import org.bukkit.plugin.Plugin; 7 | 8 | import java.io.*; 9 | import java.net.HttpURLConnection; 10 | import java.net.URI; 11 | import java.net.URL; 12 | import java.nio.file.Files; 13 | import java.nio.file.Paths; 14 | import java.util.ArrayList; 15 | import java.util.Collections; 16 | import java.util.List; 17 | 18 | import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.LOGGER; 19 | import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager; 20 | 21 | /** 22 | * OnlineWordDeny for ASW. 23 | * @since Dragon 24 | */ 25 | public class OnlineWordDeny implements IWordDeny { 26 | private final File dataFolder; 27 | private final File cacheFile; 28 | private final File timestampFile; 29 | private final String charset = settingsManager.getProperty(PluginSettings.ONLINE_WORDS_ENCODING); 30 | private final boolean isCacheEnabled = settingsManager.getProperty(PluginSettings.CACHE_ONLINE_WORDS); 31 | 32 | public OnlineWordDeny(Plugin plugin) { 33 | this.dataFolder = Paths.get(plugin.getDataFolder().getPath(), "cache").toFile(); 34 | this.cacheFile = new File(dataFolder, "cache_online_deny_words.txt"); 35 | this.timestampFile = new File(dataFolder, "cache_online_deny_words_timestamp.txt"); 36 | } 37 | @Override 38 | public List deny() { 39 | if (isCacheEnabled && cacheFile.exists() && !isCacheExpired()) { 40 | try { 41 | LOGGER.info("Loading cached words from file."); 42 | return Files.readAllLines(cacheFile.toPath()); 43 | } catch (IOException e) { 44 | LOGGER.warning("Failed to load cached words from file: " + e.getMessage()); 45 | } 46 | } 47 | 48 | String link = settingsManager.getProperty(PluginSettings.ONLINE_WORDS_URL); 49 | List lines = new ArrayList<>(); 50 | URI uri = URI.create(link); 51 | 52 | try { 53 | URL url = uri.toURL(); 54 | HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 55 | connection.setConnectTimeout(4000); 56 | connection.setReadTimeout(10000); 57 | 58 | try (BufferedReader reader = new BufferedReader( 59 | new InputStreamReader(connection.getInputStream(), charset))) { 60 | reader.lines().forEach(lines::add); 61 | } finally { 62 | connection.disconnect(); 63 | } 64 | if (isCacheEnabled) saveToCache(lines); 65 | } catch (Exception e) { 66 | LOGGER.warning("Failed to load online words list from URL: " + link); 67 | return Collections.emptyList(); 68 | } 69 | 70 | LOGGER.info("Loaded " + lines.size() + " word(s) online."); 71 | return lines; 72 | } 73 | 74 | private boolean isCacheExpired() { 75 | if (!timestampFile.exists()) { 76 | return true; 77 | } 78 | final long timeout = settingsManager.getProperty(PluginSettings.ONLINE_WORDS_CACHE_TIMEOUT); 79 | if (timeout < 0) { 80 | return false; 81 | } 82 | try { 83 | long lastModified = Long.parseLong(new String(Files.readAllBytes(timestampFile.toPath())).trim()); 84 | long currentTime = System.currentTimeMillis(); 85 | return (currentTime - lastModified) > timeout * 60L * 1000L; 86 | } catch (IOException | NumberFormatException e) { 87 | LOGGER.warning("Failed to read cache timestamp: " + e.getMessage()); 88 | return true; 89 | } 90 | } 91 | 92 | private void saveToCache(List words) { 93 | if (!Files.exists(dataFolder.toPath())) { 94 | try { 95 | Files.createDirectories(dataFolder.toPath()); 96 | } catch (IOException e) { 97 | LOGGER.severe("Error occurred while creating cache directory: " + e.getMessage()); 98 | } 99 | } 100 | 101 | try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(cacheFile, false), charset)) { 102 | for (String word : words) { 103 | writer.write(word + System.lineSeparator()); 104 | } 105 | } catch (IOException e) { 106 | LOGGER.warning("Failed to save words to cache file: " + e.getMessage()); 107 | } 108 | 109 | try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(timestampFile, false), charset)) { 110 | writer.write(String.valueOf(System.currentTimeMillis())); 111 | } catch (IOException e) { 112 | LOGGER.warning("Failed to update cache timestamp: " + e.getMessage()); 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/method/WildCardLineResolver.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.method; 2 | 3 | import java.util.*; 4 | 5 | public class WildCardLineResolver { 6 | public List resolveWildCardLine(String line) { 7 | String[] parts = line.split("\\|"); 8 | if (parts.length == 1) { 9 | return Collections.singletonList(line); 10 | } 11 | 12 | List> options = new ArrayList<>(); 13 | for (String part : parts) { 14 | String[] alternatives = part.split("\\*"); 15 | options.add(Arrays.asList(alternatives)); 16 | } 17 | 18 | List result = new ArrayList<>(); 19 | result.add(""); 20 | 21 | for (List optionList : options) { 22 | List temp = new ArrayList<>(); 23 | for (String prefix : result) { 24 | for (String option : optionList) { 25 | temp.add(prefix + option); 26 | } 27 | } 28 | result = temp; 29 | } 30 | 31 | return result; 32 | } 33 | } -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/method/WordAllow.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.method; 2 | 3 | import com.github.houbb.sensitive.word.api.IWordAllow; 4 | import io.wdsj.asw.bukkit.setting.PluginSettings; 5 | 6 | import java.util.List; 7 | 8 | import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager; 9 | 10 | public class WordAllow implements IWordAllow { 11 | @Override 12 | public List allow() { 13 | return settingsManager.getProperty(PluginSettings.WHITE_LIST); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/method/WordDeny.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.method; 2 | 3 | import com.github.houbb.sensitive.word.api.IWordDeny; 4 | import io.wdsj.asw.bukkit.setting.PluginSettings; 5 | 6 | import java.util.List; 7 | 8 | import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager; 9 | 10 | public class WordDeny implements IWordDeny { 11 | @Override 12 | public List deny() { 13 | return settingsManager.getProperty(PluginSettings.BLACK_LIST); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/method/WordReplace.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.method; 2 | 3 | import com.github.houbb.sensitive.word.api.IWordContext; 4 | import com.github.houbb.sensitive.word.api.IWordReplace; 5 | import com.github.houbb.sensitive.word.api.IWordResult; 6 | import com.github.houbb.sensitive.word.utils.InnerWordCharUtils; 7 | import io.wdsj.asw.bukkit.setting.PluginSettings; 8 | 9 | import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager; 10 | 11 | public class WordReplace implements IWordReplace { 12 | @Override 13 | public void replace(StringBuilder stringBuilder, char[] rawChars, IWordResult wordResult, IWordContext wordContext) { 14 | String sensitiveWord = InnerWordCharUtils.getString(rawChars, wordResult); 15 | for (String word : settingsManager.getProperty(PluginSettings.DEFINED_REPLACEMENT)) { 16 | String[] parts = word.split("\\|"); 17 | if (parts.length == 2) { 18 | int l = parts[1].length(); 19 | String definedSensitiveWord = parts[0]; 20 | String definedReplacement = parts[1].substring(0, l); 21 | if (definedSensitiveWord.equals(sensitiveWord)) { 22 | stringBuilder.append(definedReplacement); 23 | return; 24 | } 25 | } 26 | } 27 | int wordLength = wordResult.endIndex() - wordResult.startIndex(); 28 | 29 | for (int i = 0; i < wordLength; ++i) { 30 | stringBuilder.append(settingsManager.getProperty(PluginSettings.REPLACEMENT)); 31 | } 32 | 33 | } 34 | } -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/permission/PermissionsEnum.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.permission; 2 | 3 | /** 4 | * Permission enums 5 | */ 6 | public enum PermissionsEnum { 7 | BYPASS("bypass"), 8 | RELOAD("reload"), 9 | ADD("add"), 10 | REMOVE("remove"), 11 | STATUS("status"), 12 | TEST("test"), 13 | HELP("help"), 14 | NOTICE("notice"), 15 | INFO("info"), 16 | RESET("reset"), 17 | UPDATE("update"), 18 | PUNISH("punish"); 19 | 20 | private final String permission; 21 | 22 | PermissionsEnum(String permission) { 23 | this.permission = permission; 24 | } 25 | 26 | public String getPermission() { 27 | return PREFIX + permission; 28 | } 29 | 30 | private static final String PREFIX = "advancedsensitivewords."; 31 | } 32 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/permission/cache/CachingPermTool.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.permission.cache; 2 | 3 | import com.github.benmanes.caffeine.cache.Cache; 4 | import com.github.benmanes.caffeine.cache.Caffeine; 5 | import io.wdsj.asw.bukkit.permission.PermissionsEnum; 6 | import org.bukkit.entity.HumanEntity; 7 | import org.bukkit.event.EventHandler; 8 | import org.bukkit.event.EventPriority; 9 | import org.bukkit.event.HandlerList; 10 | import org.bukkit.event.Listener; 11 | import org.bukkit.event.player.PlayerKickEvent; 12 | import org.bukkit.event.player.PlayerQuitEvent; 13 | import org.bukkit.plugin.java.JavaPlugin; 14 | import org.jetbrains.annotations.NotNull; 15 | 16 | import java.util.Map; 17 | import java.util.UUID; 18 | import java.util.concurrent.ConcurrentHashMap; 19 | import java.util.concurrent.TimeUnit; 20 | 21 | // Taken from: xGinko/AnarchyExploitFixes 22 | public final class CachingPermTool implements Listener { 23 | 24 | private static final Map> permissionCacheMap = new ConcurrentHashMap<>(); 25 | 26 | CachingPermTool(JavaPlugin plugin) { 27 | plugin.getServer().getPluginManager().registerEvents(this, plugin); 28 | } 29 | 30 | public static CachingPermTool enable(JavaPlugin plugin) { 31 | return new CachingPermTool(plugin); 32 | } 33 | 34 | public void disable() { 35 | HandlerList.unregisterAll(this); 36 | for (Map.Entry> entry : permissionCacheMap.entrySet()) 37 | entry.getValue().cleanUp(); 38 | permissionCacheMap.clear(); 39 | } 40 | 41 | public static boolean hasPermission(PermissionsEnum permission, @NotNull HumanEntity human) { 42 | Cache permCache = permissionCacheMap.computeIfAbsent(human.getUniqueId(), 43 | k -> Caffeine.newBuilder().expireAfterWrite(8, TimeUnit.SECONDS).build()); 44 | Boolean hasPermission = permCache.getIfPresent(permission); 45 | if (hasPermission == null) { 46 | hasPermission = human.hasPermission(permission.getPermission()); 47 | permCache.put(permission, hasPermission); 48 | } 49 | return hasPermission; 50 | } 51 | 52 | @EventHandler(priority = EventPriority.MONITOR) 53 | private void onQuit(PlayerQuitEvent event) { 54 | permissionCacheMap.remove(event.getPlayer().getUniqueId()); 55 | } 56 | 57 | @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) 58 | private void onKick(PlayerKickEvent event) { 59 | permissionCacheMap.remove(event.getPlayer().getUniqueId()); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/proxy/bungee/BungeeCordChannel.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.proxy.bungee; 2 | 3 | public class BungeeCordChannel { 4 | public static final String BUNGEE_CHANNEL = "BungeeCord"; 5 | public static final String SUB_CHANNEL = "asw"; 6 | } 7 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/proxy/bungee/BungeeReceiver.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.proxy.bungee; 2 | 3 | import com.google.common.io.ByteArrayDataInput; 4 | import com.google.common.io.ByteStreams; 5 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords; 6 | import io.wdsj.asw.bukkit.manage.notice.Notifier; 7 | import io.wdsj.asw.bukkit.setting.PluginSettings; 8 | import io.wdsj.asw.common.constant.networking.ChannelDataConstant; 9 | import org.bukkit.entity.Player; 10 | import org.bukkit.plugin.messaging.PluginMessageListener; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | import java.util.Locale; 14 | 15 | import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.LOGGER; 16 | import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager; 17 | 18 | @SuppressWarnings("UnstableApiUsage") 19 | public class BungeeReceiver implements PluginMessageListener { 20 | private boolean warned = false; 21 | @Override 22 | public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) { 23 | if (!settingsManager.getProperty(PluginSettings.HOOK_BUNGEECORD)) return; 24 | if (channel.equals(BungeeCordChannel.BUNGEE_CHANNEL)) { 25 | ByteArrayDataInput input = ByteStreams.newDataInput(message); 26 | String subChannel = input.readUTF(); 27 | if (subChannel.equals(BungeeCordChannel.SUB_CHANNEL)) { 28 | if (!input.readUTF().equals(AdvancedSensitiveWords.PLUGIN_VERSION) && !warned) { 29 | LOGGER.warning("Plugin version mismatch! Things may not work properly."); 30 | warned = true; 31 | } 32 | // noinspection SwitchStatementWithTooFewBranches 33 | switch (input.readUTF().toLowerCase(Locale.ROOT)) { // Use switch for future updates 34 | case ChannelDataConstant.NOTICE: 35 | String playerName = input.readUTF(); 36 | String moduleType = input.readUTF(); 37 | String violationCount = input.readUTF(); 38 | String originalMessage = input.readUTF(); 39 | String censoredWordList = input.readUTF(); 40 | String serverName = input.readUTF(); 41 | if (settingsManager.getProperty(PluginSettings.NOTICE_OPERATOR)) { 42 | Notifier.noticeFromProxy(playerName, serverName, moduleType, violationCount, originalMessage, censoredWordList); 43 | } 44 | break; 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/proxy/bungee/BungeeSender.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.proxy.bungee; 2 | 3 | import com.google.common.io.ByteArrayDataOutput; 4 | import com.google.common.io.ByteStreams; 5 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords; 6 | import io.wdsj.asw.bukkit.manage.punish.ViolationCounter; 7 | import io.wdsj.asw.bukkit.type.ModuleType; 8 | import io.wdsj.asw.common.constant.networking.ChannelDataConstant; 9 | import io.wdsj.asw.common.datatype.io.LimitedByteArrayDataOutput; 10 | import org.bukkit.entity.Player; 11 | 12 | import java.util.List; 13 | 14 | import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.LOGGER; 15 | 16 | @SuppressWarnings("UnstableApiUsage") 17 | public class BungeeSender { 18 | public static void sendNotifyMessage(Player violatedPlayer, ModuleType moduleType, String originalMessage, List censoredList) { 19 | LimitedByteArrayDataOutput out = LimitedByteArrayDataOutput.newDataOutput(32767); 20 | try { 21 | out.writeUTF(BungeeCordChannel.SUB_CHANNEL); 22 | out.writeUTF(AdvancedSensitiveWords.PLUGIN_VERSION); 23 | out.writeUTF(ChannelDataConstant.NOTICE); 24 | out.writeUTF(violatedPlayer.getName()); 25 | out.writeUTF(moduleType.toString()); 26 | out.writeUTF(String.valueOf(ViolationCounter.INSTANCE.getViolationCount(violatedPlayer))); 27 | out.writeUTF(originalMessage); 28 | out.writeUTF(censoredList.toString()); 29 | } catch (Exception e) { 30 | LOGGER.warning("Failed to send message to BungeeCord: " + e.getMessage()); 31 | return; 32 | } 33 | byte[] data = out.toByteArray(); 34 | violatedPlayer.sendPluginMessage(AdvancedSensitiveWords.getInstance(), BungeeCordChannel.BUNGEE_CHANNEL, data); 35 | } 36 | 37 | public static void executeBungeeCommand(Player violatedPlayer, String command) { 38 | ByteArrayDataOutput out = ByteStreams.newDataOutput(); 39 | out.writeUTF(BungeeCordChannel.SUB_CHANNEL); 40 | out.writeUTF(AdvancedSensitiveWords.PLUGIN_VERSION); 41 | out.writeUTF(ChannelDataConstant.COMMAND_PROXY); 42 | out.writeUTF(command); 43 | byte[] data = out.toByteArray(); 44 | violatedPlayer.sendPluginMessage(AdvancedSensitiveWords.getInstance(), BungeeCordChannel.BUNGEE_CHANNEL, data); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/proxy/velocity/VelocityChannel.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.proxy.velocity; 2 | 3 | public class VelocityChannel { 4 | public static final String CHANNEL = "asw:main"; 5 | } 6 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/proxy/velocity/VelocityReceiver.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.proxy.velocity; 2 | 3 | import com.google.common.io.ByteArrayDataInput; 4 | import com.google.common.io.ByteStreams; 5 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords; 6 | import io.wdsj.asw.bukkit.manage.notice.Notifier; 7 | import io.wdsj.asw.bukkit.setting.PluginSettings; 8 | import io.wdsj.asw.common.constant.networking.ChannelDataConstant; 9 | import org.bukkit.entity.Player; 10 | import org.bukkit.plugin.messaging.PluginMessageListener; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | import java.util.Locale; 14 | 15 | import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.LOGGER; 16 | import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager; 17 | 18 | @SuppressWarnings("UnstableApiUsage") 19 | public class VelocityReceiver implements PluginMessageListener { 20 | private boolean warned = false; 21 | @Override 22 | public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) { 23 | if (!settingsManager.getProperty(PluginSettings.HOOK_VELOCITY)) return; 24 | if (channel.equals(VelocityChannel.CHANNEL)) { 25 | ByteArrayDataInput input = ByteStreams.newDataInput(message); 26 | if (!input.readUTF().equals(AdvancedSensitiveWords.PLUGIN_VERSION) && !warned) { 27 | LOGGER.warning("Plugin version mismatch! Things may not work properly."); 28 | warned = true; 29 | } 30 | // noinspection SwitchStatementWithTooFewBranches 31 | switch (input.readUTF().toLowerCase(Locale.ROOT)) { // Use switch for future updates 32 | case ChannelDataConstant.NOTICE: 33 | String playerName = input.readUTF(); 34 | String moduleType = input.readUTF(); 35 | String violationCount = input.readUTF(); 36 | String originalMsg = input.readUTF(); 37 | String censoredWordList = input.readUTF(); 38 | String serverName = input.readUTF(); 39 | if (settingsManager.getProperty(PluginSettings.NOTICE_OPERATOR)) { 40 | Notifier.noticeFromProxy(playerName, serverName, moduleType, violationCount, originalMsg, censoredWordList); 41 | } 42 | break; 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/proxy/velocity/VelocitySender.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.proxy.velocity; 2 | 3 | import com.google.common.io.ByteArrayDataOutput; 4 | import com.google.common.io.ByteStreams; 5 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords; 6 | import io.wdsj.asw.bukkit.manage.punish.ViolationCounter; 7 | import io.wdsj.asw.bukkit.type.ModuleType; 8 | import io.wdsj.asw.common.constant.networking.ChannelDataConstant; 9 | import io.wdsj.asw.common.datatype.io.LimitedByteArrayDataOutput; 10 | import org.bukkit.entity.Player; 11 | 12 | import java.util.List; 13 | 14 | import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.LOGGER; 15 | 16 | @SuppressWarnings("UnstableApiUsage") 17 | public class VelocitySender { 18 | public static void sendNotifyMessage(Player violatedPlayer, ModuleType moduleType, String originalMessage, List censoredList) { 19 | LimitedByteArrayDataOutput out = LimitedByteArrayDataOutput.newDataOutput(32767); 20 | try { 21 | out.writeUTF(AdvancedSensitiveWords.PLUGIN_VERSION); 22 | out.writeUTF(ChannelDataConstant.NOTICE); 23 | out.writeUTF(violatedPlayer.getName()); 24 | out.writeUTF(moduleType.toString()); 25 | out.writeUTF(String.valueOf(ViolationCounter.INSTANCE.getViolationCount(violatedPlayer))); 26 | out.writeUTF(originalMessage); 27 | out.writeUTF(censoredList.toString()); 28 | } catch (Exception e) { 29 | LOGGER.warning("Failed to send message to Velocity: " + e.getMessage()); 30 | return; 31 | } 32 | byte[] data = out.toByteArray(); 33 | violatedPlayer.sendPluginMessage(AdvancedSensitiveWords.getInstance(), VelocityChannel.CHANNEL, data); 34 | } 35 | 36 | public static void executeVelocityCommand(Player violatedPlayer, String command) { 37 | ByteArrayDataOutput out = ByteStreams.newDataOutput(); 38 | out.writeUTF(AdvancedSensitiveWords.PLUGIN_VERSION); 39 | out.writeUTF(ChannelDataConstant.COMMAND_PROXY); 40 | out.writeUTF(command); 41 | byte[] data = out.toByteArray(); 42 | violatedPlayer.sendPluginMessage(AdvancedSensitiveWords.getInstance(), VelocityChannel.CHANNEL, data); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/service/BukkitLibraryService.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.service; 2 | 3 | import com.alessiodp.libby.BukkitLibraryManager; 4 | import com.alessiodp.libby.Library; 5 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords; 6 | 7 | import java.util.Locale; 8 | 9 | public class BukkitLibraryService { 10 | private final BukkitLibraryManager libraryManager; 11 | private static final Library ollama4j = Library.builder() 12 | .groupId("com{}github{}HaHaWTH") 13 | .artifactId("ollama4j-j8") 14 | .resolveTransitiveDependencies(true) 15 | .version("8ce2ad8840") 16 | .build(); 17 | 18 | private static final Library openai4j = Library.builder() 19 | .groupId("dev{}ai4j") 20 | .artifactId("openai4j") 21 | .resolveTransitiveDependencies(true) 22 | .version("0.27.0") 23 | .build(); 24 | 25 | private static final Library caffeine = Library.builder() 26 | .groupId("com{}github{}ben-manes{}caffeine") 27 | .artifactId("caffeine") 28 | .resolveTransitiveDependencies(false) 29 | .version("2.9.3") 30 | .build(); 31 | 32 | public BukkitLibraryService(AdvancedSensitiveWords plugin) { 33 | libraryManager = new BukkitLibraryManager(plugin); 34 | if (Locale.getDefault().getCountry().toUpperCase(Locale.ROOT).equals("CN")) { 35 | libraryManager.addRepository("https://maven.aliyun.com/repository/public/"); 36 | libraryManager.addRepository("https://mirrors.cloud.tencent.com/nexus/repository/maven-public/"); 37 | libraryManager.addRepository("https://repo.huaweicloud.com/repository/maven/"); 38 | } else { 39 | libraryManager.addSonatype(); 40 | libraryManager.addMavenCentral(); 41 | } 42 | libraryManager.addJitPack(); 43 | } 44 | 45 | public void loadRequired() { 46 | libraryManager.loadLibraries(caffeine); 47 | } 48 | public void loadOllamaOptional() { 49 | libraryManager.loadLibrary(ollama4j); 50 | } 51 | 52 | public void loadOpenAiOptional() { 53 | libraryManager.loadLibrary(openai4j); 54 | } 55 | 56 | public void loadWhisperJniOptional() { 57 | libraryManager.loadLibraries(Library.builder() 58 | .groupId("io{}github{}givimad") 59 | .artifactId("whisper-jni") 60 | .version("1.6.1") 61 | .build()); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/service/ListenerService.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.service; 2 | 3 | import com.github.retrooper.packetevents.PacketEvents; 4 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords; 5 | import io.wdsj.asw.bukkit.annotation.PaperEventHandler; 6 | import io.wdsj.asw.bukkit.listener.*; 7 | import io.wdsj.asw.bukkit.listener.packet.ASWBookPacketListener; 8 | import io.wdsj.asw.bukkit.listener.packet.ASWChatPacketListener; 9 | import io.wdsj.asw.bukkit.listener.paper.PaperFakeMessageExecutor; 10 | import io.wdsj.asw.bukkit.setting.PluginSettings; 11 | import io.wdsj.asw.bukkit.util.Utils; 12 | import org.bukkit.Bukkit; 13 | import org.bukkit.event.EventHandler; 14 | import org.bukkit.event.HandlerList; 15 | import org.bukkit.event.Listener; 16 | 17 | import java.lang.reflect.Method; 18 | 19 | import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.*; 20 | import static io.wdsj.asw.bukkit.util.Utils.isClassLoaded; 21 | 22 | public class ListenerService { 23 | private final AdvancedSensitiveWords plugin; 24 | private static final boolean isModernPaper = Utils.isClassLoaded( 25 | "io.papermc.paper.event.player.AsyncChatEvent" 26 | ); 27 | public ListenerService(AdvancedSensitiveWords plugin) { 28 | this.plugin = plugin; 29 | } 30 | 31 | public void registerListeners() { 32 | if (!AdvancedSensitiveWords.isEventMode()) { 33 | if (USE_PE) { 34 | try { 35 | if (settingsManager.getProperty(PluginSettings.ENABLE_CHAT_CHECK)) { 36 | PacketEvents.getAPI().getEventManager().registerListener(ASWChatPacketListener.class.getConstructor().newInstance()); 37 | } 38 | if (settingsManager.getProperty(PluginSettings.ENABLE_BOOK_EDIT_CHECK)) { 39 | PacketEvents.getAPI().getEventManager().registerListener(ASWBookPacketListener.class.getConstructor().newInstance()); 40 | } 41 | } catch (Exception e) { 42 | LOGGER.severe("Failed to register packetevents listener." + 43 | " This should not happen, please report to the author"); 44 | LOGGER.severe(e.getMessage()); 45 | } 46 | PacketEvents.getAPI().init(); 47 | } else { 48 | LOGGER.warning("Cannot use packetevents, using event mode instead."); 49 | registerChatBookEventListeners(); 50 | setEventMode(true); 51 | } 52 | } else { 53 | registerChatBookEventListeners(); 54 | } 55 | registerEventListener(ShadowListener.class); 56 | registerEventListener(AltsListener.class); 57 | if (!registerEventListener(PaperFakeMessageExecutor.class)) { 58 | registerEventListener(FakeMessageExecutor.class); 59 | } 60 | if (settingsManager.getProperty(PluginSettings.ENABLE_SIGN_EDIT_CHECK)) { 61 | registerEventListener(SignListener.class); 62 | } 63 | if (settingsManager.getProperty(PluginSettings.ENABLE_ANVIL_EDIT_CHECK)) { 64 | registerEventListener(AnvilListener.class); 65 | } 66 | if (settingsManager.getProperty(PluginSettings.ENABLE_PLAYER_NAME_CHECK)) { 67 | registerEventListener(PlayerLoginListener.class); 68 | } 69 | if (settingsManager.getProperty(PluginSettings.ENABLE_PLAYER_ITEM_CHECK)) { 70 | registerEventListener(PlayerItemListener.class); 71 | } 72 | if (settingsManager.getProperty(PluginSettings.CHAT_BROADCAST_CHECK)) { 73 | if (isClassLoaded("org.bukkit.event.server.BroadcastMessageEvent")) { 74 | registerEventListener(BroadCastListener.class); 75 | } else { 76 | LOGGER.info("BroadcastMessage is not available, please disable chat broadcast check in config.yml"); 77 | } 78 | } 79 | if (settingsManager.getProperty(PluginSettings.CLEAN_PLAYER_DATA_CACHE)) { 80 | registerEventListener(QuitDataCleaner.class); 81 | } 82 | if (settingsManager.getProperty(PluginSettings.CHECK_FOR_UPDATE)) { 83 | registerEventListener(JoinUpdateNotifier.class); 84 | } 85 | } 86 | 87 | public void unregisterListeners() { 88 | if (!isEventMode()) { 89 | if (USE_PE) { 90 | PacketEvents.getAPI().terminate(); 91 | } 92 | } 93 | HandlerList.unregisterAll(plugin); 94 | } 95 | 96 | 97 | private boolean registerEventListener(Class listenerClass) { 98 | if (!isTargetListenerHasAllClasses(listenerClass)) { 99 | return false; 100 | } 101 | if (isPaperListener(listenerClass)) { 102 | if (isModernPaper) { 103 | Bukkit.getPluginManager().registerEvents(newListenerWithNoArgConstructor(listenerClass), plugin); 104 | LOGGER.info("Using Paper event listener " + listenerClass.getSimpleName() + "."); 105 | return true; 106 | } 107 | return false; 108 | } else { 109 | Bukkit.getPluginManager().registerEvents(newListenerWithNoArgConstructor(listenerClass), plugin); 110 | return true; 111 | } 112 | } 113 | 114 | private Listener newListenerWithNoArgConstructor(Class listenerClass) { 115 | try { 116 | return listenerClass.getConstructor().newInstance(); 117 | } catch (Exception e) { 118 | throw new IllegalStateException("Failed to register listener " + listenerClass.getSimpleName()); 119 | } 120 | } 121 | 122 | private boolean isTargetListenerHasAllClasses(Class listener) { 123 | try { 124 | Method[] methods = listener.getDeclaredMethods(); 125 | for (Method method : methods) { 126 | //noinspection StatementWithEmptyBody 127 | if (method.getAnnotation(EventHandler.class) == null || method.getParameterCount() != 1) { 128 | } 129 | } 130 | } catch (Throwable e) { 131 | return false; 132 | } 133 | return true; 134 | } 135 | private boolean isPaperListener(Class listener) { 136 | return listener.getAnnotation(PaperEventHandler.class) != null; 137 | } 138 | 139 | private void registerChatBookEventListeners() { 140 | if (settingsManager.getProperty(PluginSettings.ENABLE_CHAT_CHECK)) { 141 | if (true) { // Always listen AsyncPlayerChatEvent 142 | registerEventListener(ChatListener.class); 143 | } 144 | registerEventListener(CommandListener.class); 145 | } 146 | if (settingsManager.getProperty(PluginSettings.ENABLE_BOOK_EDIT_CHECK)) { 147 | registerEventListener(BookListener.class); 148 | } 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/service/hook/VoiceChatHookService.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.service.hook; 2 | 3 | import com.github.Anon8281.universalScheduler.scheduling.tasks.MyScheduledTask; 4 | import de.maxhenkel.voicechat.api.BukkitVoicechatService; 5 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords; 6 | import io.wdsj.asw.bukkit.integration.voicechat.VoiceChatExtension; 7 | import io.wdsj.asw.bukkit.integration.voicechat.WhisperVoiceTranscribeTool; 8 | import io.wdsj.asw.bukkit.setting.PluginSettings; 9 | import io.wdsj.asw.bukkit.task.voicechat.VoiceChatTranscribeTask; 10 | import io.wdsj.asw.bukkit.util.SchedulingUtils; 11 | 12 | import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.LOGGER; 13 | import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager; 14 | 15 | public class VoiceChatHookService { 16 | private final AdvancedSensitiveWords plugin; 17 | private VoiceChatExtension voiceChatExtension; 18 | private MyScheduledTask transcribeTask; 19 | private WhisperVoiceTranscribeTool transcribeTool; 20 | public VoiceChatHookService(AdvancedSensitiveWords plugin) { 21 | this.plugin = plugin; 22 | } 23 | 24 | public void register() { 25 | BukkitVoicechatService service = plugin.getServer().getServicesManager().load(BukkitVoicechatService.class); 26 | if (service != null) { 27 | try { 28 | voiceChatExtension = new VoiceChatExtension(); 29 | service.registerPlugin(voiceChatExtension); 30 | LOGGER.info("Successfully hooked into voicechat."); 31 | if (settingsManager.getProperty(PluginSettings.VOICE_REALTIME_TRANSCRIBING)) { 32 | transcribeTool = new WhisperVoiceTranscribeTool(); 33 | long interval = settingsManager.getProperty(PluginSettings.VOICE_CHECK_INTERVAL); 34 | transcribeTask = new VoiceChatTranscribeTask(voiceChatExtension, transcribeTool).runTaskTimerAsynchronously(plugin, interval * 20L, interval * 20L); 35 | } 36 | } catch (Exception e) { 37 | LOGGER.warning("Failed to register voicechat listener." + 38 | " This should not happen, please report to the author: " + e.getMessage()); 39 | } 40 | } else { 41 | LOGGER.warning("Failed to hook into voicechat."); 42 | } 43 | } 44 | 45 | public void unregister() { 46 | if (voiceChatExtension != null) { 47 | plugin.getServer().getServicesManager().unregister(voiceChatExtension); 48 | } 49 | SchedulingUtils.cancelTaskSafely(transcribeTask); 50 | if (transcribeTool != null) { 51 | transcribeTool.shutdown(); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/setting/PluginMessages.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.setting; 2 | 3 | import ch.jalu.configme.Comment; 4 | import ch.jalu.configme.SettingsHolder; 5 | import ch.jalu.configme.configurationdata.CommentsConfiguration; 6 | import ch.jalu.configme.properties.Property; 7 | 8 | import static ch.jalu.configme.properties.PropertyInitializer.newProperty; 9 | 10 | public class PluginMessages implements SettingsHolder { 11 | @Comment("玩家发送敏感消息时候的提示") 12 | public static final Property MESSAGE_ON_CHAT = newProperty("Chat.messageOnChat", "&c请勿在聊天中发送敏感词汇."); 13 | @Comment("玩家写入敏感消息时的提示") 14 | public static final Property MESSAGE_ON_SIGN = newProperty("Sign.messageOnSign", "&c请勿在告示牌中写入敏感词汇."); 15 | @Comment("玩家在铁砧重命名时写入敏感消息的提示") 16 | public static final Property MESSAGE_ON_ANVIL_RENAME = newProperty("Anvil.messageOnAnvilRename", "&c请勿在铁砧中写入敏感词汇."); 17 | @Comment("玩家在书中写入敏感消息的提示") 18 | public static final Property MESSAGE_ON_BOOK = newProperty("Book.messageOnBook", "&c请勿在书中写入敏感词汇."); 19 | @Comment("玩家名包含敏感词时的消息") 20 | public static final Property MESSAGE_ON_NAME = newProperty("Name.messageOnName", "&c您的用户名包含敏感词,请修改您的用户名或联系管理员."); 21 | @Comment("玩家物品包含敏感词时的消息") 22 | public static final Property MESSAGE_ON_ITEM = newProperty("Item.messageOnItem", "&c您的物品包含敏感词."); 23 | @Comment("玩家发送违规语音消息时的提示") 24 | public static final Property MESSAGE_ON_VOICE = newProperty("Voice.messageOnVoice", "&c请勿发送违规语音."); 25 | @Comment("插件重载消息") 26 | public static final Property MESSAGE_ON_COMMAND_RELOAD = newProperty("Plugin.messageOnCommandReload", "&aAdvancedSensitiveWords 已重新加载."); 27 | @Comment("违规次数重置消息") 28 | public static final Property MESSAGE_ON_VIOLATION_RESET = newProperty("Plugin.messageOnViolationReset", "&bASW&7Notify >> &a已重置所有玩家的违规次数!"); 29 | @Comment("插件帮助菜单") 30 | public static final Property MESSAGE_ON_COMMAND_HELP = newProperty("Plugin.messageOnCommandHelp", "&bAdvancedSensitiveWords&r---&b帮助菜单\n &7/asw reload&7: &a重新加载过滤词库和插件配置\n &7/asw reloadconfig: &a重新加载插件配置\n &7/asw add <敏感词>: &a添加敏感词\n &7/asw remove <敏感词>: &a移除敏感词\n &7/asw addallow <敏感词>: &a添加敏感词到白名单\n &7/asw removeallow <敏感词>: &a移除敏感词白名单\n &7/asw status: &a显示插件状态菜单\n &7/asw test <待测消息>: &a运行敏感词测试\n &7/asw help: &a显示帮助信息\n &7/asw info <玩家名称>: &a获取玩家违规次数\n &7/asw punish <玩家名称> [惩罚]: &a手动惩罚玩家, 不填惩罚将使用配置文件内容"); 31 | @Comment("插件状态菜单") 32 | public static final Property MESSAGE_ON_COMMAND_STATUS = newProperty("Plugin.messageOnCommandStatus", "&bAdvancedSensitiveWords&r---&b插件状态(%version%)(MC %mc_version%)\n &7系统信息: &b%platform% %bit% (Java %java_version% -- %java_vendor%)\n &7初始化: %init%\n &7当前模式: %mode%\n &7已过滤消息数: &a%num%\n &7近20次处理平均耗时: %ms%"); 33 | @Comment("敏感词测试返回") 34 | public static final Property MESSAGE_ON_COMMAND_TEST = newProperty("Plugin.commandTest.testResultTrue", "&b一眼丁真, 鉴定为敏感词(鉴定报告)\n &7原消息: &c%original_msg%\n &7过滤后消息: &a%processed_msg%\n &7敏感词列表: &b%censored_list%"); 35 | @Comment("敏感词测试通过") 36 | public static final Property MESSAGE_ON_COMMAND_TEST_PASS = newProperty("Plugin.commandTest.testResultPass", "&a待测消息中没有敏感词喵~"); 37 | @Comment("敏感词测试未初始化") 38 | public static final Property MESSAGE_ON_COMMAND_TEST_NOT_INIT = newProperty("Plugin.commandTest.testNotInit", "&c插件还没有初始化完毕喵"); 39 | @Comment("解析方法出错") 40 | public static final Property MESSAGE_ON_COMMAND_PUNISH_PARSE_ERROR = newProperty("Plugin.commandPunish.parseError", "&c解析方法出错, 请检查指令格式."); 41 | @Comment("已惩罚") 42 | public static final Property MESSAGE_ON_COMMAND_PUNISH_SUCCESS = newProperty("Plugin.commandPunish.success", "&a成功惩罚玩家 %player%."); 43 | @Comment("敏感词添加成功") 44 | public static final Property MESSAGE_ON_COMMAND_ADD_SUCCESS = newProperty("Plugin.commandAdd.success", "&a敏感词添加成功."); 45 | @Comment("敏感词移除成功") 46 | public static final Property MESSAGE_ON_COMMAND_REMOVE_SUCCESS = newProperty("Plugin.commandRemove.success", "&a敏感词移除成功."); 47 | @Comment("没有权限执行该指令") 48 | public static final Property NO_PERMISSION = newProperty("Plugin.noPermission", "&c你没有权限执行该指令."); 49 | @Comment("未知命令") 50 | public static final Property UNKNOWN_COMMAND = newProperty("Plugin.unknownCommand", "&c未知命令, 请使用 &7/asw help"); 51 | @Comment("命令参数不足") 52 | public static final Property NOT_ENOUGH_ARGS = newProperty("Plugin.argsNotEnough", "&c参数不足, 请使用 &7/asw help"); 53 | @Comment("找不到对应玩家") 54 | public static final Property PLAYER_NOT_FOUND = newProperty("Plugin.noSuchPlayer", "&c找不到对应玩家"); 55 | @Comment("管理员提醒消息") 56 | public static final Property ADMIN_REMINDER = newProperty("Plugin.noticeOperator", "&bASW&7Notify >> &7玩家 &c%player% &7触发了敏感词检测(%type%)(VL: %violation%)(原消息: %message%)"); 57 | @Comment("跨服提醒消息") 58 | public static final Property ADMIN_REMINDER_PROXY = newProperty("Plugin.noticeOperatorProxy", "&bASW&7Notify >> &7玩家 &c%player% (服务器: %server_name%) &7触发了敏感词检测(%type%)(VL: %violation%)(原消息: %message%)"); 59 | @Comment("更新可用") 60 | public static final Property UPDATE_AVAILABLE = newProperty("Plugin.updateAvailable", "&bASW&7Notify >> &7插件有可用更新(%latest_version%), 当前正在运行: &b%current_version%."); 61 | @Comment("获取到玩家信息") 62 | public static final Property MESSAGE_ON_PLAYER_INFO = newProperty("Plugin.messageOnCommandInfo", "&bAdvancedSensitiveWords&r---&b玩家信息\n &7玩家名称: &b%player%\n &7违规次数: &a%violation%"); 63 | @Comment("重置玩家违规次数") 64 | public static final Property MESSAGE_ON_COMMAND_RESET = newProperty("Plugin.messageOnCommandReset", "&a已重置玩家 %player% 的违规次数."); 65 | 66 | @Override 67 | public void registerComments(CommentsConfiguration conf) { 68 | conf.setComment("", "AdvancedSensitiveWords 插件消息配置"); 69 | conf.setComment("Plugin", "插件消息"); 70 | conf.setComment("Plugin.commandTest", "敏感词测试消息(不计入已过滤消息)"); 71 | conf.setComment("Plugin.commandPunish", "惩罚消息"); 72 | conf.setComment("Plugin.commandAdd", "Add 指令相关"); 73 | conf.setComment("Plugin.commandRemove", "Remove 指令相关"); 74 | conf.setComment("Chat", "聊天检测消息"); 75 | conf.setComment("Book", "书检测消息"); 76 | conf.setComment("Sign", "告示牌检测消息"); 77 | conf.setComment("Anvil", "铁砧重命名检测消息"); 78 | conf.setComment("Name", "玩家名检测消息"); 79 | conf.setComment("Voice", "玩家语音检测消息"); 80 | } 81 | 82 | private PluginMessages() { 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/task/punish/ViolationResetTask.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.task.punish; 2 | 3 | import com.github.Anon8281.universalScheduler.UniversalRunnable; 4 | import io.wdsj.asw.bukkit.manage.notice.Notifier; 5 | import io.wdsj.asw.bukkit.manage.punish.ViolationCounter; 6 | import io.wdsj.asw.bukkit.setting.PluginMessages; 7 | import io.wdsj.asw.bukkit.setting.PluginSettings; 8 | import io.wdsj.asw.bukkit.util.message.MessageUtils; 9 | import org.bukkit.Bukkit; 10 | import org.bukkit.entity.Player; 11 | 12 | import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager; 13 | 14 | /** 15 | * Asynchronous task to reset the violation count of players. 16 | */ 17 | public class ViolationResetTask extends UniversalRunnable { 18 | @Override 19 | public void run() { 20 | if (settingsManager.getProperty(PluginSettings.ONLY_RESET_ONLINE_PLAYERS)) { 21 | for (Player player : Bukkit.getOnlinePlayers()) { 22 | ViolationCounter.INSTANCE.resetViolationCount(player); 23 | } 24 | } else { 25 | ViolationCounter.INSTANCE.resetAllViolations(); 26 | } 27 | String message = MessageUtils.retrieveMessage(PluginMessages.MESSAGE_ON_VIOLATION_RESET); 28 | Notifier.normalNotice(message); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /bukkit/src/main/java/io/wdsj/asw/bukkit/task/voicechat/VoiceChatTranscribeTask.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.task.voicechat; 2 | 3 | import com.github.Anon8281.universalScheduler.UniversalRunnable; 4 | import io.wdsj.asw.bukkit.integration.voicechat.VoiceChatExtension; 5 | import io.wdsj.asw.bukkit.integration.voicechat.WhisperVoiceTranscribeTool; 6 | import io.wdsj.asw.bukkit.manage.notice.Notifier; 7 | import io.wdsj.asw.bukkit.manage.punish.Punishment; 8 | import io.wdsj.asw.bukkit.manage.punish.ViolationCounter; 9 | import io.wdsj.asw.bukkit.proxy.bungee.BungeeSender; 10 | import io.wdsj.asw.bukkit.proxy.velocity.VelocitySender; 11 | import io.wdsj.asw.bukkit.setting.PluginMessages; 12 | import io.wdsj.asw.bukkit.setting.PluginSettings; 13 | import io.wdsj.asw.bukkit.type.ModuleType; 14 | import io.wdsj.asw.bukkit.util.LoggingUtils; 15 | import io.wdsj.asw.bukkit.util.SchedulingUtils; 16 | import io.wdsj.asw.bukkit.util.TimingUtils; 17 | import io.wdsj.asw.bukkit.util.Utils; 18 | import io.wdsj.asw.bukkit.util.message.MessageUtils; 19 | import org.bukkit.Bukkit; 20 | import org.bukkit.entity.Player; 21 | 22 | import java.util.List; 23 | import java.util.Map; 24 | import java.util.Set; 25 | import java.util.UUID; 26 | 27 | import static io.wdsj.asw.bukkit.AdvancedSensitiveWords.*; 28 | 29 | public class VoiceChatTranscribeTask extends UniversalRunnable { 30 | private final VoiceChatExtension extension; 31 | private final WhisperVoiceTranscribeTool transcribeTool; 32 | public VoiceChatTranscribeTask(VoiceChatExtension extension, WhisperVoiceTranscribeTool transcribeTool) { 33 | this.extension = extension; 34 | this.transcribeTool = transcribeTool; 35 | } 36 | @Override 37 | public void run() { 38 | if (!settingsManager.getProperty(PluginSettings.VOICE_REALTIME_TRANSCRIBING)) { 39 | if (!extension.connectedPlayers.isEmpty()) extension.connectedPlayers.clear(); 40 | return; 41 | } 42 | if (extension.connectedPlayers.isEmpty()) return; 43 | Set> entries = extension.connectedPlayers.entrySet(); 44 | for (Map.Entry entry : entries) { 45 | if (!isInitialized) continue; 46 | UUID uuid = entry.getKey(); 47 | float[] data = entry.getValue(); 48 | Player player = SchedulingUtils.callSyncMethod(() -> Bukkit.getPlayer(uuid)); 49 | extension.connectedPlayers.remove(uuid); // Early remove before transcribing 50 | if (player == null) continue; 51 | transcribeTool.transcribe(data) 52 | .thenAccept(text -> { 53 | if (text.isEmpty()) return; 54 | List censoredWordList = sensitiveWordBs.findAll(text); 55 | long startTime = System.currentTimeMillis(); 56 | if (!censoredWordList.isEmpty()) { 57 | Utils.messagesFilteredNum.getAndIncrement(); 58 | if (settingsManager.getProperty(PluginSettings.VOICE_SEND_MESSAGE)) { 59 | MessageUtils.sendMessage(player, PluginMessages.MESSAGE_ON_VOICE); 60 | } 61 | if (settingsManager.getProperty(PluginSettings.LOG_VIOLATION)) { 62 | LoggingUtils.logViolation(player.getName() + "(IP: " + Utils.getPlayerIp(player) + ")(Voice)", text + censoredWordList); 63 | } 64 | ViolationCounter.INSTANCE.incrementViolationCount(player); 65 | if (settingsManager.getProperty(PluginSettings.HOOK_VELOCITY)) { 66 | VelocitySender.sendNotifyMessage(player, ModuleType.VOICE, text, censoredWordList); 67 | } 68 | if (settingsManager.getProperty(PluginSettings.HOOK_BUNGEECORD)) { 69 | BungeeSender.sendNotifyMessage(player, ModuleType.VOICE, text, censoredWordList); 70 | } 71 | long endTime = System.currentTimeMillis(); 72 | TimingUtils.addProcessStatistic(endTime, startTime); 73 | if (settingsManager.getProperty(PluginSettings.NOTICE_OPERATOR)) Notifier.notice(player, ModuleType.VOICE, text, censoredWordList); 74 | if (settingsManager.getProperty(PluginSettings.VOICE_PUNISH)) { 75 | SchedulingUtils.runSyncIfNotOnMainThread(() -> Punishment.punish(player)); 76 | } 77 | } 78 | }); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/AltsListener.kt: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.listener 2 | 3 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager 4 | import io.wdsj.asw.bukkit.manage.punish.PlayerAltController 5 | import io.wdsj.asw.bukkit.setting.PluginSettings 6 | import io.wdsj.asw.bukkit.util.PlayerUtils 7 | import io.wdsj.asw.bukkit.util.Utils 8 | import org.bukkit.event.EventHandler 9 | import org.bukkit.event.EventPriority 10 | import org.bukkit.event.Listener 11 | import org.bukkit.event.player.PlayerJoinEvent 12 | import org.bukkit.event.player.PlayerKickEvent 13 | import org.bukkit.event.player.PlayerQuitEvent 14 | 15 | class AltsListener : Listener { 16 | @EventHandler 17 | fun onPlayerJoin(event: PlayerJoinEvent) { 18 | if (!settingsManager.getProperty(PluginSettings.ENABLE_ALTS_CHECK)) { 19 | return 20 | } 21 | val player = event.player 22 | if (PlayerUtils.isNpc(player)) { 23 | return 24 | } 25 | val ip = Utils.getPlayerIp(player) 26 | if (!PlayerAltController.contains(ip, player)) { 27 | PlayerAltController.addToAlts(ip, player) 28 | } 29 | } 30 | 31 | @EventHandler 32 | fun onPlayerQuit(event: PlayerQuitEvent) { 33 | if (!settingsManager.getProperty(PluginSettings.ENABLE_ALTS_CHECK)) { 34 | return 35 | } 36 | val player = event.player 37 | if (PlayerUtils.isNpc(player)) { 38 | return 39 | } 40 | val ip = Utils.getPlayerIp(player) 41 | PlayerAltController.removeFromAlts(ip, player) 42 | } 43 | 44 | @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) 45 | fun onPlayerKick(event: PlayerKickEvent) { 46 | if (!settingsManager.getProperty(PluginSettings.ENABLE_ALTS_CHECK)) { 47 | return 48 | } 49 | val player = event.player 50 | if (PlayerUtils.isNpc(player)) { 51 | return 52 | } 53 | val ip = Utils.getPlayerIp(player) 54 | PlayerAltController.removeFromAlts(ip, player) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/AnvilListener.kt: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.listener 2 | 3 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords 4 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager 5 | import io.wdsj.asw.bukkit.manage.notice.Notifier 6 | import io.wdsj.asw.bukkit.manage.punish.Punishment 7 | import io.wdsj.asw.bukkit.manage.punish.ViolationCounter 8 | import io.wdsj.asw.bukkit.permission.PermissionsEnum 9 | import io.wdsj.asw.bukkit.permission.cache.CachingPermTool 10 | import io.wdsj.asw.bukkit.proxy.bungee.BungeeSender 11 | import io.wdsj.asw.bukkit.proxy.velocity.VelocitySender 12 | import io.wdsj.asw.bukkit.setting.PluginMessages 13 | import io.wdsj.asw.bukkit.setting.PluginSettings 14 | import io.wdsj.asw.bukkit.type.ModuleType 15 | import io.wdsj.asw.bukkit.util.LoggingUtils 16 | import io.wdsj.asw.bukkit.util.TimingUtils 17 | import io.wdsj.asw.bukkit.util.Utils 18 | import io.wdsj.asw.bukkit.util.message.MessageUtils 19 | import org.bukkit.entity.Player 20 | import org.bukkit.event.EventHandler 21 | import org.bukkit.event.EventPriority 22 | import org.bukkit.event.Listener 23 | import org.bukkit.event.inventory.InventoryClickEvent 24 | import org.bukkit.event.inventory.InventoryType 25 | 26 | class AnvilListener : Listener { 27 | @EventHandler(priority = EventPriority.LOWEST) 28 | fun onAnvil(event: InventoryClickEvent) { 29 | if (!AdvancedSensitiveWords.isInitialized) return 30 | if (!settingsManager.getProperty(PluginSettings.ENABLE_ANVIL_EDIT_CHECK)) return 31 | if (event.inventory.type == InventoryType.ANVIL) { 32 | val player = event.whoClicked as Player 33 | if (CachingPermTool.hasPermission(PermissionsEnum.BYPASS, player)) { 34 | return 35 | } 36 | if (event.rawSlot == 2) { 37 | val outputItem = event.currentItem 38 | val isCancelMode = settingsManager.getProperty(PluginSettings.ANVIL_METHOD).equals("cancel", ignoreCase = true) 39 | if (outputItem != null && outputItem.hasItemMeta()) { 40 | val itemMeta = outputItem.itemMeta 41 | if (itemMeta != null && itemMeta.hasDisplayName()) { 42 | var originalItemName = itemMeta.displayName 43 | if (settingsManager.getProperty(PluginSettings.PRE_PROCESS)) originalItemName = 44 | originalItemName.replace( 45 | Utils.preProcessRegex.toRegex(), "" 46 | ) 47 | val censoredWords = AdvancedSensitiveWords.sensitiveWordBs.findAll(originalItemName) 48 | if (censoredWords.isNotEmpty()) { 49 | val startTime = System.currentTimeMillis() 50 | Utils.messagesFilteredNum.getAndIncrement() 51 | val processedItemName = AdvancedSensitiveWords.sensitiveWordBs.replace(originalItemName) 52 | if (isCancelMode) { 53 | event.isCancelled = true 54 | } else { 55 | itemMeta.setDisplayName(processedItemName) 56 | outputItem.setItemMeta(itemMeta) 57 | } 58 | 59 | if (settingsManager.getProperty(PluginSettings.ANVIL_SEND_MESSAGE)) { 60 | MessageUtils.sendMessage( 61 | player, 62 | PluginMessages.MESSAGE_ON_ANVIL_RENAME 63 | ) 64 | } 65 | 66 | if (settingsManager.getProperty(PluginSettings.LOG_VIOLATION)) { 67 | LoggingUtils.logViolation( 68 | player.name + "(IP: " + Utils.getPlayerIp(player) + ")(Anvil)", 69 | originalItemName + censoredWords 70 | ) 71 | } 72 | ViolationCounter.INSTANCE.incrementViolationCount(player) 73 | 74 | if (settingsManager.getProperty(PluginSettings.HOOK_VELOCITY)) { 75 | VelocitySender.sendNotifyMessage(player, ModuleType.ANVIL, originalItemName, censoredWords) 76 | } 77 | 78 | if (settingsManager.getProperty(PluginSettings.HOOK_BUNGEECORD)) { 79 | BungeeSender.sendNotifyMessage(player, ModuleType.ANVIL, originalItemName, censoredWords) 80 | } 81 | val endTime = System.currentTimeMillis() 82 | TimingUtils.addProcessStatistic(endTime, startTime) 83 | if (settingsManager.getProperty(PluginSettings.NOTICE_OPERATOR)) Notifier.notice( 84 | player, 85 | ModuleType.ANVIL, 86 | originalItemName, 87 | censoredWords 88 | ) 89 | if (settingsManager.getProperty(PluginSettings.ANVIL_PUNISH)) Punishment.punish( 90 | player 91 | ) 92 | } 93 | } 94 | } 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/BroadCastListener.kt: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.listener 2 | 3 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords 4 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager 5 | import io.wdsj.asw.bukkit.setting.PluginSettings 6 | import io.wdsj.asw.bukkit.util.LoggingUtils 7 | import io.wdsj.asw.bukkit.util.TimingUtils 8 | import io.wdsj.asw.bukkit.util.Utils 9 | import org.bukkit.event.EventHandler 10 | import org.bukkit.event.EventPriority 11 | import org.bukkit.event.Listener 12 | import org.bukkit.event.server.BroadcastMessageEvent 13 | 14 | class BroadCastListener : Listener { 15 | @EventHandler(priority = EventPriority.LOWEST) 16 | fun onBroadCast(event: BroadcastMessageEvent) { 17 | if (!AdvancedSensitiveWords.isInitialized || !settingsManager.getProperty(PluginSettings.CHAT_BROADCAST_CHECK)) return 18 | var originalMessage = event.message 19 | if (settingsManager.getProperty(PluginSettings.PRE_PROCESS)) originalMessage = 20 | originalMessage.replace( 21 | Utils.preProcessRegex.toRegex(), "" 22 | ) 23 | val startTime = System.currentTimeMillis() 24 | val censoredWordList = AdvancedSensitiveWords.sensitiveWordBs.findAll(originalMessage) 25 | if (censoredWordList.isNotEmpty()) { 26 | Utils.messagesFilteredNum.getAndIncrement() 27 | val processedMessage = AdvancedSensitiveWords.sensitiveWordBs.replace(originalMessage) 28 | if (settingsManager.getProperty(PluginSettings.CHAT_METHOD) 29 | .equals("cancel", ignoreCase = true) 30 | ) { 31 | event.isCancelled = true 32 | } else { 33 | event.message = processedMessage 34 | } 35 | if (settingsManager.getProperty(PluginSettings.LOG_VIOLATION)) { 36 | LoggingUtils.logViolation("Broadcast(IP: None)(BroadCast)", originalMessage + censoredWordList) 37 | } 38 | val endTime = System.currentTimeMillis() 39 | TimingUtils.addProcessStatistic(endTime, startTime) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/CommandListener.kt: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.listener 2 | 3 | import cc.baka9.catseedlogin.bukkit.CatSeedLoginAPI 4 | import fr.xephi.authme.api.v3.AuthMeApi 5 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords.* 6 | import io.wdsj.asw.bukkit.manage.notice.Notifier 7 | import io.wdsj.asw.bukkit.manage.punish.Punishment 8 | import io.wdsj.asw.bukkit.manage.punish.ViolationCounter 9 | import io.wdsj.asw.bukkit.permission.PermissionsEnum 10 | import io.wdsj.asw.bukkit.permission.cache.CachingPermTool 11 | import io.wdsj.asw.bukkit.proxy.bungee.BungeeSender 12 | import io.wdsj.asw.bukkit.proxy.velocity.VelocitySender 13 | import io.wdsj.asw.bukkit.setting.PluginMessages 14 | import io.wdsj.asw.bukkit.setting.PluginSettings 15 | import io.wdsj.asw.bukkit.type.ModuleType 16 | import io.wdsj.asw.bukkit.util.LoggingUtils 17 | import io.wdsj.asw.bukkit.util.TimingUtils 18 | import io.wdsj.asw.bukkit.util.Utils 19 | import io.wdsj.asw.bukkit.util.message.MessageUtils 20 | import org.bukkit.entity.Player 21 | import org.bukkit.event.EventHandler 22 | import org.bukkit.event.EventPriority 23 | import org.bukkit.event.Listener 24 | import org.bukkit.event.player.PlayerCommandPreprocessEvent 25 | 26 | class CommandListener : Listener { 27 | @EventHandler(priority = EventPriority.LOWEST) 28 | fun onCommand(event: PlayerCommandPreprocessEvent) { 29 | if (!settingsManager.getProperty(PluginSettings.ENABLE_CHAT_CHECK)) return 30 | val player = event.player 31 | val originalCommand = 32 | if (settingsManager.getProperty(PluginSettings.PRE_PROCESS)) event.message.replace( 33 | Utils.preProcessRegex.toRegex(), "" 34 | ) else event.message 35 | if (shouldNotProcess(player, originalCommand)) return 36 | val censoredWordList = sensitiveWordBs.findAll(originalCommand) 37 | val startTime = System.currentTimeMillis() 38 | if (censoredWordList.isNotEmpty()) { 39 | Utils.messagesFilteredNum.getAndIncrement() 40 | val processedCommand = sensitiveWordBs.replace(originalCommand) 41 | if (settingsManager.getProperty(PluginSettings.CHAT_METHOD) 42 | .equals("cancel", ignoreCase = true) 43 | ) { 44 | event.isCancelled = true 45 | } else { 46 | if (Utils.isCommand(processedCommand)) { 47 | event.message = processedCommand 48 | } else { 49 | event.message = "/$processedCommand" 50 | } 51 | } 52 | if (settingsManager.getProperty(PluginSettings.CHAT_SEND_MESSAGE)) { 53 | MessageUtils.sendMessage( 54 | player, 55 | messagesManager.getProperty(PluginMessages.MESSAGE_ON_CHAT) 56 | .replace("%integrated_player%", player.name) 57 | .replace("%integrated_message%", originalCommand) 58 | ) 59 | } 60 | if (settingsManager.getProperty(PluginSettings.LOG_VIOLATION)) { 61 | LoggingUtils.logViolation( 62 | player.name + "(IP: " + Utils.getPlayerIp(player) + ")(Chat)", 63 | originalCommand + censoredWordList 64 | ) 65 | } 66 | ViolationCounter.INSTANCE.incrementViolationCount(player) 67 | if (settingsManager.getProperty(PluginSettings.HOOK_VELOCITY)) { 68 | VelocitySender.sendNotifyMessage(player, ModuleType.CHAT, originalCommand, censoredWordList) 69 | } 70 | if (settingsManager.getProperty(PluginSettings.HOOK_BUNGEECORD)) { 71 | BungeeSender.sendNotifyMessage(player, ModuleType.CHAT, originalCommand, censoredWordList) 72 | } 73 | val endTime = System.currentTimeMillis() 74 | TimingUtils.addProcessStatistic(endTime, startTime) 75 | if (settingsManager.getProperty(PluginSettings.NOTICE_OPERATOR)) Notifier.notice( 76 | player, 77 | ModuleType.CHAT, 78 | originalCommand, 79 | censoredWordList 80 | ) 81 | if (settingsManager.getProperty(PluginSettings.CHAT_PUNISH)) Punishment.punish(player) 82 | } 83 | } 84 | 85 | private fun shouldNotProcess(player: Player, message: String): Boolean { 86 | if (isInitialized && !CachingPermTool.hasPermission( 87 | PermissionsEnum.BYPASS, player) && !Utils.isCommandAndWhiteListed(message)) { 88 | if (isAuthMeAvailable && settingsManager.getProperty(PluginSettings.ENABLE_AUTHME_COMPATIBILITY)) { 89 | if (!AuthMeApi.getInstance().isAuthenticated(player)) return true 90 | } 91 | if (isCslAvailable && settingsManager.getProperty(PluginSettings.ENABLE_CSL_COMPATIBILITY)) { 92 | return !CatSeedLoginAPI.isLogin(player.name) || !CatSeedLoginAPI.isRegister(player.name) 93 | } 94 | return false 95 | } 96 | return true 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/FakeMessageExecutor.kt: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.listener 2 | 3 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager 4 | import io.wdsj.asw.bukkit.listener.abstraction.AbstractFakeMessageExecutor 5 | import io.wdsj.asw.bukkit.manage.punish.PlayerAltController 6 | import io.wdsj.asw.bukkit.setting.PluginSettings 7 | import org.bukkit.Bukkit 8 | import org.bukkit.entity.Player 9 | import org.bukkit.event.EventHandler 10 | import org.bukkit.event.EventPriority 11 | import org.bukkit.event.Listener 12 | import org.bukkit.event.player.AsyncPlayerChatEvent 13 | import java.util.concurrent.ConcurrentHashMap 14 | 15 | class FakeMessageExecutor : Listener, AbstractFakeMessageExecutor() { 16 | @EventHandler(priority = EventPriority.LOWEST) 17 | fun onChatFirst(event: AsyncPlayerChatEvent) { 18 | val player = event.player 19 | if (settingsManager.getProperty(PluginSettings.CHAT_FAKE_MESSAGE_ON_CANCEL) && shouldFakeMessage(player)) { 20 | event.isCancelled = true 21 | } 22 | } 23 | @EventHandler(priority = EventPriority.MONITOR) 24 | fun onChat(event: AsyncPlayerChatEvent) { 25 | val player = event.player 26 | if (shouldFakeMessage(player)) { 27 | selfDecrement(player) // Decrease even the fake message is disabled 28 | if (settingsManager.getProperty(PluginSettings.CHAT_FAKE_MESSAGE_ON_CANCEL)) { 29 | event.isCancelled = false 30 | val players: MutableCollection = event.recipients 31 | players.clear() 32 | if (settingsManager.getProperty(PluginSettings.ENABLE_ALTS_CHECK) && PlayerAltController.hasAlt(player)) { 33 | val alts = PlayerAltController.getAlts(player) 34 | for (alt in alts) { 35 | val altPlayer = Bukkit.getPlayer(alt) 36 | altPlayer?.let { players.add(it) } 37 | } 38 | } 39 | players.add(player) 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/JoinUpdateNotifier.kt: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.listener 2 | 3 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords.messagesManager 4 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager 5 | import io.wdsj.asw.bukkit.permission.PermissionsEnum 6 | import io.wdsj.asw.bukkit.permission.cache.CachingPermTool 7 | import io.wdsj.asw.bukkit.setting.PluginMessages 8 | import io.wdsj.asw.bukkit.setting.PluginSettings 9 | import io.wdsj.asw.bukkit.util.PlayerUtils 10 | import io.wdsj.asw.bukkit.util.message.MessageUtils 11 | import io.wdsj.asw.common.update.Updater 12 | import org.bukkit.event.EventHandler 13 | import org.bukkit.event.Listener 14 | import org.bukkit.event.player.PlayerJoinEvent 15 | 16 | class JoinUpdateNotifier : Listener { 17 | @EventHandler 18 | fun onPlayerJoin(event: PlayerJoinEvent) { 19 | val player = event.player 20 | if (!settingsManager.getProperty(PluginSettings.CHECK_FOR_UPDATE) 21 | || !CachingPermTool.hasPermission(PermissionsEnum.UPDATE, player) 22 | || PlayerUtils.isNpc(player)) return 23 | 24 | if (Updater.hasUpdate()) { 25 | MessageUtils.sendMessage(player, 26 | messagesManager.getProperty(PluginMessages.UPDATE_AVAILABLE) 27 | .replace("%current_version%", Updater.getCurrentVersion()) 28 | .replace("%latest_version%", Updater.getLatestVersion()) 29 | ) 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/PlayerItemListener.kt: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.listener 2 | 3 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords 4 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager 5 | import io.wdsj.asw.bukkit.manage.notice.Notifier 6 | import io.wdsj.asw.bukkit.manage.punish.Punishment 7 | import io.wdsj.asw.bukkit.manage.punish.ViolationCounter 8 | import io.wdsj.asw.bukkit.permission.PermissionsEnum 9 | import io.wdsj.asw.bukkit.permission.cache.CachingPermTool 10 | import io.wdsj.asw.bukkit.proxy.bungee.BungeeSender 11 | import io.wdsj.asw.bukkit.proxy.velocity.VelocitySender 12 | import io.wdsj.asw.bukkit.setting.PluginMessages 13 | import io.wdsj.asw.bukkit.setting.PluginSettings 14 | import io.wdsj.asw.bukkit.type.ModuleType 15 | import io.wdsj.asw.bukkit.util.LoggingUtils 16 | import io.wdsj.asw.bukkit.util.TimingUtils 17 | import io.wdsj.asw.bukkit.util.Utils 18 | import io.wdsj.asw.bukkit.util.message.MessageUtils 19 | import org.bukkit.entity.Player 20 | import org.bukkit.event.Cancellable 21 | import org.bukkit.event.EventHandler 22 | import org.bukkit.event.EventPriority 23 | import org.bukkit.event.Listener 24 | import org.bukkit.event.inventory.InventoryClickEvent 25 | import org.bukkit.event.inventory.InventoryType 26 | import org.bukkit.event.player.PlayerDropItemEvent 27 | import org.bukkit.event.player.PlayerItemHeldEvent 28 | import org.bukkit.inventory.ItemStack 29 | 30 | class PlayerItemListener : Listener { 31 | @EventHandler(priority = EventPriority.LOW) 32 | fun onPlayerHeldItem(event: PlayerItemHeldEvent) { 33 | if (!AdvancedSensitiveWords.isInitialized || !settingsManager.getProperty(PluginSettings.ENABLE_PLAYER_ITEM_CHECK)) return 34 | val player = event.player 35 | if (CachingPermTool.hasPermission(PermissionsEnum.BYPASS, player)) return 36 | val item = player.inventory.getItem(event.newSlot) ?: return 37 | itemCensorLogic(player, item, event) 38 | } 39 | 40 | @EventHandler(priority = EventPriority.LOW) 41 | fun onDrop(event: PlayerDropItemEvent) { 42 | if (!AdvancedSensitiveWords.isInitialized || !settingsManager.getProperty(PluginSettings.ENABLE_PLAYER_ITEM_CHECK)) return 43 | val player = event.player 44 | if (CachingPermTool.hasPermission(PermissionsEnum.BYPASS, player)) return 45 | val item = event.itemDrop.itemStack 46 | itemCensorLogic(player, item, event) 47 | } 48 | 49 | @EventHandler(priority = EventPriority.LOW) 50 | fun onClick(event: InventoryClickEvent) { 51 | if (!AdvancedSensitiveWords.isInitialized || !settingsManager.getProperty(PluginSettings.ENABLE_PLAYER_ITEM_CHECK)) return 52 | val player = event.whoClicked as? Player ?: return 53 | if (CachingPermTool.hasPermission(PermissionsEnum.BYPASS, player)) return 54 | if (event.clickedInventory?.type != InventoryType.PLAYER) return 55 | val item = event.currentItem ?: return 56 | itemCensorLogic(player, item, event) 57 | } 58 | 59 | private fun itemCensorLogic( 60 | player: Player, 61 | item: ItemStack, 62 | event: Cancellable 63 | ) { 64 | if (item.hasItemMeta()) { 65 | val meta = item.itemMeta 66 | if (meta != null && meta.hasDisplayName()) { 67 | var originalName = meta.displayName 68 | val startTime = System.currentTimeMillis() 69 | if (settingsManager.getProperty(PluginSettings.PRE_PROCESS)) originalName = 70 | originalName.replace( 71 | Utils.preProcessRegex.toRegex(), "" 72 | ) 73 | val censoredWordList = AdvancedSensitiveWords.sensitiveWordBs.findAll(originalName) 74 | if (censoredWordList.isNotEmpty()) { 75 | Utils.messagesFilteredNum.getAndIncrement() 76 | val processedName = AdvancedSensitiveWords.sensitiveWordBs.replace(originalName) 77 | if (settingsManager.getProperty(PluginSettings.ITEM_METHOD) 78 | .equals("cancel", ignoreCase = true) 79 | ) { 80 | event.isCancelled = true 81 | } else { 82 | meta.setDisplayName(processedName) 83 | item.setItemMeta(meta) 84 | } 85 | if (settingsManager.getProperty(PluginSettings.ITEM_SEND_MESSAGE)) { 86 | MessageUtils.sendMessage( 87 | player, 88 | PluginMessages.MESSAGE_ON_ITEM 89 | ) 90 | } 91 | if (settingsManager.getProperty(PluginSettings.LOG_VIOLATION)) { 92 | LoggingUtils.logViolation( 93 | player.name + "(IP: " + Utils.getPlayerIp(player) + ")(Item)", 94 | originalName + censoredWordList 95 | ) 96 | } 97 | ViolationCounter.INSTANCE.incrementViolationCount(player) 98 | if (settingsManager.getProperty(PluginSettings.HOOK_VELOCITY)) { 99 | VelocitySender.sendNotifyMessage(player, ModuleType.ITEM, originalName, censoredWordList) 100 | } 101 | if (settingsManager.getProperty(PluginSettings.HOOK_BUNGEECORD)) { 102 | BungeeSender.sendNotifyMessage(player, ModuleType.ITEM, originalName, censoredWordList) 103 | } 104 | val endTime = System.currentTimeMillis() 105 | TimingUtils.addProcessStatistic(endTime, startTime) 106 | if (settingsManager.getProperty(PluginSettings.NOTICE_OPERATOR)) Notifier.notice( 107 | player, 108 | ModuleType.ITEM, 109 | originalName, 110 | censoredWordList 111 | ) 112 | if (settingsManager.getProperty(PluginSettings.ITEM_PUNISH)) Punishment.punish( 113 | player 114 | ) 115 | } 116 | } 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/PlayerLoginListener.kt: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.listener 2 | 3 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords.* 4 | import io.wdsj.asw.bukkit.manage.notice.Notifier 5 | import io.wdsj.asw.bukkit.manage.punish.Punishment 6 | import io.wdsj.asw.bukkit.manage.punish.ViolationCounter 7 | import io.wdsj.asw.bukkit.permission.PermissionsEnum 8 | import io.wdsj.asw.bukkit.permission.cache.CachingPermTool 9 | import io.wdsj.asw.bukkit.proxy.bungee.BungeeSender 10 | import io.wdsj.asw.bukkit.proxy.velocity.VelocitySender 11 | import io.wdsj.asw.bukkit.setting.PluginMessages 12 | import io.wdsj.asw.bukkit.setting.PluginSettings 13 | import io.wdsj.asw.bukkit.type.ModuleType 14 | import io.wdsj.asw.bukkit.util.LoggingUtils 15 | import io.wdsj.asw.bukkit.util.PlayerUtils 16 | import io.wdsj.asw.bukkit.util.TimingUtils 17 | import io.wdsj.asw.bukkit.util.Utils 18 | import io.wdsj.asw.bukkit.util.message.MessageUtils 19 | import org.bukkit.Bukkit 20 | import org.bukkit.event.EventHandler 21 | import org.bukkit.event.EventPriority 22 | import org.bukkit.event.Listener 23 | import org.bukkit.event.player.PlayerLoginEvent 24 | import org.geysermc.floodgate.api.FloodgateApi 25 | 26 | class PlayerLoginListener : Listener { 27 | @EventHandler(priority = EventPriority.LOWEST) 28 | fun onLogin(event: PlayerLoginEvent) { 29 | if (!isInitialized || !settingsManager.getProperty(PluginSettings.ENABLE_PLAYER_NAME_CHECK)) return 30 | val player = event.player 31 | if (CachingPermTool.hasPermission(PermissionsEnum.BYPASS, player)) return 32 | if (PlayerUtils.isNpc(player) && settingsManager.getProperty(PluginSettings.NAME_IGNORE_NPC)) return 33 | if (Bukkit.getPluginManager() 34 | .getPlugin("floodgate") != null && settingsManager.getProperty( 35 | PluginSettings.NAME_IGNORE_BEDROCK 36 | ) 37 | ) { 38 | if (FloodgateApi.getInstance().isFloodgatePlayer(player.uniqueId)) return 39 | } 40 | val playerName = player.name 41 | val startTime = System.currentTimeMillis() 42 | val censoredWordList = sensitiveWordBs.findAll(playerName) 43 | if (censoredWordList.isNotEmpty()) { 44 | val processedPlayerName = sensitiveWordBs.replace(playerName) 45 | val playerIp = event.address.hostAddress 46 | Utils.messagesFilteredNum.getAndIncrement() 47 | if (settingsManager.getProperty(PluginSettings.NAME_METHOD) 48 | .equals("replace", ignoreCase = true) 49 | ) { 50 | player.setDisplayName(processedPlayerName) 51 | player.setPlayerListName(processedPlayerName) 52 | if (settingsManager.getProperty(PluginSettings.NAME_SEND_MESSAGE)) { 53 | getScheduler().runTaskLater({ 54 | MessageUtils.sendMessage( 55 | player, 56 | PluginMessages.MESSAGE_ON_NAME 57 | ) 58 | }, 20L * 3L) 59 | } 60 | } else { 61 | event.disallow( 62 | PlayerLoginEvent.Result.KICK_OTHER, 63 | MessageUtils.retrieveMessage(PluginMessages.MESSAGE_ON_NAME) 64 | ) 65 | } 66 | if (settingsManager.getProperty(PluginSettings.LOG_VIOLATION)) { 67 | LoggingUtils.logViolation(player.name + "(IP: " + playerIp + ")(Name)", playerName + censoredWordList) 68 | } 69 | ViolationCounter.INSTANCE.incrementViolationCount(player) 70 | if (settingsManager.getProperty(PluginSettings.HOOK_VELOCITY)) { 71 | VelocitySender.sendNotifyMessage(player, ModuleType.NAME, playerName, censoredWordList) 72 | } 73 | if (settingsManager.getProperty(PluginSettings.HOOK_BUNGEECORD)) { 74 | BungeeSender.sendNotifyMessage(player, ModuleType.NAME, playerName, censoredWordList) 75 | } 76 | val endTime = System.currentTimeMillis() 77 | TimingUtils.addProcessStatistic(endTime, startTime) 78 | if (settingsManager.getProperty(PluginSettings.NOTICE_OPERATOR)) Notifier.notice( 79 | player, 80 | ModuleType.NAME, 81 | playerName, 82 | censoredWordList 83 | ) 84 | if (settingsManager.getProperty(PluginSettings.NAME_PUNISH)) Punishment.punish(player) 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/QuitDataCleaner.kt: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.listener 2 | 3 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager 4 | import io.wdsj.asw.bukkit.manage.punish.PlayerShadowController 5 | import io.wdsj.asw.bukkit.setting.PluginSettings 6 | import io.wdsj.asw.bukkit.util.context.ChatContext 7 | import io.wdsj.asw.bukkit.util.context.SignContext 8 | import org.bukkit.entity.Player 9 | import org.bukkit.event.EventHandler 10 | import org.bukkit.event.EventPriority 11 | import org.bukkit.event.Listener 12 | import org.bukkit.event.player.PlayerKickEvent 13 | import org.bukkit.event.player.PlayerQuitEvent 14 | 15 | class QuitDataCleaner : Listener { 16 | @EventHandler 17 | fun onQuit(event: PlayerQuitEvent) { 18 | if (!settingsManager.getProperty(PluginSettings.CLEAN_PLAYER_DATA_CACHE)) return 19 | val player = event.player 20 | doCleanTask(player) 21 | } 22 | 23 | @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) 24 | fun onKick(event: PlayerKickEvent) { 25 | if (!settingsManager.getProperty(PluginSettings.CLEAN_PLAYER_DATA_CACHE)) return 26 | val player = event.player 27 | doCleanTask(player) 28 | } 29 | 30 | private fun doCleanTask(player: Player) { 31 | ChatContext.clearPlayerContext(player) 32 | SignContext.clearPlayerContext(player) 33 | PlayerShadowController.unshadowPlayer(player) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/ShadowListener.kt: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.listener 2 | 3 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords 4 | import io.wdsj.asw.bukkit.manage.punish.PlayerAltController 5 | import io.wdsj.asw.bukkit.manage.punish.PlayerShadowController 6 | import io.wdsj.asw.bukkit.setting.PluginSettings 7 | import org.bukkit.Bukkit 8 | import org.bukkit.entity.Player 9 | import org.bukkit.event.EventHandler 10 | import org.bukkit.event.EventPriority 11 | import org.bukkit.event.Listener 12 | import org.bukkit.event.player.AsyncPlayerChatEvent 13 | 14 | class ShadowListener : Listener { // TODO: Paper event handler 15 | @EventHandler(priority = EventPriority.MONITOR) 16 | fun onChat(event: AsyncPlayerChatEvent) { 17 | val player = event.player 18 | if (PlayerShadowController.isShadowed(player)) { 19 | val recipients: MutableCollection = event.recipients 20 | recipients.clear() 21 | if (AdvancedSensitiveWords.settingsManager.getProperty(PluginSettings.ENABLE_ALTS_CHECK) && PlayerAltController.hasAlt(player)) { 22 | val alts = PlayerAltController.getAlts(player) 23 | for (alt in alts) { 24 | val altPlayer = Bukkit.getPlayer(alt) 25 | altPlayer?.let { recipients.add(it) } 26 | } 27 | } 28 | recipients.add(player) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/abstraction/AbstractFakeMessageExecutor.kt: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.listener.abstraction 2 | 3 | import org.bukkit.entity.Player 4 | import java.util.UUID 5 | import java.util.concurrent.ConcurrentHashMap 6 | 7 | abstract class AbstractFakeMessageExecutor { 8 | protected fun shouldFakeMessage(player: Player): Boolean { 9 | return FAKE_MESSAGE_NUM.getOrPut(player.uniqueId) { 0 } > 0 10 | } 11 | 12 | companion object { 13 | @JvmStatic 14 | private val FAKE_MESSAGE_NUM = ConcurrentHashMap() 15 | @JvmStatic 16 | fun selfDecrement(player: Player) { 17 | val uuid = player.uniqueId 18 | val currentNum = FAKE_MESSAGE_NUM.getOrPut(uuid) { 0 } 19 | if (currentNum > 0) { 20 | FAKE_MESSAGE_NUM[uuid] = currentNum - 1 21 | } 22 | } 23 | 24 | @JvmStatic 25 | fun selfIncrement(player: Player) { 26 | val uuid = player.uniqueId 27 | FAKE_MESSAGE_NUM[uuid] = FAKE_MESSAGE_NUM.getOrPut(uuid) { 0 } + 1 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /bukkit/src/main/kotlin/io/wdsj/asw/bukkit/listener/paper/PaperFakeMessageExecutor.kt: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.listener.paper 2 | 3 | import io.papermc.paper.event.player.AsyncChatEvent 4 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager 5 | import io.wdsj.asw.bukkit.annotation.PaperEventHandler 6 | import io.wdsj.asw.bukkit.listener.abstraction.AbstractFakeMessageExecutor 7 | import io.wdsj.asw.bukkit.manage.punish.PlayerAltController 8 | import io.wdsj.asw.bukkit.setting.PluginSettings 9 | import net.kyori.adventure.audience.Audience 10 | import org.bukkit.Bukkit 11 | import org.bukkit.event.EventHandler 12 | import org.bukkit.event.EventPriority 13 | import org.bukkit.event.Listener 14 | import org.bukkit.event.player.AsyncPlayerChatEvent 15 | 16 | @PaperEventHandler 17 | class PaperFakeMessageExecutor : Listener, AbstractFakeMessageExecutor() { 18 | /* 19 | @EventHandler(priority = EventPriority.LOWEST) 20 | fun onChatFirst(event: AsyncChatEvent) { 21 | if (settingsManager.getProperty(PluginSettings.CHAT_FAKE_MESSAGE_ON_CANCEL) && shouldFakeMessage(event.player)) { 22 | event.isCancelled = true 23 | } 24 | } 25 | */ // In Paper, the event order is AsyncPlayerChatEvent -> AsyncChatEvent and results are inherited from AsyncPlayerChatEvent 26 | 27 | @Suppress("DEPRECATION") 28 | @EventHandler(priority = EventPriority.LOWEST) 29 | fun onLegacyChatFirst(event: AsyncPlayerChatEvent) { 30 | if (settingsManager.getProperty(PluginSettings.CHAT_FAKE_MESSAGE_ON_CANCEL) && shouldFakeMessage(event.player)) { 31 | event.isCancelled = true 32 | } 33 | } 34 | 35 | @EventHandler(priority = EventPriority.MONITOR) 36 | fun onChat(event: AsyncChatEvent) { 37 | val player = event.player 38 | if (shouldFakeMessage(player)) { 39 | selfDecrement(player) // Decrease even the fake message is disabled 40 | if (settingsManager.getProperty(PluginSettings.CHAT_FAKE_MESSAGE_ON_CANCEL)) { 41 | event.isCancelled = false 42 | val players: MutableSet = event.viewers() 43 | players.clear() 44 | if (settingsManager.getProperty(PluginSettings.ENABLE_ALTS_CHECK) && PlayerAltController.hasAlt(player)) { 45 | val alts = PlayerAltController.getAlts(player) 46 | for (alt in alts) { 47 | val altPlayer = Bukkit.getPlayer(alt) 48 | altPlayer?.let { players.add(it) } 49 | } 50 | } 51 | players.add(player) 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /bukkit/src/main/kotlin/io/wdsj/asw/bukkit/type/ModuleType.kt: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.type 2 | 3 | /** 4 | * Types for different detection modules. 5 | */ 6 | enum class ModuleType { 7 | CHAT, 8 | CHAT_AI, 9 | SIGN, 10 | ANVIL, 11 | BOOK, 12 | NAME, 13 | ITEM, 14 | VOICE 15 | } -------------------------------------------------------------------------------- /bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/LoggingUtils.kt: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.util 2 | 3 | import com.github.houbb.heaven.util.io.FileUtil 4 | import com.google.common.util.concurrent.ThreadFactoryBuilder 5 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords 6 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords.LOGGER 7 | import java.io.File 8 | import java.io.FileOutputStream 9 | import java.io.IOException 10 | import java.io.OutputStreamWriter 11 | import java.nio.charset.StandardCharsets 12 | import java.time.LocalDateTime 13 | import java.time.format.DateTimeFormatter 14 | import java.util.concurrent.Executors 15 | 16 | object LoggingUtils { 17 | private val dateFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd-HH:mm:ss") 18 | private val loggingThreadPool = Executors.newSingleThreadExecutor( 19 | ThreadFactoryBuilder() 20 | .setNameFormat("ASW Logging Thread") 21 | .setDaemon(true) 22 | .build() 23 | ) 24 | @JvmStatic 25 | fun logViolation(playerName: String, violationReason: String) { 26 | loggingThreadPool.submit { 27 | val formattedDate = LocalDateTime.now().format(dateFormatter) 28 | val logMessage = "[$formattedDate] $playerName $violationReason" 29 | val logFile = File(AdvancedSensitiveWords.getInstance().dataFolder, "violations.log") 30 | if (!logFile.exists()) { 31 | try { 32 | logFile.createNewFile() 33 | } catch (e: IOException) { 34 | LOGGER.warning("Failed to create violations.log file: " + e.message) 35 | return@submit 36 | } 37 | } 38 | try { 39 | OutputStreamWriter(FileOutputStream(logFile, true), StandardCharsets.UTF_8).use { writer -> 40 | writer.write(logMessage + System.lineSeparator()) 41 | } 42 | } catch (e: IOException) { 43 | LOGGER.warning("Failed to write to violations.log file: " + e.message) 44 | } 45 | } 46 | } 47 | 48 | @JvmStatic 49 | fun purgeLog() { 50 | val logFile = File(AdvancedSensitiveWords.getInstance().dataFolder, "violations.log") 51 | if (!logFile.exists()) return 52 | FileUtil.deleteFile(logFile) 53 | LOGGER.info("Successfully purged violations") 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/PlayerUtils.kt: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.util 2 | 3 | import org.bukkit.entity.Player 4 | 5 | object PlayerUtils { 6 | private val isLeavesServer = 7 | Utils.isAnyClassLoaded("top.leavesmc.leaves.LeavesConfig", "org.leavesmc.leaves.LeavesConfig") 8 | 9 | fun isNpc(player: Player): Boolean { 10 | return if (isLeavesServer) { 11 | player.address == null || player.hasMetadata("NPC") 12 | } else { 13 | player.hasMetadata("NPC") 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/SchedulingUtils.kt: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.util 2 | 3 | import com.github.Anon8281.universalScheduler.UniversalScheduler 4 | import com.github.Anon8281.universalScheduler.scheduling.tasks.MyScheduledTask 5 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords 6 | import org.bukkit.Bukkit 7 | import org.bukkit.Location 8 | import org.bukkit.entity.Entity 9 | import org.bukkit.event.Event 10 | import java.util.concurrent.Callable 11 | 12 | 13 | object SchedulingUtils { 14 | private val isFolia = UniversalScheduler.isFolia 15 | 16 | @JvmStatic 17 | fun runSyncIfFolia(runnable: Runnable) { 18 | if (isFolia) { 19 | AdvancedSensitiveWords.getScheduler().runTask(runnable) 20 | } else { 21 | runnable.run() 22 | } 23 | } 24 | 25 | @JvmStatic 26 | fun runSyncAtEntityIfFolia(entity: Entity, runnable: Runnable) { 27 | if (isFolia) { 28 | AdvancedSensitiveWords.getScheduler().runTask(entity, runnable) 29 | } else { 30 | runnable.run() 31 | } 32 | } 33 | 34 | @JvmStatic 35 | fun runSyncAtLocationIfFolia(location: Location, runnable: Runnable) { 36 | if (isFolia) { 37 | AdvancedSensitiveWords.getScheduler().runTask(location, runnable) 38 | } else { 39 | runnable.run() 40 | } 41 | } 42 | 43 | @JvmStatic 44 | fun runSyncIfEventAsync(event: Event, runnable: Runnable) { 45 | if (event.isAsynchronous) { 46 | AdvancedSensitiveWords.getScheduler().runTask(runnable) 47 | } else { 48 | runnable.run() 49 | } 50 | } 51 | 52 | @JvmStatic 53 | fun runSyncIfNotOnMainThread(runnable: Runnable) { 54 | if (Bukkit.isPrimaryThread()) { 55 | runnable.run() 56 | } else { 57 | AdvancedSensitiveWords.getScheduler().runTask(runnable) 58 | } 59 | } 60 | 61 | @JvmStatic 62 | fun cancelTaskSafely(task: MyScheduledTask?) { 63 | task?.cancel() 64 | } 65 | 66 | @JvmStatic 67 | fun callSyncMethod(callable: Callable): T { 68 | return AdvancedSensitiveWords.getScheduler().callSyncMethod(callable).get() 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/TimingUtils.kt: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.util 2 | 3 | import java.util.* 4 | 5 | 6 | object TimingUtils { 7 | private val processStatistic: MutableList = Collections.synchronizedList(ArrayList()) 8 | @JvmStatic 9 | val jvmVendor: String = System.getProperties().getProperty("java.vendor") ?: "Unknown" 10 | @JvmStatic 11 | val jvmVersion: String = System.getProperties().getProperty("java.version") ?: "Unknown" 12 | 13 | @JvmStatic 14 | fun addProcessStatistic(endTime: Long, startTime: Long) { 15 | val processTime = endTime - startTime 16 | while (processStatistic.size >= 20) { 17 | processStatistic.removeAt(0) 18 | } 19 | processStatistic.add(processTime) 20 | } 21 | 22 | @JvmStatic 23 | val processAverage: Long 24 | get() { 25 | var sum = 0L 26 | for (l in processStatistic) { 27 | sum += l 28 | } 29 | return if (processStatistic.isNotEmpty()) sum / processStatistic.size else 0L 30 | } 31 | 32 | @JvmStatic 33 | fun resetStatistics() { 34 | processStatistic.clear() 35 | Utils.messagesFilteredNum.set(0L) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/Utils.kt: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.util 2 | 3 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords 4 | import io.wdsj.asw.bukkit.setting.PluginSettings 5 | import org.bukkit.Bukkit 6 | import org.bukkit.entity.Player 7 | import java.util.* 8 | import java.util.concurrent.atomic.AtomicLong 9 | 10 | object Utils { 11 | @JvmField 12 | val messagesFilteredNum: AtomicLong = AtomicLong(0) 13 | 14 | @JvmStatic 15 | fun getPlayerIp(player: Player): String { 16 | val address = player.address 17 | if (address != null && address.address != null) return address.address.hostAddress 18 | throw IllegalStateException("Player address is null") 19 | } 20 | 21 | @JvmStatic 22 | fun isClassLoaded(className: String): Boolean { 23 | try { 24 | Class.forName(className) 25 | return true 26 | } catch (ignored: ClassNotFoundException) { 27 | return false 28 | } 29 | } 30 | 31 | fun isAnyClassLoaded(vararg classNames: String): Boolean { 32 | for (className in classNames) { 33 | if (isClassLoaded(className)) return true 34 | } 35 | return false 36 | } 37 | 38 | @JvmStatic 39 | fun isClassExists(className: String): Boolean { 40 | return try { 41 | val url = className.replace(".", "/") + ".class" 42 | return Thread.currentThread().contextClassLoader.getResource(url) != null 43 | } catch (ignored: Throwable) { 44 | false 45 | } 46 | } 47 | 48 | @JvmStatic 49 | fun canUsePE(): Boolean { 50 | return Bukkit.getPluginManager().getPlugin("packetevents") != null 51 | } 52 | fun isCommand(command: String): Boolean { 53 | return command.startsWith("/") 54 | } 55 | 56 | fun getSplitCommandArgs(command: String): String { 57 | val splitCommand = command.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() 58 | if (splitCommand.size <= 1) return "" 59 | return java.lang.String.join(" ", *Arrays.copyOfRange(splitCommand, 1, splitCommand.size)) 60 | } 61 | 62 | fun getSplitCommandHeaders(command: String): String { 63 | val splitCommand = command.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() 64 | if (splitCommand.isEmpty()) return "" 65 | return splitCommand[0] 66 | } 67 | 68 | val preProcessRegex: String 69 | get() = AdvancedSensitiveWords.settingsManager.getProperty(PluginSettings.PRE_PROCESS_REGEX) 70 | 71 | 72 | fun isCommandAndWhiteListed(command: String): Boolean { 73 | if (!command.startsWith("/")) return false 74 | val whitelist = AdvancedSensitiveWords.settingsManager.getProperty(PluginSettings.CHAT_COMMAND_WHITE_LIST) 75 | val splitCommand = command.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() 76 | for (s in whitelist) { 77 | if (splitCommand[0].equals(s, ignoreCase = true)) { 78 | return !AdvancedSensitiveWords.settingsManager.getProperty(PluginSettings.CHAT_INVERT_WHITELIST) 79 | } 80 | } 81 | return AdvancedSensitiveWords.settingsManager.getProperty(PluginSettings.CHAT_INVERT_WHITELIST) 82 | } 83 | 84 | @JvmStatic 85 | val minecraftVersion: String 86 | get() = Bukkit.getBukkitVersion().split("-".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0] 87 | 88 | fun isNotCommand(command: String): Boolean { 89 | return !command.startsWith("/") 90 | } 91 | 92 | /** 93 | * Checks if the given object is null, and returns the fallback value if it is. 94 | * @param obj The object to check 95 | * @param fallback The fallback value to return if the object is null 96 | */ 97 | fun checkNotNullWithFallback(obj: T?, fallback: T): T { 98 | if (obj == null) { 99 | return fallback 100 | } 101 | return obj 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/VirtualThreadUtils.kt: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.util 2 | 3 | import java.lang.reflect.Method 4 | import java.util.concurrent.ExecutorService 5 | import java.util.concurrent.Executors 6 | import java.util.concurrent.ThreadFactory 7 | 8 | /** 9 | * Utility class for virtual threads which are introduced in Java 21 10 | */ 11 | object VirtualThreadUtils { 12 | private var methodVirtualThreadFactory: Method? 13 | private var methodThreadOfVirtual: Method? 14 | 15 | private var methodVirtualThreadPerTaskExecutor: Method? 16 | 17 | init { 18 | try { 19 | methodThreadOfVirtual = Thread::class.java.getMethod("ofVirtual") 20 | val threadBuilder = Class.forName("java.lang.Thread\$Builder") 21 | methodVirtualThreadFactory= threadBuilder.getMethod("factory") 22 | methodThreadOfVirtual?.isAccessible = true 23 | methodVirtualThreadFactory?.isAccessible = true 24 | } catch (e: Exception) { 25 | methodThreadOfVirtual = null 26 | methodVirtualThreadFactory = null 27 | } 28 | try { 29 | methodVirtualThreadPerTaskExecutor = Executors::class.java.getMethod("newVirtualThreadPerTaskExecutor") 30 | methodVirtualThreadPerTaskExecutor?.isAccessible = true 31 | } catch (e: Exception) { 32 | methodVirtualThreadPerTaskExecutor = null 33 | } 34 | } 35 | 36 | @JvmStatic 37 | fun newVirtualThreadFactory(): ThreadFactory? { 38 | return invokeOfVirtualFactory() 39 | } 40 | 41 | @JvmStatic 42 | fun newVirtualThreadPerTaskExecutor(): ExecutorService? { 43 | return invokeNewVirtualThreadPerTaskExecutor() 44 | } 45 | 46 | @JvmStatic 47 | fun newVirtualThreadFactoryOrProvided(threadFactory: ThreadFactory): ThreadFactory { 48 | return invokeOfVirtualFactory() ?: threadFactory 49 | } 50 | 51 | @JvmStatic 52 | fun newVirtualThreadFactoryOrDefault(): ThreadFactory { 53 | return invokeOfVirtualFactory() ?: Executors.defaultThreadFactory() 54 | } 55 | 56 | @JvmStatic 57 | fun newVirtualThreadPerTaskExecutorOrProvided(executorService: ExecutorService): ExecutorService { 58 | return invokeNewVirtualThreadPerTaskExecutor() ?: executorService 59 | } 60 | 61 | private fun invokeNewVirtualThreadPerTaskExecutor(): ExecutorService? { 62 | return try { 63 | methodVirtualThreadPerTaskExecutor?.invoke(null) as ExecutorService 64 | } catch (e: Exception) { 65 | null 66 | } 67 | } 68 | 69 | private fun invokeOfVirtualFactory(): ThreadFactory? { 70 | return try { 71 | methodVirtualThreadFactory?.invoke(methodThreadOfVirtual?.invoke(null)) as ThreadFactory 72 | } catch (e: Exception) { 73 | null 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/cache/BookCache.kt: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.util.cache 2 | 3 | import com.github.benmanes.caffeine.cache.Cache 4 | import com.github.benmanes.caffeine.cache.Caffeine 5 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager 6 | import io.wdsj.asw.bukkit.setting.PluginSettings 7 | import java.util.concurrent.TimeUnit 8 | 9 | object BookCache { 10 | private lateinit var cache: Cache 11 | fun isBookCached(content: String): Boolean { 12 | return cache.getIfPresent(content) != null 13 | } 14 | 15 | fun addToBookCache(content: String, processedContent: String, sensitiveWordList: List) { 16 | cache.put(content, BookCacheEntry(processedContent, sensitiveWordList)) 17 | } 18 | 19 | /** 20 | * Retrieves the processed book content from the cache. 21 | * Warning: This method is only safely called after checking if the book is cached. 22 | * @param content The content of the book. 23 | * @return The processed book content. 24 | */ 25 | @Throws(NoSuchElementException::class) 26 | fun getCachedProcessedBookContent(content: String): String { 27 | val entry = cache.getIfPresent(content) ?: throw NoSuchElementException("Book not found in cache") 28 | return entry.processedContent 29 | } 30 | 31 | /** 32 | * Retrieves the list of sensitive words from the cache. 33 | * Warning: This method is only safely called after checking if the book is cached. 34 | * @param content The content of the book. 35 | * @return The list of sensitive words. 36 | */ 37 | @Throws(NoSuchElementException::class) 38 | fun getCachedBookSensitiveWordList(content: String): List { 39 | val entry = cache.getIfPresent(content) ?: throw NoSuchElementException("Book not found in cache") 40 | return entry.sensitiveWordList 41 | } 42 | 43 | @JvmStatic 44 | fun invalidateAll() { 45 | cache.invalidateAll() 46 | } 47 | 48 | @JvmStatic 49 | fun initialize() { 50 | cache = Caffeine.newBuilder() 51 | .maximumSize( 52 | settingsManager.getProperty(PluginSettings.BOOK_MAXIMUM_CACHE_SIZE).toLong() 53 | ) 54 | .expireAfterWrite( 55 | settingsManager.getProperty(PluginSettings.BOOK_CACHE_EXPIRE_TIME).toLong(), 56 | TimeUnit.MINUTES 57 | ) 58 | .build() 59 | } 60 | 61 | 62 | /** 63 | * Inner class to encapsulate the processed book content and its list of sensitive words. 64 | */ 65 | private data class BookCacheEntry(val processedContent: String, val sensitiveWordList: List) 66 | } -------------------------------------------------------------------------------- /bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/context/ChatContext.kt: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.util.context 2 | 3 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager 4 | import io.wdsj.asw.bukkit.setting.PluginSettings 5 | import io.wdsj.asw.common.datatype.TimedString 6 | import org.bukkit.entity.Player 7 | import java.util.* 8 | import java.util.concurrent.ConcurrentHashMap 9 | import java.util.concurrent.ConcurrentLinkedDeque 10 | 11 | object ChatContext { 12 | private val chatHistory = ConcurrentHashMap>() 13 | 14 | /** 15 | * Add player message to history 16 | */ 17 | fun addMessage(player: Player, message: String) { 18 | val uuid = player.uniqueId 19 | val history = chatHistory.getOrPut(uuid) { ConcurrentLinkedDeque() } 20 | while (history.size >= settingsManager.getProperty(PluginSettings.CHAT_CONTEXT_MAX_SIZE)) { 21 | history.pollFirst() 22 | } 23 | message.trim().takeIf { it.isNotEmpty() } ?.let { 24 | history.offerLast(TimedString.of(it)) 25 | } 26 | } 27 | 28 | fun getHistory(player: Player): Deque { 29 | val uuid = player.uniqueId 30 | val tsHistory = chatHistory.getOrPut(uuid) { ConcurrentLinkedDeque() } 31 | if (tsHistory.isEmpty()) return ConcurrentLinkedDeque() 32 | tsHistory.removeIf { 33 | (System.currentTimeMillis() - it.time) / 1000 > settingsManager.getProperty( 34 | PluginSettings.CHAT_CONTEXT_TIME_LIMIT 35 | ) 36 | } 37 | return tsHistory.mapTo(ConcurrentLinkedDeque()) { it.string } 38 | } 39 | 40 | fun clearPlayerContext(player: Player) { 41 | val uuid = player.uniqueId 42 | chatHistory.remove(uuid) 43 | } 44 | 45 | fun pollPlayerContext(player: Player) { 46 | val uuid = player.uniqueId 47 | val history = chatHistory[uuid] 48 | history?.pollLast() 49 | } 50 | 51 | @JvmStatic 52 | fun forceClearContext() { 53 | chatHistory.clear() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/context/SignContext.kt: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.util.context 2 | 3 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords.settingsManager 4 | import io.wdsj.asw.bukkit.setting.PluginSettings 5 | import io.wdsj.asw.common.datatype.TimedString 6 | import org.bukkit.entity.Player 7 | import java.util.* 8 | import java.util.concurrent.ConcurrentHashMap 9 | import java.util.concurrent.ConcurrentLinkedDeque 10 | 11 | object SignContext { 12 | private val signEditHistory = ConcurrentHashMap>() 13 | 14 | /** 15 | * Add player message to history 16 | */ 17 | fun addMessage(player: Player, message: String) { 18 | val uuid = player.uniqueId 19 | val history = signEditHistory.getOrPut(uuid) { ConcurrentLinkedDeque() } 20 | while (history.size >= settingsManager.getProperty(PluginSettings.SIGN_CONTEXT_MAX_SIZE)) { 21 | history.pollFirst() 22 | } 23 | message.trim().takeIf { it.isNotEmpty() } ?.let { 24 | history.offerLast(TimedString.of(it)) 25 | } 26 | } 27 | 28 | fun getHistory(player: Player): Deque { 29 | val uuid = player.uniqueId 30 | val tsHistory = signEditHistory.getOrPut(uuid) { ConcurrentLinkedDeque() } 31 | if (tsHistory.isEmpty()) return ConcurrentLinkedDeque() 32 | tsHistory.removeIf { 33 | (System.currentTimeMillis() - it.time) / 1000 > settingsManager.getProperty( 34 | PluginSettings.SIGN_CONTEXT_TIME_LIMIT 35 | ) 36 | } 37 | return tsHistory.mapTo(ConcurrentLinkedDeque()) { it.string } 38 | } 39 | 40 | fun clearPlayerContext(player: Player) { 41 | val uuid = player.uniqueId 42 | signEditHistory.remove(uuid) 43 | } 44 | 45 | @JvmStatic 46 | fun pollPlayerContext(player: Player) { 47 | val uuid = player.uniqueId 48 | val history = signEditHistory[uuid] 49 | history?.pollLast() 50 | } 51 | 52 | @JvmStatic 53 | fun forceClearContext() { 54 | signEditHistory.clear() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /bukkit/src/main/kotlin/io/wdsj/asw/bukkit/util/message/MessageUtils.kt: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bukkit.util.message 2 | 3 | import ch.jalu.configme.properties.Property 4 | import io.wdsj.asw.bukkit.AdvancedSensitiveWords 5 | import org.bukkit.ChatColor 6 | import org.bukkit.command.CommandSender 7 | 8 | object MessageUtils { 9 | private const val AMPERSAND_CHAR: Char = '&' 10 | 11 | @JvmStatic 12 | fun retrieveMessage(property: Property): String { 13 | return ChatColor.translateAlternateColorCodes( 14 | AMPERSAND_CHAR, 15 | AdvancedSensitiveWords.messagesManager.getProperty(property) 16 | ) 17 | } 18 | 19 | @JvmStatic 20 | fun sendMessage(sender: CommandSender, property: Property) { 21 | val msg = retrieveMessage(property) 22 | if (msg.isNotEmpty()) { 23 | sender.sendMessage(msg) 24 | } 25 | } 26 | 27 | @JvmStatic 28 | fun sendMessage(sender: CommandSender, message: String) { 29 | if (message.isNotEmpty()) { 30 | sender.sendMessage(ChatColor.translateAlternateColorCodes(AMPERSAND_CHAR, message)) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /bukkit/src/main/resources/messages_en.yml: -------------------------------------------------------------------------------- 1 | #AdvancedSensitiveWords-Pro Messages configuration 2 | #Chat related messages 3 | Chat: 4 | #Messages on Chat 5 | messageOnChat: '&cDo not send sensitive words in the chat.' 6 | #Sign check related messages 7 | Sign: 8 | #Messages on Sign editing 9 | messageOnSign: '&cDo not write sensitive vocabulary in the sign.' 10 | #Anvil check related messages 11 | Anvil: 12 | #Messages on Anvil renaming 13 | messageOnAnvilRename: '&cDo not write sensitive words in the Anvil.' 14 | #Book check related messages 15 | Book: 16 | #Messages on Book editing 17 | messageOnBook: '&cDo not write sensitive words in the book.' 18 | #Player name check related messages 19 | Name: 20 | #Messages on Player name 21 | messageOnName: '&cYour username contains sensitive words, please change your username or contact an administrator.' 22 | #Player item check related messages 23 | Item: 24 | #Messages on Player item 25 | messageOnItem: '&cThe item you held contains sensitive words.' 26 | #Player voice chat related messages 27 | Voice: 28 | #Messages on Voice chat 29 | messageOnVoice: '&cDo not send inappropriate voice.' 30 | #Plugin related messages 31 | Plugin: 32 | #Reload command 33 | messageOnCommandReload: '&aAdvancedSensitiveWords has been reloaded.' 34 | #Violation reset message 35 | messageOnViolationReset: '&a&lViolation for all players has been reset.' 36 | #Help command 37 | messageOnCommandHelp: |- 38 | &bAdvancedSensitiveWords&r---&b Help Menu 39 | &7/asw reload: &areload filter dictionary and plugin configuration 40 | &7/asw reloadconfig: &areload plugin config 41 | &7/asw add : &aAdd word to filter dictionary 42 | &7/asw remove : &aremove word from filter dictionary 43 | &7/asw addallow : &aAdd word to allow list 44 | &7/asw removeallow : &aremove word from allow list 45 | &7/asw status: &ashow plug-in status menu 46 | &7/asw test : &arun sensitive word test 47 | &7/asw help: &ashow help information 48 | &7/asw info : &ashow player information 49 | &7/asw punish [method]: &aPunish player with the specified method, if not specified, the config file will be used 50 | #Status command 51 | messageOnCommandStatus: |- 52 | &bAdvancedSensitiveWords&r---&b Plugin Status(%version%)(MC %mc_version%) 53 | &7 System Information: &b%platform% %bit% (Java %java_version% -- %java_vendor%) 54 | &7 Initialization: %init% 55 | &7 Current mode: %mode% 56 | &7 Number of filtered messages: %num% 57 | &7 Average time spent on nearly 20 processes: %ms% 58 | #Test command 59 | commandTest: 60 | #Return message when the test contains sensitive words 61 | testResultTrue: |- 62 | &7 Original message: &c%original_msg% 63 | &7 After filtering message: &a%processed_msg% 64 | &7 List of sensitive words: &b%censored_list% 65 | #No sensitive words in the test 66 | testResultPass: '&aPending message has no sensitive words!' 67 | #Plugin has not been initialized 68 | testNotInit: '&cPlugin has not been initialized' 69 | #Punish command 70 | commandPunish: 71 | #Parse error 72 | parseError: '&cFailed to parse the punish method, please check the syntax.' 73 | #Successfully punished 74 | success: '&aSuccessfully punished player %player%!' 75 | #Add command 76 | commandAdd: 77 | #Add success 78 | success: '&aSuccessfully added to filter dictionary!' 79 | #Remove command 80 | commandRemove: 81 | #Remove success 82 | success: '&aSuccessfully removed from filter dictionary!' 83 | #No permission 84 | noPermission: '&cYou do not have permission to execute the command.' 85 | #Unknown command 86 | unknownCommand: '&cUnknown command, please use &7/asw help.' 87 | #Not enough parameters 88 | argsNotEnough: '&cInsufficient parameters, please use &7/asw help' 89 | #No such player 90 | noSuchPlayer: '&cSpecified player does not exist.' 91 | #Admin notice 92 | noticeOperator: '&f[&bASW&7Notify&f]&7Player &c%player% &7failed anti-swear check(%type%)(Message: %message%)List: %censored_list%' 93 | #Admin notice(Proxy) 94 | noticeOperatorProxy: '&f[&bASW&7Notify&f]&7Player &c%player% (Server: %server_name%) &7failed anti-swear check(%type%)(Message: %message%)List: %censored_list%' 95 | #Update is available 96 | updateAvailable: '&f[&bASW&7Notify&f]&7A new version is available, please update. (Latest: %latest_version%, Current: %current_version%)' 97 | #Player info 98 | messageOnCommandInfo: |- 99 | &bAdvancedSensitiveWords&r---&bPlayerInfo 100 | &7 Name: &b%player% 101 | &7 Violations: &b%violation% 102 | #Reset violations 103 | messageOnCommandReset: '&aViolation for player %player% has been reset.' -------------------------------------------------------------------------------- /bukkit/src/main/resources/messages_zhcn.yml: -------------------------------------------------------------------------------- 1 | # AdvancedSensitiveWords-Pro 插件消息配置 2 | # 聊天检测消息 3 | Chat: 4 | # 玩家发送敏感消息时候的提示 5 | messageOnChat: '&c请勿在聊天中发送敏感词汇.' 6 | # 告示牌检测消息 7 | Sign: 8 | # 玩家写入敏感消息时的提示 9 | messageOnSign: '&c请勿在告示牌中写入敏感词汇.' 10 | # 铁砧重命名检测消息 11 | Anvil: 12 | # 玩家在铁砧重命名时写入敏感消息的提示 13 | messageOnAnvilRename: '&c请勿在铁砧中写入敏感词汇.' 14 | # 书检测消息 15 | Book: 16 | # 玩家在书中写入敏感消息的提示 17 | messageOnBook: '&c请勿在书中写入敏感词汇.' 18 | # 玩家名检测消息 19 | Name: 20 | # 玩家名包含敏感词时的消息 21 | messageOnName: '&c您的用户名包含敏感词,请修改您的用户名或联系管理员.' 22 | # 玩家物品检测消息 23 | Item: 24 | # 玩家物品包含敏感词时的消息 25 | messageOnItem: '&c您的物品包含敏感词.' 26 | # 玩家语音检测消息 27 | Voice: 28 | # 玩家发送违规语音消息时的提示 29 | messageOnVoice: '&c请勿发送违规语音.' 30 | # 插件消息 31 | Plugin: 32 | # 插件重载消息 33 | messageOnCommandReload: '&aAdvancedSensitiveWords 已重新加载.' 34 | # 违规次数重置消息 35 | messageOnViolationReset: '&bASW&7Notify >> &a已重置所有玩家的违规次数!' 36 | # 插件帮助菜单 37 | messageOnCommandHelp: |- 38 | &bAdvancedSensitiveWords&r---&b帮助菜单 39 | &7/asw reload: &a重新加载过滤词库和插件配置 40 | &7/asw reloadconfig: &a重新加载插件配置 41 | &7/asw add <敏感词>: &a添加敏感词 42 | &7/asw remove <敏感词>: &a移除敏感词 43 | &7/asw addallow <敏感词>: &a添加敏感词到白名单 44 | &7/asw removeallow <敏感词>: &a移除敏感词白名单 45 | &7/asw status: &a显示插件状态菜单 46 | &7/asw test <待测消息>: &a运行敏感词测试 47 | &7/asw help: &a显示帮助信息 48 | &7/asw info <玩家名称>: &a显示玩家违规次数 49 | &7/asw punish <玩家名称> [惩罚]: &a手动惩罚玩家, 不填惩罚将使用配置文件内容 50 | # 插件状态菜单 51 | messageOnCommandStatus: |- 52 | &bAdvancedSensitiveWords&r---&b插件状态(%version%)(MC %mc_version%) 53 | &7系统信息: &b%platform% %bit% (Java %java_version% -- %java_vendor%) 54 | &7初始化: %init% 55 | &7当前模式: %mode% 56 | &7已过滤消息数: %num% 57 | &7近20次处理平均耗时: %ms% 58 | # 敏感词测试消息(不计入已过滤消息) 59 | commandTest: 60 | # 敏感词测试返回 61 | testResultTrue: |- 62 | &b一眼丁真, 鉴定为敏感词(鉴定报告) 63 | &7原消息: &c%original_msg% 64 | &7过滤后消息: &a%processed_msg% 65 | &7敏感词列表: &b%censored_list% 66 | # 敏感词测试通过 67 | testResultPass: '&a待测消息中没有敏感词喵~' 68 | # 敏感词测试未初始化 69 | testNotInit: '&c插件还没有初始化完毕喵' 70 | # 惩罚消息 71 | commandPunish: 72 | # 解析方法出错 73 | parseError: '&c解析方法出错, 请检查指令格式.' 74 | # 已惩罚 75 | success: '&a成功惩罚玩家 %player%.' 76 | # Add 指令相关 77 | commandAdd: 78 | # 敏感词添加成功 79 | success: '&a敏感词添加成功.' 80 | # Remove 指令相关 81 | commandRemove: 82 | # 敏感词移除成功 83 | success: '&a敏感词移除成功.' 84 | # 没有权限执行该指令 85 | noPermission: '&c你没有权限执行该指令.' 86 | # 未知命令 87 | unknownCommand: '&c未知命令, 请使用 &7/asw help' 88 | # 参数不足 89 | argsNotEnough: '&c参数不足, 请使用 &7/asw help' 90 | # 找不到对应玩家 91 | noSuchPlayer: '&c找不到对应玩家.' 92 | # 管理员提醒消息 93 | noticeOperator: '&bASW&7Notify >> &7玩家 &c%player% &7触发了敏感词检测(%type%)(VL: %violation%)(原消息: %message%) 敏感词列表: &b%censored_list%' 94 | # 跨服提醒消息 95 | noticeOperatorProxy: '&bASW&7Notify >> &7玩家 &c%player% (服务器: %server_name%) &7触发了敏感词检测(%type%)(VL: %violation%)(原消息: %message%) 敏感词列表: &b%censored_list%' 96 | # 更新可用 97 | updateAvailable: '&bASW&7Notify >> &7插件有可用更新(%latest_version%), 当前正在运行: &b%current_version%.' 98 | # 玩家信息 99 | messageOnCommandInfo: |- 100 | &bAdvancedSensitiveWords&r---&b玩家信息 101 | &7玩家名称: &b%player% 102 | &7违规次数: &b%violation% 103 | # 重置玩家违规次数 104 | messageOnCommandReset: '&a已重置玩家 %player% 的违规次数.' -------------------------------------------------------------------------------- /bukkit/src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: AdvancedSensitiveWords 2 | version: ${project.version}-${version.channel} 3 | main: ${plugin.main} 4 | api-version: '1.13' 5 | website: ${plugin.website} 6 | build-hash: ${git.commit.id.abbrev} 7 | authors: 8 | - HeyWTF_IS_That 9 | - HaHaWTH 10 | - 0D00_0721 11 | description: ${plugin.description} 12 | loadbefore: 13 | - AddYourPluginHere 14 | softdepend: 15 | - packetevents 16 | - floodgate 17 | - PlaceholderAPI 18 | - AuthMe 19 | - CatSeedLogin 20 | - voicechat 21 | folia-supported: true 22 | commands: 23 | advancedsensitivewords: 24 | description: Advanced Sensitive Words main command. 25 | usage: /asw 26 | aliases: [asw] 27 | permissions: 28 | advancedsensitivewords.*: 29 | description: All permissions of Advanced Sensitive Words. 30 | default: false 31 | children: 32 | advancedsensitivewords.bypass: true 33 | advancedsensitivewords.reload: true 34 | advancedsensitivewords.status: true 35 | advancedsensitivewords.test: true 36 | advancedsensitivewords.help: true 37 | advancedsensitivewords.notice: true 38 | advancedsensitivewords.info: true 39 | advancedsensitivewords.update: true 40 | advancedsensitivewords.punish: true 41 | advancedsensitivewords.add: true 42 | advancedsensitivewords.remove: true 43 | advancedsensitivewords.reset: true 44 | advancedsensitivewords.bypass: 45 | description: Bypass the Advanced Sensitive Words filter. 46 | default: false 47 | advancedsensitivewords.reload: 48 | description: Reload the Advanced Sensitive Words filter. 49 | default: op 50 | advancedsensitivewords.status: 51 | description: Show the Advanced Sensitive Words filter status. 52 | default: op 53 | advancedsensitivewords.test: 54 | description: Test the Advanced Sensitive Words filter. 55 | default: op 56 | advancedsensitivewords.help: 57 | description: Show the Advanced Sensitive Words filter help. 58 | default: op 59 | advancedsensitivewords.notice: 60 | description: Retrieve the notice message when player violated. 61 | default: op 62 | advancedsensitivewords.update: 63 | description: Retrieve the update message. 64 | default: op 65 | advancedsensitivewords.info: 66 | description: Get the player info. 67 | default: op 68 | advancedsensitivewords.reset: 69 | description: Reset the player's violation count. 70 | default: op 71 | advancedsensitivewords.punish: 72 | description: Punish the player. 73 | default: op 74 | advancedsensitivewords.add: 75 | description: Add a sensitive word. 76 | default: op 77 | advancedsensitivewords.remove: 78 | description: Remove a sensitive word. 79 | default: op 80 | # Love you -------------------------------------------------------------------------------- /bukkit/src/main/resources/sensitive_word_dict_en.txt: -------------------------------------------------------------------------------- 1 | fuck 2 | fxk 3 | fxxk 4 | shit 5 | sh1t 6 | suck 7 | sucker 8 | sumyd 9 | dick 10 | d1ck 11 | cock 12 | n1ger 13 | niger 14 | nigger 15 | n1gger 16 | nlgger 17 | nlger 18 | ngger 19 | fowl 20 | sex 21 | $ex 22 | 3ex 23 | prostitute 24 | whore 25 | harlot 26 | gender 27 | pusy 28 | pu3y 29 | pussy 30 | pu33y 31 | sperm 32 | 3perm 33 | Bolocks 34 | Bulshit 35 | nazi 36 | neger 37 | negrero 38 | niga 39 | niger 40 | nigers 41 | niquer 42 | heilhitler 43 | hitler 44 | racista 45 | motherfucker 46 | Motherfucking 47 | scrote 48 | scum 49 | shít 50 | sh!t 51 | sh!te 52 | sh!tes 53 | sh1't 54 | sh1t 55 | sh1te 56 | sh1thead 57 | sh1theads 58 | shhit 59 | shit 60 | shited 61 | shitface 62 | shitfaced 63 | shitforbrains 64 | shitfuck 65 | shitfucker 66 | shitfull 67 | shithapens 68 | shithappens 69 | shithead 70 | shithole 71 | shithouse 72 | shiting 73 | shitings 74 | shitoutofluck 75 | shits 76 | shitspiter 77 | shitstabber 78 | shitstabers 79 | shitstain 80 | shitted 81 | shitter 82 | shitters 83 | shittiest 84 | shitting 85 | shittings 86 | shitty 87 | slanteye 88 | slut 89 | slutbag 90 | sluts 91 | sluting 92 | slutwear 93 | slutwhore -------------------------------------------------------------------------------- /bungee/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | AdvancedSensitiveWords-bungee 8 | jar 9 | 10 | 11 | io.wdsj 12 | AdvancedSensitiveWords 13 | 1.3 14 | ../pom.xml 15 | 16 | 17 | bungee 18 | 19 | 20 | 1.8 21 | UTF-8 22 | 23 | 24 | 25 | clean package 26 | 27 | 28 | org.apache.maven.plugins 29 | maven-compiler-plugin 30 | 3.14.0 31 | 32 | ${java.version} 33 | ${java.version} 34 | 35 | 36 | 37 | org.apache.maven.plugins 38 | maven-shade-plugin 39 | 3.6.0 40 | 41 | 42 | package 43 | 44 | shade 45 | 46 | 47 | 48 | 49 | false 50 | 51 | 52 | org.bstats 53 | io.wdsj.asw.bungee.libs.bstats 54 | 55 | 56 | io.github.thatsmusic99 57 | io.wdsj.asw.velocity.libs.thatsmusic99 58 | 59 | 60 | ${project.artifactId} 61 | 62 | 63 | 64 | 65 | 66 | src/main/resources 67 | true 68 | 69 | 70 | 71 | 72 | 73 | 74 | sonatype 75 | https://oss.sonatype.org/content/groups/public/ 76 | 77 | 78 | cm-repo 79 | https://ci.pluginwiki.us/plugin/repository/everything/ 80 | 81 | 82 | 83 | 84 | 85 | net.md-5 86 | bungeecord-api 87 | 1.21-R0.1 88 | provided 89 | 90 | 91 | org.bstats 92 | bstats-bungeecord 93 | 3.1.0 94 | compile 95 | 96 | 97 | com.github.thatsmusic99 98 | ConfigurationMaster-API 99 | v2.0.0-rc.2 100 | compile 101 | 102 | 103 | io.wdsj 104 | AdvancedSensitiveWords-common 105 | 1.3 106 | compile 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /bungee/src/main/java/io/wdsj/asw/bungee/AdvancedSensitiveWords.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bungee; 2 | 3 | import io.wdsj.asw.bungee.config.Config; 4 | import io.wdsj.asw.bungee.listener.PluginMessageListener; 5 | import io.wdsj.asw.common.update.Updater; 6 | import net.md_5.bungee.api.plugin.Plugin; 7 | import org.bstats.bungeecord.Metrics; 8 | 9 | import java.io.File; 10 | import java.io.IOException; 11 | import java.nio.file.FileAlreadyExistsException; 12 | import java.nio.file.Files; 13 | import java.util.logging.Logger; 14 | 15 | public final class AdvancedSensitiveWords extends Plugin { 16 | public static final String BUNGEE_CHANNEL = "BungeeCord"; 17 | public static final String SUB_CHANNEL = "asw"; 18 | public static Logger LOGGER; 19 | 20 | private static AdvancedSensitiveWords instance; 21 | private static Config config; 22 | public static AdvancedSensitiveWords getInstance() { 23 | return instance; 24 | } 25 | private File dataFolder; 26 | 27 | @Override 28 | public void onEnable() { 29 | LOGGER = getLogger(); 30 | dataFolder = getDataFolder(); 31 | reloadConfiguration(); 32 | instance = this; 33 | getProxy().getPluginManager().registerListener(this, new PluginMessageListener()); 34 | Metrics metrics = new Metrics(this, 21636); 35 | if (config.check_for_update) { 36 | getProxy().getScheduler().runAsync(this, () -> { 37 | LOGGER.info("Checking for update..."); 38 | if (Updater.isUpdateAvailable()) { 39 | if (Updater.isDevChannel()) { 40 | LOGGER.warning("There is a new development version available: " + Updater.getLatestVersion() + 41 | ", you're on: " + Updater.getCurrentVersion()); 42 | } else { 43 | LOGGER.warning("There is a new version available: " + Updater.getLatestVersion() + 44 | ", you're on: " + Updater.getCurrentVersion()); 45 | } 46 | } else { 47 | if (!Updater.isErred()) { 48 | LOGGER.info("You are running the latest version."); 49 | } else { 50 | LOGGER.info("Unable to fetch version info."); 51 | } 52 | } 53 | }); 54 | } 55 | } 56 | 57 | @Override 58 | public void onDisable() { 59 | getProxy().getPluginManager().unregisterListener(new PluginMessageListener()); 60 | } 61 | 62 | public static Config config() { 63 | return config; 64 | } 65 | 66 | public void createDirectory(File dir) throws IOException { 67 | try { 68 | Files.createDirectories(dir.toPath()); 69 | } catch (FileAlreadyExistsException e) { // Thrown if dir exists but is not a directory 70 | if (dir.delete()) createDirectory(dir); 71 | } 72 | } 73 | 74 | private void reloadConfiguration() { 75 | try { 76 | createDirectory(dataFolder); 77 | config = new Config(this, dataFolder); 78 | config.saveConfig(); 79 | } catch (Throwable t) { 80 | LOGGER.severe("Failed while loading config!"); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /bungee/src/main/java/io/wdsj/asw/bungee/config/Config.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bungee.config; 2 | 3 | import io.github.thatsmusic99.configurationmaster.api.ConfigFile; 4 | import io.github.thatsmusic99.configurationmaster.api.ConfigSection; 5 | import io.wdsj.asw.bungee.AdvancedSensitiveWords; 6 | import io.wdsj.asw.common.template.PluginVersionTemplate; 7 | 8 | import java.io.File; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | public class Config { 13 | 14 | private final ConfigFile config; 15 | private final AdvancedSensitiveWords plugin; 16 | public final boolean check_for_update; 17 | 18 | public Config(AdvancedSensitiveWords plugin, File dataFolder) throws Exception { 19 | this.plugin = plugin; 20 | // Load config.yml with ConfigMaster 21 | this.config = ConfigFile.loadConfig(new File(dataFolder, "config.yml")); 22 | config.set("plugin-version", PluginVersionTemplate.VERSION); 23 | 24 | // Pre-structure to force order 25 | structureConfig(); 26 | 27 | this.check_for_update = getBoolean("plugin.check-update", true, 28 | "If set to true, will check for update on plugin startup."); 29 | } 30 | 31 | public void saveConfig() { 32 | try { 33 | config.save(); 34 | } catch (Exception e) { 35 | this.plugin.getLogger().severe("Failed to save config file: " + e.getMessage()); 36 | } 37 | } 38 | 39 | private void structureConfig() { 40 | createTitledSection("Plugin general setting", "plugin"); 41 | } 42 | 43 | public void createTitledSection(String title, String path) { 44 | config.addSection(title); 45 | config.addDefault(path, null); 46 | } 47 | 48 | public boolean getBoolean(String path, boolean def, String comment) { 49 | config.addDefault(path, def, comment); 50 | return config.getBoolean(path, def); 51 | } 52 | 53 | public boolean getBoolean(String path, boolean def) { 54 | config.addDefault(path, def); 55 | return config.getBoolean(path, def); 56 | } 57 | 58 | public String getString(String path, String def, String comment) { 59 | config.addDefault(path, def, comment); 60 | return config.getString(path, def); 61 | } 62 | 63 | public String getString(String path, String def) { 64 | config.addDefault(path, def); 65 | return config.getString(path, def); 66 | } 67 | 68 | public double getDouble(String path, double def, String comment) { 69 | config.addDefault(path, def, comment); 70 | return config.getDouble(path, def); 71 | } 72 | 73 | public double getDouble(String path, double def) { 74 | config.addDefault(path, def); 75 | return config.getDouble(path, def); 76 | } 77 | 78 | public int getInt(String path, int def, String comment) { 79 | config.addDefault(path, def, comment); 80 | return config.getInteger(path, def); 81 | } 82 | 83 | public int getInt(String path, int def) { 84 | config.addDefault(path, def); 85 | return config.getInteger(path, def); 86 | } 87 | 88 | public long getLong(String path, long def, String comment) { 89 | config.addDefault(path, def, comment); 90 | return config.getLong(path, def); 91 | } 92 | 93 | public long getLong(String path, long def) { 94 | config.addDefault(path, def); 95 | return config.getLong(path, def); 96 | } 97 | 98 | public List getList(String path, List def, String comment) { 99 | config.addDefault(path, def, comment); 100 | return config.getStringList(path); 101 | } 102 | 103 | public List getList(String path, List def) { 104 | config.addDefault(path, def); 105 | return config.getStringList(path); 106 | } 107 | 108 | public ConfigSection getConfigSection(String path, Map defaultKeyValue) { 109 | config.addDefault(path, null); 110 | config.makeSectionLenient(path); 111 | defaultKeyValue.forEach((string, object) -> config.addExample(path+"."+string, object)); 112 | return config.getConfigSection(path); 113 | } 114 | 115 | public ConfigSection getConfigSection(String path, Map defaultKeyValue, String comment) { 116 | config.addDefault(path, null, comment); 117 | config.makeSectionLenient(path); 118 | defaultKeyValue.forEach((string, object) -> config.addExample(path+"."+string, object)); 119 | return config.getConfigSection(path); 120 | } 121 | 122 | public void addComment(String path, String comment) { 123 | config.addComment(path, comment); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /bungee/src/main/java/io/wdsj/asw/bungee/listener/PluginMessageListener.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.bungee.listener; 2 | 3 | import com.google.common.io.ByteArrayDataInput; 4 | import com.google.common.io.ByteStreams; 5 | import io.wdsj.asw.bungee.AdvancedSensitiveWords; 6 | import io.wdsj.asw.common.constant.networking.ChannelDataConstant; 7 | import io.wdsj.asw.common.datatype.io.LimitedByteArrayDataOutput; 8 | import io.wdsj.asw.common.template.PluginVersionTemplate; 9 | import net.md_5.bungee.api.connection.Server; 10 | import net.md_5.bungee.api.event.PluginMessageEvent; 11 | import net.md_5.bungee.api.plugin.Listener; 12 | import net.md_5.bungee.event.EventHandler; 13 | 14 | import java.util.Locale; 15 | 16 | import static io.wdsj.asw.bungee.AdvancedSensitiveWords.*; 17 | 18 | public class PluginMessageListener implements Listener { 19 | private boolean warned = false; 20 | 21 | @EventHandler 22 | public void onPluginMessage(final PluginMessageEvent event) { 23 | if (!event.getTag().equals(BUNGEE_CHANNEL)) return; 24 | if (!(event.getSender() instanceof Server)) { 25 | return; 26 | } 27 | ByteArrayDataInput in = ByteStreams.newDataInput(event.getData()); 28 | if (in.readUTF().equals(SUB_CHANNEL)) { 29 | if (!in.readUTF().equals(PluginVersionTemplate.VERSION) && !warned) { 30 | LOGGER.warning("Plugin version mismatch! Things may not work properly."); 31 | warned = true; 32 | } 33 | switch (in.readUTF().toLowerCase(Locale.ROOT)) { 34 | case ChannelDataConstant.NOTICE: 35 | try { 36 | String serverName = ((Server) event.getSender()).getInfo().getName(); 37 | LimitedByteArrayDataOutput out = LimitedByteArrayDataOutput.newDataOutput(32767); 38 | out.write(event.getData()); 39 | out.writeUTF(serverName); 40 | AdvancedSensitiveWords.getInstance().getProxy().getServers().forEach((name, server) -> { 41 | if (!server.equals(((Server) event.getSender()).getInfo()) && !server.getPlayers().isEmpty()) { 42 | server.sendData(BUNGEE_CHANNEL, out.toByteArray()); 43 | } 44 | }); 45 | event.setCancelled(true); 46 | } catch (Exception e) { 47 | LOGGER.severe("An error occurred while sending plugin message " + e.getMessage()); 48 | } 49 | break; 50 | case ChannelDataConstant.COMMAND_PROXY: 51 | String command = in.readUTF(); 52 | AdvancedSensitiveWords.getInstance().getProxy().getPluginManager().dispatchCommand(AdvancedSensitiveWords.getInstance().getProxy().getConsole(), command); 53 | event.setCancelled(true); 54 | break; 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /bungee/src/main/resources/bungee.yml: -------------------------------------------------------------------------------- 1 | name: AdvancedSensitiveWords 2 | version: '${project.version}-${version.channel}' 3 | main: io.wdsj.asw.bungee.AdvancedSensitiveWords 4 | author: HaHaWTH 5 | build-hash: ${git.commit.id.abbrev} 6 | -------------------------------------------------------------------------------- /common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | io.wdsj 9 | AdvancedSensitiveWords 10 | 1.3 11 | ../pom.xml 12 | 13 | 14 | AdvancedSensitiveWords-common 15 | 16 | common 17 | 18 | 19 | 1.8 20 | 1.8 21 | UTF-8 22 | 23 | 24 | 25 | 26 | 27 | org.codehaus.mojo 28 | templating-maven-plugin 29 | 3.0.0 30 | 31 | 32 | filter-src 33 | 34 | filter-sources 35 | 36 | 37 | ${project.build.directory}/templates 38 | src/main/java/io/wdsj/asw/common/template 39 | 40 | 41 | 42 | 43 | 44 | org.apache.maven.plugins 45 | maven-compiler-plugin 46 | 3.14.0 47 | 48 | 49 | compile 50 | compile 51 | 52 | compile 53 | 54 | 55 | 56 | 57 | ${maven.compiler.source} 58 | ${maven.compiler.target} 59 | 60 | **/io/wdsj/asw/common/template/** 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | org.jetbrains 70 | annotations 71 | 26.0.2 72 | provided 73 | 74 | 75 | 76 | com.google.code.gson 77 | gson 78 | 2.12.1 79 | provided 80 | 81 | 82 | 83 | com.google.guava 84 | guava 85 | 33.4.0-jre 86 | provided 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /common/src/main/java/io/wdsj/asw/common/constant/networking/ChannelDataConstant.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.common.constant.networking; 2 | 3 | public class ChannelDataConstant { 4 | public static final String NOTICE = "notice"; 5 | public static final String COMMAND_PROXY = "command_proxy"; 6 | } 7 | -------------------------------------------------------------------------------- /common/src/main/java/io/wdsj/asw/common/datatype/TimedString.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.common.datatype; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.util.Objects; 6 | 7 | /** 8 | * A class that represents a string with timestamp. 9 | *

10 | * Represents a string paired with a timestamp, typically used to track 11 | * when the string was generated or modified. 12 | *

13 | * Example usage: 14 | *

15 |  *     TimedString timedString = TimedString.of("Hello World", System.currentTimeMillis());
16 |  * 
17 | * 18 | * This class is immutable and thread-safe. 19 | * 20 | * @author HaHaWTH 21 | */ 22 | public class TimedString { 23 | private final String str; 24 | private final long time; 25 | 26 | private TimedString(String str, long time) { 27 | this.str = str; 28 | this.time = time; 29 | } 30 | 31 | /** 32 | * Returns the string. 33 | * @return the string 34 | */ 35 | public String getString() { 36 | return str; 37 | } 38 | 39 | /** 40 | * Returns the timestamp. 41 | * @return the timestamp 42 | */ 43 | public long getTime() { 44 | return time; 45 | } 46 | 47 | /** 48 | * Returns a new TimedString instance. 49 | * @param str the string 50 | * @param time the timestamp 51 | * @return a new TimedString instance 52 | */ 53 | public static TimedString of(@NotNull String str, long time) { 54 | Objects.requireNonNull(str, "String cannot be null"); 55 | if (time < 0) throw new IllegalArgumentException("Time cannot be negative"); 56 | return new TimedString(str, time); 57 | } 58 | 59 | /** 60 | * Returns a new TimedString instance with the current timestamp. 61 | * @param str the string 62 | * @return a new TimedString instance 63 | */ 64 | public static TimedString of(@NotNull String str) { 65 | return of(str, System.currentTimeMillis()); 66 | } 67 | 68 | @Override 69 | public boolean equals(Object o) { 70 | if (this == o) return true; 71 | if (o == null || getClass() != o.getClass()) return false; 72 | TimedString that = (TimedString) o; 73 | return time == that.time && str.equals(that.str); 74 | } 75 | 76 | @Override 77 | public int hashCode() { 78 | return Objects.hash(str, time); 79 | } 80 | 81 | @Override 82 | public String toString() { 83 | return "TimedString{" + 84 | "str='" + str + '\'' + 85 | ", time=" + time + 86 | '}'; 87 | } 88 | } -------------------------------------------------------------------------------- /common/src/main/java/io/wdsj/asw/common/datatype/io/LimitedByteArrayDataOutput.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.common.datatype.io; 2 | 3 | import com.google.common.io.ByteArrayDataOutput; 4 | import com.google.common.io.ByteStreams; 5 | 6 | import java.io.IOException; 7 | import java.nio.charset.StandardCharsets; 8 | 9 | /** 10 | * A utility class for writing data to a byte array with a size limit. 11 | * It wraps a {@link ByteArrayDataOutput} to write various data types such as integers, floats, and UTF-8 strings, 12 | * while ensuring that the total data does not exceed the specified maximum size. 13 | * 14 | *

Throws an {@link IOException} if data exceeds the allowed size during the writing process. 15 | **/ 16 | public final class LimitedByteArrayDataOutput { 17 | private static final int doubleSize = Double.BYTES; 18 | private static final int floatSize = Float.BYTES; 19 | private static final int intSize = Integer.BYTES; 20 | private static final int longSize = Long.BYTES; 21 | private static final int charSize = Character.BYTES; 22 | private static final int shortSize = Short.BYTES; 23 | private static final int booleanSize = 1; 24 | private final ByteArrayDataOutput output; 25 | private final int maxSize; 26 | private int currentSize; 27 | 28 | private LimitedByteArrayDataOutput(int maxSize) { 29 | this.output = ByteStreams.newDataOutput(); 30 | this.maxSize = maxSize; 31 | this.currentSize = 0; 32 | } 33 | 34 | public static LimitedByteArrayDataOutput newDataOutput(int maxSize) { 35 | if (maxSize < 0) { 36 | throw new IllegalArgumentException("Max size must be greater than 0"); 37 | } 38 | return new LimitedByteArrayDataOutput(maxSize); 39 | } 40 | 41 | public byte[] toByteArray() { 42 | return output.toByteArray(); 43 | } 44 | 45 | public void write(byte[] bytes) throws IOException { 46 | if (currentSize + bytes.length > maxSize) { 47 | throw new IOException("Data exceeds maximum size of " + maxSize + " bytes"); 48 | } 49 | output.write(bytes); 50 | currentSize += bytes.length; 51 | } 52 | 53 | public void writeInt(int value) throws IOException { 54 | if (currentSize + intSize > maxSize) { 55 | throw new IOException("Data exceeds maximum size of " + maxSize + " bytes"); 56 | } 57 | output.writeInt(value); 58 | currentSize += intSize; 59 | } 60 | 61 | public void writeLong(long value) throws IOException { 62 | if (currentSize + longSize > maxSize) { 63 | throw new IOException("Data exceeds maximum size of " + maxSize + " bytes"); 64 | } 65 | output.writeLong(value); 66 | currentSize += longSize; 67 | } 68 | 69 | public void writeBoolean(boolean value) throws IOException { 70 | if (currentSize + booleanSize > maxSize) { 71 | throw new IOException("Data exceeds maximum size of " + maxSize + " bytes"); 72 | } 73 | output.writeBoolean(value); 74 | currentSize += booleanSize; 75 | } 76 | 77 | public void writeUTF(String value) throws IOException { 78 | byte[] utfBytes = value.getBytes(StandardCharsets.UTF_8); 79 | if (currentSize + utfBytes.length + 2 > maxSize) { // 2 bytes for UTF string length 80 | throw new IOException("Data exceeds maximum size of " + maxSize + " bytes"); 81 | } 82 | output.writeUTF(value); 83 | currentSize += utfBytes.length + 2; 84 | } 85 | 86 | public void writeDouble(double value) throws IOException { 87 | if (currentSize + doubleSize > maxSize) { 88 | throw new IOException("Data exceeds maximum size of " + maxSize + " bytes"); 89 | } 90 | output.writeDouble(value); 91 | currentSize += doubleSize; 92 | } 93 | 94 | public void writeFloat(float value) throws IOException { 95 | if (currentSize + floatSize > maxSize) { 96 | throw new IOException("Data exceeds maximum size of " + maxSize + " bytes"); 97 | } 98 | output.writeFloat(value); 99 | currentSize += floatSize; 100 | } 101 | 102 | public void writeShort(short value) throws IOException { 103 | if (currentSize + shortSize > maxSize) { 104 | throw new IOException("Data exceeds maximum size of " + maxSize + " bytes"); 105 | } 106 | output.writeShort(value); 107 | currentSize += shortSize; 108 | } 109 | 110 | public void writeChar(char value) throws IOException { 111 | if (currentSize + charSize > maxSize) { 112 | throw new IOException("Data exceeds maximum size of " + maxSize + " bytes"); 113 | } 114 | output.writeChar(value); 115 | currentSize += charSize; 116 | } 117 | 118 | public int getCurrentSize() { 119 | return currentSize; 120 | } 121 | } -------------------------------------------------------------------------------- /common/src/main/java/io/wdsj/asw/common/template/PluginVersionTemplate.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.common.template; 2 | 3 | public class PluginVersionTemplate { 4 | public static final String VERSION = "${project.version}"; 5 | public static final String VERSION_CHANNEL = "${version.channel}"; 6 | public static final String COMMIT_HASH_SHORT = "${git.commit.id.abbrev}"; 7 | public static final String COMMIT_HASH = "${git.commit.id}"; 8 | public static final String COMMIT_BRANCH = "${git.branch}"; 9 | } 10 | -------------------------------------------------------------------------------- /common/src/main/java/io/wdsj/asw/common/update/Updater.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.common.update; 2 | 3 | import com.google.gson.JsonObject; 4 | import com.google.gson.JsonParser; 5 | import io.wdsj.asw.common.template.PluginVersionTemplate; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.io.InputStreamReader; 9 | import java.net.HttpURLConnection; 10 | import java.net.URI; 11 | import java.net.URL; 12 | 13 | public class Updater { 14 | private static String currentVersion = PluginVersionTemplate.VERSION; 15 | private static String latestVersion; 16 | private static boolean isUpdateAvailable = false; 17 | private static boolean isErred = false; 18 | private static final String USER_NAME = "HaHaWTH"; 19 | private static final String REPOSITORY = "AdvancedSensitiveWords"; 20 | private static final String RELEASE_URL = "https://api.github.com/repos/" + USER_NAME + "/" + REPOSITORY + "/releases/latest"; 21 | private static final String COMMITS_URL = "https://api.github.com/repos/" + USER_NAME + "/" + REPOSITORY + "/commits/" + PluginVersionTemplate.COMMIT_BRANCH; 22 | @SuppressWarnings("ConstantConditions") // IDE doesn't know this will be replaced 23 | private static final boolean isDevChannel = PluginVersionTemplate.VERSION_CHANNEL.equalsIgnoreCase("dev"); 24 | 25 | /** 26 | * Check if there is an update available 27 | * Note: This method will perform a network request! 28 | * @return true if there is an update available, false otherwise 29 | */ 30 | public static boolean isUpdateAvailable() { 31 | currentVersion = PluginVersionTemplate.VERSION; 32 | if (isDevChannel) { 33 | return isDevUpdateAvailable(); 34 | } 35 | URI uri = URI.create(RELEASE_URL); 36 | try { 37 | URL url = uri.toURL(); 38 | HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 39 | conn.setConnectTimeout(10000); 40 | conn.setReadTimeout(10000); 41 | conn.setRequestMethod("GET"); 42 | conn.setRequestProperty("Accept", "application/vnd.github+json"); 43 | try (InputStreamReader reader = new InputStreamReader(conn.getInputStream())) { 44 | JsonObject jsonObject = new JsonParser().parse(reader).getAsJsonObject(); 45 | String latest = jsonObject.get("tag_name").getAsString(); 46 | latestVersion = latest; 47 | isUpdateAvailable = !currentVersion.equals(latest); 48 | reader.close(); 49 | return isUpdateAvailable; 50 | } 51 | } catch (Exception e) { 52 | isErred = true; 53 | isUpdateAvailable = false; 54 | return false; 55 | } 56 | } 57 | 58 | protected static boolean isDevUpdateAvailable() { 59 | URI uri = URI.create(COMMITS_URL); 60 | try { 61 | URL url = uri.toURL(); 62 | HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 63 | connection.setRequestMethod("GET"); 64 | connection.setConnectTimeout(10000); 65 | connection.setReadTimeout(10000); 66 | connection.setRequestProperty("Accept", "application/vnd.github+json"); 67 | int responseCode = connection.getResponseCode(); 68 | if (responseCode == HttpURLConnection.HTTP_OK) { 69 | try (InputStreamReader reader = new InputStreamReader(connection.getInputStream())) { 70 | JsonObject jsonObject = new JsonParser().parse(reader).getAsJsonObject(); 71 | String latestHash = jsonObject.get("sha").getAsString(); 72 | latestVersion = latestHash.substring(0, 7); 73 | currentVersion = PluginVersionTemplate.COMMIT_HASH_SHORT; 74 | isUpdateAvailable = !PluginVersionTemplate.COMMIT_HASH.equals(latestHash); 75 | reader.close(); 76 | return isUpdateAvailable; 77 | } 78 | } 79 | } catch (Exception ignored) { 80 | } 81 | isErred = true; 82 | isUpdateAvailable = false; 83 | return false; 84 | } 85 | 86 | public static String getLatestVersion() { 87 | return latestVersion; 88 | } 89 | @NotNull 90 | public static String getCurrentVersion() { 91 | return currentVersion; 92 | } 93 | 94 | /** 95 | * Returns true if there is an update available, false otherwise 96 | * Must be called after {@link Updater#isUpdateAvailable()} 97 | * @return A boolean indicating whether there is an update available 98 | */ 99 | public static boolean hasUpdate() { 100 | return isUpdateAvailable; 101 | } 102 | 103 | public static boolean isErred() { 104 | return isErred; 105 | } 106 | 107 | public static boolean isDevChannel() { 108 | return isDevChannel; 109 | } 110 | } -------------------------------------------------------------------------------- /extended_words/zh_cn/偏旁部首变换.txt: -------------------------------------------------------------------------------- 1 | 澡称冯 2 | 澡祢冯 3 | 澡称吗 4 | 澡祢吗 5 | 傻福 6 | 傻幅 7 | 傻蝠 8 | 傻畐 9 | 称冯 -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | io.wdsj 8 | AdvancedSensitiveWords 9 | 1.3 10 | pom 11 | 12 | AdvancedSensitiveWords 13 | 14 | 15 | 1.8 16 | UTF-8 17 | dev 18 | 19 | 20 | 21 | common 22 | bukkit 23 | velocity 24 | bungee 25 | 26 | 27 | 28 | 29 | 30 | pl.project13.maven 31 | git-commit-id-plugin 32 | 4.9.10 33 | 34 | 35 | 36 | revision 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /velocity/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | AdvancedSensitiveWords-velocity 8 | jar 9 | 10 | 11 | io.wdsj 12 | AdvancedSensitiveWords 13 | 1.3 14 | ../pom.xml 15 | 16 | 17 | 18 | velocity 19 | 20 | 21 | 17 22 | UTF-8 23 | 24 | 25 | 26 | 27 | 28 | org.apache.maven.plugins 29 | maven-compiler-plugin 30 | 3.14.0 31 | 32 | 33 | compile 34 | compile 35 | 36 | compile 37 | 38 | 39 | 40 | 41 | ${java.version} 42 | ${java.version} 43 | 44 | 45 | 46 | org.apache.maven.plugins 47 | maven-shade-plugin 48 | 3.6.0 49 | 50 | 51 | package 52 | 53 | shade 54 | 55 | 56 | true 57 | false 58 | 59 | 60 | org.bstats 61 | io.wdsj.asw.velocity.libs.bstats 62 | 63 | 64 | io.github.thatsmusic99 65 | io.wdsj.asw.velocity.libs.thatsmusic99 66 | 67 | 68 | ${project.artifactId} 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | src/main/resources 77 | true 78 | 79 | 80 | 81 | 82 | 83 | 84 | papermc-repo 85 | https://repo.papermc.io/repository/maven-public/ 86 | 87 | 88 | sonatype 89 | https://oss.sonatype.org/content/groups/public/ 90 | 91 | 92 | cm-repo 93 | https://ci.pluginwiki.us/plugin/repository/everything/ 94 | 95 | 96 | 97 | 98 | 99 | com.velocitypowered 100 | velocity-api 101 | 3.4.0-SNAPSHOT 102 | provided 103 | 104 | 105 | org.bstats 106 | bstats-velocity 107 | 3.1.0 108 | compile 109 | 110 | 111 | com.github.thatsmusic99 112 | ConfigurationMaster-API 113 | v2.0.0-rc.2 114 | compile 115 | 116 | 117 | io.wdsj 118 | AdvancedSensitiveWords-common 119 | 1.3 120 | compile 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /velocity/src/main/java/io/wdsj/asw/velocity/AdvancedSensitiveWords.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.velocity; 2 | 3 | import com.google.inject.Inject; 4 | import com.velocitypowered.api.event.Subscribe; 5 | import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; 6 | import com.velocitypowered.api.plugin.Plugin; 7 | import com.velocitypowered.api.plugin.annotation.DataDirectory; 8 | import com.velocitypowered.api.proxy.ProxyServer; 9 | import com.velocitypowered.api.proxy.messages.ChannelIdentifier; 10 | import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier; 11 | import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; 12 | import io.wdsj.asw.common.template.PluginVersionTemplate; 13 | import io.wdsj.asw.common.update.Updater; 14 | import io.wdsj.asw.velocity.config.Config; 15 | import io.wdsj.asw.velocity.subscriber.PluginMessageForwarder; 16 | import org.bstats.velocity.Metrics; 17 | import org.slf4j.Logger; 18 | 19 | import java.io.File; 20 | import java.io.IOException; 21 | import java.nio.file.FileAlreadyExistsException; 22 | import java.nio.file.Files; 23 | import java.nio.file.Path; 24 | 25 | @Plugin( 26 | id = "advancedsensitivewords", 27 | name = "AdvancedSensitiveWords", 28 | version = PluginVersionTemplate.VERSION + "-" + PluginVersionTemplate.VERSION_CHANNEL, 29 | authors = {"HaHaWTH"} 30 | ) 31 | public class AdvancedSensitiveWords { 32 | 33 | private final Logger logger; 34 | private final ProxyServer server; 35 | private final Metrics.Factory metricsFactory; 36 | private final File dataFolder; 37 | public static final MinecraftChannelIdentifier CHANNEL = MinecraftChannelIdentifier.create("asw", "main"); 38 | public static final ChannelIdentifier LEGACY_CHANNEL 39 | = new LegacyChannelIdentifier("asw:main"); 40 | private static Config config; 41 | @Inject 42 | public AdvancedSensitiveWords(Logger logger, ProxyServer server, Metrics.Factory metricsFactory, @DataDirectory Path path) { 43 | this.logger = logger; 44 | this.server = server; 45 | this.metricsFactory = metricsFactory; 46 | this.dataFolder = path.toFile(); 47 | } 48 | @Subscribe 49 | public void onProxyInitialization(ProxyInitializeEvent event) { 50 | reloadConfiguration(); 51 | server.getChannelRegistrar().register(CHANNEL, LEGACY_CHANNEL); 52 | Metrics metrics = metricsFactory.make(this, 21637); 53 | server.getEventManager().register(this, new PluginMessageForwarder(logger, server)); 54 | if (config.check_for_update) { 55 | server.getScheduler().buildTask(this, () -> { 56 | logger.info("Checking for update..."); 57 | if (Updater.isUpdateAvailable()) { 58 | if (Updater.isDevChannel()) { 59 | logger.warn("There is a new development version available: {}, you're on: {}", Updater.getLatestVersion(), Updater.getCurrentVersion()); 60 | } else { 61 | logger.warn("There is a new version available: {}, you're on: {}", Updater.getLatestVersion(), Updater.getCurrentVersion()); 62 | } 63 | } else { 64 | if (!Updater.isErred()) { 65 | logger.info("You are running the latest version."); 66 | } else { 67 | logger.info("Unable to fetch version info."); 68 | } 69 | } 70 | }).schedule(); 71 | } 72 | } 73 | 74 | private void reloadConfiguration() { 75 | try { 76 | createDirectory(dataFolder); 77 | config = new Config(this, dataFolder); 78 | config.saveConfig(); 79 | } catch (Throwable t) { 80 | logger.error("Failed while loading config!", t); 81 | } 82 | } 83 | 84 | public void createDirectory(File dir) throws IOException { 85 | try { 86 | Files.createDirectories(dir.toPath()); 87 | } catch (FileAlreadyExistsException e) { // Thrown if dir exists but is not a directory 88 | if (dir.delete()) createDirectory(dir); 89 | } 90 | } 91 | 92 | public Logger getLogger() { 93 | return this.logger; 94 | } 95 | 96 | public static Config config() { 97 | return config; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /velocity/src/main/java/io/wdsj/asw/velocity/config/Config.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.velocity.config; 2 | 3 | import io.github.thatsmusic99.configurationmaster.api.ConfigFile; 4 | import io.github.thatsmusic99.configurationmaster.api.ConfigSection; 5 | import io.wdsj.asw.common.template.PluginVersionTemplate; 6 | import io.wdsj.asw.velocity.AdvancedSensitiveWords; 7 | 8 | import java.io.File; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | public class Config { 13 | 14 | private final ConfigFile config; 15 | private final AdvancedSensitiveWords plugin; 16 | public final boolean check_for_update; 17 | 18 | public Config(AdvancedSensitiveWords plugin, File dataFolder) throws Exception { 19 | this.plugin = plugin; 20 | // Load config.yml with ConfigMaster 21 | this.config = ConfigFile.loadConfig(new File(dataFolder, "config.yml")); 22 | config.set("plugin-version", PluginVersionTemplate.VERSION); 23 | 24 | // Pre-structure to force order 25 | structureConfig(); 26 | 27 | this.check_for_update = getBoolean("plugin.check-update", true, """ 28 | If set to true, will check for update on plugin startup."""); 29 | } 30 | 31 | public void saveConfig() { 32 | try { 33 | config.save(); 34 | } catch (Exception e) { 35 | this.plugin.getLogger().error("Failed to save config file", e); 36 | } 37 | } 38 | 39 | private void structureConfig() { 40 | createTitledSection("Plugin general setting", "plugin"); 41 | } 42 | 43 | public void createTitledSection(String title, String path) { 44 | config.addSection(title); 45 | config.addDefault(path, null); 46 | } 47 | 48 | public boolean getBoolean(String path, boolean def, String comment) { 49 | config.addDefault(path, def, comment); 50 | return config.getBoolean(path, def); 51 | } 52 | 53 | public boolean getBoolean(String path, boolean def) { 54 | config.addDefault(path, def); 55 | return config.getBoolean(path, def); 56 | } 57 | 58 | public String getString(String path, String def, String comment) { 59 | config.addDefault(path, def, comment); 60 | return config.getString(path, def); 61 | } 62 | 63 | public String getString(String path, String def) { 64 | config.addDefault(path, def); 65 | return config.getString(path, def); 66 | } 67 | 68 | public double getDouble(String path, double def, String comment) { 69 | config.addDefault(path, def, comment); 70 | return config.getDouble(path, def); 71 | } 72 | 73 | public double getDouble(String path, double def) { 74 | config.addDefault(path, def); 75 | return config.getDouble(path, def); 76 | } 77 | 78 | public int getInt(String path, int def, String comment) { 79 | config.addDefault(path, def, comment); 80 | return config.getInteger(path, def); 81 | } 82 | 83 | public int getInt(String path, int def) { 84 | config.addDefault(path, def); 85 | return config.getInteger(path, def); 86 | } 87 | 88 | public long getLong(String path, long def, String comment) { 89 | config.addDefault(path, def, comment); 90 | return config.getLong(path, def); 91 | } 92 | 93 | public long getLong(String path, long def) { 94 | config.addDefault(path, def); 95 | return config.getLong(path, def); 96 | } 97 | 98 | public List getList(String path, List def, String comment) { 99 | config.addDefault(path, def, comment); 100 | return config.getStringList(path); 101 | } 102 | 103 | public List getList(String path, List def) { 104 | config.addDefault(path, def); 105 | return config.getStringList(path); 106 | } 107 | 108 | public ConfigSection getConfigSection(String path, Map defaultKeyValue) { 109 | config.addDefault(path, null); 110 | config.makeSectionLenient(path); 111 | defaultKeyValue.forEach((string, object) -> config.addExample(path+"."+string, object)); 112 | return config.getConfigSection(path); 113 | } 114 | 115 | public ConfigSection getConfigSection(String path, Map defaultKeyValue, String comment) { 116 | config.addDefault(path, null, comment); 117 | config.makeSectionLenient(path); 118 | defaultKeyValue.forEach((string, object) -> config.addExample(path+"."+string, object)); 119 | return config.getConfigSection(path); 120 | } 121 | 122 | public void addComment(String path, String comment) { 123 | config.addComment(path, comment); 124 | } 125 | } -------------------------------------------------------------------------------- /velocity/src/main/java/io/wdsj/asw/velocity/subscriber/PluginMessageForwarder.java: -------------------------------------------------------------------------------- 1 | package io.wdsj.asw.velocity.subscriber; 2 | 3 | import com.google.common.io.ByteStreams; 4 | import com.velocitypowered.api.event.Subscribe; 5 | import com.velocitypowered.api.event.connection.PluginMessageEvent; 6 | import com.velocitypowered.api.proxy.ProxyServer; 7 | import com.velocitypowered.api.proxy.ServerConnection; 8 | import io.wdsj.asw.common.constant.networking.ChannelDataConstant; 9 | import io.wdsj.asw.common.datatype.io.LimitedByteArrayDataOutput; 10 | import io.wdsj.asw.common.template.PluginVersionTemplate; 11 | import org.slf4j.Logger; 12 | 13 | import java.util.Locale; 14 | 15 | import static io.wdsj.asw.velocity.AdvancedSensitiveWords.CHANNEL; 16 | import static io.wdsj.asw.velocity.AdvancedSensitiveWords.LEGACY_CHANNEL; 17 | 18 | @SuppressWarnings("UnstableApiUsage") 19 | public class PluginMessageForwarder { 20 | private boolean warned = false; 21 | private final Logger logger; 22 | private final ProxyServer server; 23 | public PluginMessageForwarder(Logger logger, ProxyServer server) { 24 | this.logger = logger; 25 | this.server = server; 26 | } 27 | @Subscribe 28 | public void onPluginMessage(PluginMessageEvent event) { 29 | if (event.getIdentifier().equals(CHANNEL) || event.getIdentifier().equals(LEGACY_CHANNEL)) { 30 | if (!(event.getSource() instanceof ServerConnection)) return; 31 | var serverInfo = ((ServerConnection) event.getSource()).getServerInfo(); 32 | byte[] message = event.getData(); 33 | var input = ByteStreams.newDataInput(message); 34 | if (!input.readUTF().equals(PluginVersionTemplate.VERSION) && !warned) { 35 | logger.warn("Plugin version mismatch! Things may not work properly."); 36 | warned = true; 37 | } 38 | switch (input.readUTF().toLowerCase(Locale.ROOT)) { 39 | case ChannelDataConstant.NOTICE: 40 | server.getAllServers().forEach(server -> { 41 | if (!server.getServerInfo().equals(serverInfo) && !server.getPlayersConnected().isEmpty()) { 42 | var out = LimitedByteArrayDataOutput.newDataOutput(32767); 43 | try { 44 | out.write(message); 45 | out.writeUTF(serverInfo.getName()); 46 | } catch (Exception e) { 47 | logger.error("Failed to write notice message: {}", e.getMessage()); 48 | } 49 | server.sendPluginMessage(CHANNEL, out.toByteArray()); 50 | logger.debug("Send notice message to {}", server.getServerInfo().getName()); 51 | } 52 | }); 53 | break; 54 | case ChannelDataConstant.COMMAND_PROXY: 55 | String command = input.readUTF(); 56 | server.getCommandManager().executeAsync(server.getConsoleCommandSource(), command); 57 | break; 58 | } 59 | event.setResult(PluginMessageEvent.ForwardResult.handled()); 60 | } 61 | } 62 | } 63 | --------------------------------------------------------------------------------