├── .all-contributorsrc ├── .gitignore ├── README.md ├── build.gradle.kts ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src └── main ├── kotlin └── com │ └── hcyacg │ └── protocol │ ├── anno │ └── NoArg.kt │ ├── common │ ├── BotApi.kt │ ├── BotClient.kt │ ├── BotEvent.kt │ ├── BotListener.kt │ ├── BotManager.kt │ ├── ExcepetionHandler.kt │ ├── Gateway.kt │ ├── MonitorMessage.kt │ └── SequelBotClient.kt │ ├── constant │ └── Constant.kt │ ├── entity │ ├── Announces.kt │ ├── AudioControl.kt │ ├── Channel.kt │ ├── ChannelPermissions.kt │ ├── Guild.kt │ ├── Member.kt │ ├── Message.kt │ ├── MessageAudited.kt │ ├── Role.kt │ ├── Roles.kt │ ├── Schedule.kt │ └── User.kt │ ├── event │ ├── AudioActionEvent.kt │ ├── ChannelEvent.kt │ ├── GuildEvent.kt │ ├── GuildMemberEvent.kt │ ├── ReadyEvent.kt │ ├── api │ │ ├── Author.kt │ │ ├── Dms.kt │ │ ├── Member.kt │ │ ├── Message.kt │ │ └── User.kt │ └── message │ │ ├── AtMessageCreateEvent.kt │ │ ├── EventApi.kt │ │ ├── MessageAuditPassEvent.kt │ │ ├── MessageAuditRejectEvent.kt │ │ ├── MessageCreateEvent.kt │ │ ├── MessageEvent.kt │ │ ├── MessageReactionEvent.kt │ │ └── directMessage │ │ ├── DirectMessage.kt │ │ ├── DirectMessageCreateEvent.kt │ │ └── DmsApi.kt │ ├── internal │ ├── BaseBotClient.kt │ ├── BaseBotListener.kt │ ├── config │ │ └── IdentifyConfig.kt │ ├── entity │ │ ├── AccessWithFragmentedWss.kt │ │ ├── Dispatch.kt │ │ ├── Heartbeat.kt │ │ ├── Identify.kt │ │ ├── Operation.kt │ │ ├── Resume.kt │ │ └── UniversalWssAccess.kt │ └── enums │ │ ├── DispatchEnums.kt │ │ └── OPCodeEnums.kt │ └── utils │ ├── OkHttpUtils.kt │ ├── ScheduleUtils.kt │ └── UA.kt └── resources └── logback.xml /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributors": [ 8 | { 9 | "login": "Nekoer", 10 | "name": "牧瀬くりす", 11 | "avatar_url": "https://avatars.githubusercontent.com/u/32485369?v=4", 12 | "profile": "https://www.hcyacg.com/", 13 | "contributions": [ 14 | "code", 15 | "doc" 16 | ] 17 | }, 18 | { 19 | "login": "cssxsh", 20 | "name": "cssxsh", 21 | "avatar_url": "https://avatars.githubusercontent.com/u/32539286?v=4", 22 | "profile": "https://github.com/cssxsh", 23 | "contributions": [ 24 | "review" 25 | ] 26 | } 27 | ], 28 | "contributorsPerLine": 7, 29 | "projectName": "tencent-guild-protocol", 30 | "projectOwner": "Nekoer", 31 | "repoType": "github", 32 | "repoHost": "https://github.com", 33 | "skipCi": true 34 | } 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 项目排除路径 2 | /.gradle/ 3 | /build/ 4 | *.properties 5 | *.rev 6 | *.gpg 7 | *.log -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tencent-guild-protocol 2 | 3 | [![All Contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?style=flat-square)](#contributors-) 4 | 5 | 6 | > Tencent频道的机器人Kotlin SDK 7 | 8 | [![maven-central](https://img.shields.io/maven-central/v/com.hcyacg/tencent-guild-protocol)](https://search.maven.org/artifact/com.hcyacg/tencent-guild-protocol) 9 | 10 | ## 导入依赖 11 | 12 | ### Maven 13 | 14 | ```Maven 15 | 16 | com.hcyacg 17 | tencent-guild-protocol 18 | ${version} 19 | 20 | ``` 21 | 22 | ### Gradle Groovy DSL 23 | 24 | ```Gradle Groovy DSL 25 | implementation 'com.hcyacg:tencent-guild-protocol:${version}' 26 | ``` 27 | 28 | ### Gradle Kotlin DSL 29 | 30 | ```Gradle Kotlin DSL 31 | implementation("com.hcyacg:tencent-guild-protocol:${version}") 32 | ``` 33 | 34 | ## 如何使用 35 | 36 | 1. 首先配置你的botId 和 botToken 37 | 38 | ```kotlin 39 | fun main(args: Array) { 40 | val token = "Bot id.token" 41 | //放入你的Listener 42 | //默认是获取公域的信息 43 | BotManager.configuration(token, false).addListen(Test()) 44 | //私域请使用 45 | BotManager.configuration(token,true).addListen(Test()) 46 | } 47 | 48 | ``` 49 | 50 | 2. 实现你的Listener ,这里会附赠给你一个例子 :`Test` 51 | 52 | ```kotlin 53 | //实际上实现一个Listener十分简单,你只需要继承`BotEvent`,然后实现它里面你需要的方法就可以了 54 | class Test : BotEvent() { 55 | 56 | //这里我实现了 私域中 事件的 监听 57 | override suspend fun onMessageCreate(event: MessageCreateEvent) { 58 | 59 | } 60 | } 61 | ``` 62 | 63 | 3.SDK内置了目前官方已出的功能 64 | 65 | ```kotlin 66 | //获取子频道消息 67 | BotApi.getChannelInfo(data.channel_id) 68 | //获取频道下的子频道列表 69 | BotApi.getChildChannelInfo(data.guild_id) 70 | //获取单个成员消息 71 | BotApi.getMemberInfo(data.guild_id, data.author.id) 72 | //获取频道信息 73 | BotApi.getGuildById(data.guild_id) 74 | //获取频道身份组列表 75 | BotApi.getRolesByGuild(data.guild_id) 76 | //创建频道身份组 77 | BotApi.createRolesByGuild(data.guild_id, Filter(1, 1, 0), Info("测试", 16757760, 0)) 78 | //修改频道身份组 79 | BotApi.changeRolesByGuild(data.guild_id, "", Filter(1, 1, 0), Info("测试", 16758465, 0)) 80 | //删除频道身份组 81 | BotApi.deleteRolesByGuild(data.guild_id, "") 82 | //增加频道身份组成员(除子频道管理员 83 | BotApi.createMemberRolesByGuild(data.guild_id, "", "") 84 | //增加频道身份组成员(仅子频道管理员 85 | BotApi.createChildMemberRolesByGuild(data.guild_id, BotApi.getChannelInfo(data.channel_id), "", "") 86 | //删除频道身份组成员(除子频道管理员 87 | BotApi.deleteMemberRolesByGuild(data.guild_id, "", "") 88 | //删除频道身份组成员(仅子频道管理员 89 | BotApi.deleteChildMemberRolesByGuild(data.guild_id, BotApi.getChannelInfo(data.channel_id), "", "") 90 | //修改指定子频道的权限 目前只支持修改查看权限 91 | BotApi.changeChannelPermissions(data.channel_id, "", false) 92 | //创建子频道公告 93 | BotApi.createAnnounces(data.channel_id, data.id) 94 | //删除子频道公告 95 | BotApi.deleteAnnounces(data.channel_id, message_id) 96 | //获得机器人信息 97 | BotApi.getMe() 98 | //获取当前用户频道列表 99 | BotApi.getMeGuildsAfter(data.guild_id, 100) 100 | BotApi.getMeGuildsBefore(data.guild_id, 100) 101 | 102 | //获取日程列表 103 | BotApi.getScheduleList(日程子频道id) 104 | //创建日程 105 | BotApi.createSchedule(日程子频道id, 日程对象) 106 | //修改日程 107 | BotApi.changeScheduleById(日程子频道id, 日程id, 日程对象) 108 | //根据日程id删除日程 109 | BotApi.deleteScheduleById(日程子频道id, 日程id) 110 | //返回结束时间在时间戳之后的日程列表 111 | BotApi.getScheduleListByTime(日程子频道id, 时间戳) 112 | 113 | ``` 114 | 115 | ### 私域功能 116 | 117 | ```Kotlin 118 | //获取频道成员列表 119 | BotApi.getGuildMemberList(data.guild_id, "0", 1) 120 | //创建子频道 121 | BotApi.createChannel(data.guild_id, ChannelDto("测试", ChannelType.textSubchannel, 排序id, "父类节点")) 122 | //修改子频道信息 123 | BotApi.changeChannelInfo(data.channel_id, ChannelDto("测试", ChannelType.textSubchannel, 排序id, "父类节点")) 124 | //删除子频道 125 | BotApi.deleteChannel(data.channel_id) 126 | //删除指定频道成员 127 | BotApi.deleteMember(data.guild_id, "用户id") 128 | ``` 129 | 130 | 4. AtMessageCreateEvent 和 MessageCreateEvent 都存在回复功能 131 | 132 | ```kotlin 133 | //被动 134 | event.replyArk() 135 | event.replyImage() 136 | event.replyEmbed() 137 | event.replyText() 138 | event.replyAudio() 139 | event.replyTextWithImage() 140 | //主动消息 141 | event.replyArkNotId() 142 | event.replyImageNotId() 143 | event.replyEmbedNotId() 144 | event.replyTextNotId() 145 | event.replyTextWithImageNotId() 146 | //禁言 147 | event.mute(120) 148 | event.author.mute(120) 149 | 150 | //ark模板例子 151 | event.replyArk( 152 | MessageArk( 153 | 23, listOf( 154 | MessageArkKv("#DESC#", "descaaaaaa", null), 155 | MessageArkKv("#PROMPT#", "promptaaaa", null), 156 | MessageArkKv( 157 | "#LIST#", null, listOf( 158 | MessageArkObj(listOf(MessageArkObjKv("desc", "你好"))), 159 | MessageArkObj(listOf(MessageArkObjKv("desc", "你好"))), 160 | MessageArkObj(listOf(MessageArkObjKv("desc", "你好"))), 161 | MessageArkObj(listOf(MessageArkObjKv("desc", "你好"))), 162 | MessageArkObj(listOf(MessageArkObjKv("desc", "你好"))) 163 | ) 164 | ) 165 | ) 166 | ) 167 | ) 168 | ``` 169 | ## Contributors ✨ 170 | 171 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 |

牧瀬くりす

💻

cssxsh

