├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md └── workflows │ ├── ChatRegulatorBuild.yml │ └── codeql-analysis.yml ├── .gitignore ├── LICENSE ├── README.md ├── api ├── build.gradle.kts └── src │ └── main │ └── java │ ├── io │ └── github │ │ └── _4drian3d │ │ └── chatregulator │ │ └── api │ │ ├── ChatRegulatorAPI.java │ │ ├── InfractionCount.java │ │ ├── InfractionPlayer.java │ │ ├── PlayerManager.java │ │ ├── Statistics.java │ │ ├── StringChain.java │ │ ├── annotations │ │ ├── Required.java │ │ └── package-info.java │ │ ├── checks │ │ ├── CapsCheck.java │ │ ├── Check.java │ │ ├── CommandCheck.java │ │ ├── CooldownCheck.java │ │ ├── FloodCheck.java │ │ ├── RegexCheck.java │ │ ├── SpamCheck.java │ │ ├── SyntaxCheck.java │ │ ├── UnicodeCheck.java │ │ └── package-info.java │ │ ├── enums │ │ ├── CapsAlgorithm.java │ │ ├── ControlType.java │ │ ├── DetectionMode.java │ │ ├── InfractionType.java │ │ ├── Permission.java │ │ ├── SourceType.java │ │ ├── WarningType.java │ │ └── package-info.java │ │ ├── event │ │ ├── ChatInfractionEvent.java │ │ ├── CommandInfractionEvent.java │ │ ├── InfractionEvent.java │ │ └── package-info.java │ │ ├── lazy │ │ ├── CheckProvider.java │ │ └── LazyDetection.java │ │ ├── package-info.java │ │ ├── result │ │ ├── CheckResult.java │ │ └── package-info.java │ │ └── utils │ │ ├── Commands.java │ │ ├── Components.java │ │ ├── Replacer.java │ │ └── package-info.java │ └── module-info.java ├── common ├── build.gradle.kts └── src │ └── main │ ├── java-templates │ └── io │ │ └── github │ │ └── _4drian3d │ │ └── chatregulator │ │ └── common │ │ └── Constants.java.peb │ └── java │ └── io │ └── github │ └── _4drian3d │ └── chatregulator │ └── common │ ├── commands │ └── RegulatorCommand.java │ ├── configuration │ ├── Blacklist.java │ ├── Checks.java │ ├── Configuration.java │ ├── ConfigurationContainer.java │ ├── CustomPatternSerializer.java │ ├── Messages.java │ └── Section.java │ └── impl │ └── StatisticsImpl.java ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── plugin ├── build.gradle.kts └── src │ ├── main │ └── java │ │ └── io │ │ └── github │ │ └── _4drian3d │ │ └── chatregulator │ │ └── plugin │ │ ├── ChatRegulator.java │ │ ├── commands │ │ ├── Argument.java │ │ ├── ClearArgument.java │ │ ├── PlayerArgument.java │ │ ├── RegulatorCommand.java │ │ ├── ReloadArgument.java │ │ ├── ResetArgument.java │ │ └── StatsArgument.java │ │ ├── impl │ │ ├── FileLogger.java │ │ ├── InfractionPlayerImpl.java │ │ ├── PlayerManagerImpl.java │ │ └── StringChainImpl.java │ │ ├── listener │ │ ├── RegulatorExecutor.java │ │ ├── chat │ │ │ └── ChatListener.java │ │ ├── command │ │ │ ├── CommandListener.java │ │ │ └── SpyListener.java │ │ └── list │ │ │ ├── JoinListener.java │ │ │ └── LeaveListener.java │ │ ├── modules │ │ ├── ConfigurationModule.java │ │ ├── PluginModule.java │ │ └── ProviderModule.java │ │ ├── placeholders │ │ ├── PlayerResolver.java │ │ ├── RegulatorExpansion.java │ │ └── formatter │ │ │ ├── Formatter.java │ │ │ └── MiniPlaceholderFormatter.java │ │ └── source │ │ └── RegulatorCommandSource.java │ └── test │ └── java │ └── io │ └── github │ └── _4drian3d │ └── chatregulator │ ├── InfractionPlayerTest.java │ ├── config │ └── ConfigurationTest.java │ ├── modules │ ├── ReplacerTest.java │ ├── StatisticTest.java │ └── checks │ │ ├── CapsTest.java │ │ ├── CommandTest.java │ │ ├── CooldownTest.java │ │ ├── FloodTest.java │ │ ├── InfractionTest.java │ │ ├── SpamTest.java │ │ ├── SyntaxTest.java │ │ └── UnicodeTest.java │ ├── objects │ └── TestPlayer.java │ └── utils │ ├── CommandsTest.java │ └── TestsUtils.java ├── renovate.json └── settings.gradle.kts /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: https://paypal.me/4drian3d # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG] Bug on command execution" 5 | labels: bug 6 | assignees: 4drian3d 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Activate the configuration option for '....' 16 | 2. Type in the chat the word '...' 17 | 3. Execute some command '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Additional context** 24 | Add any other context about the problem here. 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | #TODO: Add discord support 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for ChatRegulator 4 | title: "[Feature Request] Add configurable sounds" 5 | labels: enhancement 6 | assignees: 4drian3d 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/ChatRegulatorBuild.yml: -------------------------------------------------------------------------------- 1 | name: ChatRegulator Gradle Build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | java: [17] 12 | 13 | steps: 14 | - name: Checkout Repository 15 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 16 | 17 | - name: Set up JDK ${{ matrix.java }} 18 | uses: actions/setup-java@v4 19 | with: 20 | java-version: ${{ matrix.java }} 21 | distribution: 'temurin' 22 | cache: 'gradle' 23 | 24 | - name: Build with Gradlev2.4.1 25 | uses: nick-invision/retry@v3.0.0 26 | with: 27 | timeout_minutes: 4 28 | max_attempts: 2 29 | command: ./gradlew build 30 | 31 | - name: Upload Artifacts 32 | uses: actions/upload-artifact@v4 33 | with: 34 | name: ChatRegulator 35 | path: plugin/build/libs/ChatRegulator-*.jar 36 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '44 19 * * 6' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'java' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v3 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v3 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v3 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | /.idea/ 3 | build/ 4 | bin/ 5 | 6 | # Visual Studio Code 7 | 8 | .settings/ 9 | .classpath 10 | .factorypath 11 | .project 12 | .vscode/ 13 | 14 | plugin/run/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChatRegulator 2 | A global chat regulator for Velocity 3 | 4 | [![WorkFlow](https://img.shields.io/github/actions/workflow/status/4drian3d/ChatRegulator/ChatRegulatorBuild.yml?style=flat-square)](https://github.com/4drian3d/ChatRegulator/actions) 5 | ![Latest Version](https://img.shields.io/github/v/release/4drian3d/ChatRegulator?style=flat-square) 6 | [![Discord](https://img.shields.io/discord/899740810956910683?color=7289da&logo=Discord&label=Discord&style=flat-square)](https://discord.gg/5NMMzK5mAn) 7 | ![Modrinth Downloads](https://img.shields.io/modrinth/dt/zK5VJgm6?logo=Modrinth&style=flat-square) 8 | ![GitHub Downloads](https://img.shields.io/github/downloads/4drian3d/ChatRegulator/total?logo=GitHub&style=flat-square) 9 | 10 | ## Features 11 | 12 | - Message control via regex 13 | - Control of messages sent in specific commands 14 | - Controls the number of consecutive equal characters allowed 15 | - Controls the repetition of the same messages or commands several times in a row. 16 | - Block the execution of the commands you want to execute 17 | - Check the commands executed by players on your server 18 | - Sends warning messages, actionbars o titles to the offender in MiniMessage format 19 | - And more... 20 | 21 | ## Commands 22 | 23 | Check the plugin commands from [Commands](https://github.com/4drian3d/ChatRegulator/wiki/Commands) 24 | 25 | ## Configuration 26 | 27 | Check the configuration and function of each option here [Configuration](https://github.com/4drian3d/ChatRegulator/wiki/Configuration) 28 | 29 | ## Plugin API 30 | 31 | The plugin offers you several methods and events to interact in the detections and with the infraction players. Check the plugin API and its usage at [API](https://github.com/4drian3d/ChatRegulator/wiki/Plugin-API) -------------------------------------------------------------------------------- /api/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `java-library` 3 | `maven-publish` 4 | signing 5 | } 6 | 7 | java { 8 | toolchain { 9 | languageVersion.set(JavaLanguageVersion.of(17)) 10 | } 11 | withSourcesJar() 12 | withJavadocJar() 13 | } 14 | 15 | dependencies { 16 | compileOnlyApi(libs.adventure.api) 17 | compileOnly(libs.caffeine) 18 | } 19 | 20 | tasks { 21 | compileJava { 22 | options.release.set(17) 23 | options.encoding = "UTF-8" 24 | } 25 | 26 | javadoc { 27 | options.encoding = Charsets.UTF_8.name() 28 | (options as StandardJavadocDocletOptions).links( 29 | "https://jd.advntr.dev/api/${libs.versions.adventure.get()}/", 30 | "https://jd.advntr.dev/text-minimessage/${libs.versions.adventure.get()}/", 31 | "https://javadoc.io/doc/org.jetbrains/annotations/24.0.1/index.html", 32 | ) 33 | } 34 | } 35 | 36 | //publishing { 37 | // publications { 38 | // create("maven") { 39 | // repositories { 40 | // maven { 41 | // credentials(PasswordCredentials::class) 42 | // val central = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" 43 | // val snapshots = "https://s01.oss.sonatype.org/content/repositories/snapshots/" 44 | // if (project.version.toString().endsWith("SNAPSHOT")) { 45 | // name = "SonatypeSnapshots" 46 | // setUrl(snapshots) 47 | // } else { 48 | // name = "OSSRH" 49 | // setUrl(central) 50 | // } 51 | // } 52 | // } 53 | // from(components["java"]) 54 | // pom { 55 | // url.set("https://github.com/4drian3d/ChatRegulator") 56 | // licenses { 57 | // license { 58 | // name.set("GNU General Public License version 3 or later") 59 | // url.set("https://opensource.org/licenses/GPL-3.0") 60 | // } 61 | // } 62 | // scm { 63 | // connection.set("scm:git:https://github.com/4drian3d/ChatRegulator.git") 64 | // developerConnection.set("scm:git:ssh://git@github.com/4drian3d/ChatRegulator.git") 65 | // url.set("https://github.com/4drian3d/ChatRegulator") 66 | // } 67 | // developers { 68 | // developer { 69 | // id.set("4drian3d") 70 | // name.set("Adrian Gonzales") 71 | // email.set("adriangonzalesval@gmail.com") 72 | // } 73 | // } 74 | // issueManagement { 75 | // name.set("GitHub") 76 | // url.set("https://github.com/4drian3d/ChatRegulator/issues") 77 | // } 78 | // ciManagement { 79 | // name.set("GitHub Actions") 80 | // url.set("https://github.com/4drian3d/ChatRegulator/actions") 81 | // } 82 | // name.set(project.name) 83 | // description.set(project.description) 84 | // url.set("https://github.com/4drian3d/ChatRegulator") 85 | // } 86 | // artifactId = "chatregulator-api" 87 | // } 88 | // } 89 | //} 90 | //signing { 91 | // useGpgCmd() 92 | // sign(configurations.archives.get()) 93 | // sign(publishing.publications["maven"]) 94 | //} 95 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/ChatRegulatorAPI.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | /** 6 | * Plugin API 7 | */ 8 | public interface ChatRegulatorAPI { 9 | /** 10 | * The PlayerManager has information about the connected players, 11 | * as well as their infractions and other data 12 | * 13 | * @return ChatRegulator's PlayerManager 14 | */ 15 | @NotNull PlayerManager getPlayerManager(); 16 | 17 | /** 18 | * Obtain the global statistics of infractions 19 | * 20 | * @return the global statistics 21 | */ 22 | @NotNull Statistics getStatistics(); 23 | } -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/InfractionCount.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api; 2 | 3 | import io.github._4drian3d.chatregulator.api.enums.InfractionType; 4 | import org.checkerframework.checker.index.qual.NonNegative; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import java.util.EnumMap; 8 | import java.util.Objects; 9 | 10 | /** 11 | * Record of Infractions by a player 12 | */ 13 | public final class InfractionCount { 14 | private final EnumMap infractionMap = new EnumMap<>(InfractionType.class); 15 | 16 | /** 17 | * Adds an infraction to the count of any type of player infraction. 18 | * @param type the infraction type 19 | */ 20 | public void addViolation(final @NotNull InfractionType type) { 21 | if (type == InfractionType.GLOBAL) { 22 | throw new IllegalArgumentException("Invalid InfractionType provided"); 23 | } 24 | infractionMap.merge(type, 1, Integer::sum); 25 | } 26 | 27 | /** 28 | * Sets the new number of infractions of some kind that the player will have. 29 | * @param type the type of infraction 30 | * @param newViolationsCount the new number of infractions 31 | */ 32 | public void setViolations(final @NotNull InfractionType type, final int newViolationsCount) { 33 | if (type == InfractionType.GLOBAL) { 34 | throw new IllegalArgumentException("Invalid InfractionType provided"); 35 | } 36 | infractionMap.put(type, newViolationsCount); 37 | } 38 | 39 | /** 40 | * Reset the count of infraction of any type of this player 41 | * @param types the types 42 | */ 43 | public void resetViolations(final @NotNull InfractionType @NotNull... types) { 44 | for (final InfractionType type : types) { 45 | if (type == InfractionType.GLOBAL) { 46 | infractionMap.clear(); 47 | return; 48 | } 49 | this.setViolations(type, 0); 50 | } 51 | } 52 | 53 | /** 54 | * Get the amount of violations of any type 55 | * @param type the violation type 56 | * @return the count 57 | */ 58 | public @NonNegative int getCount(final @NotNull InfractionType type) { 59 | if (type == InfractionType.GLOBAL) { 60 | int count = 0; 61 | for (final int infraction : infractionMap.values()) { 62 | count += infraction; 63 | } 64 | return count; 65 | } 66 | return infractionMap.computeIfAbsent(type, $ -> 0); 67 | } 68 | 69 | @Override 70 | public boolean equals(final Object o){ 71 | if (this==o) return true; 72 | if (!(o instanceof final InfractionCount that)) return false; 73 | return Objects.equals(this.infractionMap, that.infractionMap); 74 | } 75 | 76 | @Override 77 | public int hashCode(){ 78 | return Objects.hash(this.infractionMap); 79 | } 80 | 81 | @Override 82 | public String toString() { 83 | final StringBuilder builder = new StringBuilder("ViolationCount["); 84 | infractionMap.forEach(((infractionType, integer) -> builder.append(infractionType).append('=').append(integer))); 85 | return builder.append("]").toString(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/InfractionPlayer.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api; 2 | 3 | import io.github._4drian3d.chatregulator.api.enums.SourceType; 4 | import net.kyori.adventure.audience.ForwardingAudience; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | /** 8 | * A player to whom the necessary warnings and variables can 9 | * be assigned in order to be sanctioned correctly. 10 | */ 11 | public interface InfractionPlayer extends ForwardingAudience.Single { 12 | /** 13 | * A simple method to obtain the player's name 14 | * @return infraction player name 15 | */ 16 | @NotNull String username(); 17 | 18 | /** 19 | * Returns the online status of the player 20 | * @return player online status 21 | */ 22 | boolean isOnline(); 23 | 24 | /** 25 | * Commands and Messages Execution Chain from a player 26 | * 27 | * @param sourceType the source of this chain 28 | * @return the command or chat chain of this player 29 | */ 30 | @NotNull StringChain getChain(final @NotNull SourceType sourceType); 31 | 32 | /** 33 | * Get the infractions count of the player 34 | * @return the infractions count 35 | * @since 2.0.0 36 | */ 37 | @NotNull InfractionCount getInfractions(); 38 | } 39 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/PlayerManager.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.util.UUID; 6 | 7 | /** 8 | * ChatRegulator's Player Manager 9 | */ 10 | public interface PlayerManager { 11 | /** 12 | * Obtain a player based on their UUID 13 | * 14 | * @param uuid the player UUID 15 | * @return a InfractionPlayer 16 | * @throws java.util.NoSuchElementException if the player with the provided UUID is not online or not cached 17 | */ 18 | @NotNull InfractionPlayer getPlayer(final @NotNull UUID uuid); 19 | } 20 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/Statistics.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api; 2 | 3 | import io.github._4drian3d.chatregulator.api.enums.InfractionType; 4 | import org.checkerframework.checker.index.qual.NonNegative; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | /** 8 | * Global Plugin Statistics 9 | */ 10 | public interface Statistics { 11 | /** 12 | * Obtain the number of infractions of some type 13 | * 14 | * @param type the infraction type 15 | * @return count of the respective infraction type 16 | */ 17 | @NonNegative int getInfractionCount(final @NotNull InfractionType type); 18 | } 19 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/StringChain.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api; 2 | 3 | import org.checkerframework.checker.index.qual.NonNegative; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.time.Instant; 7 | 8 | /** 9 | * Commands and Messages Execution Chain from a player 10 | */ 11 | public interface StringChain extends Iterable<@NotNull String> { 12 | @Deprecated 13 | @NotNull String index(final @NonNegative int index); 14 | 15 | @NotNull String first(); 16 | 17 | @NotNull String last(); 18 | 19 | @NotNull Instant lastExecuted(); 20 | 21 | @NonNegative int size(); 22 | } 23 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/annotations/Required.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api.annotations; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Target; 6 | 7 | /** 8 | * If a method contains this annotation, 9 | * it means that it must be invoked before any future execution. 10 | * 11 | *

