├── .editorconfig ├── .github ├── FUNDING.yml └── workflows │ ├── build.yml │ ├── release.yml │ └── set-version.yml ├── .gitignore ├── LICENSE ├── PistonChat ├── build.gradle.kts └── src │ └── main │ ├── java │ └── net │ │ └── pistonmaster │ │ └── pistonchat │ │ ├── PistonChat.java │ │ ├── api │ │ ├── PistonChatAPI.java │ │ ├── PistonChatEvent.java │ │ ├── PistonChatReceiveEvent.java │ │ └── PistonWhisperEvent.java │ │ ├── commands │ │ ├── MainCommand.java │ │ ├── ignore │ │ │ ├── HardIgnoreCommand.java │ │ │ ├── IgnoreListCommand.java │ │ │ └── SoftIgnoreCommand.java │ │ ├── toggle │ │ │ ├── ToggleChatCommand.java │ │ │ └── ToggleWhisperingCommand.java │ │ └── whisper │ │ │ ├── LastCommand.java │ │ │ ├── MessageCommandHelper.java │ │ │ ├── ReplyCommand.java │ │ │ └── WhisperCommand.java │ │ ├── events │ │ └── ChatEvent.java │ │ ├── storage │ │ ├── PCStorage.java │ │ ├── file │ │ │ └── FileStorage.java │ │ └── mysql │ │ │ └── MySQLStorage.java │ │ ├── tools │ │ ├── CacheTool.java │ │ ├── CommonTool.java │ │ ├── HardIgnoreTool.java │ │ ├── IgnoreTool.java │ │ ├── SoftIgnoreTool.java │ │ └── TempDataTool.java │ │ └── utils │ │ ├── ConfigManager.java │ │ ├── PlatformUtils.java │ │ └── UniqueSender.java │ └── resources │ ├── config.yml │ ├── language.yml │ └── plugin.yml ├── PistonFilter ├── build.gradle.kts └── src │ ├── main │ ├── java │ │ └── net │ │ │ └── pistonmaster │ │ │ └── pistonfilter │ │ │ ├── PistonFilter.java │ │ │ ├── commands │ │ │ └── FilterCommand.java │ │ │ ├── hooks │ │ │ └── PistonMuteHook.java │ │ │ ├── listeners │ │ │ └── ChatListener.java │ │ │ └── utils │ │ │ ├── FilteredPlayer.java │ │ │ ├── MaxSizeDeque.java │ │ │ ├── MessageInfo.java │ │ │ └── StringHelper.java │ └── resources │ │ ├── config.yml │ │ └── plugin.yml │ └── test │ └── java │ └── LeetTest.java ├── PistonMute ├── build.gradle.kts └── src │ └── main │ ├── java │ └── net │ │ └── pistonmaster │ │ └── pistonmute │ │ ├── PistonMute.java │ │ ├── api │ │ └── MuteAPI.java │ │ ├── commands │ │ ├── MuteCommand.java │ │ └── UnMuteCommand.java │ │ ├── listeners │ │ └── PistonChatListener.java │ │ └── utils │ │ └── StorageTool.java │ └── resources │ ├── config.yml │ └── plugin.yml ├── README.md ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts ├── settings.gradle.kts └── src │ └── main │ └── kotlin │ ├── pc.java-conventions.gradle.kts │ └── pc.shadow-conventions.gradle.kts ├── crowdin.yml ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images └── logo.png ├── renovate.json └── settings.gradle.kts /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | end_of_line = lf 4 | indent_size = 2 5 | indent_style = space 6 | insert_final_newline = true 7 | max_line_length = off 8 | tab_width = 2 9 | trim_trailing_whitespace = true 10 | ij_continuation_indent_size = 2 11 | 12 | [*.java] 13 | ij_java_keep_simple_blocks_in_one_line = false 14 | ij_java_keep_simple_classes_in_one_line = true 15 | ij_java_keep_simple_lambdas_in_one_line = true 16 | ij_java_keep_simple_methods_in_one_line = true 17 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: alexprogrammerde 2 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build and upload jar 2 | 3 | on: [ push, pull_request ] 4 | 5 | jobs: 6 | build: 7 | # Only run on PRs if the source branch is on someone else's repo 8 | if: "${{ github.event_name != 'pull_request' || github.repository != github.event.pull_request.head.repo.full_name }}" 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Set up JDK 21 15 | uses: actions/setup-java@v4 16 | with: 17 | java-version: 21 18 | distribution: 'temurin' 19 | - name: Setup Gradle 20 | uses: gradle/actions/setup-gradle@v4 21 | - name: Build with Gradle 22 | run: ./gradlew build 23 | - name: Upload PistonChat 24 | uses: actions/upload-artifact@v4.6.2 25 | with: 26 | name: PistonChat 27 | path: PistonChat/build/libs/*.jar 28 | - name: Upload PistonMute 29 | uses: actions/upload-artifact@v4.6.2 30 | with: 31 | name: PistonMute 32 | path: PistonMute/build/libs/*.jar 33 | - name: Upload PistonFilter 34 | uses: actions/upload-artifact@v4.6.2 35 | with: 36 | name: PistonFilter 37 | path: PistonFilter/build/libs/*.jar 38 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: 'Version to release' 8 | required: true 9 | after-version: 10 | description: 'Snapshot version after release' 11 | required: true 12 | 13 | jobs: 14 | set-release-version: 15 | uses: AlexProgrammerDE/PistonChat/.github/workflows/set-version.yml@main 16 | with: 17 | version: ${{ inputs.version }} 18 | secrets: inherit 19 | 20 | build: 21 | needs: set-release-version 22 | runs-on: ubuntu-latest 23 | permissions: 24 | contents: write 25 | steps: 26 | - name: Checkout repository 27 | uses: actions/checkout@v4 28 | with: 29 | ref: ${{ github.ref }} 30 | - name: Validate Gradle wrapper 31 | uses: gradle/actions/wrapper-validation@v4 32 | - name: Set up JDK 21 33 | uses: actions/setup-java@v4 34 | with: 35 | java-version: '21' 36 | distribution: 'temurin' 37 | - name: Setup Gradle 38 | uses: gradle/actions/setup-gradle@v4 39 | - name: Build with Gradle 40 | run: ./gradlew build test --stacktrace --scan 41 | 42 | - name: Build Changelog 43 | id: github_release 44 | uses: mikepenz/release-changelog-builder-action@v5 45 | with: 46 | mode: COMMIT 47 | toTag: ${{ github.ref }} 48 | configurationJson: | 49 | { 50 | "template": "#{{CHANGELOG}}", 51 | "commit_template": "- [`#{{SHORT_MERGE_SHA}}`](https://github.com/AlexProgrammerDE/PistonChat/commit/#{{MERGE_SHA}}) #{{TITLE}}", 52 | "categories": [ 53 | { 54 | "title": "## 🚀 Features", 55 | "labels": ["feat", "feature"] 56 | }, 57 | { 58 | "title": "## 🐛 Fixes", 59 | "labels": ["fix", "bug"] 60 | }, 61 | { 62 | "title": "## 🏎️ Performance", 63 | "labels": ["perf"] 64 | }, 65 | { 66 | "title": "## 🏗 Refactor", 67 | "labels": ["refactor"] 68 | }, 69 | { 70 | "title": "## 📝 Documentation", 71 | "labels": ["docs"] 72 | }, 73 | { 74 | "title": "## 🔨 Build", 75 | "labels": ["build", "chore", "ci"] 76 | }, 77 | { 78 | "title": "## 💅 Style", 79 | "labels": ["style"] 80 | }, 81 | { 82 | "title": "## 🧪 Tests", 83 | "labels": ["test"] 84 | }, 85 | { 86 | "title": "## 💬 Other", 87 | "labels": [] 88 | }, 89 | { 90 | "title": "## 📦 Dependencies", 91 | "labels": ["dependencies"] 92 | } 93 | ], 94 | "label_extractor": [ 95 | { 96 | "pattern": "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test){1}(\\([\\w\\-\\.]+\\))?(!)?: ([\\w ])+([\\s\\S]*)", 97 | "on_property": "title", 98 | "target": "$1" 99 | } 100 | ], 101 | "custom_placeholders": [ 102 | { 103 | "name": "SHORT_MERGE_SHA", 104 | "source": "MERGE_SHA", 105 | "transformer": { 106 | "pattern": "^([0-9a-f]{7})[0-9a-f]*$", 107 | "target": "$1" 108 | } 109 | } 110 | ] 111 | } 112 | env: 113 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 114 | 115 | - uses: Kir-Antipov/mc-publish@v3.3 116 | with: 117 | modrinth-id: pistonchat 118 | modrinth-featured: true 119 | modrinth-unfeature-mode: subset 120 | modrinth-token: ${{ secrets.MODRINTH_TOKEN }} 121 | 122 | github-tag: ${{ inputs.version }} 123 | github-generate-changelog: false 124 | github-draft: false 125 | github-prerelease: false 126 | github-commitish: main 127 | github-token: ${{ secrets.GITHUB_TOKEN }} 128 | 129 | files: | 130 | PistonChat/build/libs/PistonChat-${{ inputs.version }}.jar 131 | PistonMute/build/libs/PistonMute-${{ inputs.version }}.jar 132 | PistonFilter/build/libs/PistonFilter-${{ inputs.version }}.jar 133 | 134 | name: PistonChat ${{ inputs.version }} 135 | version: ${{ inputs.version }} 136 | version-type: release 137 | changelog: ${{ steps.github_release.outputs.changelog }} 138 | 139 | github-changelog: | 140 | [![modrinth](https://cdn.jsdelivr.net/npm/@intergrav/devins-badges@3/assets/cozy/available/modrinth_vector.svg)](https://modrinth.com/plugin/pistonchat/version/${{ inputs.version }}) 141 | 142 | ${{ steps.github_release.outputs.changelog }} 143 | 144 | loaders: | 145 | folia 146 | paper 147 | purpur 148 | spigot 149 | game-versions: | 150 | >=1.8.0 151 | game-version-filter: releases 152 | java: | 153 | 21 154 | 155 | retry-attempts: 2 156 | retry-delay: 10000 157 | fail-mode: fail 158 | 159 | - name: Discord Webhook Action 160 | uses: tsickert/discord-webhook@v7.0.0 161 | with: 162 | webhook-url: ${{ secrets.WEBHOOK_URL }} 163 | content: <@&850705047938793503> New PistonChat version released! 164 | embed-title: PistonChat ${{ inputs.version }} 165 | embed-description: PistonChat ${{ inputs.version }} has been released! Changelog and download can be found at https://modrinth.com/plugin/pistonchat/version/${{ inputs.version }} 166 | embed-color: 16641028 167 | embed-thumbnail-url: https://raw.githubusercontent.com/AlexProgrammerDE/PistonChat/refs/heads/main/images/logo.png 168 | 169 | set-after-version: 170 | needs: build 171 | uses: AlexProgrammerDE/PistonChat/.github/workflows/set-version.yml@main 172 | with: 173 | version: ${{ inputs.after-version }} 174 | secrets: inherit 175 | -------------------------------------------------------------------------------- /.github/workflows/set-version.yml: -------------------------------------------------------------------------------- 1 | name: set-version 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: 'Version to set' 8 | required: true 9 | workflow_call: 10 | inputs: 11 | version: 12 | required: true 13 | type: string 14 | 15 | jobs: 16 | set-version: 17 | name: Set Version 18 | 19 | permissions: 20 | contents: write 21 | 22 | runs-on: ubuntu-24.04 23 | steps: 24 | - name: 'Shared: Checkout repository' 25 | uses: actions/checkout@v4 26 | with: 27 | ref: ${{ github.ref }} 28 | 29 | - name: 'Set Version' 30 | run: | 31 | sed -i 's/^maven_version=.*/maven_version='"${{ inputs.version }}"'/g' gradle.properties 32 | 33 | - name: 'Commit Version' 34 | uses: stefanzweifel/git-auto-commit-action@v5 35 | with: 36 | commit_message: 'chore(release): bump version to ${{ inputs.version }}' 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled plugin files 2 | target/* 3 | 4 | # IDE files 5 | .idea/* 6 | .vscode/* 7 | .settings/* 8 | *.iml 9 | .classpath 10 | .project 11 | .factorypath 12 | 13 | # UML 14 | *.plantuml 15 | 16 | # Gradle 17 | .kotlin 18 | .gradle 19 | **/build/ 20 | !src/**/build/ 21 | 22 | # Ignore Gradle GUI config 23 | gradle-app.setting 24 | 25 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 26 | !gradle-wrapper.jar 27 | 28 | # Cache of project 29 | .gradletasknamecache 30 | run 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2020 AlexProgrammerDE 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /PistonChat/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("pc.shadow-conventions") 3 | alias(libs.plugins.runpaper) 4 | } 5 | 6 | description = "Advanced chat plugin for survival/anarchy servers." 7 | 8 | dependencies { 9 | compileOnly("com.google.code.findbugs:jsr305:3.0.2") 10 | compileOnly("io.github.miniplaceholders:miniplaceholders-api:2.3.0") 11 | compileOnly("org.spigotmc:spigot-api:1.12.2-R0.1-SNAPSHOT") 12 | 13 | implementation("net.pistonmaster:PistonUtils:1.4.0") 14 | implementation("com.github.technicallycoded:FoliaLib:main-SNAPSHOT") 15 | implementation("com.google.code.gson:gson:2.13.1") 16 | implementation("net.kyori:adventure-platform-bukkit:4.4.0") 17 | implementation("net.kyori:adventure-text-minimessage:4.21.0") 18 | implementation("org.bstats:bstats-bukkit:3.1.0") 19 | implementation("org.mariadb.jdbc:mariadb-java-client:3.5.3") 20 | implementation("com.github.ben-manes.caffeine:caffeine:3.2.0") 21 | } 22 | 23 | tasks { 24 | runServer { 25 | minecraftVersion(libs.versions.runpaperversion.get()) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /PistonChat/src/main/java/net/pistonmaster/pistonchat/PistonChat.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonchat; 2 | 3 | import com.tcoded.folialib.FoliaLib; 4 | import lombok.Getter; 5 | import net.kyori.adventure.platform.bukkit.BukkitAudiences; 6 | import net.md_5.bungee.api.ChatColor; 7 | import net.pistonmaster.pistonchat.api.PistonChatAPI; 8 | import net.pistonmaster.pistonchat.commands.MainCommand; 9 | import net.pistonmaster.pistonchat.commands.ignore.HardIgnoreCommand; 10 | import net.pistonmaster.pistonchat.commands.ignore.IgnoreListCommand; 11 | import net.pistonmaster.pistonchat.commands.ignore.SoftIgnoreCommand; 12 | import net.pistonmaster.pistonchat.commands.toggle.ToggleChatCommand; 13 | import net.pistonmaster.pistonchat.commands.toggle.ToggleWhisperingCommand; 14 | import net.pistonmaster.pistonchat.commands.whisper.LastCommand; 15 | import net.pistonmaster.pistonchat.commands.whisper.ReplyCommand; 16 | import net.pistonmaster.pistonchat.commands.whisper.WhisperCommand; 17 | import net.pistonmaster.pistonchat.events.ChatEvent; 18 | import net.pistonmaster.pistonchat.storage.PCStorage; 19 | import net.pistonmaster.pistonchat.storage.file.FileStorage; 20 | import net.pistonmaster.pistonchat.storage.mysql.MySQLStorage; 21 | import net.pistonmaster.pistonchat.tools.*; 22 | import net.pistonmaster.pistonchat.utils.ConfigManager; 23 | import net.pistonmaster.pistonutils.update.GitHubUpdateChecker; 24 | import net.pistonmaster.pistonutils.update.SemanticVersion; 25 | import org.bstats.bukkit.Metrics; 26 | import org.bukkit.Server; 27 | import org.bukkit.command.PluginCommand; 28 | import org.bukkit.configuration.file.FileConfiguration; 29 | import org.bukkit.plugin.java.JavaPlugin; 30 | 31 | import java.io.IOException; 32 | import java.util.logging.Logger; 33 | 34 | @Getter 35 | public final class PistonChat extends JavaPlugin { 36 | private final ConfigManager configManager = new ConfigManager(this, "config.yml"); 37 | private final ConfigManager languageManager = new ConfigManager(this, "language.yml"); 38 | private final TempDataTool tempDataTool = new TempDataTool(this); 39 | private final SoftIgnoreTool softignoreTool = new SoftIgnoreTool(this); 40 | private final CacheTool cacheTool = new CacheTool(this); 41 | private final IgnoreTool ignoreTool = new IgnoreTool(this); 42 | private final HardIgnoreTool hardIgnoreTool = new HardIgnoreTool(this); 43 | private final CommonTool commonTool = new CommonTool(this); 44 | private final FoliaLib foliaLib = new FoliaLib(this); 45 | private PCStorage storage; 46 | private BukkitAudiences adventure; 47 | 48 | @Override 49 | public void onEnable() { 50 | this.adventure = BukkitAudiences.create(this); 51 | PistonChatAPI.setInstance(this); 52 | 53 | Logger log = getLogger(); 54 | Server server = getServer(); 55 | 56 | log.info(" _____ _ _ _____ _ _ "); 57 | log.info(" | __ \\(_) | | / ____|| | | | "); 58 | log.info(" | |__) |_ ___ | |_ ___ _ __ | | | |__ __ _ | |_ "); 59 | log.info(" | ___/| |/ __|| __|/ _ \\ | '_ \\ | | | '_ \\ / _` || __|"); 60 | log.info(" | | | |\\__ \\| |_| (_) || | | || |____ | | | || (_| || |_ "); 61 | log.info(" |_| |_||___/ \\__|\\___/ |_| |_| \\_____||_| |_| \\__,_| \\__|"); 62 | log.info(" "); 63 | 64 | log.info(ChatColor.DARK_GREEN + "Loading config"); 65 | try { 66 | configManager.create(); 67 | languageManager.create(); 68 | } catch (IOException e) { 69 | e.printStackTrace(); 70 | getServer().getPluginManager().disablePlugin(this); 71 | } 72 | 73 | log.info(ChatColor.DARK_GREEN + "Loading storage"); 74 | var storageType = configManager.get().getString("storage"); 75 | if (storageType.equalsIgnoreCase("mysql")) { 76 | storage = new MySQLStorage(log, configManager); 77 | } else if (storageType.equalsIgnoreCase("file")) { 78 | storage = new FileStorage(log, getDataFolder().toPath()); 79 | } 80 | 81 | log.info(ChatColor.DARK_GREEN + "Registering commands"); 82 | PluginCommand ignorehard = server.getPluginCommand("ignorehard"); 83 | PluginCommand ignore = server.getPluginCommand("ignore"); 84 | PluginCommand whisper = server.getPluginCommand("whisper"); 85 | PluginCommand reply = server.getPluginCommand("reply"); 86 | PluginCommand last = server.getPluginCommand("last"); 87 | PluginCommand ignorelist = server.getPluginCommand("ignorelist"); 88 | PluginCommand toggleWhispering = server.getPluginCommand("togglewhispering"); 89 | PluginCommand toggleChat = server.getPluginCommand("togglechat"); 90 | PluginCommand main = server.getPluginCommand("pistonchat"); 91 | 92 | assert ignorehard != null; 93 | assert ignore != null; 94 | assert whisper != null; 95 | assert reply != null; 96 | assert last != null; 97 | assert ignorelist != null; 98 | assert toggleWhispering != null; 99 | assert toggleChat != null; 100 | assert main != null; 101 | 102 | ignorehard.setExecutor(new HardIgnoreCommand(this)); 103 | ignorehard.setTabCompleter(new HardIgnoreCommand(this)); 104 | 105 | ignore.setExecutor(new SoftIgnoreCommand(this)); 106 | ignore.setTabCompleter(new SoftIgnoreCommand(this)); 107 | 108 | whisper.setExecutor(new WhisperCommand(this)); 109 | whisper.setTabCompleter(new WhisperCommand(this)); 110 | 111 | reply.setExecutor(new ReplyCommand(this)); 112 | reply.setTabCompleter(new ReplyCommand(this)); 113 | 114 | last.setExecutor(new LastCommand(this)); 115 | last.setTabCompleter(new LastCommand(this)); 116 | 117 | ignorelist.setExecutor(new IgnoreListCommand(this)); 118 | ignorelist.setTabCompleter(new IgnoreListCommand(this)); 119 | 120 | toggleWhispering.setExecutor(new ToggleWhisperingCommand(this)); 121 | toggleWhispering.setTabCompleter(new ToggleWhisperingCommand(this)); 122 | 123 | toggleChat.setExecutor(new ToggleChatCommand(this)); 124 | toggleChat.setTabCompleter(new ToggleChatCommand(this)); 125 | 126 | main.setExecutor(new MainCommand(this)); 127 | main.setTabCompleter(new MainCommand(this)); 128 | 129 | log.info(ChatColor.DARK_GREEN + "Registering listeners"); 130 | server.getPluginManager().registerEvents(new ChatEvent(this), this); 131 | 132 | log.info(ChatColor.DARK_GREEN + "Checking for a newer version"); 133 | try { 134 | String currentVersionString = this.getDescription().getVersion(); 135 | SemanticVersion gitHubVersion = new GitHubUpdateChecker() 136 | .getVersion("https://api.github.com/repos/AlexProgrammerDE/PistonChat/releases/latest"); 137 | SemanticVersion currentVersion = SemanticVersion.fromString(currentVersionString); 138 | 139 | if (gitHubVersion.isNewerThan(currentVersion)) { 140 | log.info(ChatColor.RED + "There is an update available!"); 141 | log.info(ChatColor.RED + "Current version: " + currentVersionString + " New version: " + gitHubVersion); 142 | log.info(ChatColor.RED + "Download it at: https://modrinth.com/plugin/pistonchat"); 143 | } else { 144 | log.info(ChatColor.DARK_GREEN + "You're up to date!"); 145 | } 146 | } catch (IOException e) { 147 | log.severe("Could not check for updates!"); 148 | e.printStackTrace(); 149 | } 150 | 151 | log.info(ChatColor.DARK_GREEN + "Loading metrics"); 152 | new Metrics(this, 9630); 153 | 154 | log.info(ChatColor.DARK_GREEN + "Done! :D"); 155 | } 156 | 157 | @Override 158 | public void onDisable() { 159 | if (this.adventure != null) { 160 | this.adventure.close(); 161 | this.adventure = null; 162 | } 163 | } 164 | 165 | @Override 166 | public FileConfiguration getConfig() { 167 | return configManager.get(); 168 | } 169 | 170 | public FileConfiguration getLanguage() { 171 | return languageManager.get(); 172 | } 173 | 174 | public void runAsync(Runnable runnable) { 175 | foliaLib.getScheduler().runAsync(task -> runnable.run()); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /PistonChat/src/main/java/net/pistonmaster/pistonchat/api/PistonChatAPI.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonchat.api; 2 | 3 | import net.pistonmaster.pistonchat.PistonChat; 4 | 5 | import java.util.Objects; 6 | 7 | /** 8 | * API for interacting with PistonChat! 9 | */ 10 | @SuppressWarnings({"unused"}) 11 | public final class PistonChatAPI { 12 | private static PistonChat plugin = null; 13 | 14 | private PistonChatAPI() { 15 | } 16 | 17 | public static PistonChat getInstance() { 18 | return Objects.requireNonNull(plugin, "plugin is null"); 19 | } 20 | 21 | public static void setInstance(PistonChat plugin) { 22 | if (plugin != null && PistonChatAPI.plugin == null) 23 | PistonChatAPI.plugin = plugin; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /PistonChat/src/main/java/net/pistonmaster/pistonchat/api/PistonChatEvent.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonchat.api; 2 | 3 | import org.bukkit.entity.Player; 4 | import org.bukkit.event.Cancellable; 5 | import org.bukkit.event.Event; 6 | import org.bukkit.event.HandlerList; 7 | 8 | /** 9 | * Alternative chat event that is fired when PistonChat executes the chat event. 10 | */ 11 | @SuppressWarnings({"unused"}) 12 | public final class PistonChatEvent extends Event implements Cancellable { 13 | private static final HandlerList HANDLERS = new HandlerList(); 14 | private final Player player; 15 | private boolean isCancelled; 16 | private String message; 17 | 18 | public PistonChatEvent(Player player, String message, boolean isAsync) { 19 | super(isAsync); 20 | 21 | this.player = player; 22 | this.message = message; 23 | this.isCancelled = false; 24 | } 25 | 26 | public static HandlerList getHandlerList() { 27 | return HANDLERS; 28 | } 29 | 30 | @Override 31 | public HandlerList getHandlers() { 32 | return HANDLERS; 33 | } 34 | 35 | /** 36 | * Gets the cancellation state of this event. A cancelled event will not 37 | * be executed in the server, but will still pass to other plugins. 38 | * 39 | * @return true if this event is cancelled 40 | */ 41 | @Override 42 | public boolean isCancelled() { 43 | return isCancelled; 44 | } 45 | 46 | /** 47 | * Sets the cancellation state of this event. A cancelled event will not 48 | * be executed in the server, but will still pass to other plugins. 49 | * 50 | * @param cancel true if you wish to cancel this event 51 | */ 52 | @Override 53 | public void setCancelled(boolean cancel) { 54 | isCancelled = cancel; 55 | } 56 | 57 | /** 58 | * Get the player who sends the message. 59 | * 60 | * @return the player who sends the message 61 | */ 62 | public Player getPlayer() { 63 | return player; 64 | } 65 | 66 | /** 67 | * Get the message the player sends. 68 | * 69 | * @return the message 70 | */ 71 | public String getMessage() { 72 | return message; 73 | } 74 | 75 | /** 76 | * Set the message that the player sends. 77 | * 78 | * @param message the message to set 79 | */ 80 | public void setMessage(String message) { 81 | this.message = message; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /PistonChat/src/main/java/net/pistonmaster/pistonchat/api/PistonChatReceiveEvent.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonchat.api; 2 | 3 | import org.bukkit.entity.Player; 4 | import org.bukkit.event.Cancellable; 5 | import org.bukkit.event.Event; 6 | import org.bukkit.event.HandlerList; 7 | 8 | /** 9 | * Gets executed per player the plugin tries to send a message to. 10 | */ 11 | @SuppressWarnings("unused") 12 | public class PistonChatReceiveEvent extends Event implements Cancellable { 13 | private static final HandlerList HANDLERS = new HandlerList(); 14 | private final Player sender; 15 | private final Player receiver; 16 | private boolean isCancelled; 17 | private String message; 18 | 19 | public PistonChatReceiveEvent(Player sender, Player receiver, String message, boolean isAsync) { 20 | super(isAsync); 21 | 22 | this.sender = sender; 23 | this.receiver = receiver; 24 | this.message = message; 25 | this.isCancelled = false; 26 | } 27 | 28 | public static HandlerList getHandlerList() { 29 | return HANDLERS; 30 | } 31 | 32 | @Override 33 | public HandlerList getHandlers() { 34 | return HANDLERS; 35 | } 36 | 37 | /** 38 | * Gets the cancellation state of this event. A cancelled event will not 39 | * be executed in the server, but will still pass to other plugins. 40 | * 41 | * @return true if this event is cancelled 42 | */ 43 | @Override 44 | public boolean isCancelled() { 45 | return isCancelled; 46 | } 47 | 48 | /** 49 | * Sets the cancellation state of this event. A cancelled event will not 50 | * be executed in the server, but will still pass to other plugins. 51 | * 52 | * @param cancel true if you wish to cancel this event 53 | */ 54 | @Override 55 | public void setCancelled(boolean cancel) { 56 | isCancelled = cancel; 57 | } 58 | 59 | /** 60 | * Get the player who sends the message. 61 | * 62 | * @return the player who sends the message 63 | */ 64 | public Player getSender() { 65 | return sender; 66 | } 67 | 68 | /** 69 | * Get the player who receives the message. 70 | * 71 | * @return the player who receives the message 72 | */ 73 | public Player getReceiver() { 74 | return receiver; 75 | } 76 | 77 | /** 78 | * Get the message the player sends. 79 | * 80 | * @return the message 81 | */ 82 | public String getMessage() { 83 | return message; 84 | } 85 | 86 | /** 87 | * Set the message that the player sends. 88 | * 89 | * @param message the message to set 90 | */ 91 | public void setMessage(String message) { 92 | this.message = message; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /PistonChat/src/main/java/net/pistonmaster/pistonchat/api/PistonWhisperEvent.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonchat.api; 2 | 3 | import org.bukkit.command.CommandSender; 4 | import org.bukkit.event.Cancellable; 5 | import org.bukkit.event.Event; 6 | import org.bukkit.event.HandlerList; 7 | 8 | @SuppressWarnings("unused") 9 | public class PistonWhisperEvent extends Event implements Cancellable { 10 | private static final HandlerList HANDLERS = new HandlerList(); 11 | private final CommandSender sender; 12 | private final CommandSender receiver; 13 | private boolean isCancelled; 14 | private String message; 15 | 16 | public PistonWhisperEvent(CommandSender sender, CommandSender receiver, String message) { 17 | super(true); 18 | 19 | this.sender = sender; 20 | this.receiver = receiver; 21 | this.message = message; 22 | this.isCancelled = false; 23 | } 24 | 25 | public static HandlerList getHandlerList() { 26 | return HANDLERS; 27 | } 28 | 29 | @Override 30 | public HandlerList getHandlers() { 31 | return HANDLERS; 32 | } 33 | 34 | /** 35 | * Gets the cancellation state of this event. A cancelled event will not 36 | * be executed in the server, but will still pass to other plugins. 37 | * 38 | * @return true if this event is cancelled 39 | */ 40 | @Override 41 | public boolean isCancelled() { 42 | return isCancelled; 43 | } 44 | 45 | /** 46 | * Sets the cancellation state of this event. A cancelled event will not 47 | * be executed in the server, but will still pass to other plugins. 48 | * 49 | * @param cancel true if you wish to cancel this event 50 | */ 51 | @Override 52 | public void setCancelled(boolean cancel) { 53 | isCancelled = cancel; 54 | } 55 | 56 | /** 57 | * Get the player who sends the message. 58 | * 59 | * @return the player who sends the message 60 | */ 61 | public CommandSender getSender() { 62 | return sender; 63 | } 64 | 65 | /** 66 | * Get the player who receives the message. 67 | * 68 | * @return the player who receives the message 69 | */ 70 | public CommandSender getReceiver() { 71 | return receiver; 72 | } 73 | 74 | /** 75 | * Get the message the player sends. 76 | * 77 | * @return the message 78 | */ 79 | public String getMessage() { 80 | return message; 81 | } 82 | 83 | /** 84 | * Set the message that the player sends. 85 | * 86 | * @param message the message to set 87 | */ 88 | public void setMessage(String message) { 89 | this.message = message; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /PistonChat/src/main/java/net/pistonmaster/pistonchat/commands/MainCommand.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonchat.commands; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; 5 | import net.md_5.bungee.api.ChatColor; 6 | import net.md_5.bungee.api.chat.ClickEvent; 7 | import net.md_5.bungee.api.chat.ComponentBuilder; 8 | import net.md_5.bungee.api.chat.HoverEvent; 9 | import net.pistonmaster.pistonchat.PistonChat; 10 | import org.bukkit.command.Command; 11 | import org.bukkit.command.CommandExecutor; 12 | import org.bukkit.command.CommandSender; 13 | import org.bukkit.command.TabExecutor; 14 | import org.bukkit.util.StringUtil; 15 | 16 | import java.io.IOException; 17 | import java.util.ArrayList; 18 | import java.util.Collections; 19 | import java.util.List; 20 | import java.util.Map; 21 | 22 | @RequiredArgsConstructor 23 | public class MainCommand implements CommandExecutor, TabExecutor { 24 | private final PistonChat plugin; 25 | 26 | @Override 27 | public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { 28 | if (args.length == 0) { 29 | return false; 30 | } 31 | 32 | switch (args[0].toLowerCase()) { 33 | case "help" -> { 34 | if (!sender.hasPermission("pistonchat.help")) { 35 | sender.sendMessage(command.getPermissionMessage()); 36 | } 37 | 38 | String headerText = LegacyComponentSerializer.legacySection().serialize(plugin.getCommonTool().getLanguageMessage("help-header", false)); 39 | ComponentBuilder builder = new ComponentBuilder(headerText).color(ChatColor.GOLD); 40 | 41 | for (Map.Entry> entry : plugin.getDescription().getCommands().entrySet()) { 42 | String name = entry.getKey(); 43 | Map info = entry.getValue(); 44 | if (!sender.hasPermission(info.get("permission").toString())) { 45 | continue; 46 | } 47 | 48 | builder.append("\n/" + name) 49 | .color(ChatColor.GOLD) 50 | .event(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + name + " ")) 51 | .event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, 52 | new ComponentBuilder("Click me!") 53 | .color(ChatColor.GOLD) 54 | .create() 55 | )) 56 | .append(" - ") 57 | .color(ChatColor.GOLD) 58 | .append(info.get("description").toString()); 59 | } 60 | 61 | sender.spigot().sendMessage(builder.create()); 62 | } 63 | case "version" -> { 64 | if (!sender.hasPermission("pistonchat.version")) { 65 | sender.sendMessage(command.getPermissionMessage()); 66 | } 67 | 68 | sender.sendMessage(ChatColor.GOLD + "Currently running: " + plugin.getDescription().getFullName()); 69 | } 70 | case "reload" -> { 71 | if (!sender.hasPermission("pistonchat.reload")) { 72 | sender.sendMessage(command.getPermissionMessage()); 73 | } 74 | 75 | try { 76 | plugin.getConfigManager().create(); 77 | plugin.getLanguageManager().create(); 78 | } catch (IOException e) { 79 | plugin.getLogger().severe("Could not create config!"); 80 | e.printStackTrace(); 81 | } 82 | sender.sendMessage("Reloaded the config!"); 83 | } 84 | default -> { 85 | return false; 86 | } 87 | } 88 | 89 | return true; 90 | } 91 | 92 | @Override 93 | public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { 94 | if (args.length == 1) { 95 | List possibleCommands = new ArrayList<>(); 96 | List completions = new ArrayList<>(); 97 | 98 | if (sender.hasPermission("pistonchat.help")) { 99 | possibleCommands.add("help"); 100 | } 101 | 102 | if (sender.hasPermission("pistonchat.reload")) { 103 | possibleCommands.add("reload"); 104 | } 105 | 106 | StringUtil.copyPartialMatches(args[0], possibleCommands, completions); 107 | Collections.sort(completions); 108 | 109 | return completions; 110 | } else { 111 | return Collections.emptyList(); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /PistonChat/src/main/java/net/pistonmaster/pistonchat/commands/ignore/HardIgnoreCommand.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonchat.commands.ignore; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.pistonmaster.pistonchat.PistonChat; 5 | import net.pistonmaster.pistonchat.storage.PCStorage; 6 | import net.pistonmaster.pistonchat.tools.CommonTool; 7 | import net.pistonmaster.pistonchat.utils.PlatformUtils; 8 | import org.bukkit.command.Command; 9 | import org.bukkit.command.CommandExecutor; 10 | import org.bukkit.command.CommandSender; 11 | import org.bukkit.command.TabExecutor; 12 | import org.bukkit.entity.Player; 13 | 14 | import java.util.Collections; 15 | import java.util.List; 16 | import java.util.Optional; 17 | 18 | @RequiredArgsConstructor 19 | public class HardIgnoreCommand implements CommandExecutor, TabExecutor { 20 | private final PistonChat plugin; 21 | 22 | @Override 23 | public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { 24 | if (!(sender instanceof Player player)) { 25 | plugin.getCommonTool().sendLanguageMessage(sender, "playeronly"); 26 | return true; 27 | } 28 | 29 | if (args.length == 0) { 30 | return false; 31 | } 32 | 33 | Optional ignored = PlatformUtils.getPlayer(args[0]); 34 | 35 | if (ignored.isEmpty()) { 36 | plugin.getCommonTool().sendLanguageMessage(player, "notonline"); 37 | return true; 38 | } 39 | 40 | plugin.runAsync(() -> { 41 | PCStorage.HardReturn type = plugin.getHardIgnoreTool().hardIgnorePlayer(player, ignored.get()); 42 | 43 | if (type == PCStorage.HardReturn.IGNORE) { 44 | plugin.getCommonTool().sendLanguageMessageNoPrefix(player, 45 | "ignorehard", 46 | CommonTool.getStrippedNameResolver(ignored.get())); 47 | } else if (type == PCStorage.HardReturn.UN_IGNORE) { 48 | plugin.getCommonTool().sendLanguageMessageNoPrefix(player, 49 | "unignorehard", 50 | CommonTool.getStrippedNameResolver(ignored.get())); 51 | } 52 | }); 53 | 54 | return true; 55 | } 56 | 57 | @Override 58 | public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { 59 | if (args.length == 1) { 60 | return null; 61 | } else { 62 | return Collections.emptyList(); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /PistonChat/src/main/java/net/pistonmaster/pistonchat/commands/ignore/IgnoreListCommand.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonchat.commands.ignore; 2 | 3 | import com.google.common.math.IntMath; 4 | import lombok.RequiredArgsConstructor; 5 | import net.md_5.bungee.api.ChatColor; 6 | import net.md_5.bungee.api.chat.ClickEvent; 7 | import net.md_5.bungee.api.chat.ComponentBuilder; 8 | import net.md_5.bungee.api.chat.HoverEvent; 9 | import net.pistonmaster.pistonchat.PistonChat; 10 | import net.pistonmaster.pistonchat.tools.IgnoreTool; 11 | import org.bukkit.OfflinePlayer; 12 | import org.bukkit.command.Command; 13 | import org.bukkit.command.CommandExecutor; 14 | import org.bukkit.command.CommandSender; 15 | import org.bukkit.command.TabExecutor; 16 | import org.bukkit.entity.Player; 17 | import org.bukkit.util.StringUtil; 18 | 19 | import java.math.RoundingMode; 20 | import java.util.ArrayList; 21 | import java.util.Collections; 22 | import java.util.List; 23 | import java.util.Map; 24 | 25 | @RequiredArgsConstructor 26 | public class IgnoreListCommand implements CommandExecutor, TabExecutor { 27 | private final PistonChat plugin; 28 | 29 | @Override 30 | public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { 31 | if (!(sender instanceof Player player)) { 32 | plugin.getCommonTool().sendLanguageMessage(sender, "playeronly"); 33 | return true; 34 | } 35 | 36 | if (args.length == 0) { 37 | plugin.runAsync(() -> { 38 | if (noPlayersIgnored(player)) { 39 | return; 40 | } 41 | 42 | showList(1, player); 43 | }); 44 | 45 | return true; 46 | } 47 | 48 | if (args[0].equalsIgnoreCase("clear")) { 49 | plugin.runAsync(() -> { 50 | if (noPlayersIgnored(player)) { 51 | return; 52 | } 53 | 54 | plugin.getIgnoreTool().clearIgnoredPlayers(player); 55 | plugin.getCommonTool().sendLanguageMessage(player, "ignorelistcleared"); 56 | }); 57 | return true; 58 | } 59 | 60 | try { 61 | int page = Integer.parseInt(args[0]); 62 | 63 | plugin.runAsync(() -> { 64 | if (noPlayersIgnored(player)) { 65 | return; 66 | } 67 | 68 | if (page < plugin.getIgnoreTool().getIgnoredPlayers(player).size()) { 69 | showList(page, player); 70 | } else { 71 | plugin.getCommonTool().sendLanguageMessage(player, "page-not-exists"); 72 | } 73 | }); 74 | 75 | return true; 76 | } catch (NumberFormatException e) { 77 | plugin.getCommonTool().sendLanguageMessage(player, "not-a-number"); 78 | return false; 79 | } 80 | } 81 | 82 | private boolean noPlayersIgnored(Player player) { 83 | List list = new ArrayList<>(); 84 | 85 | for (OfflinePlayer offlinePlayer : plugin.getIgnoreTool().getIgnoredPlayers(player).keySet()) { 86 | list.add(offlinePlayer.getName()); 87 | } 88 | 89 | if (list.isEmpty()) { 90 | plugin.getCommonTool().sendLanguageMessage(player, "nooneignored"); 91 | return true; 92 | } 93 | 94 | return false; 95 | } 96 | 97 | @Override 98 | public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { 99 | if (args.length == 1) { 100 | return StringUtil.copyPartialMatches(args[0], List.of("clear"), new ArrayList<>()); 101 | } else { 102 | return Collections.emptyList(); 103 | } 104 | } 105 | 106 | private void showList(int page, Player player) { 107 | int maxValue = page * plugin.getConfig().getInt("ignore-list-size"); 108 | int minValue = maxValue - plugin.getConfig().getInt("ignore-list-size"); 109 | 110 | Map map = plugin.getIgnoreTool().getIgnoredPlayers(player); 111 | 112 | int allPages = IntMath.divide(map.size(), plugin.getConfig().getInt("ignore-list-size"), RoundingMode.CEILING); 113 | 114 | ComponentBuilder navigation = new ComponentBuilder("[ Ignored players ").color(ChatColor.GOLD); 115 | 116 | navigation.append("[<]").color(page > 1 ? ChatColor.AQUA : ChatColor.GRAY); 117 | 118 | if (page > 1) { 119 | navigation.event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/ignorelist " + (page - 1))); 120 | navigation.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("Click to go to the previous page!").color(ChatColor.GOLD).create())); 121 | } 122 | 123 | navigation.append(" " + page + "/" + allPages + " ").reset().color(ChatColor.GOLD); 124 | 125 | navigation.append("[>]").color(allPages > page ? ChatColor.AQUA : ChatColor.GRAY); 126 | 127 | if (allPages > page) { 128 | navigation.event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/ignorelist " + (page + 1))); 129 | navigation.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("Click to go to the next page!").color(ChatColor.GOLD).create())); 130 | } 131 | 132 | navigation.append(" ]").reset().color(ChatColor.GOLD); 133 | 134 | player.spigot().sendMessage(navigation.create()); 135 | 136 | int i = 0; 137 | 138 | for (Map.Entry entry : map.entrySet()) { 139 | if (i >= minValue && i < maxValue) { 140 | ComponentBuilder playerBuilder = new ComponentBuilder(entry.getKey().getName()); 141 | 142 | playerBuilder.append(" ").reset(); 143 | 144 | playerBuilder.append("["); 145 | 146 | playerBuilder.color(ChatColor.GRAY); 147 | 148 | if (entry.getValue() == IgnoreTool.IgnoreType.HARD) { 149 | playerBuilder.append("hard"); 150 | 151 | playerBuilder.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("Click to remove the permanent ignore").color(ChatColor.GOLD).create())); 152 | playerBuilder.event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/ignorehard " + ChatColor.stripColor(entry.getKey().getName()))); 153 | } else { 154 | playerBuilder.append("soft"); 155 | playerBuilder.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("Click to remove the temporary ignore").color(ChatColor.GOLD).create())); 156 | playerBuilder.event(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/ignore " + ChatColor.stripColor(entry.getKey().getName()))); 157 | } 158 | 159 | playerBuilder.color(ChatColor.GOLD); 160 | 161 | playerBuilder.append("]").reset(); 162 | 163 | playerBuilder.color(ChatColor.GRAY); 164 | 165 | player.spigot().sendMessage(playerBuilder.create()); 166 | } 167 | 168 | i++; 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /PistonChat/src/main/java/net/pistonmaster/pistonchat/commands/ignore/SoftIgnoreCommand.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonchat.commands.ignore; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.pistonmaster.pistonchat.PistonChat; 5 | import net.pistonmaster.pistonchat.tools.CommonTool; 6 | import net.pistonmaster.pistonchat.tools.SoftIgnoreTool; 7 | import net.pistonmaster.pistonchat.utils.PlatformUtils; 8 | import org.bukkit.command.Command; 9 | import org.bukkit.command.CommandExecutor; 10 | import org.bukkit.command.CommandSender; 11 | import org.bukkit.command.TabExecutor; 12 | import org.bukkit.entity.Player; 13 | 14 | import java.util.Collections; 15 | import java.util.List; 16 | import java.util.Optional; 17 | 18 | @RequiredArgsConstructor 19 | public class SoftIgnoreCommand implements CommandExecutor, TabExecutor { 20 | private final PistonChat plugin; 21 | 22 | @Override 23 | public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { 24 | if (!(sender instanceof Player player)) { 25 | plugin.getCommonTool().sendLanguageMessage(sender, "playeronly"); 26 | return true; 27 | } 28 | 29 | if (args.length == 0) { 30 | return false; 31 | } 32 | 33 | Optional ignored = PlatformUtils.getPlayer(args[0]); 34 | 35 | if (ignored.isEmpty()) { 36 | plugin.getCommonTool().sendLanguageMessage(player, "notonline"); 37 | return true; 38 | } 39 | 40 | plugin.runAsync(() -> { 41 | SoftIgnoreTool.SoftReturn type = plugin.getSoftignoreTool().softIgnorePlayer(player, ignored.get()); 42 | 43 | if (type == SoftIgnoreTool.SoftReturn.IGNORE) { 44 | plugin.getCommonTool().sendLanguageMessageNoPrefix(player, 45 | "ignore", 46 | CommonTool.getStrippedNameResolver(ignored.get())); 47 | } else if (type == SoftIgnoreTool.SoftReturn.UN_IGNORE) { 48 | plugin.getCommonTool().sendLanguageMessageNoPrefix(player, 49 | "unignore", 50 | CommonTool.getStrippedNameResolver(ignored.get())); 51 | } 52 | }); 53 | 54 | return true; 55 | } 56 | 57 | @Override 58 | public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { 59 | if (args.length == 1) { 60 | return null; 61 | } else { 62 | return Collections.emptyList(); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /PistonChat/src/main/java/net/pistonmaster/pistonchat/commands/toggle/ToggleChatCommand.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonchat.commands.toggle; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.pistonmaster.pistonchat.PistonChat; 5 | import org.bukkit.command.Command; 6 | import org.bukkit.command.CommandExecutor; 7 | import org.bukkit.command.CommandSender; 8 | import org.bukkit.command.TabExecutor; 9 | import org.bukkit.entity.Player; 10 | 11 | import java.util.Collections; 12 | import java.util.List; 13 | 14 | @RequiredArgsConstructor 15 | public class ToggleChatCommand implements CommandExecutor, TabExecutor { 16 | private final PistonChat plugin; 17 | 18 | @Override 19 | public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { 20 | if (!(sender instanceof Player player)) { 21 | plugin.getCommonTool().sendLanguageMessage(sender, "playeronly"); 22 | return true; 23 | } 24 | 25 | plugin.runAsync(() -> { 26 | boolean chatNowEnabled = !plugin.getTempDataTool().isChatEnabled(player); 27 | plugin.getTempDataTool().setChatEnabled(player, chatNowEnabled); 28 | 29 | if (chatNowEnabled) { 30 | plugin.getCommonTool().sendLanguageMessage(player, "chaton"); 31 | } else { 32 | plugin.getCommonTool().sendLanguageMessage(player, "chatoff"); 33 | } 34 | }); 35 | 36 | return true; 37 | } 38 | 39 | @Override 40 | public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { 41 | return Collections.emptyList(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /PistonChat/src/main/java/net/pistonmaster/pistonchat/commands/toggle/ToggleWhisperingCommand.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonchat.commands.toggle; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.pistonmaster.pistonchat.PistonChat; 5 | import org.bukkit.command.Command; 6 | import org.bukkit.command.CommandExecutor; 7 | import org.bukkit.command.CommandSender; 8 | import org.bukkit.command.TabExecutor; 9 | import org.bukkit.entity.Player; 10 | 11 | import java.util.Collections; 12 | import java.util.List; 13 | 14 | @RequiredArgsConstructor 15 | public class ToggleWhisperingCommand implements CommandExecutor, TabExecutor { 16 | private final PistonChat plugin; 17 | 18 | @Override 19 | public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { 20 | if (!(sender instanceof Player player)) { 21 | plugin.getCommonTool().sendLanguageMessage(sender, "playeronly"); 22 | return true; 23 | } 24 | 25 | plugin.runAsync(() -> { 26 | boolean whisperingNowEnabled = !plugin.getTempDataTool().isWhisperingEnabled(player); 27 | plugin.getTempDataTool().setWhisperingEnabled(player, whisperingNowEnabled); 28 | 29 | if (whisperingNowEnabled) { 30 | plugin.getCommonTool().sendLanguageMessage(player, "pmson"); 31 | } else { 32 | plugin.getCommonTool().sendLanguageMessage(player, "pmsoff"); 33 | } 34 | }); 35 | 36 | return true; 37 | } 38 | 39 | @Override 40 | public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { 41 | return Collections.emptyList(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /PistonChat/src/main/java/net/pistonmaster/pistonchat/commands/whisper/LastCommand.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonchat.commands.whisper; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.pistonmaster.pistonchat.PistonChat; 5 | import net.pistonmaster.pistonchat.tools.CommonTool; 6 | import org.bukkit.command.Command; 7 | import org.bukkit.command.CommandExecutor; 8 | import org.bukkit.command.CommandSender; 9 | import org.bukkit.command.TabExecutor; 10 | 11 | import java.util.Collections; 12 | import java.util.List; 13 | import java.util.Optional; 14 | 15 | @RequiredArgsConstructor 16 | public class LastCommand extends MessageCommandHelper implements CommandExecutor, TabExecutor { 17 | private final PistonChat plugin; 18 | 19 | @Override 20 | public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { 21 | plugin.runAsync(() -> { 22 | Optional lastSentTo = plugin.getCacheTool().getLastSentTo(sender); 23 | Optional lastMessagedOf = plugin.getCacheTool().getLastMessagedOf(sender); 24 | 25 | if (lastSentTo.isPresent()) { 26 | MessageCommandHelper.sendWhisper(plugin, sender, lastSentTo.get(), CommonTool.mergeArgs(args, 0)); 27 | } else if (lastMessagedOf.isPresent()) { 28 | MessageCommandHelper.sendWhisper(plugin, sender, lastMessagedOf.get(), CommonTool.mergeArgs(args, 0)); 29 | } else { 30 | plugin.getCommonTool().sendLanguageMessage(sender, "notonline"); 31 | } 32 | }); 33 | 34 | return true; 35 | } 36 | 37 | @Override 38 | public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { 39 | return Collections.emptyList(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /PistonChat/src/main/java/net/pistonmaster/pistonchat/commands/whisper/MessageCommandHelper.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonchat.commands.whisper; 2 | 3 | import net.pistonmaster.pistonchat.PistonChat; 4 | import org.bukkit.command.CommandSender; 5 | 6 | public class MessageCommandHelper { 7 | public static void sendWhisper(PistonChat plugin, CommandSender sender, CommandSender receiver, String message) { 8 | if (plugin.getIgnoreTool().isIgnored(sender, receiver)) { 9 | if (plugin.getConfig().getBoolean("only-hide-pms")) { 10 | plugin.getCommonTool().sendSender(sender, message, receiver); 11 | } else { 12 | plugin.getCommonTool().sendLanguageMessage(sender, "source-ignored"); 13 | } 14 | return; 15 | } 16 | 17 | if (!plugin.getConfig().getBoolean("allow-pm-ignored") && plugin.getIgnoreTool().isIgnored(receiver, sender)) { 18 | plugin.getCommonTool().sendLanguageMessage(sender, "target-ignored"); 19 | return; 20 | } 21 | 22 | plugin.getCommonTool().sendWhisperTo(sender, message, receiver); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /PistonChat/src/main/java/net/pistonmaster/pistonchat/commands/whisper/ReplyCommand.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonchat.commands.whisper; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.pistonmaster.pistonchat.PistonChat; 5 | import net.pistonmaster.pistonchat.tools.CommonTool; 6 | import org.bukkit.command.Command; 7 | import org.bukkit.command.CommandExecutor; 8 | import org.bukkit.command.CommandSender; 9 | import org.bukkit.command.TabExecutor; 10 | 11 | import java.util.Collections; 12 | import java.util.List; 13 | import java.util.Optional; 14 | 15 | @RequiredArgsConstructor 16 | public class ReplyCommand extends MessageCommandHelper implements CommandExecutor, TabExecutor { 17 | private final PistonChat plugin; 18 | 19 | @Override 20 | public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { 21 | if (args.length == 0) { 22 | return false; 23 | } 24 | 25 | plugin.runAsync(() -> { 26 | Optional lastMessagedOf = plugin.getCacheTool().getLastMessagedOf(sender); 27 | 28 | if (lastMessagedOf.isEmpty()) { 29 | plugin.getCommonTool().sendLanguageMessage(sender, "notonline"); 30 | return; 31 | } 32 | 33 | MessageCommandHelper.sendWhisper(plugin, sender, lastMessagedOf.get(), CommonTool.mergeArgs(args, 0)); 34 | }); 35 | 36 | return true; 37 | } 38 | 39 | @Override 40 | public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { 41 | return Collections.emptyList(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /PistonChat/src/main/java/net/pistonmaster/pistonchat/commands/whisper/WhisperCommand.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonchat.commands.whisper; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.pistonmaster.pistonchat.PistonChat; 5 | import net.pistonmaster.pistonchat.tools.CommonTool; 6 | import net.pistonmaster.pistonchat.utils.PlatformUtils; 7 | import org.bukkit.command.Command; 8 | import org.bukkit.command.CommandExecutor; 9 | import org.bukkit.command.CommandSender; 10 | import org.bukkit.command.TabExecutor; 11 | import org.bukkit.entity.Player; 12 | 13 | import java.util.Collections; 14 | import java.util.List; 15 | import java.util.Optional; 16 | 17 | @RequiredArgsConstructor 18 | public class WhisperCommand extends MessageCommandHelper implements CommandExecutor, TabExecutor { 19 | private final PistonChat plugin; 20 | 21 | @Override 22 | public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { 23 | if (args.length <= 1) { 24 | return false; 25 | } 26 | 27 | Optional receiver = PlatformUtils.getPlayer(args[0]); 28 | 29 | if (receiver.isEmpty()) { 30 | plugin.getCommonTool().sendLanguageMessage(sender, "notonline"); 31 | return true; 32 | } 33 | 34 | plugin.runAsync(() -> MessageCommandHelper.sendWhisper(plugin, sender, receiver.get(), CommonTool.mergeArgs(args, 1))); 35 | 36 | return true; 37 | } 38 | 39 | @Override 40 | public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { 41 | if (args.length == 1) { 42 | return null; 43 | } else { 44 | return Collections.emptyList(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /PistonChat/src/main/java/net/pistonmaster/pistonchat/events/ChatEvent.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonchat.events; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.pistonmaster.pistonchat.PistonChat; 5 | import net.pistonmaster.pistonchat.api.PistonChatEvent; 6 | import net.pistonmaster.pistonchat.api.PistonChatReceiveEvent; 7 | import org.bukkit.entity.Player; 8 | import org.bukkit.event.EventHandler; 9 | import org.bukkit.event.EventPriority; 10 | import org.bukkit.event.Listener; 11 | import org.bukkit.event.player.AsyncPlayerChatEvent; 12 | 13 | import java.util.Set; 14 | 15 | @RequiredArgsConstructor 16 | public class ChatEvent implements Listener { 17 | private final PistonChat plugin; 18 | 19 | // Mute plugins should have a lower priority to work! 20 | @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) 21 | public void onChat(AsyncPlayerChatEvent event) { 22 | Player chatter = event.getPlayer(); 23 | PistonChatEvent pistonChatEvent = new PistonChatEvent(chatter, event.getMessage(), event.isAsynchronous()); 24 | 25 | Set recipients = Set.copyOf(event.getRecipients()); 26 | event.getRecipients().clear(); 27 | 28 | plugin.getServer().getPluginManager().callEvent(pistonChatEvent); 29 | 30 | event.setCancelled(pistonChatEvent.isCancelled()); 31 | 32 | if (pistonChatEvent.isCancelled()) { 33 | return; 34 | } 35 | 36 | if (!plugin.getTempDataTool().isChatEnabled(chatter)) { 37 | plugin.getCommonTool().sendLanguageMessage(chatter, "chatisoff"); 38 | event.setCancelled(true); 39 | return; 40 | } 41 | 42 | for (Player receiver : recipients) { 43 | if (plugin.getIgnoreTool().isIgnored(chatter, receiver) 44 | || !plugin.getTempDataTool().isChatEnabled(receiver)) { 45 | continue; 46 | } 47 | 48 | PistonChatReceiveEvent perPlayerEvent = new PistonChatReceiveEvent(chatter, receiver, pistonChatEvent.getMessage(), event.isAsynchronous()); 49 | 50 | plugin.getServer().getPluginManager().callEvent(perPlayerEvent); 51 | 52 | if (perPlayerEvent.isCancelled()) { 53 | continue; 54 | } 55 | 56 | plugin.getCommonTool().sendChatMessage(chatter, perPlayerEvent.getMessage(), receiver); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /PistonChat/src/main/java/net/pistonmaster/pistonchat/storage/PCStorage.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonchat.storage; 2 | 3 | import java.util.List; 4 | import java.util.UUID; 5 | 6 | public interface PCStorage { 7 | void setChatEnabled(UUID uuid, boolean enabled); 8 | 9 | boolean isChatEnabled(UUID uuid); 10 | 11 | void setWhisperingEnabled(UUID uuid, boolean enabled); 12 | 13 | boolean isWhisperingEnabled(UUID uuid); 14 | 15 | HardReturn hardIgnorePlayer(UUID ignoringReceiver, UUID ignoredChatter); 16 | 17 | boolean isHardIgnored(UUID chatter, UUID receiver); 18 | 19 | List getIgnoredList(UUID uuid); 20 | 21 | void clearIgnoredPlayers(UUID player); 22 | 23 | enum HardReturn { 24 | IGNORE, UN_IGNORE 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /PistonChat/src/main/java/net/pistonmaster/pistonchat/storage/file/FileStorage.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonchat.storage.file; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import com.google.gson.reflect.TypeToken; 6 | import net.md_5.bungee.api.ChatColor; 7 | import net.pistonmaster.pistonchat.storage.PCStorage; 8 | 9 | import java.io.IOException; 10 | import java.io.Reader; 11 | import java.io.Writer; 12 | import java.lang.reflect.Type; 13 | import java.nio.charset.StandardCharsets; 14 | import java.nio.file.Files; 15 | import java.nio.file.Path; 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | import java.util.Map; 19 | import java.util.UUID; 20 | import java.util.concurrent.ConcurrentHashMap; 21 | import java.util.logging.Logger; 22 | 23 | public class FileStorage implements PCStorage { 24 | private final Logger log; 25 | private final Gson gson; 26 | 27 | private final Path chatSettingsFile; 28 | private final Path whisperSettingsFile; 29 | private final Path ignoreListFile; 30 | 31 | private final Map chatSettings = new ConcurrentHashMap<>(); 32 | private final Map whisperSettings = new ConcurrentHashMap<>(); 33 | private final Map> ignoreList = new ConcurrentHashMap<>(); 34 | 35 | public FileStorage(Logger log, Path dataFolder) { 36 | this.log = log; 37 | 38 | log.info(ChatColor.DARK_GREEN + "Loading file storage"); 39 | 40 | gson = new GsonBuilder().setPrettyPrinting().create(); 41 | 42 | chatSettingsFile = dataFolder.resolve("chat_settings.json"); 43 | whisperSettingsFile = dataFolder.resolve("whisper_settings.json"); 44 | ignoreListFile = dataFolder.resolve("ignore_list.json"); 45 | 46 | // Load existing data 47 | loadData(); 48 | 49 | log.info(ChatColor.DARK_GREEN + "Loaded file storage"); 50 | } 51 | 52 | private void loadData() { 53 | try { 54 | // Load chat settings 55 | if (Files.exists(chatSettingsFile)) { 56 | Type type = new TypeToken>() { 57 | }.getType(); 58 | try (Reader reader = Files.newBufferedReader(chatSettingsFile, StandardCharsets.UTF_8)) { 59 | Map loaded = gson.fromJson(reader, type); 60 | if (loaded != null) { 61 | chatSettings.putAll(loaded); 62 | } 63 | } 64 | } 65 | 66 | // Load whisper settings 67 | if (Files.exists(whisperSettingsFile)) { 68 | Type type = new TypeToken>() { 69 | }.getType(); 70 | try (Reader reader = Files.newBufferedReader(whisperSettingsFile, StandardCharsets.UTF_8)) { 71 | Map loaded = gson.fromJson(reader, type); 72 | if (loaded != null) { 73 | whisperSettings.putAll(loaded); 74 | } 75 | } 76 | } 77 | 78 | // Load ignore lists 79 | if (Files.exists(ignoreListFile)) { 80 | Type type = new TypeToken>>() { 81 | }.getType(); 82 | try (Reader reader = Files.newBufferedReader(ignoreListFile, StandardCharsets.UTF_8)) { 83 | Map> loaded = gson.fromJson(reader, type); 84 | if (loaded != null) { 85 | ignoreList.putAll(loaded); 86 | } 87 | } 88 | } 89 | } catch (Exception e) { 90 | log.severe("Error loading data: " + e.getMessage()); 91 | } 92 | } 93 | 94 | private synchronized void saveMap(Map data, Path file) { 95 | try { 96 | Files.createDirectories(file.getParent()); 97 | try (Writer writer = Files.newBufferedWriter(file, StandardCharsets.UTF_8)) { 98 | gson.toJson(data, writer); 99 | } 100 | } catch (IOException e) { 101 | log.severe("Could not save data: " + e.getMessage()); 102 | } 103 | } 104 | 105 | @Override 106 | public void setChatEnabled(UUID uuid, boolean enabled) { 107 | chatSettings.put(uuid, enabled); 108 | synchronized (chatSettings) { 109 | saveMap(chatSettings, chatSettingsFile); 110 | } 111 | } 112 | 113 | @Override 114 | public boolean isChatEnabled(UUID uuid) { 115 | return chatSettings.getOrDefault(uuid, true); 116 | } 117 | 118 | @Override 119 | public void setWhisperingEnabled(UUID uuid, boolean enabled) { 120 | whisperSettings.put(uuid, enabled); 121 | synchronized (whisperSettings) { 122 | saveMap(whisperSettings, whisperSettingsFile); 123 | } 124 | } 125 | 126 | @Override 127 | public boolean isWhisperingEnabled(UUID uuid) { 128 | return whisperSettings.getOrDefault(uuid, true); 129 | } 130 | 131 | @Override 132 | public HardReturn hardIgnorePlayer(UUID ignoringReceiver, UUID ignoredChatter) { 133 | List ignored = ignoreList.computeIfAbsent(ignoringReceiver, k -> new ArrayList<>()); 134 | 135 | if (ignored.contains(ignoredChatter)) { 136 | ignored.remove(ignoredChatter); 137 | synchronized (ignored) { 138 | saveMap(ignoreList, ignoreListFile); 139 | } 140 | return HardReturn.UN_IGNORE; 141 | } else { 142 | ignored.add(ignoredChatter); 143 | synchronized (ignored) { 144 | saveMap(ignoreList, ignoreListFile); 145 | } 146 | return HardReturn.IGNORE; 147 | } 148 | } 149 | 150 | @Override 151 | public boolean isHardIgnored(UUID chatter, UUID receiver) { 152 | List ignored = ignoreList.get(receiver); 153 | return ignored != null && ignored.contains(chatter); 154 | } 155 | 156 | @Override 157 | public List getIgnoredList(UUID uuid) { 158 | List ignored = ignoreList.get(uuid); 159 | return ignored != null ? new ArrayList<>(ignored) : new ArrayList<>(); 160 | } 161 | 162 | @Override 163 | public void clearIgnoredPlayers(UUID player) { 164 | ignoreList.remove(player); 165 | synchronized (ignoreList) { 166 | saveMap(ignoreList, ignoreListFile); 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /PistonChat/src/main/java/net/pistonmaster/pistonchat/storage/mysql/MySQLStorage.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonchat.storage.mysql; 2 | 3 | import net.md_5.bungee.api.ChatColor; 4 | import net.pistonmaster.pistonchat.storage.PCStorage; 5 | import net.pistonmaster.pistonchat.utils.ConfigManager; 6 | import org.bukkit.configuration.file.FileConfiguration; 7 | import org.mariadb.jdbc.MariaDbPoolDataSource; 8 | 9 | import java.sql.Connection; 10 | import java.sql.PreparedStatement; 11 | import java.sql.ResultSet; 12 | import java.sql.SQLException; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.UUID; 16 | import java.util.logging.Logger; 17 | 18 | public class MySQLStorage implements PCStorage { 19 | private final MariaDbPoolDataSource ds; 20 | 21 | public MySQLStorage(Logger log, ConfigManager configManager) { 22 | log.info(ChatColor.DARK_GREEN + "Connecting to database"); 23 | ds = new MariaDbPoolDataSource(); 24 | FileConfiguration config = configManager.get(); 25 | try { 26 | ds.setUser(config.getString("mysql.username")); 27 | ds.setPassword(config.getString("mysql.password")); 28 | ds.setUrl("jdbc:mariadb://" + config.getString("mysql.host") + ":" + config.getInt("mysql.port") + 29 | "/" + config.getString("mysql.database") 30 | + "?sslMode=disable&serverTimezone=UTC&maxPoolSize=10" 31 | ); 32 | 33 | try (Connection connection = ds.getConnection()) { 34 | connection.createStatement().execute("CREATE TABLE IF NOT EXISTS `pistonchat_settings_chat` (`uuid` VARCHAR(36) NOT NULL," + 35 | "`chat_enabled` tinyint(1) NOT NULL," + 36 | "PRIMARY KEY (`uuid`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;"); 37 | connection.createStatement().execute("CREATE TABLE IF NOT EXISTS `pistonchat_settings_whisper` (`uuid` VARCHAR(36) NOT NULL," + 38 | "`whisper_enabled` tinyint(1) NOT NULL," + 39 | "PRIMARY KEY (`uuid`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;"); 40 | connection.createStatement().execute("CREATE TABLE IF NOT EXISTS `pistonchat_hard_ignores` (`uuid` VARCHAR(36) NOT NULL," + 41 | "`ignored_uuid` VARCHAR(36) NOT NULL," + 42 | "PRIMARY KEY (`uuid`, `ignored_uuid`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;"); 43 | } 44 | } catch (SQLException e) { 45 | e.printStackTrace(); 46 | } 47 | 48 | log.info(ChatColor.DARK_GREEN + "Connected to database"); 49 | } 50 | 51 | @Override 52 | public void setChatEnabled(UUID uuid, boolean enabled) { 53 | try (Connection connection = ds.getConnection()) { 54 | PreparedStatement statement = connection.prepareStatement("INSERT INTO `pistonchat_settings_chat` (`uuid`, `chat_enabled`) VALUES (?, ?) ON DUPLICATE KEY UPDATE `chat_enabled` = ?;"); 55 | statement.setString(1, uuid.toString()); 56 | statement.setBoolean(2, enabled); 57 | statement.setBoolean(3, enabled); 58 | statement.execute(); 59 | } catch (SQLException e) { 60 | throw new RuntimeException(e); 61 | } 62 | } 63 | 64 | @Override 65 | public boolean isChatEnabled(UUID uuid) { 66 | try (Connection connection = ds.getConnection()) { 67 | PreparedStatement statement = connection.prepareStatement("SELECT `chat_enabled` FROM `pistonchat_settings_chat` WHERE `uuid` = ?;"); 68 | statement.setString(1, uuid.toString()); 69 | 70 | ResultSet resultSet = statement.executeQuery(); 71 | if (!resultSet.next()) { 72 | return true; 73 | } 74 | 75 | return resultSet.getBoolean("chat_enabled"); 76 | } catch (SQLException e) { 77 | throw new RuntimeException(e); 78 | } 79 | } 80 | 81 | @Override 82 | public void setWhisperingEnabled(UUID uuid, boolean enabled) { 83 | try (Connection connection = ds.getConnection()) { 84 | PreparedStatement statement = connection.prepareStatement("INSERT INTO `pistonchat_settings_whisper` (`uuid`, `whisper_enabled`) VALUES (?, ?) ON DUPLICATE KEY UPDATE `whisper_enabled` = ?;"); 85 | statement.setString(1, uuid.toString()); 86 | statement.setBoolean(2, enabled); 87 | statement.setBoolean(3, enabled); 88 | statement.execute(); 89 | } catch (SQLException e) { 90 | throw new RuntimeException(e); 91 | } 92 | } 93 | 94 | @Override 95 | public boolean isWhisperingEnabled(UUID uuid) { 96 | try (Connection connection = ds.getConnection()) { 97 | PreparedStatement statement = connection.prepareStatement("SELECT `whisper_enabled` FROM `pistonchat_settings_whisper` WHERE `uuid` = ?;"); 98 | statement.setString(1, uuid.toString()); 99 | 100 | ResultSet resultSet = statement.executeQuery(); 101 | if (!resultSet.next()) { 102 | return true; 103 | } 104 | 105 | return resultSet.getBoolean("whisper_enabled"); 106 | } catch (SQLException e) { 107 | throw new RuntimeException(e); 108 | } 109 | } 110 | 111 | @Override 112 | public HardReturn hardIgnorePlayer(UUID ignoringReceiver, UUID ignoredChatter) { 113 | try (Connection connection = ds.getConnection()) { 114 | PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `pistonchat_hard_ignores` WHERE `uuid`=? AND `ignored_uuid`=?"); 115 | preparedStatement.setString(1, ignoringReceiver.toString()); 116 | preparedStatement.setString(2, ignoredChatter.toString()); 117 | 118 | if (preparedStatement.executeQuery().next()) { 119 | preparedStatement = connection.prepareStatement("DELETE FROM `pistonchat_hard_ignores` WHERE `uuid`=? AND `ignored_uuid`=?"); 120 | preparedStatement.setString(1, ignoringReceiver.toString()); 121 | preparedStatement.setString(2, ignoredChatter.toString()); 122 | preparedStatement.execute(); 123 | 124 | return HardReturn.UN_IGNORE; 125 | } else { 126 | preparedStatement = connection.prepareStatement("INSERT INTO `pistonchat_hard_ignores` (`uuid`, `ignored_uuid`) VALUES (?, ?)"); 127 | preparedStatement.setString(1, ignoringReceiver.toString()); 128 | preparedStatement.setString(2, ignoredChatter.toString()); 129 | preparedStatement.execute(); 130 | 131 | return HardReturn.IGNORE; 132 | } 133 | } catch (SQLException e) { 134 | throw new RuntimeException(e); 135 | } 136 | } 137 | 138 | @Override 139 | public boolean isHardIgnored(UUID chatter, UUID receiver) { 140 | try (Connection connection = ds.getConnection()) { 141 | PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `pistonchat_hard_ignores` WHERE `uuid`=? AND `ignored_uuid`=?"); 142 | preparedStatement.setString(1, receiver.toString()); 143 | preparedStatement.setString(2, chatter.toString()); 144 | return preparedStatement.executeQuery().next(); 145 | } catch (SQLException e) { 146 | throw new RuntimeException(e); 147 | } 148 | } 149 | 150 | @Override 151 | public List getIgnoredList(UUID uuid) { 152 | try (Connection connection = ds.getConnection()) { 153 | PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM `pistonchat_hard_ignores` WHERE `uuid`=?"); 154 | preparedStatement.setString(1, uuid.toString()); 155 | 156 | ResultSet resultSet = preparedStatement.executeQuery(); 157 | 158 | List uuids = new ArrayList<>(); 159 | while (resultSet.next()) { 160 | uuids.add(UUID.fromString(resultSet.getString("ignored_uuid"))); 161 | } 162 | 163 | return uuids; 164 | } catch (SQLException e) { 165 | throw new RuntimeException(e); 166 | } 167 | } 168 | 169 | @Override 170 | public void clearIgnoredPlayers(UUID player) { 171 | try (Connection connection = ds.getConnection()) { 172 | PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM `pistonchat_hard_ignores` WHERE `uuid`=?"); 173 | preparedStatement.setString(1, player.toString()); 174 | preparedStatement.execute(); 175 | 176 | } catch (SQLException e) { 177 | throw new RuntimeException(e); 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /PistonChat/src/main/java/net/pistonmaster/pistonchat/tools/CacheTool.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonchat.tools; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.pistonmaster.pistonchat.PistonChat; 5 | import net.pistonmaster.pistonchat.utils.PlatformUtils; 6 | import net.pistonmaster.pistonchat.utils.UniqueSender; 7 | import org.bukkit.command.CommandSender; 8 | import org.bukkit.entity.Player; 9 | import org.bukkit.metadata.FixedMetadataValue; 10 | import org.bukkit.metadata.MetadataValue; 11 | 12 | import java.util.*; 13 | 14 | @RequiredArgsConstructor 15 | public class CacheTool { 16 | private final PistonChat plugin; 17 | private final Map customMap = new HashMap<>(); 18 | 19 | public void sendMessage(CommandSender sender, CommandSender receiver) { 20 | UUID senderUUID = new UniqueSender(sender).getUniqueId(); 21 | UUID receiverUUID = new UniqueSender(receiver).getUniqueId(); 22 | if (sender instanceof Player senderPlayer) { 23 | senderPlayer.setMetadata("pistonchat_sentTo", new FixedMetadataValue(plugin, receiverUUID.toString())); 24 | } else { 25 | indexConsole(sender); 26 | customMap.get(sender).sentTo = receiverUUID; 27 | } 28 | 29 | if (receiver instanceof Player receiverPlayer) { 30 | receiverPlayer.setMetadata("pistonchat_messagedOf", new FixedMetadataValue(plugin, senderUUID.toString())); 31 | } else { 32 | indexConsole(receiver); 33 | customMap.get(receiver).messagedOf = senderUUID; 34 | } 35 | } 36 | 37 | /** 38 | * Get the last person a player sent a message to. 39 | * 40 | * @param sender The player to get data from. 41 | * @return The last person the player sent a message to. 42 | */ 43 | public Optional getLastSentTo(CommandSender sender) { 44 | UUID sentTo; 45 | if (sender instanceof Player player) { 46 | List values = player.getMetadata("pistonchat_sentTo"); 47 | if (values.isEmpty()) { 48 | return Optional.empty(); 49 | } 50 | 51 | sentTo = UUID.fromString(values.getFirst().asString()); 52 | } else { 53 | indexConsole(sender); 54 | sentTo = customMap.get(sender).sentTo; 55 | } 56 | 57 | if (sentTo == null) { 58 | return Optional.empty(); 59 | } 60 | 61 | Optional optionalPlayer = PlatformUtils.getPlayer(sentTo).map(p -> p); // Map to bypass type inference error 62 | return optionalPlayer.or(() -> UniqueSender.byUUID(sentTo)); 63 | } 64 | 65 | /** 66 | * Get the last person a player was messaged from. 67 | * 68 | * @param sender The player to get data from. 69 | * @return The last person the player was messaged from. 70 | */ 71 | public Optional getLastMessagedOf(CommandSender sender) { 72 | UUID messagedOf; 73 | if (sender instanceof Player player) { 74 | List values = player.getMetadata("pistonchat_messagedOf"); 75 | if (values.isEmpty()) { 76 | return Optional.empty(); 77 | } 78 | 79 | messagedOf = UUID.fromString(values.getFirst().asString()); 80 | } else { 81 | indexConsole(sender); 82 | messagedOf = customMap.get(sender).messagedOf; 83 | } 84 | 85 | if (messagedOf == null) { 86 | return Optional.empty(); 87 | } 88 | 89 | Optional optionalPlayer = PlatformUtils.getPlayer(messagedOf).map(p -> p); // Map to bypass type inference error 90 | return optionalPlayer.or(() -> UniqueSender.byUUID(messagedOf)); 91 | } 92 | 93 | private void indexConsole(CommandSender sender) { 94 | customMap.computeIfAbsent(sender, k -> new MessageData()); 95 | } 96 | 97 | @RequiredArgsConstructor 98 | private static class MessageData { 99 | public UUID sentTo = null; 100 | public UUID messagedOf = null; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /PistonChat/src/main/java/net/pistonmaster/pistonchat/tools/CommonTool.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonchat.tools; 2 | 3 | import io.github.miniplaceholders.api.MiniPlaceholders; 4 | import lombok.RequiredArgsConstructor; 5 | import net.kyori.adventure.audience.Audience; 6 | import net.kyori.adventure.text.Component; 7 | import net.kyori.adventure.text.event.ClickEvent; 8 | import net.kyori.adventure.text.event.HoverEvent; 9 | import net.kyori.adventure.text.format.NamedTextColor; 10 | import net.kyori.adventure.text.format.TextColor; 11 | import net.kyori.adventure.text.minimessage.MiniMessage; 12 | import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; 13 | import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; 14 | import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; 15 | import net.pistonmaster.pistonchat.PistonChat; 16 | import net.pistonmaster.pistonchat.api.PistonWhisperEvent; 17 | import org.bukkit.ChatColor; 18 | import org.bukkit.command.CommandSender; 19 | import org.bukkit.command.ConsoleCommandSender; 20 | import org.bukkit.configuration.ConfigurationSection; 21 | import org.bukkit.configuration.file.FileConfiguration; 22 | import org.bukkit.entity.Player; 23 | import org.bukkit.metadata.MetadataValue; 24 | 25 | import java.util.Arrays; 26 | import java.util.Locale; 27 | import java.util.Optional; 28 | 29 | @RequiredArgsConstructor 30 | public class CommonTool { 31 | private final PistonChat plugin; 32 | 33 | public static String mergeArgs(String[] args, int start) { 34 | return String.join(" ", Arrays.copyOfRange(args, start, args.length)); 35 | } 36 | 37 | private static boolean isVanished(Player player) { 38 | for (MetadataValue meta : player.getMetadata("vanished")) { 39 | if (meta.asBoolean()) { 40 | return true; 41 | } 42 | } 43 | 44 | return false; 45 | } 46 | 47 | public static TagResolver getStrippedNameResolver(Player player) { 48 | return Placeholder.unparsed("player_name", ChatColor.stripColor(player.getDisplayName())); 49 | } 50 | 51 | public void sendWhisperTo(CommandSender sender, String message, CommandSender receiver) { 52 | if (!plugin.getConfig().getBoolean("allow-pm-self") && sender == receiver) { 53 | sendLanguageMessage(sender, "pmself"); 54 | return; 55 | } 56 | 57 | if (!sender.hasPermission("pistonchat.bypass")) { 58 | if (receiver instanceof Player player && !plugin.getTempDataTool().isWhisperingEnabled(player)) { 59 | if (plugin.getConfig().getBoolean("only-hide-pms")) { 60 | sendSender(sender, message, receiver); 61 | } else { 62 | sendLanguageMessage(sender, "whispering-disabled"); 63 | } 64 | return; 65 | } 66 | 67 | if (receiver instanceof Player player && isVanished(player)) { 68 | sendLanguageMessage(sender, "notonline"); 69 | return; 70 | } 71 | } 72 | 73 | PistonWhisperEvent pistonWhisperEvent = new PistonWhisperEvent(sender, receiver, message); 74 | 75 | plugin.getServer().getPluginManager().callEvent(pistonWhisperEvent); 76 | 77 | if (pistonWhisperEvent.isCancelled()) { 78 | return; 79 | } 80 | 81 | message = pistonWhisperEvent.getMessage(); 82 | 83 | sendSender(sender, message, receiver); 84 | sendReceiver(sender, message, receiver); 85 | 86 | plugin.getCacheTool().sendMessage(sender, receiver); 87 | } 88 | 89 | public void sendSender(CommandSender sender, String message, CommandSender receiver) { 90 | Audience senderAudience = senderAudience(sender); 91 | Audience receiverAudience = senderAudience(receiver); 92 | String senderString = plugin.getConfig().getString("whisper.to"); 93 | TagResolver tagResolver = TagResolver.resolver( 94 | getMiniPlaceholdersTagResolver(senderAudience, receiverAudience), 95 | Placeholder.unparsed("message", message), 96 | getDisplayNameResolver(receiver) 97 | ); 98 | 99 | senderAudience.sendMessage(MiniMessage.miniMessage().deserialize(senderString, tagResolver)); 100 | } 101 | 102 | private void sendReceiver(CommandSender sender, String message, CommandSender receiver) { 103 | Audience senderAudience = senderAudience(sender); 104 | Audience receiverAudience = senderAudience(receiver); 105 | String senderString = plugin.getConfig().getString("whisper.from"); 106 | TagResolver tagResolver = TagResolver.resolver( 107 | getMiniPlaceholdersTagResolver(senderAudience, receiverAudience), 108 | Placeholder.unparsed("message", message), 109 | getDisplayNameResolver(sender) 110 | ); 111 | 112 | receiverAudience.sendMessage(MiniMessage.miniMessage().deserialize(senderString, tagResolver)); 113 | } 114 | 115 | public Component getLanguageMessage(String messageKey, boolean prefix, TagResolver... tagResolvers) { 116 | String messageString = plugin.getLanguage().getString(messageKey); 117 | Component messageComponent = MiniMessage.miniMessage().deserialize(messageString, tagResolvers); 118 | 119 | if (!prefix) { 120 | return messageComponent; 121 | } 122 | 123 | String formatString = plugin.getLanguage().getString("format"); 124 | 125 | TagResolver tagResolver = TagResolver.resolver( 126 | Placeholder.component("message", messageComponent) 127 | ); 128 | 129 | return MiniMessage.miniMessage().deserialize(formatString, tagResolver); 130 | } 131 | 132 | public void sendLanguageMessage(CommandSender sender, String messageKey, TagResolver... tagResolvers) { 133 | senderAudience(sender).sendMessage(getLanguageMessage(messageKey, true, tagResolvers)); 134 | } 135 | 136 | public void sendLanguageMessageNoPrefix(CommandSender sender, String messageKey, TagResolver... tagResolvers) { 137 | senderAudience(sender).sendMessage(getLanguageMessage(messageKey, false, tagResolvers)); 138 | } 139 | 140 | public Optional getChatColorFor(String message, Player player) { 141 | FileConfiguration config = plugin.getConfig(); 142 | 143 | for (String str : config.getConfigurationSection("prefixes").getKeys(false)) { 144 | ConfigurationSection section = config.getConfigurationSection("prefixes." + str); 145 | String prefix = section.getString("prefix"); 146 | if (!prefix.equalsIgnoreCase("/") 147 | && message.toLowerCase().startsWith(prefix) 148 | && player.hasPermission("pistonchat.prefix." + str.toLowerCase())) { 149 | return Optional.of(NamedTextColor.NAMES.valueOrThrow(section.getString("color").toLowerCase(Locale.ROOT))); 150 | } 151 | } 152 | 153 | return Optional.empty(); 154 | } 155 | 156 | public void sendChatMessage(Player chatter, String message, Player receiver) { 157 | Audience chatterAudience = senderAudience(chatter); 158 | Audience receiverAudience = senderAudience(receiver); 159 | TagResolver miniPlaceholderResolver = getMiniPlaceholdersTagResolver(chatterAudience, receiverAudience); 160 | Component formatComponent = getFormat(chatter, miniPlaceholderResolver); 161 | 162 | if (receiver.hasPermission("pistonchat.playernamereply")) { 163 | String hoverText = plugin.getConfig().getString("hover-text"); 164 | 165 | formatComponent = formatComponent 166 | .clickEvent(ClickEvent.suggestCommand(String.format("/w %s ", chatter.getName()))) 167 | .hoverEvent(HoverEvent.showText(MiniMessage.miniMessage().deserialize(hoverText, TagResolver.resolver( 168 | miniPlaceholderResolver, 169 | getStrippedNameResolver(chatter) 170 | )))); 171 | } 172 | 173 | Component messageComponent = Component.text(message); 174 | Optional messagePrefixColor = getChatColorFor(message, chatter); 175 | if (messagePrefixColor.isPresent()) { 176 | messageComponent = messageComponent.color(messagePrefixColor.get()); 177 | } 178 | 179 | String messageFormat = plugin.getConfig().getString("message-format"); 180 | 181 | receiverAudience.sendMessage(MiniMessage.miniMessage().deserialize(messageFormat, TagResolver.resolver( 182 | miniPlaceholderResolver, 183 | Placeholder.component("message", messageComponent), 184 | Placeholder.component("format", formatComponent) 185 | ))); 186 | } 187 | 188 | public Component getFormat(Player chatter, TagResolver miniPlaceholderResolver) { 189 | String formatString = ""; 190 | for (String s : plugin.getConfig().getConfigurationSection("chat-formats").getKeys(false)) { 191 | if (chatter.hasPermission("pistonchat.chatformat." + s.toLowerCase(Locale.ROOT))) { 192 | formatString = plugin.getConfig().getString("chat-formats." + s); 193 | break; 194 | } 195 | } 196 | 197 | return MiniMessage.miniMessage().deserialize(formatString, TagResolver.resolver( 198 | miniPlaceholderResolver, 199 | getDisplayNameResolver(chatter) 200 | )); 201 | } 202 | 203 | public TagResolver getDisplayNameResolver(CommandSender sender) { 204 | if (sender instanceof Player player) { 205 | if (plugin.getConfig().getBoolean("strip-name-color")) { 206 | return getStrippedNameResolver(player); 207 | } else { 208 | return Placeholder.component("player_name", 209 | LegacyComponentSerializer.legacyAmpersand().deserialize(player.getDisplayName())); 210 | } 211 | } else if (sender instanceof ConsoleCommandSender) { 212 | return Placeholder.parsed("player_name", plugin.getConfig().getString("console-name")); 213 | } else { 214 | return Placeholder.unparsed("player_name", sender.getName()); 215 | } 216 | } 217 | 218 | private TagResolver getMiniPlaceholdersTagResolver(Audience mainAudience, Audience otherAudience) { 219 | if (plugin.getServer().getPluginManager().isPluginEnabled("MiniPlaceholders")) { 220 | return MiniPlaceholders.getRelationalGlobalPlaceholders(mainAudience, otherAudience); 221 | } else { 222 | return TagResolver.empty(); 223 | } 224 | } 225 | 226 | private Audience senderAudience(CommandSender sender) { 227 | if (sender instanceof Audience audience) { 228 | return audience; 229 | } else { 230 | return plugin.getAdventure().sender(sender); 231 | } 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /PistonChat/src/main/java/net/pistonmaster/pistonchat/tools/HardIgnoreTool.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonchat.tools; 2 | 3 | import com.github.benmanes.caffeine.cache.Caffeine; 4 | import com.github.benmanes.caffeine.cache.LoadingCache; 5 | import lombok.RequiredArgsConstructor; 6 | import net.pistonmaster.pistonchat.PistonChat; 7 | import net.pistonmaster.pistonchat.storage.PCStorage; 8 | import net.pistonmaster.pistonchat.utils.UniqueSender; 9 | import org.bukkit.command.CommandSender; 10 | import org.bukkit.entity.Player; 11 | 12 | import java.util.List; 13 | import java.util.UUID; 14 | import java.util.concurrent.TimeUnit; 15 | 16 | @RequiredArgsConstructor 17 | public class HardIgnoreTool { 18 | private final PistonChat plugin; 19 | private final LoadingCache ignoreCache = Caffeine.newBuilder() 20 | .expireAfterWrite(5, TimeUnit.SECONDS) 21 | .build(this::loadIsHardIgnored); 22 | 23 | public PCStorage.HardReturn hardIgnorePlayer(Player ignoringReceiver, Player ignoredChatter) { 24 | var hardReturn = plugin.getStorage().hardIgnorePlayer(ignoringReceiver.getUniqueId(), ignoredChatter.getUniqueId()); 25 | return switch (hardReturn) { 26 | case IGNORE -> { 27 | ignoreCache.put(new IgnorePair(ignoredChatter.getUniqueId(), ignoringReceiver.getUniqueId()), true); 28 | yield PCStorage.HardReturn.IGNORE; 29 | } 30 | case UN_IGNORE -> { 31 | ignoreCache.put(new IgnorePair(ignoredChatter.getUniqueId(), ignoringReceiver.getUniqueId()), false); 32 | yield PCStorage.HardReturn.UN_IGNORE; 33 | } 34 | }; 35 | } 36 | 37 | protected boolean isHardIgnored(CommandSender chatter, Player receiver) { 38 | UUID chatterUUID = new UniqueSender(chatter).getUniqueId(); 39 | 40 | return Boolean.TRUE.equals(ignoreCache.get(new IgnorePair(chatterUUID, receiver.getUniqueId()))); 41 | } 42 | 43 | private boolean loadIsHardIgnored(IgnorePair pair) { 44 | return plugin.getStorage().isHardIgnored(pair.chatter(), pair.receiver()); 45 | } 46 | 47 | public List getStoredList(Player player) { 48 | return plugin.getStorage().getIgnoredList(player.getUniqueId()); 49 | } 50 | 51 | public void clearIgnoredPlayers(Player player) { 52 | plugin.getStorage().clearIgnoredPlayers(player.getUniqueId()); 53 | ignoreCache.asMap().keySet().removeIf(pair -> pair.chatter().equals(player.getUniqueId())); 54 | } 55 | 56 | private record IgnorePair(UUID chatter, UUID receiver) { 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /PistonChat/src/main/java/net/pistonmaster/pistonchat/tools/IgnoreTool.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonchat.tools; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.pistonmaster.pistonchat.PistonChat; 5 | import org.bukkit.Bukkit; 6 | import org.bukkit.OfflinePlayer; 7 | import org.bukkit.command.CommandSender; 8 | import org.bukkit.entity.Player; 9 | 10 | import java.util.*; 11 | 12 | /** 13 | * Parent for both soft and hard ignoring! 14 | */ 15 | @RequiredArgsConstructor 16 | public class IgnoreTool { 17 | private final PistonChat plugin; 18 | 19 | public boolean isIgnored(CommandSender chatter, CommandSender receiver) { 20 | if (receiver instanceof Player player) { 21 | if (plugin.getSoftignoreTool().isSoftIgnored(chatter, player)) { 22 | return true; 23 | } else return plugin.getHardIgnoreTool().isHardIgnored(chatter, player); 24 | } else { 25 | return false; 26 | } 27 | } 28 | 29 | public Map getIgnoredPlayers(Player player) { 30 | Map map = new HashMap<>(); 31 | 32 | for (OfflinePlayer ignoredPlayer : convertIgnoredPlayer(plugin.getSoftignoreTool().getStoredList(player))) { 33 | map.put(ignoredPlayer, IgnoreType.SOFT); 34 | } 35 | 36 | for (OfflinePlayer ignoredPlayer : convertIgnoredPlayer(plugin.getHardIgnoreTool().getStoredList(player))) { 37 | map.put(ignoredPlayer, IgnoreType.HARD); 38 | } 39 | 40 | return map; 41 | } 42 | 43 | protected List convertIgnoredPlayer(List listUUID) { 44 | List returnedPlayers = new ArrayList<>(); 45 | 46 | for (UUID str : listUUID) { 47 | returnedPlayers.add(Bukkit.getOfflinePlayer(str)); 48 | } 49 | 50 | return returnedPlayers; 51 | } 52 | 53 | public void clearIgnoredPlayers(Player player) { 54 | plugin.getSoftignoreTool().clearIgnoredPlayers(player); 55 | plugin.getHardIgnoreTool().clearIgnoredPlayers(player); 56 | } 57 | 58 | public enum IgnoreType { 59 | SOFT, HARD 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /PistonChat/src/main/java/net/pistonmaster/pistonchat/tools/SoftIgnoreTool.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonchat.tools; 2 | 3 | import com.google.gson.Gson; 4 | import lombok.RequiredArgsConstructor; 5 | import net.pistonmaster.pistonchat.PistonChat; 6 | import net.pistonmaster.pistonchat.utils.UniqueSender; 7 | import org.bukkit.command.CommandSender; 8 | import org.bukkit.entity.Player; 9 | import org.bukkit.metadata.FixedMetadataValue; 10 | import org.bukkit.metadata.MetadataValue; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.UUID; 15 | 16 | @RequiredArgsConstructor 17 | public class SoftIgnoreTool { 18 | private static final String METADATA_KEY = "pistonchat_softignore"; 19 | private final PistonChat plugin; 20 | private final Gson gson = new Gson(); 21 | 22 | public SoftReturn softIgnorePlayer(Player ignoringReceiver, Player ignoredChatter) { 23 | List list = new ArrayList<>(getStoredList(ignoringReceiver)); 24 | 25 | boolean contains = list.contains(ignoredChatter.getUniqueId()); 26 | if (contains) { 27 | list.remove(ignoredChatter.getUniqueId()); 28 | } else { 29 | list.add(ignoredChatter.getUniqueId()); 30 | } 31 | 32 | ignoringReceiver.setMetadata(METADATA_KEY, new FixedMetadataValue(plugin, gson.toJson(list))); 33 | 34 | return contains ? SoftReturn.UN_IGNORE : SoftReturn.IGNORE; 35 | } 36 | 37 | protected boolean isSoftIgnored(CommandSender chatter, Player receiver) { 38 | UUID chatterUUID = new UniqueSender(chatter).getUniqueId(); 39 | return getStoredList(receiver).contains(chatterUUID); 40 | } 41 | 42 | protected List getStoredList(Player player) { 43 | List values = player.getMetadata(METADATA_KEY); 44 | if (values.isEmpty()) { 45 | return new ArrayList<>(); 46 | } 47 | 48 | return gson.>fromJson(values.getFirst().asString(), List.class).stream().map(UUID::fromString).toList(); 49 | } 50 | 51 | public void clearIgnoredPlayers(Player player) { 52 | player.removeMetadata(METADATA_KEY, plugin); 53 | } 54 | 55 | public enum SoftReturn { 56 | IGNORE, UN_IGNORE 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /PistonChat/src/main/java/net/pistonmaster/pistonchat/tools/TempDataTool.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonchat.tools; 2 | 3 | import com.github.benmanes.caffeine.cache.Caffeine; 4 | import com.github.benmanes.caffeine.cache.LoadingCache; 5 | import lombok.RequiredArgsConstructor; 6 | import net.pistonmaster.pistonchat.PistonChat; 7 | import org.bukkit.entity.Player; 8 | 9 | import java.util.UUID; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | @RequiredArgsConstructor 13 | public class TempDataTool { 14 | private final PistonChat plugin; 15 | private final LoadingCache whisperCache = Caffeine.newBuilder() 16 | .expireAfterWrite(5, TimeUnit.SECONDS) 17 | .build(this::loadIsWhisperingEnabled); 18 | private final LoadingCache chatCache = Caffeine.newBuilder() 19 | .expireAfterWrite(5, TimeUnit.SECONDS) 20 | .build(this::loadIsChatEnabled); 21 | 22 | public void setWhisperingEnabled(Player player, boolean value) { 23 | plugin.getStorage().setWhisperingEnabled(player.getUniqueId(), value); 24 | whisperCache.put(player.getUniqueId(), value); 25 | } 26 | 27 | public void setChatEnabled(Player player, boolean value) { 28 | plugin.getStorage().setChatEnabled(player.getUniqueId(), value); 29 | chatCache.put(player.getUniqueId(), value); 30 | } 31 | 32 | @SuppressWarnings("BooleanMethodIsAlwaysInverted") 33 | public boolean isWhisperingEnabled(Player player) { 34 | return Boolean.TRUE.equals(whisperCache.get(player.getUniqueId())); 35 | } 36 | 37 | private boolean loadIsWhisperingEnabled(UUID uuid) { 38 | return plugin.getStorage().isWhisperingEnabled(uuid); 39 | } 40 | 41 | @SuppressWarnings("BooleanMethodIsAlwaysInverted") 42 | public boolean isChatEnabled(Player player) { 43 | return Boolean.TRUE.equals(chatCache.get(player.getUniqueId())); 44 | } 45 | 46 | private boolean loadIsChatEnabled(UUID uuid) { 47 | return plugin.getStorage().isChatEnabled(uuid); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /PistonChat/src/main/java/net/pistonmaster/pistonchat/utils/ConfigManager.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonchat.utils; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.pistonmaster.pistonchat.PistonChat; 5 | import org.bukkit.configuration.InvalidConfigurationException; 6 | import org.bukkit.configuration.file.FileConfiguration; 7 | import org.bukkit.configuration.file.YamlConfiguration; 8 | 9 | import java.io.File; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.io.InputStreamReader; 13 | import java.nio.file.Files; 14 | 15 | @RequiredArgsConstructor 16 | public class ConfigManager { 17 | private final PistonChat plugin; 18 | private final String fileName; 19 | private FileConfiguration config; 20 | 21 | public void create() throws IOException { 22 | createIfAbsent(); 23 | 24 | config = getConfig(); 25 | config.setDefaults(getDefaultConfig()); 26 | config.options().copyHeader(true); 27 | config.options().copyDefaults(true); 28 | 29 | config.save(getConfigFile()); 30 | } 31 | 32 | public FileConfiguration get() { 33 | return config; 34 | } 35 | 36 | private YamlConfiguration getDefaultConfig() { 37 | YamlConfiguration config = new YamlConfiguration(); 38 | 39 | try ( 40 | InputStream inputStream = getDefaultInput(); 41 | InputStreamReader reader = new InputStreamReader(inputStream) 42 | ) { 43 | config.load(reader); 44 | } catch (IOException | InvalidConfigurationException ex) { 45 | throw new IllegalStateException("Cannot load default config", ex); 46 | } 47 | 48 | return config; 49 | } 50 | 51 | private YamlConfiguration getConfig() { 52 | YamlConfiguration config = new YamlConfiguration(); 53 | 54 | try { 55 | config.load(getConfigFile()); 56 | } catch (IOException | InvalidConfigurationException ex) { 57 | throw new IllegalStateException("Cannot load config", ex); 58 | } 59 | 60 | return config; 61 | } 62 | 63 | private void createIfAbsent() throws IOException { 64 | File configFile = getConfigFile(); 65 | 66 | if (!plugin.getDataFolder().exists() && !plugin.getDataFolder().mkdir()) { 67 | throw new IOException("Could not create data folder!"); 68 | } 69 | 70 | if (!configFile.exists()) { 71 | try (InputStream is = getDefaultInput()) { 72 | Files.copy(is, configFile.toPath()); 73 | } 74 | } 75 | } 76 | 77 | private File getConfigFile() { 78 | return new File(plugin.getDataFolder(), fileName); 79 | } 80 | 81 | private InputStream getDefaultInput() { 82 | return plugin.getResource(fileName); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /PistonChat/src/main/java/net/pistonmaster/pistonchat/utils/PlatformUtils.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonchat.utils; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.entity.Player; 5 | 6 | import java.util.Optional; 7 | import java.util.UUID; 8 | 9 | public class PlatformUtils { 10 | public static Optional getPlayer(String name) { 11 | return Optional.ofNullable(Bukkit.getPlayer(name)); 12 | } 13 | 14 | public static Optional getPlayer(UUID uuid) { 15 | return Optional.ofNullable(Bukkit.getPlayer(uuid)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /PistonChat/src/main/java/net/pistonmaster/pistonchat/utils/UniqueSender.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonchat.utils; 2 | 3 | import org.bukkit.command.CommandSender; 4 | import org.bukkit.entity.Player; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.Optional; 9 | import java.util.UUID; 10 | 11 | public record UniqueSender(CommandSender sender) { 12 | private static final Map customUUID = new HashMap<>(); 13 | 14 | public static Optional byUUID(UUID uuid) { 15 | for (Map.Entry entry : customUUID.entrySet()) { 16 | if (entry.getValue().equals(uuid)) { 17 | return Optional.of(entry.getKey()); 18 | } 19 | } 20 | 21 | return Optional.empty(); 22 | } 23 | 24 | public UUID getUniqueId() { 25 | if (sender instanceof Player player) { 26 | return player.getUniqueId(); 27 | } else { 28 | return customUUID.computeIfAbsent(sender, sender2 -> UUID.randomUUID()); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /PistonChat/src/main/resources/config.yml: -------------------------------------------------------------------------------- 1 | # To use these prefixes you need additionally the pistonchat. 2 | # / indicates disabled! 3 | # This config is configured to be what 2b2t.org has. 4 | # https://hub.spigotmc.org/javadocs/spigot/org/bukkit/ChatColor.html 5 | # pistonchat.prefix.green and pistonchat.chatformat.default are given by default. 6 | # storage is either file or mysql 7 | 8 | whisper: 9 | from: " whispers: " 10 | to: "You whisper to : " 11 | hover-text: "Message " 12 | 13 | storage: "file" 14 | mysql: 15 | host: "localhost" 16 | port: 3306 17 | database: "pistonchat" 18 | username: "root" 19 | password: "" 20 | 21 | strip-name-color: true 22 | chat-formats: 23 | special: " »" 24 | default: "<>" 25 | 26 | message-format: " " 27 | 28 | console-name: "[console]" 29 | 30 | allow-pm-self: true 31 | allow-pm-ignored: true 32 | only-hide-pms: true 33 | 34 | ignore-list-size: 9 35 | prefixes: 36 | green: 37 | prefix: ">" 38 | color: "GREEN" 39 | -------------------------------------------------------------------------------- /PistonChat/src/main/resources/language.yml: -------------------------------------------------------------------------------- 1 | # You can find pre translated languages on crowdin: 2 | # https://crowdin.com/project/pistonchat 3 | # The prefix can also be set to just "&6" 4 | format: "[PistonChat] " 5 | help-header: "---[PistonChat]---" 6 | playeronly: "You need to be a player to do this." 7 | notonline: "This player is not online." 8 | nooneignored: "No players ignored." 9 | chaton: "Chat messages unhidden." 10 | chatoff: "Chat messages hidden." 11 | pmson: "Private messages unhidden." 12 | pmsoff: "Private messages hidden." 13 | pmself: "Please do not private message yourself." 14 | chatisoff: "You have toggled off chat" 15 | source-ignored: "This person ignores you!" 16 | target-ignored: "You ignore this person!" 17 | page-not-exists: "This page doesn't exist." 18 | not-a-number: "Not a number!" 19 | whispering-disabled: "This person has whispering disabled!" 20 | ignore: "Now ignoring " 21 | unignore: "No longer ignoring " 22 | ignorehard: "Permanently ignoring . This is saved in /ignorelist." 23 | unignorehard: "No longer permanently ignoring " 24 | ignorelistcleared: "Ignore list cleared." 25 | -------------------------------------------------------------------------------- /PistonChat/src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: PistonChat 2 | version: ${version} 3 | description: ${description} 4 | website: ${url} 5 | api-version: 1.13 6 | main: net.pistonmaster.pistonchat.PistonChat 7 | authors: [ AlexProgrammerDE ] 8 | softdepend: ["MiniPlaceholders"] 9 | folia-supported: true 10 | 11 | commands: 12 | ignore: 13 | description: Ignore a players chat messages to the next server restart! 14 | permission: pistonchat.ignore 15 | permission-message: You have no permission to do that! 16 | usage: "§4Usage: / " 17 | ignorehard: 18 | description: Ignore a players chat messages permanently! 19 | permission: pistonchat.ignorehard 20 | permission-message: You have no permission to do that! 21 | usage: "§4Usage: / " 22 | whisper: 23 | description: Whisper to a player! 24 | permission: pistonchat.whisper 25 | permission-message: You have no permission to do that! 26 | usage: "§4Usage: / " 27 | aliases: 28 | - tell 29 | - w 30 | - pm 31 | - msg 32 | reply: 33 | description: Reply to the last message someone sent to you! 34 | permission: pistonchat.reply 35 | permission-message: You have no permission to do that! 36 | usage: "§4Usage: / " 37 | aliases: 38 | - r 39 | last: 40 | description: Message the last person you messaged! 41 | permission: pistonchat.reply 42 | permission-message: You have no permission to do that! 43 | usage: "§4Usage: / " 44 | aliases: 45 | - l 46 | ignorelist: 47 | description: List all ignored players! 48 | permission: pistonchat.ignorelist 49 | permission-message: You have no permission to do that! 50 | usage: "§4Usage: /" 51 | togglewhispering: 52 | description: Prevent getting whispered to! 53 | permission: pistonchat.togglewhispering 54 | permission-message: You have no permission to do that! 55 | usage: "§4Usage: /" 56 | aliases: 57 | - toggleprivatemsgs 58 | - toggletells 59 | togglechat: 60 | description: Prevent getting chat messages! 61 | permission: pistonchat.togglechat 62 | permission-message: You have no permission to do that! 63 | usage: "§4Usage: /" 64 | pistonchat: 65 | description: Main command! 66 | permission: pistonchat.command 67 | permission-message: You have no permission to do that! 68 | usage: "§4Usage: / " 69 | permissions: 70 | pistonchat.ignorehard: 71 | description: Permission for /ignorehard! 72 | children: 73 | pistonchat.ignorelist: true 74 | pistonchat.last: 75 | description: Permission for /last! 76 | pistonchat.reply: 77 | description: Permission for /reply! 78 | pistonchat.whisper: 79 | description: Permission for /whisper! 80 | children: 81 | pistonchat.playernamereply: true 82 | pistonchat.togglewhispering: true 83 | pistonchat.ignore: 84 | description: Permission for /ignore! 85 | children: 86 | pistonchat.ignorelist: true 87 | pistonchat.playernamereply: 88 | description: Make player names clickable! 89 | pistonchat.ignorelist: 90 | description: Permission for /ignorelist! 91 | pistonchat.togglewhispering: 92 | description: Permission for /togglewhispering! 93 | pistonchat.togglechat: 94 | description: Permission for /togglechat! 95 | pistonchat.command: 96 | description: Permission for /pistonchat! 97 | pistonchat.help: 98 | description: Permission for /pistonchat help! 99 | children: 100 | pistonchat.command: true 101 | pistonchat.reload: 102 | description: Permission for /pistonchat reload! 103 | children: 104 | pistonchat.command: true 105 | pistonchat.version: 106 | description: Permission for /pistonchat version 107 | pistonchat.bypass: 108 | description: Allows to talk to vanished people or people with whispering disabled. 109 | pistonchat.player: 110 | description: Default permissions for players! 111 | children: 112 | pistonchat.last: true 113 | pistonchat.reply: true 114 | pistonchat.whisper: true 115 | pistonchat.ignore: true 116 | pistonchat.ignorehard: true 117 | pistonchat.togglechat: true 118 | pistonchat.help: true 119 | pistonchat.prefix.green: true 120 | pistonchat.chatformat.default: true 121 | default: true 122 | pistonchat.admin: 123 | description: Default permissions for admins! 124 | children: 125 | pistonchat.reload: true 126 | pistonchat.version: true 127 | pistonchat.bypass: true 128 | default: op 129 | -------------------------------------------------------------------------------- /PistonFilter/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("pc.shadow-conventions") 3 | alias(libs.plugins.runpaper) 4 | } 5 | 6 | description = "Chat filter addon for PistonChat." 7 | 8 | dependencies { 9 | compileOnly("org.spigotmc:spigot-api:1.12.2-R0.1-SNAPSHOT") 10 | compileOnly(projects.pistonChat) 11 | compileOnly(projects.pistonMute) 12 | 13 | implementation("net.pistonmaster:PistonUtils:1.4.0") 14 | implementation("me.xdrop:fuzzywuzzy:1.4.0") 15 | testImplementation("org.junit.jupiter:junit-jupiter-api:5.13.0") 16 | testImplementation("org.junit.jupiter:junit-jupiter-engine:5.13.0") 17 | implementation("org.bstats:bstats-bukkit:3.1.0") 18 | implementation("org.apache.commons:commons-collections4:4.5.0") 19 | } 20 | 21 | tasks { 22 | runServer { 23 | minecraftVersion(libs.versions.runpaperversion.get()) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /PistonFilter/src/main/java/net/pistonmaster/pistonfilter/PistonFilter.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonfilter; 2 | 3 | import net.md_5.bungee.api.ChatColor; 4 | import net.pistonmaster.pistonfilter.commands.FilterCommand; 5 | import net.pistonmaster.pistonfilter.listeners.ChatListener; 6 | import net.pistonmaster.pistonutils.update.GitHubUpdateChecker; 7 | import net.pistonmaster.pistonutils.update.SemanticVersion; 8 | import org.bstats.bukkit.Metrics; 9 | import org.bukkit.Server; 10 | import org.bukkit.command.PluginCommand; 11 | import org.bukkit.plugin.java.JavaPlugin; 12 | 13 | import java.io.IOException; 14 | import java.util.logging.Logger; 15 | 16 | public class PistonFilter extends JavaPlugin { 17 | @Override 18 | public void onEnable() { 19 | Logger log = getLogger(); 20 | Server server = getServer(); 21 | 22 | log.info(ChatColor.AQUA + "Loading config"); 23 | saveDefaultConfig(); 24 | 25 | log.info(ChatColor.AQUA + "Registering commands"); 26 | PluginCommand main = server.getPluginCommand("pistonfilter"); 27 | 28 | assert main != null; 29 | main.setExecutor(new FilterCommand(this)); 30 | main.setTabCompleter(new FilterCommand(this)); 31 | 32 | log.info(ChatColor.AQUA + "Registering listeners"); 33 | getServer().getPluginManager().registerEvents(new ChatListener(this), this); 34 | 35 | log.info(ChatColor.AQUA + "Loading metrics"); 36 | new Metrics(this, 11561); 37 | 38 | log.info(ChatColor.AQUA + "Checking for a newer version"); 39 | try { 40 | String currentVersionString = this.getDescription().getVersion(); 41 | SemanticVersion gitHubVersion = new GitHubUpdateChecker() 42 | .getVersion("https://api.github.com/repos/AlexProgrammerDE/PistonChat/releases/latest"); 43 | SemanticVersion currentVersion = SemanticVersion.fromString(currentVersionString); 44 | 45 | if (gitHubVersion.isNewerThan(currentVersion)) { 46 | log.info(ChatColor.RED + "There is an update available!"); 47 | log.info(ChatColor.RED + "Current version: " + currentVersionString + " New version: " + gitHubVersion); 48 | log.info(ChatColor.RED + "Download it at: https://modrinth.com/plugin/pistonchat"); 49 | } else { 50 | log.info(ChatColor.AQUA + "You're up to date!"); 51 | } 52 | } catch (IOException e) { 53 | log.severe("Could not check for updates!"); 54 | e.printStackTrace(); 55 | } 56 | 57 | log.info(ChatColor.AQUA + "Done! :D"); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /PistonFilter/src/main/java/net/pistonmaster/pistonfilter/commands/FilterCommand.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonfilter.commands; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.md_5.bungee.api.ChatColor; 5 | import net.pistonmaster.pistonfilter.PistonFilter; 6 | import org.bukkit.command.Command; 7 | import org.bukkit.command.CommandExecutor; 8 | import org.bukkit.command.CommandSender; 9 | import org.bukkit.command.TabExecutor; 10 | import org.bukkit.configuration.file.FileConfiguration; 11 | import org.bukkit.util.StringUtil; 12 | 13 | import java.io.File; 14 | import java.io.IOException; 15 | import java.util.ArrayList; 16 | import java.util.Collections; 17 | import java.util.List; 18 | import java.util.stream.Collectors; 19 | import java.util.stream.Stream; 20 | 21 | @RequiredArgsConstructor 22 | public class FilterCommand implements CommandExecutor, TabExecutor { 23 | private final PistonFilter plugin; 24 | 25 | @Override 26 | public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { 27 | if (sender.hasPermission("pistonfilter.admin") && args.length > 0) { 28 | if (args[0].equalsIgnoreCase("reload")) { 29 | plugin.reloadConfig(); 30 | sender.sendMessage(ChatColor.GOLD + "Reloaded the config!"); 31 | } 32 | 33 | if (args[0].equalsIgnoreCase("add") && args.length > 1) { 34 | FileConfiguration config = plugin.getConfig(); 35 | 36 | config.set("banned-text", Stream.concat(plugin.getConfig().getStringList("banned-text").stream(), Stream.of(args[1])).collect(Collectors.toList())); 37 | 38 | try { 39 | config.save(new File(plugin.getDataFolder(), "config.yml")); 40 | } catch (IOException e) { 41 | e.printStackTrace(); 42 | } 43 | 44 | sender.sendMessage(ChatColor.GOLD + "Successfully added the config entry!"); 45 | } 46 | } 47 | 48 | return false; 49 | } 50 | 51 | @Override 52 | public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { 53 | if (args.length == 1) { 54 | List suggestion = new ArrayList<>(); 55 | 56 | suggestion.add("add"); 57 | suggestion.add("reload"); 58 | 59 | List completions = new ArrayList<>(); 60 | 61 | StringUtil.copyPartialMatches(args[0], suggestion, completions); 62 | 63 | Collections.sort(completions); 64 | 65 | return completions; 66 | } else { 67 | return new ArrayList<>(); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /PistonFilter/src/main/java/net/pistonmaster/pistonfilter/hooks/PistonMuteHook.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonfilter.hooks; 2 | 3 | import net.pistonmaster.pistonmute.utils.StorageTool; 4 | import org.bukkit.entity.Player; 5 | 6 | import java.util.Date; 7 | 8 | public class PistonMuteHook { 9 | public static void mute(Player player, Date date) { 10 | StorageTool.tempMutePlayer(player, date); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /PistonFilter/src/main/java/net/pistonmaster/pistonfilter/listeners/ChatListener.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonfilter.listeners; 2 | 3 | import com.google.common.cache.Cache; 4 | import com.google.common.cache.CacheBuilder; 5 | import me.xdrop.fuzzywuzzy.FuzzySearch; 6 | import net.md_5.bungee.api.ChatColor; 7 | import net.pistonmaster.pistonchat.api.PistonChatAPI; 8 | import net.pistonmaster.pistonchat.api.PistonChatEvent; 9 | import net.pistonmaster.pistonchat.api.PistonWhisperEvent; 10 | import net.pistonmaster.pistonchat.utils.UniqueSender; 11 | import net.pistonmaster.pistonfilter.PistonFilter; 12 | import net.pistonmaster.pistonfilter.hooks.PistonMuteHook; 13 | import net.pistonmaster.pistonfilter.utils.FilteredPlayer; 14 | import net.pistonmaster.pistonfilter.utils.MaxSizeDeque; 15 | import net.pistonmaster.pistonfilter.utils.MessageInfo; 16 | import net.pistonmaster.pistonfilter.utils.StringHelper; 17 | import org.bukkit.command.CommandSender; 18 | import org.bukkit.entity.Player; 19 | import org.bukkit.event.EventHandler; 20 | import org.bukkit.event.Listener; 21 | import org.bukkit.event.player.PlayerQuitEvent; 22 | 23 | import java.time.Duration; 24 | import java.time.Instant; 25 | import java.time.temporal.ChronoUnit; 26 | import java.util.*; 27 | import java.util.concurrent.ConcurrentHashMap; 28 | import java.util.concurrent.TimeUnit; 29 | import java.util.concurrent.atomic.AtomicInteger; 30 | import java.util.function.Consumer; 31 | 32 | public class ChatListener implements Listener { 33 | private final PistonFilter plugin; 34 | private final Deque globalMessages; 35 | private final Map players = new ConcurrentHashMap<>(); 36 | private final Cache violationsCache; 37 | 38 | public ChatListener(PistonFilter plugin) { 39 | this.plugin = plugin; 40 | this.globalMessages = new MaxSizeDeque<>(plugin.getConfig().getInt("global-message-stack-size")); 41 | this.violationsCache = CacheBuilder.newBuilder() 42 | .expireAfterWrite(plugin.getConfig().getInt("mute-violations-timeframe"), TimeUnit.SECONDS) 43 | .build(); 44 | } 45 | 46 | @EventHandler(ignoreCancelled = true) 47 | public void onQuit(PlayerQuitEvent event) { 48 | players.remove(event.getPlayer().getUniqueId()); 49 | } 50 | 51 | @EventHandler(ignoreCancelled = true) 52 | public void onChat(PistonChatEvent event) { 53 | handleMessage(event.getPlayer(), MessageInfo.of(Instant.now(), event.getMessage()), 54 | () -> event.setCancelled(true), 55 | message -> PistonChatAPI.getInstance().getCommonTool().sendChatMessage(event.getPlayer(), message, event.getPlayer())); 56 | } 57 | 58 | @EventHandler(ignoreCancelled = true) 59 | public void onWhisper(PistonWhisperEvent event) { 60 | if (event.getSender() == event.getReceiver()) return; 61 | 62 | handleMessage(event.getSender(), MessageInfo.of(Instant.now(), event.getMessage()), 63 | () -> event.setCancelled(true), 64 | message -> PistonChatAPI.getInstance().getCommonTool().sendSender(event.getSender(), message, event.getReceiver())); 65 | } 66 | 67 | public void handleMessage(CommandSender sender, MessageInfo message, Runnable cancelEvent, Consumer sendEmpty) { 68 | if (sender.hasPermission("pistonfilter.bypass")) return; 69 | 70 | for (String str : plugin.getConfig().getStringList("banned-text")) { 71 | if (FuzzySearch.partialRatio(message.getStrippedMessage(), StringHelper.revertLeet(str)) > plugin.getConfig().getInt("banned-text-partial-ratio")) { 72 | cancelMessage(sender, message, cancelEvent, sendEmpty, String.format("Contains banned text: %s", str)); 73 | return; 74 | } 75 | } 76 | 77 | int wordsWithNumbers = 0; 78 | for (String word : message.getWords()) { 79 | if (word.length() > plugin.getConfig().getInt("max-word-length")) { 80 | cancelMessage(sender, message, cancelEvent, sendEmpty, String.format("Contains a word with length (%d) \"%s\".", word.length(), word)); 81 | return; 82 | } else if (hasInvalidSeparators(word)) { 83 | cancelMessage(sender, message, cancelEvent, sendEmpty, String.format("Has a word with invalid separators (%s).", word)); 84 | return; 85 | } 86 | 87 | if (StringHelper.containsDigit(word)) { 88 | wordsWithNumbers++; 89 | } 90 | } 91 | 92 | if (wordsWithNumbers > plugin.getConfig().getInt("max-words-with-numbers")) { 93 | cancelMessage(sender, message, cancelEvent, sendEmpty, String.format("Used %d words with numbers.", wordsWithNumbers)); 94 | return; 95 | } 96 | 97 | if (plugin.getConfig().getBoolean("no-repeat")) { 98 | UUID uuid = new UniqueSender(sender).getUniqueId(); 99 | FilteredPlayer filteredPlayerCached = players.compute(uuid, (k, v) -> 100 | Objects.requireNonNullElseGet(v, () -> new FilteredPlayer(new UniqueSender(sender).getUniqueId(), 101 | new MaxSizeDeque<>(plugin.getConfig().getInt("no-repeat-stack-size"))))); 102 | 103 | int noRepeatTime = plugin.getConfig().getInt("no-repeat-time"); 104 | int similarRatio = plugin.getConfig().getInt("no-repeat-similar-ratio"); 105 | Deque lastMessages = filteredPlayerCached.lastMessages(); 106 | 107 | boolean blocked = isBlocked(sender, message, cancelEvent, sendEmpty, noRepeatTime, similarRatio, lastMessages, false); 108 | 109 | if (!blocked && plugin.getConfig().getBoolean("global-repeat-check")) { 110 | blocked = isBlocked(sender, message, cancelEvent, sendEmpty, noRepeatTime, similarRatio, globalMessages, true); 111 | } 112 | 113 | if (!blocked) { 114 | filteredPlayerCached.lastMessages().add(message); 115 | globalMessages.add(message); 116 | } 117 | } 118 | } 119 | 120 | private boolean isBlocked(CommandSender sender, MessageInfo message, 121 | Runnable cancelEvent, Consumer sendEmpty, 122 | int noRepeatTime, 123 | int similarRatio, Deque lastMessages, 124 | boolean global) { 125 | int noRepeatNumberMessages = plugin.getConfig().getInt("no-repeat-number-messages"); 126 | int noRepeatNumberAmount = plugin.getConfig().getInt("no-repeat-number-amount"); 127 | int noRepeatWordRatio = plugin.getConfig().getInt("no-repeat-word-ratio"); 128 | int i = 0; 129 | int foundDigits = 0; 130 | for (Iterator it = lastMessages.descendingIterator(); it.hasNext(); ) { 131 | MessageInfo pair = it.next(); 132 | if (!global && message.isContainsDigit() && pair.isContainsDigit() 133 | && i < noRepeatNumberMessages) { 134 | foundDigits++; 135 | } 136 | 137 | if (foundDigits >= noRepeatNumberAmount) { 138 | cancelMessage(sender, message, cancelEvent, sendEmpty, "Contains too many numbers."); 139 | return true; 140 | } else if (noRepeatTime == -1 || Duration.between(pair.getTime(), message.getTime()).getSeconds() < noRepeatTime) { 141 | int similarity; 142 | if ((similarity = FuzzySearch.weightedRatio(pair.getStrippedMessage(), message.getStrippedMessage())) > similarRatio) { 143 | cancelMessage(sender, message, cancelEvent, sendEmpty, 144 | String.format("Similar to previous message (%d%%) (%s)", similarity, pair.getOriginalMessage())); 145 | return true; 146 | } else if (noRepeatWordRatio > -1 && (similarity = getAverageEqualRatio(pair.getStrippedWords(), message.getStrippedWords())) > noRepeatWordRatio) { 147 | cancelMessage(sender, message, cancelEvent, sendEmpty, 148 | String.format("Word similarity to previous message (%d%%) (%s)", similarity, pair.getOriginalMessage())); 149 | return true; 150 | } 151 | return true; 152 | } 153 | i++; 154 | } 155 | return false; 156 | } 157 | 158 | private boolean hasInvalidSeparators(String word) { 159 | List chars = word.chars().mapToObj(c -> (char) c).toList(); 160 | int maxSeparators = plugin.getConfig().getInt("max-separated-numbers"); 161 | int separators = 0; 162 | int index = 0; 163 | for (char c : chars) { 164 | if (Character.isDigit(c)) { 165 | if (index >= (chars.size() - 1)) { 166 | return false; 167 | } else if (!Character.isDigit(chars.get(index + 1)) && ++separators > maxSeparators) { 168 | return true; 169 | } 170 | } 171 | index++; 172 | } 173 | return false; 174 | } 175 | 176 | private void cancelMessage(CommandSender sender, MessageInfo message, Runnable cancelEvent, Consumer sendEmpty, String reason) { 177 | cancelEvent.run(); 178 | 179 | if (plugin.getConfig().getBoolean("message-sender")) { 180 | sendEmpty.accept(message.getOriginalMessage()); 181 | } 182 | 183 | if (plugin.getConfig().getBoolean("verbose")) { 184 | plugin.getLogger().info(ChatColor.RED + "[AntiSpam] <" + sender.getName() + "> " + message.getOriginalMessage() + " (" + reason + ")"); 185 | } 186 | 187 | if (plugin.getConfig().getBoolean("mute-on-fail") 188 | && plugin.getServer().getPluginManager().isPluginEnabled("PistonMute") 189 | && sender instanceof Player) { 190 | try { 191 | UniqueSender uniqueSender = new UniqueSender(sender); 192 | int violations = violationsCache.get(uniqueSender.getUniqueId(), AtomicInteger::new).incrementAndGet(); 193 | if (violations > plugin.getConfig().getInt("mute-violations")) { 194 | violationsCache.invalidate(uniqueSender.getUniqueId()); 195 | int muteTime = plugin.getConfig().getInt("mute-time"); 196 | PistonMuteHook.mute((Player) sender, Date.from(Instant.now().plus(muteTime, ChronoUnit.SECONDS))); 197 | if (plugin.getConfig().getBoolean("verbose")) { 198 | plugin.getLogger().info(ChatColor.RED + "[AntiSpam] Muted " + uniqueSender.sender().getName() + " for " + muteTime + " seconds."); 199 | } 200 | } 201 | } catch (Exception e) { 202 | e.printStackTrace(); 203 | } 204 | } 205 | } 206 | 207 | private int getAverageEqualRatio(String[] comparedTo, String[] sentWords) { 208 | double total = 0; 209 | for (String sentWord : sentWords) { 210 | for (String comparedWord : comparedTo) { 211 | if (comparedWord.equalsIgnoreCase(sentWord)) { 212 | total += 1; 213 | break; 214 | } 215 | } 216 | } 217 | return (int) ((total / sentWords.length) * 100); 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /PistonFilter/src/main/java/net/pistonmaster/pistonfilter/utils/FilteredPlayer.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonfilter.utils; 2 | 3 | import java.util.Deque; 4 | import java.util.UUID; 5 | 6 | public record FilteredPlayer(UUID id, Deque lastMessages) { 7 | } 8 | -------------------------------------------------------------------------------- /PistonFilter/src/main/java/net/pistonmaster/pistonfilter/utils/MaxSizeDeque.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonfilter.utils; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | 5 | import java.util.concurrent.ConcurrentLinkedDeque; 6 | 7 | @RequiredArgsConstructor 8 | public class MaxSizeDeque extends ConcurrentLinkedDeque { 9 | private final int maxSize; 10 | 11 | @Override 12 | public boolean add(C c) { 13 | if (size() == maxSize) { 14 | removeFirst(); 15 | } 16 | 17 | return super.add(c); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /PistonFilter/src/main/java/net/pistonmaster/pistonfilter/utils/MessageInfo.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonfilter.utils; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.ToString; 7 | import net.md_5.bungee.api.ChatColor; 8 | 9 | import java.time.Instant; 10 | import java.util.Arrays; 11 | 12 | @Getter 13 | @ToString 14 | @RequiredArgsConstructor(access = AccessLevel.PRIVATE) 15 | public class MessageInfo { 16 | private final Instant time; 17 | private final String originalMessage; 18 | private final String strippedMessage; 19 | private final String[] words; 20 | private final String[] strippedWords; 21 | private final boolean containsDigit; 22 | 23 | public static MessageInfo of(Instant time, String message) { 24 | String[] words = Arrays.stream(message.split("\\s+")).toArray(String[]::new); 25 | String[] strippedWords = Arrays.stream(words) 26 | .map(MessageInfo::removeColorCodes) 27 | .map(StringHelper::revertLeet).toArray(String[]::new); 28 | 29 | return new MessageInfo( 30 | time, 31 | message, 32 | String.join("", strippedWords), 33 | words, 34 | strippedWords, 35 | message.matches(".*\\d.*") 36 | ); 37 | } 38 | 39 | private static String removeColorCodes(String string) { 40 | return ChatColor.stripColor(ChatColor.translateAlternateColorCodes('&', string)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /PistonFilter/src/main/java/net/pistonmaster/pistonfilter/utils/StringHelper.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonfilter.utils; 2 | 3 | import java.text.Normalizer; 4 | import java.util.regex.Pattern; 5 | 6 | public class StringHelper { 7 | public static String revertLeet(String str) { 8 | str = str.toLowerCase(); 9 | 10 | str = str.replace("0", "o"); 11 | str = str.replace("1", "i"); 12 | str = str.replace("2", "z"); 13 | str = str.replace("3", "e"); 14 | str = str.replace("4", "a"); 15 | str = str.replace("5", "s"); 16 | str = str.replace("6", "g"); 17 | str = str.replace("7", "t"); 18 | str = str.replace("8", "b"); 19 | str = str.replace("9", "g"); 20 | str = str.replace("&", "a"); 21 | str = str.replace("@", "a"); 22 | str = str.replace("(", "c"); 23 | str = str.replace("#", "h"); 24 | str = str.replace("!", "i"); 25 | str = str.replace("]", "i"); 26 | str = str.replace("|", "i"); 27 | str = str.replace("}", "i"); 28 | str = str.replace("?", "o"); 29 | str = str.replace("$", "s"); 30 | str = stripAccents(str); 31 | 32 | return str; 33 | } 34 | 35 | public static boolean containsDigit(String s) { 36 | for (char c : s.toCharArray()) { 37 | if (Character.isDigit(c)) { 38 | return true; 39 | } 40 | } 41 | 42 | return false; 43 | } 44 | 45 | private static String stripAccents(final String input) { 46 | StringBuilder decomposed = new StringBuilder(Normalizer.normalize(input, Normalizer.Form.NFD)); 47 | for (int i = 0; i < decomposed.length(); i++) { 48 | if (decomposed.charAt(i) == 'Ł') { 49 | decomposed.setCharAt(i, 'L'); 50 | } else if (decomposed.charAt(i) == 'ł') { 51 | decomposed.setCharAt(i, 'l'); 52 | } else if (decomposed.charAt(i) == 'Ø') { 53 | decomposed.setCharAt(i, 'O'); 54 | } else if (decomposed.charAt(i) == 'ø') { 55 | decomposed.setCharAt(i, 'o'); 56 | } 57 | } 58 | return Pattern.compile("\\p{InCombiningDiacriticalMarks}+").matcher(decomposed).replaceAll(""); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /PistonFilter/src/main/resources/config.yml: -------------------------------------------------------------------------------- 1 | verbose: true 2 | message-sender: true 3 | no-repeat: true 4 | # How many times after sending a message should the player wait before sending the same message again 5 | no-repeat-time: 60 6 | # How many messages are stored that are not allowed to be repeated? 7 | no-repeat-stack-size: 20 8 | # How similar from 1-100 are two messages allowed to be before they are considered the same? 9 | no-repeat-similar-ratio: 90 10 | # Check words of previous messages for repeated word ratio. -1 to disable. Value means how many percent of the messages has repeated words. 11 | no-repeat-word-ratio: 40 12 | # How many of the last message of a user are allowed to have a number inside them? 13 | no-repeat-number-messages: 5 14 | no-repeat-number-amount: 3 15 | # Store last global messages and compare them for no repeat as well 16 | global-repeat-check: true 17 | # Store the last x amount of global messages to compare for no repeat 18 | global-message-stack-size: 40 19 | # Should we mute a user if they fail the above checks too often? (Requires PistonMute) 20 | mute-on-fail: true 21 | # After how many violations of our checks do we mute someone? 22 | mute-violations: 3 23 | # How long until violations expire? 24 | mute-violations-timeframe: 60 25 | # For how long do we mute someone? (seconds) 26 | mute-time: 60 27 | # Example: "2 5f gg 8b 33 hj 6zb 6573" has 8 words and 6 words with numbers 28 | max-words-with-numbers: 5 29 | max-separated-numbers: 3 30 | max-word-length: 20 31 | banned-text-partial-ratio: 95 32 | banned-text: 33 | - "http" 34 | - "https" 35 | - "://" 36 | - ".org" 37 | - ".com" 38 | - ".net" 39 | - ".org" 40 | - ".co" 41 | - ".cc" 42 | - ".tk" 43 | - ".ga" 44 | - ".gg" 45 | - ".io" 46 | -------------------------------------------------------------------------------- /PistonFilter/src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: PistonFilter 2 | version: ${version} 3 | description: ${description} 4 | website: ${url} 5 | api-version: 1.13 6 | main: net.pistonmaster.pistonfilter.PistonFilter 7 | authors: [ AlexProgrammerDE ] 8 | depend: [ "PistonChat" ] 9 | softdepend: [ "PistonMute" ] 10 | commands: 11 | pistonfilter: 12 | description: Main command for PistonFilter 13 | permission: pistonfilter.admin 14 | permissions: 15 | pistonfilter.admin: 16 | description: Admin permission for PistonFilter 17 | default: op 18 | children: 19 | pistonfilter.bypass: true 20 | pistonfilter.bypass: 21 | description: Permission to bypass the chatfilter -------------------------------------------------------------------------------- /PistonFilter/src/test/java/LeetTest.java: -------------------------------------------------------------------------------- 1 | import net.pistonmaster.pistonfilter.utils.StringHelper; 2 | import org.junit.jupiter.api.Assertions; 3 | import org.junit.jupiter.api.Test; 4 | 5 | class LeetTest { 6 | @Test 7 | void leetTest() { 8 | String original = "test12345-/("; 9 | Assertions.assertNotEquals(original, StringHelper.revertLeet(original)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /PistonMute/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("pc.shadow-conventions") 3 | alias(libs.plugins.runpaper) 4 | } 5 | 6 | description = "Mute addon for PistonChat." 7 | 8 | dependencies { 9 | compileOnly("org.spigotmc:spigot-api:1.12.2-R0.1-SNAPSHOT") 10 | compileOnly(projects.pistonChat) 11 | 12 | implementation("net.pistonmaster:PistonUtils:1.4.0") 13 | implementation("org.bstats:bstats-bukkit:3.1.0") 14 | } 15 | 16 | tasks { 17 | runServer { 18 | minecraftVersion(libs.versions.runpaperversion.get()) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /PistonMute/src/main/java/net/pistonmaster/pistonmute/PistonMute.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonmute; 2 | 3 | import net.md_5.bungee.api.ChatColor; 4 | import net.pistonmaster.pistonmute.commands.MuteCommand; 5 | import net.pistonmaster.pistonmute.commands.UnMuteCommand; 6 | import net.pistonmaster.pistonmute.listeners.PistonChatListener; 7 | import net.pistonmaster.pistonmute.utils.StorageTool; 8 | import net.pistonmaster.pistonutils.update.GitHubUpdateChecker; 9 | import net.pistonmaster.pistonutils.update.SemanticVersion; 10 | import org.bstats.bukkit.Metrics; 11 | import org.bukkit.plugin.java.JavaPlugin; 12 | 13 | import java.io.IOException; 14 | import java.util.logging.Logger; 15 | 16 | public final class PistonMute extends JavaPlugin { 17 | @Override 18 | public void onEnable() { 19 | Logger log = getLogger(); 20 | 21 | log.info(ChatColor.YELLOW + "Loading config"); 22 | saveDefaultConfig(); 23 | StorageTool.setupTool(this); 24 | 25 | log.info(ChatColor.YELLOW + "Registering commands"); 26 | getServer().getPluginCommand("mute").setExecutor(new MuteCommand(this)); 27 | getServer().getPluginCommand("mute").setTabCompleter(new MuteCommand(this)); 28 | 29 | getServer().getPluginCommand("unmute").setExecutor(new UnMuteCommand(this)); 30 | getServer().getPluginCommand("unmute").setTabCompleter(new UnMuteCommand(this)); 31 | 32 | log.info(ChatColor.YELLOW + "Registering listeners"); 33 | getServer().getPluginManager().registerEvents(new PistonChatListener(this), this); 34 | 35 | log.info(ChatColor.YELLOW + "Loading metrics"); 36 | new Metrics(this, 11559); 37 | 38 | log.info(ChatColor.YELLOW + "Checking for a newer version"); 39 | try { 40 | String currentVersionString = this.getDescription().getVersion(); 41 | SemanticVersion gitHubVersion = new GitHubUpdateChecker() 42 | .getVersion("https://api.github.com/repos/AlexProgrammerDE/PistonChat/releases/latest"); 43 | SemanticVersion currentVersion = SemanticVersion.fromString(currentVersionString); 44 | 45 | if (gitHubVersion.isNewerThan(currentVersion)) { 46 | log.info(ChatColor.RED + "There is an update available!"); 47 | log.info(ChatColor.RED + "Current version: " + currentVersionString + " New version: " + gitHubVersion); 48 | log.info(ChatColor.RED + "Download it at: https://modrinth.com/plugin/pistonchat"); 49 | } else { 50 | log.info(ChatColor.YELLOW + "You're up to date!"); 51 | } 52 | } catch (IOException e) { 53 | log.severe("Could not check for updates!"); 54 | e.printStackTrace(); 55 | } 56 | 57 | getLogger().info(ChatColor.YELLOW + "Done! :D"); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /PistonMute/src/main/java/net/pistonmaster/pistonmute/api/MuteAPI.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonmute.api; 2 | 3 | import net.pistonmaster.pistonmute.utils.StorageTool; 4 | import org.bukkit.entity.Player; 5 | 6 | /** 7 | * Class to interact with PistonChat! 8 | */ 9 | @SuppressWarnings("unused") 10 | public final class MuteAPI { 11 | private MuteAPI() { 12 | } 13 | 14 | /** 15 | * Check if a player is muted. 16 | * 17 | * @param player The player to check 18 | * @return If the player is muted 19 | */ 20 | public static boolean isMuted(Player player) { 21 | return StorageTool.isMuted(player); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /PistonMute/src/main/java/net/pistonmaster/pistonmute/commands/MuteCommand.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonmute.commands; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.md_5.bungee.api.ChatColor; 5 | import net.md_5.bungee.api.chat.ComponentBuilder; 6 | import net.pistonmaster.pistonmute.PistonMute; 7 | import net.pistonmaster.pistonmute.utils.StorageTool; 8 | import org.bukkit.Bukkit; 9 | import org.bukkit.command.Command; 10 | import org.bukkit.command.CommandExecutor; 11 | import org.bukkit.command.CommandSender; 12 | import org.bukkit.command.TabExecutor; 13 | import org.bukkit.entity.Player; 14 | import org.bukkit.util.StringUtil; 15 | 16 | import java.util.*; 17 | 18 | @RequiredArgsConstructor 19 | public final class MuteCommand implements CommandExecutor, TabExecutor { 20 | private final PistonMute plugin; 21 | 22 | @Override 23 | public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { 24 | if (args.length > 0) { 25 | Player player = plugin.getServer().getPlayer(args[0]); 26 | 27 | if (player != null) { 28 | if (player != sender) { 29 | if (args.length > 1) { 30 | Calendar calendar = Calendar.getInstance(); 31 | calendar.setTime(new Date()); 32 | 33 | if (args[1].toLowerCase().endsWith("y")) { 34 | int d = Integer.parseInt(args[1].toLowerCase().replace("y", "")); 35 | 36 | calendar.add(Calendar.YEAR, d); 37 | } else if (args[1].toLowerCase().endsWith("d")) { 38 | int d = Integer.parseInt(args[1].toLowerCase().replace("d", "")); 39 | 40 | calendar.add(Calendar.DAY_OF_WEEK, d); 41 | } else if (args[1].toLowerCase().endsWith("h")) { 42 | int h = Integer.parseInt(args[1].toLowerCase().replace("h", "")); 43 | 44 | calendar.add(Calendar.HOUR_OF_DAY, h); 45 | } else if (args[1].toLowerCase().endsWith("m")) { 46 | int m = Integer.parseInt(args[1].toLowerCase().replace("m", "")); 47 | 48 | calendar.add(Calendar.MINUTE, m); 49 | } else if (args[1].toLowerCase().endsWith("s")) { 50 | int s = Integer.parseInt(args[1].toLowerCase().replace("s", "")); 51 | 52 | calendar.add(Calendar.SECOND, s); 53 | } else { 54 | return false; 55 | } 56 | 57 | if (StorageTool.tempMutePlayer(player, calendar.getTime())) { 58 | successMessage(sender, player); 59 | } else { 60 | alreadyMutedMessage(sender, player); 61 | } 62 | } else { 63 | if (StorageTool.hardMutePlayer(player)) { 64 | successMessage(sender, player); 65 | } else { 66 | alreadyMutedMessage(sender, player); 67 | } 68 | } 69 | } else { 70 | sender.sendMessage("You can't mute yourself!"); 71 | } 72 | } else { 73 | return false; 74 | } 75 | } else { 76 | return false; 77 | } 78 | 79 | return true; 80 | } 81 | 82 | private void alreadyMutedMessage(CommandSender sender, Player player) { 83 | sender.spigot().sendMessage(new ComponentBuilder("----------------").color(ChatColor.DARK_BLUE).create()); 84 | sender.spigot().sendMessage(new ComponentBuilder("PistonMute").color(ChatColor.GOLD).create()); 85 | sender.spigot().sendMessage(new ComponentBuilder(player.getName() + " is already muted!").color(ChatColor.RED).create()); 86 | sender.spigot().sendMessage(new ComponentBuilder("----------------").color(ChatColor.DARK_BLUE).create()); 87 | } 88 | 89 | private void successMessage(CommandSender sender, Player player) { 90 | sender.spigot().sendMessage(new ComponentBuilder("----------------").color(ChatColor.DARK_BLUE).create()); 91 | sender.spigot().sendMessage(new ComponentBuilder("PistonMute").color(ChatColor.GOLD).create()); 92 | sender.spigot().sendMessage(new ComponentBuilder("Successfully muted " + player.getName() + "!").color(ChatColor.GREEN).create()); 93 | sender.spigot().sendMessage(new ComponentBuilder("----------------").color(ChatColor.DARK_BLUE).create()); 94 | } 95 | 96 | @Override 97 | public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { 98 | if (args.length == 1) { 99 | List players = new ArrayList<>(); 100 | 101 | for (Player player : Bukkit.getOnlinePlayers()) { 102 | players.add(player.getName()); 103 | } 104 | 105 | List completions = new ArrayList<>(); 106 | 107 | StringUtil.copyPartialMatches(args[0], players, completions); 108 | 109 | Collections.sort(completions); 110 | 111 | return completions; 112 | } else { 113 | return new ArrayList<>(); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /PistonMute/src/main/java/net/pistonmaster/pistonmute/commands/UnMuteCommand.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonmute.commands; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.md_5.bungee.api.ChatColor; 5 | import net.md_5.bungee.api.chat.ComponentBuilder; 6 | import net.pistonmaster.pistonmute.PistonMute; 7 | import net.pistonmaster.pistonmute.utils.StorageTool; 8 | import org.bukkit.Bukkit; 9 | import org.bukkit.command.Command; 10 | import org.bukkit.command.CommandExecutor; 11 | import org.bukkit.command.CommandSender; 12 | import org.bukkit.command.TabExecutor; 13 | import org.bukkit.entity.Player; 14 | import org.bukkit.util.StringUtil; 15 | 16 | import java.util.ArrayList; 17 | import java.util.Collections; 18 | import java.util.List; 19 | 20 | @RequiredArgsConstructor 21 | public final class UnMuteCommand implements CommandExecutor, TabExecutor { 22 | private final PistonMute plugin; 23 | 24 | @Override 25 | public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { 26 | if (args.length > 0) { 27 | Player player = plugin.getServer().getPlayer(args[0]); 28 | 29 | if (player != null) { 30 | if (player != sender) { 31 | if (StorageTool.unMutePlayer(player)) { 32 | sender.spigot().sendMessage(new ComponentBuilder("----------------").color(ChatColor.DARK_BLUE).create()); 33 | sender.spigot().sendMessage(new ComponentBuilder("PistonMute").color(ChatColor.GOLD).create()); 34 | sender.spigot().sendMessage(new ComponentBuilder("Successfully unmuted " + player.getName() + "!").color(ChatColor.GREEN).create()); 35 | sender.spigot().sendMessage(new ComponentBuilder("----------------").color(ChatColor.DARK_BLUE).create()); 36 | } else { 37 | sender.spigot().sendMessage(new ComponentBuilder("----------------").color(ChatColor.DARK_BLUE).create()); 38 | sender.spigot().sendMessage(new ComponentBuilder("PistonMute").color(ChatColor.GOLD).create()); 39 | sender.spigot().sendMessage(new ComponentBuilder(player.getName() + " wasn't muted!").color(ChatColor.RED).create()); 40 | sender.spigot().sendMessage(new ComponentBuilder("----------------").color(ChatColor.DARK_BLUE).create()); 41 | } 42 | } else { 43 | sender.sendMessage("Please don't mute yourself!"); 44 | } 45 | } 46 | } else { 47 | return false; 48 | } 49 | 50 | return true; 51 | } 52 | 53 | @Override 54 | public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { 55 | if (args.length == 1) { 56 | List players = new ArrayList<>(); 57 | 58 | for (Player player : Bukkit.getOnlinePlayers()) { 59 | players.add(player.getName()); 60 | } 61 | 62 | List completions = new ArrayList<>(); 63 | 64 | StringUtil.copyPartialMatches(args[0], players, completions); 65 | 66 | Collections.sort(completions); 67 | 68 | return completions; 69 | } else { 70 | return new ArrayList<>(); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /PistonMute/src/main/java/net/pistonmaster/pistonmute/listeners/PistonChatListener.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonmute.listeners; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.pistonmaster.pistonchat.PistonChat; 5 | import net.pistonmaster.pistonchat.api.PistonChatEvent; 6 | import net.pistonmaster.pistonchat.api.PistonWhisperEvent; 7 | import net.pistonmaster.pistonmute.PistonMute; 8 | import net.pistonmaster.pistonmute.utils.StorageTool; 9 | import org.bukkit.entity.Player; 10 | import org.bukkit.event.EventHandler; 11 | import org.bukkit.event.Listener; 12 | 13 | @RequiredArgsConstructor 14 | public final class PistonChatListener implements Listener { 15 | private final PistonMute plugin; 16 | private final PistonChat pistonChat = PistonChat.getPlugin(PistonChat.class); 17 | 18 | @EventHandler 19 | public void onChat(PistonChatEvent event) { 20 | if (StorageTool.isMuted(event.getPlayer())) { 21 | if (plugin.getConfig().getBoolean("shadowMute")) { 22 | pistonChat.getCommonTool().sendChatMessage(event.getPlayer(), event.getMessage(), event.getPlayer()); 23 | } 24 | 25 | event.setCancelled(true); 26 | } 27 | } 28 | 29 | @EventHandler 30 | public void onChat(PistonWhisperEvent event) { 31 | if (event.getSender() == event.getReceiver()) return; 32 | 33 | if (event.getSender() instanceof Player && StorageTool.isMuted((Player) event.getSender())) { 34 | if (plugin.getConfig().getBoolean("shadowMute")) { 35 | pistonChat.getCommonTool().sendSender(event.getSender(), event.getMessage(), event.getReceiver()); 36 | } 37 | 38 | event.setCancelled(true); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /PistonMute/src/main/java/net/pistonmaster/pistonmute/utils/StorageTool.java: -------------------------------------------------------------------------------- 1 | package net.pistonmaster.pistonmute.utils; 2 | 3 | import net.pistonmaster.pistonmute.PistonMute; 4 | import org.bukkit.OfflinePlayer; 5 | import org.bukkit.configuration.file.FileConfiguration; 6 | import org.bukkit.configuration.file.YamlConfiguration; 7 | import org.bukkit.entity.Player; 8 | 9 | import java.io.File; 10 | import java.io.IOException; 11 | import java.text.ParseException; 12 | import java.text.SimpleDateFormat; 13 | import java.util.Date; 14 | import java.util.Locale; 15 | import java.util.stream.Collectors; 16 | import java.util.stream.Stream; 17 | 18 | public final class StorageTool { 19 | private static PistonMute plugin; 20 | private static FileConfiguration dataConfig; 21 | private static File dataFile; 22 | 23 | private StorageTool() { 24 | } 25 | 26 | /** 27 | * Mute a player temporarily! 28 | * 29 | * @param player The player to mute. 30 | * @param date The date when the player will be unmuted. 31 | * @return true if player got muted and if already muted false. 32 | */ 33 | public static boolean tempMutePlayer(Player player, Date date) { 34 | manageMute(player); 35 | 36 | if (!dataConfig.contains(player.getUniqueId().toString())) { 37 | dataConfig.set(player.getUniqueId().toString(), date.toString()); 38 | 39 | saveData(); 40 | 41 | return true; 42 | } else { 43 | return false; 44 | } 45 | } 46 | 47 | /** 48 | * Mute a player! 49 | * 50 | * @param player The player to mute. 51 | * @return true if player got muted and if already muted false. 52 | */ 53 | public static boolean hardMutePlayer(Player player) { 54 | manageMute(player); 55 | 56 | if (!dataConfig.getStringList("hardmutes").contains(player.getUniqueId().toString())) { 57 | dataConfig.set("hardmutes", Stream.concat(dataConfig.getStringList("hardmutes").stream(), Stream.of(player.getUniqueId().toString())).collect(Collectors.toList())); 58 | 59 | saveData(); 60 | 61 | return true; 62 | } else { 63 | return false; 64 | } 65 | } 66 | 67 | /** 68 | * Unmute a player! 69 | * 70 | * @param player The player to unmute. 71 | * @return true if player got unmuted and false if not was muted. 72 | */ 73 | public static boolean unMutePlayer(OfflinePlayer player) { 74 | if (dataConfig.contains(player.getUniqueId().toString())) { 75 | dataConfig.set(player.getUniqueId().toString(), null); 76 | 77 | saveData(); 78 | 79 | return true; 80 | } else if (dataConfig.getStringList("hardmutes").contains(player.getUniqueId().toString())) { 81 | dataConfig.set("hardmutes", dataConfig.getStringList("hardmutes").stream().filter(uuid -> !uuid.equals(player.getUniqueId().toString())).collect(Collectors.toList())); 82 | 83 | saveData(); 84 | 85 | return true; 86 | } else { 87 | 88 | return false; 89 | } 90 | } 91 | 92 | public static boolean isMuted(OfflinePlayer player) { 93 | manageMute(player); 94 | 95 | return dataConfig.contains(player.getUniqueId().toString()) || dataConfig.getStringList("hardmutes").contains(player.getUniqueId().toString()); 96 | } 97 | 98 | private static void manageMute(OfflinePlayer player) { 99 | Date now = new Date(); 100 | 101 | if (dataConfig.contains(player.getUniqueId().toString())) { 102 | SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy", Locale.US); 103 | 104 | try { 105 | Date date = sdf.parse(dataConfig.getString(player.getUniqueId().toString())); 106 | 107 | if (now.after(date) || (now.equals(date))) { 108 | unMutePlayer(player); 109 | } 110 | } catch (ParseException e) { 111 | e.printStackTrace(); 112 | } 113 | } 114 | } 115 | 116 | private static void loadData() { 117 | generateFile(); 118 | 119 | dataConfig = YamlConfiguration.loadConfiguration(dataFile); 120 | } 121 | 122 | private static void saveData() { 123 | generateFile(); 124 | 125 | try { 126 | dataConfig.save(dataFile); 127 | } catch (IOException e) { 128 | e.printStackTrace(); 129 | } 130 | } 131 | 132 | private static void generateFile() { 133 | if (!plugin.getDataFolder().exists() && !plugin.getDataFolder().mkdir()) 134 | new IOException("File already exists.").printStackTrace(); 135 | 136 | if (!dataFile.exists()) { 137 | try { 138 | if (!dataFile.createNewFile()) 139 | new IOException("File already exists.").printStackTrace(); 140 | } catch (IOException e) { 141 | e.printStackTrace(); 142 | } 143 | } 144 | } 145 | 146 | public static void setupTool(PistonMute plugin) { 147 | if (plugin == null || StorageTool.plugin != null) 148 | return; 149 | 150 | StorageTool.plugin = plugin; 151 | StorageTool.dataFile = new File(plugin.getDataFolder(), "data.yml"); 152 | 153 | loadData(); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /PistonMute/src/main/resources/config.yml: -------------------------------------------------------------------------------- 1 | shadowMute: true -------------------------------------------------------------------------------- /PistonMute/src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: PistonMute 2 | version: ${version} 3 | description: ${description} 4 | website: ${url} 5 | api-version: 1.13 6 | main: net.pistonmaster.pistonmute.PistonMute 7 | authors: [ AlexProgrammerDE ] 8 | depend: [ PistonChat ] 9 | commands: 10 | mute: 11 | description: Mute a player! 12 | usage: / [player]