├── .github └── workflows │ └── gradle.yml ├── .gitignore ├── LICENSE ├── README.MD ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── jitpack.yml ├── settings.gradle └── src ├── main ├── java │ └── cc │ │ └── sfclub │ │ ├── Internal.java │ │ ├── catcode │ │ ├── Base64.java │ │ ├── CatCodeHelper.java │ │ └── entities │ │ │ ├── At.java │ │ │ ├── Image.java │ │ │ ├── Plain.java │ │ │ └── package-info.java │ │ ├── command │ │ ├── CommandListener.java │ │ ├── Source.java │ │ └── internal │ │ │ ├── Me.java │ │ │ ├── Op.java │ │ │ └── Stop.java │ │ ├── core │ │ ├── Core.java │ │ ├── CoreCfg.java │ │ ├── DatabaseCfg.java │ │ ├── I18N.java │ │ ├── Initializer.java │ │ ├── PermCfg.java │ │ ├── ShutdownHook.java │ │ └── security │ │ │ ├── PolarSec.java │ │ │ └── modules │ │ │ ├── HarmfulMessage.java │ │ │ ├── HighFrequencyMessage.java │ │ │ └── SpamMessage.java │ │ ├── database │ │ └── converter │ │ │ ├── PermListConverter.java │ │ │ └── StrListConverter.java │ │ ├── events │ │ ├── Event.java │ │ ├── GroupEvent.java │ │ ├── MemberEvent.java │ │ ├── MessageEvent.java │ │ ├── PlatformEvent.java │ │ ├── internal │ │ │ └── MessageListener.java │ │ ├── member │ │ │ ├── MemberLeaveEvent.java │ │ │ └── MemberRequestJoinEvent.java │ │ ├── message │ │ │ ├── Message.java │ │ │ ├── MessageDeletedEvent.java │ │ │ ├── MessageModifiedEvent.java │ │ │ ├── direct │ │ │ │ ├── PrivateMessage.java │ │ │ │ ├── PrivateMessageDeletedEvent.java │ │ │ │ └── PrivateMessageModifiedEvent.java │ │ │ └── group │ │ │ │ ├── GroupMessage.java │ │ │ │ ├── GroupMessageDeletedEvent.java │ │ │ │ └── GroupMessageModifiedEvent.java │ │ └── server │ │ │ ├── ServerStartedEvent.java │ │ │ └── ServerStoppingEvent.java │ │ ├── plugin │ │ ├── Plugin.java │ │ ├── PluginDescription.java │ │ ├── PluginLoader.java │ │ ├── PluginManager.java │ │ ├── SimpleConfig.java │ │ ├── exception │ │ │ ├── DependencyMissingException.java │ │ │ ├── InvalidPluginException.java │ │ │ └── PluginNotLoadedException.java │ │ └── java │ │ │ ├── JavaPluginLoader.java │ │ │ ├── NullCatWillDress.java │ │ │ └── PolarClassloader.java │ │ ├── service │ │ ├── Registry.java │ │ ├── Service.java │ │ └── ServiceProvider.java │ │ ├── transform │ │ ├── Bot.java │ │ ├── ChatGroup.java │ │ ├── Contact.java │ │ ├── Receiver.java │ │ ├── exception │ │ │ ├── ContactNotFoundException.java │ │ │ ├── GroupNotFoundException.java │ │ │ └── PlatformNotFoundException.java │ │ └── internal │ │ │ ├── ConsoleBot.java │ │ │ ├── VirtContact.java │ │ │ └── VirtGroup.java │ │ └── user │ │ ├── Group.java │ │ ├── User.java │ │ ├── UserManager.java │ │ ├── exception │ │ └── UserPlatformUnboundException.java │ │ └── perm │ │ ├── Perm.java │ │ ├── Permissible.java │ │ └── PureStringPerm.java └── resources │ └── logback.xml └── test └── java └── cc └── sfclub └── test ├── ListConverterTest.java └── PermEqualsTest.java /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: Java CI with Gradle 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up JDK 11 18 | uses: actions/setup-java@v1 19 | with: 20 | java-version: 11.0.7 21 | - name: Grant execute permission for gradlew 22 | run: chmod +x gradlew 23 | - name: Build with Gradle 24 | run: ./gradlew shadowjar 25 | - name: Upload artifact 26 | uses: actions/upload-artifact@v1.0.0 27 | with: 28 | name: "Core" 29 | # Directory containing files to upload 30 | path: build/libs 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | build 3 | .gradle 4 | logs 5 | plugins 6 | config.json 7 | gradlew.bat 8 | sonar.sh 9 | /database.json 10 | /groups.json 11 | locale 12 | *.db 13 | /plugins/ 14 | -------------------------------------------------------------------------------- /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] [SaltedFish Club] 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 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # PolarCore 2 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/cc0af3e7ffbe4ac89ed566aeae004d6c)](https://app.codacy.com/gh/saltedfishclub/PolarCore?utm_source=github.com&utm_medium=referral&utm_content=saltedfishclub/PolarCore&utm_campaign=Badge_Grade_Dashboard). 3 | v4 refactored. 4 | 5 | Under Heavy Development. 6 | Open source / Apache2.0 License 7 | ## What 8 | A Chatbot kernel which had built-in User,Permission,SpecialMessages,Command systems. 9 | Also,It support to use `adapters` to warp diffirient messages into polarCore's. 10 | 11 | 12 | ## Dependency 13 | ```groovy 14 | maven { url 'https://jitpack.io' } 15 | ``` 16 | and 17 | `compileOnly 'com.github.saltedfishclub:PolarCore:{{ VERSION }}` 18 | If you want to use the latest build: 19 | `compileOnly 'com.github.saltedfishclub:dev-SNAPSHOT` 20 | 21 | ## How messages process 22 | User Messages from diffirient platforms <-> Adapter (Convert to CatCodes) <-> PolarCore and plugins 23 | 24 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "java" 3 | id "de.lukaskoerfer.gradle.delombok" version "0.2" 4 | id 'com.github.johnrengelman.shadow' version "6.0.0" 5 | } 6 | group 'com.github.saltedfishclub' 7 | version '4.8.0' //EDIT CORE.JAVA 8 | 9 | assemble.dependsOn(shadowJar) 10 | sourceCompatibility = 11 11 | targetCompatibility = 11 12 | 13 | repositories { 14 | maven { url 'https://jitpack.io' } 15 | maven { 16 | name = "mavenCentral" 17 | url = "http://maven.aliyun.com/nexus/content/groups/public/" 18 | } 19 | maven { url "https://libraries.minecraft.net"} 20 | } 21 | javadoc { 22 | options.encoding "UTF-8" 23 | options.charSet 'UTF-8' 24 | source = "build/delombok/delombok" 25 | failOnError = false 26 | } 27 | 28 | shadowJar { 29 | zip64 true 30 | manifest { 31 | attributes 'Main-Class': 'cc.sfclub.core.Initializer' 32 | } 33 | } 34 | dependencies { 35 | testCompile group: 'junit', name: 'junit', version: '4.12' 36 | implementation 'org.greenrobot:eventbus:3.2.0' 37 | compileOnly 'org.projectlombok:lombok:1.18.12' 38 | implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.6' 39 | //Loggers 40 | implementation 'org.slf4j:slf4j-api:1.7.30' 41 | implementation group: 'ch.qos.logback', name: 'logback-core', version: '1.2.3' 42 | implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3' 43 | //Database 44 | implementation group: 'mysql', name: 'mysql-connector-java', version: '8.0.19' 45 | implementation 'org.xerial:sqlite-jdbc:3.32.3.2' 46 | // https://mvnrepository.com/artifact/com.dieselpoint/norm 47 | implementation group: 'com.dieselpoint', name: 'norm', version: '0.8.10' 48 | 49 | //Utils 50 | implementation 'org.reflections:reflections:0.9.12' 51 | compile 'com.github.iceBear67:Util:96b6e38669' 52 | compile 'com.mojang:brigadier:1.0.17' 53 | annotationProcessor 'org.projectlombok:lombok:1.18.12' 54 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saltedfishclub/PolarCore/148b1e225295b4677977f60273ec421700622a5f/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | 88 | @rem Execute Gradle 89 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 90 | 91 | :end 92 | @rem End local scope for the variables with windows NT shell 93 | if "%ERRORLEVEL%"=="0" goto mainEnd 94 | 95 | :fail 96 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 97 | rem the _cmd.exe /c_ return code! 98 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 99 | exit /b 1 100 | 101 | :mainEnd 102 | if "%OS%"=="Windows_NT" endlocal 103 | 104 | :omega 105 | -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk11 -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'Core' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/Internal.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub; 2 | 3 | import java.lang.annotation.Documented; 4 | 5 | /** 6 | * 带有这个注解的方法表示仅PolarCore内部使用,不要调用。 7 | */ 8 | @Documented 9 | @Internal 10 | public @interface Internal { 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/catcode/Base64.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.catcode; 2 | 3 | import java.util.Arrays; 4 | 5 | /** 6 | * Base64工具类 7 | * A very fast and memory efficient class to encode and decode to and from BASE64 in full accordance 8 | * with RFC 2045.

9 | * On Windows XP sp1 with 1.4.2_04 and later ;), this encoder and decoder is about 10 times faster 10 | * on small arrays (10 - 1000 bytes) and 2-3 times as fast on larger arrays (10000 - 1000000 bytes) 11 | * compared to sun.misc.Encoder()/Decoder().

12 | *

13 | * On byte arrays the encoder is about 20% faster than Jakarta Commons Base64 Codec for encode and 14 | * about 50% faster for decoding large arrays. This implementation is about twice as fast on very small 15 | * arrays (< 30 bytes). If source/destination is a String this 16 | * version is about three times as fast due to the fact that the Commons Codec result has to be recoded 17 | * to a String from byte[], which is very expensive.

18 | *

19 | * This encode/decode algorithm doesn't create any temporary arrays as many other codecs do, it only 20 | * allocates the resulting array. This produces less garbage and it is possible to handle arrays twice 21 | * as large as algorithms that create a temporary array. (E.g. Jakarta Commons Codec). It is unknown 22 | * whether Sun's sun.misc.Encoder()/Decoder() produce temporary arrays but since performance 23 | * is quite low it probably does.

24 | *

25 | * The encoder produces the same output as the Sun one except that the Sun's encoder appends 26 | * a trailing line separator if the last character isn't a pad. Unclear why but it only adds to the 27 | * length and is probably a side effect. Both are in conformance with RFC 2045 though.
28 | * Commons codec seem to always att a trailing line separator.

29 | * 30 | * Note! 31 | * The encode/decode method pairs (types) come in three versions with the exact same algorithm and 32 | * thus a lot of code redundancy. This is to not create any temporary arrays for transcoding to/from different 33 | * format types. The methods not used can simply be commented out.

34 | *

35 | * There is also a "fast" version of all decode methods that works the same way as the normal ones, but 36 | * har a few demands on the decoded input. Normally though, these fast verions should be used if the source if 37 | * the input is known and it hasn't bee tampered with.

38 | *

39 | * If you find the code useful or you find a bug, please send me a note at base64 @ miginfocom . com. 40 | *

41 | * Licence (BSD): 42 | * ============== 43 | *

44 | * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (base64 @ miginfocom . com) 45 | * All rights reserved. 46 | *

47 | * Redistribution and use in source and binary forms, with or without modification, 48 | * are permitted provided that the following conditions are met: 49 | * Redistributions of source code must retain the above copyright notice, this list 50 | * of conditions and the following disclaimer. 51 | * Redistributions in binary form must reproduce the above copyright notice, this 52 | * list of conditions and the following disclaimer in the documentation and/or other 53 | * materials provided with the distribution. 54 | * Neither the name of the MiG InfoCom AB nor the names of its contributors may be 55 | * used to endorse or promote products derived from this software without specific 56 | * prior written permission. 57 | *