If used in builder context, 12 | * it is required to invoke this method, 13 | * otherwise, when trying to generate a new instance from the builder, 14 | * it will throw an exception.

15 | */ 16 | @Target(ElementType.METHOD) 17 | @Documented 18 | public @interface Required { 19 | } 20 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/annotations/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Documentation Annotations 3 | */ 4 | package io.github._4drian3d.chatregulator.api.annotations; -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/checks/CapsCheck.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api.checks; 2 | 3 | import io.github._4drian3d.chatregulator.api.InfractionPlayer; 4 | import io.github._4drian3d.chatregulator.api.enums.CapsAlgorithm; 5 | import io.github._4drian3d.chatregulator.api.enums.ControlType; 6 | import io.github._4drian3d.chatregulator.api.enums.InfractionType; 7 | import io.github._4drian3d.chatregulator.api.result.CheckResult; 8 | import net.kyori.adventure.builder.AbstractBuilder; 9 | import org.checkerframework.checker.index.qual.NonNegative; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import java.util.Locale; 13 | 14 | import static java.util.Objects.requireNonNull; 15 | 16 | /** 17 | * Check for compliance with uppercase character limit in a string 18 | */ 19 | public final class CapsCheck implements Check { 20 | private final int limit; 21 | private final ControlType controlType; 22 | private final CapsAlgorithm algorithm; 23 | 24 | private CapsCheck(int limit, ControlType controlType, CapsAlgorithm algorithm) { 25 | this.limit = limit; 26 | this.controlType = controlType; 27 | this.algorithm = algorithm; 28 | } 29 | 30 | @Override 31 | public @NotNull CheckResult check(@NotNull InfractionPlayer player, @NotNull String string) { 32 | final long caps = requireNonNull(string) 33 | .chars() 34 | .filter(Character::isUpperCase) 35 | .count(); 36 | final boolean surpassedLimit = switch (algorithm) { 37 | case AMOUNT -> caps >= this.limit; 38 | case PERCENTAGE -> { 39 | final double length = string.length(); 40 | final double percentageLimit = (length / 100) * this.limit; 41 | yield caps >= percentageLimit; 42 | } 43 | }; 44 | 45 | if (surpassedLimit) { 46 | if (controlType == ControlType.REPLACE) { 47 | return CheckResult.modified(type(), string.toLowerCase(Locale.ROOT)); 48 | } else { 49 | return CheckResult.denied(type()); 50 | } 51 | } else { 52 | return CheckResult.allowed(); 53 | } 54 | } 55 | 56 | @Override 57 | public @NotNull InfractionType type() { 58 | return InfractionType.CAPS; 59 | } 60 | 61 | /** 62 | * Creates a new builder 63 | * 64 | * @return a new CapsCheck Builder 65 | */ 66 | public static CapsCheck.Builder builder(){ 67 | return new CapsCheck.Builder(); 68 | } 69 | 70 | /**Caps Check Builder */ 71 | public static class Builder implements AbstractBuilder { 72 | private int limit; 73 | private ControlType controlType = ControlType.BLOCK; 74 | private CapsAlgorithm algorithm = CapsAlgorithm.AMOUNT; 75 | Builder() {} 76 | 77 | /** 78 | * Set the new caps limit 79 | * 80 | * @param limit the new limit 81 | * @return this builder 82 | */ 83 | public Builder limit(final @NonNegative int limit) { 84 | this.limit = limit; 85 | return this; 86 | } 87 | 88 | /** 89 | * Sets the ControlType of this check 90 | *

If no ControlType is provided, the ControlType.BLOCK will be used

91 | * 92 | * @param controlType the control type 93 | * @return this builder 94 | */ 95 | public Builder controlType(final @NotNull ControlType controlType) { 96 | this.controlType = requireNonNull(controlType); 97 | return this; 98 | } 99 | 100 | /** 101 | * Sets the Caps algorithm of this check 102 | *

If no Algorithm is provided, the CapsAlgorithm.AMOUNT will be used

103 | * 104 | * @param algorithm the algorithm to be used 105 | * @return this builder 106 | */ 107 | public Builder algorithm(final @NotNull CapsAlgorithm algorithm) { 108 | this.algorithm = requireNonNull(algorithm); 109 | return this; 110 | } 111 | 112 | @Override 113 | public @NotNull CapsCheck build(){ 114 | return new CapsCheck(limit, controlType, algorithm); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/checks/Check.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api.checks; 2 | 3 | import io.github._4drian3d.chatregulator.api.InfractionPlayer; 4 | import io.github._4drian3d.chatregulator.api.enums.InfractionType; 5 | import io.github._4drian3d.chatregulator.api.result.CheckResult; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | /** 9 | * Base class of the checks used in the plugin 10 | */ 11 | public sealed interface Check permits CapsCheck, CommandCheck, CooldownCheck, FloodCheck, RegexCheck, SpamCheck, SyntaxCheck, UnicodeCheck { 12 | /** 13 | * Check if the provided string contains any infraction 14 | * and returns the corresponding CheckResult 15 | * 16 | * @param string the string to check 17 | * @param player the player 18 | * @see CheckResult 19 | * @since 3.0.0 20 | * @return a result from the check 21 | */ 22 | @NotNull CheckResult check(final @NotNull InfractionPlayer player, final @NotNull String string); 23 | 24 | /** 25 | * Get the {@link InfractionType} of this check 26 | * @return the infraction type 27 | */ 28 | @NotNull InfractionType type(); 29 | } 30 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/checks/CommandCheck.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api.checks; 2 | 3 | import io.github._4drian3d.chatregulator.api.InfractionPlayer; 4 | import io.github._4drian3d.chatregulator.api.enums.InfractionType; 5 | import io.github._4drian3d.chatregulator.api.result.CheckResult; 6 | import io.github._4drian3d.chatregulator.api.utils.Commands; 7 | import net.kyori.adventure.builder.AbstractBuilder; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import java.util.Arrays; 11 | import java.util.Collection; 12 | import java.util.Collections; 13 | import java.util.HashSet; 14 | 15 | /** 16 | * Check for verification of executed commands 17 | */ 18 | public final class CommandCheck implements Check { 19 | private final Collection blockedCommands; 20 | 21 | private CommandCheck(Collection blockedCommands){ 22 | this.blockedCommands = blockedCommands; 23 | } 24 | 25 | @Override 26 | public @NotNull CheckResult check(@NotNull InfractionPlayer player, @NotNull String string) { 27 | for (final String blockedCommand : blockedCommands){ 28 | if (Commands.isStartingString(string, blockedCommand)) { 29 | return CheckResult.denied(type()); 30 | } 31 | } 32 | return CheckResult.allowed(); 33 | } 34 | 35 | @Override 36 | public @NotNull InfractionType type() { 37 | return InfractionType.BLOCKED_COMMAND; 38 | } 39 | 40 | /** 41 | * Creates a new builder 42 | * 43 | * @return a new CommandCheck Builder 44 | */ 45 | public static CommandCheck.Builder builder(){ 46 | return new CommandCheck.Builder(); 47 | } 48 | 49 | /**Command Check Builder */ 50 | public static class Builder implements AbstractBuilder { 51 | private Collection blockedCommands; 52 | 53 | private Builder(){} 54 | 55 | /** 56 | * Set the blocked commands 57 | * @param blockedCommands the blocked commands 58 | * @return this 59 | */ 60 | public Builder blockedCommands(final @NotNull Collection<@NotNull String> blockedCommands){ 61 | this.blockedCommands = blockedCommands; 62 | return this; 63 | } 64 | 65 | /** 66 | * Set the blocked commands 67 | * @param blockedCommands the blocked commands 68 | * @return this 69 | */ 70 | public Builder blockedCommands(final @NotNull String @NotNull ... blockedCommands){ 71 | if (this.blockedCommands == null) { 72 | this.blockedCommands = new HashSet<>(Arrays.asList(blockedCommands)); 73 | } else { 74 | Collections.addAll(this.blockedCommands, blockedCommands); 75 | } 76 | 77 | return this; 78 | } 79 | 80 | /** 81 | * Adds a command to the blocked commands 82 | * @param command the command to add 83 | * @return this 84 | */ 85 | public Builder blockedCommand(@NotNull String command){ 86 | if (this.blockedCommands == null) this.blockedCommands = new HashSet<>(); 87 | 88 | this.blockedCommands.add(command); 89 | return this; 90 | } 91 | 92 | /** 93 | * Build a new CommandCheck with the Builder values 94 | * @return a new CommandCheck 95 | */ 96 | @Override 97 | public @NotNull CommandCheck build(){ 98 | return new CommandCheck(blockedCommands == null ? Collections.emptyList() : blockedCommands); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/checks/CooldownCheck.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api.checks; 2 | 3 | import io.github._4drian3d.chatregulator.api.InfractionPlayer; 4 | import io.github._4drian3d.chatregulator.api.annotations.Required; 5 | import io.github._4drian3d.chatregulator.api.enums.InfractionType; 6 | import io.github._4drian3d.chatregulator.api.enums.SourceType; 7 | import io.github._4drian3d.chatregulator.api.result.CheckResult; 8 | import net.kyori.adventure.builder.AbstractBuilder; 9 | import org.checkerframework.checker.index.qual.NonNegative; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import java.time.Duration; 13 | import java.time.Instant; 14 | import java.util.concurrent.TimeUnit; 15 | 16 | public final class CooldownCheck implements Check { 17 | private final TimeUnit unit; 18 | private final long limit; 19 | private final SourceType sourceType; 20 | 21 | private CooldownCheck(final TimeUnit unit, final long limit, final SourceType sourceType) { 22 | this.limit = limit; 23 | this.unit = unit; 24 | this.sourceType = sourceType; 25 | } 26 | 27 | @Override 28 | public @NotNull CheckResult check(@NotNull InfractionPlayer player, @NotNull String string) { 29 | final Instant lastExecuted = player.getChain(sourceType).lastExecuted(); 30 | if (Duration.between(lastExecuted, Instant.now()).toMillis() < unit.toMillis(limit)) { 31 | return CheckResult.denied(type()); 32 | } 33 | return CheckResult.allowed(); 34 | } 35 | 36 | @Override 37 | public @NotNull InfractionType type() { 38 | return InfractionType.COOLDOWN; 39 | } 40 | 41 | /** 42 | * Creates a new Builder 43 | * 44 | * @return a new CooldownCheck Builder 45 | */ 46 | public static Builder builder() { 47 | return new Builder(); 48 | } 49 | 50 | /** 51 | * Cooldown Check Builder 52 | */ 53 | public static class Builder implements AbstractBuilder { 54 | private TimeUnit unit; 55 | private long limit; 56 | private SourceType source; 57 | 58 | private Builder() {} 59 | 60 | @Required 61 | public Builder timeUnit(final @NotNull TimeUnit unit) { 62 | this.unit = unit; 63 | return this; 64 | } 65 | 66 | public Builder limit(final @NonNegative long limit) { 67 | this.limit = limit; 68 | return this; 69 | } 70 | 71 | @Required 72 | public Builder source(final @NotNull SourceType source) { 73 | this.source = source; 74 | return this; 75 | } 76 | 77 | @Override 78 | public @NotNull CooldownCheck build() { 79 | return new CooldownCheck(unit, limit, source); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/checks/FloodCheck.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api.checks; 2 | 3 | import com.github.benmanes.caffeine.cache.Caffeine; 4 | import com.github.benmanes.caffeine.cache.LoadingCache; 5 | import io.github._4drian3d.chatregulator.api.InfractionPlayer; 6 | import io.github._4drian3d.chatregulator.api.enums.ControlType; 7 | import io.github._4drian3d.chatregulator.api.enums.InfractionType; 8 | import io.github._4drian3d.chatregulator.api.result.CheckResult; 9 | import net.kyori.adventure.builder.AbstractBuilder; 10 | import org.checkerframework.checker.index.qual.NonNegative; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | import java.util.regex.Matcher; 14 | import java.util.regex.Pattern; 15 | 16 | import static java.util.Objects.requireNonNull; 17 | 18 | /** 19 | * Check to detect incoherent messages containing floods 20 | */ 21 | public final class FloodCheck implements Check { 22 | private static final LoadingCache floodPatternCache = Caffeine.newBuilder() 23 | .maximumSize(3) 24 | .initialCapacity(1) 25 | .build(length -> Pattern.compile( 26 | "(.)\\1{"+length+",}", 27 | Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE) 28 | ); 29 | private final Pattern pattern; 30 | private final ControlType controlType; 31 | 32 | private FloodCheck(final Pattern pattern, final ControlType controlType) { 33 | this.pattern = pattern; 34 | this.controlType = controlType; 35 | } 36 | 37 | @Override 38 | public @NotNull CheckResult check(@NotNull InfractionPlayer player, @NotNull String string) { 39 | final Matcher matcher = pattern.matcher(requireNonNull(string)); 40 | 41 | if (matcher.find()) { 42 | if (controlType == ControlType.BLOCK) { 43 | return CheckResult.denied(type()); 44 | } else { 45 | return CheckResult.modified(type(), matcher.replaceAll(match -> String.valueOf(match.group().charAt(0)))); 46 | } 47 | } 48 | return CheckResult.allowed(); 49 | } 50 | 51 | 52 | @Override 53 | public @NotNull InfractionType type() { 54 | return InfractionType.FLOOD; 55 | } 56 | 57 | /** 58 | * Creates a new Builder 59 | * 60 | * @return a new FloodCheck Builder 61 | */ 62 | public static FloodCheck.Builder builder(){ 63 | return new FloodCheck.Builder(); 64 | } 65 | 66 | /** Flood Check Builder */ 67 | public static class Builder implements AbstractBuilder { 68 | private Pattern pattern; 69 | private ControlType controlType; 70 | 71 | Builder() {} 72 | 73 | public Builder limit(@NonNegative int limit){ 74 | this.pattern = floodPatternCache.get(limit); 75 | return this; 76 | } 77 | 78 | public Builder controlType(final @NotNull ControlType controlType) { 79 | this.controlType = controlType; 80 | return this; 81 | } 82 | 83 | @Override 84 | public @NotNull FloodCheck build(){ 85 | requireNonNull(pattern); 86 | requireNonNull(controlType); 87 | return new FloodCheck(this.pattern, this.controlType); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/checks/RegexCheck.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api.checks; 2 | 3 | import io.github._4drian3d.chatregulator.api.InfractionPlayer; 4 | import io.github._4drian3d.chatregulator.api.annotations.Required; 5 | import io.github._4drian3d.chatregulator.api.enums.ControlType; 6 | import io.github._4drian3d.chatregulator.api.enums.InfractionType; 7 | import io.github._4drian3d.chatregulator.api.result.*; 8 | import net.kyori.adventure.builder.AbstractBuilder; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | import java.util.*; 12 | import java.util.regex.MatchResult; 13 | import java.util.regex.Matcher; 14 | import java.util.regex.Pattern; 15 | 16 | import static java.util.Objects.requireNonNull; 17 | 18 | /** 19 | * Utilities for the detection of restricted words 20 | */ 21 | public final class RegexCheck implements Check { 22 | private final Pattern[] blockedWords; 23 | private final ControlType controlType; 24 | 25 | private RegexCheck(ControlType controlType, Pattern... blockedWords) { 26 | this.blockedWords = blockedWords; 27 | this.controlType = controlType; 28 | } 29 | 30 | @Override 31 | public @NotNull CheckResult check(final @NotNull InfractionPlayer player, final @NotNull String string) { 32 | final List patterns = new ArrayList<>(); 33 | for (final Pattern pattern : blockedWords) { 34 | final Matcher match = pattern.matcher(string); 35 | if (match.find()) { 36 | if (controlType == ControlType.BLOCK) { 37 | return CheckResult.denied(type()); 38 | } 39 | patterns.add(pattern); 40 | } 41 | } 42 | 43 | if (patterns.size() != 0) { 44 | String replaced = string; 45 | for (final Pattern pattern : patterns) { 46 | replaced = pattern.matcher(replaced).replaceAll(RegexCheck::generateReplacement); 47 | } 48 | return CheckResult.modified(type(), replaced); 49 | } else { 50 | return CheckResult.allowed(); 51 | } 52 | } 53 | 54 | public static String generateReplacement(final MatchResult result) { 55 | final int size = result.group().length() / 2; 56 | return "*".repeat(size); 57 | } 58 | 59 | @Override 60 | public @NotNull InfractionType type() { 61 | return InfractionType.REGEX; 62 | } 63 | 64 | /** 65 | * Creates a new Builder 66 | * 67 | * @return a new RegexCheck Builder 68 | */ 69 | public static @NotNull Builder builder() { 70 | return new RegexCheck.Builder(); 71 | } 72 | 73 | public static class Builder implements AbstractBuilder { 74 | private Collection blockedWords; 75 | private ControlType controlType; 76 | 77 | private Builder() { 78 | } 79 | 80 | public Builder blockedPatterns(final @NotNull Collection<@NotNull Pattern> patterns) { 81 | requireNonNull(patterns); 82 | if (this.blockedWords == null) { 83 | this.blockedWords = new ArrayList<>(patterns); 84 | } else { 85 | this.blockedWords.addAll(patterns); 86 | } 87 | return this; 88 | } 89 | 90 | public Builder blockedPatterns(final @NotNull Pattern @NotNull ... patterns) { 91 | if (this.blockedWords == null) { 92 | this.blockedWords = new ArrayList<>(List.of(patterns)); 93 | } else { 94 | Collections.addAll(this.blockedWords, patterns); 95 | } 96 | return this; 97 | } 98 | 99 | @Required 100 | public Builder controlType(final @NotNull ControlType controlType) { 101 | this.controlType = requireNonNull(controlType); 102 | return this; 103 | } 104 | 105 | @Override 106 | public @NotNull RegexCheck build() { 107 | if (this.blockedWords == null) { 108 | this.blockedWords = Collections.emptySet(); 109 | } 110 | requireNonNull(controlType); 111 | return new RegexCheck(controlType, blockedWords.toArray(new Pattern[0])); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/checks/SpamCheck.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api.checks; 2 | 3 | import io.github._4drian3d.chatregulator.api.InfractionPlayer; 4 | import io.github._4drian3d.chatregulator.api.StringChain; 5 | import io.github._4drian3d.chatregulator.api.annotations.Required; 6 | import io.github._4drian3d.chatregulator.api.enums.InfractionType; 7 | import io.github._4drian3d.chatregulator.api.enums.SourceType; 8 | import io.github._4drian3d.chatregulator.api.result.CheckResult; 9 | import net.kyori.adventure.builder.AbstractBuilder; 10 | import org.jetbrains.annotations.NotNull; 11 | import org.jetbrains.annotations.Range; 12 | 13 | import java.util.Iterator; 14 | 15 | import static java.util.Objects.requireNonNull; 16 | 17 | /** 18 | * Detection of command/message spamming 19 | */ 20 | public final class SpamCheck implements Check { 21 | private final SourceType type; 22 | private final int similarLimit; 23 | 24 | private SpamCheck(final @NotNull SourceType type, int similarLimit) { 25 | this.type = requireNonNull(type); 26 | this.similarLimit = similarLimit; 27 | } 28 | 29 | @Override 30 | public @NotNull CheckResult check(final @NotNull InfractionPlayer player, final @NotNull String string) { 31 | final StringChain chain = player.getChain(type); 32 | final int size = chain.size(); 33 | if (size < similarLimit) { 34 | return CheckResult.allowed(); 35 | } 36 | 37 | final Iterator it = chain.iterator(); 38 | String actual; 39 | String previous = null; 40 | while(it.hasNext()) { 41 | actual = it.next(); 42 | if (previous != null && !actual.equalsIgnoreCase(previous)) { 43 | return CheckResult.allowed(); 44 | } 45 | previous = actual; 46 | } 47 | 48 | if (chain.last().equalsIgnoreCase(string)) { 49 | return CheckResult.denied(type()); 50 | } else { 51 | return CheckResult.allowed(); 52 | } 53 | } 54 | 55 | @Override 56 | public @NotNull InfractionType type() { 57 | return InfractionType.SPAM; 58 | } 59 | 60 | /** 61 | * Creates a new Builder 62 | * 63 | * @return a new SpamCheck Builder 64 | */ 65 | public static Builder builder() { 66 | return new Builder(); 67 | } 68 | 69 | /** 70 | * Spam Check builder 71 | */ 72 | public static final class Builder implements AbstractBuilder { 73 | private SourceType source; 74 | private int similarLimit; 75 | 76 | private Builder() {} 77 | 78 | @Required 79 | public Builder source(final @NotNull SourceType source){ 80 | this.source = source; 81 | return this; 82 | } 83 | 84 | @Required 85 | public Builder similarLimit(final @Range(from = 2, to = 255) int limit) { 86 | this.similarLimit = limit; 87 | return this; 88 | } 89 | 90 | @Override 91 | public @NotNull SpamCheck build(){ 92 | requireNonNull(source); 93 | return new SpamCheck(source, Math.max(2, similarLimit)); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/checks/SyntaxCheck.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api.checks; 2 | 3 | import io.github._4drian3d.chatregulator.api.InfractionPlayer; 4 | import io.github._4drian3d.chatregulator.api.enums.InfractionType; 5 | import io.github._4drian3d.chatregulator.api.result.CheckResult; 6 | import io.github._4drian3d.chatregulator.api.utils.Commands; 7 | import net.kyori.adventure.builder.AbstractBuilder; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import java.util.Collection; 11 | import java.util.HashSet; 12 | import java.util.Set; 13 | 14 | import static java.util.Objects.requireNonNull; 15 | 16 | public final class SyntaxCheck implements Check { 17 | private final Collection allowedCommands; 18 | 19 | SyntaxCheck(Collection allowedCommands) { 20 | this.allowedCommands = allowedCommands; 21 | } 22 | 23 | @Override 24 | public @NotNull CheckResult check(@NotNull InfractionPlayer player, @NotNull String string) { 25 | final String command = Commands.getFirstArgument(requireNonNull(string)); 26 | final int index = command.indexOf(':'); 27 | if (index == -1 || allowedCommands.contains(command.substring(0, index))) { 28 | return CheckResult.allowed(); 29 | } 30 | 31 | return CheckResult.denied(type()); 32 | } 33 | 34 | @Override 35 | public @NotNull InfractionType type() { 36 | return InfractionType.SYNTAX; 37 | } 38 | 39 | /** 40 | * Creates a new Builder 41 | * 42 | * @return a new SyntaxCheck Builder 43 | */ 44 | public static Builder builder(){ 45 | return new Builder(); 46 | } 47 | 48 | /** 49 | * Syntax Check Builder 50 | */ 51 | public static class Builder implements AbstractBuilder { 52 | private final Set allowedCommands = new HashSet<>(); 53 | 54 | private Builder() {} 55 | 56 | public Builder allowedCommands(final @NotNull Collection<@NotNull String> commands){ 57 | this.allowedCommands.addAll(commands); 58 | return this; 59 | } 60 | 61 | @Override 62 | public @NotNull SyntaxCheck build(){ 63 | return new SyntaxCheck(allowedCommands); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/checks/UnicodeCheck.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api.checks; 2 | 3 | import io.github._4drian3d.chatregulator.api.InfractionPlayer; 4 | import io.github._4drian3d.chatregulator.api.annotations.Required; 5 | import io.github._4drian3d.chatregulator.api.enums.ControlType; 6 | import io.github._4drian3d.chatregulator.api.enums.DetectionMode; 7 | import io.github._4drian3d.chatregulator.api.enums.InfractionType; 8 | import io.github._4drian3d.chatregulator.api.result.CheckResult; 9 | import net.kyori.adventure.builder.AbstractBuilder; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import java.util.HashSet; 13 | import java.util.Set; 14 | import java.util.function.Predicate; 15 | 16 | import static io.github._4drian3d.chatregulator.api.utils.Commands.SPACE; 17 | import static java.util.Objects.requireNonNull; 18 | 19 | /** 20 | * Check for invalid characters 21 | */ 22 | public final class UnicodeCheck implements Check { 23 | private final char[] chars; 24 | private final ControlType control; 25 | private final Predicate charPredicate; 26 | 27 | private UnicodeCheck(char[] chars, ControlType control, DetectionMode mode) { 28 | this.chars = chars; 29 | this.control = control; 30 | if (chars == null) { 31 | this.charPredicate = UnicodeCheck::defaultCharTest; 32 | } else { 33 | this.charPredicate = (mode == DetectionMode.BLACKLIST) 34 | ? c -> defaultCharTest(c) || charTest(c) 35 | : c -> defaultCharTest(c) && !charTest(c); 36 | } 37 | } 38 | 39 | public static boolean defaultCharTest(char c) { 40 | if (c <= '¿') { 41 | return false; 42 | } 43 | return !(c <= 'þ'); 44 | } 45 | 46 | private boolean charTest(final char c) { 47 | for (final char character : this.chars) { 48 | if (character == c) { 49 | return true; 50 | } 51 | } 52 | return false; 53 | } 54 | 55 | @Override 56 | public @NotNull CheckResult check(@NotNull InfractionPlayer player, final @NotNull String string) { 57 | final char[] charArray = requireNonNull(string).toCharArray(); 58 | final Set results = new HashSet<>(charArray.length); 59 | 60 | for (final char character : charArray) { 61 | if (charPredicate.test(character)) { 62 | if (control == ControlType.BLOCK) { 63 | return CheckResult.denied(type()); 64 | } 65 | results.add(character); 66 | } 67 | } 68 | 69 | if (results.isEmpty()) { 70 | return CheckResult.allowed(); 71 | } else { 72 | String replaced = string; 73 | for (final char character : results) { 74 | replaced = replaced.replace(character, SPACE); 75 | } 76 | return CheckResult.modified(type(), replaced); 77 | } 78 | } 79 | 80 | @Override 81 | public @NotNull InfractionType type() { 82 | return InfractionType.UNICODE; 83 | } 84 | 85 | /** 86 | * Creates a new Builder 87 | * 88 | * @return a new UnicodeCheck Builder 89 | */ 90 | public static UnicodeCheck.Builder builder() { 91 | return new UnicodeCheck.Builder(); 92 | } 93 | 94 | /** 95 | * Unicode Check Builder 96 | */ 97 | public static class Builder implements AbstractBuilder { 98 | private char[] chars; 99 | private ControlType control = ControlType.REPLACE; 100 | private DetectionMode mode = DetectionMode.BLACKLIST; 101 | 102 | private Builder() { 103 | } 104 | 105 | /** 106 | * Set the blocked characters 107 | * 108 | * @param chars the characters 109 | * @return this 110 | */ 111 | public Builder characters(final char @NotNull ... chars) { 112 | this.chars = chars; 113 | return this; 114 | } 115 | 116 | /** 117 | * Set if the check can replace the infraction 118 | * 119 | * @param control the control type 120 | * @return this 121 | */ 122 | @Required 123 | public Builder controlType(final @NotNull ControlType control) { 124 | this.control = control; 125 | return this; 126 | } 127 | 128 | @Required 129 | public Builder detectionMode(final @NotNull DetectionMode mode) { 130 | this.mode = mode; 131 | return this; 132 | } 133 | 134 | @Override 135 | public @NotNull UnicodeCheck build() { 136 | requireNonNull(control); 137 | return new UnicodeCheck(chars, control, mode); 138 | } 139 | 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/checks/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * ChatRegulator checks 3 | */ 4 | package io.github._4drian3d.chatregulator.api.checks; 5 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/enums/CapsAlgorithm.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api.enums; 2 | 3 | /** 4 | * Caps Check Detection Algorithm 5 | */ 6 | public enum CapsAlgorithm { 7 | /** 8 | * This algorithm will check if there is a higher percentage of uppercase characters 9 | * in the provided string than the maximum indicated 10 | */ 11 | PERCENTAGE, 12 | /** 13 | * This algorithm will check if there are more uppercase characters 14 | * in the provided string than the maximum indicated 15 | */ 16 | AMOUNT 17 | } 18 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/enums/ControlType.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api.enums; 2 | 3 | /** 4 | * The type of control to be performed on a player 5 | * according to the check performed and configuration 6 | */ 7 | public enum ControlType { 8 | /** 9 | * Block the entire message 10 | */ 11 | BLOCK, 12 | /** 13 | * Replace the infraction 14 | */ 15 | REPLACE 16 | } 17 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/enums/DetectionMode.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api.enums; 2 | 3 | /** 4 | * UnicodeCheck's Detection Mode 5 | */ 6 | public enum DetectionMode { 7 | WHITELIST, 8 | BLACKLIST 9 | } 10 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/enums/InfractionType.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api.enums; 2 | 3 | import net.kyori.adventure.util.Index; 4 | 5 | public enum InfractionType { 6 | REGEX(Permission.BYPASS_REGEX), 7 | /** 8 | * Represents an infraction for repeating 9 | * the same character several times in a row. 10 | */ 11 | FLOOD(Permission.BYPASS_FLOOD), 12 | /** 13 | * Represents an infraction for repeating 14 | * the same word or command several times. 15 | */ 16 | SPAM(Permission.BYPASS_SPAM), 17 | COOLDOWN(Permission.BYPASS_COOLDOWN), 18 | /** 19 | * Represents a blocked command 20 | */ 21 | BLOCKED_COMMAND(Permission.BYPASS_BLOCKED_COMMAND), 22 | /** 23 | * Represents a Unicode check 24 | */ 25 | UNICODE(Permission.BYPASS_UNICODE), 26 | /** 27 | * Represents a Caps limit check 28 | */ 29 | CAPS(Permission.BYPASS_CAPS), 30 | /** 31 | * Represents a Syntax check 32 | *

/minecraft:tp 33 | */ 34 | SYNTAX(Permission.BYPASS_SYNTAX), 35 | /** 36 | * Used to represent all infractions 37 | */ 38 | GLOBAL(Permission.NO_PERMISSION); 39 | 40 | public static final Index INDEX = Index.create(InfractionType::toString, values()); 41 | 42 | private final Permission bypassPermission; 43 | 44 | InfractionType(Permission permission){ 45 | this.bypassPermission = permission; 46 | } 47 | 48 | public Permission getBypassPermission() { 49 | return this.bypassPermission; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/enums/Permission.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api.enums; 2 | 3 | import net.kyori.adventure.audience.Audience; 4 | import net.kyori.adventure.permission.PermissionChecker; 5 | 6 | import java.util.function.Predicate; 7 | 8 | /** 9 | * ChatRegulator Permissions 10 | */ 11 | public enum Permission implements Predicate { 12 | /**General Command */ 13 | COMMAND("chatregulator.command"), 14 | 15 | /**Stats Subcommand */ 16 | COMMAND_STATS("chatregulator.command.stats"), 17 | /**Player Subcommand */ 18 | COMMAND_PLAYER("chatregulator.command.player"), 19 | /**Reload Subcommand */ 20 | COMMAND_RELOAD("chatregulator.command.reload"), 21 | 22 | /**Clear Subcommand */ 23 | COMMAND_CLEAR("chatregulator.command.clear"), 24 | /**Server ChatClear Subcommand */ 25 | COMMAND_CLEAR_SERVER("chatregulator.command.clear.server"), 26 | /**Player ChatClear Subcommand */ 27 | COMMAND_CLEAR_PLAYER("chatregulator.command.clear.player"), 28 | 29 | /**Reset Subcommand */ 30 | COMMAND_RESET("chatregulator.command.reset"), 31 | /**Regular Reset Subcommand */ 32 | COMMAND_RESET_REGEX("chatregulator.command.reset.regex"), 33 | /**Flood Reset Subcommand */ 34 | COMMAND_RESET_FLOOD("chatregulator.command.reset.flood"), 35 | /**Spam Reset Subcommand */ 36 | COMMAND_RESET_SPAM("chatregulator.command.reset.spam"), 37 | /**Command Reset Subcommand */ 38 | COMMAND_RESET_BLOCKEDCOMMAND("chatregulator.command.reset.command"), 39 | /**Unicode Reset Subcommand */ 40 | COMMAND_RESET_UNICODE("chatregulator.command.reset.unicode"), 41 | /**Caps Reset Subcommand */ 42 | COMMAND_RESET_CAPS("chatregulator.command.reset.caps"), 43 | /**Syntax Reset Subcommand */ 44 | COMMAND_RESET_SYNTAX("chatregulator.command.reset.syntax"), 45 | 46 | 47 | /**Notifications */ 48 | NOTIFICATIONS("chatregulator.notifications"), 49 | /**CommandSpy alert */ 50 | COMMAND_SPY_ALERT("chatregulator.notifications.commandspy"), 51 | 52 | 53 | /**Infractions Check Bypass */ 54 | BYPASS_REGEX("chatregulator.bypass.infractions"), 55 | /**Flood Check Bypass */ 56 | BYPASS_FLOOD("chatregulator.bypass.flood"), 57 | /**Spam Check Bypass */ 58 | BYPASS_SPAM("chatregulator.bypass.spam"), 59 | BYPASS_COOLDOWN("chatregulator.bypass.cooldown"), 60 | /**Blocked Commands Bypass */ 61 | BYPASS_BLOCKED_COMMAND("chatregulator.bypass.command"), 62 | /**Unicode Check Bypass */ 63 | BYPASS_UNICODE("chatregulator.bypass.unicode"), 64 | /**Caps Check Bypass */ 65 | BYPASS_CAPS("chatregulator.bypass.caps"), 66 | /**Command Spy Bypass */ 67 | BYPASS_COMMAND_SPY("chatregulator.bypass.commandspy"), 68 | /**Syntax Check Bypass */ 69 | BYPASS_SYNTAX("chatregulator.bypass.syntax"), 70 | /** 71 | * No permission 72 | */ 73 | NO_PERMISSION("chatregulator.no-permission"); 74 | 75 | private final String permission; 76 | 77 | Permission(final String permission) { 78 | this.permission = permission; 79 | } 80 | 81 | @Override 82 | public String toString() { 83 | return this.permission; 84 | } 85 | 86 | @Override 87 | public boolean test(final Audience source) { 88 | return source.get(PermissionChecker.POINTER) 89 | .filter(checker -> checker.test(this.permission)) 90 | .isPresent(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/enums/SourceType.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api.enums; 2 | 3 | /** 4 | * The type of detection the infraction has been detected 5 | */ 6 | public enum SourceType { 7 | /** 8 | * Represents a detection performed 9 | * when the player has executed a command. 10 | */ 11 | COMMAND, 12 | /** 13 | * Represents a detection made when the player 14 | * has written a comment in the chat. 15 | */ 16 | CHAT 17 | } 18 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/enums/WarningType.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api.enums; 2 | 3 | /** 4 | * The warning format to be executed 5 | */ 6 | public enum WarningType { 7 | /** 8 | * Title type format. Will send only a subtitle 9 | * if the ";" character is not supplied. 10 | */ 11 | TITLE, 12 | /** 13 | * Actionbar type format. 14 | */ 15 | ACTIONBAR, 16 | /** 17 | * Simple message format 18 | */ 19 | MESSAGE 20 | } 21 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/enums/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * ChatRegulator enums 3 | */ 4 | package io.github._4drian3d.chatregulator.api.enums; 5 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/event/ChatInfractionEvent.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api.event; 2 | 3 | import io.github._4drian3d.chatregulator.api.result.CheckResult; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import io.github._4drian3d.chatregulator.api.InfractionPlayer; 7 | import io.github._4drian3d.chatregulator.api.enums.InfractionType; 8 | 9 | /** 10 | * Event fired when recognizing an infraction in the chat of a player 11 | */ 12 | public final class ChatInfractionEvent extends InfractionEvent { 13 | private final String message; 14 | 15 | /** 16 | * Constructor of a ChatInfractionEvent 17 | * @param infractionPlayer the player who committed the infraction 18 | * @param type the infraction type 19 | * @param detectionResult the detection result 20 | * @param message the chat message in which the violation was found 21 | */ 22 | public ChatInfractionEvent( 23 | final @NotNull InfractionPlayer infractionPlayer, 24 | final @NotNull InfractionType type, 25 | final @NotNull CheckResult detectionResult, 26 | final @NotNull String message 27 | ) { 28 | super(infractionPlayer, type, detectionResult); 29 | this.message = message; 30 | } 31 | 32 | /** 33 | * Get the message from which the infraction was detected 34 | * @return the infraction message 35 | * @since 1.1.0 36 | */ 37 | public @NotNull String getMessage(){ 38 | return this.message; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/event/CommandInfractionEvent.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api.event; 2 | 3 | import io.github._4drian3d.chatregulator.api.result.CheckResult; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import io.github._4drian3d.chatregulator.api.InfractionPlayer; 7 | import io.github._4drian3d.chatregulator.api.enums.InfractionType; 8 | 9 | import static java.util.Objects.requireNonNull; 10 | 11 | /** 12 | * Event fired when recognizing an infraction in a command executed by a player. 13 | */ 14 | public final class CommandInfractionEvent extends InfractionEvent { 15 | private final String command; 16 | 17 | /** 18 | * Constructor of a CommandInfractionEvent 19 | * @param infractionPlayer the player who committed the infraction 20 | * @param type the infraction type 21 | * @param command the executed command in which the violation was found 22 | * @param result the result of the detection 23 | */ 24 | public CommandInfractionEvent( 25 | @NotNull InfractionPlayer infractionPlayer, 26 | @NotNull InfractionType type, 27 | @NotNull CheckResult result, 28 | @NotNull String command 29 | ) { 30 | super(requireNonNull(infractionPlayer), type, requireNonNull(result)); 31 | this.command = command; 32 | } 33 | 34 | /** 35 | * Get the command from which the infraction was detected 36 | * @return the infraction command 37 | * @since 1.1.0 38 | */ 39 | public @NotNull String getCommand(){ 40 | return this.command; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/event/InfractionEvent.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api.event; 2 | 3 | import io.github._4drian3d.chatregulator.api.InfractionPlayer; 4 | import io.github._4drian3d.chatregulator.api.enums.InfractionType; 5 | import io.github._4drian3d.chatregulator.api.result.CheckResult; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | /** 9 | * Basis for infringement events 10 | */ 11 | public sealed abstract class InfractionEvent permits ChatInfractionEvent, CommandInfractionEvent { 12 | /** 13 | * InfractionPlayer involved in detection 14 | */ 15 | private final InfractionPlayer infractionPlayer; 16 | /** 17 | * Type of detection 18 | */ 19 | private final InfractionType type; 20 | private final CheckResult detectionResult; 21 | 22 | /** 23 | * InfractionEvent Constructor 24 | * @param infractionPlayer the player who committed the infraction 25 | * @param type the infraction type 26 | * @param detectionResult the result of the detection 27 | */ 28 | protected InfractionEvent( 29 | final @NotNull InfractionPlayer infractionPlayer, 30 | final @NotNull InfractionType type, 31 | final @NotNull CheckResult detectionResult 32 | ) { 33 | this.infractionPlayer = infractionPlayer; 34 | this.type = type; 35 | this.detectionResult = detectionResult; 36 | } 37 | 38 | /** 39 | * Get the InfractionPlayer that has committed the infraction 40 | * @return the player 41 | * @since 1.1.0 42 | */ 43 | public @NotNull InfractionPlayer getInfractor(){ 44 | return this.infractionPlayer; 45 | } 46 | 47 | /** 48 | * Get the type of infraction committed 49 | * @return the infraction committed 50 | * @since 1.1.0 51 | */ 52 | public @NotNull InfractionType getType(){ 53 | return this.type; 54 | } 55 | 56 | /** 57 | * Obtain the detection performed 58 | * With this object, you can get the pattern, 59 | * the detected string and more. 60 | * @return the detection performed 61 | */ 62 | public @NotNull CheckResult getDetectionResult(){ 63 | return this.detectionResult; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/event/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * ChatRegulator Events 3 | */ 4 | package io.github._4drian3d.chatregulator.api.event; 5 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/lazy/CheckProvider.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api.lazy; 2 | 3 | import io.github._4drian3d.chatregulator.api.InfractionPlayer; 4 | import io.github._4drian3d.chatregulator.api.checks.Check; 5 | 6 | public interface CheckProvider { 7 | C provide(InfractionPlayer player); 8 | } 9 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/lazy/LazyDetection.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api.lazy; 2 | 3 | import io.github._4drian3d.chatregulator.api.InfractionPlayer; 4 | import io.github._4drian3d.chatregulator.api.checks.Check; 5 | import io.github._4drian3d.chatregulator.api.enums.InfractionType; 6 | import io.github._4drian3d.chatregulator.api.result.CheckResult; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import java.util.Objects; 10 | import java.util.concurrent.CompletableFuture; 11 | import java.util.concurrent.atomic.AtomicReference; 12 | 13 | public final class LazyDetection { 14 | private final CheckProvider[] checks; 15 | 16 | LazyDetection(final CheckProvider[] checks) { 17 | this.checks = checks; 18 | } 19 | 20 | @SafeVarargs 21 | public static LazyDetection checks(final CheckProvider... checks) { 22 | return new LazyDetection(checks); 23 | } 24 | 25 | public @NotNull CompletableFuture detect(final @NotNull InfractionPlayer player, final @NotNull String string) { 26 | return CompletableFuture.supplyAsync(() -> { 27 | final AtomicReference modifiedString = new AtomicReference<>(); 28 | for (final CheckProvider provider : checks) { 29 | final Check providedCheck = provider.provide(player); 30 | if (providedCheck == null) { 31 | continue; 32 | } 33 | final CheckResult result = providedCheck.check(player, modifiedOrDefault(modifiedString, string)); 34 | if (result.isAllowed()) { 35 | continue; 36 | } 37 | 38 | if (result.isDenied()) { 39 | return result; 40 | } 41 | 42 | if (result instanceof final CheckResult.ReplaceCheckResult replaceCheckResult) { 43 | modifiedString.set(new InfractionDetection(replaceCheckResult.infractionType(), replaceCheckResult.replaced())); 44 | } 45 | } 46 | final InfractionDetection finalResult = modifiedString.get(); 47 | if (finalResult == null) { 48 | return CheckResult.allowed(); 49 | } 50 | if (!Objects.equals(finalResult.modified, string)) { 51 | return CheckResult.modified(finalResult.infractionType, finalResult.modified); 52 | } 53 | return CheckResult.allowed(); 54 | }); 55 | } 56 | 57 | private @NotNull String modifiedOrDefault(final @NotNull AtomicReference reference, final @NotNull String defaultValue) { 58 | final InfractionDetection actualDetection = reference.get(); 59 | if (actualDetection == null) { 60 | return defaultValue; 61 | } 62 | return actualDetection.modified; 63 | } 64 | 65 | private record InfractionDetection(@NotNull InfractionType infractionType, @NotNull String modified) {} 66 | } 67 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * ChatRegulator API 3 | */ 4 | package io.github._4drian3d.chatregulator.api; -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/result/CheckResult.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api.result; 2 | 3 | import io.github._4drian3d.chatregulator.api.enums.InfractionType; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import static java.util.Objects.requireNonNull; 7 | 8 | /** 9 | * Result of any check 10 | */ 11 | public sealed interface CheckResult { 12 | /** 13 | * Successful detection in a check 14 | * 15 | * @param type the InfractionType detected 16 | * @return the result 17 | */ 18 | static @NotNull CheckResult denied(final @NotNull InfractionType type) { 19 | return new DeniedCheckResult(type); 20 | } 21 | 22 | /** 23 | * Allowed result of a check 24 | * 25 | * @return the result 26 | */ 27 | static @NotNull CheckResult allowed() { 28 | return AllowedCheckResult.INSTANCE; 29 | } 30 | 31 | /** 32 | * Successful detection in a check 33 | *

Contrary to a denied result, it must be modified as configured in its creation

34 | * 35 | * @param modifier the modified result 36 | * @return the result 37 | */ 38 | static @NotNull CheckResult modified( final @NotNull InfractionType infractionType, final @NotNull String modifier) { 39 | return new ReplaceCheckResult(requireNonNull(infractionType), requireNonNull(modifier)); 40 | } 41 | 42 | /** 43 | * Check if a check has been unsuccessful and that the detection chain can be followed 44 | * 45 | * @return true if the check was not successful 46 | */ 47 | boolean isAllowed(); 48 | 49 | boolean isDenied(); 50 | 51 | boolean shouldModify(); 52 | 53 | final class AllowedCheckResult implements CheckResult { 54 | private static final AllowedCheckResult INSTANCE = new AllowedCheckResult(); 55 | 56 | private AllowedCheckResult() {} 57 | 58 | @Override 59 | public boolean isAllowed() { 60 | return true; 61 | } 62 | 63 | @Override 64 | public boolean isDenied() { 65 | return false; 66 | } 67 | 68 | @Override 69 | public boolean shouldModify() { 70 | return false; 71 | } 72 | } 73 | 74 | record DeniedCheckResult(InfractionType infractionType) implements CheckResult, DetectedResult { 75 | @Override 76 | public boolean isAllowed() { 77 | return false; 78 | } 79 | 80 | @Override 81 | public boolean isDenied() { 82 | return true; 83 | } 84 | 85 | @Override 86 | public boolean shouldModify() { 87 | return false; 88 | } 89 | } 90 | 91 | record ReplaceCheckResult(InfractionType infractionType, String replaced) implements CheckResult, DetectedResult { 92 | @Override 93 | public boolean isAllowed() { 94 | return false; 95 | } 96 | 97 | @Override 98 | public boolean isDenied() { 99 | return false; 100 | } 101 | 102 | @Override 103 | public boolean shouldModify() { 104 | return true; 105 | } 106 | } 107 | 108 | sealed interface DetectedResult { 109 | InfractionType infractionType(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/result/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Results of a detection 3 | */ 4 | package io.github._4drian3d.chatregulator.api.result; -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/utils/Commands.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api.utils; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.util.Locale; 6 | 7 | import static java.util.Objects.requireNonNull; 8 | 9 | public final class Commands { 10 | public static final char SPACE = ' '; 11 | /** 12 | * Get the first argument of a string 13 | * 14 | * @param string the string 15 | * @return the first argument 16 | */ 17 | public static @NotNull String getFirstArgument(final @NotNull String string) { 18 | final int index = requireNonNull(string).indexOf(SPACE); 19 | if (index == -1) { 20 | return string; 21 | } 22 | return string.substring(0, index); 23 | } 24 | 25 | /** 26 | * Check if a string starts with another string, checking for its arguments 27 | * If the second string has a "*" symbol at the end, 28 | * it will be checked by means of a {@link String#startsWith(String)} 29 | * 30 | * @param string the base string 31 | * @param startingString the starting string 32 | * @return if a string starts with another string 33 | */ 34 | public static boolean isStartingString(@NotNull String string, @NotNull String startingString) { 35 | final int startingStringLength = requireNonNull(startingString).length(); 36 | if (requireNonNull(string).length() < startingStringLength) { 37 | return false; 38 | } 39 | if (string.equalsIgnoreCase(startingString)) { 40 | return true; 41 | } 42 | startingString = startingString.toLowerCase(Locale.ROOT); 43 | string = string.toLowerCase(Locale.ROOT); 44 | return string.startsWith( 45 | getLastChar(startingString) == '*' 46 | ? startingString.substring(0, startingStringLength-1) 47 | : startingString.concat(" ") 48 | ); 49 | } 50 | 51 | /** 52 | * Get the last character of a string 53 | * 54 | * @param string the string 55 | * @return the last character 56 | */ 57 | public static char getLastChar(final @NotNull String string) { 58 | requireNonNull(string); 59 | if (string.isEmpty()) { 60 | return SPACE; 61 | } 62 | return string.charAt(string.length()-1); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/utils/Components.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api.utils; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import net.kyori.adventure.text.TextComponent; 5 | 6 | public final class Components { 7 | 8 | /** 9 | * Spaces component for "/chatregulator clear" command 10 | */ 11 | public static final Component SPACES_COMPONENT; 12 | 13 | static { 14 | final TextComponent.Builder builder = Component.text(); 15 | for (int i = 0; i < 100; i++) { 16 | builder.appendNewline(); 17 | } 18 | SPACES_COMPONENT = builder.build().compact(); 19 | } 20 | private Components() {} 21 | } 22 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/utils/Replacer.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.api.utils; 2 | 3 | import java.util.Objects; 4 | 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | /** 8 | * String Replacer Utils 9 | */ 10 | public final class Replacer { 11 | private Replacer() { 12 | } 13 | /** 14 | * Converts a string with the first character converted to uppercase 15 | * 16 | * @param string the string 17 | * @return a string with the first character converted to uppercase 18 | */ 19 | public static @NotNull String firstLetterUppercase(@NotNull final String string) { 20 | if (Objects.requireNonNull(string).length() < 1) { 21 | return string; 22 | } 23 | 24 | final char firstCharacter = string.charAt(0); 25 | if (Character.isUpperCase(firstCharacter)) return string; 26 | 27 | return Character.toUpperCase(firstCharacter) + 28 | string.substring(1); 29 | } 30 | 31 | /** 32 | * Add a dot at the end of a string 33 | *

34 | * If the string already has an endpoint, it will return the same string 35 | * 36 | * @param string the string 37 | * @return the string converted 38 | */ 39 | public static @NotNull String addFinalDot(@NotNull final String string) { 40 | Objects.requireNonNull(string); 41 | if (string.length() <= 1 || string.charAt(string.length() - 1) == '.') { 42 | return string; 43 | } 44 | 45 | return string + "."; 46 | } 47 | 48 | /** 49 | * Applies a trailing dot and a leading capital letter to the specified string 50 | * 51 | * @param string the string 52 | * @return the string converted 53 | */ 54 | public static @NotNull String applyFormat(final @NotNull String string) { 55 | return firstLetterUppercase(addFinalDot(string)); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /api/src/main/java/io/github/_4drian3d/chatregulator/api/utils/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * ChatRegulator string utils 3 | */ 4 | package io.github._4drian3d.chatregulator.api.utils; -------------------------------------------------------------------------------- /api/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * ChatRegulator API Module 3 | */ 4 | module io.github._4drian3d.chatregulator.api { 5 | requires static org.jetbrains.annotations; 6 | requires static org.checkerframework.checker.qual; 7 | requires net.kyori.adventure; 8 | requires net.kyori.examination.api; 9 | requires com.github.benmanes.caffeine; 10 | 11 | exports io.github._4drian3d.chatregulator.api; 12 | exports io.github._4drian3d.chatregulator.api.result; 13 | exports io.github._4drian3d.chatregulator.api.enums; 14 | exports io.github._4drian3d.chatregulator.api.checks; 15 | exports io.github._4drian3d.chatregulator.api.annotations; 16 | exports io.github._4drian3d.chatregulator.api.event; 17 | exports io.github._4drian3d.chatregulator.api.utils; 18 | } -------------------------------------------------------------------------------- /common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `java-library` 3 | alias(libs.plugins.blossom) 4 | alias(libs.plugins.idea.ext) 5 | } 6 | 7 | java { 8 | toolchain { 9 | languageVersion.set(JavaLanguageVersion.of(17)) 10 | } 11 | } 12 | 13 | dependencies { 14 | compileOnlyApi(libs.configurate) 15 | api(projects.chatregulatorApi) 16 | compileOnly(libs.slf4j) 17 | compileOnly(libs.adventure.minimessage) 18 | compileOnly(libs.miniplaceholders) 19 | } 20 | 21 | tasks { 22 | compileJava { 23 | options.release.set(17) 24 | options.encoding = "UTF-8" 25 | } 26 | } 27 | 28 | sourceSets { 29 | main { 30 | blossom { 31 | javaSources { 32 | property("version", project.version.toString()) 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /common/src/main/java-templates/io/github/_4drian3d/chatregulator/common/Constants.java.peb: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.common; 2 | 3 | /** 4 | * Plugin Constants 5 | */ 6 | public final class Constants { 7 | /** 8 | * ChatRegulator Version 9 | */ 10 | public static final String VERSION = "{{ version }}"; 11 | 12 | private Constants() {} 13 | } 14 | -------------------------------------------------------------------------------- /common/src/main/java/io/github/_4drian3d/chatregulator/common/commands/RegulatorCommand.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.common.commands; 2 | 3 | public interface RegulatorCommand { 4 | void register(); 5 | } 6 | -------------------------------------------------------------------------------- /common/src/main/java/io/github/_4drian3d/chatregulator/common/configuration/Blacklist.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.common.configuration; 2 | 3 | import java.util.Objects; 4 | import java.util.Set; 5 | import java.util.regex.Pattern; 6 | 7 | import org.spongepowered.configurate.objectmapping.ConfigSerializable; 8 | import org.spongepowered.configurate.objectmapping.meta.Comment; 9 | import org.spongepowered.configurate.objectmapping.meta.Setting; 10 | 11 | import static io.github._4drian3d.chatregulator.api.utils.Commands.getFirstArgument; 12 | 13 | /** 14 | * Blacklist Configuration 15 | */ 16 | @ConfigSerializable 17 | public final class Blacklist implements Section { 18 | public static final String HEADER = """ 19 | ChatRegulator | by 4drian3d 20 | Blacklist of Commands and Regular Expressions 21 | To test each regular expression, use: 22 | https://regex101.com/ 23 | If you are using patterns that include '\\', replace them with '\\\\'"""; 24 | 25 | @Comment(""" 26 | Sets the expressions to be checked in the 27 | Infractions module in commands and general chat""") 28 | @Setting(value = "blocked-words") 29 | private Pattern[] blockedPatterns = { 30 | Pattern.compile("f[uv4@]ck", Pattern.CASE_INSENSITIVE), 31 | Pattern.compile("sh[i@lj1y]t", Pattern.CASE_INSENSITIVE), 32 | Pattern.compile("d[i@lj1y]c(k)?", Pattern.CASE_INSENSITIVE), 33 | Pattern.compile("b[i@lj1y]tch", Pattern.CASE_INSENSITIVE), 34 | Pattern.compile("[a@4x]w[3@ex]b[o@0x8]n[a@4x]d[o@0x8]", Pattern.CASE_INSENSITIVE), 35 | Pattern.compile("p[u@v]ssy", Pattern.CASE_INSENSITIVE), 36 | Pattern.compile("(?:(?:https?|ftp|file)://|www\\.|ftp\\.)(?:\\([-A-Z0-9+&@#/%=~_|$?!:,.]*\\)|[-A-Z0-9+&@#/%=~_|$?!:,.])*(?:\\([-A-Z0-9+&@#/%=~_|$?!:,.]*\\)|[A-Z0-9+&@#/%=~_|$])", Pattern.CASE_INSENSITIVE), 37 | Pattern.compile("[i@lj1y]mb[3@ex]c[i@lj1y]l", Pattern.CASE_INSENSITIVE), 38 | Pattern.compile("m[o@0x8]th[3@ex]rf[u@v]ck[3@ex]r", Pattern.CASE_INSENSITIVE) 39 | }; 40 | 41 | @Comment(""" 42 | Sets the commands that cannot be executed 43 | (configurable in the command module)""") 44 | @Setting(value = "blocked-commands") 45 | private Set blockedCommands = Set.of( 46 | "execute", 47 | "/calc", 48 | "/calculate", 49 | "/solve", 50 | "/eval", 51 | "pex", 52 | "mv", 53 | "multiverse" 54 | ); 55 | 56 | /** 57 | * Get the blocked regex strings 58 | * @return the blocked regex strings 59 | */ 60 | public Pattern[] getBlockedPatterns(){ 61 | return this.blockedPatterns; 62 | } 63 | 64 | /** 65 | * Get the blocked commands 66 | * @return the blocked commands 67 | */ 68 | public Set getBlockedCommands(){ 69 | return this.blockedCommands; 70 | } 71 | 72 | public boolean isBlockedCommand(String command) { 73 | final String firstArgument = getFirstArgument(Objects.requireNonNull(command)); 74 | return getBlockedCommands().stream() 75 | .anyMatch(firstArgument::equalsIgnoreCase); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /common/src/main/java/io/github/_4drian3d/chatregulator/common/configuration/Configuration.java: -------------------------------------------------------------------------------- 1 | package io.github._4drian3d.chatregulator.common.configuration; 2 | 3 | import java.util.Set; 4 | import java.util.concurrent.TimeUnit; 5 | 6 | import io.github._4drian3d.chatregulator.api.enums.*; 7 | import io.github._4drian3d.chatregulator.api.utils.Commands; 8 | import net.kyori.adventure.audience.Audience; 9 | import org.spongepowered.configurate.objectmapping.ConfigSerializable; 10 | import org.spongepowered.configurate.objectmapping.meta.Comment; 11 | import org.spongepowered.configurate.objectmapping.meta.Setting; 12 | 13 | @ConfigSerializable 14 | @SuppressWarnings("FieldMayBeFinal") 15 | public final class Configuration implements Section { 16 | public static final String HEADER = """ 17 | ChatRegulator | by 4drian3d 18 | Check the function of each configuration option at 19 | https://github.com/4drian3d/ChatRegulator/wiki/Configuration"""; 20 | 21 | @Comment("Formatter Module") 22 | private Formatter formatter = new Formatter(); 23 | 24 | @Comment("CommandSpy configuration") 25 | private CommandSpy commandSpy = new CommandSpy(); 26 | 27 | @Comment("Settings on the log of alert messages in console or files") 28 | private Log log = new Log(); 29 | 30 | @Comment(""" 31 | Specify in which commands you want the violations to be detected 32 | I recommend you to put chat commands, for example: /tell""") 33 | @Setting(value = "commands-checked") 34 | private Set commandsChecked = Set.of( 35 | "tell", 36 | "etell", 37 | "msg", 38 | "emsg", 39 | "chat", 40 | "global", 41 | "reply" 42 | ); 43 | 44 | @Comment("Set the maximum time in which a user's violations will be saved after the user leaves your server") 45 | @Setting(value = "delete-users-after") 46 | private long deleteUsersAfter = 30; 47 | 48 | @Comment(""" 49 | Set the time unit of the delete-users-after setting 50 | Available values: NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS""") 51 | @Setting(value = "time-unit") 52 | private TimeUnit unit = TimeUnit.SECONDS; 53 | 54 | @Comment("Limit the amount of users showed on autocompletion") 55 | @Setting(value = "tab-complete-limit") 56 | private int limitTabComplete = 40; 57 | 58 | public long deleteUsersTime(){ 59 | return this.deleteUsersAfter; 60 | } 61 | 62 | public TimeUnit unit() { 63 | return this.unit; 64 | } 65 | 66 | public int tabCompleteLimit(){ 67 | return this.limitTabComplete; 68 | } 69 | 70 | public Set getCommandsChecked(){ 71 | return this.commandsChecked; 72 | } 73 | 74 | public Formatter getFormatterConfig(){ 75 | return this.formatter; 76 | } 77 | 78 | public CommandSpy getCommandSpyConfig(){ 79 | return this.commandSpy; 80 | } 81 | 82 | public Log getLog() { 83 | return this.log; 84 | } 85 | 86 | @ConfigSerializable 87 | public static class Formatter { 88 | @Comment("Enable Format Module") 89 | private boolean enabled = false; 90 | 91 | @Comment("Set the first letter of a sentence in uppercase") 92 | @Setting(value = "first-letter-uppercase") 93 | private boolean firstLetterUppercase = true; 94 | 95 | @Comment("Adds a final dot in each sentence") 96 | @Setting(value = "final-dot") 97 | private boolean finalDot = true; 98 | 99 | public boolean enabled(){ 100 | return this.enabled; 101 | } 102 | 103 | public boolean setFirstLetterUppercase(){ 104 | return this.firstLetterUppercase; 105 | } 106 | 107 | public boolean setFinalDot(){ 108 | return this.finalDot; 109 | } 110 | } 111 | 112 | @ConfigSerializable 113 | public static class CommandSpy { 114 | @Comment("Enable CommandSpy module") 115 | private boolean enabled = false; 116 | 117 | @Comment("Commands to ignore") 118 | private Set ignoredCommands = Set.of( 119 | "login", 120 | "register", 121 | "changepassword" 122 | ); 123 | 124 | public boolean enabled() { 125 | return this.enabled; 126 | } 127 | 128 | public boolean shouldAnnounce(Audience source, String command){ 129 | return ignoredCommands.contains(Commands.getFirstArgument(command)) 130 | && Permission.BYPASS_COMMAND_SPY.test(source); 131 | } 132 | } 133 | 134 | @ConfigSerializable 135 | public static class Log { 136 | @Comment("Toggle to show in console the alert message in case of check detection") 137 | private boolean warningLog = true; 138 | private File file = new File(); 139 | 140 | public boolean warningLog() { 141 | return warningLog; 142 | } 143 | 144 | public File getFile() { 145 | return file; 146 | } 147 | 148 | @ConfigSerializable 149 | public static class File { 150 | @Comment("Sets whether this module will be activated") 151 | private boolean enabled = false; 152 | @Comment("Sets the format of the file in which the log will be written") 153 | private String fileFormat = "'infractions'-dd-MM-yy'.txt'"; 154 | @Comment("Sets the log format") 155 | private String logFormat = "[