👀
182 | 183 | 184 | 185 | 186 | 187 | 188 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 2 | 3 | plugins { 4 | `java-library` 5 | kotlin("jvm") version "1.6.0" 6 | kotlin("plugin.serialization") version "1.6.0" 7 | id("org.jetbrains.kotlin.plugin.noarg") version "1.6.0" 8 | `maven-publish` 9 | signing 10 | id("io.github.gradle-nexus.publish-plugin") version "1.1.0" 11 | } 12 | 13 | group = "com.hcyacg" 14 | version = "0.3.9" 15 | 16 | 17 | repositories { 18 | mavenLocal() 19 | mavenCentral() 20 | } 21 | 22 | dependencies { 23 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.0-native-mt") 24 | // implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1") 25 | implementation("com.squareup.okhttp3:okhttp:4.9.3") 26 | 27 | implementation("org.apache.commons:commons-lang3:3.12.0") 28 | implementation("org.apache.httpcomponents:httpclient:4.5.13") 29 | implementation("org.jsoup:jsoup:1.14.3") 30 | implementation("org.slf4j:slf4j-api:1.7.33") 31 | implementation("ch.qos.logback:logback-core:1.2.10") 32 | implementation("ch.qos.logback:logback-classic:1.2.10") 33 | implementation("com.google.code.gson:gson:2.8.9") 34 | 35 | testImplementation("org.jetbrains.kotlin:kotlin-test:1.6.0") 36 | } 37 | 38 | tasks.test { 39 | useJUnit() 40 | } 41 | 42 | tasks.withType() { 43 | kotlinOptions.jvmTarget = "11" 44 | } 45 | 46 | noArg { 47 | annotation("com.lindroid.projectname.annotation.NoArg") 48 | } 49 | 50 | java { 51 | withJavadocJar() 52 | withSourcesJar() 53 | } 54 | 55 | tasks.register("stuffZip") { 56 | archiveBaseName.set("stuff") 57 | from("src/stuff") 58 | } 59 | 60 | publishing { 61 | publications { 62 | create("mavenJava") { 63 | artifactId = rootProject.name 64 | from(components["java"]) 65 | versionMapping { 66 | usage("java-api") { 67 | fromResolutionOf("runtimeClasspath") 68 | } 69 | usage("java-runtime") { 70 | fromResolutionResult() 71 | } 72 | } 73 | pom { 74 | name.set(rootProject.name) 75 | description.set("The Robot Kotlin SDK on Tencent Channel") 76 | url.set("https://github.com/Nekoer/tencent-guild-protocol") 77 | // properties.set(mapOf( 78 | // "myProp" to "value", 79 | // "prop.with.dots" to "anotherValue" 80 | // )) 81 | licenses { 82 | license { 83 | name.set("The Apache License, Version 2.0") 84 | url.set("https://www.apache.org/licenses/LICENSE-2.0.txt") 85 | } 86 | } 87 | developers { 88 | developer { 89 | id.set("Nekoer") 90 | name.set("Nekoer") 91 | email.set("hcyacg@vip.qq.com") 92 | } 93 | } 94 | scm { 95 | connection.set("scm:git:git://github.com/Nekoer/tencent-guild-protocol.git") 96 | developerConnection.set("scm:git:ssh://github.com/Nekoer/tencent-guild-protocol.git") 97 | url.set("https://github.com/Nekoer/tencent-guild-protocol") 98 | } 99 | } 100 | } 101 | } 102 | repositories { 103 | maven { 104 | // change URLs to point to your repos, e.g. http://my.org/repo 105 | val releasesRepoUrl = uri(layout.buildDirectory.dir("repos/releases")) 106 | val snapshotsRepoUrl = uri(layout.buildDirectory.dir("repos/snapshots")) 107 | url = if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl 108 | } 109 | } 110 | } 111 | 112 | signing { 113 | sign(publishing.publications["mavenJava"]) 114 | } 115 | 116 | tasks.javadoc { 117 | if (JavaVersion.current().isJava9Compatible) { 118 | (options as StandardJavadocDocletOptions).addBooleanOption("html5", true) 119 | } 120 | } 121 | 122 | nexusPublishing { 123 | repositories { 124 | sonatype { //only for users registered in Sonatype after 24 Feb 2021 125 | nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) 126 | snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) 127 | 128 | username.set(properties["myNexusTokenUsername"].toString()) 129 | password.set(properties["myNexusTokenPassword"].toString()) 130 | } 131 | } 132 | } -------------------------------------------------------------------------------- /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 | MSYS* | 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 execute 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 execute 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 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | rootProject.name = "tencent-guild-protocol" 3 | 4 | -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/anno/NoArg.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.anno 2 | 3 | annotation class NoArg() 4 | -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/common/BotApi.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.common 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.reflect.TypeToken 5 | import com.hcyacg.protocol.constant.Constant.Companion.botToken 6 | import com.hcyacg.protocol.constant.Constant.Companion.logger 7 | import com.hcyacg.protocol.constant.Constant.Companion.proUrl 8 | import com.hcyacg.protocol.entity.* 9 | import com.hcyacg.protocol.entity.Member 10 | import com.hcyacg.protocol.entity.Message 11 | import com.hcyacg.protocol.entity.User 12 | import com.hcyacg.protocol.utils.OkHttpUtils 13 | import okhttp3.Headers 14 | 15 | object BotApi { 16 | 17 | private const val userMe = "$proUrl/users/@me" 18 | private const val userMeGuild = "$userMe/guilds" 19 | 20 | private const val channel = "$proUrl/channels/{{channel_id}}" 21 | private const val channelPermissions = "$channel/members/{{user_id}}/permissions" 22 | private const val sendMessage = "$channel/messages" 23 | private const val getMessage = "$channel/messages/{{message_id}}" 24 | private const val sendAudio = "$channel/audio" 25 | private const val recall = "$sendMessage/{{message_id}}" 26 | 27 | private const val channelRolePermissions = "$channel/roles/{{role_id}}/permissions" 28 | 29 | private const val guildInfo = "$proUrl/guilds/{{guild_id}}" 30 | private const val channelList = "$guildInfo/channels" 31 | private const val memberInfo = "$guildInfo/members/{{user_id}}" 32 | private const val memberMute = "$guildInfo/members/{{user_id}}/mute" 33 | 34 | private const val memberList = "$guildInfo/members" 35 | private const val roles = "$guildInfo/roles" 36 | private const val changeRole = "$guildInfo/roles/{{role_id}}" 37 | private const val editMemberRole = "$guildInfo/members/{{user_id}}/roles/{{role_id}}" 38 | 39 | private const val guildAnnounce = "$proUrl/guilds/{{guild_id}}/announces" 40 | private const val deleteGuildAnnounce = "$guildAnnounce/{{message_id}}" 41 | private const val channelAnnounces = "$proUrl/channels/{{channel_id}}/announces" 42 | private const val deleteChannelAnnounces = "$channelAnnounces/{{message_id}}" 43 | 44 | private const val schedules = "$proUrl/channels/{{channel_id}}/schedules" 45 | private const val scheduleUrl = "$schedules/{{schedule_id}}" 46 | 47 | private const val mute = "$proUrl/guilds/{{guild_id}}/mute" 48 | 49 | 50 | private fun officeApiHeader(): MutableMap { 51 | return mutableMapOf( 52 | "Authorization" to botToken!! 53 | ) 54 | } 55 | 56 | 57 | /** 58 | * 获取频道信息 59 | * @param guildId 频道id 60 | */ 61 | @JvmStatic 62 | fun getGuildById(guildId: String): Guild { 63 | 64 | 65 | val url = guildInfo.replace("{{guild_id}}", guildId) 66 | val res = OkHttpUtils[url, officeApiHeader()] 67 | 68 | val result = res.body?.string() 69 | logger.debug(result) 70 | return Gson().fromJson(result, Guild::class.java) 71 | } 72 | 73 | 74 | /** 75 | * 获取频道身份组列表 76 | * @param guildId 频道id 77 | */ 78 | @JvmStatic 79 | fun getRolesByGuild(guildId: String): Roles { 80 | val url = roles.replace("{{guild_id}}", guildId) 81 | val res = OkHttpUtils.getJson(url, officeApiHeader()) 82 | logger.debug(res) 83 | return Gson().fromJson(res, Roles::class.java) 84 | } 85 | 86 | /** 87 | * 创建频道身份组 88 | * @param guildId 频道id 89 | * @param filter 标识需要修改哪些字段 90 | * @param info 携带需要修改的字段内容 91 | */ 92 | @JvmStatic 93 | fun createRolesByGuild(guildId: String, filter: Filter, info: Info): RoleVo { 94 | val url = roles.replace("{{guild_id}}", guildId) 95 | 96 | val json = Gson().toJson(RoleDto(filter, info)) 97 | val res = OkHttpUtils.postJson(url, OkHttpUtils.addJson(json), officeApiHeader()) 98 | logger.debug(res) 99 | return Gson().fromJson(res, RoleVo::class.java) 100 | } 101 | 102 | /** 103 | * 修改频道身份组 104 | * @param guildId 频道id 105 | * @param roleId 身份组id 106 | * @param filter 标识需要修改哪些字段 107 | * @param info 携带需要修改的字段内容 108 | */ 109 | @JvmStatic 110 | fun changeRolesByGuild(guildId: String, roleId: String, filter: Filter, info: Info): RoleVo { 111 | val url = changeRole.replace("{{guild_id}}", guildId).replace("{{role_id}}", roleId) 112 | val json = Gson().toJson(RoleDto(filter, info)) 113 | val res = OkHttpUtils.patchJson(url, OkHttpUtils.addJson(json), officeApiHeader()) 114 | logger.debug(res) 115 | return Gson().fromJson(res, RoleVo::class.java) 116 | } 117 | 118 | /** 119 | * 删除频道身份组 120 | * @param guildId 频道id 121 | * @param roleId 身份组id 122 | */ 123 | @JvmStatic 124 | fun deleteRolesByGuild(guildId: String, roleId: String): Boolean { 125 | val url = changeRole.replace("{{guild_id}}", guildId).replace("{{role_id}}", roleId) 126 | 127 | val res = OkHttpUtils.delete(url, mutableMapOf(), officeApiHeader()) 128 | logger.debug(res.body!!.string()) 129 | return res.code == 204 130 | } 131 | 132 | 133 | /** 134 | * 增加频道身份组成员(除子频道管理员 135 | * @param guildId 频道id 136 | * @param userId 用户id 137 | * @param roleId 身份组id 138 | */ 139 | @JvmStatic 140 | fun createMemberRolesByGuild(guildId: String, userId: String, roleId: String): Boolean { 141 | val url = editMemberRole.replace("{{guild_id}}", guildId).replace("{{user_id}}", userId) 142 | .replace("{{role_id}}", roleId) 143 | val res = OkHttpUtils.put(url, mutableMapOf(), officeApiHeader()) 144 | logger.debug(res.code.toString()) 145 | return res.code == 204 146 | } 147 | 148 | /** 149 | * 增加频道身份组成员(仅子频道管理员 150 | * @param guildId 频道id 151 | * @param channel Channel对象 152 | * @param userId 用户id 153 | * @param roleId 身份组id 154 | */ 155 | @JvmStatic 156 | fun createChildMemberRolesByGuild(guildId: String, channel: Channel, userId: String, roleId: String): Boolean { 157 | val url = editMemberRole.replace("{{guild_id}}", guildId).replace("{{user_id}}", userId) 158 | .replace("{{role_id}}", roleId) 159 | val json = Gson().toJson(channel) 160 | val res = OkHttpUtils.put(url, OkHttpUtils.addJson(json), Headers.headersOf("Authorization", botToken!!)) 161 | logger.debug(res.code.toString()) 162 | return res.code == 204 163 | } 164 | 165 | /** 166 | * 删除频道身份组成员(除子频道管理员 167 | * @param guildId 频道id 168 | * @param userId 用户id 169 | * @param roleId 身份组id 170 | */ 171 | @JvmStatic 172 | fun deleteMemberRolesByGuild(guildId: String, userId: String, roleId: String): Boolean { 173 | val url = editMemberRole.replace("{{guild_id}}", guildId).replace("{{user_id}}", userId) 174 | .replace("{{role_id}}", roleId) 175 | val res = OkHttpUtils.delete(url, mutableMapOf(), officeApiHeader()) 176 | logger.debug(res.code.toString()) 177 | return res.code == 204 178 | } 179 | 180 | /** 181 | * 删除频道身份组成员(仅子频道管理员 182 | * @param guildId 频道id 183 | * @param channel Channel对象 184 | * @param userId 用户id 185 | * @param roleId 身份组id 186 | */ 187 | @JvmStatic 188 | fun deleteChildMemberRolesByGuild(guildId: String, channel: Channel, userId: String, roleId: String): Boolean { 189 | val url = editMemberRole.replace("{{guild_id}}", guildId).replace("{{user_id}}", userId) 190 | .replace("{{role_id}}", roleId) 191 | val json = Gson().toJson(channel) 192 | val res = OkHttpUtils.delete(url, OkHttpUtils.addJson(json), officeApiHeader()) 193 | logger.debug(res.code.toString()) 194 | return res.code == 204 195 | } 196 | 197 | /** 198 | * 获取单个成员消息 199 | * @param guildId 频道id 200 | * @param userId 用户id 201 | */ 202 | @JvmStatic 203 | fun getMemberInfo(guildId: String, userId: String): Member { 204 | val url = memberInfo.replace("{{guild_id}}", guildId).replace("{{user_id}}", userId) 205 | 206 | val res = OkHttpUtils.getJson(url, officeApiHeader()) 207 | logger.debug(res) 208 | return Gson().fromJson(res, Member::class.java) 209 | } 210 | 211 | /** 212 | * 获取子频道消息 213 | * @param channelId 子频道id 214 | */ 215 | @JvmStatic 216 | fun getChannelInfo(channelId: String): Channel { 217 | val url = channel.replace("{{channel_id}}", channelId) 218 | val res = OkHttpUtils.getJson(url, officeApiHeader()) 219 | logger.debug(res) 220 | return Gson().fromJson(res, Channel::class.java) 221 | } 222 | 223 | /** 224 | * 获取频道下的子频道列表 225 | * @param guildId 频道id 226 | */ 227 | @JvmStatic 228 | fun getChildChannelInfo(guildId: String): List { 229 | val url = channelList.replace("{{guild_id}}", guildId) 230 | 231 | val res = OkHttpUtils.getJson(url, officeApiHeader()) 232 | logger.debug(res) 233 | return Gson().fromJson(res, object : TypeToken>() {}.type) 234 | } 235 | 236 | /** 237 | * 根据message的id获取消息数据 238 | * @param channelId 子频道id 239 | * @param messageId 消息id 240 | */ 241 | @JvmStatic 242 | fun getMessageById(channelId: String, messageId: String): Message { 243 | val url = getMessage.replace("{{channel_id}}", channelId).replace("{{message_id}}", messageId) 244 | 245 | val res = OkHttpUtils.getJson(url, officeApiHeader()) 246 | logger.debug(res) 247 | return Gson().fromJson(res, Message::class.java) 248 | } 249 | 250 | /** 251 | * 获取指定子频道的权限 252 | * @param channelId 子频道id 253 | * @param userId 用户id 254 | */ 255 | @JvmStatic 256 | fun getChannelPermissions(channelId: String, userId: String): ChannelPermissions { 257 | val url = channelPermissions.replace("{{channel_id}}", channelId).replace("{{user_id}}", userId) 258 | val res = OkHttpUtils.getJson(url, officeApiHeader()) 259 | logger.debug(res) 260 | return Gson().fromJson(res, ChannelPermissions::class.java) 261 | } 262 | 263 | /** 264 | * 修改指定子频道的权限 目前只支持修改查看权限 265 | * @param channelId 子频道id 266 | * @param userId 用户id 267 | * @param isAdd 是否赋予用户查看子频道权限 268 | */ 269 | @JvmStatic 270 | fun changeChannelPermissions(channelId: String, userId: String, isAdd: Boolean): Boolean { 271 | val url = channelPermissions.replace("{{channel_id}}", channelId).replace("{{user_id}}", userId) 272 | val map = mutableMapOf() 273 | if (isAdd) { 274 | map["add"] = 0.shl(1).toString() 275 | } else { 276 | map["remove"] = 1.shl(1).toString() 277 | } 278 | val res = OkHttpUtils.put(url, map, officeApiHeader()) 279 | logger.debug(res.code.toString()) 280 | return res.code == 204 281 | } 282 | 283 | /** 284 | * 获取指定子频道身份组的权限 285 | * @param channelId 子频道id 286 | * @param roleId 身份组id 287 | */ 288 | @JvmStatic 289 | fun getChannelRolePermissions(channelId: String, roleId: String): ChannelPermissions { 290 | val url = channelRolePermissions.replace("{{channel_id}}", channelId).replace("{{role_id}}", roleId) 291 | val res = OkHttpUtils.getJson(url, officeApiHeader()) 292 | logger.debug(res) 293 | return Gson().fromJson(res, ChannelPermissions::class.java) 294 | } 295 | 296 | /** 297 | * 修改指定子频道身份组的权限 目前只支持修改查看权限 298 | * @param channelId 子频道id 299 | * @param roleId 身份组id 300 | * @param isAdd 是否赋予用户查看子频道权限 301 | */ 302 | @JvmStatic 303 | fun changeChannelRolePermissions(channelId: String, roleId: String, isAdd: Boolean): Boolean { 304 | val url = channelRolePermissions.replace("{{channel_id}}", channelId).replace("{{role_id}}", roleId) 305 | val map = mutableMapOf() 306 | if (isAdd) { 307 | map["add"] = 0.shl(1).toString() 308 | } else { 309 | map["remove"] = 1.shl(1).toString() 310 | } 311 | val res = OkHttpUtils.put(url, map, officeApiHeader()) 312 | logger.debug(res.code.toString()) 313 | return res.code == 204 314 | } 315 | 316 | 317 | /** 318 | * 获取当前用户信息 319 | */ 320 | @JvmStatic 321 | fun getMe(): User { 322 | val res = OkHttpUtils.getJson(userMe, officeApiHeader()) 323 | logger.debug(res) 324 | val user = Gson().fromJson(res, User::class.java) 325 | user.bot = true 326 | return user 327 | } 328 | 329 | /** 330 | * 获取当前用户频道列表 331 | * guildId string 读此id之前的数据 guild id, before/after 只能带一个 332 | * limit int 每次拉取多少条数据 最大不超过100,默认100 333 | */ 334 | @JvmStatic 335 | fun getMeGuildsBefore(guildId: String, limit: Int): List { 336 | val url = userMeGuild.plus("?before=$guildId").plus("&limit=$limit") 337 | val res = OkHttpUtils.getJson(url, officeApiHeader()) 338 | logger.debug(res) 339 | return Gson().fromJson(res, object : TypeToken>() {}.type) 340 | } 341 | 342 | /** 343 | * 获取当前用户频道列表 344 | * guildId string 读此id之后的数据 guild id, before/after 只能带一个 345 | * limit int 每次拉取多少条数据 最大不超过100,默认100 346 | */ 347 | @JvmStatic 348 | fun getMeGuildsAfter(guildId: String, limit: Int): List { 349 | val url = userMeGuild.plus("?after=$guildId").plus("&limit=$limit") 350 | val res = OkHttpUtils.getJson(url, officeApiHeader()) 351 | logger.debug(res) 352 | return Gson().fromJson(res, object : TypeToken>() {}.type) 353 | } 354 | 355 | /** 356 | * 私域功能 357 | */ 358 | 359 | /** 360 | * 获取频道成员列表 361 | * @param guildId 频道id 362 | * @param userId 上一次回包中最大的用户ID, 如果是第一次请求填0,默认为0 363 | * @param limit 分页大小,1-1000,默认是1 364 | */ 365 | @JvmStatic 366 | fun getGuildMemberList(guildId: String, userId: String, limit: Int): List { 367 | val url = memberList.replace("{{guild_id}}", guildId) 368 | url.plus("?after=$userId").plus("&limit=$limit") 369 | val res = OkHttpUtils.getJson(url, Headers.headersOf("Authorization", botToken!!)) 370 | logger.debug(res) 371 | return Gson().fromJson(res, object : TypeToken>() {}.type) 372 | } 373 | 374 | /** 375 | * 删除指定频道成员 376 | * @param guildId 频道id 377 | * @param userId 用户id 378 | */ 379 | @JvmStatic 380 | fun deleteMember(guildId: String, userId: String): Boolean { 381 | val url = memberInfo.replace("{{guild_id}}", guildId).replace("{{user_id}}", userId) 382 | val res = OkHttpUtils.delete(url, mutableMapOf(), officeApiHeader()) 383 | logger.debug(res.code.toString()) 384 | return res.code == 204 385 | } 386 | 387 | /** 388 | * 创建子频道 389 | * @param guildId 频道id 390 | * @param channelDto 创建子频道需要的参数对象 391 | */ 392 | @JvmStatic 393 | fun createChannel(guildId: String, channelDto: ChannelDto): Channel { 394 | val url = channelList.replace("{{guild_id}}", guildId) 395 | val json = Gson().toJson(channelDto) 396 | val res = OkHttpUtils.postJson(url, OkHttpUtils.addJson(json), officeApiHeader()) 397 | logger.debug(res) 398 | return Gson().fromJson(res, Channel::class.java) 399 | } 400 | 401 | /** 402 | * 修改子频道信息 403 | * @param channelId 子频道id 404 | * @param channelDto 创建子频道需要的参数对象 405 | */ 406 | @JvmStatic 407 | fun changeChannelInfo(channelId: String, channelDto: ChannelDto): Channel { 408 | val url = channel.replace("{{channel_id}}", channelId) 409 | val json = Gson().toJson(channelDto) 410 | val res = OkHttpUtils.patchJson(url, OkHttpUtils.addJson(json), officeApiHeader()) 411 | logger.debug(res) 412 | return Gson().fromJson(res, Channel::class.java) 413 | } 414 | 415 | /** 416 | * 删除子频道 417 | * @param channelId 子频道id 418 | */ 419 | @JvmStatic 420 | fun deleteChannel(channelId: String): Boolean { 421 | val url = channel.replace("{{channel_id}}", channelId) 422 | val res = OkHttpUtils.delete(url, mutableMapOf(), officeApiHeader()) 423 | logger.debug(res.code.toString()) 424 | return res.code == 200 425 | } 426 | 427 | 428 | /** 429 | * 创建全频道公告 430 | * @param guildId 频道id 431 | * @param channelId 子频道id 432 | * @param messageId 消息id 433 | */ 434 | @JvmStatic 435 | fun createGuildAnnounces(guildId: String,channelId: String, messageId: String): Announces { 436 | val url = guildAnnounce.replace("{{guild_id}}", guildId) 437 | val json = "{\"message_id\": \"$messageId\",\"channel_id\":\"$channelId\"}" 438 | val res = OkHttpUtils.postJson(url, OkHttpUtils.addJson(json), officeApiHeader()) 439 | logger.debug(res) 440 | return Gson().fromJson(res, Announces::class.java) 441 | } 442 | 443 | /** 444 | * 删除全频道公告 445 | * @param guildId 频道id 446 | * @param messageId 消息id 447 | */ 448 | @JvmStatic 449 | fun deleteGuildAnnounces(guildId: String, messageId: String): Boolean { 450 | val url = deleteGuildAnnounce.replace("{{guild_id}}", guildId).replace("{{message_id}}", messageId) 451 | val res = OkHttpUtils.delete(url, mutableMapOf(), officeApiHeader()) 452 | logger.debug(res.code.toString()) 453 | return res.code == 204 454 | } 455 | 456 | 457 | /** 458 | * 创建子频道公告 459 | * @param channelId 子频道id 460 | * @param messageId 消息id 461 | */ 462 | @JvmStatic 463 | fun createChannelAnnounces(channelId: String, messageId: String): Announces { 464 | val url = channelAnnounces.replace("{{channel_id}}", channelId) 465 | val json = "{\"message_id\": \"$messageId\"}" 466 | val res = OkHttpUtils.postJson(url, OkHttpUtils.addJson(json), officeApiHeader()) 467 | logger.debug(res) 468 | return Gson().fromJson(res, Announces::class.java) 469 | } 470 | 471 | /** 472 | * 删除子频道公告 473 | * @param channelId 子频道id 474 | * @param messageId 消息id 475 | */ 476 | @JvmStatic 477 | fun deleteChannelAnnounces(channelId: String, messageId: String): Boolean { 478 | val url = deleteChannelAnnounces.replace("{{channel_id}}", channelId).replace("{{message_id}}", messageId) 479 | val res = OkHttpUtils.delete(url, mutableMapOf(), officeApiHeader()) 480 | logger.debug(res.code.toString()) 481 | return res.code == 204 482 | } 483 | 484 | /** 485 | * 则返回结束时间在 since 之后的日程列表 486 | * @param channelId 子频道id 487 | * @param since 时间戳 488 | */ 489 | @JvmStatic 490 | fun getScheduleListByTime(channelId: String, since: Long): List { 491 | val url = schedules.replace("{{channel_id}}", channelId) 492 | url.plus("?since=$since") 493 | val res = OkHttpUtils.getJson(url, Headers.headersOf("Authorization", botToken!!)) 494 | logger.debug(res) 495 | if (res.contentEquals("null")) { 496 | return mutableListOf() 497 | } 498 | return Gson().fromJson(res, object : TypeToken>() {}.type) 499 | } 500 | 501 | /** 502 | * 返回当天的日程列表 503 | * @param channelId 子频道id 504 | */ 505 | @JvmStatic 506 | fun getScheduleList(channelId: String): List { 507 | val url = schedules.replace("{{channel_id}}", channelId) 508 | val res = OkHttpUtils.getJson(url, Headers.headersOf("Authorization", botToken!!)) 509 | logger.debug(res) 510 | if (res.contentEquals("null")) { 511 | return mutableListOf() 512 | } 513 | return Gson().fromJson(res, object : TypeToken>() {}.type) 514 | } 515 | 516 | /** 517 | * 获取单个日程信息 518 | * @param channelId 子频道id 519 | * @param scheduleId 日程id 520 | */ 521 | @JvmStatic 522 | fun getScheduleById(channelId: String, scheduleId: String): Schedule { 523 | val url = scheduleUrl.replace("{{channel_id}}", channelId).replace("{{schedule_id}}", scheduleId) 524 | val res = OkHttpUtils.getJson(url, Headers.headersOf("Authorization", botToken!!)) 525 | logger.debug(res) 526 | return Gson().fromJson(res, Schedule::class.java) 527 | } 528 | 529 | 530 | /** 531 | * 创建日程 532 | * 要求操作人具有管理频道的权限,如果是机器人,则需要将机器人设置为管理员。 533 | * 时间戳的值不允许创建开始时间早于现在的; 534 | * 结束时间戳不能超过创建开始的时间戳7天 535 | * @param channelId 子频道id 536 | * @param schedule 日程对象 537 | */ 538 | @JvmStatic 539 | fun createSchedule(channelId: String, schedule: Schedule): Schedule { 540 | val url = schedules.replace("{{channel_id}}", channelId) 541 | schedule.creator = null 542 | val json = "{\"schedule\": ${Gson().toJson(schedule)}}" 543 | val res = OkHttpUtils.postJson(url, OkHttpUtils.addJson(json), officeApiHeader()) 544 | logger.debug(res) 545 | return Gson().fromJson(res, Schedule::class.java) 546 | } 547 | 548 | /** 549 | * 修改日程 550 | * 要求操作人具有管理频道的权限,如果是机器人,则需要将机器人设置为管理员。 551 | * @param channelId 子频道id 552 | * @param scheduleId 日程id 553 | * @param schedule 日程对象,不需要带id 554 | */ 555 | @JvmStatic 556 | fun changeScheduleById(channelId: String, scheduleId: String, schedule: Schedule): Schedule { 557 | val url = scheduleUrl.replace("{{channel_id}}", channelId).replace("{{schedule_id}}", scheduleId) 558 | schedule.creator = null 559 | val json = "{\"schedule\": ${Gson().toJson(schedule)}}" 560 | val res = OkHttpUtils.patchJson(url, OkHttpUtils.addJson(json), officeApiHeader()) 561 | logger.debug(res) 562 | return Gson().fromJson(res, Schedule::class.java) 563 | } 564 | 565 | /** 566 | * 删除日程 567 | * 要求操作人具有管理频道的权限,如果是机器人,则需要将机器人设置为管理员。 568 | * @param channelId 子频道id 569 | * @param scheduleId 日程id 570 | */ 571 | @JvmStatic 572 | fun deleteScheduleById(channelId: String, scheduleId: String): Boolean { 573 | val url = scheduleUrl.replace("{{channel_id}}", channelId).replace("{{schedule_id}}", scheduleId) 574 | val res = OkHttpUtils.delete(url, mutableMapOf(), officeApiHeader()) 575 | logger.debug(res.code.toString()) 576 | return res.code == 204 577 | } 578 | 579 | 580 | /** 581 | * 撤回当前消息 582 | * 用来撤回频道内的消息 583 | * 管理员可以撤回普通成员的消息 584 | * 频道主可以撤回所有人的消息 585 | */ 586 | @JvmStatic 587 | fun recall(channelId:String,messageId: String): Boolean { 588 | val url = recall.replace("{{channel_id}}", channelId).replace("{{message_id}}", messageId) 589 | val res = OkHttpUtils.delete(url, mutableMapOf(), officeApiHeader()) 590 | logger.debug(res.code.toString()) 591 | return res.code == 200 592 | } 593 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/common/BotClient.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.common 2 | 3 | import com.hcyacg.protocol.constant.Constant.Companion.accessWithFragmentedWss 4 | import com.hcyacg.protocol.internal.config.IdentifyConfig 5 | 6 | open class BotClient( 7 | config: IdentifyConfig, 8 | officialEvents: List = emptyList(), 9 | uri: String = accessWithFragmentedWss!!.url, 10 | ) : SequelBotClient( 11 | uri = uri, 12 | BotListener(config, officialEvents) 13 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/common/BotEvent.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.common 2 | 3 | import com.hcyacg.protocol.constant.Constant.Companion.botToken 4 | import com.hcyacg.protocol.event.* 5 | import com.hcyacg.protocol.internal.config.IdentifyConfig 6 | import com.hcyacg.protocol.event.ReadyEvent 7 | import com.hcyacg.protocol.event.message.* 8 | import com.hcyacg.protocol.event.message.directMessage.DirectMessageCreateEvent 9 | 10 | /** 11 | * 频道事件 12 | */ 13 | abstract class BotEvent { 14 | 15 | 16 | open fun getToken(): String? { 17 | return botToken 18 | } 19 | 20 | open suspend fun onReady(event: ReadyEvent) {} 21 | 22 | open suspend fun onGuildMemberAdd(event: GuildMemberEvent) {} 23 | 24 | open suspend fun onGuildMemberUpdate(event: GuildMemberEvent) {} 25 | 26 | open suspend fun onGuildMemberRemove(event: GuildMemberEvent) {} 27 | 28 | open suspend fun onAtMessageCreate(event: AtMessageCreateEvent) {} 29 | 30 | open suspend fun onMessageCreate(event: MessageCreateEvent) {} 31 | 32 | open suspend fun onDirectMessageCreate(event: DirectMessageCreateEvent) {} 33 | 34 | open suspend fun onChannelCreate(event: ChannelEvent) {} 35 | 36 | open suspend fun onChannelUpdate(event: ChannelEvent) {} 37 | 38 | open suspend fun onChannelDelete(event: ChannelEvent) {} 39 | 40 | open suspend fun onGuildCreate(event: GuildEvent) {} 41 | 42 | open suspend fun onGuildUpdate(event: GuildEvent) {} 43 | 44 | open suspend fun onGuildDelete(event: GuildEvent) {} 45 | 46 | open suspend fun onResumed(config: IdentifyConfig, sessionId: String) {} 47 | 48 | open suspend fun onMessageReactionAdd(event: MessageReactionEvent) {} 49 | 50 | open suspend fun onMessageReactionRemove(event: MessageReactionEvent) {} 51 | 52 | open suspend fun onMessageAuditPass(event: MessageAuditPassEvent) {} 53 | 54 | open suspend fun onMessageAuditReject(event: MessageAuditRejectEvent) {} 55 | 56 | open suspend fun onAudioStart(event: AudioActionEvent) {} 57 | 58 | open suspend fun onAudioFinish(event: AudioActionEvent) {} 59 | 60 | open suspend fun onAudioOnMic(event: AudioActionEvent) {} 61 | 62 | open suspend fun onAudioOffMic(event: AudioActionEvent) {} 63 | 64 | //TODO 官方有许多事件,后续在这里添加事件名称 65 | 66 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/common/BotListener.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.common 2 | 3 | 4 | import com.google.gson.Gson 5 | import com.google.gson.reflect.TypeToken 6 | import com.hcyacg.protocol.constant.Constant.Companion.logger 7 | import com.hcyacg.protocol.constant.Constant.Companion.threadLocal 8 | import com.hcyacg.protocol.event.* 9 | import com.hcyacg.protocol.internal.BaseBotListener 10 | import com.hcyacg.protocol.internal.config.IdentifyConfig 11 | import com.hcyacg.protocol.internal.entity.* 12 | import com.hcyacg.protocol.internal.enums.DispatchEnums 13 | import com.hcyacg.protocol.internal.enums.OPCodeEnums.* 14 | import com.hcyacg.protocol.utils.ScheduleUtils 15 | import okhttp3.Response 16 | import okhttp3.WebSocket 17 | import com.hcyacg.protocol.event.ReadyEvent 18 | import com.hcyacg.protocol.event.message.* 19 | import com.hcyacg.protocol.event.message.directMessage.DirectMessageCreateEvent 20 | import com.hcyacg.protocol.internal.enums.OPCodeEnums 21 | import kotlinx.coroutines.* 22 | import java.util.* 23 | import java.util.concurrent.atomic.AtomicBoolean 24 | import java.util.concurrent.atomic.AtomicLong 25 | 26 | class BotListener( 27 | private val config: IdentifyConfig, 28 | private val officialEvents: List = emptyList(), 29 | private val heartbeatDelay: Long = 30000, 30 | private val reconnectTimeout: Long = 60000, 31 | ) : BaseBotListener() { 32 | private val identifyOpDto = Gson().toJson(Identify(config.toIdentifyOperationData())) 33 | var sessionId: String = "" 34 | 35 | private val logHeader = "${config.index + 1} of ${config.shards}" 36 | 37 | private var hbTimer: Timer? = null 38 | private val messageSeq by lazy { AtomicLong(0) } 39 | private val lastReceivedHeartBeat = AtomicLong(0) 40 | private val isResume = AtomicBoolean(false) 41 | 42 | 43 | override fun onOpen(webSocket: WebSocket, response: Response) { 44 | logger.info("$logHeader 正在连接中 ") 45 | } 46 | 47 | inline fun Gson.fromJson(json: String) = this.fromJson(json, object : TypeToken() {}.type) 48 | 49 | 50 | @OptIn(DelicateCoroutinesApi::class) 51 | override fun onMessage(webSocket: WebSocket, text: String) { 52 | runCatching { 53 | // logger.trace("$logHeader 收到了信息 $text") 54 | Gson().fromJson(text, Operation::class.java)?.also { opType -> 55 | 56 | when (OPCodeEnums.getOPCodeByCode(opType.op)) { 57 | HEARTBEAT_ACK -> { 58 | lastReceivedHeartBeat.getAndSet(System.currentTimeMillis()) 59 | } 60 | //首次连接 发送Identify信息鉴权 61 | HELLO -> { 62 | // 初始化操作 63 | initConnection(webSocket) 64 | } 65 | //收到事件 66 | DISPATCH -> { 67 | Gson().fromJson(text, DispatchType::class.java)?.also { dispatchDto -> 68 | successConnect(dispatchDto) 69 | // logger.debug("$logHeader 收到了事件:${dispatchDto.type} 内容:$text") 70 | when (dispatchDto.type) { 71 | 72 | DispatchEnums.READY -> { 73 | 74 | Gson().fromJson>(text)?.also { readyEvent -> 75 | sessionId = readyEvent.d.sessionId 76 | officialEvents.forEach { 77 | runBlocking { 78 | it.onReady(readyEvent.d) 79 | } 80 | } 81 | 82 | } 83 | 84 | } 85 | DispatchEnums.GUILD_MEMBER_ADD -> { 86 | Gson().fromJson>(text)?.also { guildMemberEvent -> 87 | officialEvents.forEach { 88 | runBlocking { 89 | it.onGuildMemberAdd(guildMemberEvent.d) 90 | } 91 | } 92 | 93 | } 94 | } 95 | DispatchEnums.GUILD_MEMBER_UPDATE -> { 96 | Gson().fromJson>(text)?.also { guildMemberEvent -> 97 | officialEvents.forEach { 98 | runBlocking { 99 | it.onGuildMemberUpdate(guildMemberEvent.d) 100 | } 101 | } 102 | } 103 | } 104 | DispatchEnums.GUILD_MEMBER_REMOVE -> { 105 | Gson().fromJson>(text)?.also { guildMemberEvent -> 106 | officialEvents.forEach { 107 | runBlocking { 108 | it.onGuildMemberRemove(guildMemberEvent.d) 109 | } 110 | } 111 | } 112 | } 113 | DispatchEnums.AT_MESSAGE_CREATE -> { 114 | Gson().fromJson>(text)?.also { guildAtMessage -> 115 | officialEvents.forEach { 116 | GlobalScope.launch(threadLocal.asContextElement(guildAtMessage.d.guildId)) { 117 | it.onAtMessageCreate(guildAtMessage.d) 118 | } 119 | } 120 | } 121 | } 122 | DispatchEnums.MESSAGE_CREATE -> { 123 | Gson().fromJson>(text)?.also { guildAtMessage -> 124 | officialEvents.forEach { 125 | GlobalScope.launch(threadLocal.asContextElement(guildAtMessage.d.guildId)) { 126 | it.onMessageCreate(guildAtMessage.d) 127 | } 128 | } 129 | } 130 | } 131 | DispatchEnums.CHANNEL_CREATE -> { 132 | Gson().fromJson>(text)?.also { channelEvent -> 133 | officialEvents.forEach { 134 | runBlocking { 135 | it.onChannelCreate(channelEvent.d) 136 | } 137 | } 138 | } 139 | } 140 | DispatchEnums.CHANNEL_UPDATE -> { 141 | Gson().fromJson>(text)?.also { channelEvent -> 142 | officialEvents.forEach { 143 | runBlocking { 144 | it.onChannelUpdate(channelEvent.d) 145 | } 146 | } 147 | } 148 | } 149 | DispatchEnums.CHANNEL_DELETE -> { 150 | Gson().fromJson>(text)?.also { channelEvent -> 151 | officialEvents.forEach { 152 | runBlocking { 153 | it.onChannelDelete(channelEvent.d) 154 | } 155 | } 156 | } 157 | } 158 | DispatchEnums.GUILD_CREATE -> { 159 | Gson().fromJson>(text)?.also { guildEvent -> 160 | officialEvents.forEach { 161 | runBlocking { 162 | it.onGuildCreate(guildEvent.d) 163 | } 164 | } 165 | } 166 | } 167 | DispatchEnums.GUILD_UPDATE -> { 168 | Gson().fromJson>(text)?.also { guildEvent -> 169 | officialEvents.forEach { 170 | runBlocking { 171 | it.onGuildUpdate(guildEvent.d) 172 | } 173 | } 174 | 175 | } 176 | } 177 | DispatchEnums.GUILD_DELETE -> { 178 | Gson().fromJson>(text)?.also { guildEvent -> 179 | officialEvents.forEach { 180 | runBlocking { 181 | it.onGuildDelete(guildEvent.d) 182 | } 183 | } 184 | } 185 | } 186 | DispatchEnums.RESUMED -> { 187 | officialEvents.forEach { runBlocking { it.onResumed(config, sessionId) } } 188 | } 189 | DispatchEnums.MESSAGE_REACTION_ADD -> { 190 | Gson().fromJson>(text) 191 | ?.also { messageReactionEvent -> 192 | officialEvents.forEach { 193 | runBlocking { 194 | it.onMessageReactionAdd(messageReactionEvent.d) 195 | } 196 | } 197 | 198 | 199 | } 200 | } 201 | DispatchEnums.MESSAGE_REACTION_REMOVE -> { 202 | Gson().fromJson>(text) 203 | ?.also { messageReactionEvent -> 204 | officialEvents.forEach { 205 | runBlocking { 206 | it.onMessageReactionRemove(messageReactionEvent.d) 207 | } 208 | } 209 | 210 | } 211 | } 212 | DispatchEnums.DIRECT_MESSAGE_CREATE -> { 213 | Gson().fromJson>(text) 214 | ?.also { directMessageCreateEvent -> 215 | officialEvents.forEach { 216 | runBlocking { 217 | it.onDirectMessageCreate(directMessageCreateEvent.d) 218 | } 219 | } 220 | 221 | } 222 | logger.debug("$logHeader 收到了事件:${dispatchDto.type} 内容:$text") 223 | } 224 | DispatchEnums.THREAD_CREATE -> { 225 | logger.debug("$logHeader 收到了事件:${dispatchDto.type} 内容:$text") 226 | } 227 | DispatchEnums.THREAD_UPDATE -> { 228 | logger.debug("$logHeader 收到了事件:${dispatchDto.type} 内容:$text") 229 | } 230 | DispatchEnums.THREAD_DELETE -> { 231 | logger.debug("$logHeader 收到了事件:${dispatchDto.type} 内容:$text") 232 | } 233 | DispatchEnums.POST_CREATE -> { 234 | logger.debug("$logHeader 收到了事件:${dispatchDto.type} 内容:$text") 235 | } 236 | DispatchEnums.POST_DELETE -> { 237 | logger.debug("$logHeader 收到了事件:${dispatchDto.type} 内容:$text") 238 | } 239 | DispatchEnums.REPLY_CREATE -> { 240 | logger.debug("$logHeader 收到了事件:${dispatchDto.type} 内容:$text") 241 | } 242 | DispatchEnums.REPLY_DELETE -> { 243 | logger.debug("$logHeader 收到了事件:${dispatchDto.type} 内容:$text") 244 | } 245 | DispatchEnums.AUDIO_START -> { 246 | Gson().fromJson>(text)?.also { audioActionEvent -> 247 | officialEvents.forEach { 248 | runBlocking { 249 | it.onAudioStart(audioActionEvent.d) 250 | } 251 | } 252 | } 253 | } 254 | DispatchEnums.AUDIO_FINISH -> { 255 | Gson().fromJson>(text)?.also { audioActionEvent -> 256 | officialEvents.forEach { 257 | runBlocking { 258 | it.onAudioFinish(audioActionEvent.d) 259 | } 260 | } 261 | } 262 | } 263 | DispatchEnums.AUDIO_ON_MIC -> { 264 | Gson().fromJson>(text)?.also { audioActionEvent -> 265 | officialEvents.forEach { 266 | runBlocking { 267 | it.onAudioOnMic(audioActionEvent.d) 268 | } 269 | } 270 | } 271 | } 272 | DispatchEnums.AUDIO_OFF_MIC -> { 273 | Gson().fromJson>(text)?.also { audioActionEvent -> 274 | officialEvents.forEach { 275 | runBlocking { 276 | it.onAudioOffMic(audioActionEvent.d) 277 | } 278 | } 279 | } 280 | } 281 | DispatchEnums.MESSAGE_AUDIT_PASS -> { 282 | Gson().fromJson>(text)?.also { messageAuditPassEvent -> 283 | officialEvents.forEach { 284 | runBlocking { 285 | it.onMessageAuditPass(messageAuditPassEvent.d) 286 | } 287 | } 288 | } 289 | } 290 | DispatchEnums.MESSAGE_AUDIT_REJECT -> { 291 | Gson().fromJson>(text)?.also { messageAuditRejectEvent -> 292 | officialEvents.forEach { 293 | runBlocking { 294 | it.onMessageAuditReject(messageAuditRejectEvent.d) 295 | } 296 | } 297 | } 298 | } 299 | else -> { 300 | logger.warn("$logHeader 未知的事件! 信息:$text") 301 | } 302 | } 303 | } 304 | } 305 | RECONNECT -> { 306 | logger.warn("$logHeader 需要重连!") 307 | isResume.getAndSet(true) 308 | reconnectClient() 309 | } 310 | INVALID_SESSION -> { 311 | logger.error("$logHeader 错误:", INVALID_SESSION.description) 312 | failureConnect(webSocket) 313 | 314 | } 315 | HEARTBEAT -> TODO() 316 | IDENTIFY -> TODO() 317 | RESUME -> TODO() 318 | UNKNOWN -> TODO() 319 | } 320 | 321 | } 322 | }.onFailure { 323 | it.printStackTrace() 324 | } 325 | } 326 | 327 | private fun failureConnect(webSocket: WebSocket) { 328 | if (isResume.get()) { 329 | isResume.set(false) 330 | reconnectClient() 331 | } else { 332 | webSocket.cancel() 333 | hbTimer?.cancel() 334 | throw RuntimeException(INVALID_SESSION.description) 335 | } 336 | } 337 | 338 | private fun successConnect(dispatchDto: DispatchType) { 339 | messageSeq.getAndSet(dispatchDto.seq) 340 | isResume.set(false) 341 | } 342 | 343 | override fun onClosing(webSocket: WebSocket, code: Int, reason: String) { 344 | logger.warn("$logHeader 正在关闭") 345 | } 346 | 347 | override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { 348 | logger.warn("$logHeader 已关闭") 349 | } 350 | 351 | override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { 352 | logger.warn("$logHeader 尝试重新连接") 353 | reconnectClient() 354 | } 355 | 356 | private fun initConnection(webSocket: WebSocket) { 357 | // 鉴权 358 | if (isResume.get()) { 359 | val resume = Resume(ResumeData(messageSeq.get(), sessionId, config.token)) 360 | webSocket.sendAndPrintLog(Gson().toJson(resume)) 361 | } else { 362 | webSocket.sendAndPrintLog(identifyOpDto) 363 | } 364 | // 启动心跳发送 365 | lastReceivedHeartBeat.getAndSet(System.currentTimeMillis()) 366 | val processor = createHeartBeatProcessor(webSocket) 367 | // 先取消以前的定时器 368 | hbTimer?.cancel() 369 | // 启动新的心跳 370 | hbTimer = ScheduleUtils.loopEvent(processor, Date(), heartbeatDelay) 371 | } 372 | 373 | private fun createHeartBeatProcessor(webSocket: WebSocket): suspend () -> Unit { 374 | return suspend { 375 | val last = lastReceivedHeartBeat.get() 376 | val now = System.currentTimeMillis() 377 | if (now - last > reconnectTimeout) { 378 | logger.warn("$logHeader 心跳超时,尝试重新连接") 379 | reconnectClient() 380 | } else { 381 | val hb = Gson().toJson(Heartbeat(messageSeq.get())) 382 | webSocket.sendAndPrintLog(hb, true) 383 | } 384 | } 385 | } 386 | 387 | private fun reconnectClient() { 388 | hbTimer?.cancel() 389 | reconnect() 390 | } 391 | 392 | private fun WebSocket.sendAndPrintLog(text: String, isHeartbeat: Boolean = false) { 393 | // if (isHeartbeat) { 394 | // logger.debug("$logHeader 发送心跳包 $text") 395 | // } else { 396 | // logger.info("$logHeader 发送信息 $text") 397 | // } 398 | this.send(text) 399 | } 400 | 401 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/common/BotManager.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.common 2 | 3 | import com.hcyacg.protocol.constant.Constant.Companion.botToken 4 | import com.hcyacg.protocol.internal.config.IdentifyConfig 5 | import com.hcyacg.protocol.internal.config.Intents 6 | import kotlinx.coroutines.runBlocking 7 | 8 | object BotManager { 9 | 10 | private var isPrivate: Boolean = false 11 | private lateinit var token: String 12 | 13 | 14 | /** 15 | * 默认公域 16 | */ 17 | private var intent: Intents = Intents( 18 | guilds = true, 19 | guildMembers = true, 20 | directMessage = false, 21 | audioAction = true, 22 | atMessages = true, 23 | messages = false, 24 | forum = false, 25 | guildMessageReactions = false, 26 | messageAudit = true 27 | ) 28 | 29 | @JvmStatic 30 | fun configuration(token:String, isPrivate:Boolean):BotManager{ 31 | this.token = token 32 | this.isPrivate = isPrivate 33 | 34 | botToken = token 35 | 36 | /** 37 | * 判断是否是私域 38 | */ 39 | if (BotManager.isPrivate) { 40 | intent = Intents( 41 | guilds = true, 42 | guildMembers = true, 43 | directMessage = true, 44 | audioAction = true, 45 | atMessages = true, 46 | messages = true, 47 | forum = false, 48 | guildMessageReactions = true, 49 | messageAudit = true 50 | ) 51 | } 52 | return this 53 | } 54 | 55 | 56 | @JvmStatic 57 | fun addListen(vararg listener: BotEvent) { 58 | val list = listener.toMutableList() 59 | list.add(MonitorMessage()) 60 | val gatewayAccessWithFragmentedWss = Gateway.gatewayAccessWithFragmentedWss(botToken!!) 61 | 62 | for (i in 0 until gatewayAccessWithFragmentedWss!!.shards) { 63 | runBlocking { 64 | BotClient(IdentifyConfig(botToken!!, gatewayAccessWithFragmentedWss.shards, i, intent), list) 65 | } 66 | } 67 | 68 | } 69 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/common/ExcepetionHandler.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.common 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.JsonParser 5 | import okhttp3.Response 6 | 7 | internal class ExceptionHandler(response: Response) { 8 | 9 | init { 10 | val regex = Regex(pattern = "20[0-9]") 11 | if (regex.containsMatchIn(response.code.toString())) { 12 | var traceId = response.headers["X-Tps-trace-ID"] 13 | val message = exceptionCode(response.code) 14 | }else { 15 | val result = response.body?.string() 16 | val code = JsonParser.parseString(result).asJsonObject.get("code").asInt 17 | val message = exceptionCode(code) 18 | } 19 | } 20 | 21 | 22 | private fun exceptionCode(code: Int): String? { 23 | when (code) { 24 | 401 -> return "认证失败" 25 | 404 -> return "未找到 API" 26 | 405 -> return "http method 不允许" 27 | 429 -> return "频率限制" 28 | 500 -> return "处理失败" 29 | 504 -> return "处理失败" 30 | 10001 -> return "UnknownAccount 账号异常" 31 | 10003 -> return "UnknownChannel 子频道异常" 32 | 10004 -> return "UnknownGuild 频道异常" 33 | 11281 -> return "ErrorCheckAdminFailed 检查是否是管理员失败,系统错误,一般重试一次会好,最多只能重试一次" 34 | 11282 -> return "ErrorCheckAdminNotPass 检查是否是管理员未通过,该接口需要管理员权限,但是用户在添加机器人的时候并未授予该权限,属于逻辑错误,可以提示用户进行授权" 35 | 11251 -> return "ErrorWrongAppid 参数中的 appid 错误,开发者填的 token 错误,appid 无法识别" 36 | 11252 -> return "ErrorCheckAppPrivilegeFailed 检查应用权限失败,系统错误,一般重试一次会好,最多只能重试一次" 37 | 11253 -> return "ErrorCheckAppPrivilegeNotPass 检查应用权限不通过,该机器人应用未获得调用该接口的权限,需要向平台申请" 38 | 11254 -> return "ErrorInterfaceForbidden 应用接口被封禁,该机器人虽然获得了该接口权限,但是被封禁了。" 39 | 11261 -> return "ErrorWrongAppid 参数中缺少 appid,同 11251" 40 | 11262 -> return "ErrorCheckRobot 当前接口不支持使用机器人 Bot Token 调用" 41 | 11263 -> return "ErrorCheckGuildAuth 检查频道权限失败,系统错误,一般重试一次会好,最多只能重试一次" 42 | 11264 -> return "ErrorGuildAuthNotPass 检查小站权限未通过,管理员添加机器人的时候未授予该接口权限,属于逻辑错误,可提示用户进行授权" 43 | 11265 -> return "ErrorRobotHasBaned 机器人已经被封禁" 44 | 11241 -> return "ErrorWrongToken 参数中缺少 token" 45 | 11242 -> return "ErrorCheckTokenFailed 校验 token 失败,系统错误,一般重试一次会好,最多只能重试一次" 46 | 11243 -> return "ErrorCheckTokenNotPass 校验 token 未通过,用户填充的 token 错误,需要开发者进行检查" 47 | 11273 -> return "ErrorCheckUserAuth 检查用户权限失败,当前接口不支持使用 Bearer Token 调用" 48 | 11274 -> return "ErrorUserAuthNotPass 检查用户权限未通过,用户 OAuth 授权时未给与该接口权限,可提示用户重新进行授权" 49 | 11275 -> return "ErrorWrongAppid 无 appid ,同 11251" 50 | 12001 -> return "ReplaceIDFailed 替换 id 失败" 51 | 12002 -> return "RequestInvalid 请求体错误" 52 | 12003 -> return "ResponseInvalid 回包错误" 53 | 20028 -> return "ChannelHitWriteRateLimit 子频道消息触发限频" 54 | 50006 -> return "CannotSendEmptyMessage 消息为空" 55 | 50035 -> return "InvalidFormBody form-data 内容异常" 56 | 301000 -> return "参数错误" 57 | 301001 -> return "查询频道信息错误" 58 | 301002 -> return "查询子频道权限错误" 59 | 301003 -> return "修改子频道权限错误" 60 | 301004 -> return "私密子频道关联的人数到达上限" 61 | 301005 -> return "调用Rpc服务失败" 62 | 301006 -> return "非群成员没有查询权限" 63 | 301007 -> return "参数超过数量限制" 64 | 65 | 502001 -> return "频道id无效" 66 | 502002 -> return "频道id为空" 67 | 502003 -> return "用户id无效" 68 | 502004 -> return "用户id为空" 69 | 502005 -> return "timestamp不合法" 70 | 502006 -> return "timestamp无效" 71 | 502007 -> return "参数转换错误" 72 | 502008 -> return "rpc调用失败" 73 | 502009 -> return "安全打击" 74 | 502010 -> return "请求头错误" 75 | 304003 -> return "URL_NOT_ALLOWED url 未报备" 76 | 304004 -> return "ARK_NOT_ALLOWED 没有发 ark 消息权限" 77 | 304005 -> return "EMBED_LIMIT embed 长度超限" 78 | 304006 -> return "SERVER_CONFIG 后台配置错误" 79 | 304007 -> return "GET_GUILD 查询频道异常" 80 | 304008 -> return "GET_BOT 查询机器人异常" 81 | 304009 -> return "GET_CHENNAL 查询子频道异常" 82 | 304010 -> return "CHANGE_IMAGE_URL 图片转存错误" 83 | 304011 -> return "NO_TEMPLATE 模板不存在" 84 | 304012 -> return "GET_TEMPLATE 取模板错误" 85 | 304014 -> return "ARK_PRIVILEGE 没有模板权限" 86 | 304016 -> return "SEND_ERROR 发消息错误" 87 | 304017 -> return "UPLOAD_IMAGE 图片上传错误" 88 | 304018 -> return "SESSION_NOT_EXIST 机器人没连上 gateway" 89 | 304019 -> return "AT_EVERYONE_TIMES @全体成员 次数超限" 90 | 304020 -> return "FILE_SIZE 文件大小超限" 91 | 304021 -> return "GET_FILE 下载文件错误" 92 | 304022 -> return "PUSH_TIME 推送消息时间限制" 93 | 304023 -> return "PUSH_MSG_ASYNC_OK 推送消息异步调用成功, 等待人工审核" 94 | 304024 -> return "REPLY_MSG_ASYNC_OK 回复消息异步调用成功, 等待人工审核" 95 | 304025 -> return "BEAT 消息被打击" 96 | 304026 -> return "MSG_ID 回复的消息 id 错误" 97 | 304027 -> return "MSG_EXPIRE 回复的消息过期" 98 | 304028 -> return "MSG_PROTECT 非 At 当前用户的消息不允许回复" 99 | 304029 -> return "CORPUS_ERROR 调语料服务错误" 100 | 304030 -> return "CORPUS_NOT_MATCH 语料不匹配" 101 | 306001 -> return "param invalid 撤回消息参数错误" 102 | 306002 -> return "msgid error 消息id错误" 103 | 306003 -> return "fail to get message 获取消息错误(可重试)" 104 | 306004 -> return "no permission to delete message 没有撤回此消息的权限" 105 | 306005 -> return "retract message error 消息撤回失败(可重试)" 106 | 306006 -> return "fail to get channel 获取子频道失败(可重试)" 107 | 108 | 501001 -> return "参数校验失败" 109 | 501002 -> return "创建子频道公告失败(可重试)" 110 | 501003 -> return "删除子频道公告失败(可重试)" 111 | 501004 -> return "获取频道信息失败(可重试)" 112 | 501005 -> return "MessageID 错误" 113 | 501006 -> return "创建频道全局公告失败(可重试)" 114 | 501007 -> return "删除频道全局公告失败(可重试)" 115 | 501008 -> return "MessageID 不存在" 116 | 1100100 -> return "安全打击:消息被限频" 117 | 1100101 -> return "安全打击:内容涉及敏感,请返回修改" 118 | 1100102 -> return "安全打击:抱歉,暂未获得新功能体验资格" 119 | 1100103 -> return "安全打击" 120 | 1100104 -> return "安全打击:该群已失效或当前群已不存在" 121 | 1100300 -> return "系统内部错误" 122 | 1100301 -> return "调用方不是群成员" 123 | 1100302 -> return "获取指定频道名称失败" 124 | 1100303 -> return "主页频道非管理员不允许发消息" 125 | 1100304 -> return "@次数鉴权失败" 126 | 1100305 -> return "TinyId 转换 Uin 失败" 127 | 1100306 -> return "非私有频道成员" 128 | 1100307 -> return "非白名单应用子频道" 129 | 1100308 -> return "触发频道内限频" 130 | 1100499 -> return "其他错误" 131 | } 132 | return null 133 | } 134 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/common/Gateway.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.common 2 | 3 | 4 | import com.google.gson.Gson 5 | import com.hcyacg.protocol.constant.Constant 6 | import com.hcyacg.protocol.constant.Constant.Companion.accessWithFragmentedWss 7 | import com.hcyacg.protocol.constant.Constant.Companion.logger 8 | import com.hcyacg.protocol.internal.entity.AccessWithFragmentedWss 9 | import okhttp3.Headers 10 | import okhttp3.OkHttpClient 11 | import okhttp3.Request 12 | import okhttp3.RequestBody 13 | import java.util.concurrent.TimeUnit 14 | 15 | class Gateway { 16 | 17 | companion object { 18 | private val headers = Headers.Builder() 19 | private var requestBody: RequestBody? = null 20 | 21 | private val client: OkHttpClient by lazy { 22 | OkHttpClient.Builder() 23 | .readTimeout(30, TimeUnit.SECONDS) 24 | .writeTimeout(30, TimeUnit.SECONDS) 25 | .connectTimeout(30, TimeUnit.SECONDS) 26 | .build() 27 | } 28 | 29 | /** 30 | * 获取wss的地址以及相关数据 封装成对象 31 | */ 32 | fun gatewayAccessWithFragmentedWss(token: String): AccessWithFragmentedWss? { 33 | if (null != accessWithFragmentedWss) { 34 | return accessWithFragmentedWss 35 | } 36 | headers.add("Authorization", token) 37 | val request: Request by lazy { 38 | Request.Builder().get().url(Constant.proUrl + "/gateway/bot").header("Authorization", token).build() 39 | } 40 | val execute = client.newCall(request).execute() 41 | val string = execute.body!!.string() 42 | logger.debug(string) 43 | accessWithFragmentedWss = Gson().fromJson(string, AccessWithFragmentedWss::class.java) 44 | logger.info("wss地址已获取 ${accessWithFragmentedWss!!.url}") 45 | execute.close() 46 | return accessWithFragmentedWss 47 | } 48 | 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/common/MonitorMessage.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.common 2 | 3 | import com.google.gson.Gson 4 | import com.hcyacg.protocol.constant.Constant 5 | import com.hcyacg.protocol.constant.Constant.Companion.bot 6 | import com.hcyacg.protocol.constant.Constant.Companion.logger 7 | import com.hcyacg.protocol.event.* 8 | import com.hcyacg.protocol.event.message.* 9 | import com.hcyacg.protocol.event.message.directMessage.DirectMessageCreateEvent 10 | 11 | 12 | internal class MonitorMessage : BotEvent() { 13 | 14 | override suspend fun onReady(event: ReadyEvent) { 15 | bot = event.user 16 | logger.info("${event.shard[0] + 1} of ${event.shard[1]} 已连接") 17 | } 18 | 19 | override suspend fun onGuildMemberAdd(event: GuildMemberEvent) { 20 | logger.info("${event.user.username}(${event.user.id}) 加入了 ${BotApi.getGuildById(event.guildId).name}(${event.guildId})") 21 | } 22 | 23 | override suspend fun onGuildMemberUpdate(event: GuildMemberEvent) {} 24 | 25 | override suspend fun onGuildMemberRemove(event: GuildMemberEvent) { 26 | logger.info("${event.user.username}(${event.user.id}) 退出了 ${BotApi.getGuildById(event.guildId).name}(${event.guildId})") 27 | } 28 | 29 | override suspend fun onAtMessageCreate(event: AtMessageCreateEvent) { 30 | logger.info( 31 | "${BotApi.getGuildById(event.guildId).name}(${event.guildId}) - ${ 32 | BotApi.getChannelInfo( 33 | event.channelId 34 | ).name 35 | }(${event.channelId}) - ${event.author.username}(${event.author.id}): ${event.content}" 36 | ) 37 | } 38 | 39 | override suspend fun onMessageCreate(event: MessageCreateEvent) { 40 | if (event.isContentInitialized() && event.content.indexOf("<@!${bot!!.id}>") < 0) { 41 | logger.info( 42 | "${BotApi.getGuildById(event.guildId).name}(${event.guildId}) - ${ 43 | BotApi.getChannelInfo( 44 | event.channelId 45 | ).name 46 | }(${event.channelId}) - ${event.author.username}(${event.author.id}):${ 47 | if (null != event.attachments && event.attachments.isNotEmpty()) Gson().toJson( 48 | event.attachments 49 | ) else "" 50 | } ${event.content}" 51 | ) 52 | } 53 | } 54 | 55 | override suspend fun onDirectMessageCreate(event: DirectMessageCreateEvent) { 56 | if (event.isContentInitialized() && event.content.indexOf("<@!${bot!!.id}>") < 0) { 57 | logger.info( 58 | "${event.author.username}(${event.author.id}): ${event.content}" 59 | ) 60 | } 61 | } 62 | 63 | override suspend fun onChannelCreate(event: ChannelEvent) { 64 | logger.info("${BotApi.getGuildById(event.guildId).name}(${event.guildId}) - ${event.name}(${event.id}) 子频道被创建") 65 | } 66 | 67 | override suspend fun onChannelUpdate(event: ChannelEvent) { 68 | logger.info("${BotApi.getGuildById(event.guildId).name}(${event.guildId}) - ${event.name}(${event.id}) 子频道信息已被修改") 69 | } 70 | 71 | override suspend fun onChannelDelete(event: ChannelEvent) { 72 | logger.info("${BotApi.getGuildById(event.guildId).name}(${event.guildId}) - ${event.name}(${event.id}) 子频道被删除") 73 | } 74 | 75 | override suspend fun onGuildCreate(event: GuildEvent) { 76 | logger.info("机器人加入了 ${event.name}(${event.id}) 介绍: ${event.description}") 77 | } 78 | 79 | override suspend fun onGuildUpdate(event: GuildEvent) { 80 | logger.info("${event.name}(${event.id}) 频道信息变更") 81 | 82 | } 83 | 84 | override suspend fun onGuildDelete(event: GuildEvent) { 85 | logger.info("机器人离开了 ${event.name}(${event.id})") 86 | } 87 | 88 | override suspend fun onMessageReactionAdd(event: MessageReactionEvent) { 89 | val memberInfo = BotApi.getMemberInfo(event.guildId, event.userId) 90 | logger.info( 91 | "${BotApi.getGuildById(event.guildId).name}(${event.guildId}) - ${ 92 | BotApi.getChannelInfo( 93 | event.channelId 94 | ).name 95 | }(${event.channelId}) - ${memberInfo.user!!.username}(${event.userId}): 添加了表情表态对象: ${event.emoji} ${ 96 | getInfo( 97 | event.emoji.id 98 | ) 99 | }" 100 | ) 101 | } 102 | 103 | override suspend fun onMessageReactionRemove(event: MessageReactionEvent) { 104 | val memberInfo = BotApi.getMemberInfo(event.guildId, event.userId) 105 | logger.info( 106 | "${BotApi.getGuildById(event.guildId).name}(${event.guildId}) - ${ 107 | BotApi.getChannelInfo( 108 | event.channelId 109 | ).name 110 | }(${event.channelId}) - ${memberInfo.user!!.username}(${event.userId}): 删除了表情表态对象: ${event.emoji} ${ 111 | getInfo( 112 | event.emoji.id 113 | ) 114 | }" 115 | ) 116 | } 117 | 118 | 119 | override suspend fun onAudioStart(event: AudioActionEvent) { 120 | logger.info( 121 | "${BotApi.getGuildById(event.guildId).name}(${event.guildId}) - ${ 122 | BotApi.getChannelInfo( 123 | event.channelId 124 | ).name 125 | }(${event.channelId}) - ${event.text} -${event.audioUrl}" 126 | ) 127 | } 128 | 129 | override suspend fun onAudioFinish(event: AudioActionEvent) { 130 | logger.info( 131 | "${BotApi.getGuildById(event.guildId).name}(${event.guildId}) - ${ 132 | BotApi.getChannelInfo( 133 | event.channelId 134 | ).name 135 | }(${event.channelId}) - ${event.text} -${event.audioUrl}" 136 | ) 137 | } 138 | 139 | override suspend fun onAudioOnMic(event: AudioActionEvent) { 140 | logger.info( 141 | "${BotApi.getGuildById(event.guildId).name}(${event.guildId}) - ${ 142 | BotApi.getChannelInfo( 143 | event.channelId 144 | ).name 145 | }(${event.channelId}) - 上麦了" 146 | ) 147 | 148 | } 149 | 150 | override suspend fun onAudioOffMic(event: AudioActionEvent) { 151 | logger.info( 152 | "${BotApi.getGuildById(event.guildId).name}(${event.guildId}) - ${ 153 | BotApi.getChannelInfo( 154 | event.channelId 155 | ).name 156 | }(${event.channelId}) - 下麦了" 157 | ) 158 | 159 | } 160 | 161 | override suspend fun onMessageAuditPass(event: MessageAuditPassEvent) { 162 | logger.info("${event.createTime} 发送的消息未通过审核") 163 | } 164 | 165 | override suspend fun onMessageAuditReject(event: MessageAuditRejectEvent) { 166 | logger.info("${event.createTime} 发送的消息通过审核") 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/common/SequelBotClient.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.common 2 | 3 | import com.hcyacg.protocol.constant.Constant.Companion.logger 4 | import com.hcyacg.protocol.internal.BaseBotClient 5 | import com.hcyacg.protocol.internal.BaseBotListener 6 | import okhttp3.OkHttpClient 7 | import okhttp3.Request 8 | import java.util.concurrent.TimeUnit 9 | 10 | /** 11 | * 中续 机器人客户端 12 | */ 13 | open class SequelBotClient( 14 | private val uri: String, 15 | private val listener: T 16 | ) : BaseBotClient { 17 | private val client: OkHttpClient by lazy { 18 | OkHttpClient.Builder() 19 | .readTimeout(30, TimeUnit.SECONDS) 20 | .writeTimeout(30, TimeUnit.SECONDS) 21 | .connectTimeout(30, TimeUnit.SECONDS) 22 | .build() 23 | } 24 | private val request: Request by lazy { Request.Builder().get().url(uri).build() } 25 | 26 | private var connectWebSocket = client.newWebSocket(request, listener) 27 | 28 | init { 29 | listener.reconnect { reconnect() } 30 | } 31 | 32 | override fun reconnect() { 33 | connectWebSocket.close(1000, "reconnect") 34 | logger.info("重连中 ... ") 35 | connectWebSocket = client.newWebSocket(request, listener) 36 | } 37 | 38 | override fun sendMessage(text: String) { 39 | logger.info("发送信息 $text") 40 | connectWebSocket.send(text) 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/constant/Constant.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.constant 2 | 3 | import com.hcyacg.protocol.event.api.User 4 | import com.hcyacg.protocol.internal.entity.AccessWithFragmentedWss 5 | import org.slf4j.Logger 6 | import org.slf4j.LoggerFactory 7 | 8 | class Constant { 9 | companion object { 10 | /** 11 | * 正式环境 12 | */ 13 | const val proUrl: String = "https://api.sgroup.qq.com" 14 | 15 | /** 16 | * 沙箱环境 17 | */ 18 | const val testUrl: String = "https://sandbox.api.sgroup.qq.com" 19 | 20 | /** 21 | * 网关返回的数据 22 | */ 23 | var accessWithFragmentedWss: AccessWithFragmentedWss? = null 24 | 25 | /** 26 | * 机器人token 27 | */ 28 | var botToken: String? = null 29 | 30 | val logger: Logger = LoggerFactory.getLogger(this::class.java) 31 | 32 | /** 33 | * 机器人自身信息 34 | */ 35 | var bot: User? = null 36 | 37 | val threadLocal = ThreadLocal() 38 | 39 | } 40 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/entity/Announces.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.entity 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.hcyacg.protocol.anno.NoArg 5 | 6 | 7 | @NoArg 8 | data class Announces( 9 | @SerializedName("guild_id") 10 | val guildId: String, 11 | @SerializedName("channel_id") 12 | val channelId: String, 13 | @SerializedName("message_id") 14 | val messageId: String, 15 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/entity/AudioControl.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.entity 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.hcyacg.protocol.anno.NoArg 5 | 6 | 7 | @NoArg 8 | data class AudioControl( 9 | @SerializedName("audio_url") 10 | val audioUrl: String, 11 | @SerializedName("text") 12 | val text: String, 13 | @SerializedName("status") 14 | val status: Int 15 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/entity/Channel.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.entity 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.hcyacg.protocol.anno.NoArg 5 | 6 | @NoArg 7 | data class Channel( 8 | @SerializedName("id") 9 | val id: String = "", 10 | @SerializedName("guild_id") 11 | val guildId: String = "", 12 | @SerializedName("name") 13 | val name: String = "", 14 | @SerializedName("type") 15 | val type: Int = 0, 16 | @SerializedName("sub_type") 17 | val subType: Int = 0, 18 | @SerializedName("position") 19 | val position: Int = 0, 20 | @SerializedName("parent_id") 21 | val parentId: String = "", 22 | @SerializedName("owner_id") 23 | val ownerId: String = "" 24 | ) 25 | 26 | /** 27 | * 子频道种类 28 | */ 29 | 30 | object ChannelType { 31 | 32 | /** 33 | * 文字子频道 34 | */ 35 | val textSubchannel = 0 36 | 37 | /** 38 | * 保留,不可用 39 | */ 40 | val unavailable1 = 1 41 | 42 | /** 43 | * 语音子频道 44 | */ 45 | val voiceSubchannel = 2 46 | 47 | /** 48 | * 保留,不可用 49 | */ 50 | val unavailable3 = 3 51 | 52 | /** 53 | * 子频道分组 54 | */ 55 | val subchannelGrouping = 4 56 | 57 | /** 58 | * 直播子频道 59 | */ 60 | val liveSubchannel = 10005 61 | 62 | /** 63 | * 应用子频道 64 | */ 65 | val applicationSubchannel = 10006 66 | 67 | /** 68 | * 论坛子频道 69 | */ 70 | val forumSubchannel = 10007 71 | 72 | } 73 | 74 | /** 75 | * 文字子频道种类 76 | */ 77 | 78 | object ChannelSubType { 79 | 80 | /** 81 | * 闲聊 82 | */ 83 | val smallTalk = 0 84 | 85 | /** 86 | * 公告 87 | */ 88 | val announcement = 1 89 | 90 | /** 91 | * 攻略 92 | */ 93 | val strategy = 2 94 | 95 | /** 96 | * 开黑 97 | */ 98 | val openBlack = 3 99 | 100 | 101 | } 102 | 103 | 104 | @NoArg 105 | data class ChannelDto( 106 | @SerializedName("name") 107 | val name: String, 108 | @SerializedName("type") 109 | val type: Int, 110 | @SerializedName("position") 111 | val position: Int, 112 | @SerializedName("parent_id") 113 | val parentId: String 114 | ) 115 | -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/entity/ChannelPermissions.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.entity 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.hcyacg.protocol.anno.NoArg 5 | 6 | 7 | @NoArg 8 | data class ChannelPermissions( 9 | @SerializedName("channel_id") 10 | val channelId: String, 11 | @SerializedName("user_id") 12 | val userId: String, 13 | @SerializedName("permissions") 14 | val permissions: String 15 | ) 16 | 17 | 18 | class Permissions { 19 | companion object { 20 | /** 21 | * 可查看子频道 22 | */ 23 | const val CHANNEL_VIEWABLE: Long = 1 shl 0 24 | 25 | /** 26 | * 可管理子频道 27 | */ 28 | const val CHANNEL_MANAGEABLE: Long = 1 shl 1 29 | } 30 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/entity/Guild.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.entity 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.hcyacg.protocol.anno.NoArg 5 | 6 | 7 | @NoArg 8 | data class Guild( 9 | @SerializedName("id") 10 | val id: String, 11 | @SerializedName("name") 12 | val name: String, 13 | @SerializedName("icon") 14 | val icon: String, 15 | @SerializedName("owner_id") 16 | val ownerId: String, 17 | @SerializedName("owner") 18 | val owner: Boolean, 19 | @SerializedName("member_count") 20 | val memberCount: Int, 21 | @SerializedName("max_members") 22 | val maxMembers: Int, 23 | @SerializedName("description") 24 | val description: String, 25 | @SerializedName("joined_at") 26 | val joinedAt: String, 27 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/entity/Member.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.entity 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.hcyacg.protocol.anno.NoArg 5 | 6 | 7 | @NoArg 8 | data class Member( 9 | @SerializedName("user") 10 | val user: User?, 11 | @SerializedName("nick") 12 | val nick: String, 13 | @SerializedName("joined_at") 14 | val joinedAt: String, 15 | @SerializedName("roles") 16 | val roles: List, 17 | @SerializedName("deaf") 18 | val deaf: Boolean, 19 | @SerializedName("mute") 20 | val mute: Boolean, 21 | @SerializedName("pending") 22 | val pending: Boolean 23 | ) 24 | 25 | 26 | @NoArg 27 | data class MemberWithGuildID( 28 | @SerializedName("guild_id") 29 | val guildId: String, 30 | @SerializedName("user") 31 | val user: User, 32 | @SerializedName("nick") 33 | val nick: String, 34 | @SerializedName("joined_at") 35 | val joinedAt: String, 36 | @SerializedName("roles") 37 | val roles: List 38 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/entity/Message.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.entity 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.hcyacg.protocol.anno.NoArg 5 | import com.hcyacg.protocol.event.api.MessageArk 6 | import com.hcyacg.protocol.event.api.MessageAttachment 7 | import com.hcyacg.protocol.event.api.MessageEmbed 8 | import com.hcyacg.protocol.event.api.MessageReference 9 | 10 | 11 | @NoArg 12 | data class Message( 13 | @SerializedName("id") 14 | val id: String, 15 | @SerializedName("channel_id") 16 | val channelId: String, 17 | @SerializedName("guild_id") 18 | val guildId: String, 19 | @SerializedName("content") 20 | val content: String, 21 | @SerializedName("timestamp") 22 | // @Serializable(with = LocalDateTimeSerializer::class) 23 | val timestamp: String, 24 | @SerializedName("edited_timestamp") 25 | // @Serializable(with = LocalDateTimeSerializer::class) 26 | val editedTimestamp: String, 27 | @SerializedName("mention_everyone") 28 | val mentionEveryone: Boolean, 29 | @SerializedName("author") 30 | val author: User, 31 | @SerializedName("attachments") 32 | val attachments: MessageAttachment, 33 | @SerializedName("embeds") 34 | val embeds: MessageEmbed, 35 | @SerializedName("mentions") 36 | val mentions: User, 37 | @SerializedName("member") 38 | val member: Member, 39 | @SerializedName("ark") 40 | val ark: MessageArk, 41 | @SerializedName("message_reference") 42 | val messageReference: MessageReference, 43 | @SerializedName("seq_in_channel") 44 | val seqInChannel: String, 45 | @SerializedName("src_guild_id") 46 | val srcGuildId: String, 47 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/entity/MessageAudited.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.entity 2 | import com.google.gson.annotations.SerializedName 3 | import com.hcyacg.protocol.anno.NoArg 4 | 5 | @NoArg 6 | open class MessageAudited { 7 | @SerializedName("audit_id") 8 | lateinit var auditId: String 9 | @SerializedName("audit_time") 10 | lateinit var auditTime: String 11 | @SerializedName("channel_id") 12 | lateinit var channelId: String 13 | @SerializedName("create_time") 14 | lateinit var createTime: String 15 | @SerializedName("guild_id") 16 | lateinit var guildId: String 17 | @SerializedName("message_id") 18 | lateinit var messageId: String 19 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/entity/Role.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.entity 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.hcyacg.protocol.anno.NoArg 5 | 6 | 7 | @NoArg 8 | data class Role( 9 | @SerializedName("id") 10 | val id: String, 11 | @SerializedName("name") 12 | val name: String, 13 | @SerializedName("color") 14 | val color: Long, 15 | @SerializedName("hoist") 16 | val hoist: Long, 17 | @SerializedName("number") 18 | val number: Long, 19 | @SerializedName("number_limit") 20 | val numberLimit: Int, 21 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/entity/Roles.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.entity 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.hcyacg.protocol.anno.NoArg 5 | 6 | 7 | @NoArg 8 | data class Roles( 9 | @SerializedName("guild_id") 10 | val guildId: String, 11 | @SerializedName("roles") 12 | val roles: List, 13 | @SerializedName("number_limit") 14 | val numberLimit: Int, 15 | ) 16 | 17 | /** 18 | *@param name 是否修改名称: 0-否, 1-是 19 | *@param color 是否修改颜色: 0-否, 1-是 20 | *@param hoist 是否修改在成员列表中单独展示: 0-否, 1-是 21 | */ 22 | 23 | @NoArg 24 | data class Filter( 25 | @SerializedName("name") 26 | val name: Long, 27 | @SerializedName("color") 28 | val color: Long, 29 | @SerializedName("hoist") 30 | val hoist: Long, 31 | ) 32 | 33 | /** 34 | *@param name 名称 35 | *@param color ARGB的HEX十六进制颜色值转换后的十进制数值 36 | *@param hoist 在成员列表中单独展示: 0-否, 1-是 37 | */ 38 | 39 | @NoArg 40 | data class Info( 41 | @SerializedName("name") 42 | val name: String, 43 | @SerializedName("color") 44 | val color: Long, 45 | @SerializedName("hoist") 46 | val hoist: Long, 47 | ) 48 | 49 | 50 | 51 | @NoArg 52 | data class RoleVo( 53 | @SerializedName("role_id") 54 | val roleId: Long, 55 | @SerializedName("role") 56 | val role: Role, 57 | ) 58 | 59 | /** 60 | * @param filter 标识需要修改哪些字段 61 | * @param info 携带需要修改的字段内容 62 | */ 63 | 64 | @NoArg 65 | data class RoleDto( 66 | @SerializedName("filter") 67 | val filter: Filter, 68 | @SerializedName("info") 69 | val info: Info, 70 | ) 71 | 72 | /** 73 | * 系统自带的身份组 74 | */ 75 | 76 | class DefaultRoles { 77 | companion object { 78 | /** 79 | * 全体成员 80 | */ 81 | const val ALL_MEMBER: Long = 1 82 | 83 | /** 84 | * 管理员 85 | */ 86 | const val ADMINISTRATOR: Long = 2 87 | 88 | /** 89 | * 群主/创建者 90 | */ 91 | const val CREATOR: Long = 4 92 | 93 | /** 94 | * 子频道管理员 95 | */ 96 | const val CHANNEL_MANAGER: Long = 5 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/entity/Schedule.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.entity 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.hcyacg.protocol.anno.NoArg 5 | 6 | /** 7 | * 字段名 类型 描述 8 | id string 日程 id 9 | name string 日程名称 10 | description string 日程描述 11 | start_timestamp string 日程开始时间戳(ms) 12 | end_timestamp string 日程结束时间戳(ms) 13 | creator Member 创建者 14 | jump_channel_id string 日程开始时跳转到的子频道 id 15 | remind_type string 日程提醒类型,取值参考RemindType 16 | */ 17 | 18 | @NoArg 19 | data class Schedule( 20 | @SerializedName("id") 21 | val id: String?, 22 | @SerializedName("name") 23 | val name: String, 24 | @SerializedName("description") 25 | val description: String, 26 | @SerializedName("start_timestamp") 27 | val startTimestamp: String, 28 | @SerializedName("end_timestamp") 29 | val endTimestamp: String, 30 | @SerializedName("creator") 31 | var creator: Member?, 32 | @SerializedName("jump_channel_id") 33 | val jumpChannelId: String, 34 | @SerializedName("remind_type") 35 | val remindType: String, 36 | ) 37 | 38 | /** 39 | * 提醒类型 id 描述 40 | 0 不提醒 41 | 1 开始时提醒 42 | 2 开始前5分钟提醒 43 | 3 开始前15分钟提醒 44 | 4 开始前30分钟提醒 45 | 5 开始前60分钟提醒 46 | */ 47 | 48 | class RemindType { 49 | companion object { 50 | /** 51 | * 不提醒 52 | */ 53 | const val NO_REMINDER: String = "0" 54 | 55 | /** 56 | * 开始时提醒 57 | */ 58 | const val START_WITH_REMINDER: String = "1" 59 | 60 | /** 61 | * 开始前5分钟提醒 62 | */ 63 | const val FIVE_MINUTES_BEFORE_THE_START: String = "2" 64 | 65 | /** 66 | * 开始前10分钟提醒 67 | */ 68 | const val TEN_MINUTES_BEFORE_THE_START: String = "3" 69 | 70 | /** 71 | * 开始前30分钟提醒 72 | */ 73 | const val THIRTY_MINUTES_BEFORE_THE_START: String = "4" 74 | 75 | /** 76 | * 开始前60分钟提醒 77 | */ 78 | const val SIXTY_MINUTES_BEFORE_THE_START: String = "5" 79 | } 80 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/entity/User.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.entity 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.hcyacg.protocol.anno.NoArg 5 | 6 | 7 | @NoArg 8 | data class User( 9 | @SerializedName("bot") 10 | var bot: Boolean?, 11 | @SerializedName("id") 12 | val id: String?, 13 | @SerializedName("username") 14 | val username: String?, 15 | @SerializedName("avatar") 16 | val avatar: String?, 17 | @SerializedName("union_openid") 18 | val unionOpenid: String?, 19 | @SerializedName("union_user_account") 20 | val unionUserAccount: String?, 21 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/event/AudioActionEvent.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.event 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.hcyacg.protocol.anno.NoArg 5 | 6 | 7 | @NoArg 8 | data class AudioActionEvent( 9 | @SerializedName("guild_id") 10 | val guildId: String, 11 | @SerializedName("channel_id") 12 | val channelId: String, 13 | @SerializedName("audio_url") 14 | val audioUrl: String, 15 | @SerializedName("text") 16 | val text: String 17 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/event/ChannelEvent.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.event 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.hcyacg.protocol.anno.NoArg 5 | 6 | 7 | @NoArg 8 | data class ChannelEvent( 9 | @SerializedName("guild_id") 10 | val guildId: String, 11 | @SerializedName("id") 12 | val id: String, 13 | @SerializedName("name") 14 | val name: String, 15 | @SerializedName("op_user_id") 16 | val opUserId: String, 17 | @SerializedName("owner_id") 18 | val ownerId: String, 19 | @SerializedName("sub_type") 20 | val subType: Int, 21 | @SerializedName("type") 22 | val type: Int 23 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/event/GuildEvent.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.event 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.hcyacg.protocol.anno.NoArg 5 | 6 | @NoArg 7 | data class GuildEvent( 8 | @SerializedName("description") 9 | val description: String, 10 | @SerializedName("icon") 11 | val icon: String, 12 | @SerializedName("id") 13 | val id: String, 14 | @SerializedName("joined_at") 15 | val joinedAt: String, 16 | @SerializedName("max_members") 17 | val maxMembers: Int, 18 | @SerializedName("member_count") 19 | val memberCount: Int, 20 | @SerializedName("name") 21 | val name: String, 22 | @SerializedName("op_user_id") 23 | val opUserId: String, 24 | @SerializedName("owner_id") 25 | val ownerId: String 26 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/event/GuildMemberEvent.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.event 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.hcyacg.protocol.anno.NoArg 5 | import com.hcyacg.protocol.event.api.User 6 | 7 | @NoArg 8 | data class GuildMemberEvent( 9 | @SerializedName("guild_id") 10 | val guildId: String, 11 | @SerializedName("joined_at") 12 | val joinedAt: String, 13 | @SerializedName("nick") 14 | val nick: String, 15 | @SerializedName("op_user_id") 16 | val opUserId: String, 17 | @SerializedName("roles") 18 | val roles: List, 19 | @SerializedName("user") 20 | val user: User 21 | ) 22 | 23 | -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/event/ReadyEvent.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.event 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.hcyacg.protocol.anno.NoArg 5 | import com.hcyacg.protocol.event.api.User 6 | 7 | 8 | @NoArg 9 | 10 | data class ReadyEvent( 11 | @SerializedName("session_id") 12 | val sessionId: String, 13 | @SerializedName("shard") 14 | val shard: List, 15 | @SerializedName("user") 16 | val user: User, 17 | @SerializedName("version") 18 | val version: Int 19 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/event/api/Author.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.event.api 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.hcyacg.protocol.anno.NoArg 5 | import com.hcyacg.protocol.constant.Constant 6 | import com.hcyacg.protocol.constant.Constant.Companion.proUrl 7 | import com.hcyacg.protocol.constant.Constant.Companion.threadLocal 8 | import com.hcyacg.protocol.utils.OkHttpUtils 9 | 10 | @NoArg 11 | open class Author{ 12 | @SerializedName("avatar") 13 | lateinit var avatar: String 14 | @SerializedName("bot") 15 | var bot: Boolean = false 16 | @SerializedName("id") 17 | lateinit var id: String 18 | @SerializedName("username") 19 | lateinit var username: String 20 | 21 | private val guildInfo = "${proUrl}/guilds/{{guild_id}}" 22 | private val memberMute = "${guildInfo}/members/{{user_id}}/mute" 23 | 24 | private fun officeApiHeader(): MutableMap { 25 | return mutableMapOf( 26 | "Authorization" to Constant.botToken!! 27 | ) 28 | } 29 | 30 | /** 31 | * 成员禁言 32 | * @param timestamp 时间戳 单位:秒 33 | */ 34 | fun mute(timestamp: Long): Boolean { 35 | val url = memberMute.replace("{{guild_id}}", threadLocal.get()).replace("{{user_id}}", this.id) 36 | val json = "{\"mute_end_timestamp\": ${timestamp}}" 37 | val res = OkHttpUtils.patch(url, OkHttpUtils.addJson(json), officeApiHeader()) 38 | Constant.logger.debug(res.code.toString()) 39 | return res.code == 204 40 | } 41 | 42 | /** 43 | * 成员禁言 44 | * @param seconds 时间戳 单位:秒 45 | */ 46 | fun mute(seconds: Int): Boolean { 47 | val guildId = threadLocal.get() 48 | val url = memberMute.replace("{{guild_id}}", guildId).replace("{{user_id}}", this.id) 49 | val json = "{\"mute_seconds\": ${seconds}}" 50 | val res = OkHttpUtils.patch(url, OkHttpUtils.addJson(json), officeApiHeader()) 51 | Constant.logger.debug(res.code.toString()) 52 | return res.code == 204 53 | } 54 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/event/api/Dms.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.event.api 2 | import com.google.gson.annotations.SerializedName 3 | import com.hcyacg.protocol.anno.NoArg 4 | 5 | @NoArg 6 | data class Dms( 7 | @SerializedName("channel_id") 8 | val channelId: String, 9 | @SerializedName("create_time") 10 | val createTime: String, 11 | @SerializedName("guild_id") 12 | val guildId: String 13 | ) 14 | 15 | @NoArg 16 | data class DmsDto( 17 | @SerializedName("recipient_id") 18 | val recipient_id : String, 19 | @SerializedName("source_guild_id") 20 | val source_guild_id: String 21 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/event/api/Member.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.event.api 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | 6 | data class Member( 7 | @SerializedName("joined_at") 8 | val joinedAt: String, 9 | @SerializedName("roles") 10 | val roles: List 11 | ) 12 | -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/event/api/Message.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.event.api 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | 6 | data class Message( 7 | @SerializedName("content") 8 | val content: String, //消息内容,文本内容,支持内嵌格式 9 | @SerializedName("embed") 10 | val embed: MessageEmbed, //embed 消息,一种特殊的 ark 11 | @SerializedName("ark") 12 | val ark: MessageArk, //ark 消息 13 | @SerializedName("image") 14 | val image: String, //图片url地址 15 | @SerializedName("msg_id") 16 | val msgId: String //要回复的消息id(Message.id), 在 AT_CREATE_MESSAGE 事件中获取。带了 msg_id 视为被动回复消息,否则视为主动推送消息 17 | ) 18 | 19 | 20 | data class TextMessage( 21 | @SerializedName("content") 22 | val content: String, //消息内容,文本内容,支持内嵌格式 23 | @SerializedName("msg_id") 24 | val msgId: String //要回复的消息id(Message.id), 在 AT_CREATE_MESSAGE 事件中获取。带了 msg_id 视为被动回复消息,否则视为主动推送消息 25 | ) 26 | 27 | 28 | data class initiativeTextMessage( 29 | @SerializedName("content") 30 | val content: String //消息内容,文本内容,支持内嵌格式 31 | ) 32 | 33 | 34 | data class TextWithImageMessage( 35 | @SerializedName("content") 36 | val content: String, //消息内容,文本内容,支持内嵌格式 37 | @SerializedName("image") 38 | val image: String, //图片url地址 39 | @SerializedName("msg_id") 40 | val msgId: String //要回复的消息id(Message.id), 在 AT_CREATE_MESSAGE 事件中获取。带了 msg_id 视为被动回复消息,否则视为主动推送消息 41 | ) 42 | 43 | 44 | data class initiativeTextWithImageMessage( 45 | @SerializedName("content") 46 | val content: String, //消息内容,文本内容,支持内嵌格式 47 | @SerializedName("image") 48 | val image: String //图片url地址 49 | ) 50 | 51 | 52 | 53 | data class ImageMessage( 54 | @SerializedName("image") 55 | val image: String, //图片url地址 56 | @SerializedName("msg_id") 57 | val msgId: String //要回复的消息id(Message.id), 在 AT_CREATE_MESSAGE 事件中获取。带了 msg_id 视为被动回复消息,否则视为主动推送消息 58 | ) 59 | 60 | 61 | data class initiativeImageMessage( 62 | @SerializedName("image") 63 | val image: String, //图片url地址 64 | ) 65 | 66 | 67 | 68 | data class ArkMessage( 69 | @SerializedName("ark") 70 | val ark: MessageArk, //ark 消息 71 | @SerializedName("msg_id") 72 | val msgId: String //要回复的消息id(Message.id), 在 AT_CREATE_MESSAGE 事件中获取。带了 msg_id 视为被动回复消息,否则视为主动推送消息 73 | ) 74 | 75 | 76 | data class initiativeArkMessage( 77 | @SerializedName("ark") 78 | val ark: MessageArk //ark 消息 79 | ) 80 | 81 | 82 | 83 | data class EmbedMessage( 84 | @SerializedName("embed") 85 | val embed: MessageEmbed, //embed 消息,一种特殊的 ark 86 | @SerializedName("msg_id") 87 | val msgId: String //要回复的消息id(Message.id), 在 AT_CREATE_MESSAGE 事件中获取。带了 msg_id 视为被动回复消息,否则视为主动推送消息 88 | ) 89 | 90 | 91 | 92 | data class initiativeEmbedMessage( 93 | @SerializedName("embed") 94 | val embed: MessageEmbed, //embed 消息,一种特殊的 ark 95 | ) 96 | 97 | /** 98 | * embed 99 | *@param title 标题 100 | *@param description 描述 101 | *@param prompt 消息弹窗内容 102 | *@param timestamp 消息创建时间 103 | *@param fields MessageEmbedField 对象数组 消息创建时间 104 | */ 105 | 106 | data class MessageEmbed( 107 | @SerializedName("title") 108 | val title: String, //标题 109 | @SerializedName("description") 110 | val description: String, //描述 111 | @SerializedName("prompt") 112 | val prompt: String, //消息弹窗内容 113 | @SerializedName("timestamp") 114 | // (with = LocalDateTimeSerializer::class) 115 | val timestamp: String, //消息创建时间 116 | @SerializedName("fields") 117 | val fields: MessageEmbedField //消息创建时间 118 | ) 119 | 120 | /** 121 | *@param name 字段名 122 | *@param value 字段值 123 | */ 124 | 125 | data class MessageEmbedField( 126 | @SerializedName("name") 127 | val name: String, 128 | @SerializedName("value") 129 | val value: String 130 | ) 131 | 132 | /** 133 | *@param url 下载地址 134 | */ 135 | 136 | data class MessageAttachment( 137 | @SerializedName("url") 138 | val url: String //下载地址 139 | ) 140 | 141 | /** 142 | * @param templateId ark模板id(需要先申请) 23 链接+文本列表模板 24 文本+缩略图模板 37 大图模板 143 | * @param kv MessageAkrKv arkkv数组 kv值列表 144 | */ 145 | 146 | data class MessageArk( 147 | @SerializedName("template_id") 148 | val templateId: Int, 149 | @SerializedName("kv") 150 | val kv: List 151 | ) 152 | 153 | /** 154 | *@param key key 155 | *@param value value 156 | *@param obj MessageArkObj arkobj类型的数组 ark obj类型的列表 157 | */ 158 | 159 | data class MessageArkKv( 160 | @SerializedName("key") 161 | val key: String, 162 | @SerializedName("value") 163 | val value: String?, 164 | @SerializedName("obj") 165 | val obj: List? 166 | ) 167 | 168 | /** 169 | *@param objKv MessageArkObjKv objkv类型的数组 ark objkv列表 170 | */ 171 | 172 | data class MessageArkObj( 173 | @SerializedName("obj_kv") 174 | val objKv: List 175 | ) 176 | 177 | /** 178 | *@param key key 179 | *@param value value 180 | */ 181 | 182 | data class MessageArkObjKv( 183 | @SerializedName("key") 184 | val key: String, 185 | @SerializedName("value") 186 | val value: String 187 | ) 188 | 189 | data class MessageReference( 190 | @SerializedName("message_id") 191 | val messageId:String, 192 | @SerializedName("ignore_get_message_error") 193 | val ignoreGetMessageError : Boolean 194 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/event/api/User.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.event.api 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | 6 | data class User( 7 | @SerializedName("bot") 8 | val bot: Boolean, 9 | @SerializedName("id") 10 | val id: String, 11 | @SerializedName("username") 12 | val username: String 13 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/event/message/AtMessageCreateEvent.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.event.message 2 | 3 | import com.hcyacg.protocol.anno.NoArg 4 | 5 | 6 | @NoArg 7 | class AtMessageCreateEvent : EventApi() { 8 | 9 | 10 | fun messageContent(): String { 11 | return content.replace(Regex("<@!\\d+>"), "") 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/event/message/EventApi.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.event.message 2 | 3 | import com.google.gson.Gson 4 | import com.hcyacg.protocol.constant.Constant 5 | import com.hcyacg.protocol.constant.Constant.Companion.logger 6 | import com.hcyacg.protocol.constant.Constant.Companion.proUrl 7 | import com.hcyacg.protocol.entity.* 8 | import com.hcyacg.protocol.entity.Message 9 | import com.hcyacg.protocol.event.api.* 10 | import com.hcyacg.protocol.utils.OkHttpUtils 11 | 12 | open class EventApi : MessageEvent() { 13 | private val channel = "${proUrl}/channels/{{channel_id}}" 14 | private val sendMessage = "$channel/messages" 15 | private val sendAudio = "$channel/audio" 16 | private val recall = "$sendMessage/{{message_id}}" 17 | 18 | private val guildInfo = "$proUrl/guilds/{{guild_id}}" 19 | private val memberMute = "${guildInfo}/members/{{user_id}}/mute" 20 | private val mute = "$proUrl/guilds/{{guild_id}}/mute" 21 | 22 | 23 | private fun officeApiHeader(): MutableMap { 24 | return mutableMapOf( 25 | "Authorization" to Constant.botToken!! 26 | ) 27 | } 28 | 29 | fun replyText(msg: String): Message { 30 | val url = sendMessage.replace("{{channel_id}}", this.channelId) 31 | val json = Gson().toJson(TextMessage(msg, this.id)) 32 | 33 | val res = OkHttpUtils.postJson(url, OkHttpUtils.addJson(json), officeApiHeader()) 34 | logger.debug(res) 35 | return Gson().fromJson(res, Message::class.java) 36 | } 37 | 38 | fun sendText(msg: String): Message { 39 | val url = sendMessage.replace("{{channel_id}}", this.channelId) 40 | val json = Gson().toJson(initiativeTextMessage(msg)) 41 | 42 | val res = OkHttpUtils.postJson(url, OkHttpUtils.addJson(json), officeApiHeader()) 43 | logger.debug(res) 44 | return Gson().fromJson(res, Message::class.java) 45 | } 46 | 47 | fun replyTextWithImage(msg: String, imageUrl: String): Message { 48 | val url = sendMessage.replace("{{channel_id}}", this.channelId) 49 | val json = Gson().toJson(TextWithImageMessage(msg, imageUrl, this.id)) 50 | 51 | val res = OkHttpUtils.postJson(url, OkHttpUtils.addJson(json), officeApiHeader()) 52 | logger.debug(res) 53 | return Gson().fromJson(res, Message::class.java) 54 | } 55 | 56 | fun sendTextWithImage(msg: String, imageUrl: String): Message { 57 | val url = sendMessage.replace("{{channel_id}}", this.channelId) 58 | val json = Gson().toJson(initiativeTextWithImageMessage(msg, imageUrl)) 59 | 60 | val res = OkHttpUtils.postJson(url, OkHttpUtils.addJson(json), officeApiHeader()) 61 | logger.debug(res) 62 | return Gson().fromJson(res, Message::class.java) 63 | } 64 | 65 | fun replyImage(imageUrl: String): Message { 66 | val url = sendMessage.replace("{{channel_id}}", this.channelId) 67 | val json = Gson().toJson(ImageMessage(imageUrl, this.id)) 68 | val res = OkHttpUtils.postJson(url, OkHttpUtils.addJson(json), officeApiHeader()) 69 | logger.debug(res) 70 | return Gson().fromJson(res, Message::class.java) 71 | } 72 | 73 | 74 | fun sendImage(imageUrl: String): Message { 75 | val url = sendMessage.replace("{{channel_id}}", this.channelId) 76 | val json = Gson().toJson(initiativeImageMessage(imageUrl)) 77 | val res = OkHttpUtils.postJson(url, OkHttpUtils.addJson(json), officeApiHeader()) 78 | logger.debug(res) 79 | return Gson().fromJson(res, Message::class.java) 80 | } 81 | 82 | fun replyArk(messageArk: MessageArk): Message { 83 | val url = sendMessage.replace("{{channel_id}}", this.channelId) 84 | val json = Gson().toJson(ArkMessage(messageArk, this.id)) 85 | 86 | val res = OkHttpUtils.postJson(url, OkHttpUtils.addJson(json), officeApiHeader()) 87 | logger.debug(res) 88 | return Gson().fromJson(res, Message::class.java) 89 | } 90 | 91 | fun sendArk(messageArk: MessageArk): Message { 92 | val url = sendMessage.replace("{{channel_id}}", this.channelId) 93 | val json = Gson().toJson(initiativeArkMessage(messageArk)) 94 | 95 | val res = OkHttpUtils.postJson(url, OkHttpUtils.addJson(json), officeApiHeader()) 96 | logger.debug(res) 97 | return Gson().fromJson(res, Message::class.java) 98 | } 99 | 100 | fun replyEmbed(messageEmbed: MessageEmbed): Message { 101 | val url = sendMessage.replace("{{channel_id}}", this.channelId) 102 | val json = Gson().toJson(EmbedMessage(messageEmbed, this.id)) 103 | 104 | val res = OkHttpUtils.postJson(url, OkHttpUtils.addJson(json), officeApiHeader()) 105 | logger.debug(res) 106 | return Gson().fromJson(res, Message::class.java) 107 | } 108 | 109 | fun sendEmbed(messageEmbed: MessageEmbed): Message { 110 | val url = sendMessage.replace("{{channel_id}}", this.channelId) 111 | val json = Gson().toJson(initiativeEmbedMessage(messageEmbed)) 112 | 113 | val res = OkHttpUtils.postJson(url, OkHttpUtils.addJson(json), officeApiHeader()) 114 | logger.debug(res) 115 | return Gson().fromJson(res, Message::class.java) 116 | } 117 | 118 | fun sendAudio(audioUrl: String, text: String, status: Int): Message { 119 | val url = sendAudio.replace("{{channel_id}}", this.channelId) 120 | val json = Gson().toJson(AudioControl(audioUrl, text, status)) 121 | 122 | val res = OkHttpUtils.postJson(url, OkHttpUtils.addJson(json), officeApiHeader()) 123 | logger.debug(res) 124 | return Gson().fromJson(res, Message::class.java) 125 | } 126 | 127 | 128 | /** 129 | * 全局禁言 130 | * @param timestamp 时间戳 单位:秒 131 | */ 132 | fun mute(timestamp: Long): Boolean { 133 | val url = mute.replace("{{guild_id}}", guildId) 134 | val json = "{\"mute_end_timestamp\": ${timestamp}}" 135 | val res = OkHttpUtils.patch(url, OkHttpUtils.addJson(json), officeApiHeader()) 136 | logger.debug(res.code.toString()) 137 | return res.code == 204 138 | } 139 | 140 | /** 141 | * 全局禁言 142 | * @param seconds 时间戳 单位:秒 143 | */ 144 | fun mute(seconds: Int): Boolean { 145 | val url = mute.replace("{{guild_id}}", guildId) 146 | val json = "{\"mute_seconds\": ${seconds}}" 147 | val res = OkHttpUtils.patch(url, OkHttpUtils.addJson(json), officeApiHeader()) 148 | logger.debug(res.code.toString()) 149 | return res.code == 204 150 | } 151 | 152 | 153 | /** 154 | * 撤回当前消息 155 | * 用来撤回频道内的消息 156 | * 管理员可以撤回普通成员的消息 157 | * 频道主可以撤回所有人的消息 158 | */ 159 | fun recall(): Boolean { 160 | val url = recall.replace("{{channel_id}}", channelId).replace("{{message_id}}", id) 161 | val res = OkHttpUtils.delete(url, mutableMapOf(), officeApiHeader()) 162 | logger.debug(res.code.toString()) 163 | return res.code == 200 164 | } 165 | 166 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/event/message/MessageAuditPassEvent.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.event.message 2 | 3 | import com.hcyacg.protocol.entity.MessageAudited 4 | 5 | class MessageAuditPassEvent : MessageAudited() -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/event/message/MessageAuditRejectEvent.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.event.message 2 | 3 | import com.hcyacg.protocol.entity.MessageAudited 4 | 5 | class MessageAuditRejectEvent : MessageAudited() -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/event/message/MessageCreateEvent.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.event.message 2 | 3 | import com.hcyacg.protocol.anno.NoArg 4 | 5 | @NoArg 6 | class MessageCreateEvent : EventApi() { 7 | 8 | } 9 | 10 | -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/event/message/MessageEvent.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.event.message 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.hcyacg.protocol.anno.NoArg 5 | import com.hcyacg.protocol.event.api.Author 6 | import com.hcyacg.protocol.event.api.Member 7 | 8 | @NoArg 9 | open class MessageEvent { 10 | @SerializedName("attachments") 11 | var attachments: List = mutableListOf() 12 | 13 | @SerializedName("author") 14 | lateinit var author: Author 15 | 16 | @SerializedName("channel_id") 17 | lateinit var channelId: String 18 | 19 | @SerializedName("content") 20 | lateinit var content: String 21 | 22 | @SerializedName("guild_id") 23 | lateinit var guildId: String 24 | 25 | @SerializedName("id") 26 | lateinit var id: String 27 | 28 | @SerializedName("member") 29 | lateinit var member: Member 30 | 31 | @SerializedName("mentions") 32 | lateinit var mentions: List 33 | 34 | @SerializedName("timestamp") 35 | lateinit var timestamp: String 36 | 37 | fun isContentInitialized(): Boolean { 38 | return this::content.isInitialized 39 | } 40 | } 41 | 42 | /** 43 | * 附件 例:图片 44 | */ 45 | @NoArg 46 | data class Attachment( 47 | @SerializedName("content_type") 48 | var content_type: String = "", 49 | @SerializedName("filename") 50 | var filename: String = "", 51 | @SerializedName("height") 52 | var height: Int = 0, 53 | @SerializedName("id") 54 | var id: String = "", 55 | @SerializedName("size") 56 | var size: Int = 0, 57 | @SerializedName("url") 58 | var url: String = "", 59 | @SerializedName("width") 60 | var width: Int = 0 61 | ) 62 | -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/event/message/MessageReactionEvent.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.event.message 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.hcyacg.protocol.anno.NoArg 5 | 6 | @NoArg 7 | 8 | data class MessageReactionEvent( 9 | @SerializedName("channel_id") 10 | val channelId: String, 11 | @SerializedName("emoji") 12 | val emoji: Emoji, 13 | @SerializedName("guild_id") 14 | val guildId: String, 15 | @SerializedName("target") 16 | val target: Target, 17 | @SerializedName("user_id") 18 | val userId: String 19 | ) 20 | 21 | @NoArg 22 | 23 | data class Emoji( 24 | @SerializedName("id") 25 | val id: String, 26 | @SerializedName("type") 27 | val type: Int 28 | ) 29 | 30 | @NoArg 31 | 32 | data class Target( 33 | @SerializedName("id") 34 | val id: String, 35 | @SerializedName("type") 36 | val type: Int 37 | ) 38 | 39 | fun getInfo(id: String): String { 40 | when (id) { 41 | "0" -> return "惊讶" 42 | "1" -> return "撇嘴" 43 | "2" -> return "色" 44 | "3" -> return "发呆" 45 | "4" -> return "得意" 46 | "5" -> return "流泪" 47 | "6" -> return "害羞" 48 | "7" -> return "闭嘴" 49 | "8" -> return "睡" 50 | "9" -> return "大哭" 51 | "10" -> return "尴尬" 52 | "11" -> return "发怒" 53 | "12" -> return "调皮" 54 | "13" -> return "呲牙" 55 | "14" -> return "微笑" 56 | "15" -> return "难过" 57 | "16" -> return "酷" 58 | "18" -> return "抓狂" 59 | "19" -> return "吐" 60 | "20" -> return "偷笑" 61 | "21" -> return "可爱" 62 | "22" -> return "白眼" 63 | "23" -> return "傲慢" 64 | "24" -> return "饥饿" 65 | "25" -> return "困" 66 | "26" -> return "惊恐" 67 | "27" -> return "流汗" 68 | "28" -> return "憨笑" 69 | "29" -> return "悠闲" 70 | "30" -> return "奋斗" 71 | "31" -> return "咒骂" 72 | "32" -> return "疑问" 73 | "33" -> return "嘘" 74 | "34" -> return "晕" 75 | "35" -> return "折磨" 76 | "36" -> return "衰" 77 | "37" -> return "骷髅" 78 | "38" -> return "敲打" 79 | "39" -> return "再见" 80 | "41" -> return "发抖" 81 | "42" -> return "爱情" 82 | "43" -> return "跳跳" 83 | "46" -> return "猪头" 84 | "49" -> return "拥抱" 85 | "53" -> return "蛋糕" 86 | "54" -> return "闪电" 87 | "55" -> return "炸弹" 88 | "56" -> return "刀" 89 | "57" -> return "足球" 90 | "59" -> return "便便" 91 | "60" -> return "咖啡" 92 | "61" -> return "饭" 93 | "63" -> return "玫瑰" 94 | "64" -> return "凋谢" 95 | "66" -> return "爱心" 96 | "67" -> return "心碎" 97 | "69" -> return "礼物" 98 | "74" -> return "太阳" 99 | "75" -> return "月亮" 100 | "76" -> return "赞" 101 | "77" -> return "踩" 102 | "78" -> return "握手" 103 | "79" -> return "胜利" 104 | "85" -> return "飞吻" 105 | "86" -> return "怄火" 106 | "89" -> return "西瓜" 107 | "96" -> return "冷汗" 108 | "97" -> return "擦汗" 109 | "98" -> return "抠鼻" 110 | "99" -> return "鼓掌" 111 | "100" -> return "糗大了" 112 | "101" -> return "坏笑" 113 | "102" -> return "左哼哼" 114 | "103" -> return "右哼哼" 115 | "104" -> return "哈欠" 116 | "105" -> return "鄙视" 117 | "106" -> return "委屈" 118 | "107" -> return "快哭了" 119 | "108" -> return "阴险" 120 | "109" -> return "亲亲" 121 | "110" -> return "吓" 122 | "111" -> return "可怜" 123 | "112" -> return "菜刀" 124 | "113" -> return "啤酒" 125 | "114" -> return "篮球" 126 | "115" -> return "乒乓" 127 | "116" -> return "示爱" 128 | "117" -> return "瓢虫" 129 | "118" -> return "抱拳" 130 | "119" -> return "勾引" 131 | "120" -> return "拳头" 132 | "121" -> return "差劲" 133 | "122" -> return "爱你" 134 | "123" -> return "NO" 135 | "124" -> return "OK" 136 | "125" -> return "转圈" 137 | "126" -> return "磕头" 138 | "127" -> return "回头" 139 | "128" -> return "跳绳" 140 | "129" -> return "挥手" 141 | "130" -> return "激动" 142 | "131" -> return "街舞" 143 | "132" -> return "献吻" 144 | "133" -> return "左太极" 145 | "134" -> return "右太极" 146 | "136" -> return "双喜" 147 | "137" -> return "鞭炮" 148 | "138" -> return "灯笼" 149 | "140" -> return "K歌" 150 | "144" -> return "喝彩" 151 | "145" -> return "祈祷" 152 | "146" -> return "爆筋" 153 | "147" -> return "棒棒糖" 154 | "148" -> return "喝奶" 155 | "151" -> return "飞机" 156 | "158" -> return "钞票" 157 | "168" -> return "药" 158 | "169" -> return "手枪" 159 | "171" -> return "茶" 160 | "172" -> return "眨眼睛" 161 | "173" -> return "泪奔" 162 | "174" -> return "无奈" 163 | "175" -> return "卖萌" 164 | "176" -> return "小纠结" 165 | "177" -> return "喷血" 166 | "178" -> return "斜眼笑" 167 | "179" -> return "doge" 168 | "180" -> return "惊喜" 169 | "181" -> return "骚扰" 170 | "182" -> return "笑哭" 171 | "183" -> return "我最美" 172 | "184" -> return "河蟹" 173 | "185" -> return "羊驼" 174 | "187" -> return "幽灵" 175 | "188" -> return "蛋" 176 | "190" -> return "菊花" 177 | "192" -> return "红包" 178 | "193" -> return "大笑" 179 | "194" -> return "不开心" 180 | "197" -> return "冷漠" 181 | "198" -> return "呃" 182 | "199" -> return "好棒" 183 | "200" -> return "拜托" 184 | "201" -> return "点赞" 185 | "202" -> return "无聊" 186 | "203" -> return "托脸" 187 | "204" -> return "吃" 188 | "205" -> return "送花" 189 | "206" -> return "害怕" 190 | "207" -> return "花痴" 191 | "208" -> return "小样儿" 192 | "210" -> return "飙泪" 193 | "211" -> return "我不看" 194 | "212" -> return "托腮" 195 | "214" -> return "啵啵" 196 | "215" -> return "糊脸" 197 | "216" -> return "拍头" 198 | "217" -> return "扯一扯" 199 | "218" -> return "舔一舔" 200 | "219" -> return "蹭一蹭" 201 | "220" -> return "拽炸天" 202 | "221" -> return "顶呱呱" 203 | "222" -> return "抱抱" 204 | "223" -> return "暴击" 205 | "224" -> return "开枪" 206 | "225" -> return "撩一撩" 207 | "226" -> return "拍桌" 208 | "227" -> return "拍手" 209 | "228" -> return "恭喜" 210 | "229" -> return "干杯" 211 | "230" -> return "嘲讽" 212 | "231" -> return "哼" 213 | "232" -> return "佛系" 214 | "233" -> return "掐一掐" 215 | "234" -> return "惊呆" 216 | "235" -> return "颤抖" 217 | "236" -> return "啃头" 218 | "237" -> return "偷看" 219 | "238" -> return "扇脸" 220 | "239" -> return "原谅" 221 | "240" -> return "喷脸" 222 | "241" -> return "生日快乐" 223 | "242" -> return "头撞击" 224 | "243" -> return "甩头" 225 | "244" -> return "扔狗" 226 | "245" -> return "加油必胜" 227 | "246" -> return "加油抱抱" 228 | "247" -> return "口罩护体" 229 | "260" -> return "办公" 230 | "261" -> return "忙碌" 231 | "262" -> return "心累" 232 | "263" -> return "沧桑" 233 | "264" -> return "捂脸" 234 | "265" -> return "刷手机" 235 | "266" -> return "嫌弃" 236 | "267" -> return "头秃" 237 | "268" -> return "问号" 238 | "269" -> return "暗中观察" 239 | "270" -> return "尴尬" 240 | "271" -> return "吃瓜" 241 | "272" -> return "呵呵" 242 | "273" -> return "柠檬" 243 | "274" -> return "南" 244 | "👗" -> return "连衣裙" 245 | "😏" -> return "哼哼" 246 | "😄" -> return "高兴" 247 | "😔" -> return "失落" 248 | "😍" -> return "花痴" 249 | "😉" -> return "媚眼" 250 | "☺" -> return "可爱" 251 | "😜" -> return "淘气" 252 | "😁" -> return "呲牙" 253 | "😝" -> return "吐舌" 254 | "😰" -> return "紧张" 255 | "😓" -> return "汗" 256 | "😚" -> return "亲亲" 257 | "😌" -> return "羞涩" 258 | "😊" -> return "嘿嘿" 259 | "❔" -> return "问号" 260 | "❕" -> return "叹号" 261 | "❌" -> return "错误" 262 | "☎" -> return "电话" 263 | "📷" -> return "相机" 264 | "📠" -> return "传真" 265 | "💻" -> return "电脑" 266 | "🎥" -> return "摄影机" 267 | "🎤" -> return "话筒" 268 | "🔫" -> return "手枪" 269 | "💿" -> return "光碟" 270 | "💓" -> return "爱心" 271 | "✨" -> return "闪光" 272 | "♣" -> return "扑克" 273 | "🀄" -> return "麻将" 274 | "〽" -> return "股票" 275 | "🎰" -> return "老虎机" 276 | "🚥" -> return "信号灯" 277 | "🚧" -> return "路障" 278 | "🎸" -> return "吉他" 279 | "💈" -> return "理发厅" 280 | "🛀" -> return "浴缸" 281 | "🚽" -> return "马桶" 282 | "🏠" -> return "家" 283 | "⛪" -> return "教堂" 284 | "⭕" -> return "正确" 285 | "⛄" -> return "雪人" 286 | "🌙" -> return "月亮" 287 | "☔" -> return "雨天" 288 | "☀" -> return "晴天" 289 | "☁" -> return "云朵" 290 | "💄" -> return "口红" 291 | "👟" -> return "鞋子" 292 | "👕" -> return "衣服" 293 | "👙" -> return "内衣" 294 | "👜" -> return "包" 295 | "🌂" -> return "雨伞" 296 | "👢" -> return "鞋子" 297 | "👠" -> return "高跟鞋" 298 | "🏦" -> return "银行" 299 | "👒" -> return "帽子" 300 | "🐭" -> return "老鼠" 301 | "🐳" -> return "海豚" 302 | "🐧" -> return "企鹅" 303 | "👼" -> return "天使" 304 | "🐯" -> return "老虎" 305 | "🐶" -> return "狗" 306 | "🐠" -> return "鱼" 307 | "🐛" -> return "虫" 308 | "👻" -> return "幽灵" 309 | "🐸" -> return "青蛙" 310 | "🐔" -> return "公鸡" 311 | "🐮" -> return "牛" 312 | "🐨" -> return "树懒" 313 | "🐤" -> return "小鸡" 314 | "💀" -> return "骷髅" 315 | "🐷" -> return "猪" 316 | "🐙" -> return "章鱼" 317 | "🐵" -> return "猴" 318 | "👦" -> return "男孩" 319 | "👧" -> return "女孩" 320 | "👨" -> return "爸爸" 321 | "👩" -> return "妈妈" 322 | "⛵" -> return "船" 323 | "🚌" -> return "公交" 324 | "🚀" -> return "火箭" 325 | "🐎" -> return "骑马" 326 | "🚲" -> return "自行车" 327 | "🚤" -> return "快艇" 328 | "🚗" -> return "汽车" 329 | "🚄" -> return "列车" 330 | "✈" -> return "飞机" 331 | "🔒" -> return "锁" 332 | "🔑" -> return "钥匙" 333 | "📫" -> return "文件" 334 | "♨" -> return "热" 335 | "💉" -> return "打针" 336 | "💩" -> return "便便" 337 | "👣" -> return "脚印" 338 | "🏥" -> return "医院" 339 | "⚡" -> return "闪电" 340 | "💤" -> return "睡觉" 341 | "💰" -> return "钱" 342 | "🏆" -> return "奖杯" 343 | "🔥" -> return "火" 344 | "🏨" -> return "酒店" 345 | "🏧" -> return "取款机" 346 | "🏪" -> return "超市" 347 | "🚹" -> return "男性" 348 | "💦" -> return "水" 349 | "🚺" -> return "女性" 350 | "💨" -> return "吹气" 351 | "📱" -> return "手机" 352 | "⭐" -> return "星星" 353 | "🔔" -> return "铃铛" 354 | "👑" -> return "皇冠" 355 | "💣" -> return "炸弹" 356 | "💍" -> return "戒指" 357 | "🐚" -> return "海螺" 358 | "🎈" -> return "气球" 359 | "🎀" -> return "蝴蝶结" 360 | "💝" -> return "礼物" 361 | "🌴" -> return "椰子树" 362 | "🎉" -> return "庆祝" 363 | "🌹" -> return "玫瑰" 364 | "🎄" -> return "圣诞树" 365 | "🚬" -> return "吸烟" 366 | "💊" -> return "药丸" 367 | "🍉" -> return "西瓜" 368 | "🍓" -> return "草莓" 369 | "🍊" -> return "橙子" 370 | "🍎" -> return "苹果" 371 | "☕" -> return "咖啡" 372 | "🍸" -> return "高脚杯" 373 | "🍻" -> return "干杯" 374 | "🍺" -> return "啤酒" 375 | "🍟" -> return "薯条" 376 | "🍳" -> return "煎蛋" 377 | "🙏" -> return "合十" 378 | "🍔" -> return "汉堡" 379 | "🍞" -> return "起司" 380 | "🎂" -> return "蛋糕" 381 | "🍣" -> return "寿司" 382 | "🍧" -> return "刨冰" 383 | "🍙" -> return "饭团" 384 | "🍜" -> return "拉面" 385 | "🍝" -> return "意面" 386 | "🍚" -> return "米饭" 387 | "👂" -> return "耳朵" 388 | "👄" -> return "嘴唇" 389 | "👃" -> return "鼻子" 390 | "👀" -> return "眼睛" 391 | "👇" -> return "向下" 392 | "👆" -> return "向上" 393 | "👉" -> return "向右" 394 | "👈" -> return "向左" 395 | "👌" -> return "好的" 396 | "👎" -> return "鄙视" 397 | "✌" -> return "胜利" 398 | "👏" -> return "鼓掌" 399 | "☝" -> return "向上" 400 | "👍" -> return "厉害" 401 | "👊" -> return "拳头" 402 | "💪" -> return "肌肉" 403 | "😂" -> return "激动" 404 | "😱" -> return "害怕" 405 | "😭" -> return "大哭" 406 | "😘" -> return "飞吻" 407 | "😳" -> return "瞪眼" 408 | "😒" -> return "不屑" 409 | else -> return "未知表情" 410 | } 411 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/event/message/directMessage/DirectMessage.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.event.message.directMessage 2 | 3 | import com.hcyacg.protocol.anno.NoArg 4 | import com.google.gson.annotations.SerializedName 5 | 6 | 7 | @NoArg 8 | open class DirectMessage{ 9 | @SerializedName("author") 10 | lateinit var author: Author 11 | @SerializedName("channel_id") 12 | lateinit var channelId: String 13 | @SerializedName("content") 14 | lateinit var content: String 15 | @SerializedName("direct_message") 16 | var directMessage: Boolean = true 17 | @SerializedName("guild_id") 18 | lateinit var guildId: String 19 | @SerializedName("id") 20 | lateinit var id: String 21 | @SerializedName("member") 22 | lateinit var member: Member 23 | @SerializedName("seq") 24 | var seq: Int = 0 25 | @SerializedName("seq_in_channel") 26 | lateinit var seqInChannel: String 27 | @SerializedName("src_guild_id") 28 | lateinit var srcGuildId: String 29 | @SerializedName("timestamp") 30 | lateinit var timestamp: String 31 | 32 | fun isContentInitialized(): Boolean { 33 | return this::content.isInitialized 34 | } 35 | } 36 | 37 | data class Author( 38 | @SerializedName("avatar") 39 | val avatar: String, 40 | @SerializedName("id") 41 | val id: String, 42 | @SerializedName("username") 43 | val username: String 44 | ) 45 | 46 | data class Member( 47 | @SerializedName("joined_at") 48 | val joinedAt: String 49 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/event/message/directMessage/DirectMessageCreateEvent.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.event.message.directMessage 2 | 3 | import com.hcyacg.protocol.anno.NoArg 4 | 5 | @NoArg 6 | open class DirectMessageCreateEvent : DmsApi() 7 | 8 | -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/event/message/directMessage/DmsApi.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.event.message.directMessage 2 | 3 | import com.google.gson.Gson 4 | import com.hcyacg.protocol.constant.Constant 5 | import com.hcyacg.protocol.constant.Constant.Companion.botToken 6 | import com.hcyacg.protocol.constant.Constant.Companion.logger 7 | import com.hcyacg.protocol.constant.Constant.Companion.proUrl 8 | import com.hcyacg.protocol.entity.Message 9 | import com.hcyacg.protocol.event.api.* 10 | import com.hcyacg.protocol.utils.OkHttpUtils 11 | 12 | open class DmsApi : DirectMessage() { 13 | 14 | private val sendMessage = "${proUrl}/dms/{{guild_id}}/messages" 15 | private val dms = "${proUrl}/users/@me/dms" 16 | private val recall = "${proUrl}/dms/{{guild_id}}/messages/{{message_id}}" 17 | 18 | private fun officeApiHeader(): MutableMap { 19 | return mutableMapOf( 20 | "Authorization" to Constant.botToken!! 21 | ) 22 | } 23 | 24 | /** 25 | * 创建私聊会话 26 | */ 27 | private fun createDms(authorId:String, guildId:String):Dms{ 28 | val json = Gson().toJson(DmsDto(authorId,guildId)) 29 | val res = OkHttpUtils.postJson(dms, OkHttpUtils.addJson(json), officeApiHeader()) 30 | logger.debug(res) 31 | return Gson().fromJson(res, Dms::class.java) 32 | } 33 | 34 | fun replyText(msg: String): Message { 35 | val url = sendMessage.replace("{{guild_id}}", this.guildId) 36 | val json = Gson().toJson(TextMessage(msg, this.id)) 37 | 38 | val res = OkHttpUtils.postJson(url, OkHttpUtils.addJson(json), officeApiHeader()) 39 | logger.debug(res) 40 | return Gson().fromJson(res, Message::class.java) 41 | } 42 | 43 | fun sendText(authorId:String,guildId:String,msg: String): Message { 44 | val createDms = createDms(authorId, guildId) 45 | val url = sendMessage.replace("{{guild_id}}", createDms.guildId) 46 | val json = Gson().toJson(initiativeTextMessage(msg)) 47 | 48 | val res = OkHttpUtils.postJson(url, OkHttpUtils.addJson(json), officeApiHeader()) 49 | logger.debug(res) 50 | return Gson().fromJson(res, Message::class.java) 51 | } 52 | 53 | fun replyTextWithImage(msg: String, imageUrl: String): Message { 54 | val url = sendMessage.replace("{{guild_id}}", this.guildId) 55 | val json = Gson().toJson(TextWithImageMessage(msg, imageUrl, this.id)) 56 | 57 | val res = OkHttpUtils.postJson(url, OkHttpUtils.addJson(json), officeApiHeader()) 58 | logger.debug(res) 59 | return Gson().fromJson(res, Message::class.java) 60 | } 61 | 62 | fun sendTextWithImage(authorId:String,guildId:String,msg: String, imageUrl: String): Message { 63 | val createDms = createDms(authorId, guildId) 64 | val url = sendMessage.replace("{{guild_id}}", createDms.guildId) 65 | val json = Gson().toJson(initiativeTextWithImageMessage(msg, imageUrl)) 66 | 67 | val res = OkHttpUtils.postJson(url, OkHttpUtils.addJson(json), officeApiHeader()) 68 | logger.debug(res) 69 | return Gson().fromJson(res, Message::class.java) 70 | } 71 | 72 | fun replyImage(imageUrl: String): Message { 73 | val url = sendMessage.replace("{{guild_id}}", this.guildId) 74 | val json = Gson().toJson(ImageMessage(imageUrl, this.id)) 75 | val res = OkHttpUtils.postJson(url, OkHttpUtils.addJson(json), officeApiHeader()) 76 | logger.debug(res) 77 | return Gson().fromJson(res, Message::class.java) 78 | } 79 | 80 | 81 | fun sendImage(authorId:String,guildId:String,imageUrl: String): Message { 82 | val createDms = createDms(authorId, guildId) 83 | val url = sendMessage.replace("{{guild_id}}", createDms.guildId) 84 | val json = Gson().toJson(initiativeImageMessage(imageUrl)) 85 | val res = OkHttpUtils.postJson(url, OkHttpUtils.addJson(json), officeApiHeader()) 86 | logger.debug(res) 87 | return Gson().fromJson(res, Message::class.java) 88 | } 89 | 90 | /** 91 | * 撤回当前消息 92 | * 用来撤回频道内的消息 93 | * 管理员可以撤回普通成员的消息 94 | * 频道主可以撤回所有人的消息 95 | */ 96 | fun recall(guildId:String,messageId:String): Boolean { 97 | val url = recall.replace("{{guild_id}}", guildId).replace("{{message_id}}", messageId) 98 | val res = OkHttpUtils.delete(url, mutableMapOf(), officeApiHeader()) 99 | logger.debug(res.code.toString()) 100 | return res.code == 200 101 | } 102 | 103 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/internal/BaseBotClient.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.internal 2 | 3 | /** 4 | * 机器人客户端父类 5 | */ 6 | internal interface BaseBotClient { 7 | 8 | fun reconnect() 9 | 10 | fun sendMessage(text: String) 11 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/internal/BaseBotListener.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.internal 2 | 3 | import okhttp3.WebSocketListener 4 | 5 | /** 6 | * 机器人监听父类 7 | */ 8 | abstract class BaseBotListener : WebSocketListener() { 9 | 10 | lateinit var reconnect: () -> Unit 11 | 12 | fun reconnect(func: () -> Unit) { 13 | reconnect = func 14 | } 15 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/internal/config/IdentifyConfig.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.internal.config 2 | 3 | import com.hcyacg.protocol.internal.entity.D 4 | import com.hcyacg.protocol.internal.entity.Properties 5 | 6 | 7 | data class IdentifyConfig( 8 | val token: String, 9 | val shards: Int = 1, 10 | var index: Int = 0, 11 | val intents: Intents = Intents(), 12 | val properties: Properties = Properties() 13 | ) { 14 | fun toIdentifyOperationData(): D { 15 | return D( 16 | intents = intents.toIntentsValue(), 17 | properties = properties, 18 | shard = mutableListOf(index, shards), 19 | token = token 20 | ) 21 | } 22 | } 23 | 24 | 25 | data class Intents( 26 | val guilds: Boolean = true, 27 | val guildMembers: Boolean = true, 28 | val directMessage: Boolean = true, 29 | val audioAction: Boolean = true, 30 | val forum: Boolean = true, 31 | val atMessages: Boolean = true, 32 | val messages: Boolean = false, 33 | val guildMessageReactions: Boolean = true, 34 | val messageAudit:Boolean = true 35 | ) { 36 | fun toIntentsValue(): Long { 37 | return ((if (guilds) 1.shl(0) else 0) 38 | + (if (guildMembers) 1.shl(1) else 0) 39 | + (if (messages) 1.shl(9) else 0) 40 | + (if (guildMessageReactions) 1.shl(10) else 0) 41 | + (if (directMessage) 1.shl(12) else 0) 42 | + (if (messageAudit) 1.shl(27) else 0) 43 | + (if (forum) 1.shl(28) else 0) 44 | + (if (audioAction) 1.shl(29) else 0) 45 | + (if (atMessages) 1.shl(30) else 0)) 46 | .toLong() 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/internal/entity/AccessWithFragmentedWss.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.internal.entity 2 | 3 | import com.hcyacg.protocol.anno.NoArg 4 | import com.google.gson.annotations.SerializedName 5 | 6 | /** 7 | * 获取带分片 WSS 接入点 时返回的实体对象 8 | * 字段名 类型 描述 9 | * url string WebSocket 的连接地址 10 | * shards int 建议的 shard 数 11 | * session_start_limit SessionStartLimit 创建Session限制信息 12 | * 13 | */ 14 | 15 | @NoArg 16 | data class AccessWithFragmentedWss( 17 | @SerializedName("session_start_limit") 18 | var sessionStartLimit: SessionStartLimit, 19 | @SerializedName("shards") 20 | var shards: Int, 21 | @SerializedName("url") 22 | var url: String 23 | ) 24 | 25 | /** 26 | * 字段名 类型 描述 27 | * total int 每 24 小时可创建 Session 数 28 | * remaining int 目前还可以创建的 Session 数 29 | * reset_after int 重置计数的剩余时间(ms) 30 | * max_concurrency int 每 5s 可以创建的 Session 数 31 | */ 32 | 33 | @NoArg 34 | data class SessionStartLimit( 35 | @SerializedName("max_concurrency") 36 | var maxConcurrency: Int, 37 | @SerializedName("remaining") 38 | var remaining: Int, 39 | @SerializedName("reset_after") 40 | var resetAfter: Int, 41 | @SerializedName("total") 42 | var total: Int 43 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/internal/entity/Dispatch.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.internal.entity 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.hcyacg.protocol.internal.enums.DispatchEnums 5 | 6 | 7 | data class Dispatch( 8 | @SerializedName("s") 9 | val seq: Long, 10 | @SerializedName("t") 11 | val type: DispatchEnums, 12 | @SerializedName("d") 13 | val d: T 14 | ) : Operation(0) 15 | 16 | 17 | data class DispatchType( 18 | @SerializedName("t") 19 | val type: DispatchEnums, 20 | @SerializedName("s") 21 | val seq: Long, 22 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/internal/entity/Heartbeat.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.internal.entity 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | 6 | data class Heartbeat( 7 | @SerializedName("d") 8 | val count: Long 9 | ) : Operation(1) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/internal/entity/Identify.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.internal.entity 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | 6 | data class Identify( 7 | @SerializedName("d") 8 | var d: D, 9 | ) : Operation(2) { 10 | constructor(token: String) : this(D(token = token)) 11 | } 12 | 13 | 14 | data class D( 15 | @SerializedName("intents") 16 | var intents: Long = 1610612739L, 17 | @SerializedName("properties") 18 | var properties: Properties = Properties(), 19 | @SerializedName("shard") 20 | var shard: List = listOf(0, 1), 21 | @SerializedName("token") 22 | var token: String 23 | ) 24 | 25 | 26 | data class Properties( 27 | @SerializedName("\$browser") 28 | var browser: String = "okhttp", 29 | @SerializedName("\$device") 30 | var device: String = "tencent-guild-protocol by Nekoer", 31 | @SerializedName("\$os") 32 | var os: String? = System.getProperty("os.name").split(" ").firstOrNull() ?: "unknown" 33 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/internal/entity/Operation.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.internal.entity 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | 6 | open class Operation( 7 | @SerializedName("op") 8 | val op: Int 9 | ) 10 | -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/internal/entity/Resume.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.internal.entity 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | 6 | 7 | data class Resume( 8 | @SerializedName("d") 9 | val d: ResumeData 10 | ) : Operation(6) 11 | 12 | 13 | data class ResumeData( 14 | @SerializedName("seq") 15 | val seq: Long, 16 | @SerializedName("session_id") 17 | val sessionId: String, 18 | @SerializedName("token") 19 | val token: String 20 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/internal/entity/UniversalWssAccess.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.internal.entity 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.hcyacg.protocol.anno.NoArg 5 | 6 | /** 7 | * 获取通用 WSS 接入点 时返回的实体对象 8 | * 段名 类型 描述 9 | * url string WebSocket 的连接地址 10 | */ 11 | 12 | @NoArg 13 | data class UniversalWssAccess( 14 | @SerializedName("url") 15 | var url: String = "" 16 | ) -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/internal/enums/DispatchEnums.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.internal.enums 2 | 3 | /** 4 | * 事件种类 5 | */ 6 | enum class DispatchEnums { 7 | READY, // 准备 8 | RESUMED, // 恢复 9 | GUILD_MEMBER_ADD, // 新用户加入频道 10 | GUILD_MEMBER_UPDATE, // 暂无 11 | GUILD_MEMBER_REMOVE, // 用户离开频道 12 | GUILD_CREATE, // 当机器人加入新guild时 13 | GUILD_UPDATE, // 当guild资料发生变更时 14 | GUILD_DELETE, // 当机器人退出guild时 15 | CHANNEL_CREATE, // 子频道被创建 16 | CHANNEL_UPDATE, // 子频道信息变更 17 | CHANNEL_DELETE, // 子频道被删除 18 | AT_MESSAGE_CREATE, // 艾特机器人 19 | MESSAGE_CREATE, // 私域不需要艾特 20 | 21 | MESSAGE_REACTION_ADD, // 为消息添加表情表态 22 | MESSAGE_REACTION_REMOVE, // 为消息删除表情表态 23 | DIRECT_MESSAGE_CREATE, // 当收到用户发给机器人的私信消息时 24 | 25 | MESSAGE_AUDIT_PASS, // 消息审核通过 26 | MESSAGE_AUDIT_REJECT, // 消息审核不通过 27 | 28 | THREAD_CREATE, // 当用户创建主题时 29 | THREAD_UPDATE, // 当用户更新主题时 30 | THREAD_DELETE, // 当用户删除主题时 31 | POST_CREATE, // 当用户创建帖子时 32 | POST_DELETE, // 当用户删除帖子时 33 | REPLY_CREATE, // 当用户回复评论时 34 | REPLY_DELETE, // 当用户回复评论时 35 | 36 | AUDIO_START, // 音频开始播放时 37 | AUDIO_FINISH, // 音频播放结束时 38 | AUDIO_ON_MIC, // 上麦时 39 | AUDIO_OFF_MIC, // 下麦时 40 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/internal/enums/OPCodeEnums.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.internal.enums 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | 6 | /* 7 | CODE 名称 客户端操作 描述 8 | 0 Dispatch Receive 服务端进行消息推送 9 | 1 Heartbeat Send/Receive 客户端或服务端发送心跳 10 | 2 Identify Send 客户端发送鉴权 11 | 6 Resume Send 客户端回复会话 12 | 7 Reconnect Receive 服务端通知客户端重新连接 13 | 9 Invalid Session Receive 当identify或resume的时候,如果参数有错,服务端会返回该消息 14 | 11 Heartbeat ACK Receive 当发送心跳成功之后,就会收到该消息 15 | */ 16 | 17 | 18 | enum class OPCodeEnums( 19 | val code: Int, 20 | val description: String, 21 | ) { 22 | 23 | @SerializedName("Dispatch") 24 | DISPATCH(0, "服务端进行消息推送"), 25 | 26 | @SerializedName("Heartbeat") 27 | HEARTBEAT(1, "客户端或服务端发送心跳"), 28 | 29 | @SerializedName("Identify") 30 | IDENTIFY(2, "客户端发送鉴权"), 31 | 32 | @SerializedName("Resume") 33 | RESUME(6, "客户端回复会话"), 34 | 35 | @SerializedName("Reconnect") 36 | RECONNECT(7, "服务端通知客户端重新连接"), 37 | 38 | @SerializedName("Invalid_Session") 39 | INVALID_SESSION(9, "当identify或resume的时候,如果参数有错,服务端会返回该消息"), 40 | 41 | @SerializedName("Hello") 42 | HELLO(10, "心跳参数"), 43 | 44 | @SerializedName("Heartbeat_ACK") 45 | HEARTBEAT_ACK(11, "当发送心跳成功之后,就会收到该消息"), 46 | UNKNOWN(-1, "未知") 47 | ; 48 | 49 | companion object { 50 | private val codeMap = values().associateBy { it.code } 51 | fun getOPCodeByCode(code: Int): OPCodeEnums { 52 | return codeMap[code] ?: UNKNOWN 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/utils/OkHttpUtils.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.utils 2 | 3 | import com.google.gson.Gson 4 | import okhttp3.* 5 | import okhttp3.MediaType.Companion.toMediaType 6 | import okhttp3.RequestBody.Companion.asRequestBody 7 | import okhttp3.RequestBody.Companion.toRequestBody 8 | import okio.ByteString 9 | import java.io.File 10 | import java.io.IOException 11 | import java.io.InputStream 12 | import java.io.UnsupportedEncodingException 13 | import java.net.URLEncoder 14 | import java.util.* 15 | import java.util.concurrent.TimeUnit 16 | import java.util.regex.Pattern 17 | 18 | object OkHttpUtils { 19 | private val MEDIA_JSON: MediaType = "application/json;charset=utf-8".toMediaType() 20 | private val MEDIA_STREAM: MediaType = "application/octet-stream".toMediaType() 21 | private val MEDIA_X_JSON: MediaType = "text/x-json".toMediaType() 22 | private val MEDIA_ENCRYPTED_JSON: MediaType = "application/encrypted-json;charset=UTF-8".toMediaType() 23 | private val MEDIA_TEXT: MediaType = "text/plain;charset=UTF-8".toMediaType() 24 | private const val TIME_OUT = 10L 25 | private val okHttpClient: OkHttpClient by lazy { 26 | OkHttpClient.Builder().followRedirects(false).followSslRedirects(false).connectTimeout(10L, TimeUnit.SECONDS) 27 | .readTimeout(10L, TimeUnit.SECONDS).build() 28 | } 29 | 30 | private fun emptyHeaders(): Headers { 31 | return addSingleHeader("user-agent", UA.PC.getValue()) 32 | } 33 | 34 | @JvmOverloads 35 | @Throws(IOException::class) 36 | operator fun get(url: String, headers: Headers = emptyHeaders()): Response { 37 | val request: Request = Request.Builder().url(url).headers(headers).build() 38 | return okHttpClient.newCall(request).execute() 39 | } 40 | 41 | @Throws(IOException::class) 42 | operator fun get(url: String, map: Map): Response { 43 | return OkHttpUtils[url, addHeaders(map)] 44 | } 45 | 46 | @JvmOverloads 47 | @Throws(IOException::class) 48 | fun post(url: String, requestBody: RequestBody, headers: Headers = emptyHeaders()): Response { 49 | val request: Request = Request.Builder().url(url).post(requestBody).headers(headers).build() 50 | return okHttpClient.newCall(request).execute() 51 | } 52 | 53 | @JvmName("post1") 54 | @JvmOverloads 55 | @Throws(IOException::class) 56 | fun post( 57 | url: String, 58 | map: Map = mutableMapOf(), 59 | headers: Headers = emptyHeaders() 60 | ): Response { 61 | return post(url, mapToFormBody(map), headers) 62 | } 63 | 64 | @Throws(IOException::class) 65 | fun post(url: String, map: Map, headerMap: Map): Response { 66 | return post(url, mapToFormBody(map), addHeaders(headerMap)) 67 | } 68 | 69 | @Throws(IOException::class) 70 | fun post(url: String, requestBody: RequestBody, headerMap: Map): Response { 71 | return post(url, requestBody, addHeaders(headerMap)) 72 | } 73 | 74 | @Throws(IOException::class) 75 | fun post(url: String, map: Map): Response { 76 | return post(url, map, emptyHeaders()) 77 | } 78 | 79 | @JvmOverloads 80 | @Throws(IOException::class) 81 | fun put(url: String, requestBody: RequestBody, headers: Headers): Response { 82 | val request: Request = Request.Builder().url(url).put(requestBody).headers(headers).build() 83 | return okHttpClient.newCall(request).execute() 84 | } 85 | 86 | @Throws(IOException::class) 87 | fun put(url: String, map: Map): Response { 88 | return put(url, mapToFormBody(map), emptyHeaders()) 89 | } 90 | 91 | @Throws(IOException::class) 92 | fun put(url: String, map: Map, headers: Map): Response { 93 | return put(url, mapToFormBody(map), addHeaders(headers)) 94 | } 95 | 96 | @Throws(IOException::class) 97 | fun put(url: String, map: Map, headers: Headers): Response { 98 | return put(url, mapToFormBody(map), headers) 99 | } 100 | 101 | @Throws(IOException::class) 102 | private fun delete(url: String, requestBody: RequestBody, headers: Headers = emptyHeaders()): Response { 103 | val request: Request = Request.Builder().url(url).delete(requestBody).headers(headers).build() 104 | return okHttpClient.newCall(request).execute() 105 | } 106 | 107 | @JvmOverloads 108 | @Throws(IOException::class) 109 | fun delete(url: String, map: Map, headers: Headers = emptyHeaders()): Response { 110 | return delete(url, mapToFormBody(map), headers) 111 | } 112 | 113 | @Throws(IOException::class) 114 | fun delete(url: String, map: Map, headerMap: Map): Response { 115 | return delete(url, mapToFormBody(map), addHeaders(headerMap)) 116 | } 117 | 118 | @Throws(IOException::class) 119 | fun delete(url: String, requestBody: RequestBody, headerMap: Map): Response { 120 | return delete(url, requestBody, addHeaders(headerMap)) 121 | } 122 | 123 | @JvmOverloads 124 | @Throws(IOException::class) 125 | fun patch(url: String, requestBody: RequestBody, headers: Headers = emptyHeaders()): Response { 126 | val request: Request = Request.Builder().url(url).patch(requestBody).headers(headers).build() 127 | return okHttpClient.newCall(request).execute() 128 | } 129 | 130 | @JvmOverloads 131 | @Throws(IOException::class) 132 | fun patch(url: String, map: Map, headers: Headers = emptyHeaders()): Response { 133 | return patch(url, mapToFormBody(map), headers) 134 | } 135 | 136 | @Throws(IOException::class) 137 | fun patch(url: String, map: Map, headerMap: Map): Response { 138 | return patch(url, mapToFormBody(map), addHeaders(headerMap)) 139 | } 140 | 141 | @Throws(IOException::class) 142 | fun patch(url: String, requestBody: RequestBody, headerMap: Map): Response { 143 | return patch(url, requestBody, addHeaders(headerMap)) 144 | } 145 | 146 | @Throws(IOException::class) 147 | fun getStr(response: Response): String { 148 | return (Objects.requireNonNull(response.body) as ResponseBody).string() 149 | } 150 | 151 | @Throws(IOException::class) 152 | fun getJson(response: Response): String { 153 | val str = getStr(response) 154 | return str 155 | } 156 | 157 | @Throws(IOException::class) 158 | fun getBytes(url: String): ByteArray { 159 | return getBytes(url, emptyHeaders()) 160 | } 161 | 162 | @Throws(IOException::class) 163 | fun getBytes(url: String, headers: Headers): ByteArray { 164 | val response = OkHttpUtils[url, headers] 165 | return getBytes(response) 166 | } 167 | 168 | @Throws(IOException::class) 169 | fun getBytes(url: String, headers: Map): ByteArray { 170 | val response = OkHttpUtils[url, headers] 171 | return getBytes(response) 172 | } 173 | 174 | @Throws(IOException::class) 175 | fun getBytes(response: Response): ByteArray { 176 | return (Objects.requireNonNull(response.body) as ResponseBody).bytes() 177 | } 178 | 179 | fun getByteStream(response: Response): InputStream { 180 | return (Objects.requireNonNull(response.body) as ResponseBody).byteStream() 181 | } 182 | 183 | @Throws(IOException::class) 184 | fun getByteStream(url: String, headers: Headers): InputStream { 185 | val response = OkHttpUtils[url, headers] 186 | return getByteStream(response) 187 | } 188 | 189 | @Throws(IOException::class) 190 | fun getByteStream(url: String, headers: Map): InputStream { 191 | val response = OkHttpUtils[url, headers] 192 | return getByteStream(response) 193 | } 194 | 195 | @Throws(IOException::class) 196 | fun getByteStream(url: String): InputStream { 197 | return getByteStream(url, emptyHeaders()) 198 | } 199 | 200 | @Throws(IOException::class) 201 | private fun getByteStr(response: Response): ByteString { 202 | return (Objects.requireNonNull(response.body) as ResponseBody).byteString() 203 | } 204 | 205 | @Throws(IOException::class) 206 | fun getByteStr(url: String, headers: Headers): ByteString { 207 | val response = OkHttpUtils[url, headers] 208 | return getByteStr(response) 209 | } 210 | 211 | @Throws(IOException::class) 212 | fun getByteStr(url: String, headers: Map): ByteString { 213 | val response = OkHttpUtils[url, headers] 214 | return getByteStr(response) 215 | } 216 | 217 | @Throws(IOException::class) 218 | fun getByteStr(url: String): ByteString { 219 | return getByteStr(url, emptyHeaders()) 220 | } 221 | 222 | private fun getIs(response: Response): InputStream { 223 | return (Objects.requireNonNull(response.body) as ResponseBody).byteStream() 224 | } 225 | 226 | @Throws(IOException::class) 227 | fun getStr(url: String, headers: Headers): String { 228 | val response = OkHttpUtils[url, headers] 229 | return getStr(response) 230 | } 231 | 232 | @Throws(IOException::class) 233 | fun getStr(url: String, headers: Map): String { 234 | val response = OkHttpUtils[url, headers] 235 | return getStr(response) 236 | } 237 | 238 | @Throws(IOException::class) 239 | fun getStr(url: String): String { 240 | val response = OkHttpUtils[url, emptyHeaders()] 241 | return getStr(response) 242 | } 243 | 244 | @Throws(IOException::class) 245 | fun getJson(url: String, headers: Headers): String { 246 | val response = OkHttpUtils[url, headers] 247 | return getJson(response) 248 | } 249 | 250 | @Throws(IOException::class) 251 | fun getJson(url: String, map: Map): String { 252 | return getJson(url, addHeaders(map)) 253 | } 254 | 255 | @Throws(IOException::class) 256 | fun getJson(url: String): String { 257 | val response = OkHttpUtils[url, emptyHeaders()] 258 | return getJson(response) 259 | } 260 | 261 | @Throws(IOException::class) 262 | fun postStr(url: String, requestBody: RequestBody, headers: Headers): String { 263 | val response = post(url, requestBody, headers) 264 | return getStr(response) 265 | } 266 | 267 | @Throws(IOException::class) 268 | fun postStr(url: String, requestBody: RequestBody, headers: Map): String { 269 | val response = post(url, requestBody, headers) 270 | return getStr(response) 271 | } 272 | 273 | @Throws(IOException::class) 274 | fun postStr(url: String, requestBody: RequestBody): String { 275 | val response = post(url, requestBody, emptyHeaders()) 276 | return getStr(response) 277 | } 278 | 279 | @Throws(IOException::class) 280 | fun postJson(url: String, requestBody: RequestBody, headers: Headers): String { 281 | val response = post(url, requestBody, headers) 282 | return getJson(response) 283 | } 284 | 285 | @Throws(IOException::class) 286 | fun postJson(url: String, requestBody: RequestBody, headers: Map): String { 287 | val response = post(url, requestBody, headers) 288 | return getJson(response) 289 | } 290 | 291 | @Throws(IOException::class) 292 | fun postJson(url: String, requestBody: RequestBody): String { 293 | val response = post(url, requestBody, emptyHeaders()) 294 | return getJson(response) 295 | } 296 | 297 | private fun mapToFormBody(map: Map): RequestBody { 298 | val builder = FormBody.Builder() 299 | val var2: Iterator<*> = map.entries.iterator() 300 | while (var2.hasNext()) { 301 | val (key, value) = var2.next() as Map.Entry<*, *> 302 | builder.add(key as String, value as String) 303 | } 304 | return builder.build() 305 | } 306 | 307 | @JvmOverloads 308 | @Throws(IOException::class) 309 | fun postStr(url: String, map: Map, headers: Headers = emptyHeaders()): String { 310 | val response = post(url, mapToFormBody(map), headers) 311 | return getStr(response) 312 | } 313 | 314 | @Throws(IOException::class) 315 | fun postStr(url: String, map: Map, headers: Map): String { 316 | val response = post(url, mapToFormBody(map), headers) 317 | return getStr(response) 318 | } 319 | 320 | @JvmOverloads 321 | @Throws(IOException::class) 322 | fun deleteStr(url: String, map: Map, headers: Headers = emptyHeaders()): String { 323 | val response = delete(url, mapToFormBody(map), headers) 324 | return getStr(response) 325 | } 326 | 327 | @Throws(IOException::class) 328 | fun deleteStr(url: String, map: Map, headers: Map): String { 329 | val response = delete(url, mapToFormBody(map), headers) 330 | return getStr(response) 331 | } 332 | 333 | @Throws(IOException::class) 334 | fun postJson(url: String, map: Map, headers: Headers): String { 335 | val str = postStr(url, map, headers) 336 | return Gson().toJson(str) 337 | } 338 | 339 | @Throws(IOException::class) 340 | fun postJson(url: String, map: Map, headers: Map): String { 341 | val str = postStr(url, map, headers) 342 | return Gson().toJson(str) 343 | } 344 | 345 | @Throws(IOException::class) 346 | fun postJson(url: String, map: Map): String { 347 | val str = postStr(url, map, emptyHeaders()) 348 | return Gson().toJson(str) 349 | } 350 | 351 | @Throws(IOException::class) 352 | fun patchJson(url: String, map: Map): String { 353 | val str = getStr(patch(url, map, emptyHeaders())) 354 | return Gson().toJson(str) 355 | } 356 | 357 | @Throws(IOException::class) 358 | fun patchJson(url: String, requestBody: RequestBody, headers: Map): String { 359 | val str = getStr(patch(url, requestBody, headers)) 360 | return Gson().toJson(str) 361 | } 362 | 363 | 364 | @Throws(IOException::class) 365 | fun deleteJson(url: String, map: Map, headers: Headers): String { 366 | val str = deleteStr(url, map, headers) 367 | return Gson().toJson(str) 368 | } 369 | 370 | @Throws(IOException::class) 371 | fun deleteJson(url: String, map: Map, headers: Map): String { 372 | val str = deleteStr(url, map, headers) 373 | return Gson().toJson(str) 374 | } 375 | 376 | @Throws(IOException::class) 377 | fun deleteJson(url: String, map: Map): String { 378 | val str = deleteStr(url, map, emptyHeaders()) 379 | return Gson().toJson(str) 380 | } 381 | 382 | 383 | 384 | fun addJson(jsonStr: String): RequestBody { 385 | return jsonStr.toRequestBody(MEDIA_JSON) 386 | } 387 | 388 | fun addText(text: String): RequestBody { 389 | return text.toRequestBody(MEDIA_TEXT) 390 | } 391 | 392 | fun addBody(text: String, contentType: String): RequestBody { 393 | val mediaType: MediaType = contentType.toMediaType() 394 | return text.toRequestBody(mediaType) 395 | } 396 | 397 | // fun addJson(JsonElement: String): RequestBody { 398 | // return JsonElement.toRequestBody(MEDIA_JSON) 399 | // } 400 | 401 | fun addEncryptedJson(str: String): RequestBody { 402 | return str.toRequestBody(MEDIA_ENCRYPTED_JSON) 403 | } 404 | 405 | fun addSingleHeader(name: String, value: String): Headers { 406 | return Headers.Builder().add(name, value).build() 407 | } 408 | 409 | fun addHeaders(cookie: String?, referer: String = "", userAgent: String = UA.PC.getValue()): Headers { 410 | val header = Headers.Builder() 411 | if (cookie != null) { 412 | header.add("cookie", cookie) 413 | } 414 | return header.add("referer", referer).add("user-agent", userAgent).build() 415 | } 416 | 417 | fun addHeaders(cookie: String, referer: String, ua: UA): Headers { 418 | return addHeaders(cookie, referer, ua.getValue()) 419 | } 420 | 421 | fun addHeaders(cookie: String, referer: String): Headers { 422 | return addHeaders(cookie, referer, UA.PC.getValue()) 423 | } 424 | 425 | fun addHeaders(map: Map): Headers { 426 | val builder = Headers.Builder() 427 | val var2: Iterator<*> = map.entries.iterator() 428 | while (var2.hasNext()) { 429 | val (key, value) = var2.next() as Map.Entry<*, *> 430 | builder.add(key as String, value as String) 431 | } 432 | return builder.build() 433 | } 434 | 435 | fun addHeader(): Headers.Builder { 436 | return Headers.Builder() 437 | } 438 | 439 | fun addUA(ua: UA): Headers { 440 | return addSingleHeader("user-agent", ua.getValue()) 441 | } 442 | 443 | fun addUA(ua: String): Headers { 444 | return addSingleHeader("user-agent", ua) 445 | } 446 | 447 | fun addCookie(cookie: String): Headers { 448 | return addSingleHeader("cookie", cookie) 449 | } 450 | 451 | fun addReferer(url: String): Headers { 452 | return addSingleHeader("referer", url) 453 | } 454 | 455 | fun addStream(byteString: ByteString): RequestBody { 456 | return byteString.toRequestBody(MEDIA_STREAM) 457 | } 458 | 459 | @Throws(IOException::class) 460 | fun addStream(url: String): RequestBody { 461 | return addStream(getByteStr(url)) 462 | } 463 | 464 | 465 | fun getCookie(cookie: String, name: String): String? { 466 | var arr = cookie.split("; ").toTypedArray() 467 | if (arr.isEmpty()) { 468 | arr = cookie.split(";").toTypedArray() 469 | } 470 | val var3 = arr 471 | val var4 = arr.size 472 | for (var5 in 0 until var4) { 473 | val str = var3[var5] 474 | val newArr = str.split("=").toTypedArray() 475 | if (newArr.size > 1 && newArr[0].trim { it <= ' ' } == name) { 476 | return str.substring(str.indexOf(61.toChar()) + 1) 477 | } 478 | } 479 | return null 480 | } 481 | 482 | fun getCookie(cookie: String, vararg name: String): Map { 483 | return name.toList().map { it to getCookie(cookie, it) }.filter { it.second != null } 484 | .associate { it.first to it.second!! } 485 | } 486 | 487 | fun cookieToMap(cookie: String): Map { 488 | val map: MutableMap = mutableMapOf() 489 | val arr = cookie.split(";").toTypedArray() 490 | val var4 = arr.size 491 | for (var5 in 0 until var4) { 492 | val str = arr[var5] 493 | val newArr = str.split("=").toTypedArray() 494 | if (newArr.size > 1) { 495 | map[newArr[0].trim { it <= ' ' }] = newArr[1].trim { it <= ' ' } 496 | } 497 | } 498 | return map 499 | } 500 | 501 | private fun fileNameByUrl(url: String): String { 502 | val index = url.lastIndexOf("/") 503 | return url.substring(index + 1) 504 | } 505 | 506 | fun getStreamBody(file: File): RequestBody { 507 | return file.asRequestBody(MEDIA_STREAM) 508 | } 509 | 510 | fun getStreamBody(file: File, contentType: String): RequestBody { 511 | return file.asRequestBody(contentType.toMediaType()) 512 | } 513 | 514 | 515 | fun getStreamBody(fileName: String, bytes: ByteArray): RequestBody { 516 | return bytes.toRequestBody(MEDIA_STREAM) 517 | } 518 | 519 | fun getStreamBody(bytes: ByteArray): RequestBody { 520 | return getStreamBody(UUID.randomUUID().toString(), bytes) 521 | } 522 | 523 | fun urlParams(map: Map): String { 524 | val sb = StringBuilder() 525 | val var2: Iterator> = map.entries.iterator() 526 | while (var2.hasNext()) { 527 | val (key, value) = var2.next() 528 | try { 529 | sb.append(key).append("=").append(URLEncoder.encode(value, "utf-8")).append("&") 530 | } catch (var5: UnsupportedEncodingException) { 531 | var5.printStackTrace() 532 | } 533 | } 534 | return sb.toString() 535 | } 536 | 537 | fun urlParamsEn(map: Map): String { 538 | val sb = StringBuilder() 539 | val var2: Iterator> = map.entries.iterator() 540 | while (var2.hasNext()) { 541 | val (key, value) = var2.next() 542 | sb.append(key).append("=").append(value).append("&") 543 | } 544 | return sb.toString() 545 | } 546 | 547 | } 548 | -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/utils/ScheduleUtils.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.utils 2 | 3 | import kotlinx.coroutines.* 4 | import java.util.* 5 | import kotlin.coroutines.CoroutineContext 6 | 7 | 8 | object ScheduleUtils : CoroutineScope { 9 | fun loopEvent(process: suspend () -> Unit, start: Date, period: Long): Timer { 10 | val timer = Timer() 11 | val task = object : TimerTask() { 12 | override fun run() { 13 | launch { 14 | process() 15 | } 16 | } 17 | } 18 | timer.schedule(task, start, period) 19 | return timer 20 | } 21 | 22 | override val coroutineContext: CoroutineContext = 23 | Dispatchers.IO + CoroutineName(this::class.simpleName!!) + SupervisorJob() 24 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/hcyacg/protocol/utils/UA.kt: -------------------------------------------------------------------------------- 1 | package com.hcyacg.protocol.utils 2 | 3 | enum class UA(private val value: String) { 4 | PC("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36"), 5 | CHROME_91( 6 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" 7 | ), 8 | MOBILE("Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.92 Mobile Safari/537.36"), QQ( 9 | "Mozilla/5.0 (Linux; Android 10; V1914A Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/6.2 TBS/045132 Mobile Safari/537.36 V1_AND_SQ_8.3.0_1362_YYB_D QQ/8.3.0.4480 NetType/4G WebP/0.3.0 Pixel/1080 StatusBarHeight/85 SimpleUISwitch/0 QQTheme/1000" 10 | ), 11 | QQ2("Mozilla/5.0 (Linux; Android 10; V1914A Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.120 MQQBrowser/6.2 TBS/045224 Mobile Safari/537.36 V1_AND_SQ_8.3.9_1424_YYB_D QQ/8.3.9.4635 NetType/4G WebP/0.3.0 Pixel/1080 StatusBarHeight/85 SimpleUISwitch/0 QQTheme/1000"), QQ3( 12 | "Mozilla/5.0 (Linux; Android 10; M2002J9E Build/QKQ1.191222.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.120 MQQBrowser/6.2 TBS/045230 Mobile Safari/537.36 V1_AND_SQ_8.4.5_1468_YYB_D QQ/8.4.5.4745 NetType/4G WebP/0.3.0 Pixel/1080 StatusBarHeight/70 SimpleUISwitch/0 QQTheme/1000 InMagicWin/0" 13 | ), 14 | OPPO("Mozilla/5.0 (Linux; Android 11; RMX3031 Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/91.0.4472.120 Mobile Safari/537.36 oppostore/200902 ColorOS/V11.1 brand/realme model/RMX3031"), PIXEL( 15 | "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Mobile Safari/537.36" 16 | ); 17 | 18 | override fun toString(): String { 19 | return "UA.$name(value=$value)" 20 | } 21 | 22 | fun getValue(): String { 23 | return this.value 24 | } 25 | } -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | ${CONSOLE_LOG_PATTERN} 13 | 14 | 15 | 16 | DEBUG 17 | 18 | 19 | 20 | 21 | 22 | ${log.path}/log_info.log 23 | 24 | 25 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 26 | UTF-8 27 | 28 | 29 | 30 | 31 | ${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log 32 | 33 | 100MB 34 | 35 | 36 | 15 37 | 38 | 39 | 40 | INFO 41 | 42 | 43 | 44 | 45 | 46 | ${log.path}/log_warn.log 47 | 48 | 49 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 50 | UTF-8 51 | 52 | 53 | 54 | ${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log 55 | 56 | 100MB 57 | 58 | 59 | 15 60 | 61 | 62 | 63 | warn 64 | ACCEPT 65 | DENY 66 | 67 | 68 | 69 | 70 | 71 | ${log.path}/log_error.log 72 | 73 | 74 | %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 75 | UTF-8 76 | 77 | 78 | 79 | ${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log 80 | 81 | 100MB 82 | 83 | 84 | 15 85 | 86 | 87 | 88 | ERROR 89 | ACCEPT 90 | DENY 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | --------------------------------------------------------------------------------