58 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 59 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 60 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 61 | * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 62 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 63 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 64 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 65 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 66 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 67 | * OF SUCH DAMAGE. 68 | * 69 | * @author Mikael Grev 70 | * Date: 2004-aug-02 71 | * Time: 11:31:11 72 | * @version 2.2 73 | */ 74 | 75 | @SuppressWarnings("all") 76 | public class Base64 { 77 | private static final char[] CA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); 78 | private static final int[] IA = new int[256]; 79 | 80 | static { 81 | Arrays.fill(IA, -1); 82 | for (int i = 0, iS = CA.length; i < iS; i++) 83 | IA[CA[i]] = i; 84 | IA['='] = 0; 85 | } 86 | 87 | // **************************************************************************************** 88 | // * char[] version 89 | // **************************************************************************************** 90 | 91 | /** 92 | * Encodes a raw byte array into a BASE64 char[] representation i accordance with RFC 2045. 93 | * 94 | * @param sArr The bytes to convert. If null or length 0 an empty array will be returned. 95 | * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
96 | * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a 97 | * little faster. 98 | * @return A BASE64 encoded array. Never null. 99 | */ 100 | public static char[] encodeToChar(byte[] sArr, boolean lineSep) { 101 | // Check special case 102 | int sLen = sArr != null ? sArr.length : 0; 103 | if (sLen == 0) 104 | return new char[0]; 105 | 106 | int eLen = (sLen / 3) * 3; // Length of even 24-bits. 107 | int cCnt = ((sLen - 1) / 3 + 1) << 2; // Returned character count 108 | int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0); // Length of returned array 109 | char[] dArr = new char[dLen]; 110 | 111 | // Encode even 24-bits 112 | for (int s = 0, d = 0, cc = 0; s < eLen; ) { 113 | // Copy next three bytes into lower 24 bits of int, paying attension to sign. 114 | int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff); 115 | 116 | // Encode the int into four chars 117 | dArr[d++] = CA[(i >>> 18) & 0x3f]; 118 | dArr[d++] = CA[(i >>> 12) & 0x3f]; 119 | dArr[d++] = CA[(i >>> 6) & 0x3f]; 120 | dArr[d++] = CA[i & 0x3f]; 121 | 122 | // Add optional line separator 123 | if (lineSep && ++cc == 19 && d < dLen - 2) { 124 | dArr[d++] = '\r'; 125 | dArr[d++] = '\n'; 126 | cc = 0; 127 | } 128 | } 129 | 130 | // Pad and encode last bits if source isn't even 24 bits. 131 | int left = sLen - eLen; // 0 - 2. 132 | if (left > 0) { 133 | // Prepare the int 134 | int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0); 135 | 136 | // Set last four chars 137 | dArr[dLen - 4] = CA[i >> 12]; 138 | dArr[dLen - 3] = CA[(i >>> 6) & 0x3f]; 139 | dArr[dLen - 2] = left == 2 ? CA[i & 0x3f] : '='; 140 | dArr[dLen - 1] = '='; 141 | } 142 | return dArr; 143 | } 144 | 145 | /** 146 | * Decodes a BASE64 encoded char array. All illegal characters will be ignored and can handle both arrays with 147 | * and without line separators. 148 | * 149 | * @param sArr The source array. null or length 0 will return an empty array. 150 | * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters 151 | * (including '=') isn't divideable by 4. (I.e. definitely corrupted). 152 | */ 153 | public static byte[] decode(char[] sArr) { 154 | // Check special case 155 | int sLen = sArr != null ? sArr.length : 0; 156 | if (sLen == 0) 157 | return new byte[0]; 158 | 159 | // Count illegal characters (including '\r', '\n') to know what size the returned array will be, 160 | // so we don't have to reallocate & copy it later. 161 | int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...) 162 | for (int i = 0; i < sLen; i++) // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out. 163 | if (IA[sArr[i]] < 0) 164 | sepCnt++; 165 | 166 | // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045. 167 | if ((sLen - sepCnt) % 4 != 0) 168 | return null; 169 | 170 | int pad = 0; 171 | for (int i = sLen; i > 1 && IA[sArr[--i]] <= 0; ) 172 | if (sArr[i] == '=') 173 | pad++; 174 | 175 | int len = ((sLen - sepCnt) * 6 >> 3) - pad; 176 | 177 | byte[] dArr = new byte[len]; // Preallocate byte[] of exact length 178 | 179 | for (int s = 0, d = 0; d < len; ) { 180 | // Assemble three bytes into an int from four "valid" characters. 181 | int i = 0; 182 | for (int j = 0; j < 4; j++) { // j only increased if a valid char was found. 183 | int c = IA[sArr[s++]]; 184 | if (c >= 0) 185 | i |= c << (18 - j * 6); 186 | else 187 | j--; 188 | } 189 | // Add the bytes 190 | dArr[d++] = (byte) (i >> 16); 191 | if (d < len) { 192 | dArr[d++] = (byte) (i >> 8); 193 | if (d < len) 194 | dArr[d++] = (byte) i; 195 | } 196 | } 197 | return dArr; 198 | } 199 | 200 | /** 201 | * Decodes a BASE64 encoded char array that is known to be resonably well formatted. The method is about twice as 202 | * fast as {@link #decode(char[])}. The preconditions are:
203 | * + The array must have a line length of 76 chars OR no line separators at all (one line).
204 | * + Line separator must be "\r\n", as specified in RFC 2045 205 | * + The array must not contain illegal characters within the encoded string
206 | * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
207 | * 208 | * @param sArr The source array. Length 0 will return an empty array. null will throw an exception. 209 | * @return The decoded array of bytes. May be of length 0. 210 | */ 211 | public static byte[] decodeFast(char[] sArr) { 212 | // Check special case 213 | int sLen = sArr.length; 214 | if (sLen == 0) 215 | return new byte[0]; 216 | 217 | int sIx = 0, eIx = sLen - 1; // Start and end index after trimming. 218 | 219 | // Trim illegal chars from start 220 | while (sIx < eIx && IA[sArr[sIx]] < 0) 221 | sIx++; 222 | 223 | // Trim illegal chars from end 224 | while (eIx > 0 && IA[sArr[eIx]] < 0) 225 | eIx--; 226 | 227 | // get the padding count (=) (0, 1 or 2) 228 | int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0; // Count '=' at end. 229 | int cCnt = eIx - sIx + 1; // Content count including possible separators 230 | int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0; 231 | 232 | int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes 233 | byte[] dArr = new byte[len]; // Preallocate byte[] of exact length 234 | 235 | // Decode all but the last 0 - 2 bytes. 236 | int d = 0; 237 | for (int cc = 0, eLen = (len / 3) * 3; d < eLen; ) { 238 | // Assemble three bytes into an int from four "valid" characters. 239 | int i = IA[sArr[sIx++]] << 18 | IA[sArr[sIx++]] << 12 | IA[sArr[sIx++]] << 6 | IA[sArr[sIx++]]; 240 | 241 | // Add the bytes 242 | dArr[d++] = (byte) (i >> 16); 243 | dArr[d++] = (byte) (i >> 8); 244 | dArr[d++] = (byte) i; 245 | 246 | // If line separator, jump over it. 247 | if (sepCnt > 0 && ++cc == 19) { 248 | sIx += 2; 249 | cc = 0; 250 | } 251 | } 252 | 253 | if (d < len) { 254 | // Decode last 1-3 bytes (incl '=') into 1-3 bytes 255 | int i = 0; 256 | for (int j = 0; sIx <= eIx - pad; j++) 257 | i |= IA[sArr[sIx++]] << (18 - j * 6); 258 | 259 | for (int r = 16; d < len; r -= 8) 260 | dArr[d++] = (byte) (i >> r); 261 | } 262 | 263 | return dArr; 264 | } 265 | 266 | // **************************************************************************************** 267 | // * byte[] version 268 | // **************************************************************************************** 269 | 270 | /** 271 | * Encodes a raw byte array into a BASE64 byte[] representation i accordance with RFC 2045. 272 | * 273 | * @param sArr The bytes to convert. If null or length 0 an empty array will be returned. 274 | * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
275 | * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a 276 | * little faster. 277 | * @return A BASE64 encoded array. Never null. 278 | */ 279 | public static byte[] encodeToByte(byte[] sArr, boolean lineSep) { 280 | // Check special case 281 | int sLen = sArr != null ? sArr.length : 0; 282 | if (sLen == 0) 283 | return new byte[0]; 284 | 285 | int eLen = (sLen / 3) * 3; // Length of even 24-bits. 286 | int cCnt = ((sLen - 1) / 3 + 1) << 2; // Returned character count 287 | int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0); // Length of returned array 288 | byte[] dArr = new byte[dLen]; 289 | 290 | // Encode even 24-bits 291 | for (int s = 0, d = 0, cc = 0; s < eLen; ) { 292 | // Copy next three bytes into lower 24 bits of int, paying attension to sign. 293 | int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff); 294 | 295 | // Encode the int into four chars 296 | dArr[d++] = (byte) CA[(i >>> 18) & 0x3f]; 297 | dArr[d++] = (byte) CA[(i >>> 12) & 0x3f]; 298 | dArr[d++] = (byte) CA[(i >>> 6) & 0x3f]; 299 | dArr[d++] = (byte) CA[i & 0x3f]; 300 | 301 | // Add optional line separator 302 | if (lineSep && ++cc == 19 && d < dLen - 2) { 303 | dArr[d++] = '\r'; 304 | dArr[d++] = '\n'; 305 | cc = 0; 306 | } 307 | } 308 | 309 | // Pad and encode last bits if source isn't an even 24 bits. 310 | int left = sLen - eLen; // 0 - 2. 311 | if (left > 0) { 312 | // Prepare the int 313 | int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0); 314 | 315 | // Set last four chars 316 | dArr[dLen - 4] = (byte) CA[i >> 12]; 317 | dArr[dLen - 3] = (byte) CA[(i >>> 6) & 0x3f]; 318 | dArr[dLen - 2] = left == 2 ? (byte) CA[i & 0x3f] : (byte) '='; 319 | dArr[dLen - 1] = '='; 320 | } 321 | return dArr; 322 | } 323 | 324 | /** 325 | * Decodes a BASE64 encoded byte array. All illegal characters will be ignored and can handle both arrays with 326 | * and without line separators. 327 | * 328 | * @param sArr The source array. Length 0 will return an empty array. null will throw an exception. 329 | * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters 330 | * (including '=') isn't divideable by 4. (I.e. definitely corrupted). 331 | */ 332 | public static byte[] decode(byte[] sArr) { 333 | // Check special case 334 | int sLen = sArr.length; 335 | 336 | // Count illegal characters (including '\r', '\n') to know what size the returned array will be, 337 | // so we don't have to reallocate & copy it later. 338 | int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...) 339 | for (int i = 0; i < sLen; i++) // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out. 340 | if (IA[sArr[i] & 0xff] < 0) 341 | sepCnt++; 342 | 343 | // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045. 344 | if ((sLen - sepCnt) % 4 != 0) 345 | return null; 346 | 347 | int pad = 0; 348 | for (int i = sLen; i > 1 && IA[sArr[--i] & 0xff] <= 0; ) 349 | if (sArr[i] == '=') 350 | pad++; 351 | 352 | int len = ((sLen - sepCnt) * 6 >> 3) - pad; 353 | 354 | byte[] dArr = new byte[len]; // Preallocate byte[] of exact length 355 | 356 | for (int s = 0, d = 0; d < len; ) { 357 | // Assemble three bytes into an int from four "valid" characters. 358 | int i = 0; 359 | for (int j = 0; j < 4; j++) { // j only increased if a valid char was found. 360 | int c = IA[sArr[s++] & 0xff]; 361 | if (c >= 0) 362 | i |= c << (18 - j * 6); 363 | else 364 | j--; 365 | } 366 | 367 | // Add the bytes 368 | dArr[d++] = (byte) (i >> 16); 369 | if (d < len) { 370 | dArr[d++] = (byte) (i >> 8); 371 | if (d < len) 372 | dArr[d++] = (byte) i; 373 | } 374 | } 375 | 376 | return dArr; 377 | } 378 | 379 | 380 | /** 381 | * Decodes a BASE64 encoded byte array that is known to be resonably well formatted. The method is about twice as 382 | * fast as {@link #decode(byte[])}. The preconditions are:
383 | * + The array must have a line length of 76 chars OR no line separators at all (one line).
384 | * + Line separator must be "\r\n", as specified in RFC 2045 385 | * + The array must not contain illegal characters within the encoded string
386 | * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
387 | * 388 | * @param sArr The source array. Length 0 will return an empty array. null will throw an exception. 389 | * @return The decoded array of bytes. May be of length 0. 390 | */ 391 | public static byte[] decodeFast(byte[] sArr) { 392 | // Check special case 393 | int sLen = sArr.length; 394 | if (sLen == 0) 395 | return new byte[0]; 396 | 397 | int sIx = 0, eIx = sLen - 1; // Start and end index after trimming. 398 | 399 | // Trim illegal chars from start 400 | while (sIx < eIx && IA[sArr[sIx] & 0xff] < 0) 401 | sIx++; 402 | 403 | // Trim illegal chars from end 404 | while (eIx > 0 && IA[sArr[eIx] & 0xff] < 0) 405 | eIx--; 406 | 407 | // get the padding count (=) (0, 1 or 2) 408 | int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0; // Count '=' at end. 409 | int cCnt = eIx - sIx + 1; // Content count including possible separators 410 | int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0; 411 | 412 | int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes 413 | byte[] dArr = new byte[len]; // Preallocate byte[] of exact length 414 | 415 | // Decode all but the last 0 - 2 bytes. 416 | int d = 0; 417 | for (int cc = 0, eLen = (len / 3) * 3; d < eLen; ) { 418 | // Assemble three bytes into an int from four "valid" characters. 419 | int i = IA[sArr[sIx++]] << 18 | IA[sArr[sIx++]] << 12 | IA[sArr[sIx++]] << 6 | IA[sArr[sIx++]]; 420 | 421 | // Add the bytes 422 | dArr[d++] = (byte) (i >> 16); 423 | dArr[d++] = (byte) (i >> 8); 424 | dArr[d++] = (byte) i; 425 | 426 | // If line separator, jump over it. 427 | if (sepCnt > 0 && ++cc == 19) { 428 | sIx += 2; 429 | cc = 0; 430 | } 431 | } 432 | 433 | if (d < len) { 434 | // Decode last 1-3 bytes (incl '=') into 1-3 bytes 435 | int i = 0; 436 | for (int j = 0; sIx <= eIx - pad; j++) 437 | i |= IA[sArr[sIx++]] << (18 - j * 6); 438 | 439 | for (int r = 16; d < len; r -= 8) 440 | dArr[d++] = (byte) (i >> r); 441 | } 442 | 443 | return dArr; 444 | } 445 | 446 | // **************************************************************************************** 447 | // * String version 448 | // **************************************************************************************** 449 | 450 | /** 451 | * Encodes a raw byte array into a BASE64 String representation i accordance with RFC 2045. 452 | * 453 | * @param sArr The bytes to convert. If null or length 0 an empty array will be returned. 454 | * @param lineSep Optional "\r\n" after 76 characters, unless end of file.
455 | * No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a 456 | * little faster. 457 | * @return A BASE64 encoded array. Never null. 458 | */ 459 | public static String encodeToString(byte[] sArr, boolean lineSep) { 460 | // Reuse char[] since we can't create a String incrementally anyway and StringBuffer/Builder would be slower. 461 | return new String(encodeToChar(sArr, lineSep)); 462 | } 463 | 464 | /** 465 | * Decodes a BASE64 encoded String. All illegal characters will be ignored and can handle both strings with 466 | * and without line separators.
467 | * Note! It can be up to about 2x the speed to call decode(str.toCharArray()) instead. That 468 | * will create a temporary array though. This version will use str.charAt(i) to iterate the string. 469 | * 470 | * @param str The source string. null or length 0 will return an empty array. 471 | * @return The decoded array of bytes. May be of length 0. Will be null if the legal characters 472 | * (including '=') isn't divideable by 4. (I.e. definitely corrupted). 473 | */ 474 | public static byte[] decode(String str) { 475 | // Check special case 476 | int sLen = str != null ? str.length() : 0; 477 | if (sLen == 0) 478 | return new byte[0]; 479 | 480 | // Count illegal characters (including '\r', '\n') to know what size the returned array will be, 481 | // so we don't have to reallocate & copy it later. 482 | int sepCnt = 0; // Number of separator characters. (Actually illegal characters, but that's a bonus...) 483 | for (int i = 0; i < sLen; i++) // If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out. 484 | if (IA[str.charAt(i)] < 0) 485 | sepCnt++; 486 | 487 | // Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045. 488 | if ((sLen - sepCnt) % 4 != 0) 489 | return null; 490 | 491 | // Count '=' at end 492 | int pad = 0; 493 | for (int i = sLen; i > 1 && IA[str.charAt(--i)] <= 0; ) 494 | if (str.charAt(i) == '=') 495 | pad++; 496 | 497 | int len = ((sLen - sepCnt) * 6 >> 3) - pad; 498 | 499 | byte[] dArr = new byte[len]; // Preallocate byte[] of exact length 500 | 501 | for (int s = 0, d = 0; d < len; ) { 502 | // Assemble three bytes into an int from four "valid" characters. 503 | int i = 0; 504 | for (int j = 0; j < 4; j++) { // j only increased if a valid char was found. 505 | int c = IA[str.charAt(s++)]; 506 | if (c >= 0) 507 | i |= c << (18 - j * 6); 508 | else 509 | j--; 510 | } 511 | // Add the bytes 512 | dArr[d++] = (byte) (i >> 16); 513 | if (d < len) { 514 | dArr[d++] = (byte) (i >> 8); 515 | if (d < len) 516 | dArr[d++] = (byte) i; 517 | } 518 | } 519 | return dArr; 520 | } 521 | 522 | /** 523 | * Decodes a BASE64 encoded string that is known to be resonably well formatted. The method is about twice as 524 | * fast as {@link #decode(String)}. The preconditions are:
525 | * + The array must have a line length of 76 chars OR no line separators at all (one line).
526 | * + Line separator must be "\r\n", as specified in RFC 2045 527 | * + The array must not contain illegal characters within the encoded string
528 | * + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.
529 | * 530 | * @param s The source string. Length 0 will return an empty array. null will throw an exception. 531 | * @return The decoded array of bytes. May be of length 0. 532 | */ 533 | public static byte[] decodeFast(String s) { 534 | // Check special case 535 | int sLen = s.length(); 536 | if (sLen == 0) 537 | return new byte[0]; 538 | 539 | int sIx = 0, eIx = sLen - 1; // Start and end index after trimming. 540 | 541 | // Trim illegal chars from start 542 | while (sIx < eIx && IA[s.charAt(sIx) & 0xff] < 0) 543 | sIx++; 544 | 545 | // Trim illegal chars from end 546 | while (eIx > 0 && IA[s.charAt(eIx) & 0xff] < 0) 547 | eIx--; 548 | 549 | // get the padding count (=) (0, 1 or 2) 550 | int pad = s.charAt(eIx) == '=' ? (s.charAt(eIx - 1) == '=' ? 2 : 1) : 0; // Count '=' at end. 551 | int cCnt = eIx - sIx + 1; // Content count including possible separators 552 | int sepCnt = sLen > 76 ? (s.charAt(76) == '\r' ? cCnt / 78 : 0) << 1 : 0; 553 | 554 | int len = ((cCnt - sepCnt) * 6 >> 3) - pad; // The number of decoded bytes 555 | byte[] dArr = new byte[len]; // Preallocate byte[] of exact length 556 | 557 | // Decode all but the last 0 - 2 bytes. 558 | int d = 0; 559 | for (int cc = 0, eLen = (len / 3) * 3; d < eLen; ) { 560 | // Assemble three bytes into an int from four "valid" characters. 561 | int i = IA[s.charAt(sIx++)] << 18 | IA[s.charAt(sIx++)] << 12 | IA[s.charAt(sIx++)] << 6 | IA[s.charAt(sIx++)]; 562 | 563 | // Add the bytes 564 | dArr[d++] = (byte) (i >> 16); 565 | dArr[d++] = (byte) (i >> 8); 566 | dArr[d++] = (byte) i; 567 | 568 | // If line separator, jump over it. 569 | if (sepCnt > 0 && ++cc == 19) { 570 | sIx += 2; 571 | cc = 0; 572 | } 573 | } 574 | 575 | if (d < len) { 576 | // Decode last 1-3 bytes (incl '=') into 1-3 bytes 577 | int i = 0; 578 | for (int j = 0; sIx <= eIx - pad; j++) 579 | i |= IA[s.charAt(sIx++)] << (18 - j * 6); 580 | 581 | for (int r = 16; d < len; r -= 8) 582 | dArr[d++] = (byte) (i >> r); 583 | } 584 | 585 | return dArr; 586 | } 587 | 588 | public static String dup(char c, int num) { 589 | if (c == 0 || num < 1) 590 | return ""; 591 | StringBuilder sb = new StringBuilder(num); 592 | for (int i = 0; i < num; i++) 593 | sb.append(c); 594 | return sb.toString(); 595 | } 596 | 597 | @SuppressWarnings("StringBufferReplaceableByString") 598 | public static class URLSafe { 599 | 600 | /** 601 | * 返回一个不带折行的 Base64 编码的 URL 字符串 602 | *

603 | * Note: 根据 RFC 4648 的规定,把『+』变成『-』,把『/』变成『_』,去除末尾填充的『=』 604 | * 605 | * @param url 需要编码的 URL 606 | * @return 不带折行的 Base64 编码的 URL 字符串 607 | */ 608 | public static String encode(String url) { 609 | return encode(url.getBytes(), false); 610 | } 611 | 612 | /** 613 | * 返回一个 Base64 编码的 URL 字符串 614 | *

615 | * Note: 根据 RFC 4648 的规定,把『+』变成『-』,把『/』变成『_』,去除末尾填充的『=』 616 | * 617 | * @param url 需要编码的 URL 618 | * @param lineSep 是否在76个字符处添加折行 619 | * @return Base64 编码的 URL 字符串 620 | */ 621 | public static String encode(byte[] url, boolean lineSep) { 622 | return Base64.encodeToString(url, lineSep).replace('+', '-').replace('/', '_').replaceAll("=", ""); 623 | } 624 | 625 | /** 626 | * 返回通过 URL Safe Base64 解码后的 URL 字符串。
627 | * 如果传入的字符串不合法,将返回空字符串。 628 | * 629 | * @param base64 URL Safe Base64 编码的 URL 630 | * @return URL 字符串 631 | */ 632 | public static String decode(String base64) { 633 | StringBuilder sb = new StringBuilder(base64); 634 | sb.append(dup('=', 5 - base64.length() % 4 - 1)); 635 | byte[] decode = Base64.decode(sb.toString().replace('-', '+').replace('_', '/')); 636 | if (decode == null || decode.length == 0) { 637 | return ""; 638 | } 639 | return new String(decode); 640 | } 641 | } 642 | } 643 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/catcode/CatCodeHelper.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.catcode; 2 | 3 | import cc.sfclub.catcode.entities.At; 4 | import cc.sfclub.catcode.entities.Image; 5 | 6 | import java.util.LinkedList; 7 | import java.util.List; 8 | import java.util.regex.Pattern; 9 | 10 | /** 11 | * 猫码工具类 12 | */ 13 | public class CatCodeHelper { 14 | private static final Pattern number = Pattern.compile("^([-+])?\\d+(\\.\\d+)?E?$"); 15 | 16 | /** 17 | * Spilt into array. 18 | * For example: 19 | * x[x]xx[x]xx -> 20 | * x 21 | * [x] 22 | * xx 23 | * [x] 24 | * xx 25 | * 26 | * @param str message contains catCode 27 | * @return string array 28 | */ 29 | public static String[] spilt(String str) { 30 | char[] ch = str.toCharArray(); 31 | List list = new LinkedList<>(); 32 | StringBuilder builder = new StringBuilder(); 33 | for (int i = 0; i < ch.length; i++) { 34 | char c = ch[i]; 35 | area_1: 36 | if (c == '[') { 37 | if (i > 0 && ch[i - 1] == '\\') break area_1; 38 | if (builder.length() > 0) list.add(builder.toString()); 39 | builder = new StringBuilder(); 40 | builder.append(c); 41 | continue; 42 | } else if (c == ']') { 43 | if (i > 0 && ch[i - 1] == '\\') break area_1; 44 | builder.append(c); 45 | list.add(builder.toString()); 46 | builder = new StringBuilder(); 47 | continue; 48 | } 49 | builder.append(c); 50 | } 51 | if (builder.length() > 0) list.add(builder.toString()); 52 | String[] strs = new String[list.size()]; 53 | for (int i = 0; i < strs.length; i++) { 54 | strs[i] = list.get(i); 55 | } 56 | return strs; 57 | } 58 | 59 | /** 60 | * Parse "at" to a entity. 61 | * 62 | * @param strAt CatCodeHelper 63 | * @return At 64 | */ 65 | public static At parseAt(String strAt) { 66 | if (!(strAt.startsWith("[") && strAt.endsWith("]"))) { 67 | return null; 68 | } 69 | String str = strAt.replaceFirst("\\[", ""); 70 | str = str.replaceAll("]", ""); 71 | if ("AtAll".equals(str)) { 72 | return At.builder().all(true).build(); 73 | } 74 | String[] args = str.split(","); 75 | At at = At.builder().build(); 76 | long usrId; 77 | String[] a; 78 | if (args.length == 0) { 79 | a = str.split(":"); 80 | } else { 81 | a = args[0].split(":"); 82 | } 83 | /*if (!isNumeric(a[1])) { 84 | return null; 85 | }*/ 86 | at.userId = a[1]; 87 | return at; 88 | } 89 | 90 | /** 91 | * Parse Img to Entity 92 | * 93 | * @param strImg cCode 94 | * @return image entity 95 | */ 96 | public static Image parseImg(String strImg) { 97 | if (!(strImg.startsWith("[") && strImg.endsWith("]"))) { 98 | return null; 99 | } 100 | String str = strImg.replaceFirst("\\[", ""); 101 | str = str.replaceAll("]", ""); 102 | String[] args = str.split(","); 103 | Image img = Image.builder().build(); 104 | String[] a; 105 | if (args.length == 0) { 106 | a = str.split(":"); 107 | } else { 108 | a = args[0].split(":"); 109 | } 110 | if (a[1].endsWith(".jpg") || a[1].endsWith(".png")) { 111 | img.ID = a[1]; 112 | return img; 113 | } 114 | img.URI = Base64.URLSafe.decode(a[1]); 115 | return img; 116 | } 117 | 118 | /** 119 | * return true if str is number. 120 | * 121 | * @param str string 122 | * @return result 123 | */ 124 | public static boolean isNumeric(String str) { 125 | return number.matcher(str).matches(); 126 | } 127 | } -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/catcode/entities/At.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.catcode.entities; 2 | 3 | import lombok.Builder; 4 | 5 | @Builder 6 | public class At { 7 | 8 | /** 9 | * 目标用户ID 10 | */ 11 | public String userId; 12 | /** 13 | * 是否是at所有人 14 | */ 15 | public boolean all; 16 | 17 | @Override 18 | public String toString() { 19 | if (all) { 20 | return "[AtAll]"; 21 | } 22 | return "[At:" + userId + "]"; 23 | } 24 | } -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/catcode/entities/Image.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.catcode.entities; 2 | 3 | import cc.sfclub.catcode.Base64; 4 | import lombok.Builder; 5 | 6 | /** 7 | * 图片消息 8 | */ 9 | @Builder 10 | public class Image { 11 | /** 12 | * 图片地址 13 | */ 14 | public String URI; 15 | /** 16 | * 图片ID,一般由transformer方提供 17 | */ 18 | public String ID; 19 | 20 | @Override 21 | public String toString() { 22 | if (URI.isEmpty()) { 23 | return "[Image:" + ID + "]"; 24 | } 25 | return "[Image:" + Base64.URLSafe.encode(URI) + "]"; 26 | } 27 | } -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/catcode/entities/Plain.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.catcode.entities; 2 | 3 | import lombok.Builder; 4 | 5 | /** 6 | * 纯文本 7 | */ 8 | @Builder 9 | public class Plain { 10 | /** 11 | * 文本内容 12 | */ 13 | public String text; 14 | 15 | @Override 16 | public String toString() { 17 | return "[Plain:" + text + "]"; 18 | } 19 | } -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/catcode/entities/package-info.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.catcode.entities; 2 | /* 3 | * 猫码实体 4 | * 所有猫码实体均重写toString,输出猫码 5 | */ -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/command/CommandListener.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.command; 2 | 3 | import cc.sfclub.core.Core; 4 | import cc.sfclub.events.MessageEvent; 5 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 6 | import org.greenrobot.eventbus.Subscribe; 7 | import org.greenrobot.eventbus.ThreadMode; 8 | 9 | public class CommandListener { 10 | @Subscribe(threadMode = ThreadMode.ASYNC, sticky = true) 11 | public void onMessage(MessageEvent event) { 12 | if (event.getMessage() != null && event.getMessage().startsWith(Core.get().config().getCommandPrefix() + " ")) { 13 | String command = event.getMessage().substring((Core.get().config().getCommandPrefix() + " ").length()); 14 | try { 15 | Core.get().dispatcher().execute(command, new Source(event, System.currentTimeMillis())); 16 | } catch (CommandSyntaxException exception) { 17 | event.reply(event.getMessageID(), exception.getMessage()); 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/command/Source.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.command; 2 | 3 | import cc.sfclub.core.Core; 4 | import cc.sfclub.events.MessageEvent; 5 | import cc.sfclub.user.User; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Getter; 8 | 9 | /** 10 | * 消息源 11 | */ 12 | @AllArgsConstructor 13 | @Getter 14 | public class Source { 15 | /* 16 | * 消息事件 17 | */ 18 | private final MessageEvent messageEvent; 19 | /** 20 | * 产生时间 21 | */ 22 | private final long time; 23 | 24 | /** 25 | * 直接获取纯文本消息 26 | * 27 | * @return 消息事件里的消息 28 | */ 29 | public String getMessage() { 30 | return messageEvent.getMessage(); 31 | } 32 | 33 | /** 34 | * 获取发送者 35 | * 36 | * @return 发送者 37 | */ 38 | public User getSender() { 39 | return Core.get().userManager().byUUID(messageEvent.getUserID()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/command/internal/Me.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.command.internal; 2 | 3 | import cc.sfclub.command.Source; 4 | import cc.sfclub.user.User; 5 | import com.mojang.brigadier.Command; 6 | import com.mojang.brigadier.context.CommandContext; 7 | 8 | import java.util.Arrays; 9 | 10 | public class Me implements Command { 11 | @Override 12 | public int run(CommandContext context) { 13 | Source src = context.getSource(); 14 | User u = src.getSender(); 15 | src.getMessageEvent().reply("Your UserID: " + u.getUniqueID() + "\n" + 16 | "Your UserGroup: " + u.getUserGroup() + "\n" + 17 | "Redirected User: " + (u.getRedirectTo() != null) + "\n" + 18 | "Permissions(" + u.permList.size() + "): " + Arrays.toString(u.permList.toArray()) 19 | ); 20 | return 0; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/command/internal/Op.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.command.internal; 2 | 3 | import cc.sfclub.command.Source; 4 | import cc.sfclub.core.Core; 5 | import cc.sfclub.user.User; 6 | import cc.sfclub.user.perm.Perm; 7 | import com.mojang.brigadier.Command; 8 | import com.mojang.brigadier.arguments.StringArgumentType; 9 | import com.mojang.brigadier.context.CommandContext; 10 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 11 | 12 | public class Op implements Command { 13 | @Override 14 | public int run(CommandContext context) throws CommandSyntaxException { 15 | User user = Core.get().userManager().byUUID(StringArgumentType.getString(context, "user")); 16 | if (user == null) { 17 | user = Core.get().userManager().byName(StringArgumentType.getString(context, "user")); 18 | } 19 | if (user == null) { 20 | context.getSource().getMessageEvent().reply("User ID/Name not exists."); 21 | return 0; 22 | } 23 | if (user.hasPermission(Perm.of(".*"))) { 24 | context.getSource().getMessageEvent().reply("Already OP!"); 25 | return 0; 26 | } 27 | user.addPermission(Perm.of(".*")); 28 | Core.get().userManager().update(user); 29 | context.getSource().getMessageEvent().reply("User " + user.asFormattedName() + " is op now."); 30 | return 0; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/command/internal/Stop.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.command.internal; 2 | 3 | import cc.sfclub.command.Source; 4 | import cc.sfclub.core.Core; 5 | import cc.sfclub.core.Initializer; 6 | import com.mojang.brigadier.Command; 7 | import com.mojang.brigadier.context.CommandContext; 8 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 9 | 10 | public class Stop implements Command { 11 | @Override 12 | public int run(CommandContext context) throws CommandSyntaxException { 13 | if (Initializer.getCONSOLE().getUniqueID().equals(context.getSource().getSender().getUniqueID())) { 14 | Core.get().stop(); 15 | System.exit(0); 16 | } 17 | return 0; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/core/Core.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.core; 2 | 3 | import cc.sfclub.command.Source; 4 | import cc.sfclub.core.security.PolarSec; 5 | import cc.sfclub.events.Event; 6 | import cc.sfclub.events.internal.MessageListener; 7 | import cc.sfclub.events.server.ServerStartedEvent; 8 | import cc.sfclub.events.server.ServerStoppingEvent; 9 | import cc.sfclub.plugin.PluginManager; 10 | import cc.sfclub.transform.Bot; 11 | import cc.sfclub.user.Group; 12 | import cc.sfclub.user.User; 13 | import cc.sfclub.user.UserManager; 14 | import com.dieselpoint.norm.Database; 15 | import com.mojang.brigadier.CommandDispatcher; 16 | import lombok.Getter; 17 | import lombok.NonNull; 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | 21 | import java.io.File; 22 | import java.util.HashMap; 23 | import java.util.Map; 24 | import java.util.Optional; 25 | 26 | /** 27 | * 核心。 28 | */ 29 | public class Core { 30 | private static Core core; 31 | 32 | public static final int CONFIG_VERSION = 2; 33 | /** 34 | * get a logger 35 | * 36 | * @return logger 37 | */ 38 | private final Logger logger = LoggerFactory.getLogger("Core"); 39 | private final CoreCfg config; 40 | private final PermCfg permCfg; 41 | private final DatabaseCfg dbcfg; 42 | private UserManager userManager; 43 | @Getter 44 | private final PolarSec polarSec = new PolarSec(); 45 | private final CommandDispatcher dispatcher = new CommandDispatcher<>(); 46 | private final Map bots = new HashMap<>(); 47 | private final PluginManager pluginManager = new PluginManager("./plugins"); 48 | public static final String CORE_VERSION = "V4.8.0"; 49 | private Database ORM; 50 | 51 | public Core(CoreCfg config, PermCfg permCfg, DatabaseCfg dbcfg) { 52 | Core.core = this; 53 | this.config = config; 54 | this.permCfg = permCfg; 55 | this.dbcfg = dbcfg; 56 | loadLang(config); 57 | loadDatabase(); 58 | loadUserManager(); 59 | pluginManager.loadPlugins(); 60 | Event.registerListeners(new MessageListener()); 61 | Event.postEvent(new ServerStartedEvent()); 62 | } 63 | 64 | private void loadDatabase() { 65 | ORM = new Database(); 66 | ORM.setJdbcUrl(dbcfg.getJdbcUrl()); 67 | ORM.setUser(dbcfg.getUser()); 68 | ORM.setPassword(dbcfg.getPassword()); 69 | //load user table 70 | logger.info("Loading Database.."); 71 | if (config.isResetDatabase()) { 72 | ORM().createTable(User.class); 73 | logger.info(I18N.get().exceptions.TABLE_LOADING, "user"); 74 | ORM().createTable(Group.class); 75 | logger.info(I18N.get().exceptions.TABLE_LOADING, "userGroup"); 76 | config.setResetDatabase(false); 77 | config.saveConfig(); 78 | } 79 | } 80 | 81 | private void loadLang(CoreCfg cfg) { 82 | new File("locale").mkdir(); 83 | I18N i18N = new I18N(cfg.getLocale()); 84 | I18N.setInst((I18N) i18N.saveDefaultOrLoad()); 85 | if (i18N.getConfVer() != I18N.CONFIG_VERSION) { 86 | logger.warn(I18N.get().exceptions.CONFIG_OUTDATED, cfg.getConfigName()); 87 | } 88 | } 89 | 90 | private void loadUserManager() { 91 | userManager = new UserManager(ORM()); 92 | permCfg.getGroupList().forEach(userManager::addRaw); 93 | } 94 | 95 | public void stop() { 96 | logger.info(I18N.get().server.STOPPING_SERVER); 97 | Event.postEvent(new ServerStoppingEvent()); 98 | pluginManager.unloadPlugins(); 99 | Event.unregisterAllListeners(); 100 | } 101 | 102 | /** 103 | * Get core 104 | * 105 | * @return Core 106 | */ 107 | public static Core get() { 108 | return core; 109 | } 110 | 111 | /** 112 | * Register a bot 113 | * 114 | * @param bot bot 115 | */ 116 | 117 | public void registerBot(@NonNull Bot bot) { 118 | bots.put(bot.getName(), bot); 119 | } 120 | 121 | /** 122 | * get a bot 123 | * 124 | * @param name bot name 125 | * @return nullable bot 126 | */ 127 | public Optional bot(@NonNull String name) { 128 | return Optional.ofNullable(bots.get(name)); 129 | } 130 | 131 | /** 132 | * Get ORM 133 | * 134 | * @return ORM 135 | */ 136 | public Database ORM() { 137 | assert ORM != null; 138 | return ORM; 139 | } 140 | 141 | /** 142 | * get config about permission groups 143 | * 144 | * @return PermCfg 145 | */ 146 | public PermCfg permCfg() { 147 | return permCfg; 148 | } 149 | 150 | /** 151 | * get config 152 | * 153 | * @return config 154 | */ 155 | public CoreCfg config() { 156 | return this.config; 157 | } 158 | 159 | 160 | /** 161 | * @return command dispatcher 162 | */ 163 | public CommandDispatcher dispatcher() { 164 | return this.dispatcher; 165 | } 166 | 167 | /** 168 | * 获取用户管理器 169 | * 170 | * @return 171 | */ 172 | public UserManager userManager() { 173 | return this.userManager; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/core/CoreCfg.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.core; 2 | 3 | import cc.sfclub.util.common.JsonConfig; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | @SuppressWarnings("all") 8 | @Getter 9 | public class CoreCfg extends JsonConfig { 10 | private String commandPrefix = "!p"; 11 | private String locale = "en_US"; 12 | private int config_version = Core.CONFIG_VERSION; 13 | private boolean debug = true; 14 | private String name = "Polar"; 15 | private String version = "v4-production"; 16 | @Setter 17 | private boolean resetDatabase = true; 18 | 19 | public CoreCfg() { 20 | super("."); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/core/DatabaseCfg.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.core; 2 | 3 | import cc.sfclub.util.common.JsonConfig; 4 | import lombok.Getter; 5 | 6 | @SuppressWarnings("all") 7 | @Getter 8 | public class DatabaseCfg extends JsonConfig { 9 | private String jdbcUrl = "jdbc:sqlite:data.db"; //Mysql: jdbc:mysql://localhost:3306/XX 10 | private String driver = "org.sqlite.JDBC"; 11 | private String user = ""; 12 | private String password = ""; 13 | 14 | public DatabaseCfg() { 15 | super("."); 16 | } 17 | 18 | @Override 19 | public String getConfigName() { 20 | return "database.json"; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/core/I18N.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.core; 2 | 3 | import cc.sfclub.util.common.JsonConfig; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | public class I18N extends JsonConfig { 8 | public static final int CONFIG_VERSION = 1; 9 | /** 10 | * Set lang 11 | */ 12 | @Setter 13 | private static transient I18N inst = new I18N("en_US"); 14 | /** 15 | * a version number for check outdated config. 16 | * 17 | * @return config version 18 | */ 19 | @Getter 20 | private final int confVer = CONFIG_VERSION; 21 | public Server server = new Server(); 22 | public Exceptions exceptions = new Exceptions(); 23 | public Misc misc = new Misc(); 24 | /** 25 | * @return lang name 26 | */ 27 | @Getter 28 | private final transient String locale; 29 | 30 | public I18N(String locale) { 31 | super("./locale"); 32 | this.locale = locale; 33 | } 34 | 35 | @Override 36 | public String getConfigName() { 37 | return locale + ".json"; 38 | } 39 | 40 | /** 41 | * get I18N 42 | * 43 | * @return instance of i18n 44 | */ 45 | public static I18N get() { 46 | return inst; 47 | } 48 | public class Server { 49 | public String STARTING = "PolarCore {} starting.."; 50 | public String STARTED = "Server Started."; 51 | public String LOAD_MODULE = "Loading {} ver {}"; 52 | public String LOADING_MODULES = "Loading Modules.."; 53 | public String LOADED_MODULE = "Modules Loaded."; 54 | public String FIRST_START = "Go to edit your config first :D"; 55 | public String STOPPING_SERVER = "Stopping server.."; 56 | public String PLUGIN_PRELOADING = "Scanned Plugin {} ver {}"; 57 | public String PLUGIN_LOADING = "Loading Plugin {} ver {}"; 58 | } 59 | 60 | public class Misc { 61 | public String UNKNOWN_OPERATOR = "Unknown Command."; 62 | public String COULD_NOT_LOAD_PLUGIN = "Could not load plugin: {}"; 63 | public String LOADED_PLUGINS = "Loaded plugins({}): {}"; 64 | } 65 | 66 | public class Exceptions { 67 | public String TABLE_LOADING = "Loading Table \"{}\".."; 68 | public String CONFIG_OUTDATED = "CONFIG \"{}\" IS OUTDATED! TRY TO DELETE IT FOR A REGENERATION."; 69 | public String PLUGIN_NOT_SUBSCRIBER = "Plugin {} didn't subscribe any event!"; 70 | public String PLUGIN_DEPENDENCY_MISSING = "Can't find dependency {} for {}!"; 71 | public String PLUGIN_MAIN_CLASS_NOT_FOUND = "Can't find class {} as main class for plugin {}."; 72 | public String PLUGIN_DATA_CLASS_NOT_FOUND = "Can't find class {} as data class for plugin {}."; 73 | public String PLUGIN_HAVE_NO_AVAILABLE_CONSTRUCTOR = "Plugin {} have no empty args or accessible constructor."; 74 | public String PLUGIN_DEPEND_LOOP = "Dependency loop detected! {} and {} will not loaded."; 75 | public String PLUGIN_FAILED_TO_LOAD = "Failed to load these plugins: {}"; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/core/Initializer.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.core; 2 | 3 | import cc.sfclub.command.CommandListener; 4 | import cc.sfclub.command.Source; 5 | import cc.sfclub.command.internal.Me; 6 | import cc.sfclub.command.internal.Op; 7 | import cc.sfclub.events.Event; 8 | import cc.sfclub.events.message.group.GroupMessage; 9 | import cc.sfclub.transform.internal.ConsoleBot; 10 | import cc.sfclub.user.User; 11 | import cc.sfclub.user.perm.Perm; 12 | import cc.sfclub.util.common.SimpleFile; 13 | import com.mojang.brigadier.builder.LiteralArgumentBuilder; 14 | import com.mojang.brigadier.builder.RequiredArgumentBuilder; 15 | import lombok.Getter; 16 | import lombok.SneakyThrows; 17 | import org.slf4j.Logger; 18 | import org.slf4j.LoggerFactory; 19 | 20 | import java.util.Scanner; 21 | 22 | import static com.mojang.brigadier.arguments.StringArgumentType.string; 23 | 24 | public class Initializer { 25 | private static final CoreCfg cfg = (CoreCfg) new CoreCfg().saveDefaultOrLoad(); 26 | @Getter 27 | private static User CONSOLE; 28 | private static final Logger logger = LoggerFactory.getLogger(Initializer.class); 29 | 30 | @SneakyThrows 31 | public static void main(String[] args) { 32 | Class.forName("org.sqlite.JDBC"); //Don't ask why,that's magic. 33 | System.out.println("\n" + 34 | "________ ______ _________ \n" + 35 | "___ __ \\________ /_____ _________ ____/_________________ \n" + 36 | "__ /_/ / __ \\_ /_ __ `/_ ___/ / _ __ \\_ ___/ _ \\\n" + 37 | "_ ____// /_/ / / / /_/ /_ / / /___ / /_/ / / / __/\n" + 38 | "/_/ \\____//_/ \\__,_/ /_/ \\____/ \\____//_/ \\___/ \n" + 39 | " \n"); 40 | logger.info(I18N.get().server.STARTING, Core.CORE_VERSION); 41 | logger.info("Java Version: {}", Runtime.version().toString()); 42 | logger.info(I18N.get().server.LOADING_MODULES); 43 | Runtime.getRuntime().addShutdownHook(new ShutdownHook()); 44 | loadCore(); 45 | logger.info(I18N.get().server.LOADED_MODULE); 46 | waitCommand(); 47 | } 48 | private static void waitCommand() { 49 | Scanner scanner = new Scanner(System.in); 50 | Event.registerListeners(new CommandListener()); 51 | Core.get().dispatcher().register(LiteralArgumentBuilder.literal("me").executes(new Me())); 52 | Core.get().dispatcher().register(LiteralArgumentBuilder.literal("op") 53 | .requires(e -> e.getSender().hasPermission(Perm.of("internal.op"))) 54 | .then(RequiredArgumentBuilder.argument("user", string()).executes(new Op())) 55 | ); 56 | String command; 57 | while (scanner.hasNextLine()) { 58 | command = scanner.nextLine(); 59 | Event.postEvent(new GroupMessage(CONSOLE.getUniqueID(), command, 0L, User.CONSOLE_USER_NAME, 0L)); 60 | } 61 | scanner.close(); 62 | } 63 | 64 | @SneakyThrows 65 | private static void loadCore() { 66 | //Load Configs 67 | boolean firstLoad = false; 68 | DatabaseCfg dbCfg = new DatabaseCfg(); 69 | PermCfg permCfg = new PermCfg(); 70 | if (!SimpleFile.exists(permCfg.getConfigName()) || !SimpleFile.exists(dbCfg.getConfigName())) { 71 | firstLoad = true; 72 | } 73 | dbCfg = (DatabaseCfg) dbCfg.saveDefaultOrLoad(); 74 | permCfg = (PermCfg) permCfg.saveDefaultOrLoad(); 75 | if (firstLoad) { 76 | logger.info(I18N.get().server.FIRST_START); 77 | System.exit(0); 78 | } 79 | if (cfg.getConfig_version() != Core.CONFIG_VERSION) 80 | logger.warn(I18N.get().exceptions.CONFIG_OUTDATED, cfg.getConfigName()); 81 | new Core(cfg, permCfg, dbCfg); 82 | if (!Core.get().userManager().existsName(User.CONSOLE_USER_NAME)) { 83 | User console = Core.get().userManager().register(null, Perm.of(".*")); 84 | console.setUserName(User.CONSOLE_USER_NAME); 85 | Core.get().userManager().update(console); 86 | } 87 | CONSOLE = Core.get().userManager().byName(User.CONSOLE_USER_NAME); 88 | Core.get().registerBot(new ConsoleBot()); 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/core/PermCfg.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.core; 2 | 3 | import cc.sfclub.user.Group; 4 | import cc.sfclub.util.common.JsonConfig; 5 | import lombok.Getter; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | @Getter 11 | public class PermCfg extends JsonConfig { 12 | private final String defaultGroup = "_"; 13 | private final List groupList = new ArrayList<>(); 14 | 15 | public PermCfg() { 16 | super("."); 17 | } 18 | 19 | @Override 20 | public String getConfigName() { 21 | return "groups.json"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/core/ShutdownHook.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.core; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | public class ShutdownHook extends Thread { 7 | private final Logger logger = LoggerFactory.getLogger("ShutdownHook"); 8 | 9 | @Override 10 | public void run() { 11 | logger.info("Interrupt signal received!"); 12 | if (Core.get() == null) { 13 | logger.error("Core is null! Maybe polar didn't initialize completed or something wrong?"); 14 | return; 15 | } 16 | Core.get().stop(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/core/security/PolarSec.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.core.security; 2 | 3 | import cc.sfclub.events.MessageEvent; 4 | 5 | /** 6 | * Polar 安全模块 7 | */ 8 | public class PolarSec { 9 | public void postMessage(MessageEvent event, long time) { 10 | //todo 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/core/security/modules/HarmfulMessage.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.core.security.modules; 2 | 3 | /** 4 | * 有害消息。恐怖主义,色情消息.. 5 | */ 6 | public class HarmfulMessage { 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/core/security/modules/HighFrequencyMessage.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.core.security.modules; 2 | 3 | /** 4 | * 高频消息 5 | */ 6 | public class HighFrequencyMessage { 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/core/security/modules/SpamMessage.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.core.security.modules; 2 | 3 | /** 4 | * 垃圾信息,广告等 5 | */ 6 | public class SpamMessage { 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/database/converter/PermListConverter.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.database.converter; 2 | 3 | import cc.sfclub.user.perm.Perm; 4 | 5 | import javax.persistence.AttributeConverter; 6 | import javax.persistence.Converter; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.StringJoiner; 10 | 11 | @Converter 12 | public class PermListConverter implements AttributeConverter, String> { 13 | @Override 14 | public String convertToDatabaseColumn(List attribute) { 15 | StringJoiner result = new StringJoiner(","); 16 | attribute.forEach(e -> result.add(e.toString().replace("\\", "\\\\").replaceAll(",", "\\\\,"))); 17 | return result.toString(); 18 | } 19 | 20 | @Override 21 | public List convertToEntityAttribute(String dbData) { 22 | List result = new ArrayList<>(); 23 | boolean flag = false; 24 | StringBuilder temp = new StringBuilder(); 25 | for (char c : dbData.toCharArray()) { 26 | if (c == '\\') { 27 | if (flag) { 28 | temp.append(c); 29 | flag = false; 30 | continue; 31 | } 32 | flag = true; 33 | continue; 34 | } 35 | if (c == ',') { 36 | if (flag) { 37 | flag = false; 38 | temp.append(c); 39 | continue; 40 | } 41 | result.add(Perm.of(temp.toString())); 42 | temp = new StringBuilder(); 43 | continue; 44 | } 45 | temp.append(c); 46 | } 47 | result.add(Perm.of(temp.toString())); 48 | return result; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/database/converter/StrListConverter.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.database.converter; 2 | 3 | import javax.persistence.AttributeConverter; 4 | import javax.persistence.Converter; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.StringJoiner; 8 | 9 | @Converter 10 | public class StrListConverter implements AttributeConverter, String> { 11 | @Override 12 | public String convertToDatabaseColumn(List attribute) { 13 | StringJoiner result = new StringJoiner(","); 14 | attribute.forEach(e -> result.add(e.replace("\\", "\\\\").replaceAll(",", "\\\\,"))); 15 | return result.toString(); 16 | } 17 | 18 | @Override 19 | public List convertToEntityAttribute(String dbData) { 20 | List result = new ArrayList<>(); 21 | boolean flag = false; 22 | StringBuilder temp = new StringBuilder(); 23 | for (char c : dbData.toCharArray()) { 24 | if (c == '\\') { 25 | if (flag) { 26 | temp.append(c); 27 | flag = false; 28 | continue; 29 | } 30 | flag = true; 31 | continue; 32 | } 33 | if (c == ',') { 34 | if (flag) { 35 | flag = false; 36 | temp.append(c); 37 | continue; 38 | } 39 | result.add(temp.toString()); 40 | temp = new StringBuilder(); 41 | continue; 42 | } 43 | temp.append(c); 44 | } 45 | result.add(temp.toString()); 46 | return result; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/events/Event.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.events; 2 | 3 | import cc.sfclub.core.Core; 4 | import org.greenrobot.eventbus.EventBus; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | /** 9 | * 事件基类/控制相关 10 | */ 11 | 12 | public abstract class Event { 13 | private static final Logger logger = LoggerFactory.getLogger(Event.class); 14 | private static EventBus eventBus = EventBus.builder() 15 | .sendNoSubscriberEvent(false) 16 | .logNoSubscriberMessages(false) 17 | .throwSubscriberException(true) 18 | .build(); 19 | 20 | public static void registerListeners(Object... object) { 21 | for (Object o : object) { 22 | eventBus.register(o); 23 | } 24 | } 25 | 26 | public static void unregisterListeners(Object... object) { 27 | eventBus.unregister(object); 28 | } 29 | 30 | private static void initEventBus() { 31 | eventBus = EventBus.builder() 32 | .sendNoSubscriberEvent(false) 33 | .throwSubscriberException(true) 34 | .logNoSubscriberMessages(false) 35 | .build(); 36 | } 37 | 38 | public static void unregisterAllListeners() { 39 | eventBus = null; 40 | initEventBus(); 41 | } 42 | 43 | public static void postEvent(Event event) { 44 | eventBus.post(event); 45 | } 46 | 47 | public static void postEventSticky(Event event) { 48 | eventBus.postSticky(event); 49 | } 50 | 51 | public static void setCancelled(Event event) { 52 | eventBus.cancelEventDelivery(event); 53 | } 54 | 55 | public static void broadcastMessage(MessageEvent event, long time) { 56 | Core.get().getPolarSec().postMessage(event, time); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/events/GroupEvent.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.events; 2 | 3 | public abstract class GroupEvent extends PlatformEvent { 4 | public abstract long getGroupId(); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/events/MemberEvent.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.events; 2 | 3 | /** 4 | * Events about member. 5 | */ 6 | public abstract class MemberEvent extends GroupEvent { 7 | public abstract String getUserID(); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/events/MessageEvent.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.events; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * Event about message 7 | */ 8 | 9 | public class MessageEvent extends PlatformEvent { 10 | @Getter 11 | private final String UserID; 12 | @Getter 13 | private final String message; 14 | @Getter 15 | private final long messageID; 16 | @Getter 17 | private final String transform; 18 | 19 | public MessageEvent(String userID, String message, String transform, long messageID) { 20 | this.UserID = userID; 21 | this.message = message; 22 | this.transform = transform; 23 | this.messageID = messageID; 24 | } 25 | 26 | public void reply(long msgId, String message) { 27 | throw new IllegalArgumentException("REPLY IS NOT SUPPORTED"); 28 | } 29 | 30 | public void reply(String message) { 31 | throw new IllegalArgumentException("REPLY IS NOT SUPPORTED"); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/events/PlatformEvent.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.events; 2 | 3 | public abstract class PlatformEvent extends Event { 4 | public abstract String getTransform(); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/events/internal/MessageListener.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.events.internal; 2 | 3 | import cc.sfclub.events.message.direct.PrivateMessage; 4 | import cc.sfclub.events.message.group.GroupMessage; 5 | import org.greenrobot.eventbus.Subscribe; 6 | 7 | import java.util.HashSet; 8 | import java.util.Set; 9 | import java.util.function.Consumer; 10 | 11 | public final class MessageListener { 12 | public static final Set> gHandlers = new HashSet<>(); 13 | public static final Set> mHandlers = new HashSet<>(); 14 | 15 | @Subscribe 16 | public void onMessage(GroupMessage message) { 17 | gHandlers.forEach(e -> e.accept(message)); 18 | } 19 | 20 | @Subscribe 21 | public void onMessage(PrivateMessage message) { 22 | mHandlers.forEach(e -> e.accept(message)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/events/member/MemberLeaveEvent.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.events.member; 2 | 3 | import cc.sfclub.events.MemberEvent; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | 7 | /** 8 | * 当成员离开群聊后触发 9 | */ 10 | @Getter 11 | @Builder 12 | public class MemberLeaveEvent extends MemberEvent { 13 | public final String userID; 14 | public final long groupId; 15 | public final String transform; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/events/member/MemberRequestJoinEvent.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.events.member; 2 | 3 | import cc.sfclub.events.MemberEvent; 4 | 5 | /** 6 | * 成员请求加入群聊 7 | */ 8 | public abstract class MemberRequestJoinEvent extends MemberEvent { 9 | public abstract boolean deal(boolean accept); 10 | 11 | public abstract String getMessage(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/events/message/Message.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.events.message; 2 | 3 | import cc.sfclub.events.MessageEvent; 4 | 5 | /** 6 | * When message was received 7 | */ 8 | 9 | public class Message extends MessageEvent { 10 | 11 | public Message(String userID, String message, String transform, long messageID) { 12 | super(userID, message, transform, messageID); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/events/message/MessageDeletedEvent.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.events.message; 2 | 3 | import cc.sfclub.events.MessageEvent; 4 | 5 | /** 6 | * When message was received 7 | */ 8 | 9 | public class MessageDeletedEvent extends MessageEvent { 10 | public MessageDeletedEvent(String userID, String message, String transform, long messageID) { 11 | super(userID, message, transform, messageID); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/events/message/MessageModifiedEvent.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.events.message; 2 | 3 | import cc.sfclub.events.MessageEvent; 4 | 5 | /** 6 | * When message was modified 7 | */ 8 | 9 | public class MessageModifiedEvent extends MessageEvent { 10 | public MessageModifiedEvent(String userID, String message, String transform, long messageID) { 11 | super(userID, message, transform, messageID); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/events/message/direct/PrivateMessage.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.events.message.direct; 2 | 3 | import cc.sfclub.core.Core; 4 | import cc.sfclub.events.internal.MessageListener; 5 | import cc.sfclub.events.message.Message; 6 | import cc.sfclub.transform.Bot; 7 | import cc.sfclub.transform.Contact; 8 | import cc.sfclub.user.User; 9 | import lombok.Getter; 10 | 11 | import java.util.function.Consumer; 12 | 13 | /** 14 | * When a private message was received 15 | */ 16 | 17 | public class PrivateMessage extends Message { 18 | @Getter 19 | private final Contact contact; 20 | 21 | public PrivateMessage(String userID, String message, String transform, long messageID) { 22 | super(userID, message, transform, messageID); 23 | this.contact = Core.get() 24 | .bot(getTransform()) 25 | .orElseThrow(() -> new NullPointerException("Bot with transform " + getTransform() + "not found!")) 26 | .asContact(userID).orElseThrow(() -> new NullPointerException("Unknown error happened.(Contact not found)")); 27 | } 28 | 29 | @Override 30 | public void reply(long msgId, String message) { 31 | contact.reply(msgId, message); 32 | } 33 | 34 | public void reply(String message) { 35 | reply(super.getMessageID(), message); 36 | } 37 | 38 | public static void subscribeAlways(Consumer handler) { 39 | MessageListener.mHandlers.add(handler); 40 | } 41 | 42 | public Contact senderAsContact() { 43 | return getTransformAsBot().getContact(Long.parseLong(getSender().getPlatformId())).orElseThrow(NullPointerException::new); 44 | } 45 | 46 | public Bot getTransformAsBot() { 47 | return Core.get().bot(getTransform()).orElseThrow(NullPointerException::new); 48 | } 49 | 50 | public User getSender() { 51 | return Core.get().userManager().byUUID(getUserID()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/events/message/direct/PrivateMessageDeletedEvent.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.events.message.direct; 2 | 3 | import cc.sfclub.core.Core; 4 | import cc.sfclub.events.message.Message; 5 | import cc.sfclub.transform.Contact; 6 | import lombok.Getter; 7 | 8 | /** 9 | * When a private message was deleted 10 | */ 11 | 12 | public class PrivateMessageDeletedEvent extends Message { 13 | @Getter 14 | private final Contact contact; 15 | 16 | public PrivateMessageDeletedEvent(String userID, String message, String transform, long messageID) { 17 | super(userID, message, transform, messageID); 18 | this.contact = Core.get() 19 | .bot(getTransform()) 20 | .orElseThrow(() -> new NullPointerException("Bot with transform " + getTransform() + "not found!")) 21 | .asContact(userID).orElseThrow(() -> new NullPointerException("Unknown error happened.(Contact not found)")); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/events/message/direct/PrivateMessageModifiedEvent.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.events.message.direct; 2 | 3 | import cc.sfclub.core.Core; 4 | import cc.sfclub.events.message.Message; 5 | import cc.sfclub.transform.Contact; 6 | import lombok.Getter; 7 | 8 | /** 9 | * When a private message was edited. 10 | */ 11 | 12 | public class PrivateMessageModifiedEvent extends Message { 13 | @Getter 14 | private final Contact contact; 15 | 16 | public PrivateMessageModifiedEvent(String userID, String message, String transform, long messageID) { 17 | super(userID, message, transform, messageID); 18 | this.contact = Core.get() 19 | .bot(getTransform()) 20 | .orElseThrow(() -> new NullPointerException("Bot with transform " + getTransform() + "not found!")) 21 | .asContact(userID).orElseThrow(() -> new NullPointerException("Unknown error happened.(Contact not found)")); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/events/message/group/GroupMessage.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.events.message.group; 2 | 3 | import cc.sfclub.core.Core; 4 | import cc.sfclub.events.internal.MessageListener; 5 | import cc.sfclub.events.message.Message; 6 | import cc.sfclub.transform.Bot; 7 | import cc.sfclub.transform.ChatGroup; 8 | import cc.sfclub.transform.Contact; 9 | import cc.sfclub.user.User; 10 | import lombok.Getter; 11 | 12 | import java.util.function.Consumer; 13 | 14 | /** 15 | * When a group message was received 16 | */ 17 | 18 | public class GroupMessage extends Message { 19 | @Getter 20 | private final long groupId; 21 | @Getter 22 | private final ChatGroup group; 23 | 24 | public GroupMessage(String userID, String message, long group, String transform, long messageID) { 25 | super(userID, message, transform, messageID); 26 | this.groupId = group; 27 | this.group = Core.get() 28 | .bot(getTransform()) 29 | .orElseThrow(() -> new NullPointerException("Bot with transform " + getTransform() + "not found!")) 30 | .getGroup(groupId).orElseThrow(() -> new NullPointerException("Unknown error happened.(Group not found)")); 31 | } 32 | 33 | @Override 34 | public void reply(long msgId, String message) { 35 | group.reply(msgId, message); 36 | } 37 | 38 | public void reply(String message) { 39 | group.reply(super.getMessageID(), message); 40 | } 41 | 42 | public static void subscribeAlways(Consumer handler) { 43 | MessageListener.gHandlers.add(handler); 44 | } 45 | 46 | public Contact senderAsContact() { 47 | return getTransformAsBot().getContact(Long.parseLong(getSender().getPlatformId())).orElseThrow(NullPointerException::new); 48 | } 49 | 50 | public ChatGroup getChatGroup() { 51 | return getTransformAsBot().getGroup(groupId).orElseThrow(NullPointerException::new); 52 | } 53 | 54 | public Bot getTransformAsBot() { 55 | return Core.get().bot(getTransform()).orElseThrow(NullPointerException::new); 56 | } 57 | 58 | public User getSender() { 59 | return Core.get().userManager().byUUID(getUserID()); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/events/message/group/GroupMessageDeletedEvent.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.events.message.group; 2 | 3 | import cc.sfclub.core.Core; 4 | import cc.sfclub.events.message.MessageDeletedEvent; 5 | import cc.sfclub.transform.ChatGroup; 6 | import lombok.Getter; 7 | 8 | /** 9 | * When a group message was received 10 | */ 11 | 12 | public class GroupMessageDeletedEvent extends MessageDeletedEvent { 13 | @Getter 14 | private final long groupId; 15 | @Getter 16 | private final ChatGroup group; 17 | 18 | public GroupMessageDeletedEvent(String userID, String message, long group, String transform, long messageID) { 19 | super(userID, message, transform, messageID); 20 | this.groupId = group; 21 | this.group = Core.get() 22 | .bot(getTransform()) 23 | .orElseThrow(() -> new NullPointerException("Bot with transform " + getTransform() + "not found!")) 24 | .getGroup(groupId).orElseThrow(() -> new NullPointerException("Unknown error happened.(Group not found)")); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/events/message/group/GroupMessageModifiedEvent.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.events.message.group; 2 | 3 | import cc.sfclub.core.Core; 4 | import cc.sfclub.events.message.MessageModifiedEvent; 5 | import cc.sfclub.transform.ChatGroup; 6 | import lombok.Getter; 7 | 8 | /** 9 | * When a group message was modified 10 | */ 11 | public class GroupMessageModifiedEvent extends MessageModifiedEvent { 12 | @Getter 13 | private final long groupId; 14 | @Getter 15 | private final ChatGroup group; 16 | 17 | public GroupMessageModifiedEvent(String userID, String message, long group, String transform, long messageID) { 18 | super(userID, message, transform, messageID); 19 | this.groupId = group; 20 | this.group = Core.get() 21 | .bot(getTransform()) 22 | .orElseThrow(() -> new NullPointerException("Bot with transform " + getTransform() + "not found!")) 23 | .getGroup(groupId).orElseThrow(() -> new NullPointerException("Unknown error happened.(Group not found)")); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/events/server/ServerStartedEvent.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.events.server; 2 | 3 | import cc.sfclub.events.Event; 4 | 5 | /** 6 | * 初始化完毕后触发 7 | */ 8 | public class ServerStartedEvent extends Event { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/events/server/ServerStoppingEvent.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.events.server; 2 | 3 | import cc.sfclub.events.Event; 4 | 5 | /** 6 | * 服务器停止时触发 7 | */ 8 | public class ServerStoppingEvent extends Event { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/plugin/Plugin.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.plugin; 2 | 3 | import cc.sfclub.command.Source; 4 | import cc.sfclub.core.Core; 5 | import cc.sfclub.plugin.java.PolarClassloader; 6 | import cc.sfclub.transform.Bot; 7 | import com.mojang.brigadier.builder.LiteralArgumentBuilder; 8 | import lombok.Getter; 9 | import lombok.Setter; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.io.File; 14 | 15 | @Getter 16 | @Setter 17 | public abstract class Plugin { 18 | private PluginDescription description; 19 | private SimpleConfig config; 20 | private File dataFolder; 21 | /** 22 | * 获取插件Logger 23 | * 24 | * @return 25 | */ 26 | private Logger logger = LoggerFactory.getLogger(this.getClass()); 27 | private boolean loaded = false; 28 | /** 29 | * Plugin 静态方法,用于获取到插件实例 30 | * 31 | * @param t 32 | * @param 33 | * @return 34 | */ 35 | @SuppressWarnings("unchecked") 36 | public static T get(Class t) { 37 | ClassLoader loader = t.getClassLoader(); 38 | if (loader instanceof PolarClassloader) { 39 | PolarClassloader pcl = ((PolarClassloader) loader); 40 | return (T) pcl.getLoader().getPluginManager().getPlugin(pcl.getPluginName()); 41 | } 42 | return null; 43 | } 44 | 45 | public abstract void onEnable(); 46 | 47 | public abstract void onDisable(); 48 | 49 | @Deprecated 50 | //todo 51 | public void registerBot(Bot bot) { 52 | Core.get().registerBot(bot); 53 | } 54 | 55 | /** 56 | * 注册命令 57 | * 58 | * @param commandTree 命令树 59 | */ 60 | public void registerCommand(LiteralArgumentBuilder commandTree) { 61 | Core.get().dispatcher().register(commandTree); 62 | } 63 | 64 | /** 65 | * 获取PolarCore版本 66 | * 67 | * @return 68 | */ 69 | public String getCoreVersion() { 70 | return Core.CORE_VERSION; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/plugin/PluginDescription.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.plugin; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | import java.io.File; 9 | import java.util.HashSet; 10 | import java.util.Set; 11 | 12 | @Getter 13 | @Builder 14 | public class PluginDescription { 15 | /** 16 | * Plugin Name. 17 | */ 18 | private String name; 19 | /** 20 | * Plugin Main Class. 21 | */ 22 | private String main; 23 | /** 24 | * Plugin Version 25 | */ 26 | private String version; 27 | /** 28 | * Auto Register main class as a listener. 29 | */ 30 | private boolean autoRegister; 31 | /** 32 | * Config class 33 | */ 34 | @SerializedName("dataClass") 35 | private String dataClass; 36 | /** 37 | * dependencies. 38 | */ 39 | @SerializedName("depend") 40 | private Set dependencies; 41 | /** 42 | * dependencies but optional. 43 | */ 44 | @SerializedName("soft-depend") 45 | private Set softDependencies; 46 | @Setter 47 | private transient File pluginFile; 48 | 49 | public Set getAllDependencies() { 50 | Set d = new HashSet<>(); 51 | d.addAll(softDependencies); 52 | d.addAll(dependencies); 53 | return d; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/plugin/PluginLoader.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.plugin; 2 | 3 | import cc.sfclub.plugin.exception.DependencyMissingException; 4 | import cc.sfclub.plugin.exception.InvalidPluginException; 5 | 6 | import java.io.File; 7 | 8 | public interface PluginLoader { 9 | String getFilePattern(); 10 | 11 | PluginDescription getDescriptionOf(File file); 12 | 13 | Plugin loadPlugin(File file) throws InvalidPluginException, DependencyMissingException; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/plugin/PluginManager.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.plugin; 2 | 3 | import cc.sfclub.core.I18N; 4 | import cc.sfclub.plugin.exception.DependencyMissingException; 5 | import cc.sfclub.plugin.exception.InvalidPluginException; 6 | import cc.sfclub.plugin.java.JavaPluginLoader; 7 | import lombok.SneakyThrows; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.io.File; 12 | import java.util.*; 13 | 14 | public class PluginManager { 15 | private final Logger logger = LoggerFactory.getLogger(this.getClass()); 16 | private final List pluginLoaders = new LinkedList<>(); 17 | private final Set failedToLoads = new HashSet<>(); 18 | private final Map pluginMap = new HashMap<>(); 19 | private final String rootPath; 20 | 21 | @SneakyThrows 22 | public PluginManager(String rootPath) { 23 | this.rootPath = rootPath; 24 | pluginLoaders.add(new JavaPluginLoader(new File(rootPath).toPath(), this)); 25 | } 26 | public void loadPlugins() { 27 | File pluginsDir = new File(rootPath); 28 | Map preloadingPlugins = new HashMap<>(); 29 | //Pre-load descriptions 30 | for (File file : pluginsDir.listFiles()) { 31 | PluginLoader loader = fileToLoader(file); 32 | if (loader == null) { 33 | continue; 34 | } 35 | PluginDescription desc = loader.getDescriptionOf(file); 36 | if (desc == null) { 37 | continue; 38 | } 39 | preloadingPlugins.put(desc.getName(), desc); 40 | logger.info(I18N.get().server.PLUGIN_PRELOADING, desc.getName(), desc.getVersion()); 41 | } 42 | //End 43 | //Check dependencies 44 | Set errorPlugins = checkDependencies(preloadingPlugins); 45 | errorPlugins.forEach(preloadingPlugins::remove); 46 | preloadingPlugins.forEach(this::loadPluginWithDependency); 47 | if (failedToLoads.size() != 0) { 48 | logger.error(I18N.get().exceptions.PLUGIN_FAILED_TO_LOAD, Arrays.toString(failedToLoads.toArray())); 49 | } 50 | } 51 | 52 | public Set checkDependencies(Map preloadingPlugins) { 53 | Set errorPlugins = new HashSet<>(); 54 | Iterator iter = preloadingPlugins.values().iterator(); 55 | while (iter.hasNext()) { 56 | PluginDescription desc = iter.next(); 57 | if (desc.getDependencies() == null) { 58 | continue; 59 | } 60 | 61 | for (String dependency : desc.getDependencies()) { 62 | if (!preloadingPlugins.containsKey(dependency)) { 63 | logger.warn(I18N.get().exceptions.PLUGIN_DEPENDENCY_MISSING, dependency, desc.getName()); 64 | //preloadingPlugins.remove(desc.getName()); 65 | errorPlugins.add(desc.getName()); 66 | break; 67 | } 68 | } 69 | } 70 | return errorPlugins; 71 | } 72 | 73 | private PluginLoader fileToLoader(File file) { 74 | for (PluginLoader pluginLoader : pluginLoaders) { 75 | if (file.getName().matches(pluginLoader.getFilePattern())) { 76 | return pluginLoader; 77 | } 78 | } 79 | return null; 80 | } 81 | 82 | private boolean loadPluginWithDependency(String name, PluginDescription plugin) { 83 | return loadPluginWithDependency(name, plugin, new Stack<>()); 84 | } 85 | 86 | public Plugin loadPlugin(File file) throws InvalidPluginException, DependencyMissingException { 87 | PluginLoader loader = fileToLoader(file); 88 | if (loader == null) { 89 | return null; 90 | } 91 | return loader.loadPlugin(file); 92 | } 93 | 94 | public Collection getPlugins() { 95 | return pluginMap.values(); 96 | } 97 | 98 | /** 99 | * 插件是否加载 100 | * 101 | * @param name 102 | * @return 103 | */ 104 | public boolean isPluginLoaded(String name) { 105 | return pluginMap.get(name) != null && pluginMap.get(name).isLoaded(); 106 | } 107 | 108 | public Plugin getPlugin(String name) { 109 | return pluginMap.get(name); 110 | } 111 | 112 | @SneakyThrows 113 | private boolean loadPluginWithDependency(String name, PluginDescription plugin, Stack loadStack) { 114 | if (failedToLoads.contains(name)) { 115 | return false; 116 | } 117 | if (loadStack.search(name) != -1) { 118 | failedToLoads.add(name); 119 | logger.error(I18N.get().exceptions.PLUGIN_DEPEND_LOOP, name, loadStack.pop()); 120 | logger.error("Plugin Dependency Stack: {}", Arrays.toString(loadStack.toArray())); 121 | return false; 122 | } 123 | loadStack.push(name); 124 | if (isPluginLoaded(name)) { 125 | return false; 126 | } 127 | if (plugin.getDependencies() != null) { 128 | for (String dep : plugin.getDependencies()) { 129 | if (!isPluginLoaded(dep)) { 130 | if (!loadPluginWithDependency(dep, plugin, loadStack)) { 131 | failedToLoads.add(dep); 132 | return false; 133 | } 134 | } 135 | } 136 | } 137 | if (plugin.getSoftDependencies() != null) { 138 | for (String dep : plugin.getSoftDependencies()) { 139 | if (!isPluginLoaded(dep)) { 140 | if (!loadPluginWithDependency(dep, plugin, loadStack)) { 141 | failedToLoads.add(dep); 142 | return true; 143 | } 144 | } 145 | } 146 | } 147 | try { 148 | Plugin p = loadPlugin(plugin.getPluginFile()); 149 | if (p == null) { 150 | return false; 151 | } 152 | p.onEnable(); 153 | pluginMap.put(plugin.getName(), p); 154 | return true; 155 | } catch (DependencyMissingException e) { 156 | logger.error("Unknown Error!", e); 157 | return false; 158 | } 159 | } 160 | 161 | public boolean unloadPlugin(String name) { 162 | if (!isPluginLoaded(name)) { 163 | return false; 164 | } 165 | Plugin plugin = getPlugin(name); 166 | plugin.setLoaded(false); 167 | plugin.onDisable(); 168 | if (plugin.getConfig() != null) plugin.getConfig().saveConfig(); 169 | pluginMap.put(name, null); 170 | return true; 171 | } 172 | 173 | public void unloadPlugins() { 174 | pluginMap.keySet().forEach(this::unloadPlugin); 175 | pluginMap.clear(); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/plugin/SimpleConfig.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.plugin; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | import lombok.SneakyThrows; 8 | 9 | import java.io.BufferedReader; 10 | import java.io.File; 11 | import java.io.FileReader; 12 | import java.nio.file.Files; 13 | import java.nio.file.Path; 14 | 15 | /** 16 | * 配置包装 17 | * 18 | * @param 数据类 19 | */ 20 | public class SimpleConfig { 21 | private static final Gson gson = new GsonBuilder().setPrettyPrinting().create(); 22 | private String root; 23 | private C configObj; 24 | @Setter 25 | @Getter 26 | private String configFileName = "config.json"; 27 | private Class clazz; 28 | 29 | @SneakyThrows 30 | public SimpleConfig(Plugin plugin, Class configClass) { 31 | plugin.getDataFolder().mkdirs(); 32 | this.root = plugin.getDataFolder().getAbsolutePath(); 33 | this.clazz = configClass; 34 | this.configObj = configClass.getDeclaredConstructor().newInstance(); 35 | saveDefault(); 36 | reloadConfig(); 37 | } 38 | 39 | /** 40 | * Save Config to config.json 41 | */ 42 | @SneakyThrows 43 | public void saveConfig() { 44 | Files.write(new File(root + "/" + getConfigFileName()).toPath(), gson.toJson(configObj).getBytes()); 45 | } 46 | 47 | /** 48 | * 若文件不存在则保存默认 49 | */ 50 | @SneakyThrows 51 | public void saveDefault() { 52 | Path a = new File(root + "/" + getConfigFileName()).toPath(); 53 | if (!Files.exists(a)) { 54 | Files.write(a, gson.toJson(configObj).getBytes()); 55 | } 56 | } 57 | 58 | /** 59 | * 获取包装对象 60 | * 61 | * @return 62 | */ 63 | public C get() { 64 | return configObj; 65 | } 66 | 67 | public void set(C c) { 68 | this.configObj = c; 69 | } 70 | 71 | /** 72 | * Reload Config 73 | */ 74 | @SneakyThrows 75 | public void reloadConfig() { 76 | configObj = gson.fromJson(new BufferedReader(new FileReader(root + "/" + getConfigFileName())), clazz); 77 | } 78 | } -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/plugin/exception/DependencyMissingException.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.plugin.exception; 2 | 3 | public class DependencyMissingException extends Exception { 4 | public DependencyMissingException(String s) { 5 | super(s); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/plugin/exception/InvalidPluginException.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.plugin.exception; 2 | 3 | public class InvalidPluginException extends Exception { 4 | public InvalidPluginException(String s) { 5 | super(s); 6 | } 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/plugin/exception/PluginNotLoadedException.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.plugin.exception; 2 | 3 | public class PluginNotLoadedException extends Exception { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/plugin/java/JavaPluginLoader.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.plugin.java; 2 | 3 | import cc.sfclub.core.I18N; 4 | import cc.sfclub.events.Event; 5 | import cc.sfclub.plugin.*; 6 | import cc.sfclub.plugin.exception.DependencyMissingException; 7 | import cc.sfclub.plugin.exception.InvalidPluginException; 8 | import com.google.gson.Gson; 9 | import lombok.Getter; 10 | import lombok.SneakyThrows; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | import sun.misc.Unsafe; 14 | 15 | import java.io.BufferedReader; 16 | import java.io.File; 17 | import java.io.IOException; 18 | import java.io.InputStreamReader; 19 | import java.lang.reflect.Field; 20 | import java.lang.reflect.InvocationTargetException; 21 | import java.net.MalformedURLException; 22 | import java.nio.file.Path; 23 | import java.util.jar.JarEntry; 24 | import java.util.jar.JarFile; 25 | 26 | public class JavaPluginLoader implements PluginLoader { 27 | private final Logger logger = LoggerFactory.getLogger(this.getClass()); 28 | private final Path rootPath; 29 | private final Gson gson = new Gson(); 30 | @Getter 31 | private final PluginManager pluginManager; 32 | private Unsafe unsafe; 33 | 34 | public JavaPluginLoader(Path rootPath, PluginManager pm) { 35 | this.rootPath = rootPath; 36 | this.pluginManager = pm; 37 | if (!rootPath.toFile().exists()) { 38 | rootPath.toFile().mkdir(); 39 | } 40 | } 41 | 42 | @SneakyThrows 43 | public Unsafe getUnsafe() { 44 | if (unsafe == null) { 45 | Field f = Unsafe.class.getField("theUnsafe"); 46 | f.setAccessible(true); 47 | unsafe = (Unsafe) f.get(null); 48 | } 49 | return unsafe; 50 | } 51 | 52 | /** 53 | * 在所有插件Classpath中寻找class 54 | * 55 | * @param clazzName 类名 56 | * @param excepted 排除掉的CL 57 | * @return clazz 58 | * @throws ClassNotFoundException 59 | */ 60 | public Class findClass(String clazzName, ClassLoader excepted) throws ClassNotFoundException { 61 | Class[] result = new Class[]{null}; 62 | boolean found = false; 63 | pluginManager.getPlugins().stream().filter(cl -> cl.getClass().getClassLoader() != excepted).forEach(p -> { 64 | PolarClassloader cl = (PolarClassloader) p.getClass().getClassLoader(); 65 | try { 66 | if (result[0] == null) { 67 | result[0] = cl.findClass(clazzName, false); 68 | } 69 | } catch (ClassNotFoundException ignored) { 70 | } 71 | }); 72 | if (result[0] == null) { 73 | throw new ClassNotFoundException(clazzName); 74 | } 75 | return result[0]; 76 | } 77 | 78 | @Override 79 | public String getFilePattern() { 80 | return ".*\\.jar"; 81 | } 82 | 83 | 84 | /** 85 | * 获取插件文件描述信息 86 | * 87 | * @param file 88 | * @return 89 | */ 90 | @Override 91 | public PluginDescription getDescriptionOf(File file) { 92 | try { 93 | JarFile jar = new JarFile(file); 94 | JarEntry descriptionFile = jar.getJarEntry("plugin.json"); 95 | PluginDescription description = gson.fromJson(new BufferedReader(new InputStreamReader(jar.getInputStream(descriptionFile))), PluginDescription.class); 96 | description.setPluginFile(file); 97 | if (description.getMain() != null && description.getName() != null && description.getVersion() != null) { 98 | return description; 99 | } 100 | } catch (IOException e) { 101 | logger.debug("Failed to get description of {}!", file.getName()); 102 | return null; 103 | } 104 | return null; 105 | } 106 | 107 | /** 108 | * 获取插件PolarClassloader.Nullable 109 | * 110 | * @param name 111 | * @return 112 | */ 113 | public PolarClassloader getClassloaderOf(String name) { 114 | if (!pluginManager.isPluginLoaded(name)) { 115 | return null; 116 | } 117 | return (PolarClassloader) pluginManager.getPlugin(name).getClass().getClassLoader(); 118 | } 119 | 120 | /** 121 | * 加载插件,不会自动加载依赖 122 | * 123 | * @param file 插件 124 | * @return 插件对象 125 | * @throws InvalidPluginException 126 | * @throws DependencyMissingException 127 | */ 128 | @Override 129 | public Plugin loadPlugin(File file) throws InvalidPluginException, DependencyMissingException { 130 | PluginDescription description = getDescriptionOf(file); 131 | if (description == null) { 132 | throw new InvalidPluginException("Failed to load " + file.getAbsolutePath()); 133 | } 134 | /*if (pluginM) { 135 | throw new DependencyMissingException("Dependency missing for " + description.getName()); 136 | }*/ 137 | try { 138 | PolarClassloader cl = new PolarClassloader(file, this, description.getName()); 139 | Class pluginClass = cl.findClass(description.getMain(), false); 140 | Plugin plugin = (Plugin) pluginClass.getConstructor().newInstance(); 141 | if (description.isAutoRegister()) { 142 | Event.registerListeners(plugin); 143 | } 144 | plugin.setDescription(description); 145 | plugin.setLoaded(true); 146 | plugin.setDataFolder(new File(rootPath.toString() + "/" + description.getName())); 147 | if (description.getDataClass() != null) { 148 | //Setup simpleConfig 149 | try { 150 | Class clazz = cl.findClass(description.getDataClass(), false); 151 | plugin.setConfig(new SimpleConfig<>(plugin, clazz)); 152 | } catch (ClassNotFoundException e) { 153 | logger.error(I18N.get().exceptions.PLUGIN_DATA_CLASS_NOT_FOUND, description.getDataClass(), description.getName()); 154 | } 155 | } 156 | //pluginMap.put(description.getName(), plugin); 157 | return plugin; 158 | } catch (ClassNotFoundException | NoSuchMethodException e) { 159 | e.printStackTrace(); 160 | if (e instanceof ClassNotFoundException) { 161 | logger.error(I18N.get().exceptions.PLUGIN_MAIN_CLASS_NOT_FOUND, description.getMain(), description.getName()); 162 | } else { 163 | logger.error(I18N.get().exceptions.PLUGIN_HAVE_NO_AVAILABLE_CONSTRUCTOR, description.getName()); 164 | } 165 | } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) { 166 | e.printStackTrace(); 167 | if (e instanceof IllegalAccessException) { 168 | logger.error(I18N.get().exceptions.PLUGIN_HAVE_NO_AVAILABLE_CONSTRUCTOR, description.getName()); 169 | } 170 | } catch (MalformedURLException e) { 171 | e.printStackTrace(); 172 | } 173 | return null; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/plugin/java/NullCatWillDress.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.plugin.java; 2 | 3 | import java.net.URL; 4 | 5 | public interface NullCatWillDress { 6 | void addUrl(URL url); 7 | 8 | Class findClass(String s) throws ClassNotFoundException; 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/plugin/java/PolarClassloader.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.plugin.java; 2 | 3 | import lombok.Getter; 4 | import lombok.SneakyThrows; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.net.MalformedURLException; 10 | import java.net.URL; 11 | import java.net.URLClassLoader; 12 | import java.security.CodeSigner; 13 | import java.security.CodeSource; 14 | import java.util.Map; 15 | import java.util.Set; 16 | import java.util.concurrent.ConcurrentHashMap; 17 | import java.util.jar.JarEntry; 18 | import java.util.jar.JarFile; 19 | import java.util.jar.Manifest; 20 | 21 | public class PolarClassloader extends URLClassLoader implements NullCatWillDress { 22 | @Getter 23 | private final JavaPluginLoader loader; 24 | @Getter 25 | private final String pluginName; 26 | private final Map> classes = new ConcurrentHashMap<>(); 27 | private final File plugin; 28 | private final JarFile jar; 29 | private final Manifest manifest; 30 | private final URL url; 31 | 32 | @SneakyThrows 33 | public PolarClassloader(File plugin, JavaPluginLoader loader, String pluginName) throws MalformedURLException { 34 | super(new URL[]{plugin.toURI().toURL()}); 35 | this.loader = loader; 36 | this.pluginName = pluginName; 37 | this.plugin = plugin; 38 | this.jar = new JarFile(plugin); 39 | this.manifest = jar.getManifest(); 40 | this.url = plugin.toURI().toURL(); 41 | } 42 | 43 | public Set getClasses() { 44 | return classes.keySet(); 45 | } 46 | 47 | @Override 48 | public void addUrl(URL url) { 49 | super.addURL(url); 50 | } 51 | 52 | @Override 53 | public Class findClass(String moduleName) throws ClassNotFoundException { 54 | return findClass(moduleName, true); 55 | } 56 | @SuppressWarnings("all") 57 | public Class findClass(String clazz, boolean searchGlobal) throws ClassNotFoundException { 58 | Class target = classes.get(clazz); 59 | if (target == null) { 60 | String path = clazz.replace('.', '/').concat(".class"); 61 | JarEntry entry = jar.getJarEntry(path); 62 | 63 | if (entry != null) { 64 | byte[] classBytes; 65 | 66 | try (InputStream is = jar.getInputStream(entry)) { 67 | classBytes = is.readAllBytes(); 68 | } catch (IOException ex) { 69 | throw new ClassNotFoundException(clazz, ex); 70 | } 71 | 72 | //classBytes = loader.getUnsafe().processClass(description, path, classBytes); 73 | 74 | int dot = clazz.lastIndexOf('.'); 75 | if (dot != -1) { 76 | String pkgName = clazz.substring(0, dot); 77 | if (getPackage(pkgName) == null) { 78 | try { 79 | if (manifest != null) { 80 | definePackage(pkgName, manifest, url); 81 | } else { 82 | definePackage(pkgName, null, null, null, null, null, null, null); 83 | } 84 | } catch (IllegalArgumentException ex) { 85 | if (getPackage(pkgName) == null) { 86 | throw new IllegalStateException("Cannot find package " + pkgName); 87 | } 88 | } 89 | } 90 | } 91 | 92 | CodeSigner[] signers = entry.getCodeSigners(); 93 | CodeSource source = new CodeSource(url, signers); 94 | target = defineClass(clazz, classBytes, 0, classBytes.length, source); 95 | if (target != null) { 96 | classes.put(clazz, target); 97 | return target; 98 | } 99 | } 100 | 101 | if (target == null) { 102 | try { 103 | target = super.findClass(clazz); 104 | } catch (ClassNotFoundException ignored) { 105 | 106 | } 107 | } 108 | if (searchGlobal) { 109 | target = loader.findClass(clazz, this); 110 | return target; 111 | } 112 | } 113 | if (target != null) { 114 | return target; 115 | } 116 | throw new ClassNotFoundException("Couldn't find class " + clazz + " for plugin: " + pluginName); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/service/Registry.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.service; 2 | 3 | @FunctionalInterface 4 | public interface Registry { 5 | T get(); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/service/Service.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.service; 2 | 3 | /** 4 | * Mark. 5 | */ 6 | public interface Service { 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/service/ServiceProvider.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.service; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class ServiceProvider { 7 | private static final Map> services = new HashMap<>(); 8 | 9 | public static void setRegistry(Class service, Registry registry) { 10 | services.put(service.getCanonicalName(), registry); 11 | } 12 | 13 | @SuppressWarnings("unchecked") 14 | public static T get(Class service) { 15 | return (T) services.get(service.getCanonicalName()).get(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/transform/Bot.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.transform; 2 | 3 | import cc.sfclub.core.Core; 4 | import cc.sfclub.transform.exception.GroupNotFoundException; 5 | import cc.sfclub.transform.exception.PlatformNotFoundException; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | import java.util.Optional; 10 | 11 | /** 12 | * Transform Bot 基类 13 | */ 14 | public abstract class Bot { 15 | private static final Map groupCache = new HashMap<>(); 16 | private static final Map contactCache = new HashMap<>(); 17 | 18 | public abstract String getName(); 19 | 20 | public static ChatGroup getGroup(String platform, long groupId) throws GroupNotFoundException, PlatformNotFoundException { 21 | return Core.get().bot(platform).orElseThrow(() -> new PlatformNotFoundException(platform)) 22 | .getGroup(groupId) 23 | .orElseThrow(() -> new GroupNotFoundException(groupId + "")); 24 | } 25 | 26 | /** 27 | * Add a group to simple cache. 28 | * 29 | * @param group group 30 | * @param overwrite 31 | */ 32 | public void addGroup(ChatGroup group, boolean overwrite) { 33 | if (overwrite) { 34 | groupCache.remove(group.getID()); 35 | } 36 | if (!groupCache.containsKey(group.getID())) { 37 | groupCache.put(group.getID(), group); 38 | } 39 | } 40 | 41 | /** 42 | * get a group reference. 43 | * 44 | * @param id group id 45 | * @return group 46 | */ 47 | public Optional getGroup(long id) { 48 | return Optional.ofNullable(groupCache.get(id)); 49 | } 50 | 51 | /** 52 | * get a contact reference 53 | * 54 | * @param id uid 55 | * @return contact 56 | */ 57 | public Optional getContact(long id) { 58 | return Optional.ofNullable(contactCache.get(id)); 59 | } 60 | 61 | public abstract Optional asContact(String UserID); 62 | 63 | /** 64 | * Add a contact to cache 65 | * 66 | * @param contact 67 | * @param overwrite 68 | */ 69 | public void addContact(Contact contact, boolean overwrite) { 70 | if (overwrite) { 71 | contactCache.remove(contact.getID()); 72 | } 73 | if (!contactCache.containsKey(contact.getID())) { 74 | contactCache.put(contact.getID(), contact); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/transform/ChatGroup.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.transform; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | import java.util.HashSet; 7 | import java.util.Optional; 8 | import java.util.Set; 9 | 10 | @RequiredArgsConstructor 11 | public abstract class ChatGroup implements Receiver { 12 | @Getter 13 | private final long ID; 14 | /** 15 | * all members from the group. 16 | * 17 | * @return member set 18 | */ 19 | @Getter 20 | private final Set members; 21 | 22 | /** 23 | * the group name 24 | * 25 | * @return name 26 | */ 27 | public abstract String getName(); 28 | 29 | /** 30 | * get the honor of contact 31 | * 32 | * @param contact target 33 | * @return honor 34 | */ 35 | public abstract String honorOf(Contact contact); 36 | 37 | /** 38 | * get the nick of contact 39 | * 40 | * @param contact target 41 | * @return nickname 42 | */ 43 | public abstract String nickOf(Contact contact); 44 | 45 | /** 46 | * Simple wrapped.\n 47 | * Attention! If id not exists it will cause a IllegalArgumentException. 48 | * 49 | * @param id contact uid 50 | * @return honor 51 | */ 52 | public String honorOf(long id) throws IllegalArgumentException { 53 | return honorOf(searchContact(id).orElseThrow(() -> new IllegalArgumentException("ID not exists"))); 54 | } 55 | 56 | /** 57 | * Simple wrapped\n 58 | * Attention! If id not exists it will cause a IllegalArgumentException. 59 | * 60 | * @param id contact uid 61 | * @return nickname 62 | */ 63 | public String nickOf(long id) throws IllegalArgumentException { 64 | return nickOf(searchContact(id).orElseThrow(() -> new IllegalArgumentException("ID not exists"))); 65 | } 66 | 67 | /** 68 | * Search the contacts. 69 | * 70 | * @param id target id 71 | * @return target 72 | */ 73 | public Optional searchContact(long id) { 74 | for (Contact contact : members) { 75 | if (contact.getID() == id) return Optional.of(contact); 76 | } 77 | return Optional.empty(); 78 | } 79 | 80 | /** 81 | * get contact's role in the group. 82 | * 83 | * @param contact target 84 | * @return target's role 85 | */ 86 | public abstract Role roleOf(Contact contact); 87 | 88 | /** 89 | * get contacts which in selected role. 90 | * 91 | * @param role role 92 | * @return characters 93 | */ 94 | public Set contactsFromRole(Role role) { 95 | Set characters = new HashSet<>(); 96 | members.forEach(m -> { 97 | if (roleOf(m) == role) characters.add(m); 98 | }); 99 | return characters; 100 | } 101 | 102 | @Override 103 | public boolean equals(Object obj) { 104 | if (super.equals(obj)) return true; 105 | if (obj instanceof ChatGroup) { 106 | ChatGroup group = (ChatGroup) obj; 107 | return group.getID() == this.ID; 108 | } 109 | return false; 110 | } 111 | 112 | /** 113 | * roles 114 | */ 115 | @SuppressWarnings("UnnecessarySemicolon") 116 | public enum Role { 117 | OWNER, ADMIN, MEMBER 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/transform/Contact.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.transform; 2 | 3 | 4 | import cc.sfclub.user.User; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | 8 | @AllArgsConstructor 9 | @Getter 10 | public abstract class Contact implements Receiver { 11 | private final long ID; 12 | 13 | /** 14 | * @return nickname 15 | */ 16 | public abstract String getNickname(); 17 | 18 | /** 19 | * return a name which is the name in platform. 20 | * 21 | * @return UserName 22 | */ 23 | public abstract String getUsername(); 24 | 25 | /** 26 | * convert to permissible users. 27 | * 28 | * @return User 29 | */ 30 | public abstract User asPermObj(); 31 | 32 | @Override 33 | public boolean equals(Object obj) { 34 | if (super.equals(obj)) return true; 35 | if (!(obj instanceof Contact)) return false; 36 | Contact contact = (Contact) obj; 37 | return contact.getID() == this.ID; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/transform/Receiver.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.transform; 2 | 3 | public interface Receiver { 4 | /** 5 | * Send a message 6 | * 7 | * @param message message 8 | */ 9 | void sendMessage(String message); 10 | 11 | /** 12 | * reply the message 13 | * 14 | * @param messageId target 15 | * @param message message 16 | */ 17 | void reply(long messageId, String message); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/transform/exception/ContactNotFoundException.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.transform.exception; 2 | 3 | public class ContactNotFoundException extends NullPointerException { 4 | public ContactNotFoundException(String s) { 5 | super(s); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/transform/exception/GroupNotFoundException.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.transform.exception; 2 | 3 | public class GroupNotFoundException extends NullPointerException { 4 | public GroupNotFoundException(String s) { 5 | super(s); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/transform/exception/PlatformNotFoundException.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.transform.exception; 2 | 3 | public class PlatformNotFoundException extends NullPointerException { 4 | public PlatformNotFoundException(String s) { 5 | super("Platform " + s + " Not Found"); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/transform/internal/ConsoleBot.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.transform.internal; 2 | 3 | import cc.sfclub.transform.Bot; 4 | import cc.sfclub.transform.ChatGroup; 5 | import cc.sfclub.transform.Contact; 6 | 7 | import java.util.Collections; 8 | import java.util.Optional; 9 | 10 | public class ConsoleBot extends Bot { 11 | private final VirtContact virtualContact = new VirtContact(0L); 12 | private final VirtGroup virtualGroup = new VirtGroup(0L, Collections.singleton(virtualContact)); 13 | 14 | public ConsoleBot() { 15 | this.addGroup(virtualGroup, true); 16 | } 17 | 18 | @Override 19 | public Optional getGroup(long id) { 20 | return super.getGroup(id); 21 | } 22 | 23 | @Override 24 | public String getName() { 25 | return "CONSOLE"; 26 | } 27 | 28 | @Override 29 | public Optional asContact(String UserID) { 30 | return Optional.empty(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/transform/internal/VirtContact.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.transform.internal; 2 | 3 | import cc.sfclub.core.Initializer; 4 | import cc.sfclub.transform.Contact; 5 | import cc.sfclub.user.User; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | public class VirtContact extends Contact { 10 | private static final Logger logger = LoggerFactory.getLogger("VirtualContact"); 11 | public VirtContact(long ID) { 12 | super(ID); 13 | } 14 | 15 | @Override 16 | public String getNickname() { 17 | return "NICK_NAME"; 18 | } 19 | 20 | @Override 21 | public String getUsername() { 22 | return User.CONSOLE_USER_NAME; 23 | } 24 | 25 | @Override 26 | public User asPermObj() { 27 | return Initializer.getCONSOLE(); 28 | } 29 | 30 | @Override 31 | public void sendMessage(String message) { 32 | logger.info("[CONSOLE_PRIVATE_MESSAGE] {}", message); 33 | } 34 | 35 | @Override 36 | public void reply(long messageId, String message) { 37 | logger.info("[CONSOLE_PRIVATE_REPLY] {}", message); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/transform/internal/VirtGroup.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.transform.internal; 2 | 3 | import cc.sfclub.transform.ChatGroup; 4 | import cc.sfclub.transform.Contact; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import java.util.Set; 9 | 10 | public class VirtGroup extends ChatGroup { 11 | private static final Logger logger = LoggerFactory.getLogger("VirtualGroup"); 12 | public VirtGroup(long ID, Set members) { 13 | super(ID, members); 14 | } 15 | 16 | @Override 17 | public String getName() { 18 | return "Console Virtual"; 19 | } 20 | 21 | @Override 22 | public String honorOf(Contact contact) { 23 | return ""; 24 | } 25 | 26 | @Override 27 | public String nickOf(Contact contact) { 28 | return ""; 29 | } 30 | 31 | @Override 32 | public Role roleOf(Contact contact) { 33 | return Role.ADMIN; 34 | } 35 | 36 | @Override 37 | public void sendMessage(String message) { 38 | logger.info("[CONSOLE_GROUP] {}", message); 39 | } 40 | 41 | @Override 42 | public void reply(long messageId, String message) { 43 | logger.info("[CONSOLE_GROUP_REPLY] {} :: {}", messageId, message); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/user/Group.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.user; 2 | 3 | import cc.sfclub.Internal; 4 | import cc.sfclub.core.Core; 5 | import cc.sfclub.database.converter.PermListConverter; 6 | import cc.sfclub.user.perm.Perm; 7 | import cc.sfclub.user.perm.Permissible; 8 | import lombok.NonNull; 9 | import lombok.Setter; 10 | 11 | import javax.persistence.Convert; 12 | import javax.persistence.Id; 13 | import javax.persistence.Table; 14 | import java.util.ArrayList; 15 | import java.util.Arrays; 16 | import java.util.List; 17 | import java.util.Optional; 18 | 19 | /** 20 | * 权限组 21 | */ 22 | @Table(name = "userGroup") 23 | @SuppressWarnings("unused") 24 | public class Group implements Permissible { 25 | public static final Group DEFAULT = new Group("_"); 26 | @NonNull 27 | @Deprecated 28 | @Convert(converter = PermListConverter.class) 29 | public List permList = new ArrayList<>(); 30 | @Setter 31 | private String name; 32 | /** 33 | * 父租 34 | */ 35 | @Setter 36 | private String extend; 37 | 38 | /** 39 | * Only for ORM Initial.(NoArgConstructor required) 40 | */ 41 | @Deprecated 42 | @Internal 43 | public Group() { 44 | name = null; 45 | } 46 | 47 | @Internal 48 | protected Group(String name, Perm... InitialPerms) { 49 | permList.addAll(Arrays.asList(InitialPerms)); 50 | this.name = name; 51 | } 52 | 53 | @Override 54 | public boolean hasPermission(Perm perm) { 55 | if ("_".equals(name)) { 56 | return permList.contains(perm); 57 | } 58 | Optional father = Core.get().userManager().getGroup(extend); 59 | return father.orElse(DEFAULT).hasPermission(perm) || permList.contains(perm); 60 | } 61 | 62 | public boolean hasPermission(String perm) { 63 | return hasPermission(Perm.of(perm)); 64 | } 65 | 66 | @Override 67 | public void addPermission(Perm perm) { 68 | if (!hasPermission(perm)) { 69 | permList.add(perm); 70 | } 71 | } 72 | 73 | @Override 74 | public void delPermission(Perm perm) { 75 | permList.remove(perm); 76 | } 77 | 78 | @Id 79 | public String getName() { 80 | return name; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/user/User.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.user; 2 | 3 | import cc.sfclub.Internal; 4 | import cc.sfclub.core.Core; 5 | import cc.sfclub.database.converter.PermListConverter; 6 | import cc.sfclub.transform.Contact; 7 | import cc.sfclub.transform.exception.ContactNotFoundException; 8 | import cc.sfclub.user.exception.UserPlatformUnboundException; 9 | import cc.sfclub.user.perm.Perm; 10 | import cc.sfclub.user.perm.Permissible; 11 | import lombok.AccessLevel; 12 | import lombok.Getter; 13 | import lombok.Setter; 14 | import lombok.SneakyThrows; 15 | 16 | import javax.persistence.*; 17 | import java.util.Arrays; 18 | import java.util.LinkedList; 19 | import java.util.List; 20 | import java.util.UUID; 21 | 22 | @Entity 23 | @Table(name = "user") 24 | @SuppressWarnings("unused") 25 | public class User implements Permissible { 26 | public static final String CONSOLE_USER_NAME = "CONSOLE"; 27 | /** 28 | * 权限列表 29 | */ 30 | @Column(name = "permList") 31 | @Convert(converter = PermListConverter.class) 32 | public List permList = new LinkedList<>(); 33 | /** 34 | * 用户名 35 | */ 36 | @Column(name = "userName") 37 | @Getter 38 | @Setter 39 | public String userName; 40 | /** 41 | * UID 42 | */ 43 | @Setter 44 | public String uniqueID = UUID.randomUUID().toString(); 45 | /** 46 | * 用户组名 47 | */ 48 | @Column(name = "userGroup") 49 | @Getter 50 | @Setter 51 | private String userGroup; 52 | /** 53 | * 来源平台 54 | */ 55 | @Column(name = "platform") 56 | @Getter 57 | @Setter 58 | private String platform; 59 | /** 60 | * 来源平台分配的ID 61 | */ 62 | @Column(name = "platformId") 63 | @Getter 64 | @Setter 65 | private String platformId; 66 | /** 67 | * 在进行权限判断,信息获取的时候会跳转到这个变量索引的User。 68 | * 用于进行跨平台同步一用户。使用UUID 69 | */ 70 | @Column(name = "redirectTo") 71 | @Getter 72 | private String redirectTo; 73 | @Setter(AccessLevel.PROTECTED) 74 | @Transient 75 | private User realUser; 76 | 77 | @Internal 78 | public User() { 79 | 80 | } 81 | 82 | @Internal 83 | protected User(String Group) { 84 | userGroup = Group; 85 | } 86 | 87 | protected User(String group, Perm... InitialPermissions) { 88 | this.userGroup = group; 89 | permList.addAll(Arrays.asList(InitialPermissions)); 90 | } 91 | 92 | protected User(String Group, String platform, String platformId) { 93 | this.platformId = platformId; 94 | this.platform = platform; 95 | userGroup = Group; 96 | } 97 | 98 | @SneakyThrows 99 | public Contact asContact() { 100 | return Core.get().bot(platform).orElseThrow(() -> new UserPlatformUnboundException(this)) 101 | .getContact(Long.parseLong(platformId)) 102 | .orElseThrow(() -> new ContactNotFoundException("Cannot convert " + this + " to a contact.(" + platform + ":: " + platformId + ")")); 103 | } 104 | 105 | public boolean hasPermission(String perm) { 106 | return hasPermission(Perm.of(perm)); 107 | } 108 | 109 | @Override 110 | public boolean hasPermission(Perm perm) { 111 | if (realUser == null) { 112 | if (Core.get().userManager().getGroup(getUserGroup()).orElse(Group.DEFAULT).hasPermission(perm)) { 113 | return true; 114 | } else { 115 | return permList.stream().anyMatch(e -> Perm.compare(e, perm, this) == Perm.Result.SUCCEED); 116 | } 117 | } 118 | return realUser.hasPermission(perm); 119 | } 120 | 121 | @Override 122 | public void addPermission(Perm perm) { 123 | if (redirectTo == null) { 124 | if (!hasPermission(perm)) { 125 | permList.add(perm); 126 | } 127 | } else { 128 | realUser.addPermission(perm); 129 | } 130 | } 131 | 132 | @Override 133 | public void delPermission(Perm perm) { 134 | if (redirectTo != null) realUser.delPermission(perm); 135 | else { 136 | permList.remove(perm); 137 | } 138 | } 139 | 140 | public String asFormattedName() { 141 | if (userName == null) { 142 | return uniqueID; 143 | } 144 | return userName + "(" + uniqueID + ")"; 145 | } 146 | 147 | /** 148 | * 注意: 请使用UserManager.setRedirectUser(本对象,目标); 149 | * 150 | * @param s 151 | */ 152 | @Internal 153 | public void setRedirectTo(String s) { 154 | this.redirectTo = s; 155 | } 156 | 157 | @Id 158 | public String getUniqueID() { 159 | return uniqueID; 160 | } 161 | 162 | @Override 163 | public String toString() { 164 | return asFormattedName(); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/user/UserManager.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.user; 2 | 3 | import cc.sfclub.core.Core; 4 | import cc.sfclub.user.perm.Perm; 5 | import com.dieselpoint.norm.Database; 6 | 7 | import java.util.Optional; 8 | 9 | /** 10 | * 用户管理器 11 | */ 12 | public class UserManager { 13 | private final Database db; 14 | 15 | public UserManager(Database database) { 16 | this.db = database; 17 | } 18 | 19 | /** 20 | * 用用户名获取用户对象,Nullable 21 | * 22 | * @param userName 23 | * @return 24 | */ 25 | public User byName(String userName) { 26 | return preprocess(db.where("userName=?", userName).first(User.class)); 27 | } 28 | 29 | /** 30 | * 用UUID获取用户对象,Nullable 31 | * 32 | * @param userId 33 | * @return 34 | */ 35 | public User byUUID(String userId) { 36 | return preprocess(db.where("uniqueId=?", userId).first(User.class)); 37 | } 38 | 39 | /** 40 | * 用平台名+平台专属ID获取用户,nullable 41 | * 42 | * @param platform 43 | * @param id 44 | * @return 45 | */ 46 | public User byPlatformID(String platform, String id) { 47 | return preprocess(db.where("platform=? AND platformId=?", platform, id).first(User.class)); 48 | } 49 | 50 | /** 51 | * 设置用户的"指针" 52 | * 大部分关于这个用户的操作将会被重定向到目标上 53 | * 54 | * @param redirected 55 | * @param to 56 | * @return 57 | */ 58 | public User setRedirectUser(User redirected, User to) { 59 | redirected.setRedirectTo(to.getUniqueID()); 60 | redirected.setRealUser(to); 61 | return redirected; 62 | } 63 | 64 | private User preprocess(User u) { 65 | if (u == null) return null; 66 | if (u.getRedirectTo() != null) { 67 | u.setRealUser(byUUID(u.getRedirectTo())); 68 | } 69 | return u; 70 | } 71 | 72 | /** 73 | * 判断用户ID是否存在 74 | * 75 | * @param userId 76 | * @return 77 | */ 78 | public boolean existsId(String userId) { 79 | return db.where("uniqueID=?", userId) == null; 80 | } 81 | 82 | /** 83 | * 判断是否存在用户名 84 | */ 85 | public boolean existsName(String userName) { 86 | 87 | return db.table("user").where("userName=?", userName).results(User.class).size() != 0; 88 | } 89 | 90 | public void addRaw(User u) { 91 | db.insert(u); 92 | } 93 | 94 | public void update(User u) { 95 | db.update(u); 96 | } 97 | 98 | /** 99 | * 注册用户 100 | * 101 | * @param group 权限组,可null 102 | * @param platform 平台 103 | * @param id 平台ID 104 | * @return 105 | */ 106 | public User register(String group, String platform, String id) { 107 | User user = new User(group, platform, id); 108 | db.insert(user); 109 | return user; 110 | } 111 | 112 | /** 113 | * 注册用户 114 | * 115 | * @param group 权限组 116 | * @param initialPermissions 初始权限 117 | * @return 118 | */ 119 | public User register(String group, Perm... initialPermissions) { 120 | User user = new User(group, initialPermissions); 121 | db.insert(user); 122 | return user; 123 | } 124 | 125 | public void addRaw(Group group) { 126 | db.insert(group); 127 | } 128 | 129 | /** 130 | * 注册权限组,已经存在的话会返回存在的 131 | * 132 | * @param name 133 | * @param InitialPerms 134 | * @return 135 | */ 136 | public Group registerGroup(String name, Perm... InitialPerms) { 137 | Optional i = getGroup(name); 138 | if (i.isPresent()) { 139 | return i.get(); 140 | } 141 | Group group = new Group(name, InitialPerms); 142 | db.insert(group); 143 | return group; 144 | } 145 | 146 | /** 147 | * 获取权限组 148 | * 149 | * @param name 150 | * @return 151 | */ 152 | public Optional getGroup(String name) { 153 | if (name == null) return Optional.empty(); 154 | return Optional.ofNullable(db.where("name=?", name).first(Group.class)); 155 | } 156 | 157 | /** 158 | * 取默认 159 | * 160 | * @return 161 | */ 162 | public Group getDefault() { 163 | return getGroup(Core.get().permCfg().getDefaultGroup()).orElse(Group.DEFAULT); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/user/exception/UserPlatformUnboundException.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.user.exception; 2 | 3 | import cc.sfclub.user.User; 4 | 5 | public class UserPlatformUnboundException extends NullPointerException { 6 | public UserPlatformUnboundException(User u) { 7 | super("User " + u + " haven't bind to available platform or platform not found(" + u.getPlatform() + ")"); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/user/perm/Perm.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.user.perm; 2 | 3 | import cc.sfclub.Internal; 4 | import cc.sfclub.core.Core; 5 | import cc.sfclub.user.User; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.util.WeakHashMap; 12 | 13 | @Getter 14 | @Setter 15 | public abstract class Perm { 16 | private static final Logger logger = LoggerFactory.getLogger(Perm.class); 17 | private static final WeakHashMap cachedPermObj = new WeakHashMap<>(); 18 | 19 | /** 20 | * Please use Perm.of() 21 | */ 22 | @Internal 23 | public Perm() { 24 | 25 | } 26 | 27 | /** 28 | * 从缓存中获取权限,当缓存中不存在时使用PureStringPerm模板新建存入并返回。 29 | * 30 | * @param perm 31 | * @return 32 | */ 33 | public static Perm of(String perm) { 34 | if (!cachedPermObj.containsKey(perm)) { 35 | cachedPermObj.put(perm, new PureStringPerm(perm)); 36 | } 37 | return cachedPermObj.get(perm); 38 | } 39 | 40 | /** 41 | * 注册权限到缓存,注意缓存内的对象若无额外的强引用可能被GC 42 | * 43 | * @param perm 44 | */ 45 | public static void register(Perm perm) { 46 | cachedPermObj.put(perm.toString(), perm); 47 | } 48 | 49 | /** 50 | * 权限判定 51 | */ 52 | public static Result compare(Perm orig, Perm target, User u) { 53 | Result result = Result.FAILED; 54 | if (target.toString().startsWith("-") && target.toString().matches(orig.toString().replaceFirst("-", ""))) { 55 | result = Result.BANNED; 56 | } else { 57 | if (target.toString().matches(orig.toString())) result = Result.SUCCEED; 58 | } 59 | if (Core.get() != null && Core.get().config().isDebug()) { 60 | logger.info("[DEBUG][Perm] Compare: {} , {} == {}", orig, target, result); 61 | } 62 | if (u == null) { 63 | return result; 64 | } 65 | return target.hasPermission(u, result); 66 | } 67 | 68 | public abstract Result hasPermission(User user, Result regexResult); 69 | 70 | /** 71 | * 只会比较权限节点是否一致 72 | * 73 | * @param obj 74 | * @return 75 | */ 76 | @Override 77 | public boolean equals(Object obj) { 78 | if (super.equals(obj)) { 79 | return true; 80 | } 81 | if (obj instanceof String) { 82 | String perm = (String) obj; 83 | return compare(Perm.of(perm), this, null) == Result.SUCCEED; 84 | } 85 | if (obj instanceof Perm) { 86 | Perm perm = (Perm) obj; 87 | return compare(perm, this, null) == Result.SUCCEED; 88 | } 89 | return false; 90 | } 91 | 92 | public enum Result { 93 | FAILED, BANNED, SUCCEED 94 | } 95 | 96 | /** 97 | * @return permission node name 98 | */ 99 | @Override 100 | public abstract String toString(); 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/user/perm/Permissible.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.user.perm; 2 | 3 | /** 4 | * 可以被权限度量的对象 5 | */ 6 | public interface Permissible { 7 | boolean hasPermission(Perm perm); 8 | 9 | void addPermission(Perm perm); 10 | 11 | void delPermission(Perm perm); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/cc/sfclub/user/perm/PureStringPerm.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.user.perm; 2 | 3 | import cc.sfclub.user.User; 4 | 5 | /** 6 | * 纯文本模板 7 | */ 8 | public class PureStringPerm extends Perm { 9 | private final String node; 10 | 11 | public PureStringPerm(String node) { 12 | this.node = node; 13 | } 14 | 15 | @Override 16 | public Result hasPermission(User user, Result regexYes) { 17 | return regexYes; 18 | } 19 | 20 | @Override 21 | public String toString() { 22 | return node; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss} %white([%thread]) %highlight(%-5level) - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 12 | utf-8 13 | 14 | logs/logs.log 15 | 16 | logs/logs-%i.log 17 | 18 | 19 | 1MB 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/test/java/cc/sfclub/test/ListConverterTest.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.test; 2 | 3 | import cc.sfclub.database.converter.PermListConverter; 4 | import cc.sfclub.database.converter.StrListConverter; 5 | import cc.sfclub.user.perm.Perm; 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | 9 | import java.util.Arrays; 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | public class ListConverterTest { 14 | private final String specialListAsString = ".*"; 15 | @Test 16 | public void on() { 17 | StrListConverter strListConverter = new StrListConverter(); 18 | List simpleList = Arrays.asList("a", "b"); 19 | String simpleListAsString = "a,b"; 20 | Assert.assertEquals(strListConverter.convertToDatabaseColumn(simpleList), simpleListAsString); 21 | Assert.assertEquals(simpleList, strListConverter.convertToEntityAttribute(simpleListAsString)); 22 | 23 | List complexList = Arrays.asList("\\", ",", "a"); 24 | /* \\ 25 | , 26 | a*/ 27 | String complexListAsString = "\\\\" + "," + "\\," + "," + "a"; 28 | Assert.assertEquals(strListConverter.convertToDatabaseColumn(complexList), complexListAsString); 29 | Assert.assertEquals(complexList, strListConverter.convertToEntityAttribute(complexListAsString)); 30 | 31 | List specialList = Collections.singletonList(Perm.of(".*")); 32 | PermListConverter permListConverter = new PermListConverter(); 33 | Assert.assertEquals(permListConverter.convertToEntityAttribute(".*"), specialList); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/cc/sfclub/test/PermEqualsTest.java: -------------------------------------------------------------------------------- 1 | package cc.sfclub.test; 2 | 3 | import cc.sfclub.user.perm.Perm; 4 | import org.junit.Assert; 5 | import org.junit.Test; 6 | 7 | import java.util.Arrays; 8 | import java.util.List; 9 | 10 | public class PermEqualsTest { 11 | @Test 12 | public void run() { 13 | List perms = Arrays.asList(new Perm("aa.bb"), new Perm("bb.cc")); 14 | 15 | Assert.assertTrue(perms.contains(new Perm("aa.bb"))); 16 | Assert.assertTrue(perms.contains(new Perm("bb.cc"))); 17 | } 18 | } 19 | --------------------------------------------------------------------------------