├── .gitignore ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main ├── java │ └── com │ │ └── ame │ │ └── mihoyosign │ │ ├── MiHoYoSignApplication.java │ │ ├── config │ │ ├── RestTemplateConfig.java │ │ ├── SQLiteDialect.java │ │ └── WebSocketConfig.java │ │ ├── controller │ │ └── MiraiWebSocket.java │ │ ├── dao │ │ ├── MiHoYoRepository.java │ │ ├── RewardRepository.java │ │ ├── RoleRepository.java │ │ └── UserRepository.java │ │ ├── entity │ │ ├── Reward.java │ │ ├── Role.java │ │ ├── User.java │ │ └── mirai │ │ │ ├── Message.java │ │ │ └── Sender.java │ │ ├── exception │ │ ├── MiHoYoApiException.java │ │ ├── NoGameException.java │ │ ├── NotAdminException.java │ │ ├── NullRoleException.java │ │ └── NullUserException.java │ │ ├── service │ │ ├── AdminService.java │ │ ├── MessageService.java │ │ ├── MiHoYoService.java │ │ ├── MiraiService.java │ │ ├── RewardService.java │ │ ├── RoleService.java │ │ ├── SignService.java │ │ └── UserService.java │ │ └── util │ │ └── Utils.java └── resources │ ├── banner.txt │ └── static │ └── index.html └── test └── java └── com └── ame └── mihoyosign └── MiHoYoSignApplicationTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/** 6 | !**/src/test/** 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | 17 | ### IntelliJ IDEA ### 18 | .idea 19 | *.iws 20 | *.iml 21 | *.ipr 22 | out/ 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 米游社签到(miHoYoSign) 2 | 3 | **基于SpringBoot 和 CQHTTP 的 米游社自动签到机器人,支持原神和崩坏3,可以带小伙伴一起使用哦** 4 | 5 | - 此软件仅供学习参考,不得用于非法盈利 6 | - 本来只是为了自己用的,不是重大问题,大概率不会继续更新 7 | - 写的很烂,有很多地方不符合规范,还望海涵 8 | 9 | ### 获取米游社Cookie: 10 | 11 | **cookie抓取参考[Womsxd/AutoMihoyoBBS](https://github.com/Womsxd/AutoMihoyoBBS)** 12 | 13 | 1. 打开你的浏览器,进入**无痕/隐身模式** 14 | 2. 由于米哈游修改了bbs可以获取的Cookie,导致一次获取的Cookie缺失,所以需要增加步骤 15 | 3. 打开`http://bbs.mihoyo.com/ys/` 并进行登入操作 16 | 4. 在上一步登入完成后新建标签页,打开`http://user.mihoyo.com/` 并进行登入操作 (如果你不需要自动获取米游币可以忽略这个步骤,并把`mihoyobbs`的`enable`改为`false`即可) 17 | 5. 按下键盘上的`F12`或右键检查,打开开发者工具,点击Console 18 | 6. 输入 19 | ```javascript 20 | var cookie=document.cookie;var ask=confirm('Cookie:'+cookie+'\n\nDo you want to copy the cookie to the clipboard?');if(ask==true){copy(cookie);msg=cookie}else{msg='Cancel'} 21 | ``` 22 | 回车执行,并在确认无误后点击确定。 23 | 7. **此时Cookie已经复制到你的粘贴板上了** 24 | 25 | ### 获取UA: 26 | 27 | - **用米游社APP扫描下面二维码,复制 User-Agent** 28 | - [![image.png](https://i.postimg.cc/FKxHmRxX/image.png)](https://postimg.cc/xkc23976) 29 | 30 | ## 一. 功能 31 | 32 | - 支持多用户,一个QQ号可以绑定一个米游社账号,让你的小伙伴可以一起享受自动签到的乐趣 33 | - 管理员模式(管理员QQ 可以以某个QQ号的身份 向本机器人发送消息) 34 | - 能发送通知,签到是否成功,领了个啥(默认关,且需要加为好友) 35 | - 支持自定义延迟,支持自定义UA 36 | 37 | ## 二. 使用 38 | 39 | ### 1. 准备 40 | 41 | - java运行环境 42 | - MySQL (SQlite数据库会自动创建) 43 | - OneBot Mirai (CQHTTP Mirai) 或 go-cqhttp 等 OneBot标准环境 44 | - 注意:OneBot Mirai 的群临时消息类型不为private,而为group,不符合OneBot标准,所以必须加为好友才能使用除帮助外的命令 45 | ### 2. 编译配置文件 46 | 1.新建application.yml,放到mihoyosign.jar的同级目录 47 | 48 | application.yml 49 | 50 | ```yml 51 | debug: false 52 | server: 53 | # 签到服务器端口 54 | port: 4404 55 | 56 | ##[sqlite]删除下面的注释 57 | #spring: 58 | # datasource: 59 | # # 数据库驱动 60 | # driver-class-name: org.sqlite.JDBC 61 | # # 数据库地址 62 | # url: jdbc:sqlite:mihoyo_sign.sqlite 63 | # jpa: 64 | # database-platform: org.sqlite.hibernate.dialect.SQLiteDialect 65 | # hibernate: 66 | # ddl-auto: update 67 | # open-in-view: true 68 | 69 | #[mysql]删除下面的注释并修改参数 70 | spring: 71 | datasource: 72 | # 数据库驱动 73 | driver-class-name: com.mysql.cj.jdbc.Driver 74 | # 数据库地址 75 | url: jdbc:mysql://192.168.1.103:3306/mihoyo_sign?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 76 | # 数据库用户名 77 | username: root 78 | # 数据库密码 79 | password: "123456789" 80 | jpa: 81 | database-platform: org.hibernate.dialect.MySQL5InnoDBDialect 82 | hibernate: 83 | ddl-auto: update 84 | open-in-view: true 85 | 86 | app-config: 87 | # 帮助的回复 88 | help: | 89 | 命令: 90 | 绑定 [*cookie] 91 | 解绑 (切换账号需要先解绑,更新cookie不用) 92 | 修改UA [UA] 93 | 修改api延迟 [延迟(单位:秒 例:0~10)] 94 | 修改签到延迟 [延迟] 95 | 开启签到 [游戏名] [游戏角色UID] 96 | 关闭签到 [游戏名] [游戏角色UID] 97 | 签到 [游戏名] [游戏角色UID] 98 | 开启通知 [游戏名] [游戏角色UID] 99 | 关闭通知 [游戏名] [游戏角色UID] 100 | 所有角色 101 | 已开启角色 102 | 例如: 103 | 开启签到 原神 100000000 104 | 开启指定角色的自动签到 105 | 开启签到 原神 106 | 开启指定游戏的所有角色 107 | 开启签到 100000000 108 | 开启指定UID的所有角色 109 | 开启签到 110 | 开启所有角色 111 | 注意: 112 | []为参数,发送的时候参数不要带[],带*的为必填参数 113 | 抓取米游社cokkie 自行百度,方法有很多,一搜一大把 114 | 抓取UA 用米游社扫一扫以下二维码 115 | [CQ:image,file=https://i.postimg.cc/FKxHmRxX/image.png] 116 | 117 | 118 | # OneBot Mirai地址 119 | url: http://192.168.1.103:4400 120 | # 管理员的QQ号 121 | admin-qq-id: 123456789 122 | # 日志发送到哪个群 -1:不发送到群 -2:发送给管理员 123 | log-group-id: 987654321 124 | # 默认是否发签到结果通知 125 | is-notice: false 126 | # 默认发送miHoYoApi随机延迟范围(单位:秒) 127 | api-delay: '[2,10]' 128 | # 自定义api延迟必须所在范围 129 | api-delay-max: '[0,120]' 130 | # 默认自动签到随机延迟范围(单位:秒) 131 | sign-delay: '[0,1200]' 132 | # 自定义自动签到随机延迟必须所在范围 133 | sign-delay-max: '[0,7200]' 134 | # 签到计划执行时间(每天00:00 8:00 16:00) 135 | sign-cron: 0 0 0,8,16 * * ? 136 | # 奖励列表更新频率(每月1日00:00:00,程序启动时会自动执行一次) 137 | sign-reward-cron: 0 0 0 1 * ? 138 | 139 | # 命令 140 | # 收到以左列关键字开头的消息,执行对应右列功能 141 | # 左列不能重复,右列可以重复 142 | # 记得结尾打英文逗号,最后一行不要打逗号 143 | commands: "{ 144 | '帮助' : '帮助', 145 | '绑定' : '绑定', 146 | '解绑' : '解绑', 147 | '修改UA' : '修改UA', 148 | '修改api延迟' : '修改api延迟', 149 | '修改签到延迟' : '修改签到延迟', 150 | '开启签到' : '开启签到', 151 | '关闭签到' : '关闭签到', 152 | '签到' : '签到', 153 | '开启通知' : '开启通知', 154 | '关闭通知' : '关闭通知', 155 | '所有角色' : '所有角色', 156 | '已开启角色' : '已开启角色', 157 | '所有用户角色' : '所有用户角色', 158 | '通知' : '通知', 159 | '全部签到' : '全部签到' 160 | }" 161 | 162 | # 这些不需要改 163 | salt: 9nQiU3AV0rJSIBWgdynfoGMGKaklfbM7 164 | # 版本号 165 | version: 2.34.1 166 | # 默认UA 注意修改末尾版本号为{version} 167 | UA: Mozilla/5.0 (Linux; Android 12; MIX2 Build/SKQ1.220303.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/104.0.5112.97 Mobile Safari/537.36 miHoYoBBS/{version} 168 | # header 169 | headers: "{ 170 | 'Accept': 'application/json, text/plain, */*', 171 | 'Content_Type': 'application/json;charset=UTF-8', 172 | 'Connection': 'keep-alive', 173 | 'Origin': 'https://webstatic.mihoyo.com', 174 | 'X_Requested_With': 'com.mihoyo.hyperion', 175 | 'Sec_Fetch_Site': 'same-site', 176 | 'Sec_Fetch_Mode': 'cors', 177 | 'Sec_Fetch_Dest': 'empty', 178 | 'Accept_Encoding': 'gzip,deflate', 179 | 'Accept_Language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7', 180 | 181 | 'Content-Length': '66', 182 | 'x-rpc-client_type': '5', 183 | 'Referer': 'https://webstatic.mihoyo.com/bbs/event/signin-ys/index.html?bbs_auth_required=true&act_id=e202009291139501&utm_source=bbs&utm_medium=mys&utm_campaign=icon', 184 | 'x-rpc-app_version': '2.34.1' 185 | }" 186 | ``` 187 | 188 | 2.编辑OneBot Mirai 的 sitting.yml 189 | 190 | ```yml 191 | debug: false 192 | proxy: "" 193 | #机器人QQ号 194 | '123456789': 195 | cacheImage: false 196 | cacheRecord: false 197 | heartbeat: 198 | enable: false 199 | interval: 15000 200 | http: 201 | #需要开启 202 | enable: true 203 | host: 0.0.0.0 204 | #HTTP API服务器监听端口 205 | port: 4400 206 | accessToken: "" 207 | postUrl: "" 208 | postMessageFormat: string 209 | secret: "" 210 | ws_reverse: 211 | # 开启反向代理 212 | - enable: true 213 | postMessageFormat: string 214 | # 签到服务器ip 215 | reverseHost: 0.0.0.0 216 | # 签到服务器端口 217 | reversePort: 4404 218 | accessToken: '' 219 | # 反向Websocket路径 220 | reversePath: /mihoyosign 221 | # 反向Websocket Api路径 222 | reverseApiPath: /api 223 | # 反向Websocket Event路径 224 | reverseEventPath: /event 225 | useUniversal: true 226 | useTLS: false 227 | # 反向 WebSocket 客户端断线重连间隔,单位毫秒 228 | reconnectInterval: 3000 229 | 230 | #其他反向代理设置 231 | - enable: false 232 | postMessageFormat: string 233 | ... 234 | ... 235 | ``` 236 | 具体参数参考[OneBot Mirai](https://github.com/yyuueexxiinngg/onebot-kotlin) 237 | 238 | ### 3. 运行jar包 239 | 命令: 240 | ``` 241 | java -jar miHoYoSign.jar 242 | ``` 243 | ### 4.运行mirai 244 | 245 | ## 三. 命令格式 246 | ``` 247 | 用户命令: 248 | 参考 application.yml 配置文件中的 help 参数 249 | 250 | 管理员命令: 251 | 所有用户角色 252 | 通知 [通知内容] [QQ号] 253 | 全部签到 254 | (QQ:[QQ号])命令 #这是以某个QQ号的身份发送命令 255 | 256 | ``` 257 | 258 | ## 四. 预览图 259 | 260 | [![2022-09-14-230435.png](https://i.postimg.cc/KzVxfgNF/2022-09-14-230435.png)](https://postimg.cc/34gsJNVb) 261 | 262 | ## 五. 注意事项 263 | 264 | - 注意服务器时间要为中国时间 265 | 266 | ## 直接或间接引用到的其他开源项目 267 | 268 | - [OneBot Mirai](https://github.com/yyuueexxiinngg/onebot-kotlin) 269 | - [genshin-impact-helper](https://github.com/y1ndan/genshin-impact-helper) 270 | - [Womsxd/AutoMihoyoBBS](https://github.com/Womsxd/AutoMihoyoBBS) 271 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '2.3.7.RELEASE' 3 | id 'io.spring.dependency-management' version '1.0.10.RELEASE' 4 | id 'java' 5 | } 6 | 7 | group = 'com.ame' 8 | //version = '1.2' 9 | sourceCompatibility = '1.8' 10 | 11 | configurations { 12 | developmentOnly 13 | runtimeClasspath { 14 | extendsFrom developmentOnly 15 | } 16 | compileOnly { 17 | extendsFrom annotationProcessor 18 | } 19 | } 20 | 21 | repositories { 22 | mavenCentral() 23 | } 24 | 25 | dependencies { 26 | implementation 'com.alibaba:fastjson:1.2.73' 27 | // implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.0' 28 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 29 | implementation 'org.springframework.boot:spring-boot-starter-web' 30 | implementation 'org.springframework.boot:spring-boot-starter-websocket' 31 | compileOnly 'org.projectlombok:lombok' 32 | developmentOnly 'org.springframework.boot:spring-boot-devtools' 33 | runtimeOnly 'mysql:mysql-connector-java' 34 | runtimeOnly 'org.xerial:sqlite-jdbc' 35 | annotationProcessor 'org.projectlombok:lombok' 36 | annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' 37 | testImplementation('org.springframework.boot:spring-boot-starter-test') { 38 | exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' 39 | } 40 | } 41 | 42 | test { 43 | useJUnitPlatform() 44 | } 45 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AMEKENN/miHoYoSign/a7feeb9ee8ca67b5bf7a6cac86c513e91a1cd6c5/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.4.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | 88 | @rem Execute Gradle 89 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 90 | 91 | :end 92 | @rem End local scope for the variables with windows NT shell 93 | if "%ERRORLEVEL%"=="0" goto mainEnd 94 | 95 | :fail 96 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 97 | rem the _cmd.exe /c_ return code! 98 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 99 | exit /b 1 100 | 101 | :mainEnd 102 | if "%OS%"=="Windows_NT" endlocal 103 | 104 | :omega 105 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'miHoYoSign' 2 | -------------------------------------------------------------------------------- /src/main/java/com/ame/mihoyosign/MiHoYoSignApplication.java: -------------------------------------------------------------------------------- 1 | package com.ame.mihoyosign; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.scheduling.annotation.EnableAsync; 6 | import org.springframework.scheduling.annotation.EnableScheduling; 7 | 8 | @EnableAsync 9 | @EnableScheduling 10 | @SpringBootApplication 11 | public class MiHoYoSignApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(MiHoYoSignApplication.class, args); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/ame/mihoyosign/config/RestTemplateConfig.java: -------------------------------------------------------------------------------- 1 | package com.ame.mihoyosign.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.http.converter.BufferedImageHttpMessageConverter; 6 | import org.springframework.http.converter.HttpMessageConverter; 7 | import org.springframework.web.client.RestTemplate; 8 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 9 | 10 | import java.util.List; 11 | 12 | @Configuration 13 | public class RestTemplateConfig implements WebMvcConfigurer { 14 | 15 | @Bean 16 | public RestTemplate restTemplate() { 17 | return new RestTemplate(); 18 | } 19 | 20 | @Override 21 | public void extendMessageConverters(List> converters) { 22 | converters.add(new BufferedImageHttpMessageConverter()); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/ame/mihoyosign/config/SQLiteDialect.java: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AMEKENN/miHoYoSign/a7feeb9ee8ca67b5bf7a6cac86c513e91a1cd6c5/src/main/java/com/ame/mihoyosign/config/SQLiteDialect.java -------------------------------------------------------------------------------- /src/main/java/com/ame/mihoyosign/config/WebSocketConfig.java: -------------------------------------------------------------------------------- 1 | package com.ame.mihoyosign.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.socket.server.standard.ServerEndpointExporter; 6 | 7 | @Configuration 8 | public class WebSocketConfig { 9 | 10 | /** 11 | * 注入一个ServerEndpointExporter,该Bean会自动注册使用@ServerEndpoint注解申明的websocket endpoint 12 | */ 13 | @Bean 14 | public ServerEndpointExporter serverEndpointExporter() { 15 | return new ServerEndpointExporter(); 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /src/main/java/com/ame/mihoyosign/controller/MiraiWebSocket.java: -------------------------------------------------------------------------------- 1 | package com.ame.mihoyosign.controller; 2 | 3 | import com.ame.mihoyosign.service.MiraiService; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.stereotype.Controller; 6 | 7 | import javax.annotation.Resource; 8 | import javax.websocket.OnClose; 9 | import javax.websocket.OnError; 10 | import javax.websocket.OnMessage; 11 | import javax.websocket.OnOpen; 12 | import javax.websocket.server.ServerEndpoint; 13 | 14 | 15 | @Slf4j 16 | @ServerEndpoint(value = "/mihoyosign") 17 | @Controller 18 | public class MiraiWebSocket { 19 | 20 | //记录当前在线连接数 private static final AtomicInteger onlineCount = new AtomicInteger(0); 21 | //存放所有在线的客户端 private static final Map clients = new ConcurrentHashMap<>(); 22 | 23 | private static MiraiService miraiService; 24 | 25 | @Resource 26 | public void setMiraiService(MiraiService miraiService) { 27 | MiraiWebSocket.miraiService = miraiService; 28 | } 29 | 30 | @OnOpen 31 | public void onOpen() { 32 | log.info("已连接mirai"); 33 | } 34 | 35 | @OnClose 36 | public void onClose() { 37 | log.info("已断开mirai"); 38 | } 39 | 40 | @OnError 41 | public void onError(Throwable error) { 42 | log.error("mirai意外掉线"); 43 | error.printStackTrace(); 44 | } 45 | 46 | @OnMessage 47 | public void onMessage(String messageStr) { 48 | miraiService.onMessage(messageStr); 49 | } 50 | } -------------------------------------------------------------------------------- /src/main/java/com/ame/mihoyosign/dao/MiHoYoRepository.java: -------------------------------------------------------------------------------- 1 | package com.ame.mihoyosign.dao; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.ame.mihoyosign.entity.Role; 5 | import org.springframework.http.*; 6 | import org.springframework.stereotype.Repository; 7 | import org.springframework.web.client.RestTemplate; 8 | 9 | import javax.annotation.Resource; 10 | 11 | @Repository 12 | public class MiHoYoRepository { 13 | 14 | @Resource 15 | private RestTemplate restTemplate; 16 | 17 | /** 18 | * 发送 验证码 19 | * 20 | * @deprecated 已失效 21 | */ 22 | @Deprecated 23 | public JSONObject createMobileCaptcha(String phone) { 24 | String url = "https://webapi.account.mihoyo.com/Api/create_mobile_captcha"; 25 | String body = "action_type=login&mobile=" + phone + "&action_ticket=&no_sms=false"; 26 | HttpHeaders headers = new HttpHeaders(); 27 | headers.setContentType(MediaType.parseMediaType("application/x-www-form-urlencoded")); 28 | HttpEntity requestEntity = new HttpEntity<>(body, headers); 29 | return restTemplate.postForObject(url, requestEntity, JSONObject.class); 30 | } 31 | 32 | /** 33 | * 通过 验证码 获取 LoginTicket 34 | * @deprecated 已失效 35 | */ 36 | @Deprecated 37 | public JSONObject loginByMobileCaptcha(String phone, String captcha) { 38 | String url = "https://webapi.account.mihoyo.com/Api/login_by_mobilecaptcha"; 39 | String p = "mobile=" + phone + "&mobile_captcha=" + captcha; 40 | return restTemplate.getForObject(url + "?" + p, JSONObject.class); 41 | } 42 | 43 | /** 44 | * 通过 LoginTicket 获取 token 45 | * @deprecated 已失效 46 | */ 47 | @Deprecated 48 | public JSONObject getMultiTokenByLoginTicket(String webLoginToken, String accountId) { 49 | String url = "https://api-takumi.mihoyo.com/auth/api/getMultiTokenByLoginTicket"; 50 | String p = "login_ticket=" + webLoginToken 51 | + "&token_types=3&uid=" + accountId; 52 | return restTemplate.getForObject(url + "?" + p, JSONObject.class); 53 | } 54 | 55 | /** 56 | * 通过 LoginTicket 获取 cookie 57 | * @deprecated 已失效 58 | */ 59 | @Deprecated 60 | public JSONObject getCookieAccountInfoByLoginTicket(String webLoginToken) { 61 | String url = "https://webapi.account.mihoyo.com/Api/cookie_accountinfo_by_loginticket"; 62 | String p = "login_ticket=" + webLoginToken; 63 | return restTemplate.getForObject(url + "?" + p, JSONObject.class); 64 | } 65 | 66 | /** 67 | * 传 cookie 获取 游戏角色 68 | */ 69 | public JSONObject getRolesByCookie(String cookie, String gameBiz) { 70 | if (gameBiz == null) { 71 | gameBiz = ""; 72 | } 73 | String url = "https://api-takumi.mihoyo.com/binding/api/getUserGameRolesByCookie?game_biz=" + gameBiz; 74 | HttpHeaders headers = new HttpHeaders(); 75 | headers.add(HttpHeaders.COOKIE, cookie); 76 | HttpEntity httpEntity = new HttpEntity<>(null, headers); 77 | ResponseEntity re = restTemplate.exchange(url, HttpMethod.GET, httpEntity, JSONObject.class); 78 | return re.getBody(); 79 | } 80 | 81 | /** 82 | * 获取原神签到奖励列表 83 | */ 84 | public JSONObject getYsSignRewards() { 85 | String url = "https://api-takumi.mihoyo.com/event/bbs_sign_reward/home?act_id=e202009291139501"; 86 | return restTemplate.getForObject(url, JSONObject.class); 87 | } 88 | 89 | /** 90 | * 获取原神累计签到天数 91 | */ 92 | public JSONObject getYsSignInfo(Role role, HttpHeaders headers) { 93 | String url = "https://api-takumi.mihoyo.com/event/bbs_sign_reward/info"; 94 | String p = "act_id=e202009291139501" + 95 | "®ion=" + role.getRegion() + 96 | "&uid=" + role.getGameUid(); 97 | HttpEntity httpEntity = new HttpEntity<>(null, headers); 98 | ResponseEntity re = restTemplate.exchange(url + "?" + p, HttpMethod.GET, httpEntity, JSONObject.class); 99 | return re.getBody(); 100 | } 101 | 102 | public JSONObject ysSign(Role role, HttpHeaders headers) { 103 | String url = "https://api-takumi.mihoyo.com/event/bbs_sign_reward/sign"; 104 | JSONObject jsonObject = new JSONObject(); 105 | jsonObject.put("act_id", "e202009291139501"); 106 | jsonObject.put("region", role.getRegion()); 107 | jsonObject.put("uid", role.getGameUid()); 108 | HttpEntity requestEntity = new HttpEntity<>(jsonObject, headers); 109 | return restTemplate.postForObject(url, requestEntity, JSONObject.class); 110 | } 111 | 112 | /** 113 | * 获取崩坏3签到奖励列表 114 | */ 115 | public JSONObject getBh3SignRewards() { 116 | String url = "https://api-takumi.mihoyo.com/event/luna/home?act_id=e202207181446311"; 117 | return restTemplate.getForObject(url, JSONObject.class); 118 | } 119 | 120 | /** 121 | * 获取崩坏3累计签到天数 122 | */ 123 | public JSONObject getBh3SignInfo(Role role, HttpHeaders headers) { 124 | String url = "https://api-takumi.mihoyo.com/event/luna/info"; 125 | String p = "act_id=e202207181446311" + 126 | "®ion=" + role.getRegion() + 127 | "&uid=" + role.getGameUid(); 128 | HttpEntity httpEntity = new HttpEntity<>(null, headers); 129 | ResponseEntity re = restTemplate.exchange(url + "?" + p, HttpMethod.GET, httpEntity, JSONObject.class); 130 | return re.getBody(); 131 | } 132 | 133 | public JSONObject bh3Sign(Role role, HttpHeaders headers) { 134 | String url = "https://api-takumi.mihoyo.com/event/luna/sign"; 135 | JSONObject jsonObject = new JSONObject(); 136 | jsonObject.put("act_id", "e202207181446311"); 137 | jsonObject.put("region", role.getRegion()); 138 | jsonObject.put("uid", role.getGameUid()); 139 | HttpEntity requestEntity = new HttpEntity<>(jsonObject, headers); 140 | return restTemplate.postForObject(url, requestEntity, JSONObject.class); 141 | } 142 | 143 | } 144 | 145 | -------------------------------------------------------------------------------- /src/main/java/com/ame/mihoyosign/dao/RewardRepository.java: -------------------------------------------------------------------------------- 1 | package com.ame.mihoyosign.dao; 2 | 3 | 4 | import com.ame.mihoyosign.entity.Reward; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.transaction.annotation.Transactional; 7 | 8 | 9 | public interface RewardRepository extends JpaRepository { 10 | 11 | Reward findRewardByGameBizAndDay(String gameBiz,int day); 12 | 13 | @Transactional 14 | void deleteRewardsByGameBiz(String gameBiz); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/ame/mihoyosign/dao/RoleRepository.java: -------------------------------------------------------------------------------- 1 | package com.ame.mihoyosign.dao; 2 | 3 | 4 | import com.ame.mihoyosign.entity.Role; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | 7 | import java.util.List; 8 | 9 | public interface RoleRepository extends JpaRepository { 10 | 11 | List findRolesByUserId(long userId); 12 | 13 | List findRolesByUserIdAndGameBiz(long userId, String gameBiz); 14 | 15 | List findRolesByUserIdAndGameUid(long userId, String gameUid); 16 | 17 | List findRolesByUserIdAndGameBizAndGameUid(long userId, String gameBiz, String gameUid); 18 | 19 | Role findRoleByUserIdAndGameBizAndGameUid(long userId, String gameBiz, String gameUid); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/ame/mihoyosign/dao/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.ame.mihoyosign.dao; 2 | 3 | import com.ame.mihoyosign.entity.User; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface UserRepository extends JpaRepository { 7 | 8 | User findUserById(long qqId); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/ame/mihoyosign/entity/Reward.java: -------------------------------------------------------------------------------- 1 | package com.ame.mihoyosign.entity; 2 | 3 | 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.persistence.Entity; 9 | import javax.persistence.GeneratedValue; 10 | import javax.persistence.Id; 11 | import javax.persistence.Table; 12 | 13 | @Data 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | @Entity 17 | @Table 18 | public class Reward { 19 | 20 | @Id 21 | @GeneratedValue 22 | private int id; 23 | private int month; 24 | private int day; 25 | private String gameBiz; 26 | private String icon; 27 | private String name; 28 | private int cnt; 29 | 30 | public static String getInfo(Reward reward) { 31 | if (reward == null) { 32 | return "奖励" + "暂未更新奖励"; 33 | } else { 34 | return "天数:" + reward.day + "天\n" + 35 | "奖励:" + reward.getName() + "×" + reward.getCnt() + 36 | "\n[CQ:image,file=" + reward.getIcon() + "]\n"; 37 | } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/ame/mihoyosign/entity/Role.java: -------------------------------------------------------------------------------- 1 | package com.ame.mihoyosign.entity; 2 | 3 | import com.ame.mihoyosign.exception.NoGameException; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | 9 | import javax.persistence.*; 10 | import java.util.List; 11 | 12 | @Getter 13 | @Data 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | @Entity 17 | @Table 18 | public class Role { 19 | @Id 20 | @GeneratedValue 21 | private long id; 22 | private String gameBiz; 23 | private String region; 24 | private String gameUid; 25 | private String regionName; 26 | private String nickname; 27 | private int level; 28 | @Column(nullable=false,columnDefinition="boolean default false") 29 | private boolean notice; 30 | //可选属性optional=false,表示user不能为空。删除角色,不影响用户 31 | @ManyToOne(cascade = {CascadeType.MERGE, CascadeType.REFRESH}, optional = false) 32 | //设置在role表中的关联字段(外键) 33 | @JoinColumn(name = "user_id", referencedColumnName = "id", foreignKey = @ForeignKey(name = "role_user_fk")) 34 | private User user; 35 | 36 | public String getInfo() { 37 | return "游戏:" + getGameName(gameBiz) + 38 | "\n昵称:" + getNickname() + 39 | "\n等级:" + getLevel() + 40 | "\nUID:" + getGameUid() + 41 | "\n区服:" + getRegionName(); 42 | } 43 | 44 | public static String getRolesInfo(List roles) { 45 | StringBuilder allRoles = new StringBuilder(); 46 | for (Role r : roles) { 47 | allRoles.append("\n\n").append(r.getInfo()); 48 | } 49 | return allRoles.substring(2); 50 | } 51 | 52 | public static String getGameBiz(String gameName) throws NoGameException { 53 | switch (gameName) { 54 | case "原神": 55 | return "hk4e_cn"; 56 | case "崩坏3": 57 | return "bh3_cn"; 58 | default: 59 | throw new NoGameException(); 60 | } 61 | } 62 | 63 | public static String getGameName(String gameBiz) { 64 | switch (gameBiz) { 65 | case "hk4e_cn": 66 | return "原神"; 67 | case "bh3_cn": 68 | return "崩坏3"; 69 | default: 70 | return null; 71 | } 72 | } 73 | 74 | } 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /src/main/java/com/ame/mihoyosign/entity/User.java: -------------------------------------------------------------------------------- 1 | package com.ame.mihoyosign.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.persistence.*; 8 | import java.util.List; 9 | 10 | @Data 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | @Entity 14 | @Table 15 | public class User { 16 | @Id 17 | private Long id; 18 | @Column(length = 2048) 19 | private String cookie; 20 | @Column(length = 2048) 21 | private String ua; 22 | private String deviceId; 23 | @Column(columnDefinition = "varchar(255) default '[2,10]'") 24 | private String apiDelay; 25 | @Column(columnDefinition = "varchar(255) default '[0,1200]'") 26 | private String signDelay; 27 | //级联保存、更新、删除、刷新;延迟加载。当删除用户,会级联删除该用户的所有角色 28 | //拥有mappedBy注解的实体类为关系被维护端 29 | //mappedBy="user"是role中的user属性 30 | @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) 31 | private List roleList;//角色列表 32 | 33 | } 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/main/java/com/ame/mihoyosign/entity/mirai/Message.java: -------------------------------------------------------------------------------- 1 | package com.ame.mihoyosign.entity.mirai; 2 | 3 | import lombok.*; 4 | 5 | @Data 6 | @NoArgsConstructor 7 | @AllArgsConstructor 8 | public class Message { 9 | 10 | private String request_type; 11 | private String comment; 12 | private String flag; 13 | 14 | private String type; 15 | private long self_id; 16 | private String sub_type; 17 | private long message_id; 18 | private long user_id; 19 | private long group_id; 20 | private String message; 21 | private String raw_message; 22 | private long font; 23 | private Sender sender; 24 | private long time; 25 | private String post_type; 26 | private String message_type; 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/ame/mihoyosign/entity/mirai/Sender.java: -------------------------------------------------------------------------------- 1 | package com.ame.mihoyosign.entity.mirai; 2 | 3 | import lombok.*; 4 | 5 | @Data 6 | @NoArgsConstructor 7 | @AllArgsConstructor 8 | public class Sender { 9 | 10 | private long user_id; 11 | private String nickname; 12 | private String sex; 13 | private int age; 14 | private long group_id; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/ame/mihoyosign/exception/MiHoYoApiException.java: -------------------------------------------------------------------------------- 1 | package com.ame.mihoyosign.exception; 2 | 3 | public class MiHoYoApiException extends Exception { 4 | 5 | public MiHoYoApiException(String message) { 6 | super(message); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/ame/mihoyosign/exception/NoGameException.java: -------------------------------------------------------------------------------- 1 | package com.ame.mihoyosign.exception; 2 | 3 | public class NoGameException extends Exception { 4 | } -------------------------------------------------------------------------------- /src/main/java/com/ame/mihoyosign/exception/NotAdminException.java: -------------------------------------------------------------------------------- 1 | package com.ame.mihoyosign.exception; 2 | 3 | public class NotAdminException extends Exception { 4 | } -------------------------------------------------------------------------------- /src/main/java/com/ame/mihoyosign/exception/NullRoleException.java: -------------------------------------------------------------------------------- 1 | package com.ame.mihoyosign.exception; 2 | 3 | public class NullRoleException extends Exception{ 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/com/ame/mihoyosign/exception/NullUserException.java: -------------------------------------------------------------------------------- 1 | package com.ame.mihoyosign.exception; 2 | 3 | public class NullUserException extends Exception{ 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/com/ame/mihoyosign/service/AdminService.java: -------------------------------------------------------------------------------- 1 | package com.ame.mihoyosign.service; 2 | 3 | import com.ame.mihoyosign.dao.RoleRepository; 4 | import com.ame.mihoyosign.entity.Role; 5 | import com.ame.mihoyosign.entity.User; 6 | import com.ame.mihoyosign.exception.NotAdminException; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.scheduling.annotation.Async; 9 | import org.springframework.scheduling.annotation.Scheduled; 10 | import org.springframework.stereotype.Service; 11 | 12 | import javax.annotation.Resource; 13 | import java.util.List; 14 | 15 | @Service 16 | public class AdminService { 17 | 18 | @Resource 19 | private UserService userService; 20 | @Resource 21 | private MiHoYoService miHoYoService; 22 | @Resource 23 | private RoleRepository roleRepository; 24 | @Resource 25 | private SignService signService; 26 | @Resource 27 | private MessageService messageService; 28 | @Value("${app-config.admin-qq-id}") 29 | private long AdminQQId; 30 | 31 | public void checkAdmin(long qqId) throws NotAdminException { 32 | if (qqId != AdminQQId) { 33 | throw new NotAdminException(); 34 | } 35 | } 36 | 37 | public void updateRewards(long qqId) throws NotAdminException { 38 | checkAdmin(qqId); 39 | miHoYoService.updateRewards(); 40 | } 41 | 42 | public String sendNotice(long qqId, String notice, String toQQId) throws NotAdminException { 43 | checkAdmin(qqId); 44 | if (toQQId != null) { 45 | try { 46 | long to = Long.parseLong(toQQId); 47 | messageService.sendPrivateMsg(notice, to, -1); 48 | } catch (Exception e) { 49 | return "请输入正确的QQ号"; 50 | } 51 | } else { 52 | List allUser = userService.getAllUser(); 53 | for (User user : allUser) { 54 | messageService.sendPrivateMsg(notice, user.getId(), -1); 55 | } 56 | } 57 | return "已发送\n注:未加为好友会无法接收到"; 58 | } 59 | 60 | public String getAllRole(long qqId) throws NotAdminException { 61 | checkAdmin(qqId); 62 | StringBuilder re = new StringBuilder("「所有用户角色」"); 63 | List roles = roleRepository.findAll(); 64 | for (Role role : roles) { 65 | re.append("\n").append(role.getInfo()).append("\nQQ:").append(role.getUser().getId()).append("\n"); 66 | } 67 | return re.toString(); 68 | } 69 | 70 | @Async 71 | @Scheduled(cron = "${app-config.sign-cron}") 72 | public void signAll() { 73 | for (User user : userService.getAllUser()) { 74 | signService.sign(user); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/com/ame/mihoyosign/service/MessageService.java: -------------------------------------------------------------------------------- 1 | package com.ame.mihoyosign.service; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import lombok.extern.log4j.Log4j2; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.scheduling.annotation.Async; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.web.client.RestTemplate; 9 | 10 | import javax.annotation.Resource; 11 | 12 | @Log4j2 13 | @Service 14 | public class MessageService { 15 | @Resource 16 | private RestTemplate restTemplate; 17 | @Value("${app-config.url}") 18 | private String url; 19 | @Value("${app-config.log-group-id}") 20 | private long LogGroupId; 21 | @Value("${app-config.admin-qq-id}") 22 | private long adminQQId; 23 | 24 | public void sendMsg(String msg, String type, long userId, long groupId) { 25 | try { 26 | JSONObject json = new JSONObject(); 27 | json.put("message", msg); 28 | if (groupId != -1) { 29 | json.put("group_id", groupId); 30 | } 31 | if (userId != -1) { 32 | json.put("user_id", userId); 33 | } 34 | JSONObject re = restTemplate.postForObject(url + "/send_" + type + "_msg", json, JSONObject.class); 35 | if (re == null || re.getIntValue("retcode") != 0) { 36 | log.warn("发送消息失败"); 37 | } 38 | log.info("--> QQ[{}] Message:[{}]", userId, msg.replace("\n", " ")); 39 | } catch (Exception e) { 40 | e.printStackTrace(); 41 | log.warn("发送消息失败"); 42 | } 43 | } 44 | 45 | public void sendPrivateMsg(String msg, long userId, long groupId) { 46 | sendMsg(msg, "private", userId, groupId); 47 | } 48 | 49 | public void sendGroupMsg(String msg, long groupId) { 50 | sendMsg(msg, "group", -1, groupId); 51 | } 52 | 53 | @Async 54 | public void sendLog(String msg) { 55 | log.info(msg.replace("\n", " ")); 56 | if (LogGroupId == -2) { 57 | sendPrivateMsg(msg, adminQQId, -1); 58 | } else if (LogGroupId != -1) { 59 | sendGroupMsg(msg, LogGroupId); 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/ame/mihoyosign/service/MiHoYoService.java: -------------------------------------------------------------------------------- 1 | package com.ame.mihoyosign.service; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.ame.mihoyosign.dao.MiHoYoRepository; 5 | import com.ame.mihoyosign.entity.Reward; 6 | import com.ame.mihoyosign.entity.Role; 7 | import com.ame.mihoyosign.entity.User; 8 | import com.ame.mihoyosign.exception.MiHoYoApiException; 9 | import com.ame.mihoyosign.util.Utils; 10 | import lombok.extern.log4j.Log4j2; 11 | import org.springframework.beans.factory.annotation.Value; 12 | import org.springframework.boot.context.event.ApplicationReadyEvent; 13 | import org.springframework.context.event.EventListener; 14 | import org.springframework.http.HttpHeaders; 15 | import org.springframework.scheduling.annotation.Async; 16 | import org.springframework.scheduling.annotation.Scheduled; 17 | import org.springframework.stereotype.Service; 18 | import org.springframework.util.DigestUtils; 19 | 20 | import javax.annotation.Resource; 21 | import java.sql.Timestamp; 22 | import java.util.Calendar; 23 | import java.util.List; 24 | import java.util.stream.Collectors; 25 | 26 | @Log4j2 27 | @Service 28 | public class MiHoYoService { 29 | 30 | @Value("#{${app-config.headers}}") 31 | private HttpHeaders headers; 32 | @Value("${app-config.salt}") 33 | private String salt; 34 | @Value("${app-config.version}") 35 | private String version; 36 | @Value("${app-config.ua}") 37 | private String UA; 38 | @Resource 39 | private MiHoYoRepository miHoYoRepository; 40 | @Resource 41 | private RewardService rewardService; 42 | 43 | /** 44 | * 获取DS 45 | */ 46 | private String getDS() { 47 | Timestamp ts = new Timestamp(System.currentTimeMillis()); 48 | String t = Integer.toString((int) ((ts.getTime()) / 1000)); 49 | String r = Utils.getRandomFromArray(null, 6); 50 | String c = DigestUtils.md5DigestAsHex(("salt=" + salt + "&t=" + t + "&r=" + r).getBytes()); 51 | return t + "," + r + "," + c; 52 | } 53 | 54 | public HttpHeaders getHeaders(User user) { 55 | HttpHeaders h = new HttpHeaders(); 56 | h.addAll(headers); 57 | h.add("DS", getDS()); 58 | h.add("x-rpc-device_id", user.getDeviceId()); 59 | h.add("x-rpc-app_version", version); 60 | h.add(HttpHeaders.COOKIE, user.getCookie()); 61 | if (user.getUa() == null) { 62 | h.add(HttpHeaders.USER_AGENT, UA.replace("{version}", version)); 63 | } else { 64 | h.add(HttpHeaders.USER_AGENT, user.getUa(). 65 | replaceAll("miHoYoBBS/\\d+(.\\d+)*", "miHoYoBBS/" + version)); 66 | } 67 | return h; 68 | } 69 | 70 | 71 | public List getRoles(User user, String gameBiz, String gameUid) throws MiHoYoApiException { 72 | JSONObject rolesByCookie = miHoYoRepository.getRolesByCookie(user.getCookie(), gameBiz); 73 | if (rolesByCookie.getIntValue("retcode") != 0) { 74 | throw new MiHoYoApiException(rolesByCookie.getString("message")); 75 | } 76 | 77 | List roles = rolesByCookie.getJSONObject("data").getJSONArray("list").toJavaList(Role.class); 78 | if (gameUid == null) { 79 | return roles; 80 | } else { 81 | return roles.stream() 82 | .filter(role -> role.getGameUid().equals(gameUid)) 83 | .collect(Collectors.toList()); 84 | } 85 | } 86 | 87 | @Async 88 | @Scheduled(cron = "${app-config.sign-reward-cron}") 89 | @EventListener(ApplicationReadyEvent.class) 90 | public synchronized void updateRewards() { 91 | updateRewards("hk4e_cn", miHoYoRepository.getYsSignRewards()); 92 | updateRewards("bh3_cn", miHoYoRepository.getBh3SignRewards()); 93 | } 94 | 95 | public void updateRewards(String gameBiz, JSONObject signRewards) { 96 | Calendar calendar = Calendar.getInstance(); 97 | int day = calendar.get(Calendar.DAY_OF_MONTH); 98 | int month = calendar.get(Calendar.MONTH) + 1; 99 | Reward reward = rewardService.getReward(gameBiz, day); 100 | if (reward == null || month != reward.getMonth()) { 101 | List rewards = signRewards.getJSONObject("data").getJSONArray("awards").toJavaList(Reward.class); 102 | rewardService.deleteRewards(gameBiz); 103 | month = signRewards.getJSONObject("data").getIntValue("month"); 104 | for (int i = 0; i < rewards.size(); i++) { 105 | reward = rewards.get(i); 106 | reward.setDay(i + 1); 107 | reward.setGameBiz(gameBiz); 108 | reward.setMonth(month); 109 | } 110 | rewardService.updateReward(rewards); 111 | } 112 | } 113 | 114 | 115 | public Reward ysSign(Role role, User user, boolean isCommand) throws MiHoYoApiException { 116 | String apiDelay = user.getApiDelay(); 117 | Utils.delay(apiDelay); 118 | HttpHeaders headers = getHeaders(user); 119 | JSONObject signInInfo = miHoYoRepository.getYsSignInfo(role, headers); 120 | if (signInInfo.getJSONObject("data").getBooleanValue("is_sign") && isCommand) { 121 | int day = signInInfo.getJSONObject("data").getIntValue("total_sign_day"); 122 | return rewardService.getReward("hk4e_cn", day); 123 | } else if (signInInfo.getJSONObject("data").getBooleanValue("is_sign")) { 124 | return null; 125 | } 126 | Utils.delay(apiDelay); 127 | JSONObject sign = miHoYoRepository.ysSign(role, headers); 128 | if (sign.getIntValue("retcode") == 0) { 129 | Utils.delay(apiDelay); 130 | signInInfo = miHoYoRepository.getYsSignInfo(role, headers); 131 | if (signInInfo.getJSONObject("data").getBooleanValue("is_sign")) { 132 | int day = signInInfo.getJSONObject("data").getIntValue("total_sign_day"); 133 | return rewardService.getReward("hk4e_cn", day); 134 | } else { 135 | throw new MiHoYoApiException("需要验证码,建议修改为常用设备的User-Agent"); 136 | } 137 | } else { 138 | throw new MiHoYoApiException(sign.getString("message")); 139 | } 140 | } 141 | 142 | public Reward bh3Sign(Role role, User user, boolean isCommand) throws MiHoYoApiException { 143 | String apiDelay = user.getApiDelay(); 144 | Utils.delay(apiDelay); 145 | HttpHeaders headers = getHeaders(user); 146 | JSONObject signInInfo = miHoYoRepository.getBh3SignInfo(role, headers); 147 | if (signInInfo.getJSONObject("data").getBooleanValue("is_sign") && isCommand) { 148 | int day = signInInfo.getJSONObject("data").getIntValue("total_sign_day"); 149 | return rewardService.getReward("bh3_cn", day); 150 | } else if (signInInfo.getJSONObject("data").getBooleanValue("is_sign")) { 151 | return null; 152 | } 153 | Utils.delay(apiDelay); 154 | JSONObject sign = miHoYoRepository.bh3Sign(role, headers); 155 | if (sign.getIntValue("retcode") == 0) { 156 | Utils.delay(apiDelay); 157 | signInInfo = miHoYoRepository.getBh3SignInfo(role, headers); 158 | if (signInInfo.getJSONObject("data").getBooleanValue("is_sign")) { 159 | int day = signInInfo.getJSONObject("data").getIntValue("total_sign_day"); 160 | return rewardService.getReward("bh3_cn", day); 161 | } else { 162 | throw new MiHoYoApiException("需要验证码,建议修改为常用设备的User-Agent"); 163 | } 164 | } else { 165 | throw new MiHoYoApiException(sign.getString("message")); 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/main/java/com/ame/mihoyosign/service/MiraiService.java: -------------------------------------------------------------------------------- 1 | package com.ame.mihoyosign.service; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.ame.mihoyosign.entity.Role; 5 | import com.ame.mihoyosign.entity.mirai.Message; 6 | import com.ame.mihoyosign.exception.*; 7 | import lombok.extern.log4j.Log4j2; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.stereotype.Service; 10 | 11 | import javax.annotation.Resource; 12 | import java.util.Arrays; 13 | import java.util.Map; 14 | 15 | @Log4j2 16 | @Service 17 | public class MiraiService { 18 | 19 | @Value("${app-config.admin-qq-id}") 20 | private long adminQqId; 21 | @Value("${app-config.help}") 22 | private String help; 23 | @Value("#{${app-config.commands}}") 24 | private Map commands; 25 | @Resource 26 | private SignService signService; 27 | @Resource 28 | private RoleService roleService; 29 | @Resource 30 | private AdminService adminService; 31 | @Resource 32 | private UserService userService; 33 | @Resource 34 | private MiHoYoService miHoYoService; 35 | @Resource 36 | private MessageService messageService; 37 | 38 | 39 | public void onMessage(String messageStr) { 40 | //log.info(messageStr); 41 | Message message = JSON.parseObject(messageStr, Message.class); 42 | String msg = message.getMessage(); 43 | 44 | if ("group".equals(message.getMessage_type()) && "帮助".equals(commands.get(msg))) { 45 | messageService.sendGroupMsg(help, message.getGroup_id()); 46 | } else if ("private".equals(message.getMessage_type())) { 47 | long qqId = message.getUser_id(); 48 | log.info("<-- QQ:[{}] Message:[{}]", qqId, msg.replace("\n", " ")); 49 | 50 | //检测是否使用管理员模式 51 | boolean adminMode = false; 52 | if (msg.startsWith("(QQ:") && qqId == adminQqId) { 53 | try { 54 | qqId = Long.parseLong(msg.substring(4, msg.indexOf(')'))); 55 | msg = msg.substring(msg.indexOf(')') + 1); 56 | adminMode = true; 57 | } catch (Exception e) { 58 | messageService.sendPrivateMsg("管理员模式的格式错误", qqId, -1); 59 | return; 60 | } 61 | } 62 | 63 | String[] s = msg.split(" ", 3); 64 | String re; 65 | 66 | if (commands.containsKey(s[0])) { 67 | String command = commands.get(s[0]); 68 | String[] args = Arrays.copyOfRange(s, 1, 3); 69 | if (args[0] != null && args[0].matches("[0-9]+")) { 70 | re = function(qqId, msg, command, args[1], args[0]); 71 | } else { 72 | re = function(qqId, msg, command, args[0], args[1]); 73 | } 74 | } else { 75 | return; 76 | } 77 | if (re != null) { 78 | messageService.sendPrivateMsg(re, adminMode ? adminQqId : qqId, message.getSender().getGroup_id()); 79 | } 80 | } 81 | } 82 | 83 | private String function(long qqId, String msg, String command, String str, String num) { 84 | //所有功能 85 | String re = null; 86 | try { 87 | switch (command) { 88 | case "帮助": 89 | re = help; 90 | break; 91 | case "绑定": 92 | re = userService.bind(qqId, msg.substring(3)); 93 | break; 94 | case "解绑": 95 | re = userService.unbind(qqId); 96 | break; 97 | case "修改UA": 98 | re = userService.setUA(qqId, msg.substring(5)); 99 | break; 100 | case "修改api延迟": 101 | re = userService.setApiDelay(qqId, str); 102 | break; 103 | case "修改签到延迟": 104 | re = userService.setSignDelay(qqId, str); 105 | break; 106 | case "开启签到": 107 | re = roleService.turnOnSign(qqId, str, num); 108 | break; 109 | case "关闭签到": 110 | re = roleService.turnOffSign(qqId, str, num); 111 | break; 112 | case "签到": 113 | re = signService.sign(qqId, str, num); 114 | break; 115 | case "开启通知": 116 | re = roleService.changeNotice(qqId, str, num, true); 117 | break; 118 | case "关闭通知": 119 | re = roleService.changeNotice(qqId, str, num, false); 120 | break; 121 | case "所有角色": 122 | re = "「米游社中所有角色」\n" + Role.getRolesInfo(miHoYoService.getRoles(userService.getUser(qqId), null, null)); 123 | break; 124 | case "已开启角色": 125 | re = roleService.getSignOnRolesInfo(qqId); 126 | break; 127 | case "通知": 128 | re = adminService.sendNotice(qqId, str, num); 129 | break; 130 | case "所有用户角色": 131 | re = adminService.getAllRole(qqId); 132 | break; 133 | case "更新奖励列表": 134 | adminService.updateRewards(qqId); 135 | re = "正在更新奖励表"; 136 | break; 137 | case "全部签到": 138 | adminService.checkAdmin(qqId); 139 | adminService.signAll(); 140 | re = "正在全部签到"; 141 | break; 142 | } 143 | } catch (IndexOutOfBoundsException e) { 144 | re = "指令格式错误"; 145 | } catch (NullUserException e) { 146 | re = "未绑定米游社"; 147 | } catch (NullRoleException e) { 148 | re = "没有角色"; 149 | } catch (NoGameException e) { 150 | re = "游戏名错误"; 151 | } catch (MiHoYoApiException e) { 152 | re = "调用米游社API错误\n米游社返回消息:\n\t" + e.getMessage(); 153 | } catch (NotAdminException ignored) { 154 | } catch (Exception e) { 155 | re = "指令格式错误或执行过程中出错"; 156 | log.warn(re); 157 | e.printStackTrace(); 158 | } 159 | return re; 160 | } 161 | 162 | } 163 | -------------------------------------------------------------------------------- /src/main/java/com/ame/mihoyosign/service/RewardService.java: -------------------------------------------------------------------------------- 1 | package com.ame.mihoyosign.service; 2 | 3 | import com.ame.mihoyosign.dao.RewardRepository; 4 | import com.ame.mihoyosign.entity.Reward; 5 | import com.ame.mihoyosign.entity.Role; 6 | import org.springframework.stereotype.Service; 7 | 8 | import javax.annotation.Resource; 9 | import java.util.List; 10 | 11 | @Service 12 | public class RewardService { 13 | 14 | @Resource 15 | private RewardRepository rewardRepository; 16 | @Resource 17 | private MessageService messageService; 18 | 19 | public Reward getReward(String gameBiz, int day) { 20 | return rewardRepository.findRewardByGameBizAndDay(gameBiz, day); 21 | } 22 | 23 | public void updateReward(List rewards) { 24 | rewardRepository.saveAll(rewards); 25 | messageService.sendLog(Role.getGameName(rewards.get(0).getGameBiz()) + " 签到奖励列表已更新"); 26 | } 27 | 28 | public void deleteRewards(String gameBiz) { 29 | rewardRepository.deleteRewardsByGameBiz(gameBiz); 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/java/com/ame/mihoyosign/service/RoleService.java: -------------------------------------------------------------------------------- 1 | package com.ame.mihoyosign.service; 2 | 3 | import com.ame.mihoyosign.dao.RoleRepository; 4 | import com.ame.mihoyosign.entity.Role; 5 | import com.ame.mihoyosign.entity.User; 6 | import com.ame.mihoyosign.exception.MiHoYoApiException; 7 | import com.ame.mihoyosign.exception.NoGameException; 8 | import com.ame.mihoyosign.exception.NullRoleException; 9 | import com.ame.mihoyosign.exception.NullUserException; 10 | import org.springframework.beans.factory.annotation.Value; 11 | import org.springframework.stereotype.Service; 12 | 13 | import javax.annotation.Resource; 14 | import java.util.List; 15 | 16 | @Service 17 | public class RoleService { 18 | 19 | @Resource 20 | private RoleRepository roleRepository; 21 | @Resource 22 | private MiHoYoService miHoYoService; 23 | @Resource 24 | private UserService userService; 25 | @Resource 26 | private MessageService messageService; 27 | @Value("${app-config.is-notice}") 28 | private boolean isNotice; 29 | 30 | 31 | public String getSignOnRolesInfo(long qqId) { 32 | return "「已开启签到角色」\n" + Role.getRolesInfo(roleRepository.findRolesByUserId(qqId)); 33 | } 34 | 35 | public void updateNicknameAndLevel(User user) throws MiHoYoApiException, NullRoleException, NoGameException { 36 | List signOnRoles = getRoles(user.getId(),null,null); 37 | List allRoles = miHoYoService.getRoles(user, null, null); 38 | for (Role role : signOnRoles) { 39 | for (Role r : allRoles) { 40 | if (role.equals(r)) { 41 | r.setNickname(role.getNickname()); 42 | r.setLevel(role.getLevel()); 43 | roleRepository.save(r); 44 | break; 45 | } 46 | } 47 | } 48 | } 49 | 50 | public String turnOnSign(long qqId, String gameName, String gameUid) throws NullUserException, NoGameException, NullRoleException, MiHoYoApiException { 51 | User user = userService.getUser(qqId); 52 | String gameBiz = gameName == null ? null : Role.getGameBiz(gameName); 53 | List roles = miHoYoService.getRoles(user, gameBiz, gameUid); 54 | if (roles.isEmpty()) { 55 | throw new NullRoleException(); 56 | } 57 | 58 | StringBuilder s = new StringBuilder(); 59 | for (Role role : roles) { 60 | if (Role.getGameName(role.getGameBiz()) != null) { 61 | s.append("\n\n"); 62 | role.setUser(user); 63 | role.setNotice(isNotice); 64 | if (roleRepository.findRoleByUserIdAndGameBizAndGameUid(user.getId(), role.getGameBiz(), role.getGameUid()) != null) { 65 | s.append(role.getInfo()).append("\n失败:已开启自动签到,无需重复开启"); 66 | } else { 67 | roleRepository.save(role); 68 | s.append(role.getInfo()); 69 | } 70 | } 71 | } 72 | String rolesInfo = s.substring(2); 73 | 74 | messageService.sendLog("「开启签到」\nQQ:" + user.getId() + "\n「角色」\n" + rolesInfo); 75 | return "「开启签到」\n" + rolesInfo; 76 | } 77 | 78 | public String turnOffSign(long qqId, String gameName, String gameUid) throws NoGameException, NullRoleException { 79 | List roles = getRoles(qqId, gameName, gameUid); 80 | roleRepository.deleteAll(roles); 81 | return "「关闭签到」\n" + Role.getRolesInfo(roles); 82 | } 83 | 84 | public String changeNotice(long qqId, String gameName, String gameUid, boolean flag) throws NoGameException, NullRoleException { 85 | List roles = getRoles(qqId, gameName, gameUid); 86 | for (Role role : roles) { 87 | role.setNotice(flag); 88 | } 89 | roleRepository.saveAll(roles); 90 | if (flag) { 91 | return "「开启通知」\n" + Role.getRolesInfo(roles); 92 | } else { 93 | return "「关闭通知」\n" + Role.getRolesInfo(roles); 94 | } 95 | } 96 | 97 | public List getRoles(long qqId, String gameName, String gameUid) throws NoGameException, NullRoleException { 98 | List roles; 99 | if (gameUid == null && gameName == null) { 100 | roles = roleRepository.findRolesByUserId(qqId); 101 | } else if (gameName == null) { 102 | roles = roleRepository.findRolesByUserIdAndGameUid(qqId, gameUid); 103 | } else if (gameUid == null) { 104 | roles = roleRepository.findRolesByUserIdAndGameBiz(qqId, Role.getGameBiz(gameName)); 105 | } else { 106 | roles = roleRepository.findRolesByUserIdAndGameBizAndGameUid(qqId, Role.getGameBiz(gameName), gameUid); 107 | } 108 | if (roles == null || roles.isEmpty()) { 109 | throw new NullRoleException(); 110 | } 111 | return roles; 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/com/ame/mihoyosign/service/SignService.java: -------------------------------------------------------------------------------- 1 | package com.ame.mihoyosign.service; 2 | 3 | import com.ame.mihoyosign.entity.Reward; 4 | import com.ame.mihoyosign.entity.Role; 5 | import com.ame.mihoyosign.entity.User; 6 | import com.ame.mihoyosign.exception.NoGameException; 7 | import com.ame.mihoyosign.exception.NullRoleException; 8 | import com.ame.mihoyosign.exception.NullUserException; 9 | import com.ame.mihoyosign.util.Utils; 10 | import lombok.extern.log4j.Log4j2; 11 | import org.springframework.scheduling.annotation.Async; 12 | import org.springframework.stereotype.Service; 13 | 14 | import javax.annotation.Resource; 15 | import java.util.List; 16 | 17 | @Log4j2 18 | @Service 19 | public class SignService { 20 | 21 | @Resource 22 | private MiHoYoService miHoYoService; 23 | @Resource 24 | private RoleService roleService; 25 | @Resource 26 | private UserService userService; 27 | @Resource 28 | private MessageService messageService; 29 | 30 | @Async 31 | public void sign(User user) { 32 | try { 33 | long delay = Utils.delay(user.getSignDelay()); 34 | roleService.updateNicknameAndLevel(user); 35 | List roles = roleService.getRoles(user.getId(), null, null); 36 | String sign = sign(user, roles, false); 37 | if (sign != null) { 38 | messageService.sendPrivateMsg(sign + "\n\n延迟签到:" + delay + "秒", user.getId(), -1); 39 | } 40 | } catch (NullRoleException ignored) { 41 | } catch (Exception e) { 42 | e.printStackTrace(); 43 | } 44 | } 45 | 46 | public String sign(long qqId, String gameName, String gameUid) throws NullRoleException, NullUserException, NoGameException { 47 | User user = userService.getUser(qqId); 48 | List roles = roleService.getRoles(qqId, gameName, gameUid); 49 | String sign = sign(user, roles, true); 50 | return sign == null ? "没有角色开启签到" : sign; 51 | } 52 | 53 | public String sign(User user, List roles, boolean isCommand) { 54 | StringBuilder s = new StringBuilder(); 55 | for (Role role : roles) { 56 | String gameBiz = role.getGameBiz(); 57 | try { 58 | Reward reward = null; 59 | switch (gameBiz) { 60 | case "hk4e_cn": 61 | reward = miHoYoService.ysSign(role, user, isCommand); 62 | break; 63 | case "bh3_cn": 64 | reward = miHoYoService.bh3Sign(role, user, isCommand); 65 | break; 66 | } 67 | if (reward != null && (isCommand || role.isNotice())) { 68 | s.append("「签到 成功」\n").append(Reward.getInfo(reward)).append(role.getInfo()).append("\n\n"); 69 | } 70 | log.info("[签到结果:成功] QQ:{} 游戏:{} UID:{}", 71 | user.getId(), 72 | Role.getGameName(role.getGameBiz()), 73 | role.getGameUid()); 74 | } catch (Exception e) { 75 | s.append("「签到 失败」\n" + "原因:").append(e.getMessage()).append("\n") 76 | .append(role.getInfo()).append("\n\n"); 77 | log.info("[签到结果:失败] QQ:{} 游戏:{} UID:{} 原因:{}", 78 | user.getId(), 79 | Role.getGameName(role.getGameBiz()), 80 | role.getGameUid(), 81 | e.getMessage()); 82 | } 83 | } 84 | return s.length() > 3 ? s.substring(0, s.length() - 2) : null; 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/ame/mihoyosign/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.ame.mihoyosign.service; 2 | 3 | import com.ame.mihoyosign.dao.UserRepository; 4 | import com.ame.mihoyosign.entity.Role; 5 | import com.ame.mihoyosign.entity.User; 6 | import com.ame.mihoyosign.exception.MiHoYoApiException; 7 | import com.ame.mihoyosign.exception.NullUserException; 8 | import com.ame.mihoyosign.util.Utils; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.stereotype.Service; 11 | 12 | import javax.annotation.Resource; 13 | import java.util.List; 14 | import java.util.UUID; 15 | 16 | @Service 17 | public class UserService { 18 | @Resource 19 | private UserRepository userRepository; 20 | @Resource 21 | private MiHoYoService miHoYoService; 22 | @Resource 23 | private MessageService messageService; 24 | @Value("${app-config.api-delay}") 25 | private String ApiDelay; 26 | @Value("${app-config.api-delay-max}") 27 | private String ApiDelayMax; 28 | @Value("${app-config.sign-delay}") 29 | private String signDelay; 30 | @Value("${app-config.sign-delay-max}") 31 | private String signDelayMax; 32 | 33 | 34 | public String bind(long qqId, String cookie) throws MiHoYoApiException { 35 | User user = userRepository.findUserById(qqId); 36 | String r; 37 | if (user == null) { 38 | user = new User(qqId, cookie, null, UUID.randomUUID().toString(), ApiDelay, signDelay, null); 39 | r = "新用户绑定"; 40 | } else { 41 | user.setCookie(cookie); 42 | r = "旧用户更新绑定"; 43 | } 44 | List roles = miHoYoService.getRoles(user, null, null); 45 | messageService.sendLog(r + qqId); 46 | userRepository.save(user); 47 | return "已成功绑定或更新账号\n「米游社中所有角色」\n" + Role.getRolesInfo(roles); 48 | } 49 | 50 | 51 | public String unbind(long qqId) throws NullUserException { 52 | User user = getUser(qqId); 53 | userRepository.delete(user); 54 | return "已解除绑定"; 55 | } 56 | 57 | public String setApiDelay(long qqId, String delay) throws NullUserException { 58 | User user = getUser(qqId); 59 | try { 60 | if (delay == null || delay.equals("无")) { 61 | delay = null; 62 | } else { 63 | delay = Utils.parseRange(delay, ApiDelayMax); 64 | } 65 | user.setApiDelay(delay); 66 | userRepository.save(user); 67 | return "已修改延迟:" + (delay == null ? "关" : delay); 68 | } catch (Exception e) { 69 | return "修改失败:" + e.getMessage(); 70 | } 71 | } 72 | 73 | public String setSignDelay(long qqId, String delay) throws NullUserException { 74 | User user = getUser(qqId); 75 | try { 76 | if (delay == null || delay.equals("关")) { 77 | delay = null; 78 | } else { 79 | delay = Utils.parseRange(delay, signDelayMax); 80 | } 81 | user.setSignDelay(delay); 82 | userRepository.save(user); 83 | return "已修改延迟:" + (delay == null ? "关" : delay); 84 | } catch (Exception e) { 85 | return "修改失败:" + e.getMessage(); 86 | } 87 | } 88 | 89 | public String setUA(long qqId, String ua) throws NullUserException { 90 | if (ua == null || "".equals(ua)) { 91 | return "UA错误,用米游社扫描此二维码获取 User-Agent\n" + 92 | "[CQ:image,file=https://i.postimg.cc/FKxHmRxX/image.png]"; 93 | } 94 | User user = getUser(qqId); 95 | user.setUa(ua); 96 | user = userRepository.save(user); 97 | return "已修改User-Agent:\n" + user.getUa(); 98 | } 99 | 100 | public User getUser(long qqId) throws NullUserException { 101 | User user = userRepository.findUserById(qqId); 102 | if (user == null) { 103 | throw new NullUserException(); 104 | } 105 | return user; 106 | } 107 | 108 | public List getAllUser() { 109 | return userRepository.findAll(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/com/ame/mihoyosign/util/Utils.java: -------------------------------------------------------------------------------- 1 | package com.ame.mihoyosign.util; 2 | 3 | import com.alibaba.fastjson.JSONArray; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.text.ParseException; 8 | import java.text.SimpleDateFormat; 9 | import java.util.*; 10 | 11 | @Slf4j 12 | @Component 13 | public class Utils { 14 | 15 | private static final Random RANDOM = new Random(); 16 | 17 | private static final Character[] CHARACTERS = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 18 | 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 19 | 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}; 20 | 21 | private Utils() { 22 | } 23 | 24 | public static Map getCookieByStr(String str) { 25 | String[] cookieStr = str.split(";"); 26 | Map cookie = new HashMap<>(cookieStr.length); 27 | for (String s : cookieStr) { 28 | String[] oneCookie = s.split("="); 29 | cookie.put(oneCookie[0], oneCookie[1]); 30 | } 31 | return cookie; 32 | } 33 | 34 | public static String getChinaTime(String format) { 35 | Calendar calendar = Calendar.getInstance(Locale.CHINA); 36 | java.util.Date date = calendar.getTime(); 37 | SimpleDateFormat dateFormat = new SimpleDateFormat(format); 38 | return dateFormat.format(date); 39 | } 40 | 41 | /** 42 | * 随机数 43 | */ 44 | public static String getRandomFromArray(Character[] array, int count) { 45 | if (array == null) { 46 | array = CHARACTERS; 47 | } 48 | StringBuilder re = new StringBuilder(); 49 | List list = Arrays.asList(array); 50 | List arrList = new ArrayList<>(list); 51 | for (int i = 0; i < count; i++) { 52 | int t = RANDOM.nextInt(arrList.size()); 53 | re.append(arrList.get(t)); 54 | arrList.remove(t); 55 | } 56 | return re.toString(); 57 | } 58 | 59 | public static long delay(String rangeStr) { 60 | if (rangeStr == null) { 61 | return -1; 62 | } 63 | List range = getRange(rangeStr); 64 | int i = RANDOM.nextInt(range.get(1) - range.get(0)) + range.get(0); 65 | try { 66 | Thread.sleep(i * 1000L); 67 | return i; 68 | } catch (InterruptedException e) { 69 | log.warn("延迟执行出错"); 70 | return -1; 71 | } 72 | } 73 | 74 | public static List getRange(String rangeStr) { 75 | return JSONArray.parseArray(rangeStr, Integer.class); 76 | } 77 | 78 | public static String parseRange(String rangeStr, String rangeMax) throws Exception { 79 | List maxRange = getRange(rangeMax); 80 | List strings = Arrays.asList(rangeStr.split("~")); 81 | try { 82 | int a = Integer.parseInt(strings.get(0)); 83 | int b = Integer.parseInt(strings.get(1)); 84 | int[] ints = {a, b}; 85 | Arrays.sort(ints); 86 | a = ints[0]; 87 | b = ints[1]; 88 | if (a == b) { 89 | b++; 90 | } 91 | if (maxRange != null && (a < maxRange.get(0) || b > maxRange.get(1))) { 92 | throw new Exception("超出范围,请位于" + maxRange + "之间"); 93 | } 94 | return "[" + a + "," + b + "]"; 95 | } catch (NumberFormatException e) { 96 | throw new Exception("格式错误,例 0~10"); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | _ _ _ __ __ _____ _ 2 | (_)| | | | \ \ / / / ___|(_) 3 | _ __ ___ _ | |_| | ___ \ V / ___ \ `--. _ __ _ _ __ 4 | | '_ ` _ \ | || _ | / _ \ \ / / _ \ `--. \| | / _` || '_ \ 5 | | | | | | || || | | || (_) || || (_) |/\__/ /| || (_| || | | | 6 | |_| |_| |_||_|\_| |_/ \___/ \_/ \___/ \____/ |_| \__, ||_| |_| 7 | __/ | 8 | |___/ -------------------------------------------------------------------------------- /src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | Document 9 | 10 | 11 | 程序启动成功 12 | 13 | -------------------------------------------------------------------------------- /src/test/java/com/ame/mihoyosign/MiHoYoSignApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.ame.mihoyosign; 2 | 3 | import com.ame.mihoyosign.service.MiHoYoService; 4 | import com.ame.mihoyosign.service.RoleService; 5 | import com.ame.mihoyosign.service.SignService; 6 | import com.ame.mihoyosign.service.UserService; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | 9 | import javax.annotation.Resource; 10 | 11 | @SpringBootTest 12 | class MiHoYoSignApplicationTests { 13 | @Resource 14 | MiHoYoService miHoYoService; 15 | @Resource 16 | UserService userService; 17 | @Resource 18 | RoleService roleService; 19 | @Resource 20 | SignService signService; 21 | 22 | } 23 | --------------------------------------------------------------------------------