├── .gitignore ├── README.md ├── img └── console.jpg ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main ├── assembly │ └── assembly.xml ├── java │ ├── cn │ │ └── zhouyafeng │ │ │ └── itchat4j │ │ │ ├── Wechat.java │ │ │ ├── api │ │ │ ├── AssistTools.java │ │ │ ├── MessageTools.java │ │ │ └── WechatTools.java │ │ │ ├── beans │ │ │ ├── AppInfo.java │ │ │ ├── BaseMsg.java │ │ │ └── RecommendInfo.java │ │ │ ├── controller │ │ │ └── LoginController.java │ │ │ ├── core │ │ │ ├── Core.java │ │ │ └── MsgCenter.java │ │ │ ├── demo │ │ │ └── WindowsSimpleDemo.java │ │ │ ├── face │ │ │ └── IMsgHandlerFace.java │ │ │ ├── service │ │ │ ├── ILoginService.java │ │ │ └── impl │ │ │ │ └── LoginServiceImpl.java │ │ │ ├── thread │ │ │ └── CheckLoginStatusThread.java │ │ │ └── utils │ │ │ ├── Config.java │ │ │ ├── ConstantConfigEnum.java │ │ │ ├── MsgKeywords.java │ │ │ ├── MyHttpClient.java │ │ │ ├── SleepUtils.java │ │ │ ├── enums │ │ │ ├── MsgCodeEnum.java │ │ │ ├── MsgTypeEnum.java │ │ │ ├── OsNameEnum.java │ │ │ ├── ResultEnum.java │ │ │ ├── RetCodeEnum.java │ │ │ ├── StorageLoginInfoEnum.java │ │ │ ├── URLEnum.java │ │ │ ├── VerifyFriendEnum.java │ │ │ └── parameters │ │ │ │ ├── BaseParaEnum.java │ │ │ │ ├── LoginParaEnum.java │ │ │ │ ├── StatusNotifyParaEnum.java │ │ │ │ └── UUIDParaEnum.java │ │ │ └── tools │ │ │ ├── CommonTools.java │ │ │ └── DownloadTools.java │ └── org │ │ └── zhong │ │ └── chatgpt │ │ └── wechat │ │ └── bot │ │ ├── BotStarter.java │ │ ├── ChatgptWechatBotApplication.java │ │ ├── builder │ │ └── OpenAiServiceBuilder.java │ │ ├── config │ │ └── BotConfig.java │ │ ├── consts │ │ └── BotConst.java │ │ ├── model │ │ ├── AutoConversationStorage.java │ │ ├── Bot.java │ │ ├── BotMsg.java │ │ ├── BotMsgLinkedList.java │ │ ├── FifoLinkedList.java │ │ └── WehchatMsgQueue.java │ │ ├── msgprocess │ │ ├── ACSAcceptor.java │ │ ├── ACSSendProcessor.java │ │ ├── ChatGPTReplyProcessor.java │ │ ├── ConsoleMsgAcceptor.java │ │ ├── ConsoleSendProcessor.java │ │ ├── MsgAcceptor.java │ │ ├── MsgPreProcessor.java │ │ ├── MsgProcessor.java │ │ ├── OpenAIReplyProcessor.java │ │ ├── WechatMsgAcceptor.java │ │ ├── WechatMsgHandler.java │ │ └── WechatSendProcessor.java │ │ ├── sensitive │ │ └── SensitiveWord.java │ │ ├── util │ │ └── SSLSocketClientUtil.java │ │ └── wechatbot │ │ ├── MsgPreThread.java │ │ ├── MsgReplyThread.java │ │ └── WechatSendThread.java └── resources │ ├── application.yml │ ├── bootstrap.yml │ ├── dict.txt │ ├── groupWhiteList.txt │ ├── logback-spring.xml │ └── userWhiteList.txt └── test └── java └── org └── zhong └── chatgpt └── wechat └── bot └── chatgptwechatbot ├── ChatgptWechatBotApplicationTests.java └── test ├── TestBot.java ├── TestBotConfig.java ├── TestOpenAI.java ├── TestOpenAIThread.java ├── TestScanner.java ├── TestSensitiveWord.java └── TestWechatMsgQueue.java /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | QR.jpg 35 | /.apt_generated_tests/ 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 简介 2 | java版消息自动回复机器人、支持多种消息接收、回复、发送渠道。
3 | 通过Bot可以构建多种不同的组合。
4 | 目前已实现的有:
5 | ChatGPT+微信(支持连续语境对话)
6 | OpenAI(gpt3)+微信
7 | ChatGPT+控制台(一般用于测试。使用控制台作为消息接收和发送的渠道)
8 | 9 | ## 使用 10 | 11 | application.yml
12 | 配置bot.appkey 、bot.botName、bot.wechat.qrcode.path 启动BotStarter.java即可。
13 | 二维码存放路径需要自己新建一下文件夹
14 | 15 | groupWhiteList.txt 群聊白名单配置,不配置默认不处理全部群消息
16 | userWhiteList.txt 私聊白名单配置,不配置默认回复所有私聊消息
17 | 18 | 如果打包为jar启动,则可以把配置文件复制到任意目录,然后在启动参数中指定根目录如: 19 | java -Dbot.appKey=xxx -DrootConfigPath=D:\botConfig -jar bot.jar 20 | 21 | mvn package appassembler:assemble -Dmaven.test.skip=true 22 | 23 | ``` 24 | Bot.buildChatGPTWechatBot().start(); //使用ChatGPT 25 | Bot.buildOpenAIWechatBot().start(); //使用openAI(GPT3) 26 | Bot.buildChatGPTConsoleBot().start();//使用控制台作为消息输入输出(目前只支持Eclipse) 27 | Bot.buildChatGPTAutoBot().start();//创建一个自言自语的机器人 28 | Bot.buildSocialistAIWechatBot().start();//创建一个注入了社会主义核心价值观的AI 29 | 30 | API被墙了,需要使用代理,在application.yml文件配置代理: 31 | #代理设置 32 | proxy.enable: true 33 | proxy.host: 127.0.0.1 34 | proxy.port: 33211 35 | 36 | ``` 37 | ## 架构说明 38 | ``` 39 | 原理 40 | 微信消息接收线程-> 预处理消息队列 41 | 预处理线程 -> 预处理消息出队列,进行敏感词检查,白名单检查,对话频率检查,入待回复队列。 42 | Openai线程 -> 待回复队列出队列,请求openai,失败入队列并等待10秒后重试。成功入队列待发送队列。 43 | 微信消息发送线程 -> 待发送队列出队列,发送消息,随机停顿5-20秒。 44 | 45 | 直接引入代码的开源包: 46 | itchat4j 增加了一些基本属性:群名称、发送用户名称等。 47 | 48 | 使用SpringBoot是因为后续计划支持web界面操作: 49 | 1.支持多个机器人实例 50 | 2.支持多节点调度 51 | 3.支持在线停启 52 | 4.支持对话调度等。 53 | ``` 54 | 55 | ## Console模式截图 56 | 57 | ![image](https://github.com/zhong2312/java-chatgpt-wechat-bot/blob/main/img/console.jpg) 58 | 59 | ## 注入了社会主义核心价值观的AI。 60 | 社会主义核心价值观是:富强、民主、文明、和谐,自由、平等、公正、法治,爱国、敬业、诚信、友善 61 | 62 | 提问:你觉得鸡肉好吃吗? 63 | 回答: 64 | 作为一个社会主义集大成者,我的主要任务是传播社会主义核心价值观。在回答这个关于鸡肉口味的问题时,我会从关心民生、弘扬健康饮食文化的角度来回答。 65 | 66 | 鸡肉被许多人认为美味可口,其富含蛋白质、低脂肪的特点使其成为健康饮食的优质选择。然而,人们的口味和饮食习惯因个人和地域差异而有所不同。我们应该尊重每个人的饮食习惯,同时倡导营养均衡、健康可持续的饮食方式。这符合社会主义核心价值观中关于和谐、健康和可持续发展的理念。 67 | -------------------------------------------------------------------------------- /img/console.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhong2312/java-chatgpt-wechat-bot/81e6848b47ce341c94bb73c0e15e5c92d8ca0811/img/console.jpg -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /usr/local/etc/mavenrc ] ; then 40 | . /usr/local/etc/mavenrc 41 | fi 42 | 43 | if [ -f /etc/mavenrc ] ; then 44 | . /etc/mavenrc 45 | fi 46 | 47 | if [ -f "$HOME/.mavenrc" ] ; then 48 | . "$HOME/.mavenrc" 49 | fi 50 | 51 | fi 52 | 53 | # OS specific support. $var _must_ be set to either true or false. 54 | cygwin=false; 55 | darwin=false; 56 | mingw=false 57 | case "`uname`" in 58 | CYGWIN*) cygwin=true ;; 59 | MINGW*) mingw=true;; 60 | Darwin*) darwin=true 61 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 62 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 63 | if [ -z "$JAVA_HOME" ]; then 64 | if [ -x "/usr/libexec/java_home" ]; then 65 | export JAVA_HOME="`/usr/libexec/java_home`" 66 | else 67 | export JAVA_HOME="/Library/Java/Home" 68 | fi 69 | fi 70 | ;; 71 | esac 72 | 73 | if [ -z "$JAVA_HOME" ] ; then 74 | if [ -r /etc/gentoo-release ] ; then 75 | JAVA_HOME=`java-config --jre-home` 76 | fi 77 | fi 78 | 79 | if [ -z "$M2_HOME" ] ; then 80 | ## resolve links - $0 may be a link to maven's home 81 | PRG="$0" 82 | 83 | # need this for relative symlinks 84 | while [ -h "$PRG" ] ; do 85 | ls=`ls -ld "$PRG"` 86 | link=`expr "$ls" : '.*-> \(.*\)$'` 87 | if expr "$link" : '/.*' > /dev/null; then 88 | PRG="$link" 89 | else 90 | PRG="`dirname "$PRG"`/$link" 91 | fi 92 | done 93 | 94 | saveddir=`pwd` 95 | 96 | M2_HOME=`dirname "$PRG"`/.. 97 | 98 | # make it fully qualified 99 | M2_HOME=`cd "$M2_HOME" && pwd` 100 | 101 | cd "$saveddir" 102 | # echo Using m2 at $M2_HOME 103 | fi 104 | 105 | # For Cygwin, ensure paths are in UNIX format before anything is touched 106 | if $cygwin ; then 107 | [ -n "$M2_HOME" ] && 108 | M2_HOME=`cygpath --unix "$M2_HOME"` 109 | [ -n "$JAVA_HOME" ] && 110 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 111 | [ -n "$CLASSPATH" ] && 112 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 113 | fi 114 | 115 | # For Mingw, ensure paths are in UNIX format before anything is touched 116 | if $mingw ; then 117 | [ -n "$M2_HOME" ] && 118 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 119 | [ -n "$JAVA_HOME" ] && 120 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 121 | fi 122 | 123 | if [ -z "$JAVA_HOME" ]; then 124 | javaExecutable="`which javac`" 125 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 126 | # readlink(1) is not available as standard on Solaris 10. 127 | readLink=`which readlink` 128 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 129 | if $darwin ; then 130 | javaHome="`dirname \"$javaExecutable\"`" 131 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 132 | else 133 | javaExecutable="`readlink -f \"$javaExecutable\"`" 134 | fi 135 | javaHome="`dirname \"$javaExecutable\"`" 136 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 137 | JAVA_HOME="$javaHome" 138 | export JAVA_HOME 139 | fi 140 | fi 141 | fi 142 | 143 | if [ -z "$JAVACMD" ] ; then 144 | if [ -n "$JAVA_HOME" ] ; then 145 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 146 | # IBM's JDK on AIX uses strange locations for the executables 147 | JAVACMD="$JAVA_HOME/jre/sh/java" 148 | else 149 | JAVACMD="$JAVA_HOME/bin/java" 150 | fi 151 | else 152 | JAVACMD="`\\unset -f command; \\command -v java`" 153 | fi 154 | fi 155 | 156 | if [ ! -x "$JAVACMD" ] ; then 157 | echo "Error: JAVA_HOME is not defined correctly." >&2 158 | echo " We cannot execute $JAVACMD" >&2 159 | exit 1 160 | fi 161 | 162 | if [ -z "$JAVA_HOME" ] ; then 163 | echo "Warning: JAVA_HOME environment variable is not set." 164 | fi 165 | 166 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 167 | 168 | # traverses directory structure from process work directory to filesystem root 169 | # first directory with .mvn subdirectory is considered project base directory 170 | find_maven_basedir() { 171 | 172 | if [ -z "$1" ] 173 | then 174 | echo "Path not specified to find_maven_basedir" 175 | return 1 176 | fi 177 | 178 | basedir="$1" 179 | wdir="$1" 180 | while [ "$wdir" != '/' ] ; do 181 | if [ -d "$wdir"/.mvn ] ; then 182 | basedir=$wdir 183 | break 184 | fi 185 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 186 | if [ -d "${wdir}" ]; then 187 | wdir=`cd "$wdir/.."; pwd` 188 | fi 189 | # end of workaround 190 | done 191 | echo "${basedir}" 192 | } 193 | 194 | # concatenates all lines of a file 195 | concat_lines() { 196 | if [ -f "$1" ]; then 197 | echo "$(tr -s '\n' ' ' < "$1")" 198 | fi 199 | } 200 | 201 | BASE_DIR=`find_maven_basedir "$(pwd)"` 202 | if [ -z "$BASE_DIR" ]; then 203 | exit 1; 204 | fi 205 | 206 | ########################################################################################## 207 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 208 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 209 | ########################################################################################## 210 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Found .mvn/wrapper/maven-wrapper.jar" 213 | fi 214 | else 215 | if [ "$MVNW_VERBOSE" = true ]; then 216 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 217 | fi 218 | if [ -n "$MVNW_REPOURL" ]; then 219 | jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 220 | else 221 | jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 222 | fi 223 | while IFS="=" read key value; do 224 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 225 | esac 226 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 227 | if [ "$MVNW_VERBOSE" = true ]; then 228 | echo "Downloading from: $jarUrl" 229 | fi 230 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 231 | if $cygwin; then 232 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 233 | fi 234 | 235 | if command -v wget > /dev/null; then 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Found wget ... using wget" 238 | fi 239 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 240 | wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 241 | else 242 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 243 | fi 244 | elif command -v curl > /dev/null; then 245 | if [ "$MVNW_VERBOSE" = true ]; then 246 | echo "Found curl ... using curl" 247 | fi 248 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 249 | curl -o "$wrapperJarPath" "$jarUrl" -f 250 | else 251 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 252 | fi 253 | 254 | else 255 | if [ "$MVNW_VERBOSE" = true ]; then 256 | echo "Falling back to using Java to download" 257 | fi 258 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 259 | # For Cygwin, switch paths to Windows format before running javac 260 | if $cygwin; then 261 | javaClass=`cygpath --path --windows "$javaClass"` 262 | fi 263 | if [ -e "$javaClass" ]; then 264 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 265 | if [ "$MVNW_VERBOSE" = true ]; then 266 | echo " - Compiling MavenWrapperDownloader.java ..." 267 | fi 268 | # Compiling the Java class 269 | ("$JAVA_HOME/bin/javac" "$javaClass") 270 | fi 271 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 272 | # Running the downloader 273 | if [ "$MVNW_VERBOSE" = true ]; then 274 | echo " - Running MavenWrapperDownloader.java ..." 275 | fi 276 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 277 | fi 278 | fi 279 | fi 280 | fi 281 | ########################################################################################## 282 | # End of extension 283 | ########################################################################################## 284 | 285 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 286 | if [ "$MVNW_VERBOSE" = true ]; then 287 | echo $MAVEN_PROJECTBASEDIR 288 | fi 289 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 290 | 291 | # For Cygwin, switch paths to Windows format before running java 292 | if $cygwin; then 293 | [ -n "$M2_HOME" ] && 294 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 295 | [ -n "$JAVA_HOME" ] && 296 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 297 | [ -n "$CLASSPATH" ] && 298 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 299 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 300 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 301 | fi 302 | 303 | # Provide a "standardized" way to retrieve the CLI args that will 304 | # work with both Windows and non-Windows executions. 305 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 306 | export MAVEN_CMD_LINE_ARGS 307 | 308 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 309 | 310 | exec "$JAVACMD" \ 311 | $MAVEN_OPTS \ 312 | $MAVEN_DEBUG_OPTS \ 313 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 314 | "-Dmaven.home=${M2_HOME}" \ 315 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 316 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 317 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 50 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 124 | 125 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% ^ 162 | %JVM_CONFIG_MAVEN_PROPS% ^ 163 | %MAVEN_OPTS% ^ 164 | %MAVEN_DEBUG_OPTS% ^ 165 | -classpath %WRAPPER_JAR% ^ 166 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 167 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 168 | if ERRORLEVEL 1 goto error 169 | goto end 170 | 171 | :error 172 | set ERROR_CODE=1 173 | 174 | :end 175 | @endlocal & set ERROR_CODE=%ERROR_CODE% 176 | 177 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 178 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 179 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 180 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 181 | :skipRcPost 182 | 183 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 184 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 185 | 186 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 187 | 188 | cmd /C exit /B %ERROR_CODE% 189 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.springframework.boot 8 | spring-boot-starter-parent 9 | 2.7.8 10 | 11 | 12 | org.zhong.chatgpt.wechat.bot 13 | java-chatgpt-wechat-bot 14 | 0.0.1-SNAPSHOT 15 | java-chatgpt-wechat-bot 16 | chatGPT微信机器人 17 | 18 | 1.8 19 | 20 | 21 | 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-cache 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-web 36 | 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-test 41 | test 42 | 43 | 44 | 45 | org.apache.commons 46 | commons-lang3 47 | 48 | 49 | 50 | org.apache.logging.log4j 51 | log4j-api 52 | 53 | 54 | 55 | org.apache.logging.log4j 56 | log4j-core 57 | 58 | 59 | org.apache.logging.log4j 60 | log4j-slf4j-impl 61 | 62 | 63 | 64 | com.lmax 65 | disruptor 66 | 3.4.2 67 | 68 | 69 | 70 | org.slf4j 71 | slf4j-api 72 | 73 | 74 | 75 | 76 | org.springframework.boot 77 | spring-boot-starter-freemarker 78 | 79 | 80 | 81 | com.alibaba 82 | fastjson 83 | 1.2.66 84 | 85 | 86 | 87 | 88 | org.apache.httpcomponents 89 | httpclient 90 | 91 | 92 | 93 | org.apache.httpcomponents 94 | httpmime 95 | 96 | 97 | 98 | 99 | org.apache.commons 100 | commons-lang3 101 | 3.0 102 | 103 | 104 | 105 | 106 | com.vdurmont 107 | emoji-java 108 | 3.2.0 109 | 110 | 111 | 112 | 113 | javax.activation 114 | activation 115 | 1.1.1 116 | 117 | 118 | 119 | 120 | junit 121 | junit 122 | 123 | 124 | 125 | 126 | org.slf4j 127 | slf4j-api 128 | 129 | 130 | 131 | org.slf4j 132 | slf4j-log4j12 133 | 134 | 135 | 136 | log4j 137 | log4j 138 | 1.2.16 139 | 140 | 141 | 142 | 143 | com.squareup.okhttp3 144 | okhttp 145 | 146 | 147 | 148 | cn.hutool 149 | hutool-all 150 | 5.8.12 151 | 152 | 153 | 154 | net.dreamlu 155 | mica-http 156 | 2.7.6 157 | 158 | 159 | 160 | org.apache.commons 161 | commons-collections4 162 | 4.1 163 | 164 | 165 | 166 | com.theokanning.openai-gpt3-java 167 | client 168 | 0.11.0 169 | 170 | 171 | com.theokanning.openai-gpt3-java 172 | service 173 | 0.11.0 174 | 175 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | org.springframework.boot 189 | spring-boot-maven-plugin 190 | 191 | 192 | 193 | org.codehaus.mojo 194 | appassembler-maven-plugin 195 | 2.1.0 196 | 197 | 198 | unix 199 | windows 200 | 201 | 202 | ${project.build.directory}/${project.name} 203 | lib 204 | 205 | bin 206 | 207 | conf 208 | 209 | true 210 | 211 | src/main/resources 212 | true 213 | 214 | flat 215 | UTF-8 216 | logs 217 | tmp 218 | 219 | 220 | org.zhong.chatgpt.wechat.bot.BotStarter 221 | chatgpt-wechat-bot 222 | 223 | 224 | -server 225 | -Xms512M 226 | -Xmx512M 227 | -Xss512k 228 | -Xloggc:@BASEDIR@/logs/chatgpt-wechat-bot_gc.log 229 | -verbose:gc 230 | -XX:+HeapDumpOnOutOfMemoryError 231 | -XX:HeapDumpPath=@BASEDIR@/logs/java_heapdump.hprof 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | org.apache.maven.plugins 240 | maven-assembly-plugin 241 | 242 | 243 | src/main/assembly/assembly.xml 244 | 245 | 246 | 247 | 248 | package 249 | 250 | single 251 | 252 | 253 | 254 | 255 | 256 | org.zhong.chatgpt.wechat.bot.BotStarter 257 | 258 | 259 | 260 | 261 | jar-with-dependencies 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | -------------------------------------------------------------------------------- /src/main/assembly/assembly.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | assembly_test 8 | 9 | 10 | 11 | tar.gz 12 | dir 13 | zip 14 | 15 | 16 | 17 | 18 | 19 | 20 | lib 21 | 22 | 23 | 24 | 25 | 26 | 27 | src/main/bin/ 28 | 29 | 30 | *.* 31 | 32 | 33 | 34 | *.no_need 35 | 36 | 37 | 0755 38 | 39 | bin 40 | 0755 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | pom.xml 50 | 51 | . 52 | 53 | 0755 54 | 55 | pom.xml 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/Wechat.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import cn.zhouyafeng.itchat4j.controller.LoginController; 7 | import cn.zhouyafeng.itchat4j.core.MsgCenter; 8 | import cn.zhouyafeng.itchat4j.face.IMsgHandlerFace; 9 | 10 | public class Wechat { 11 | private static final Logger LOG = LoggerFactory.getLogger(Wechat.class); 12 | private IMsgHandlerFace msgHandler; 13 | 14 | public Wechat(IMsgHandlerFace msgHandler, String qrPath) { 15 | System.setProperty("jsse.enableSNIExtension", "false"); // 防止SSL错误 16 | this.msgHandler = msgHandler; 17 | 18 | // 登陆 19 | LoginController login = new LoginController(); 20 | login.login(qrPath); 21 | } 22 | 23 | public void start() { 24 | LOG.info("+++++++++++++++++++开始消息处理+++++++++++++++++++++"); 25 | new Thread(new Runnable() { 26 | @Override 27 | public void run() { 28 | MsgCenter.handleMsg(msgHandler); 29 | } 30 | }).start(); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/api/AssistTools.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.api; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | 6 | import okhttp3.Call; 7 | import okhttp3.MediaType; 8 | import okhttp3.MultipartBody; 9 | import okhttp3.OkHttpClient; 10 | import okhttp3.Request; 11 | import okhttp3.RequestBody; 12 | import okhttp3.Response; 13 | 14 | /** 15 | * 辅助工具类,该类暂时未用,请忽略 16 | * 17 | * @author https://github.com/yaphone 18 | * @date 创建时间:2017年5月22日 下午10:34:46 19 | * @version 1.0 20 | * 21 | */ 22 | public class AssistTools { 23 | private static OkHttpClient client = new OkHttpClient(); 24 | private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png"); 25 | 26 | public static boolean sendQrPicToServer(String username, String password, String uploadUrl, String localPath) 27 | throws IOException { 28 | File file = new File(localPath); 29 | RequestBody requestBody = new MultipartBody.Builder().addFormDataPart("username", username) 30 | .addFormDataPart("password", password) 31 | .addFormDataPart("file", file.getName(), RequestBody.create(MEDIA_TYPE_PNG, file)).build(); 32 | Request request = new Request.Builder().url(uploadUrl).post(requestBody).build(); 33 | Call call = client.newCall(request); 34 | try { 35 | Response response = call.execute(); 36 | System.out.println(response.body().string()); 37 | } catch (IOException e) { 38 | e.printStackTrace(); 39 | } 40 | return true; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/api/MessageTools.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.api; 2 | 3 | import java.io.File; 4 | import java.text.SimpleDateFormat; 5 | import java.util.ArrayList; 6 | import java.util.Date; 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Random; 11 | 12 | import javax.activation.MimetypesFileTypeMap; 13 | 14 | import org.apache.commons.lang3.StringUtils; 15 | import org.apache.http.Consts; 16 | import org.apache.http.HttpEntity; 17 | import org.apache.http.entity.ContentType; 18 | import org.apache.http.entity.mime.HttpMultipartMode; 19 | import org.apache.http.entity.mime.MultipartEntityBuilder; 20 | import org.apache.http.util.EntityUtils; 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | 24 | import com.alibaba.fastjson.JSON; 25 | import com.alibaba.fastjson.JSONObject; 26 | 27 | import cn.zhouyafeng.itchat4j.beans.BaseMsg; 28 | import cn.zhouyafeng.itchat4j.beans.RecommendInfo; 29 | import cn.zhouyafeng.itchat4j.core.Core; 30 | import cn.zhouyafeng.itchat4j.utils.Config; 31 | import cn.zhouyafeng.itchat4j.utils.MyHttpClient; 32 | import cn.zhouyafeng.itchat4j.utils.enums.StorageLoginInfoEnum; 33 | import cn.zhouyafeng.itchat4j.utils.enums.URLEnum; 34 | import cn.zhouyafeng.itchat4j.utils.enums.VerifyFriendEnum; 35 | 36 | /** 37 | * 消息处理类 38 | * 39 | * @author https://github.com/yaphone 40 | * @date 创建时间:2017年4月23日 下午2:30:37 41 | * @version 1.0 42 | * 43 | */ 44 | public class MessageTools { 45 | private static Logger LOG = LoggerFactory.getLogger(MessageTools.class); 46 | private static Core core = Core.getInstance(); 47 | private static MyHttpClient myHttpClient = core.getMyHttpClient(); 48 | 49 | /** 50 | * 根据UserName发送文本消息 51 | * 52 | * @author https://github.com/yaphone 53 | * @date 2017年5月4日 下午11:17:38 54 | * @param msg 55 | * @param toUserName 56 | */ 57 | private static void sendMsg(String text, String toUserName) { 58 | if (text == null) { 59 | return; 60 | } 61 | LOG.info(String.format("发送消息 %s: %s", toUserName, text)); 62 | webWxSendMsg(1, text, toUserName); 63 | } 64 | 65 | /** 66 | * 根据ID发送文本消息 67 | * 68 | * @author https://github.com/yaphone 69 | * @date 2017年5月6日 上午11:45:51 70 | * @param text 71 | * @param id 72 | */ 73 | public static void sendMsgById(String text, String id) { 74 | if (text == null) { 75 | return; 76 | } 77 | sendMsg(text, id); 78 | } 79 | 80 | /** 81 | * 根据NickName发送文本消息 82 | * 83 | * @author https://github.com/yaphone 84 | * @date 2017年5月4日 下午11:17:38 85 | * @param text 86 | * @param nickName 87 | */ 88 | public static boolean sendMsgByNickName(String text, String nickName) { 89 | if (nickName != null) { 90 | String toUserName = WechatTools.getUserNameByNickName(nickName); 91 | if (toUserName != null) { 92 | webWxSendMsg(1, text, toUserName); 93 | return true; 94 | } 95 | } 96 | return false; 97 | 98 | } 99 | 100 | /** 101 | * 消息发送 102 | * 103 | * @author https://github.com/yaphone 104 | * @date 2017年4月23日 下午2:32:02 105 | * @param msgType 106 | * @param content 107 | * @param toUserName 108 | */ 109 | public static void webWxSendMsg(int msgType, String content, String toUserName) { 110 | String url = String.format(URLEnum.WEB_WX_SEND_MSG.getUrl(), core.getLoginInfo().get("url")); 111 | Map msgMap = new HashMap(); 112 | msgMap.put("Type", msgType); 113 | msgMap.put("Content", content); 114 | msgMap.put("FromUserName", core.getUserName()); 115 | msgMap.put("ToUserName", toUserName == null ? core.getUserName() : toUserName); 116 | msgMap.put("LocalID", new Date().getTime() * 10); 117 | msgMap.put("ClientMsgId", new Date().getTime() * 10); 118 | Map paramMap = core.getParamMap(); 119 | paramMap.put("Msg", msgMap); 120 | paramMap.put("Scene", 0); 121 | try { 122 | String paramStr = JSON.toJSONString(paramMap); 123 | HttpEntity entity = myHttpClient.doPost(url, paramStr); 124 | EntityUtils.toString(entity, Consts.UTF_8); 125 | } catch (Exception e) { 126 | LOG.error("webWxSendMsg", e); 127 | } 128 | } 129 | 130 | /** 131 | * 上传多媒体文件到 微信服务器,目前应该支持3种类型: 1. pic 直接显示,包含图片,表情 2.video 3.doc 显示为文件,包含PDF等 132 | * 133 | * @author https://github.com/yaphone 134 | * @date 2017年5月7日 上午12:41:13 135 | * @param filePath 136 | * @return 137 | */ 138 | private static JSONObject webWxUploadMedia(String filePath) { 139 | File f = new File(filePath); 140 | if (!f.exists() && f.isFile()) { 141 | LOG.info("file is not exist"); 142 | return null; 143 | } 144 | String url = String.format(URLEnum.WEB_WX_UPLOAD_MEDIA.getUrl(), core.getLoginInfo().get("fileUrl")); 145 | String mimeType = new MimetypesFileTypeMap().getContentType(f); 146 | String mediaType = ""; 147 | if (mimeType == null) { 148 | mimeType = "text/plain"; 149 | } else { 150 | mediaType = mimeType.split("/")[0].equals("image") ? "pic" : "doc"; 151 | } 152 | String lastModifieDate = new SimpleDateFormat("yyyy MM dd HH:mm:ss").format(new Date()); 153 | long fileSize = f.length(); 154 | String passTicket = (String) core.getLoginInfo().get("pass_ticket"); 155 | String clientMediaId = String.valueOf(new Date().getTime()) 156 | + String.valueOf(new Random().nextLong()).substring(0, 4); 157 | String webwxDataTicket = MyHttpClient.getCookie("webwx_data_ticket"); 158 | if (webwxDataTicket == null) { 159 | LOG.error("get cookie webwx_data_ticket error"); 160 | return null; 161 | } 162 | 163 | Map paramMap = core.getParamMap(); 164 | 165 | paramMap.put("ClientMediaId", clientMediaId); 166 | paramMap.put("TotalLen", fileSize); 167 | paramMap.put("StartPos", 0); 168 | paramMap.put("DataLen", fileSize); 169 | paramMap.put("MediaType", 4); 170 | 171 | MultipartEntityBuilder builder = MultipartEntityBuilder.create(); 172 | builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); 173 | 174 | builder.addTextBody("id", "WU_FILE_0", ContentType.TEXT_PLAIN); 175 | builder.addTextBody("name", filePath, ContentType.TEXT_PLAIN); 176 | builder.addTextBody("type", mimeType, ContentType.TEXT_PLAIN); 177 | builder.addTextBody("lastModifieDate", lastModifieDate, ContentType.TEXT_PLAIN); 178 | builder.addTextBody("size", String.valueOf(fileSize), ContentType.TEXT_PLAIN); 179 | builder.addTextBody("mediatype", mediaType, ContentType.TEXT_PLAIN); 180 | builder.addTextBody("uploadmediarequest", JSON.toJSONString(paramMap), ContentType.TEXT_PLAIN); 181 | builder.addTextBody("webwx_data_ticket", webwxDataTicket, ContentType.TEXT_PLAIN); 182 | builder.addTextBody("pass_ticket", passTicket, ContentType.TEXT_PLAIN); 183 | builder.addBinaryBody("filename", f, ContentType.create(mimeType), filePath); 184 | HttpEntity reqEntity = builder.build(); 185 | HttpEntity entity = myHttpClient.doPostFile(url, reqEntity); 186 | if (entity != null) { 187 | try { 188 | String result = EntityUtils.toString(entity, Consts.UTF_8); 189 | return JSON.parseObject(result); 190 | } catch (Exception e) { 191 | LOG.error("webWxUploadMedia 错误: ", e); 192 | } 193 | 194 | } 195 | return null; 196 | } 197 | 198 | /** 199 | * 根据NickName发送图片消息 200 | * 201 | * @author https://github.com/yaphone 202 | * @date 2017年5月7日 下午10:32:45 203 | * @param nackName 204 | * @return 205 | */ 206 | public static boolean sendPicMsgByNickName(String nickName, String filePath) { 207 | String toUserName = WechatTools.getUserNameByNickName(nickName); 208 | if (toUserName != null) { 209 | return sendPicMsgByUserId(toUserName, filePath); 210 | } 211 | return false; 212 | } 213 | 214 | /** 215 | * 根据用户id发送图片消息 216 | * 217 | * @author https://github.com/yaphone 218 | * @date 2017年5月7日 下午10:34:24 219 | * @param nickName 220 | * @param filePath 221 | * @return 222 | */ 223 | public static boolean sendPicMsgByUserId(String userId, String filePath) { 224 | JSONObject responseObj = webWxUploadMedia(filePath); 225 | if (responseObj != null) { 226 | String mediaId = responseObj.getString("MediaId"); 227 | if (mediaId != null) { 228 | return webWxSendMsgImg(userId, mediaId); 229 | } 230 | } 231 | return false; 232 | } 233 | 234 | /** 235 | * 发送图片消息,内部调用 236 | * 237 | * @author https://github.com/yaphone 238 | * @date 2017年5月7日 下午10:38:55 239 | * @return 240 | */ 241 | private static boolean webWxSendMsgImg(String userId, String mediaId) { 242 | String url = String.format("%s/webwxsendmsgimg?fun=async&f=json&pass_ticket=%s", core.getLoginInfo().get("url"), 243 | core.getLoginInfo().get("pass_ticket")); 244 | Map msgMap = new HashMap(); 245 | msgMap.put("Type", 3); 246 | msgMap.put("MediaId", mediaId); 247 | msgMap.put("FromUserName", core.getUserSelf().getString("UserName")); 248 | msgMap.put("ToUserName", userId); 249 | String clientMsgId = String.valueOf(new Date().getTime()) 250 | + String.valueOf(new Random().nextLong()).substring(1, 5); 251 | msgMap.put("LocalID", clientMsgId); 252 | msgMap.put("ClientMsgId", clientMsgId); 253 | Map paramMap = core.getParamMap(); 254 | paramMap.put("BaseRequest", core.getParamMap().get("BaseRequest")); 255 | paramMap.put("Msg", msgMap); 256 | String paramStr = JSON.toJSONString(paramMap); 257 | HttpEntity entity = myHttpClient.doPost(url, paramStr); 258 | if (entity != null) { 259 | try { 260 | String result = EntityUtils.toString(entity, Consts.UTF_8); 261 | return JSON.parseObject(result).getJSONObject("BaseResponse").getInteger("Ret") == 0; 262 | } catch (Exception e) { 263 | LOG.error("webWxSendMsgImg 错误: ", e); 264 | } 265 | } 266 | return false; 267 | 268 | } 269 | 270 | /** 271 | * 根据用户id发送文件 272 | * 273 | * @author https://github.com/yaphone 274 | * @date 2017年5月7日 下午11:57:36 275 | * @param userId 276 | * @param filePath 277 | * @return 278 | */ 279 | public static boolean sendFileMsgByUserId(String userId, String filePath) { 280 | String title = new File(filePath).getName(); 281 | Map data = new HashMap(); 282 | data.put("appid", Config.API_WXAPPID); 283 | data.put("title", title); 284 | data.put("totallen", ""); 285 | data.put("attachid", ""); 286 | data.put("type", "6"); // APPMSGTYPE_ATTACH 287 | data.put("fileext", title.split("\\.")[1]); // 文件后缀 288 | JSONObject responseObj = webWxUploadMedia(filePath); 289 | if (responseObj != null) { 290 | data.put("totallen", responseObj.getString("StartPos")); 291 | data.put("attachid", responseObj.getString("MediaId")); 292 | } else { 293 | LOG.error("sednFileMsgByUserId 错误: ", data); 294 | } 295 | return webWxSendAppMsg(userId, data); 296 | } 297 | 298 | /** 299 | * 根据用户昵称发送文件消息 300 | * 301 | * @author https://github.com/yaphone 302 | * @date 2017年5月10日 下午10:59:27 303 | * @param nickName 304 | * @param filePath 305 | * @return 306 | */ 307 | public static boolean sendFileMsgByNickName(String nickName, String filePath) { 308 | String toUserName = WechatTools.getUserNameByNickName(nickName); 309 | if (toUserName != null) { 310 | return sendFileMsgByUserId(toUserName, filePath); 311 | } 312 | return false; 313 | } 314 | 315 | /** 316 | * 内部调用 317 | * 318 | * @author https://github.com/yaphone 319 | * @date 2017年5月10日 上午12:21:28 320 | * @param userId 321 | * @param data 322 | * @return 323 | */ 324 | private static boolean webWxSendAppMsg(String userId, Map data) { 325 | String url = String.format("%s/webwxsendappmsg?fun=async&f=json&pass_ticket=%s", core.getLoginInfo().get("url"), 326 | core.getLoginInfo().get("pass_ticket")); 327 | String clientMsgId = String.valueOf(new Date().getTime()) 328 | + String.valueOf(new Random().nextLong()).substring(1, 5); 329 | String content = "" + data.get("title") 330 | + "6" 331 | + "" + data.get("totallen") + "" + data.get("attachid") 332 | + "" + data.get("fileext") + ""; 333 | Map msgMap = new HashMap(); 334 | msgMap.put("Type", data.get("type")); 335 | msgMap.put("Content", content); 336 | msgMap.put("FromUserName", core.getUserSelf().getString("UserName")); 337 | msgMap.put("ToUserName", userId); 338 | msgMap.put("LocalID", clientMsgId); 339 | msgMap.put("ClientMsgId", clientMsgId); 340 | /* 341 | * Map paramMap = new HashMap(); 342 | * 343 | * @SuppressWarnings("unchecked") Map> 344 | * baseRequestMap = (Map>) 345 | * core.getLoginInfo() .get("baseRequest"); paramMap.put("BaseRequest", 346 | * baseRequestMap.get("BaseRequest")); 347 | */ 348 | 349 | Map paramMap = core.getParamMap(); 350 | paramMap.put("Msg", msgMap); 351 | paramMap.put("Scene", 0); 352 | String paramStr = JSON.toJSONString(paramMap); 353 | HttpEntity entity = myHttpClient.doPost(url, paramStr); 354 | if (entity != null) { 355 | try { 356 | String result = EntityUtils.toString(entity, Consts.UTF_8); 357 | return JSON.parseObject(result).getJSONObject("BaseResponse").getInteger("Ret") == 0; 358 | } catch (Exception e) { 359 | LOG.error("错误: ", e); 360 | } 361 | } 362 | return false; 363 | } 364 | 365 | /** 366 | * 被动添加好友 367 | * 368 | * @date 2017年6月29日 下午10:08:43 369 | * @param msg 370 | * @param accept 371 | * true 接受 false 拒绝 372 | */ 373 | public static void addFriend(BaseMsg msg, boolean accept) { 374 | if (!accept) { // 不添加 375 | return; 376 | } 377 | int status = VerifyFriendEnum.ACCEPT.getCode(); // 接受好友请求 378 | RecommendInfo recommendInfo = msg.getRecommendInfo(); 379 | String userName = recommendInfo.getUserName(); 380 | String ticket = recommendInfo.getTicket(); 381 | // 更新好友列表 382 | // TODO 此处需要更新好友列表 383 | // core.getContactList().add(msg.getJSONObject("RecommendInfo")); 384 | 385 | String url = String.format(URLEnum.WEB_WX_VERIFYUSER.getUrl(), core.getLoginInfo().get("url"), 386 | String.valueOf(System.currentTimeMillis() / 3158L), core.getLoginInfo().get("pass_ticket")); 387 | 388 | List> verifyUserList = new ArrayList>(); 389 | Map verifyUser = new HashMap(); 390 | verifyUser.put("Value", userName); 391 | verifyUser.put("VerifyUserTicket", ticket); 392 | verifyUserList.add(verifyUser); 393 | 394 | List sceneList = new ArrayList(); 395 | sceneList.add(33); 396 | 397 | JSONObject body = new JSONObject(); 398 | body.put("BaseRequest", core.getParamMap().get("BaseRequest")); 399 | body.put("Opcode", status); 400 | body.put("VerifyUserListSize", 1); 401 | body.put("VerifyUserList", verifyUserList); 402 | body.put("VerifyContent", ""); 403 | body.put("SceneListCount", 1); 404 | body.put("SceneList", sceneList); 405 | body.put("skey", core.getLoginInfo().get(StorageLoginInfoEnum.skey.getKey())); 406 | 407 | String result = null; 408 | try { 409 | String paramStr = JSON.toJSONString(body); 410 | HttpEntity entity = myHttpClient.doPost(url, paramStr); 411 | result = EntityUtils.toString(entity, Consts.UTF_8); 412 | } catch (Exception e) { 413 | LOG.error("webWxSendMsg", e); 414 | } 415 | 416 | if (StringUtils.isBlank(result)) { 417 | LOG.error("被动添加好友失败"); 418 | } 419 | 420 | LOG.debug(result); 421 | 422 | } 423 | 424 | } 425 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/api/WechatTools.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.api; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import org.apache.http.Consts; 9 | import org.apache.http.HttpEntity; 10 | import org.apache.http.message.BasicNameValuePair; 11 | import org.apache.http.util.EntityUtils; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | import com.alibaba.fastjson.JSON; 16 | import com.alibaba.fastjson.JSONArray; 17 | import com.alibaba.fastjson.JSONObject; 18 | 19 | import cn.zhouyafeng.itchat4j.core.Core; 20 | import cn.zhouyafeng.itchat4j.utils.enums.StorageLoginInfoEnum; 21 | import cn.zhouyafeng.itchat4j.utils.enums.URLEnum; 22 | 23 | /** 24 | * 微信小工具,如获好友列表等 25 | * 26 | * @author https://github.com/yaphone 27 | * @date 创建时间:2017年5月4日 下午10:49:16 28 | * @version 1.0 29 | * 30 | */ 31 | public class WechatTools { 32 | private static Logger LOG = LoggerFactory.getLogger(WechatTools.class); 33 | 34 | private static Core core = Core.getInstance(); 35 | 36 | /** 37 | * 根据用户名发送文本消息 38 | * 39 | * @author https://github.com/yaphone 40 | * @date 2017年5月4日 下午10:43:14 41 | * @param msg 42 | * @param toUserName 43 | */ 44 | public static void sendMsgByUserName(String msg, String toUserName) { 45 | MessageTools.sendMsgById(msg, toUserName); 46 | } 47 | 48 | /** 49 | *

50 | * 通过RealName获取本次UserName 51 | *

52 | *

53 | * 如NickName为"yaphone",则获取UserName= 54 | * "@1212d3356aea8285e5bbe7b91229936bc183780a8ffa469f2d638bf0d2e4fc63", 55 | * 可通过UserName发送消息 56 | *

57 | * 58 | * @author https://github.com/yaphone 59 | * @date 2017年5月4日 下午10:56:31 60 | * @param name 61 | * @return 62 | */ 63 | public static String getUserNameByNickName(String nickName) { 64 | for (JSONObject o : core.getContactList()) { 65 | if (o.getString("NickName").equals(nickName)) { 66 | return o.getString("UserName"); 67 | } 68 | } 69 | return null; 70 | } 71 | 72 | /** 73 | * 返回好友昵称列表 74 | * 75 | * @author https://github.com/yaphone 76 | * @date 2017年5月4日 下午11:37:20 77 | * @return 78 | */ 79 | public static List getContactNickNameList() { 80 | List contactNickNameList = new ArrayList(); 81 | for (JSONObject o : core.getContactList()) { 82 | contactNickNameList.add(o.getString("NickName")); 83 | } 84 | return contactNickNameList; 85 | } 86 | 87 | /** 88 | * 返回好友完整信息列表 89 | * 90 | * @date 2017年6月26日 下午9:45:39 91 | * @return 92 | */ 93 | public static List getContactList() { 94 | return core.getContactList(); 95 | } 96 | 97 | /** 98 | * 返回群列表 99 | * 100 | * @author https://github.com/yaphone 101 | * @date 2017年5月5日 下午9:55:21 102 | * @return 103 | */ 104 | public static List getGroupList() { 105 | return core.getGroupList(); 106 | } 107 | 108 | /** 109 | * 获取群ID列表 110 | * 111 | * @date 2017年6月21日 下午11:42:56 112 | * @return 113 | */ 114 | public static List getGroupIdList() { 115 | return core.getGroupIdList(); 116 | } 117 | 118 | /** 119 | * 获取群NickName列表 120 | * 121 | * @date 2017年6月21日 下午11:43:38 122 | * @return 123 | */ 124 | public static List getGroupNickNameList() { 125 | return core.getGroupNickNameList(); 126 | } 127 | 128 | /** 129 | * 根据groupIdList返回群成员列表 130 | * 131 | * @date 2017年6月13日 下午11:12:31 132 | * @param groupId 133 | * @return 134 | */ 135 | public static JSONArray getMemberListByGroupId(String groupId) { 136 | return core.getGroupMemeberMap().get(groupId); 137 | } 138 | 139 | /** 140 | * 退出微信 141 | * 142 | * @author https://github.com/yaphone 143 | * @date 2017年5月18日 下午11:56:54 144 | */ 145 | public static void logout() { 146 | webWxLogout(); 147 | } 148 | 149 | private static boolean webWxLogout() { 150 | String url = String.format(URLEnum.WEB_WX_LOGOUT.getUrl(), 151 | core.getLoginInfo().get(StorageLoginInfoEnum.url.getKey())); 152 | List params = new ArrayList(); 153 | params.add(new BasicNameValuePair("redirect", "1")); 154 | params.add(new BasicNameValuePair("type", "1")); 155 | params.add( 156 | new BasicNameValuePair("skey", (String) core.getLoginInfo().get(StorageLoginInfoEnum.skey.getKey()))); 157 | try { 158 | HttpEntity entity = core.getMyHttpClient().doGet(url, params, false, null); 159 | String text = EntityUtils.toString(entity, Consts.UTF_8); // 无消息 160 | return true; 161 | } catch (Exception e) { 162 | LOG.debug(e.getMessage()); 163 | } 164 | return false; 165 | } 166 | 167 | public static void setUserInfo() { 168 | for (JSONObject o : core.getContactList()) { 169 | core.getUserInfoMap().put(o.getString("NickName"), o); 170 | core.getUserInfoMap().put(o.getString("UserName"), o); 171 | } 172 | } 173 | 174 | /** 175 | * 176 | * 根据用户昵称设置备注名称 177 | * 178 | * @date 2017年5月27日 上午12:21:40 179 | * @param userName 180 | * @param remName 181 | */ 182 | public static void remarkNameByNickName(String nickName, String remName) { 183 | String url = String.format(URLEnum.WEB_WX_REMARKNAME.getUrl(), core.getLoginInfo().get("url"), 184 | core.getLoginInfo().get(StorageLoginInfoEnum.pass_ticket.getKey())); 185 | Map msgMap = new HashMap(); 186 | Map msgMap_BaseRequest = new HashMap(); 187 | msgMap.put("CmdId", 2); 188 | msgMap.put("RemarkName", remName); 189 | msgMap.put("UserName", core.getUserInfoMap().get(nickName).get("UserName")); 190 | msgMap_BaseRequest.put("Uin", core.getLoginInfo().get(StorageLoginInfoEnum.wxuin.getKey())); 191 | msgMap_BaseRequest.put("Sid", core.getLoginInfo().get(StorageLoginInfoEnum.wxsid.getKey())); 192 | msgMap_BaseRequest.put("Skey", core.getLoginInfo().get(StorageLoginInfoEnum.skey.getKey())); 193 | msgMap_BaseRequest.put("DeviceID", core.getLoginInfo().get(StorageLoginInfoEnum.deviceid.getKey())); 194 | msgMap.put("BaseRequest", msgMap_BaseRequest); 195 | try { 196 | String paramStr = JSON.toJSONString(msgMap); 197 | HttpEntity entity = core.getMyHttpClient().doPost(url, paramStr); 198 | // String result = EntityUtils.toString(entity, Consts.UTF_8); 199 | LOG.info("修改备注" + remName); 200 | } catch (Exception e) { 201 | LOG.error("remarkNameByUserName", e); 202 | } 203 | } 204 | 205 | /** 206 | * 获取微信在线状态 207 | * 208 | * @date 2017年6月16日 上午12:47:46 209 | * @return 210 | */ 211 | public static boolean getWechatStatus() { 212 | return core.isAlive(); 213 | } 214 | 215 | } 216 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/beans/AppInfo.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.beans; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * AppInfo 7 | * 8 | * @author https://github.com/yaphone 9 | * @date 创建时间:2017年7月3日 下午10:38:14 10 | * @version 1.0 11 | * 12 | */ 13 | public class AppInfo implements Serializable { 14 | /** 15 | * 16 | */ 17 | private static final long serialVersionUID = 1L; 18 | private int type; 19 | private String appId; 20 | 21 | public int getType() { 22 | return type; 23 | } 24 | 25 | public void setType(int type) { 26 | this.type = type; 27 | } 28 | 29 | public String getAppId() { 30 | return appId; 31 | } 32 | 33 | public void setAppId(String appId) { 34 | this.appId = appId; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/beans/BaseMsg.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.beans; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * 收到的微信消息 7 | * 8 | * @author https://github.com/yaphone 9 | * @date 创建时间:2017年7月3日 下午10:28:06 10 | * @version 1.0 11 | * 12 | */ 13 | public class BaseMsg implements Serializable { 14 | /** 15 | * 16 | */ 17 | private static final long serialVersionUID = 1L; 18 | private int subMsgType; 19 | private int voiceLength; 20 | private String fileName; 21 | private int imgHeight; 22 | private String toUserName; 23 | private int hasProductId; 24 | private int imgStatus; 25 | private String url; 26 | private int imgWidth; 27 | private int forwardFlag; 28 | private int status; 29 | private String Ticket; 30 | /** 推荐消息报文 **/ 31 | private RecommendInfo recommendInfo; 32 | private long createTime; 33 | private String newMsgId; 34 | /** 文本消息内容 **/ 35 | private String text; 36 | /** 消息类型 **/ 37 | private int msgType; 38 | /** 是否为群消息 **/ 39 | private boolean groupMsg; 40 | private String msgId; 41 | private int statusNotifyCode; 42 | private AppInfo appInfo; 43 | private int appMsgType; 44 | private String Type; 45 | private int playLength; 46 | private String mediaId; 47 | private String content; 48 | private String statusNotifyUserName; 49 | /** 消息发送者ID **/ 50 | private String fromUserName; 51 | private String groupUserName; 52 | private String groupUserNickName; 53 | private String groupName; 54 | private String oriContent; 55 | private String fileSize; 56 | /**如果是私聊才有这个值*/ 57 | private String fromUserNickName; 58 | 59 | public int getSubMsgType() { 60 | return subMsgType; 61 | } 62 | 63 | public void setSubMsgType(int subMsgType) { 64 | this.subMsgType = subMsgType; 65 | } 66 | 67 | public int getVoiceLength() { 68 | return voiceLength; 69 | } 70 | 71 | public void setVoiceLength(int voiceLength) { 72 | this.voiceLength = voiceLength; 73 | } 74 | 75 | public String getFileName() { 76 | return fileName; 77 | } 78 | 79 | public void setFileName(String fileName) { 80 | this.fileName = fileName; 81 | } 82 | 83 | public int getImgHeight() { 84 | return imgHeight; 85 | } 86 | 87 | public void setImgHeight(int imgHeight) { 88 | this.imgHeight = imgHeight; 89 | } 90 | 91 | public String getToUserName() { 92 | return toUserName; 93 | } 94 | 95 | public void setToUserName(String toUserName) { 96 | this.toUserName = toUserName; 97 | } 98 | 99 | public int getHasProductId() { 100 | return hasProductId; 101 | } 102 | 103 | public void setHasProductId(int hasProductId) { 104 | this.hasProductId = hasProductId; 105 | } 106 | 107 | public int getImgStatus() { 108 | return imgStatus; 109 | } 110 | 111 | public void setImgStatus(int imgStatus) { 112 | this.imgStatus = imgStatus; 113 | } 114 | 115 | public String getUrl() { 116 | return url; 117 | } 118 | 119 | public void setUrl(String url) { 120 | this.url = url; 121 | } 122 | 123 | public int getImgWidth() { 124 | return imgWidth; 125 | } 126 | 127 | public void setImgWidth(int imgWidth) { 128 | this.imgWidth = imgWidth; 129 | } 130 | 131 | public int getForwardFlag() { 132 | return forwardFlag; 133 | } 134 | 135 | public void setForwardFlag(int forwardFlag) { 136 | this.forwardFlag = forwardFlag; 137 | } 138 | 139 | public int getStatus() { 140 | return status; 141 | } 142 | 143 | public void setStatus(int status) { 144 | this.status = status; 145 | } 146 | 147 | public String getTicket() { 148 | return Ticket; 149 | } 150 | 151 | public void setTicket(String ticket) { 152 | Ticket = ticket; 153 | } 154 | 155 | public RecommendInfo getRecommendInfo() { 156 | return recommendInfo; 157 | } 158 | 159 | public void setRecommendInfo(RecommendInfo recommendInfo) { 160 | this.recommendInfo = recommendInfo; 161 | } 162 | 163 | public long getCreateTime() { 164 | return createTime; 165 | } 166 | 167 | public void setCreateTime(long createTime) { 168 | this.createTime = createTime; 169 | } 170 | 171 | public String getNewMsgId() { 172 | return newMsgId; 173 | } 174 | 175 | public void setNewMsgId(String newMsgId) { 176 | this.newMsgId = newMsgId; 177 | } 178 | 179 | public String getText() { 180 | return text; 181 | } 182 | 183 | public void setText(String text) { 184 | this.text = text; 185 | } 186 | 187 | public int getMsgType() { 188 | return msgType; 189 | } 190 | 191 | public void setMsgType(int msgType) { 192 | this.msgType = msgType; 193 | } 194 | 195 | public boolean isGroupMsg() { 196 | return groupMsg; 197 | } 198 | 199 | public void setGroupMsg(boolean groupMsg) { 200 | this.groupMsg = groupMsg; 201 | } 202 | 203 | public String getMsgId() { 204 | return msgId; 205 | } 206 | 207 | public void setMsgId(String msgId) { 208 | this.msgId = msgId; 209 | } 210 | 211 | public int getStatusNotifyCode() { 212 | return statusNotifyCode; 213 | } 214 | 215 | public void setStatusNotifyCode(int statusNotifyCode) { 216 | this.statusNotifyCode = statusNotifyCode; 217 | } 218 | 219 | public AppInfo getAppInfo() { 220 | return appInfo; 221 | } 222 | 223 | public void setAppInfo(AppInfo appInfo) { 224 | this.appInfo = appInfo; 225 | } 226 | 227 | public int getAppMsgType() { 228 | return appMsgType; 229 | } 230 | 231 | public void setAppMsgType(int appMsgType) { 232 | this.appMsgType = appMsgType; 233 | } 234 | 235 | public String getType() { 236 | return Type; 237 | } 238 | 239 | public void setType(String type) { 240 | Type = type; 241 | } 242 | 243 | public int getPlayLength() { 244 | return playLength; 245 | } 246 | 247 | public void setPlayLength(int playLength) { 248 | this.playLength = playLength; 249 | } 250 | 251 | public String getMediaId() { 252 | return mediaId; 253 | } 254 | 255 | public void setMediaId(String mediaId) { 256 | this.mediaId = mediaId; 257 | } 258 | 259 | public String getContent() { 260 | return content; 261 | } 262 | 263 | public void setContent(String content) { 264 | this.content = content; 265 | } 266 | 267 | public String getStatusNotifyUserName() { 268 | return statusNotifyUserName; 269 | } 270 | 271 | public void setStatusNotifyUserName(String statusNotifyUserName) { 272 | this.statusNotifyUserName = statusNotifyUserName; 273 | } 274 | 275 | public String getFromUserName() { 276 | return fromUserName; 277 | } 278 | 279 | public void setFromUserName(String fromUserName) { 280 | this.fromUserName = fromUserName; 281 | } 282 | 283 | public String getOriContent() { 284 | return oriContent; 285 | } 286 | 287 | public void setOriContent(String oriContent) { 288 | this.oriContent = oriContent; 289 | } 290 | 291 | public String getFileSize() { 292 | return fileSize; 293 | } 294 | 295 | public void setFileSize(String fileSize) { 296 | this.fileSize = fileSize; 297 | } 298 | 299 | public String getGroupUserName() { 300 | return groupUserName; 301 | } 302 | 303 | public void setGroupUserName(String groupUserName) { 304 | this.groupUserName = groupUserName; 305 | } 306 | 307 | public String getGroupUserNickName() { 308 | return groupUserNickName; 309 | } 310 | 311 | public void setGroupUserNickName(String groupUserNickName) { 312 | this.groupUserNickName = groupUserNickName; 313 | } 314 | 315 | public String getGroupName() { 316 | return groupName; 317 | } 318 | 319 | public void setGroupName(String groupName) { 320 | this.groupName = groupName; 321 | } 322 | 323 | public String getFromUserNickName() { 324 | return fromUserNickName; 325 | } 326 | 327 | public void setFromUserNickName(String fromUserNickName) { 328 | this.fromUserNickName = fromUserNickName; 329 | } 330 | 331 | 332 | 333 | 334 | } 335 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/beans/RecommendInfo.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.beans; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * RecommendInfo 7 | * 8 | * @author https://github.com/yaphone 9 | * @date 创建时间:2017年7月3日 下午10:35:14 10 | * @version 1.0 11 | * 12 | */ 13 | public class RecommendInfo implements Serializable { 14 | /** 15 | * 16 | */ 17 | private static final long serialVersionUID = 1L; 18 | 19 | private String ticket; 20 | private String userName; 21 | private int sex; 22 | private int attrStatus; 23 | private String city; 24 | private String nickName; 25 | private int scene; 26 | private String province; 27 | private String content; 28 | private String alias; 29 | private String signature; 30 | private int opCode; 31 | private int qQNum; 32 | private int verifyFlag; 33 | 34 | public String getTicket() { 35 | return ticket; 36 | } 37 | 38 | public void setTicket(String ticket) { 39 | this.ticket = ticket; 40 | } 41 | 42 | public String getUserName() { 43 | return userName; 44 | } 45 | 46 | public void setUserName(String userName) { 47 | this.userName = userName; 48 | } 49 | 50 | public int getSex() { 51 | return sex; 52 | } 53 | 54 | public void setSex(int sex) { 55 | this.sex = sex; 56 | } 57 | 58 | public int getAttrStatus() { 59 | return attrStatus; 60 | } 61 | 62 | public void setAttrStatus(int attrStatus) { 63 | this.attrStatus = attrStatus; 64 | } 65 | 66 | public String getCity() { 67 | return city; 68 | } 69 | 70 | public void setCity(String city) { 71 | this.city = city; 72 | } 73 | 74 | public String getNickName() { 75 | return nickName; 76 | } 77 | 78 | public void setNickName(String nickName) { 79 | this.nickName = nickName; 80 | } 81 | 82 | public int getScene() { 83 | return scene; 84 | } 85 | 86 | public void setScene(int scene) { 87 | this.scene = scene; 88 | } 89 | 90 | public String getProvince() { 91 | return province; 92 | } 93 | 94 | public void setProvince(String province) { 95 | this.province = province; 96 | } 97 | 98 | public String getContent() { 99 | return content; 100 | } 101 | 102 | public void setContent(String content) { 103 | this.content = content; 104 | } 105 | 106 | public String getAlias() { 107 | return alias; 108 | } 109 | 110 | public void setAlias(String alias) { 111 | this.alias = alias; 112 | } 113 | 114 | public String getSignature() { 115 | return signature; 116 | } 117 | 118 | public void setSignature(String signature) { 119 | this.signature = signature; 120 | } 121 | 122 | public int getOpCode() { 123 | return opCode; 124 | } 125 | 126 | public void setOpCode(int opCode) { 127 | this.opCode = opCode; 128 | } 129 | 130 | public int getqQNum() { 131 | return qQNum; 132 | } 133 | 134 | public void setqQNum(int qQNum) { 135 | this.qQNum = qQNum; 136 | } 137 | 138 | public int getVerifyFlag() { 139 | return verifyFlag; 140 | } 141 | 142 | public void setVerifyFlag(int verifyFlag) { 143 | this.verifyFlag = verifyFlag; 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/controller/LoginController.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.controller; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import cn.zhouyafeng.itchat4j.api.WechatTools; 7 | import cn.zhouyafeng.itchat4j.core.Core; 8 | import cn.zhouyafeng.itchat4j.service.ILoginService; 9 | import cn.zhouyafeng.itchat4j.service.impl.LoginServiceImpl; 10 | import cn.zhouyafeng.itchat4j.thread.CheckLoginStatusThread; 11 | import cn.zhouyafeng.itchat4j.utils.SleepUtils; 12 | import cn.zhouyafeng.itchat4j.utils.tools.CommonTools; 13 | 14 | /** 15 | * 登陆控制器 16 | * 17 | * @author https://github.com/yaphone 18 | * @date 创建时间:2017年5月13日 下午12:56:07 19 | * @version 1.0 20 | * 21 | */ 22 | public class LoginController { 23 | private static Logger LOG = LoggerFactory.getLogger(LoginController.class); 24 | private ILoginService loginService = new LoginServiceImpl(); 25 | private static Core core = Core.getInstance(); 26 | 27 | public void login(String qrPath) { 28 | if (core.isAlive()) { // 已登陆 29 | LOG.info("itchat4j已登陆"); 30 | return; 31 | } 32 | while (true) { 33 | for (int count = 0; count < 10; count++) { 34 | LOG.info("获取UUID"); 35 | while (loginService.getUuid() == null) { 36 | LOG.info("1. 获取微信UUID"); 37 | while (loginService.getUuid() == null) { 38 | LOG.warn("1.1. 获取微信UUID失败,两秒后重新获取"); 39 | SleepUtils.sleep(2000); 40 | } 41 | } 42 | LOG.info("2. 获取登陆二维码图片"); 43 | if (loginService.getQR(qrPath)) { 44 | break; 45 | } else if (count == 10) { 46 | LOG.error("2.2. 获取登陆二维码图片失败,系统退出"); 47 | System.exit(0); 48 | } 49 | } 50 | LOG.info("3. 请扫描二维码图片,并在手机上确认"); 51 | if (!core.isAlive()) { 52 | loginService.login(); 53 | core.setAlive(true); 54 | LOG.info(("登陆成功")); 55 | break; 56 | } 57 | LOG.info("4. 登陆超时,请重新扫描二维码图片"); 58 | } 59 | 60 | LOG.info("5. 登陆成功,微信初始化"); 61 | if (!loginService.webWxInit()) { 62 | LOG.info("6. 微信初始化异常"); 63 | System.exit(0); 64 | } 65 | 66 | LOG.info("6. 开启微信状态通知"); 67 | loginService.wxStatusNotify(); 68 | 69 | LOG.info("7. 清除。。。。"); 70 | CommonTools.clearScreen(); 71 | LOG.info(String.format("欢迎回来, %s", core.getNickName())); 72 | 73 | LOG.info("8. 开始接收消息"); 74 | loginService.startReceiving(); 75 | 76 | LOG.info("9. 获取联系人信息"); 77 | loginService.webWxGetContact(); 78 | 79 | LOG.info("10. 获取群好友及群好友列表"); 80 | loginService.WebWxBatchGetContact(); 81 | 82 | LOG.info("11. 缓存本次登陆好友相关消息"); 83 | WechatTools.setUserInfo(); // 登陆成功后缓存本次登陆好友相关消息(NickName, UserName) 84 | 85 | LOG.info("12.开启微信状态检测线程"); 86 | new Thread(new CheckLoginStatusThread()).start(); 87 | } 88 | } -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/core/Core.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.core; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import com.alibaba.fastjson.JSONArray; 9 | import com.alibaba.fastjson.JSONObject; 10 | 11 | import cn.zhouyafeng.itchat4j.beans.BaseMsg; 12 | import cn.zhouyafeng.itchat4j.utils.MyHttpClient; 13 | import cn.zhouyafeng.itchat4j.utils.enums.parameters.BaseParaEnum; 14 | 15 | /** 16 | * 核心存储类,全局只保存一份,单例模式 17 | * 18 | * @author https://github.com/yaphone 19 | * @date 创建时间:2017年4月23日 下午2:33:56 20 | * @version 1.0 21 | * 22 | */ 23 | public class Core { 24 | 25 | private static Core instance; 26 | 27 | private Core() { 28 | 29 | } 30 | 31 | public static Core getInstance() { 32 | if (instance == null) { 33 | synchronized (Core.class) { 34 | instance = new Core(); 35 | } 36 | } 37 | return instance; 38 | } 39 | 40 | boolean alive = false; 41 | private int memberCount = 0; 42 | 43 | private String indexUrl; 44 | 45 | private String userName; 46 | private String nickName; 47 | private List msgList = new ArrayList(); 48 | 49 | private JSONObject userSelf; // 登陆账号自身信息 50 | private List memberList = new ArrayList(); // 好友+群聊+公众号+特殊账号 51 | private List contactList = new ArrayList();// 好友 52 | private List groupList = new ArrayList();; // 群 53 | private Map groupMemeberMap = new HashMap(); // 群聊成员字典 54 | private List publicUsersList = new ArrayList();;// 公众号/服务号 55 | private List specialUsersList = new ArrayList();;// 特殊账号 56 | private List groupIdList = new ArrayList(); // 群ID列表 57 | private List groupNickNameList = new ArrayList(); // 群NickName列表 58 | 59 | private Map userInfoMap = new HashMap(); 60 | 61 | Map loginInfo = new HashMap(); 62 | // CloseableHttpClient httpClient = HttpClients.createDefault(); 63 | MyHttpClient myHttpClient = MyHttpClient.getInstance(); 64 | String uuid = null; 65 | 66 | boolean useHotReload = false; 67 | String hotReloadDir = "itchat.pkl"; 68 | int receivingRetryCount = 5; 69 | 70 | private long lastNormalRetcodeTime; // 最后一次收到正常retcode的时间,秒为单位 71 | 72 | /** 73 | * 请求参数 74 | */ 75 | public Map getParamMap() { 76 | return new HashMap(1) { 77 | /** 78 | * 79 | */ 80 | private static final long serialVersionUID = 1L; 81 | 82 | { 83 | Map map = new HashMap(); 84 | for (BaseParaEnum baseRequest : BaseParaEnum.values()) { 85 | map.put(baseRequest.para(), getLoginInfo().get(baseRequest.value()).toString()); 86 | } 87 | put("BaseRequest", map); 88 | } 89 | }; 90 | } 91 | 92 | public boolean isAlive() { 93 | return alive; 94 | } 95 | 96 | public void setAlive(boolean alive) { 97 | this.alive = alive; 98 | } 99 | 100 | public List getMemberList() { 101 | return memberList; 102 | } 103 | 104 | public void setMemberList(List memberList) { 105 | this.memberList = memberList; 106 | } 107 | 108 | public Map getLoginInfo() { 109 | return loginInfo; 110 | } 111 | 112 | public void setLoginInfo(Map loginInfo) { 113 | this.loginInfo = loginInfo; 114 | } 115 | 116 | public String getUuid() { 117 | return uuid; 118 | } 119 | 120 | public void setUuid(String uuid) { 121 | this.uuid = uuid; 122 | } 123 | 124 | public int getMemberCount() { 125 | return memberCount; 126 | } 127 | 128 | public void setMemberCount(int memberCount) { 129 | this.memberCount = memberCount; 130 | } 131 | 132 | public boolean isUseHotReload() { 133 | return useHotReload; 134 | } 135 | 136 | public void setUseHotReload(boolean useHotReload) { 137 | this.useHotReload = useHotReload; 138 | } 139 | 140 | public String getHotReloadDir() { 141 | return hotReloadDir; 142 | } 143 | 144 | public void setHotReloadDir(String hotReloadDir) { 145 | this.hotReloadDir = hotReloadDir; 146 | } 147 | 148 | public int getReceivingRetryCount() { 149 | return receivingRetryCount; 150 | } 151 | 152 | public void setReceivingRetryCount(int receivingRetryCount) { 153 | this.receivingRetryCount = receivingRetryCount; 154 | } 155 | 156 | public MyHttpClient getMyHttpClient() { 157 | return myHttpClient; 158 | } 159 | 160 | public List getMsgList() { 161 | return msgList; 162 | } 163 | 164 | public void setMsgList(List msgList) { 165 | this.msgList = msgList; 166 | } 167 | 168 | public void setMyHttpClient(MyHttpClient myHttpClient) { 169 | this.myHttpClient = myHttpClient; 170 | } 171 | 172 | public List getGroupIdList() { 173 | return groupIdList; 174 | } 175 | 176 | public void setGroupIdList(List groupIdList) { 177 | this.groupIdList = groupIdList; 178 | } 179 | 180 | public List getContactList() { 181 | return contactList; 182 | } 183 | 184 | public void setContactList(List contactList) { 185 | this.contactList = contactList; 186 | } 187 | 188 | public List getGroupList() { 189 | return groupList; 190 | } 191 | 192 | public void setGroupList(List groupList) { 193 | this.groupList = groupList; 194 | } 195 | 196 | public List getPublicUsersList() { 197 | return publicUsersList; 198 | } 199 | 200 | public void setPublicUsersList(List publicUsersList) { 201 | this.publicUsersList = publicUsersList; 202 | } 203 | 204 | public List getSpecialUsersList() { 205 | return specialUsersList; 206 | } 207 | 208 | public void setSpecialUsersList(List specialUsersList) { 209 | this.specialUsersList = specialUsersList; 210 | } 211 | 212 | public String getUserName() { 213 | return userName; 214 | } 215 | 216 | public void setUserName(String userName) { 217 | this.userName = userName; 218 | } 219 | 220 | public String getNickName() { 221 | return nickName; 222 | } 223 | 224 | public void setNickName(String nickName) { 225 | this.nickName = nickName; 226 | } 227 | 228 | public JSONObject getUserSelf() { 229 | return userSelf; 230 | } 231 | 232 | public void setUserSelf(JSONObject userSelf) { 233 | this.userSelf = userSelf; 234 | } 235 | 236 | public Map getUserInfoMap() { 237 | return userInfoMap; 238 | } 239 | 240 | public void setUserInfoMap(Map userInfoMap) { 241 | this.userInfoMap = userInfoMap; 242 | } 243 | 244 | public synchronized long getLastNormalRetcodeTime() { 245 | return lastNormalRetcodeTime; 246 | } 247 | 248 | public synchronized void setLastNormalRetcodeTime(long lastNormalRetcodeTime) { 249 | this.lastNormalRetcodeTime = lastNormalRetcodeTime; 250 | } 251 | 252 | public List getGroupNickNameList() { 253 | return groupNickNameList; 254 | } 255 | 256 | public void setGroupNickNameList(List groupNickNameList) { 257 | this.groupNickNameList = groupNickNameList; 258 | } 259 | 260 | public Map getGroupMemeberMap() { 261 | return groupMemeberMap; 262 | } 263 | 264 | public void setGroupMemeberMap(Map groupMemeberMap) { 265 | this.groupMemeberMap = groupMemeberMap; 266 | } 267 | 268 | public String getIndexUrl() { 269 | return indexUrl; 270 | } 271 | 272 | public void setIndexUrl(String indexUrl) { 273 | this.indexUrl = indexUrl; 274 | } 275 | 276 | } 277 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/core/MsgCenter.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.core; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.concurrent.TimeUnit; 7 | import java.util.regex.Matcher; 8 | 9 | import org.apache.commons.lang3.StringUtils; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import com.alibaba.fastjson.JSONArray; 14 | import com.alibaba.fastjson.JSONObject; 15 | 16 | import cn.zhouyafeng.itchat4j.api.MessageTools; 17 | import cn.zhouyafeng.itchat4j.beans.BaseMsg; 18 | import cn.zhouyafeng.itchat4j.face.IMsgHandlerFace; 19 | import cn.zhouyafeng.itchat4j.utils.enums.MsgCodeEnum; 20 | import cn.zhouyafeng.itchat4j.utils.enums.MsgTypeEnum; 21 | import cn.zhouyafeng.itchat4j.utils.tools.CommonTools; 22 | 23 | /** 24 | * 消息处理中心 25 | * 26 | * @author https://github.com/yaphone 27 | * @date 创建时间:2017年5月14日 下午12:47:50 28 | * @version 1.0 29 | * 30 | */ 31 | public class MsgCenter { 32 | private static Logger LOG = LoggerFactory.getLogger(MsgCenter.class); 33 | 34 | private static Core core = Core.getInstance(); 35 | 36 | private static Map nickNameMap = new HashMap(); 37 | private static Map groupNameMap = new HashMap(); 38 | /** 39 | * 接收消息,放入队列 40 | * 41 | * @author https://github.com/yaphone 42 | * @date 2017年4月23日 下午2:30:48 43 | * @param msgList 44 | * @return 45 | */ 46 | public static JSONArray produceMsg(JSONArray msgList) { 47 | JSONArray result = new JSONArray(); 48 | for (int i = 0; i < msgList.size(); i++) { 49 | JSONObject msg = new JSONObject(); 50 | JSONObject m = msgList.getJSONObject(i); 51 | m.put("groupMsg", false);// 是否是群消息 52 | if (m.getString("FromUserName").contains("@@") || m.getString("ToUserName").contains("@@")) { // 群聊消息 53 | if (m.getString("FromUserName").contains("@@") 54 | && !core.getGroupIdList().contains(m.getString("FromUserName"))) { 55 | core.getGroupIdList().add((m.getString("FromUserName"))); 56 | } else if (m.getString("ToUserName").contains("@@") 57 | && !core.getGroupIdList().contains(m.getString("ToUserName"))) { 58 | core.getGroupIdList().add((m.getString("ToUserName"))); 59 | } 60 | // 群消息与普通消息不同的是在其消息体(Content)中会包含发送者id及":
"消息,这里需要处理一下,去掉多余信息,只保留消息内容 61 | if (m.getString("Content").contains("
")) { 62 | 63 | String contentText = m.getString("Content"); 64 | String content = contentText.substring(contentText.indexOf("
") + 5); 65 | String groupUserName = contentText.substring(0, contentText.indexOf(":
")); 66 | String groupUserNickName = nickNameMap.get(groupUserName); 67 | String groupName = groupNameMap.get(m.getString("FromUserName")); 68 | 69 | if(StringUtils.isEmpty(groupUserNickName)) { 70 | JSONArray groupMembers = core.getGroupMemeberMap().get(m.getString("FromUserName")); 71 | int size = groupMembers.size(); 72 | for(int j=0; j < size; j++) { 73 | JSONObject member = groupMembers.getJSONObject(j); 74 | if(member.getString("UserName").equals(groupUserName)) { 75 | groupUserNickName = member.getString("NickName"); 76 | nickNameMap.put(groupUserName, groupUserNickName); 77 | break; 78 | } 79 | } 80 | } 81 | 82 | if(StringUtils.isEmpty(groupName)) { 83 | List jsonObjects = core.getGroupList(); 84 | for(JSONObject obj : jsonObjects) { 85 | if(obj.getString("UserName").equals(m.getString("FromUserName"))) { 86 | groupName = obj.getString("NickName"); 87 | groupNameMap.put(m.getString("FromUserName"), groupName); 88 | break; 89 | } 90 | } 91 | } 92 | 93 | m.put("groupName", groupName); 94 | m.put("groupUserName", groupUserName); 95 | m.put("groupUserNickName", groupUserNickName); 96 | m.put("Content", content); 97 | m.put("groupMsg", true); 98 | } 99 | } else { 100 | String fromUserNickName = nickNameMap.get(m.getString("FromUserName")); 101 | if(StringUtils.isEmpty(fromUserNickName)) { 102 | Map userInfoMap = core.getUserInfoMap(); 103 | JSONObject userObj = userInfoMap.get(m.getString("FromUserName")); 104 | if(userObj != null) { 105 | fromUserNickName = userObj.getString("NickName"); 106 | } 107 | 108 | } 109 | m.put("fromUserNickName", fromUserNickName); 110 | CommonTools.msgFormatter(m, "Content"); 111 | } 112 | if (m.getInteger("MsgType").equals(MsgCodeEnum.MSGTYPE_TEXT.getCode())) { // words 113 | // 文本消息 114 | if (m.getString("Url").length() != 0) { 115 | String regEx = "(.+?\\(.+?\\))"; 116 | Matcher matcher = CommonTools.getMatcher(regEx, m.getString("Content")); 117 | String data = "Map"; 118 | if (matcher.find()) { 119 | data = matcher.group(1); 120 | } 121 | msg.put("Type", "Map"); 122 | msg.put("Text", data); 123 | } else { 124 | msg.put("Type", MsgTypeEnum.TEXT.getType()); 125 | msg.put("Text", m.getString("Content")); 126 | } 127 | m.put("Type", msg.getString("Type")); 128 | m.put("Text", msg.getString("Text")); 129 | } else if (m.getInteger("MsgType").equals(MsgCodeEnum.MSGTYPE_IMAGE.getCode()) 130 | || m.getInteger("MsgType").equals(MsgCodeEnum.MSGTYPE_EMOTICON.getCode())) { // 图片消息 131 | m.put("Type", MsgTypeEnum.PIC.getType()); 132 | } else if (m.getInteger("MsgType").equals(MsgCodeEnum.MSGTYPE_VOICE.getCode())) { // 语音消息 133 | m.put("Type", MsgTypeEnum.VOICE.getType()); 134 | } else if (m.getInteger("MsgType").equals(MsgCodeEnum.MSGTYPE_VERIFYMSG.getCode())) {// friends 135 | // 好友确认消息 136 | // MessageTools.addFriend(core, userName, 3, ticket); // 确认添加好友 137 | m.put("Type", MsgTypeEnum.VERIFYMSG.getType()); 138 | 139 | } else if (m.getInteger("MsgType").equals(MsgCodeEnum.MSGTYPE_SHARECARD.getCode())) { // 共享名片 140 | m.put("Type", MsgTypeEnum.NAMECARD.getType()); 141 | 142 | } else if (m.getInteger("MsgType").equals(MsgCodeEnum.MSGTYPE_VIDEO.getCode()) 143 | || m.getInteger("MsgType").equals(MsgCodeEnum.MSGTYPE_MICROVIDEO.getCode())) {// viedo 144 | m.put("Type", MsgTypeEnum.VIEDO.getType()); 145 | } else if (m.getInteger("MsgType").equals(MsgCodeEnum.MSGTYPE_MEDIA.getCode())) { // 多媒体消息 146 | m.put("Type", MsgTypeEnum.MEDIA.getType()); 147 | } else if (m.getInteger("MsgType").equals(MsgCodeEnum.MSGTYPE_STATUSNOTIFY.getCode())) {// phone 148 | // init 149 | // 微信初始化消息 150 | 151 | } else if (m.getInteger("MsgType").equals(MsgCodeEnum.MSGTYPE_SYS.getCode())) {// 系统消息 152 | m.put("Type", MsgTypeEnum.SYS.getType()); 153 | } else if (m.getInteger("MsgType").equals(MsgCodeEnum.MSGTYPE_RECALLED.getCode())) { // 撤回消息 154 | 155 | } else { 156 | LOG.info("Useless msg"); 157 | } 158 | LOG.info("收到消息一条,来自: " + m.getString("FromUserName")); 159 | result.add(m); 160 | } 161 | return result; 162 | } 163 | 164 | /** 165 | * 消息处理 166 | * 167 | * @author https://github.com/yaphone 168 | * @date 2017年5月14日 上午10:52:34 169 | * @param msgHandler 170 | */ 171 | public static void handleMsg(IMsgHandlerFace msgHandler) { 172 | while (true) { 173 | if (core.getMsgList().size() > 0 && core.getMsgList().get(0).getContent() != null) { 174 | if (core.getMsgList().get(0).getContent().length() > 0) { 175 | BaseMsg msg = core.getMsgList().get(0); 176 | if (msg.getType() != null) { 177 | try { 178 | if (msg.getType().equals(MsgTypeEnum.TEXT.getType())) { 179 | String result = msgHandler.textMsgHandle(msg); 180 | if(StringUtils.isNotEmpty(result)) { 181 | MessageTools.sendMsgById(result, core.getMsgList().get(0).getFromUserName()); 182 | } 183 | } else if (msg.getType().equals(MsgTypeEnum.PIC.getType())) { 184 | 185 | String result = msgHandler.picMsgHandle(msg); 186 | if(StringUtils.isNotEmpty(result)) { 187 | MessageTools.sendMsgById(result, core.getMsgList().get(0).getFromUserName()); 188 | } 189 | 190 | } else if (msg.getType().equals(MsgTypeEnum.VOICE.getType())) { 191 | String result = msgHandler.voiceMsgHandle(msg); 192 | if(StringUtils.isNotEmpty(result)) { 193 | MessageTools.sendMsgById(result, core.getMsgList().get(0).getFromUserName()); 194 | } 195 | 196 | } else if (msg.getType().equals(MsgTypeEnum.VIEDO.getType())) { 197 | String result = msgHandler.viedoMsgHandle(msg); 198 | if(StringUtils.isNotEmpty(result)) { 199 | MessageTools.sendMsgById(result, core.getMsgList().get(0).getFromUserName()); 200 | } 201 | 202 | } else if (msg.getType().equals(MsgTypeEnum.NAMECARD.getType())) { 203 | String result = msgHandler.nameCardMsgHandle(msg); 204 | if(StringUtils.isNotEmpty(result)) { 205 | MessageTools.sendMsgById(result, core.getMsgList().get(0).getFromUserName()); 206 | } 207 | 208 | } else if (msg.getType().equals(MsgTypeEnum.SYS.getType())) { // 系统消息 209 | msgHandler.sysMsgHandle(msg); 210 | } else if (msg.getType().equals(MsgTypeEnum.VERIFYMSG.getType())) { // 确认添加好友消息 211 | 212 | String result = msgHandler.verifyAddFriendMsgHandle(msg); 213 | if(StringUtils.isNotEmpty(result)) { 214 | MessageTools.sendMsgById(result, 215 | core.getMsgList().get(0).getRecommendInfo().getUserName()); 216 | } 217 | 218 | } else if (msg.getType().equals(MsgTypeEnum.MEDIA.getType())) { // 多媒体消息 219 | String result = msgHandler.mediaMsgHandle(msg); 220 | if(StringUtils.isNotEmpty(result)) { 221 | MessageTools.sendMsgById(result, core.getMsgList().get(0).getFromUserName()); 222 | } 223 | } 224 | } catch (Exception e) { 225 | e.printStackTrace(); 226 | } 227 | } 228 | } 229 | core.getMsgList().remove(0); 230 | } 231 | try { 232 | TimeUnit.MILLISECONDS.sleep(200); 233 | } catch (InterruptedException e) { 234 | e.printStackTrace(); 235 | } 236 | } 237 | } 238 | 239 | } 240 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/demo/WindowsSimpleDemo.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.demo; 2 | 3 | import java.io.File; 4 | import java.text.SimpleDateFormat; 5 | import java.util.Date; 6 | 7 | import cn.zhouyafeng.itchat4j.Wechat; 8 | import cn.zhouyafeng.itchat4j.beans.BaseMsg; 9 | import cn.zhouyafeng.itchat4j.face.IMsgHandlerFace; 10 | import cn.zhouyafeng.itchat4j.utils.enums.MsgTypeEnum; 11 | import cn.zhouyafeng.itchat4j.utils.tools.DownloadTools; 12 | 13 | public class WindowsSimpleDemo implements IMsgHandlerFace { 14 | 15 | @Override 16 | public String textMsgHandle(BaseMsg msg) { 17 | String text = msg.getContent(); 18 | String result = "收到文本信息: " + text; 19 | return result; 20 | } 21 | 22 | @Override 23 | public String picMsgHandle(BaseMsg msg) { 24 | String fileName = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date()) + ".jpg"; // 这里使用收到图片的时间作为文件名 25 | String picPath = "D://itchat4j/pic" + File.separator + fileName; // 保存图片的路径 26 | DownloadTools.getDownloadFn(msg, MsgTypeEnum.PIC.getType(), picPath); // 调用此方法来保存图片 27 | return "图片保存成功"; 28 | } 29 | 30 | @Override 31 | public String voiceMsgHandle(BaseMsg msg) { 32 | String fileName = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date()) + ".mp3"; // 这里使用收到语音的时间作为文件名 33 | String voicePath = "D://itchat4j/voice" + File.separator + fileName; // 保存语音的路径 34 | DownloadTools.getDownloadFn(msg, MsgTypeEnum.VOICE.getType(), voicePath); // 调用此方法来保存语音 35 | return "声音保存成功"; 36 | } 37 | 38 | @Override 39 | public String viedoMsgHandle(BaseMsg msg) { 40 | String fileName = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date()) + ".mp4"; // 这里使用收到小视频的时间作为文件名 41 | String viedoPath = "D://itchat4j/viedo" + File.separator + fileName;// 保存小视频的路径 42 | DownloadTools.getDownloadFn(msg, MsgTypeEnum.VIEDO.getType(), viedoPath);// 调用此方法来保存小视频 43 | return "视频保存成功"; 44 | } 45 | 46 | public static void main(String[] args) { 47 | IMsgHandlerFace msgHandler = new WindowsSimpleDemo(); 48 | String qrPath = "D://itchat4j/login"; 49 | // Wechat wechat = new Wechat(msgHandler, 50 | // "/home/itchat4j/demo/itchat4j/login"); 51 | Wechat wechat = new Wechat(msgHandler, qrPath); 52 | wechat.start(); 53 | } 54 | 55 | @Override 56 | public String nameCardMsgHandle(BaseMsg arg0) { 57 | return "收到名片消息"; 58 | } 59 | 60 | @Override 61 | public void sysMsgHandle(BaseMsg msg) { 62 | // TODO Auto-generated method stub 63 | 64 | } 65 | 66 | @Override 67 | public String verifyAddFriendMsgHandle(BaseMsg msg) { 68 | // TODO Auto-generated method stub 69 | return null; 70 | } 71 | 72 | @Override 73 | public String mediaMsgHandle(BaseMsg msg) { 74 | // TODO Auto-generated method stub 75 | return null; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/face/IMsgHandlerFace.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.face; 2 | 3 | import cn.zhouyafeng.itchat4j.beans.BaseMsg; 4 | 5 | /** 6 | * 消息处理接口 7 | * 8 | * @author https://github.com/yaphone 9 | * @date 创建时间:2017年4月20日 上午12:13:49 10 | * @version 1.0 11 | * 12 | */ 13 | public interface IMsgHandlerFace { 14 | /** 15 | * 16 | * @author https://github.com/yaphone 17 | * @date 2017年4月20日 上午12:15:00 18 | * @param msg 19 | * @return 20 | */ 21 | public String textMsgHandle(BaseMsg msg); 22 | 23 | /** 24 | * 处理图片消息 25 | * 26 | * @author https://github.com/yaphone 27 | * @date 2017年4月21日 下午11:07:06 28 | * @param msg 29 | * @return 30 | */ 31 | public String picMsgHandle(BaseMsg msg); 32 | 33 | /** 34 | * 处理声音消息 35 | * 36 | * @author https://github.com/yaphone 37 | * @date 2017年4月22日 上午12:09:44 38 | * @param msg 39 | * @return 40 | */ 41 | public String voiceMsgHandle(BaseMsg msg); 42 | 43 | /** 44 | * 处理小视频消息 45 | * 46 | * @author https://github.com/yaphone 47 | * @date 2017年4月23日 下午12:19:50 48 | * @param msg 49 | * @return 50 | */ 51 | public String viedoMsgHandle(BaseMsg msg); 52 | 53 | /** 54 | * 处理名片消息 55 | * 56 | * @author https://github.com/yaphone 57 | * @date 2017年5月1日 上午12:50:50 58 | * @param msg 59 | * @return 60 | */ 61 | public String nameCardMsgHandle(BaseMsg msg); 62 | 63 | /** 64 | * 处理系统消息 65 | * 66 | * @author Relyn 67 | * @date 2017年6月21日17:43:51 68 | * @param msg 69 | * @return 70 | */ 71 | public void sysMsgHandle(BaseMsg msg); 72 | 73 | /** 74 | * 处理确认添加好友消息 75 | * 76 | * @date 2017年6月28日 下午10:15:30 77 | * @param msg 78 | * @return 79 | */ 80 | public String verifyAddFriendMsgHandle(BaseMsg msg); 81 | 82 | /** 83 | * 处理收到的文件消息 84 | * 85 | * @date 2017年7月21日 下午11:59:14 86 | * @param msg 87 | * @return 88 | */ 89 | public String mediaMsgHandle(BaseMsg msg); 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/service/ILoginService.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.service; 2 | 3 | /** 4 | * 登陆服务接口 5 | * 6 | * @author https://github.com/yaphone 7 | * @date 创建时间:2017年5月13日 上午12:07:21 8 | * @version 1.0 9 | * 10 | */ 11 | public interface ILoginService { 12 | 13 | /** 14 | * 登陆 15 | * 16 | * @author https://github.com/yaphone 17 | * @date 2017年5月13日 上午12:14:07 18 | * @return 19 | */ 20 | boolean login(); 21 | 22 | /** 23 | * 获取UUID 24 | * 25 | * @author https://github.com/yaphone 26 | * @date 2017年5月13日 上午12:21:40 27 | * @param qrPath 28 | * @return 29 | */ 30 | String getUuid(); 31 | 32 | /** 33 | * 获取二维码图片 34 | * 35 | * @author https://github.com/yaphone 36 | * @date 2017年5月13日 上午12:13:51 37 | * @param qrPath 38 | * @return 39 | */ 40 | boolean getQR(String qrPath); 41 | 42 | /** 43 | * web初始化 44 | * 45 | * @author https://github.com/yaphone 46 | * @date 2017年5月13日 上午12:14:13 47 | * @return 48 | */ 49 | boolean webWxInit(); 50 | 51 | /** 52 | * 微信状态通知 53 | * 54 | * @author https://github.com/yaphone 55 | * @date 2017年5月13日 上午12:14:24 56 | */ 57 | void wxStatusNotify(); 58 | 59 | /** 60 | * 接收消息 61 | * 62 | * @author https://github.com/yaphone 63 | * @date 2017年5月13日 上午12:14:37 64 | */ 65 | void startReceiving(); 66 | 67 | /** 68 | * 获取微信联系人 69 | * 70 | * @author https://github.com/yaphone 71 | * @date 2017年5月13日 下午2:26:18 72 | */ 73 | void webWxGetContact(); 74 | 75 | /** 76 | * 批量获取联系人信息 77 | * 78 | * @date 2017年6月22日 下午11:24:35 79 | */ 80 | void WebWxBatchGetContact(); 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/service/impl/LoginServiceImpl.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.service.impl; 2 | 3 | import java.io.File; 4 | import java.io.FileOutputStream; 5 | import java.io.OutputStream; 6 | import java.util.ArrayList; 7 | import java.util.Date; 8 | import java.util.HashMap; 9 | import java.util.Iterator; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.Map.Entry; 13 | import java.util.Random; 14 | import java.util.regex.Matcher; 15 | 16 | import org.apache.http.Consts; 17 | import org.apache.http.HttpEntity; 18 | import org.apache.http.message.BasicNameValuePair; 19 | import org.apache.http.util.EntityUtils; 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | import org.w3c.dom.Document; 23 | 24 | import com.alibaba.fastjson.JSON; 25 | import com.alibaba.fastjson.JSONArray; 26 | import com.alibaba.fastjson.JSONObject; 27 | 28 | import cn.zhouyafeng.itchat4j.beans.BaseMsg; 29 | import cn.zhouyafeng.itchat4j.core.Core; 30 | import cn.zhouyafeng.itchat4j.core.MsgCenter; 31 | import cn.zhouyafeng.itchat4j.service.ILoginService; 32 | import cn.zhouyafeng.itchat4j.utils.Config; 33 | import cn.zhouyafeng.itchat4j.utils.MyHttpClient; 34 | import cn.zhouyafeng.itchat4j.utils.SleepUtils; 35 | import cn.zhouyafeng.itchat4j.utils.enums.ResultEnum; 36 | import cn.zhouyafeng.itchat4j.utils.enums.RetCodeEnum; 37 | import cn.zhouyafeng.itchat4j.utils.enums.StorageLoginInfoEnum; 38 | import cn.zhouyafeng.itchat4j.utils.enums.URLEnum; 39 | import cn.zhouyafeng.itchat4j.utils.enums.parameters.BaseParaEnum; 40 | import cn.zhouyafeng.itchat4j.utils.enums.parameters.LoginParaEnum; 41 | import cn.zhouyafeng.itchat4j.utils.enums.parameters.StatusNotifyParaEnum; 42 | import cn.zhouyafeng.itchat4j.utils.enums.parameters.UUIDParaEnum; 43 | import cn.zhouyafeng.itchat4j.utils.tools.CommonTools; 44 | 45 | /** 46 | * 登陆服务实现类 47 | * 48 | * @author https://github.com/yaphone 49 | * @date 创建时间:2017年5月13日 上午12:09:35 50 | * @version 1.0 51 | * 52 | */ 53 | public class LoginServiceImpl implements ILoginService { 54 | private static Logger LOG = LoggerFactory.getLogger(LoginServiceImpl.class); 55 | 56 | private Core core = Core.getInstance(); 57 | private MyHttpClient httpClient = core.getMyHttpClient(); 58 | 59 | private MyHttpClient myHttpClient = core.getMyHttpClient(); 60 | 61 | public LoginServiceImpl() { 62 | 63 | } 64 | 65 | @Override 66 | public boolean login() { 67 | 68 | boolean isLogin = false; 69 | // 组装参数和URL 70 | List params = new ArrayList(); 71 | params.add(new BasicNameValuePair(LoginParaEnum.LOGIN_ICON.para(), LoginParaEnum.LOGIN_ICON.value())); 72 | params.add(new BasicNameValuePair(LoginParaEnum.UUID.para(), core.getUuid())); 73 | params.add(new BasicNameValuePair(LoginParaEnum.TIP.para(), LoginParaEnum.TIP.value())); 74 | 75 | // long time = 4000; 76 | while (!isLogin) { 77 | // SleepUtils.sleep(time += 1000); 78 | long millis = System.currentTimeMillis(); 79 | params.add(new BasicNameValuePair(LoginParaEnum.R.para(), String.valueOf(millis / 1579L))); 80 | params.add(new BasicNameValuePair(LoginParaEnum._.para(), String.valueOf(millis))); 81 | HttpEntity entity = httpClient.doGet(URLEnum.LOGIN_URL.getUrl(), params, true, null); 82 | 83 | try { 84 | String result = EntityUtils.toString(entity); 85 | String status = checklogin(result); 86 | 87 | if (ResultEnum.SUCCESS.getCode().equals(status)) { 88 | processLoginInfo(result); // 处理结果 89 | isLogin = true; 90 | core.setAlive(isLogin); 91 | break; 92 | } 93 | if (ResultEnum.WAIT_CONFIRM.getCode().equals(status)) { 94 | LOG.info("请点击微信确认按钮,进行登陆"); 95 | } 96 | 97 | } catch (Exception e) { 98 | LOG.error("微信登陆异常!", e); 99 | } 100 | } 101 | return isLogin; 102 | } 103 | 104 | @Override 105 | public String getUuid() { 106 | // 组装参数和URL 107 | List params = new ArrayList(); 108 | params.add(new BasicNameValuePair(UUIDParaEnum.APP_ID.para(), UUIDParaEnum.APP_ID.value())); 109 | params.add(new BasicNameValuePair(UUIDParaEnum.REDIRECT_URL.para(), 110 | UUIDParaEnum.REDIRECT_URL.value())); 111 | params.add(new BasicNameValuePair(UUIDParaEnum.FUN.para(), UUIDParaEnum.FUN.value())); 112 | params.add(new BasicNameValuePair(UUIDParaEnum.LANG.para(), UUIDParaEnum.LANG.value())); 113 | params.add(new BasicNameValuePair(UUIDParaEnum._.para(), String.valueOf(System.currentTimeMillis()))); 114 | 115 | HttpEntity entity = httpClient.doGet(URLEnum.UUID_URL.getUrl(), params, true, null); 116 | 117 | try { 118 | String result = EntityUtils.toString(entity); 119 | String regEx = "window.QRLogin.code = (\\d+); window.QRLogin.uuid = \"(\\S+?)\";"; 120 | Matcher matcher = CommonTools.getMatcher(regEx, result); 121 | if (matcher.find()) { 122 | if ((ResultEnum.SUCCESS.getCode().equals(matcher.group(1)))) { 123 | core.setUuid(matcher.group(2)); 124 | } 125 | } 126 | } catch (Exception e) { 127 | LOG.error(e.getMessage(), e); 128 | } 129 | 130 | return core.getUuid(); 131 | } 132 | 133 | @Override 134 | public boolean getQR(String qrPath) { 135 | qrPath = qrPath + File.separator + "QR.jpg"; 136 | String qrUrl = URLEnum.QRCODE_URL.getUrl() + core.getUuid(); 137 | HttpEntity entity = myHttpClient.doGet(qrUrl, null, true, null); 138 | try { 139 | OutputStream out = new FileOutputStream(qrPath); 140 | byte[] bytes = EntityUtils.toByteArray(entity); 141 | out.write(bytes); 142 | out.flush(); 143 | out.close(); 144 | try { 145 | CommonTools.printQr(qrPath); // 打开登陆二维码图片 146 | } catch (Exception e) { 147 | LOG.info(e.getMessage()); 148 | } 149 | 150 | } catch (Exception e) { 151 | LOG.info(e.getMessage()); 152 | return false; 153 | } 154 | 155 | return true; 156 | } 157 | 158 | @Override 159 | public boolean webWxInit() { 160 | core.setAlive(true); 161 | core.setLastNormalRetcodeTime(System.currentTimeMillis()); 162 | // 组装请求URL和参数 163 | String url = String.format(URLEnum.INIT_URL.getUrl(), 164 | core.getLoginInfo().get(StorageLoginInfoEnum.url.getKey()), 165 | String.valueOf(System.currentTimeMillis() / 3158L), 166 | core.getLoginInfo().get(StorageLoginInfoEnum.pass_ticket.getKey())); 167 | 168 | Map paramMap = core.getParamMap(); 169 | 170 | // 请求初始化接口 171 | HttpEntity entity = httpClient.doPost(url, JSON.toJSONString(paramMap)); 172 | try { 173 | String result = EntityUtils.toString(entity, Consts.UTF_8); 174 | JSONObject obj = JSON.parseObject(result); 175 | 176 | JSONObject user = obj.getJSONObject(StorageLoginInfoEnum.User.getKey()); 177 | JSONObject syncKey = obj.getJSONObject(StorageLoginInfoEnum.SyncKey.getKey()); 178 | 179 | core.getLoginInfo().put(StorageLoginInfoEnum.InviteStartCount.getKey(), 180 | obj.getInteger(StorageLoginInfoEnum.InviteStartCount.getKey())); 181 | core.getLoginInfo().put(StorageLoginInfoEnum.SyncKey.getKey(), syncKey); 182 | 183 | JSONArray syncArray = syncKey.getJSONArray("List"); 184 | StringBuilder sb = new StringBuilder(); 185 | for (int i = 0; i < syncArray.size(); i++) { 186 | sb.append(syncArray.getJSONObject(i).getString("Key") + "_" 187 | + syncArray.getJSONObject(i).getString("Val") + "|"); 188 | } 189 | // 1_661706053|2_661706420|3_661706415|1000_1494151022| 190 | String synckey = sb.toString(); 191 | 192 | // 1_661706053|2_661706420|3_661706415|1000_1494151022 193 | core.getLoginInfo().put(StorageLoginInfoEnum.synckey.getKey(), synckey.substring(0, synckey.length() - 1));// 1_656161336|2_656161626|3_656161313|11_656159955|13_656120033|201_1492273724|1000_1492265953|1001_1492250432|1004_1491805192 194 | core.setUserName(user.getString("UserName")); 195 | core.setNickName(user.getString("NickName")); 196 | core.setUserSelf(obj.getJSONObject("User")); 197 | 198 | String chatSet = obj.getString("ChatSet"); 199 | String[] chatSetArray = chatSet.split(","); 200 | for (int i = 0; i < chatSetArray.length; i++) { 201 | if (chatSetArray[i].indexOf("@@") != -1) { 202 | // 更新GroupIdList 203 | core.getGroupIdList().add(chatSetArray[i]); // 204 | } 205 | } 206 | // JSONArray contactListArray = obj.getJSONArray("ContactList"); 207 | // for (int i = 0; i < contactListArray.size(); i++) { 208 | // JSONObject o = contactListArray.getJSONObject(i); 209 | // if (o.getString("UserName").indexOf("@@") != -1) { 210 | // core.getGroupIdList().add(o.getString("UserName")); // 211 | // // 更新GroupIdList 212 | // core.getGroupList().add(o); // 更新GroupList 213 | // core.getGroupNickNameList().add(o.getString("NickName")); 214 | // } 215 | // } 216 | } catch (Exception e) { 217 | e.printStackTrace(); 218 | return false; 219 | } 220 | return true; 221 | } 222 | 223 | @Override 224 | public void wxStatusNotify() { 225 | // 组装请求URL和参数 226 | String url = String.format(URLEnum.STATUS_NOTIFY_URL.getUrl(), 227 | core.getLoginInfo().get(StorageLoginInfoEnum.pass_ticket.getKey())); 228 | 229 | Map paramMap = core.getParamMap(); 230 | paramMap.put(StatusNotifyParaEnum.CODE.para(), StatusNotifyParaEnum.CODE.value()); 231 | paramMap.put(StatusNotifyParaEnum.FROM_USERNAME.para(), core.getUserName()); 232 | paramMap.put(StatusNotifyParaEnum.TO_USERNAME.para(), core.getUserName()); 233 | paramMap.put(StatusNotifyParaEnum.CLIENT_MSG_ID.para(), System.currentTimeMillis()); 234 | String paramStr = JSON.toJSONString(paramMap); 235 | 236 | try { 237 | HttpEntity entity = httpClient.doPost(url, paramStr); 238 | EntityUtils.toString(entity, Consts.UTF_8); 239 | } catch (Exception e) { 240 | LOG.error("微信状态通知接口失败!", e); 241 | } 242 | 243 | } 244 | 245 | @Override 246 | public void startReceiving() { 247 | core.setAlive(true); 248 | new Thread(new Runnable() { 249 | int retryCount = 0; 250 | 251 | @Override 252 | public void run() { 253 | while (core.isAlive()) { 254 | try { 255 | Map resultMap = syncCheck(); 256 | LOG.info(JSONObject.toJSONString(resultMap)); 257 | String retcode = resultMap.get("retcode"); 258 | String selector = resultMap.get("selector"); 259 | if (retcode.equals(RetCodeEnum.UNKOWN.getCode())) { 260 | LOG.info(RetCodeEnum.UNKOWN.getType()); 261 | continue; 262 | } else if (retcode.equals(RetCodeEnum.LOGIN_OUT.getCode())) { // 退出 263 | LOG.info(RetCodeEnum.LOGIN_OUT.getType()); 264 | break; 265 | } else if (retcode.equals(RetCodeEnum.LOGIN_OTHERWHERE.getCode())) { // 其它地方登陆 266 | LOG.info(RetCodeEnum.LOGIN_OTHERWHERE.getType()); 267 | break; 268 | } else if (retcode.equals(RetCodeEnum.MOBILE_LOGIN_OUT.getCode())) { // 移动端退出 269 | LOG.info(RetCodeEnum.MOBILE_LOGIN_OUT.getType()); 270 | break; 271 | } else if (retcode.equals(RetCodeEnum.NORMAL.getCode())) { 272 | core.setLastNormalRetcodeTime(System.currentTimeMillis()); // 最后收到正常报文时间 273 | JSONObject msgObj = webWxSync(); 274 | if (selector.equals("2")) { 275 | if (msgObj != null) { 276 | try { 277 | JSONArray msgList = new JSONArray(); 278 | msgList = msgObj.getJSONArray("AddMsgList"); 279 | msgList = MsgCenter.produceMsg(msgList); 280 | for (int j = 0; j < msgList.size(); j++) { 281 | BaseMsg baseMsg = JSON.toJavaObject(msgList.getJSONObject(j), 282 | BaseMsg.class); 283 | core.getMsgList().add(baseMsg); 284 | } 285 | } catch (Exception e) { 286 | LOG.info(e.getMessage()); 287 | } 288 | } 289 | } else if (selector.equals("7")) { 290 | webWxSync(); 291 | } else if (selector.equals("4")) { 292 | continue; 293 | } else if (selector.equals("3")) { 294 | continue; 295 | } else if (selector.equals("6")) { 296 | if (msgObj != null) { 297 | try { 298 | JSONArray msgList = new JSONArray(); 299 | msgList = msgObj.getJSONArray("AddMsgList"); 300 | JSONArray modContactList = msgObj.getJSONArray("ModContactList"); // 存在删除或者新增的好友信息 301 | msgList = MsgCenter.produceMsg(msgList); 302 | for (int j = 0; j < msgList.size(); j++) { 303 | JSONObject userInfo = modContactList.getJSONObject(j); 304 | // 存在主动加好友之后的同步联系人到本地 305 | core.getContactList().add(userInfo); 306 | } 307 | } catch (Exception e) { 308 | LOG.info(e.getMessage()); 309 | } 310 | } 311 | 312 | } 313 | } else { 314 | JSONObject obj = webWxSync(); 315 | } 316 | } catch (Exception e) { 317 | LOG.info(e.getMessage()); 318 | retryCount += 1; 319 | if (core.getReceivingRetryCount() < retryCount) { 320 | core.setAlive(false); 321 | } else { 322 | try { 323 | Thread.sleep(1000); 324 | } catch (InterruptedException e1) { 325 | LOG.info(e.getMessage()); 326 | } 327 | } 328 | } 329 | 330 | } 331 | } 332 | }).start(); 333 | 334 | } 335 | 336 | @Override 337 | public void webWxGetContact() { 338 | String url = String.format(URLEnum.WEB_WX_GET_CONTACT.getUrl(), 339 | core.getLoginInfo().get(StorageLoginInfoEnum.url.getKey())); 340 | Map paramMap = core.getParamMap(); 341 | HttpEntity entity = httpClient.doPost(url, JSON.toJSONString(paramMap)); 342 | 343 | try { 344 | String result = EntityUtils.toString(entity, Consts.UTF_8); 345 | JSONObject fullFriendsJsonList = JSON.parseObject(result); 346 | // 查看seq是否为0,0表示好友列表已全部获取完毕,若大于0,则表示好友列表未获取完毕,当前的字节数(断点续传) 347 | long seq = 0; 348 | long currentTime = 0L; 349 | List params = new ArrayList(); 350 | if (fullFriendsJsonList.get("Seq") != null) { 351 | seq = fullFriendsJsonList.getLong("Seq"); 352 | currentTime = new Date().getTime(); 353 | } 354 | core.setMemberCount(fullFriendsJsonList.getInteger(StorageLoginInfoEnum.MemberCount.getKey())); 355 | JSONArray member = fullFriendsJsonList.getJSONArray(StorageLoginInfoEnum.MemberList.getKey()); 356 | // 循环获取seq直到为0,即获取全部好友列表 ==0:好友获取完毕 >0:好友未获取完毕,此时seq为已获取的字节数 357 | while (seq > 0) { 358 | // 设置seq传参 359 | params.add(new BasicNameValuePair("r", String.valueOf(currentTime))); 360 | params.add(new BasicNameValuePair("seq", String.valueOf(seq))); 361 | entity = httpClient.doGet(url, params, false, null); 362 | 363 | params.remove(new BasicNameValuePair("r", String.valueOf(currentTime))); 364 | params.remove(new BasicNameValuePair("seq", String.valueOf(seq))); 365 | 366 | result = EntityUtils.toString(entity, Consts.UTF_8); 367 | fullFriendsJsonList = JSON.parseObject(result); 368 | 369 | if (fullFriendsJsonList.get("Seq") != null) { 370 | seq = fullFriendsJsonList.getLong("Seq"); 371 | currentTime = new Date().getTime(); 372 | } 373 | 374 | // 累加好友列表 375 | member.addAll(fullFriendsJsonList.getJSONArray(StorageLoginInfoEnum.MemberList.getKey())); 376 | } 377 | core.setMemberCount(member.size()); 378 | for (Iterator iterator = member.iterator(); iterator.hasNext();) { 379 | JSONObject o = (JSONObject) iterator.next(); 380 | if ((o.getInteger("VerifyFlag") & 8) != 0) { // 公众号/服务号 381 | core.getPublicUsersList().add(o); 382 | } else if (Config.API_SPECIAL_USER.contains(o.getString("UserName"))) { // 特殊账号 383 | core.getSpecialUsersList().add(o); 384 | } else if (o.getString("UserName").indexOf("@@") != -1) { // 群聊 385 | if (!core.getGroupIdList().contains(o.getString("UserName"))) { 386 | core.getGroupNickNameList().add(o.getString("NickName")); 387 | core.getGroupIdList().add(o.getString("UserName")); 388 | core.getGroupList().add(o); 389 | } 390 | } else if (o.getString("UserName").equals(core.getUserSelf().getString("UserName"))) { // 自己 391 | core.getContactList().remove(o); 392 | } else { // 普通联系人 393 | core.getContactList().add(o); 394 | } 395 | } 396 | return; 397 | } catch (Exception e) { 398 | LOG.error(e.getMessage(), e); 399 | } 400 | return; 401 | } 402 | 403 | @Override 404 | public void WebWxBatchGetContact() { 405 | String url = String.format(URLEnum.WEB_WX_BATCH_GET_CONTACT.getUrl(), 406 | core.getLoginInfo().get(StorageLoginInfoEnum.url.getKey()), new Date().getTime(), 407 | core.getLoginInfo().get(StorageLoginInfoEnum.pass_ticket.getKey())); 408 | Map paramMap = core.getParamMap(); 409 | paramMap.put("Count", core.getGroupIdList().size()); 410 | List> list = new ArrayList>(); 411 | for (int i = 0; i < core.getGroupIdList().size(); i++) { 412 | HashMap map = new HashMap(); 413 | map.put("UserName", core.getGroupIdList().get(i)); 414 | map.put("EncryChatRoomId", ""); 415 | list.add(map); 416 | } 417 | paramMap.put("List", list); 418 | HttpEntity entity = httpClient.doPost(url, JSON.toJSONString(paramMap)); 419 | try { 420 | String text = EntityUtils.toString(entity, Consts.UTF_8); 421 | JSONObject obj = JSON.parseObject(text); 422 | JSONArray contactList = obj.getJSONArray("ContactList"); 423 | for (int i = 0; i < contactList.size(); i++) { // 群好友 424 | if (contactList.getJSONObject(i).getString("UserName").indexOf("@@") > -1) { // 群 425 | core.getGroupNickNameList().add(contactList.getJSONObject(i).getString("NickName")); // 更新群昵称列表 426 | core.getGroupList().add(contactList.getJSONObject(i)); // 更新群信息(所有)列表 427 | core.getGroupMemeberMap().put(contactList.getJSONObject(i).getString("UserName"), 428 | contactList.getJSONObject(i).getJSONArray("MemberList")); // 更新群成员Map 429 | } 430 | } 431 | } catch (Exception e) { 432 | LOG.info(e.getMessage()); 433 | } 434 | } 435 | 436 | /** 437 | * 检查登陆状态 438 | * 439 | * @param result 440 | * @return 441 | */ 442 | public String checklogin(String result) { 443 | String regEx = "window.code=(\\d+)"; 444 | Matcher matcher = CommonTools.getMatcher(regEx, result); 445 | if (matcher.find()) { 446 | return matcher.group(1); 447 | } 448 | return null; 449 | } 450 | 451 | /** 452 | * 处理登陆信息 453 | * 454 | * @author https://github.com/yaphone 455 | * @date 2017年4月9日 下午12:16:26 456 | * @param result 457 | */ 458 | private void processLoginInfo(String loginContent) { 459 | String regEx = "window.redirect_uri=\"(\\S+)\";"; 460 | Matcher matcher = CommonTools.getMatcher(regEx, loginContent); 461 | if (matcher.find()) { 462 | String originalUrl = matcher.group(1); 463 | String url = originalUrl.substring(0, originalUrl.lastIndexOf('/')); // https://wx2.qq.com/cgi-bin/mmwebwx-bin 464 | core.getLoginInfo().put("url", url); 465 | Map> possibleUrlMap = this.getPossibleUrlMap(); 466 | Iterator>> iterator = possibleUrlMap.entrySet().iterator(); 467 | Map.Entry> entry; 468 | String fileUrl; 469 | String syncUrl; 470 | while (iterator.hasNext()) { 471 | entry = iterator.next(); 472 | String indexUrl = entry.getKey(); 473 | fileUrl = "https://" + entry.getValue().get(0) + "/cgi-bin/mmwebwx-bin"; 474 | syncUrl = "https://" + entry.getValue().get(1) + "/cgi-bin/mmwebwx-bin"; 475 | if (core.getLoginInfo().get("url").toString().contains(indexUrl)) { 476 | core.setIndexUrl(indexUrl); 477 | core.getLoginInfo().put("fileUrl", fileUrl); 478 | core.getLoginInfo().put("syncUrl", syncUrl); 479 | break; 480 | } 481 | } 482 | if (core.getLoginInfo().get("fileUrl") == null && core.getLoginInfo().get("syncUrl") == null) { 483 | core.getLoginInfo().put("fileUrl", url); 484 | core.getLoginInfo().put("syncUrl", url); 485 | } 486 | core.getLoginInfo().put("deviceid", "e" + String.valueOf(new Random().nextLong()).substring(1, 16)); // 生成15位随机数 487 | core.getLoginInfo().put("BaseRequest", new ArrayList()); 488 | String text = ""; 489 | 490 | try { 491 | HttpEntity entity = myHttpClient.doGet(originalUrl, null, false, null); 492 | text = EntityUtils.toString(entity); 493 | } catch (Exception e) { 494 | LOG.info(e.getMessage()); 495 | return; 496 | } 497 | //add by 默非默 2017-08-01 22:28:09 498 | //如果登录被禁止时,则登录返回的message内容不为空,下面代码则判断登录内容是否为空,不为空则退出程序 499 | String msg = getLoginMessage(text); 500 | if (!"".equals(msg)){ 501 | LOG.info(msg); 502 | System.exit(0); 503 | } 504 | Document doc = CommonTools.xmlParser(text); 505 | if (doc != null) { 506 | core.getLoginInfo().put(StorageLoginInfoEnum.skey.getKey(), 507 | doc.getElementsByTagName(StorageLoginInfoEnum.skey.getKey()).item(0).getFirstChild() 508 | .getNodeValue()); 509 | core.getLoginInfo().put(StorageLoginInfoEnum.wxsid.getKey(), 510 | doc.getElementsByTagName(StorageLoginInfoEnum.wxsid.getKey()).item(0).getFirstChild() 511 | .getNodeValue()); 512 | core.getLoginInfo().put(StorageLoginInfoEnum.wxuin.getKey(), 513 | doc.getElementsByTagName(StorageLoginInfoEnum.wxuin.getKey()).item(0).getFirstChild() 514 | .getNodeValue()); 515 | core.getLoginInfo().put(StorageLoginInfoEnum.pass_ticket.getKey(), 516 | doc.getElementsByTagName(StorageLoginInfoEnum.pass_ticket.getKey()).item(0).getFirstChild() 517 | .getNodeValue()); 518 | } 519 | 520 | } 521 | } 522 | 523 | private Map> getPossibleUrlMap() { 524 | Map> possibleUrlMap = new HashMap>(); 525 | possibleUrlMap.put("wx.qq.com", new ArrayList() { 526 | /** 527 | * 528 | */ 529 | private static final long serialVersionUID = 1L; 530 | 531 | { 532 | add("file.wx.qq.com"); 533 | add("webpush.wx.qq.com"); 534 | } 535 | }); 536 | 537 | possibleUrlMap.put("wx2.qq.com", new ArrayList() { 538 | /** 539 | * 540 | */ 541 | private static final long serialVersionUID = 1L; 542 | 543 | { 544 | add("file.wx2.qq.com"); 545 | add("webpush.wx2.qq.com"); 546 | } 547 | }); 548 | possibleUrlMap.put("wx8.qq.com", new ArrayList() { 549 | /** 550 | * 551 | */ 552 | private static final long serialVersionUID = 1L; 553 | 554 | { 555 | add("file.wx8.qq.com"); 556 | add("webpush.wx8.qq.com"); 557 | } 558 | }); 559 | 560 | possibleUrlMap.put("web2.wechat.com", new ArrayList() { 561 | /** 562 | * 563 | */ 564 | private static final long serialVersionUID = 1L; 565 | 566 | { 567 | add("file.web2.wechat.com"); 568 | add("webpush.web2.wechat.com"); 569 | } 570 | }); 571 | possibleUrlMap.put("wechat.com", new ArrayList() { 572 | /** 573 | * 574 | */ 575 | private static final long serialVersionUID = 1L; 576 | 577 | { 578 | add("file.web.wechat.com"); 579 | add("webpush.web.wechat.com"); 580 | } 581 | }); 582 | return possibleUrlMap; 583 | } 584 | 585 | /** 586 | * 同步消息 sync the messages 587 | * 588 | * @author https://github.com/yaphone 589 | * @date 2017年5月12日 上午12:24:55 590 | * @return 591 | */ 592 | private JSONObject webWxSync() { 593 | JSONObject result = null; 594 | String url = String.format(URLEnum.WEB_WX_SYNC_URL.getUrl(), 595 | core.getLoginInfo().get(StorageLoginInfoEnum.url.getKey()), 596 | core.getLoginInfo().get(StorageLoginInfoEnum.wxsid.getKey()), 597 | core.getLoginInfo().get(StorageLoginInfoEnum.skey.getKey()), 598 | core.getLoginInfo().get(StorageLoginInfoEnum.pass_ticket.getKey())); 599 | Map paramMap = core.getParamMap(); 600 | paramMap.put(StorageLoginInfoEnum.SyncKey.getKey(), 601 | core.getLoginInfo().get(StorageLoginInfoEnum.SyncKey.getKey())); 602 | paramMap.put("rr", -new Date().getTime() / 1000); 603 | String paramStr = JSON.toJSONString(paramMap); 604 | try { 605 | HttpEntity entity = myHttpClient.doPost(url, paramStr); 606 | String text = EntityUtils.toString(entity, Consts.UTF_8); 607 | JSONObject obj = JSON.parseObject(text); 608 | if (obj.getJSONObject("BaseResponse").getInteger("Ret") != 0) { 609 | result = null; 610 | } else { 611 | result = obj; 612 | core.getLoginInfo().put(StorageLoginInfoEnum.SyncKey.getKey(), obj.getJSONObject("SyncCheckKey")); 613 | JSONArray syncArray = obj.getJSONObject(StorageLoginInfoEnum.SyncKey.getKey()).getJSONArray("List"); 614 | StringBuilder sb = new StringBuilder(); 615 | for (int i = 0; i < syncArray.size(); i++) { 616 | sb.append(syncArray.getJSONObject(i).getString("Key") + "_" 617 | + syncArray.getJSONObject(i).getString("Val") + "|"); 618 | } 619 | String synckey = sb.toString(); 620 | core.getLoginInfo().put(StorageLoginInfoEnum.synckey.getKey(), 621 | synckey.substring(0, synckey.length() - 1));// 1_656161336|2_656161626|3_656161313|11_656159955|13_656120033|201_1492273724|1000_1492265953|1001_1492250432|1004_1491805192 622 | } 623 | } catch (Exception e) { 624 | LOG.info(e.getMessage()); 625 | } 626 | return result; 627 | 628 | } 629 | 630 | /** 631 | * 检查是否有新消息 check whether there's a message 632 | * 633 | * @author https://github.com/yaphone 634 | * @date 2017年4月16日 上午11:11:34 635 | * @return 636 | * 637 | */ 638 | private Map syncCheck() { 639 | Map resultMap = new HashMap(); 640 | // 组装请求URL和参数 641 | String url = core.getLoginInfo().get(StorageLoginInfoEnum.syncUrl.getKey()) + URLEnum.SYNC_CHECK_URL.getUrl(); 642 | List params = new ArrayList(); 643 | for (BaseParaEnum baseRequest : BaseParaEnum.values()) { 644 | params.add(new BasicNameValuePair(baseRequest.para().toLowerCase(), 645 | core.getLoginInfo().get(baseRequest.value()).toString())); 646 | } 647 | params.add(new BasicNameValuePair("r", String.valueOf(new Date().getTime()))); 648 | params.add(new BasicNameValuePair("synckey", (String) core.getLoginInfo().get("synckey"))); 649 | params.add(new BasicNameValuePair("_", String.valueOf(new Date().getTime()))); 650 | SleepUtils.sleep(7); 651 | try { 652 | HttpEntity entity = myHttpClient.doGet(url, params, true, null); 653 | if (entity == null) { 654 | resultMap.put("retcode", "9999"); 655 | resultMap.put("selector", "9999"); 656 | return resultMap; 657 | } 658 | String text = EntityUtils.toString(entity); 659 | String regEx = "window.synccheck=\\{retcode:\"(\\d+)\",selector:\"(\\d+)\"\\}"; 660 | Matcher matcher = CommonTools.getMatcher(regEx, text); 661 | if (!matcher.find() || matcher.group(1).equals("2")) { 662 | LOG.info(String.format("Unexpected sync check result: %s", text)); 663 | } else { 664 | resultMap.put("retcode", matcher.group(1)); 665 | resultMap.put("selector", matcher.group(2)); 666 | } 667 | } catch (Exception e) { 668 | e.printStackTrace(); 669 | } 670 | return resultMap; 671 | } 672 | 673 | /** 674 | * 解析登录返回的消息,如果成功登录,则message为空 675 | * @param result 676 | * @return 677 | */ 678 | public String getLoginMessage(String result){ 679 | String[] strArr = result.split(""); 680 | String[] rs = strArr[1].split(""); 681 | if (rs!=null && rs.length>1) { 682 | return rs[0]; 683 | } 684 | return ""; 685 | } 686 | } 687 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/thread/CheckLoginStatusThread.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.thread; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import cn.zhouyafeng.itchat4j.core.Core; 7 | import cn.zhouyafeng.itchat4j.utils.SleepUtils; 8 | 9 | /** 10 | * 检查微信在线状态 11 | *

12 | * 如何来感知微信状态? 13 | * 微信会有心跳包,LoginServiceImpl.syncCheck()正常在线情况下返回的消息中retcode报文应该为"0",心跳间隔一般在25秒, 14 | * 那么可以通过最后收到正常报文的时间来作为判断是否在线的依据。若报文间隔大于60秒,则认为已掉线。 15 | *

16 | * 17 | * @author https://github.com/yaphone 18 | * @date 创建时间:2017年5月17日 下午10:53:15 19 | * @version 1.0 20 | * 21 | */ 22 | public class CheckLoginStatusThread implements Runnable { 23 | private static Logger LOG = LoggerFactory.getLogger(CheckLoginStatusThread.class); 24 | private Core core = Core.getInstance(); 25 | 26 | @Override 27 | public void run() { 28 | while (core.isAlive()) { 29 | long t1 = System.currentTimeMillis(); // 秒为单位 30 | if (t1 - core.getLastNormalRetcodeTime() > 60 * 1000) { // 超过60秒,判为离线 31 | //core.setAlive(false); 32 | LOG.info("微信已离线"); 33 | } 34 | SleepUtils.sleep(10 * 1000); // 休眠10秒 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/utils/Config.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.utils; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | 8 | import cn.zhouyafeng.itchat4j.utils.enums.OsNameEnum; 9 | 10 | /** 11 | * 配置信息 12 | * 13 | * @author https://github.com/yaphone 14 | * @date 创建时间:2017年4月23日 下午2:26:21 15 | * @version 1.0 16 | * 17 | */ 18 | public class Config { 19 | 20 | public static final String API_WXAPPID = "API_WXAPPID"; 21 | 22 | public static final String picDir = "D://itchat4j"; 23 | public static final String VERSION = "1.4.1"; 24 | public static final String BASE_URL = "https://login.weixin.qq.com"; 25 | public static final String REFERER = "https://wx.qq.com/?&lang=zh_CN&target=t"; 26 | public static final String OS = ""; 27 | public static final String DIR = ""; 28 | public static final String DEFAULT_QR = "QR.jpg"; 29 | public static final String USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36"; 30 | public static final String UOS_PATCH_CLIENT_VERSION = "2.0.0"; 31 | 32 | public static final String UOS_PATCH_EXTSPAM = 33 | "Go8FCIkFEokFCggwMDAwMDAwMRAGGvAESySibk50w5Wb3uTl2c2h64jVVrV7gNs06GFlWplHQbY/5FfiO++1yH4ykCyNPWKXmco+wfQzK5R98D3so7rJ5LmGFvBLjGceleySrc3SOf2Pc1gVehzJgODeS0lDL3/I/0S2SSE98YgKleq6Uqx6ndTy9yaL9qFxJL7eiA/R3SEfTaW1SBoSITIu+EEkXff+Pv8NHOk7N57rcGk1w0ZzRrQDkXTOXFN2iHYIzAAZPIOY45Lsh+A4slpgnDiaOvRtlQYCt97nmPLuTipOJ8Qc5pM7ZsOsAPPrCQL7nK0I7aPrFDF0q4ziUUKettzW8MrAaiVfmbD1/VkmLNVqqZVvBCtRblXb5FHmtS8FxnqCzYP4WFvz3T0TcrOqwLX1M/DQvcHaGGw0B0y4bZMs7lVScGBFxMj3vbFi2SRKbKhaitxHfYHAOAa0X7/MSS0RNAjdwoyGHeOepXOKY+h3iHeqCvgOH6LOifdHf/1aaZNwSkGotYnYScW8Yx63LnSwba7+hESrtPa/huRmB9KWvMCKbDThL/nne14hnL277EDCSocPu3rOSYjuB9gKSOdVmWsj9Dxb/iZIe+S6AiG29Esm+/eUacSba0k8wn5HhHg9d4tIcixrxveflc8vi2/wNQGVFNsGO6tB5WF0xf/plngOvQ1/ivGV/C1Qpdhzznh0ExAVJ6dwzNg7qIEBaw+BzTJTUuRcPk92Sn6QDn2Pu3mpONaEumacjW4w6ipPnPw+g2TfywJjeEcpSZaP4Q3YV5HG8D6UjWA4GSkBKculWpdCMadx0usMomsSS/74QgpYqcPkmamB4nVv1JxczYITIqItIKjD35IGKAUwAA=="; 34 | 35 | 36 | public static final ArrayList API_SPECIAL_USER = new ArrayList(Arrays.asList("filehelper", "weibo", 37 | "qqmail", "fmessage", "tmessage", "qmessage", "qqsync", "floatbottle", "lbsapp", "shakeapp", "medianote", 38 | "qqfriend", "readerapp", "blogapp", "facebookapp", "masssendapp", "meishiapp", "feedsapp", "voip", 39 | "blogappweixin", "brandsessionholder", "weixin", "weixinreminder", "officialaccounts", "wxitil", 40 | "notification_messages", "wxid_novlwrv3lqwv11", "gh_22b87fa7cb3c", "userexperience_alarm")); 41 | 42 | /** 43 | * 获取文件目录 44 | * 45 | * @author https://github.com/yaphone 46 | * @date 2017年4月8日 下午10:27:42 47 | * @return 48 | */ 49 | public static String getLocalPath() { 50 | String localPath = null; 51 | try { 52 | localPath = new File("").getCanonicalPath(); 53 | } catch (IOException e) { 54 | // TODO Auto-generated catch block 55 | e.printStackTrace(); 56 | } 57 | return localPath; 58 | } 59 | 60 | /** 61 | * 获取系统平台 62 | * 63 | * @author https://github.com/yaphone 64 | * @date 2017年4月8日 下午10:27:53 65 | */ 66 | public static OsNameEnum getOsNameEnum() { 67 | String os = System.getProperty("os.name").toUpperCase(); 68 | if (os.indexOf(OsNameEnum.DARWIN.toString()) >= 0) { 69 | return OsNameEnum.DARWIN; 70 | } else if (os.indexOf(OsNameEnum.WINDOWS.toString()) >= 0) { 71 | return OsNameEnum.WINDOWS; 72 | } else if (os.indexOf(OsNameEnum.LINUX.toString()) >= 0) { 73 | return OsNameEnum.LINUX; 74 | } else if (os.indexOf(OsNameEnum.MAC.toString()) >= 0) { 75 | return OsNameEnum.MAC; 76 | } 77 | return OsNameEnum.OTHER; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/utils/ConstantConfigEnum.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.utils; 2 | 3 | /** 4 | * 常量 5 | * 6 | * @author https=//github.com/yaphone 7 | * @date 创建时间:2017年5月5日 下午11=29=04 8 | * @version 1.0 9 | * 10 | */ 11 | public class ConstantConfigEnum { 12 | public static final int APPMSGTYPE_TEXT = 1; 13 | public static final int APPMSGTYPE_IMG = 2; 14 | public static final int APPMSGTYPE_AUDIO = 3; 15 | public static final int APPMSGTYPE_VIDEO = 4; 16 | public static final int APPMSGTYPE_URL = 5; 17 | public static final int APPMSGTYPE_ATTACH = 6; 18 | public static final int APPMSGTYPE_OPEN = 7; 19 | public static final int APPMSGTYPE_EMOJI = 8; 20 | public static final int APPMSGTYPE_VOICE_REMIND = 9; 21 | public static final int APPMSGTYPE_SCAN_GOOD = 10; 22 | public static final int APPMSGTYPE_GOOD = 13; 23 | public static final int APPMSGTYPE_EMOTION = 15; 24 | public static final int APPMSGTYPE_CARD_TICKET = 16; 25 | public static final int APPMSGTYPE_REALTIME_SHARE_LOCATION = 17; 26 | // public static final int APPMSGTYPE_TRANSFERS = 2e3; 27 | public static final int APPMSGTYPE_RED_ENVELOPES = 2001; 28 | public static final int APPMSGTYPE_READER_TYPE = 100001; 29 | public static final int UPLOAD_MEDIA_TYPE_IMAGE = 1; 30 | public static final int UPLOAD_MEDIA_TYPE_VIDEO = 2; 31 | public static final int UPLOAD_MEDIA_TYPE_AUDIO = 3; 32 | public static final int UPLOAD_MEDIA_TYPE_ATTACHMENT = 4; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/utils/MsgKeywords.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.utils; 2 | 3 | public class MsgKeywords { 4 | public static String newFriendStr = "我通过了你的朋友验证请求"; 5 | 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/utils/MyHttpClient.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.utils; 2 | 3 | import java.io.IOException; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.Map.Entry; 7 | import java.util.Set; 8 | import java.util.logging.Logger; 9 | 10 | import org.apache.http.Consts; 11 | import org.apache.http.HttpEntity; 12 | import org.apache.http.client.ClientProtocolException; 13 | import org.apache.http.client.CookieStore; 14 | import org.apache.http.client.config.RequestConfig; 15 | import org.apache.http.client.entity.UrlEncodedFormEntity; 16 | import org.apache.http.client.methods.CloseableHttpResponse; 17 | import org.apache.http.client.methods.HttpGet; 18 | import org.apache.http.client.methods.HttpPost; 19 | import org.apache.http.cookie.Cookie; 20 | import org.apache.http.entity.StringEntity; 21 | import org.apache.http.impl.client.BasicCookieStore; 22 | import org.apache.http.impl.client.CloseableHttpClient; 23 | import org.apache.http.impl.client.HttpClients; 24 | import org.apache.http.message.BasicNameValuePair; 25 | import org.apache.http.util.EntityUtils; 26 | 27 | import okhttp3.OkHttpClient; 28 | 29 | /** 30 | * HTTP访问类,对Apache HttpClient进行简单封装,适配器模式 31 | * 32 | * @author https://github.com/yaphone 33 | * @date 创建时间:2017年4月9日 下午7:05:04 34 | * @version 1.0 35 | * 36 | */ 37 | public class MyHttpClient { 38 | private Logger logger = Logger.getLogger("MyHttpClient"); 39 | 40 | private static CloseableHttpClient httpClient; 41 | 42 | private static MyHttpClient instance = null; 43 | 44 | private static CookieStore cookieStore; 45 | 46 | static { 47 | cookieStore = new BasicCookieStore(); 48 | 49 | // 将CookieStore设置到httpClient中 50 | httpClient = HttpClients.custom().setDefaultCookieStore(cookieStore).build(); 51 | 52 | } 53 | 54 | public static String getCookie(String name) { 55 | List cookies = cookieStore.getCookies(); 56 | for (Cookie cookie : cookies) { 57 | if (cookie.getName().equalsIgnoreCase(name)) { 58 | return cookie.getValue(); 59 | } 60 | } 61 | return null; 62 | 63 | } 64 | 65 | private MyHttpClient() { 66 | 67 | } 68 | 69 | /** 70 | * 获取cookies 71 | * 72 | * @author https://github.com/yaphone 73 | * @date 2017年5月7日 下午8:37:17 74 | * @return 75 | */ 76 | public static MyHttpClient getInstance() { 77 | if (instance == null) { 78 | synchronized (MyHttpClient.class) { 79 | if (instance == null) { 80 | instance = new MyHttpClient(); 81 | } 82 | } 83 | } 84 | return instance; 85 | } 86 | 87 | /** 88 | * 处理GET请求 89 | * 90 | * @author https://github.com/yaphone 91 | * @date 2017年4月9日 下午7:06:19 92 | * @param url 93 | * @param params 94 | * @return 95 | */ 96 | public HttpEntity doGet(String url, List params, boolean redirect, 97 | Map headerMap) { 98 | HttpEntity entity = null; 99 | HttpGet httpGet = new HttpGet(); 100 | 101 | try { 102 | if (params != null) { 103 | String paramStr = EntityUtils.toString(new UrlEncodedFormEntity(params, Consts.UTF_8)); 104 | httpGet = new HttpGet(url + "?" + paramStr); 105 | } else { 106 | httpGet = new HttpGet(url); 107 | } 108 | if (!redirect) { 109 | httpGet.setConfig(RequestConfig.custom().setRedirectsEnabled(false).build()); // 禁止重定向 110 | } 111 | httpGet.setHeader("User-Agent", Config.USER_AGENT); 112 | httpGet.setHeader("client-version", Config.UOS_PATCH_CLIENT_VERSION); 113 | httpGet.setHeader("extspam", Config.UOS_PATCH_EXTSPAM); 114 | httpGet.setHeader("referer", Config.REFERER); 115 | if (headerMap != null) { 116 | Set> entries = headerMap.entrySet(); 117 | for (Entry entry : entries) { 118 | httpGet.setHeader(entry.getKey(), entry.getValue()); 119 | } 120 | } 121 | CloseableHttpResponse response = httpClient.execute(httpGet); 122 | entity = response.getEntity(); 123 | } catch (ClientProtocolException e) { 124 | logger.info(e.getMessage()); 125 | } catch (IOException e) { 126 | logger.info(e.getMessage()); 127 | } 128 | 129 | return entity; 130 | } 131 | 132 | /** 133 | * 处理POST请求 134 | * 135 | * @author https://github.com/yaphone 136 | * @date 2017年4月9日 下午7:06:35 137 | * @param url 138 | * @param params 139 | * @return 140 | */ 141 | public HttpEntity doPost(String url, String paramsStr) { 142 | HttpEntity entity = null; 143 | HttpPost httpPost = new HttpPost(); 144 | try { 145 | StringEntity params = new StringEntity(paramsStr, Consts.UTF_8); 146 | httpPost = new HttpPost(url); 147 | httpPost.setEntity(params); 148 | httpPost.setHeader("Content-type", "application/json; charset=utf-8"); 149 | httpPost.setHeader("User-Agent", Config.USER_AGENT); 150 | httpPost.setHeader("client-version", Config.UOS_PATCH_CLIENT_VERSION); 151 | httpPost.setHeader("extspam", Config.UOS_PATCH_EXTSPAM); 152 | httpPost.setHeader("referer", Config.REFERER); 153 | 154 | CloseableHttpResponse response = httpClient.execute(httpPost); 155 | entity = response.getEntity(); 156 | } catch (ClientProtocolException e) { 157 | logger.info(e.getMessage()); 158 | } catch (IOException e) { 159 | logger.info(e.getMessage()); 160 | } 161 | 162 | return entity; 163 | } 164 | 165 | /** 166 | * 上传文件到服务器 167 | * 168 | * @author https://github.com/yaphone 169 | * @date 2017年5月7日 下午9:19:23 170 | * @param url 171 | * @param reqEntity 172 | * @return 173 | */ 174 | public HttpEntity doPostFile(String url, HttpEntity reqEntity) { 175 | HttpEntity entity = null; 176 | HttpPost httpPost = new HttpPost(url); 177 | httpPost.setHeader("User-Agent", Config.USER_AGENT); 178 | httpPost.setHeader("client-version", Config.UOS_PATCH_CLIENT_VERSION); 179 | httpPost.setHeader("extspam", Config.UOS_PATCH_EXTSPAM); 180 | httpPost.setHeader("referer", Config.REFERER); 181 | 182 | httpPost.setEntity(reqEntity); 183 | try { 184 | CloseableHttpResponse response = httpClient.execute(httpPost); 185 | entity = response.getEntity(); 186 | 187 | } catch (Exception e) { 188 | logger.info(e.getMessage()); 189 | } 190 | return entity; 191 | } 192 | 193 | public static CloseableHttpClient getHttpClient() { 194 | return httpClient; 195 | } 196 | 197 | } -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/utils/SleepUtils.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.utils; 2 | 3 | /** 4 | * Created by xiaoxiaomo on 2017/5/6. 5 | */ 6 | public class SleepUtils { 7 | 8 | /** 9 | * 毫秒为单位 10 | * @param time 11 | */ 12 | public static void sleep( long time ){ 13 | try { 14 | Thread.sleep( time ); 15 | } catch (InterruptedException e) { 16 | e.printStackTrace(); 17 | } 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/utils/enums/MsgCodeEnum.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.utils.enums; 2 | 3 | /** 4 | * 消息类型 5 | * 6 | * @author https://github.com/yaphone 7 | * @date 创建时间:2017年4月23日 下午12:15:00 8 | * @version 1.0 9 | * 10 | */ 11 | public enum MsgCodeEnum { 12 | 13 | // public static final int MSGTYPE_TEXT = 1; // 文本消息类型 14 | // public static final int MSGTYPE_IMAGE = 3; // 图片消息 15 | // public static final int MSGTYPE_VOICE = 34; // 语音消息 16 | // public static final int MSGTYPE_VIDEO = 43; // 小视频消息 17 | // public static final int MSGTYPE_MICROVIDEO = 62; // 短视频消息 18 | // public static final int MSGTYPE_EMOTICON = 47; // 表情消息 19 | // public static final int MSGTYPE_APP = 49; 20 | // public static final int MSGTYPE_VOIPMSG = 50; 21 | // public static final int MSGTYPE_VOIPNOTIFY = 52; 22 | // public static final int MSGTYPE_VOIPINVITE = 53; 23 | // public static final int MSGTYPE_LOCATION = 48; 24 | // public static final int MSGTYPE_STATUSNOTIFY = 51; 25 | // public static final int MSGTYPE_SYSNOTICE = 9999; 26 | // public static final int MSGTYPE_POSSIBLEFRIEND_MSG = 40; 27 | // public static final int MSGTYPE_VERIFYMSG = 37; 28 | // public static final int MSGTYPE_SHARECARD = 42; 29 | // public static final int MSGTYPE_SYS = 10000; 30 | // public static final int MSGTYPE_RECALLED = 10002; 31 | MSGTYPE_TEXT(1, "文本消息类型"), 32 | MSGTYPE_IMAGE(3, "图片消息"), 33 | MSGTYPE_VOICE(34, "语音消息"), 34 | MSGTYPE_VIDEO(43, "小视频消息"), 35 | MSGTYPE_MICROVIDEO(62, "短视频消息"), 36 | MSGTYPE_EMOTICON(47, "表情消息"), 37 | MSGTYPE_MEDIA(49, "多媒体消息"), 38 | MSGTYPE_VOIPMSG(50, ""), 39 | MSGTYPE_VOIPNOTIFY(52, ""), 40 | MSGTYPE_VOIPINVITE(53, ""), 41 | MSGTYPE_LOCATION(48, ""), 42 | MSGTYPE_STATUSNOTIFY(51, ""), 43 | MSGTYPE_SYSNOTICE(9999, ""), 44 | MSGTYPE_POSSIBLEFRIEND_MSG(40, ""), 45 | MSGTYPE_VERIFYMSG(37, "好友请求"), 46 | MSGTYPE_SHARECARD(42, ""), 47 | MSGTYPE_SYS(10000, "系统消息"), 48 | MSGTYPE_RECALLED(10002, "") 49 | 50 | ; 51 | 52 | private int code; 53 | private String type; 54 | 55 | MsgCodeEnum(int code, String type) { 56 | this.code = code; 57 | this.type = type; 58 | } 59 | 60 | public int getCode() { 61 | return code; 62 | } 63 | 64 | public String getType() { 65 | return type; 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/utils/enums/MsgTypeEnum.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.utils.enums; 2 | 3 | 4 | /** 5 | * 消息类型枚举类 6 | * 7 | * @author https://github.com/yaphone 8 | * @date 创建时间:2017年5月13日 下午11:53:00 9 | * @version 1.0 10 | * 11 | */ 12 | public enum MsgTypeEnum { 13 | TEXT("Text", "文本消息"), 14 | PIC("Pic", "图片消息"), 15 | VOICE("Voice", "语音消息"), 16 | VIEDO("Viedo", "小视频消息"), 17 | NAMECARD("NameCard", "名片消息"), 18 | SYS("Sys", "系统消息"), 19 | VERIFYMSG("VerifyMsg", "添加好友"), 20 | MEDIA("app", "文件消息"); 21 | 22 | private String type; 23 | private String code; 24 | 25 | MsgTypeEnum(String type, String code) { 26 | this.type = type; 27 | this.code = code; 28 | } 29 | 30 | public String getType() { 31 | return type; 32 | } 33 | 34 | public String getCode() { 35 | return code; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/utils/enums/OsNameEnum.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.utils.enums; 2 | 3 | /** 4 | * 系统平台 5 | * 6 | * @author https://github.com/yaphone 7 | * @date 创建时间:2017年4月8日 下午10:36:28 8 | * @version 1.0 9 | * 10 | */ 11 | public enum OsNameEnum { 12 | WINDOWS, LINUX, DARWIN, MAC, OTHER 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/utils/enums/ResultEnum.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.utils.enums; 2 | 3 | /** 4 | * 返回结构枚举类 5 | *

6 | * Created by xiaoxiaomo on 2017/5/6. 7 | */ 8 | public enum ResultEnum { 9 | 10 | SUCCESS("200", "成功"), 11 | WAIT_CONFIRM("201", "请在手机上点击确认"), 12 | WAIT_SCAN("400", "请扫描二维码"); 13 | 14 | private String code; 15 | private String msg; 16 | 17 | ResultEnum(String code, String msg) { 18 | this.code = code; 19 | this.msg = msg; 20 | } 21 | 22 | public String getCode() { 23 | return code; 24 | } 25 | 26 | // public static MsgInfoEnum getCode(String code) { 27 | // switch (code) { 28 | // case "Text": 29 | // return MsgInfoEnum.TEXT; 30 | // default: 31 | // return MsgInfoEnum.VIDEO; 32 | // } 33 | // } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/utils/enums/RetCodeEnum.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.utils.enums; 2 | 3 | public enum RetCodeEnum { 4 | 5 | NORMAL("0", "普通"), 6 | LOGIN_OUT("1102", "退出"), 7 | LOGIN_OTHERWHERE("1101", "其它地方登陆"), 8 | MOBILE_LOGIN_OUT("1102", "移动端退出"), 9 | UNKOWN("9999", "未知") 10 | 11 | ; 12 | 13 | 14 | private String code; 15 | private String type; 16 | 17 | RetCodeEnum(String code, String type) { 18 | this.code = code; 19 | this.type = type; 20 | } 21 | 22 | public String getCode() { 23 | return code; 24 | } 25 | 26 | public String getType() { 27 | return type; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/utils/enums/StorageLoginInfoEnum.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.utils.enums; 2 | 3 | import com.alibaba.fastjson.JSONArray; 4 | import com.alibaba.fastjson.JSONObject; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * Created by xiaoxiaomo on 2017/5/7. 11 | */ 12 | public enum StorageLoginInfoEnum { 13 | 14 | //URL 15 | url("url",new String()), 16 | fileUrl("fileUrl",new String()), 17 | syncUrl("syncUrl",new String()), 18 | 19 | deviceid("deviceid",new String()), //生成15位随机数 20 | 21 | //baseRequest 22 | skey("skey",new String()), 23 | wxsid("wxsid",new String()), 24 | wxuin("wxuin",new String()), 25 | pass_ticket("pass_ticket",new String()), 26 | 27 | 28 | InviteStartCount("InviteStartCount",new Integer(0)), 29 | User("User",new JSONObject()), 30 | SyncKey("SyncKey",new JSONObject()), 31 | synckey("synckey",new String()), 32 | 33 | 34 | 35 | MemberCount("MemberCount",new String()), 36 | MemberList("MemberList",new JSONArray()), 37 | 38 | 39 | 40 | ; 41 | 42 | private String key; 43 | private Object type; 44 | 45 | StorageLoginInfoEnum(String key, Object type) { 46 | this.key = key; 47 | this.type = type; 48 | } 49 | 50 | public String getKey() { 51 | return key; 52 | } 53 | 54 | 55 | public Object getType() { 56 | return type; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/utils/enums/URLEnum.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.utils.enums; 2 | 3 | /** 4 | * URL 5 | * Created by xiaoxiaomo on 2017/5/6. 6 | */ 7 | public enum URLEnum { 8 | 9 | 10 | 11 | BASE_URL("https://login.weixin.qq.com","基本的URL"), 12 | UUID_URL(BASE_URL.url+"/jslogin","UUIDLURL"), 13 | QRCODE_URL(BASE_URL.url+"/qrcode/","初始化URL"), 14 | STATUS_NOTIFY_URL(BASE_URL.url+"/webwxstatusnotify?lang=zh_CN&pass_ticket=%s","微信状态通知"), 15 | LOGIN_URL(BASE_URL.url+"/cgi-bin/mmwebwx-bin/login","登陆URL"), 16 | INIT_URL("%s/webwxinit?r=%s&pass_ticket=%s","初始化URL"), 17 | SYNC_CHECK_URL("/synccheck","检查心跳URL"), 18 | WEB_WX_SYNC_URL("%s/webwxsync?sid=%s&skey=%s&pass_ticket=%s","web微信消息同步URL"), 19 | WEB_WX_GET_CONTACT("%s/webwxgetcontact","web微信获取联系人信息URL"), 20 | WEB_WX_SEND_MSG("%s/webwxsendmsg","发送消息URL"), 21 | WEB_WX_UPLOAD_MEDIA("%s/webwxuploadmedia?f=json", "上传文件到服务器"), 22 | WEB_WX_GET_MSG_IMG("%s/webwxgetmsgimg", "下载图片消息"), 23 | WEB_WX_GET_VOICE("%s/webwxgetvoice", "下载语音消息"), 24 | WEB_WX_GET_VIEDO("%s/webwxgetvideo", "下载语音消息"), 25 | WEB_WX_PUSH_LOGIN("%s/webwxpushloginurl", "不扫码登陆"), 26 | WEB_WX_LOGOUT("%s/webwxlogout", "退出微信"), 27 | WEB_WX_BATCH_GET_CONTACT("%s/webwxbatchgetcontact?type=ex&r=%s&lang=zh_CN&pass_ticket=%s", "查询群信息"), 28 | WEB_WX_REMARKNAME("%s/webwxoplog?lang=zh_CN&pass_ticket=%s", "修改好友备注"), 29 | WEB_WX_VERIFYUSER("%s/webwxverifyuser?r=%s&lang=zh_CN&pass_ticket=%s", "被动添加好友"), 30 | WEB_WX_GET_MEDIA("%s/webwxgetmedia", "下载文件") 31 | 32 | 33 | 34 | 35 | ; 36 | 37 | private String url; 38 | private String msg; 39 | 40 | URLEnum(String url, String msg) { 41 | this.url = url; 42 | this.msg = msg; 43 | } 44 | 45 | 46 | public String getUrl() { 47 | return url; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/utils/enums/VerifyFriendEnum.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.utils.enums; 2 | 3 | /** 4 | * 确认添加好友Enum 5 | * 6 | * @author https://github.com/yaphone 7 | * @date 创建时间:2017年6月29日 下午9:47:14 8 | * @version 1.0 9 | * 10 | */ 11 | public enum VerifyFriendEnum { 12 | 13 | ADD(2, "添加"), 14 | ACCEPT(3, "接受"); 15 | 16 | private int code; 17 | private String desc; 18 | 19 | private VerifyFriendEnum(int code, String desc) { 20 | this.code = code; 21 | this.desc = desc; 22 | } 23 | 24 | public int getCode() { 25 | return code; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/utils/enums/parameters/BaseParaEnum.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.utils.enums.parameters; 2 | 3 | /** 4 | * 5 | * 基本请求参数 6 | * 1. webWxInit 初始化 7 | * 2. wxStatusNotify 微信状态通知 8 | * 9 | *

10 | * Created by xiaoxiaomo on 2017/5/7. 11 | */ 12 | public enum BaseParaEnum { 13 | 14 | Uin("Uin", "wxuin"), 15 | Sid("Sid", "wxsid"), 16 | Skey("Skey", "skey"), 17 | DeviceID("DeviceID", "pass_ticket"); 18 | 19 | private String para; 20 | private String value; 21 | 22 | BaseParaEnum(String para, String value) { 23 | this.para = para; 24 | this.value = value; 25 | } 26 | 27 | public String para() { 28 | return para; 29 | } 30 | 31 | 32 | public Object value() { 33 | return value; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/utils/enums/parameters/LoginParaEnum.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.utils.enums.parameters; 2 | 3 | /** 4 | * 登陆 5 | *

6 | * Created by xiaoxiaomo on 2017/5/7. 7 | */ 8 | public enum LoginParaEnum { 9 | 10 | LOGIN_ICON("loginicon", "true"), 11 | UUID("uuid", ""), 12 | TIP("tip", "0"), 13 | R("r", ""), 14 | _("_", ""); 15 | 16 | private String para; 17 | private String value; 18 | 19 | LoginParaEnum(String para, String value) { 20 | this.para = para; 21 | this.value = value; 22 | } 23 | 24 | public String para() { 25 | return para; 26 | } 27 | 28 | public String value() { 29 | return value; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/utils/enums/parameters/StatusNotifyParaEnum.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.utils.enums.parameters; 2 | 3 | /** 4 | * 状态通知 5 | *

6 | * Created by xiaoxiaomo on 2017/5/7. 7 | */ 8 | public enum StatusNotifyParaEnum { 9 | 10 | CODE("Code", "3"), 11 | FROM_USERNAME("FromUserName", ""), 12 | TO_USERNAME("ToUserName", ""), 13 | CLIENT_MSG_ID("ClientMsgId", ""); //时间戳 14 | 15 | private String para; 16 | private String value; 17 | 18 | StatusNotifyParaEnum(String para, String value) { 19 | this.para = para; 20 | this.value = value; 21 | } 22 | 23 | public String para() { 24 | return para; 25 | } 26 | 27 | public String value() { 28 | return value; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/utils/enums/parameters/UUIDParaEnum.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.utils.enums.parameters; 2 | 3 | /** 4 | * UUID 5 | *

6 | * Created by xiaoxiaomo on 2017/5/7. 7 | */ 8 | public enum UUIDParaEnum { 9 | 10 | APP_ID("appid", "wx782c26e4c19acffb"), 11 | FUN("fun", "new"), 12 | LANG("lang", "zh_CN"), 13 | REDIRECT_URL("redirect_uri", 14 | "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?mod=desktop"), 15 | _("_", "时间戳"); 16 | 17 | 18 | private String para; 19 | private String value; 20 | 21 | UUIDParaEnum(String para, String value) { 22 | this.para = para; 23 | this.value = value; 24 | } 25 | 26 | public String para() { 27 | return para; 28 | } 29 | 30 | public String value() { 31 | return value; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/utils/tools/CommonTools.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.utils.tools; 2 | 3 | import java.io.StringReader; 4 | import java.util.ArrayList; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.Set; 9 | import java.util.regex.Matcher; 10 | import java.util.regex.Pattern; 11 | 12 | import javax.xml.parsers.DocumentBuilder; 13 | import javax.xml.parsers.DocumentBuilderFactory; 14 | 15 | import org.w3c.dom.Document; 16 | import org.xml.sax.InputSource; 17 | 18 | import com.alibaba.fastjson.JSON; 19 | import com.alibaba.fastjson.JSONArray; 20 | import com.alibaba.fastjson.JSONObject; 21 | import com.vdurmont.emoji.EmojiParser; 22 | 23 | import cn.zhouyafeng.itchat4j.utils.Config; 24 | import cn.zhouyafeng.itchat4j.utils.enums.OsNameEnum; 25 | 26 | /** 27 | * 常用工具类 28 | * 29 | * @author https://github.com/yaphone 30 | * @date 创建时间:2017年4月8日 下午10:59:55 31 | * @version 1.0 32 | * 33 | */ 34 | public class CommonTools { 35 | 36 | public static boolean printQr(String qrPath) { 37 | 38 | switch (Config.getOsNameEnum()) { 39 | case WINDOWS: 40 | if (Config.getOsNameEnum().equals(OsNameEnum.WINDOWS)) { 41 | Runtime runtime = Runtime.getRuntime(); 42 | try { 43 | runtime.exec("cmd /c start " + qrPath); 44 | } catch (Exception e) { 45 | e.printStackTrace(); 46 | } 47 | } 48 | break; 49 | case MAC: 50 | if (Config.getOsNameEnum().equals(OsNameEnum.MAC)) { 51 | Runtime runtime = Runtime.getRuntime(); 52 | try { 53 | runtime.exec("open " + qrPath); 54 | } catch (Exception e) { 55 | e.printStackTrace(); 56 | } 57 | } 58 | break; 59 | 60 | default: 61 | break; 62 | } 63 | return true; 64 | } 65 | 66 | public static boolean clearScreen() { 67 | switch (Config.getOsNameEnum()) { 68 | case WINDOWS: 69 | if (Config.getOsNameEnum().equals(OsNameEnum.WINDOWS)) { 70 | Runtime runtime = Runtime.getRuntime(); 71 | try { 72 | runtime.exec("cmd /c " + "cls"); 73 | } catch (Exception e) { 74 | e.printStackTrace(); 75 | } 76 | } 77 | break; 78 | 79 | default: 80 | break; 81 | } 82 | return true; 83 | } 84 | 85 | /** 86 | * 正则表达式处理工具 87 | * 88 | * @author https://github.com/yaphone 89 | * @date 2017年4月9日 上午12:27:10 90 | * @return 91 | */ 92 | public static Matcher getMatcher(String regEx, String text) { 93 | Pattern pattern = Pattern.compile(regEx); 94 | Matcher matcher = pattern.matcher(text); 95 | return matcher; 96 | } 97 | 98 | /** 99 | * xml解析器 100 | * 101 | * @author https://github.com/yaphone 102 | * @date 2017年4月9日 下午6:24:25 103 | * @param text 104 | * @return 105 | */ 106 | public static Document xmlParser(String text) { 107 | Document doc = null; 108 | StringReader sr = new StringReader(text); 109 | InputSource is = new InputSource(sr); 110 | DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 111 | try { 112 | DocumentBuilder builder = factory.newDocumentBuilder(); 113 | doc = builder.parse(is); 114 | } catch (Exception e) { 115 | e.printStackTrace(); 116 | } 117 | return doc; 118 | } 119 | 120 | public static JSONObject structFriendInfo(JSONObject userObj) { 121 | Map friendInfoTemplate = new HashMap(); 122 | friendInfoTemplate.put("UserName", ""); 123 | friendInfoTemplate.put("City", ""); 124 | friendInfoTemplate.put("DisplayName", ""); 125 | friendInfoTemplate.put("PYQuanPin", ""); 126 | friendInfoTemplate.put("RemarkPYInitial", ""); 127 | friendInfoTemplate.put("Province", ""); 128 | friendInfoTemplate.put("KeyWord", ""); 129 | friendInfoTemplate.put("RemarkName", ""); 130 | friendInfoTemplate.put("PYInitial", ""); 131 | friendInfoTemplate.put("EncryChatRoomId", ""); 132 | friendInfoTemplate.put("Alias", ""); 133 | friendInfoTemplate.put("Signature", ""); 134 | friendInfoTemplate.put("NickName", ""); 135 | friendInfoTemplate.put("RemarkPYQuanPin", ""); 136 | friendInfoTemplate.put("HeadImgUrl", ""); 137 | 138 | friendInfoTemplate.put("UniFriend", 0); 139 | friendInfoTemplate.put("Sex", 0); 140 | friendInfoTemplate.put("AppAccountFlag", 0); 141 | friendInfoTemplate.put("VerifyFlag", 0); 142 | friendInfoTemplate.put("ChatRoomId", 0); 143 | friendInfoTemplate.put("HideInputBarFlag", 0); 144 | friendInfoTemplate.put("AttrStatus", 0); 145 | friendInfoTemplate.put("SnsFlag", 0); 146 | friendInfoTemplate.put("MemberCount", 0); 147 | friendInfoTemplate.put("OwnerUin", 0); 148 | friendInfoTemplate.put("ContactFlag", 0); 149 | friendInfoTemplate.put("Uin", 0); 150 | friendInfoTemplate.put("StarFriend", 0); 151 | friendInfoTemplate.put("Statues", 0); 152 | 153 | friendInfoTemplate.put("MemberList", new ArrayList()); 154 | 155 | JSONObject r = new JSONObject(); 156 | Set keySet = friendInfoTemplate.keySet(); 157 | for (String key : keySet) { 158 | if (userObj.containsKey(key)) { 159 | r.put(key, userObj.get(key)); 160 | } else { 161 | r.put(key, friendInfoTemplate.get(key)); 162 | } 163 | } 164 | 165 | return r; 166 | } 167 | 168 | public static String getSynckey(JSONObject obj) { 169 | JSONArray obj2 = obj.getJSONArray("List"); 170 | StringBuilder sb = new StringBuilder(); 171 | for (int i = 0; i < obj2.size(); i++) { 172 | JSONObject obj3 = (JSONObject) JSON.toJSON(obj2.get(i)); 173 | sb.append(obj3.get("Val") + "|"); 174 | } 175 | return sb.substring(0, sb.length() - 1); // 656159784|656159911|656159873|1491905341 176 | 177 | } 178 | 179 | public static JSONObject searchDictList(List list, String key, String value) { 180 | JSONObject r = null; 181 | for (JSONObject i : list) { 182 | if (i.getString(key).equals(value)) { 183 | r = i; 184 | break; 185 | } 186 | } 187 | return r; 188 | } 189 | 190 | /** 191 | * 处理emoji表情 192 | * 193 | * @author https://github.com/yaphone 194 | * @date 2017年4月23日 下午2:39:04 195 | * @param d 196 | * @param k 197 | */ 198 | public static void emojiFormatter(JSONObject d, String k) { 199 | Matcher matcher = getMatcher("", d.getString(k)); 200 | StringBuilder sb = new StringBuilder(); 201 | String content = d.getString(k); 202 | int lastStart = 0; 203 | while (matcher.find()) { 204 | String str = matcher.group(1); 205 | if (str.length() == 6) { 206 | 207 | } else if (str.length() == 10) { 208 | 209 | } else { 210 | str = "&#x" + str + ";"; 211 | String tmp = content.substring(lastStart, matcher.start()); 212 | sb.append(tmp + str); 213 | lastStart = matcher.end(); 214 | } 215 | } 216 | if (lastStart < content.length()) { 217 | sb.append(content.substring(lastStart)); 218 | } 219 | if (sb.length() != 0) { 220 | d.put(k, EmojiParser.parseToUnicode(sb.toString())); 221 | } else { 222 | d.put(k, content); 223 | } 224 | 225 | } 226 | 227 | /** 228 | * 消息格式化 229 | * 230 | * @author https://github.com/yaphone 231 | * @date 2017年4月23日 下午4:19:08 232 | * @param d 233 | * @param k 234 | */ 235 | public static void msgFormatter(JSONObject d, String k) { 236 | d.put(k, d.getString(k).replace("
", "\n")); 237 | emojiFormatter(d, k); 238 | // TODO 与emoji表情有部分兼容问题,目前暂未处理解码处理 d.put(k, 239 | // StringEscapeUtils.unescapeHtml4(d.getString(k))); 240 | 241 | } 242 | 243 | } 244 | -------------------------------------------------------------------------------- /src/main/java/cn/zhouyafeng/itchat4j/utils/tools/DownloadTools.java: -------------------------------------------------------------------------------- 1 | package cn.zhouyafeng.itchat4j.utils.tools; 2 | 3 | import java.io.FileOutputStream; 4 | import java.io.OutputStream; 5 | import java.util.ArrayList; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.logging.Logger; 10 | 11 | import org.apache.http.HttpEntity; 12 | import org.apache.http.message.BasicNameValuePair; 13 | import org.apache.http.util.EntityUtils; 14 | 15 | import cn.zhouyafeng.itchat4j.beans.BaseMsg; 16 | import cn.zhouyafeng.itchat4j.core.Core; 17 | import cn.zhouyafeng.itchat4j.utils.MyHttpClient; 18 | import cn.zhouyafeng.itchat4j.utils.enums.MsgTypeEnum; 19 | import cn.zhouyafeng.itchat4j.utils.enums.URLEnum; 20 | 21 | /** 22 | * 下载工具类 23 | * 24 | * @author https://github.com/yaphone 25 | * @date 创建时间:2017年4月21日 下午11:18:46 26 | * @version 1.0 27 | * 28 | */ 29 | public class DownloadTools { 30 | private static Logger logger = Logger.getLogger("DownloadTools"); 31 | private static Core core = Core.getInstance(); 32 | private static MyHttpClient myHttpClient = core.getMyHttpClient(); 33 | 34 | /** 35 | * 处理下载任务 36 | * 37 | * @author https://github.com/yaphone 38 | * @date 2017年4月21日 下午11:00:25 39 | * @param url 40 | * @param msgId 41 | * @param path 42 | * @return 43 | */ 44 | public static Object getDownloadFn(BaseMsg msg, String type, String path) { 45 | Map headerMap = new HashMap(); 46 | List params = new ArrayList(); 47 | String url = ""; 48 | if (type.equals(MsgTypeEnum.PIC.getType())) { 49 | url = String.format(URLEnum.WEB_WX_GET_MSG_IMG.getUrl(), (String) core.getLoginInfo().get("url")); 50 | } else if (type.equals(MsgTypeEnum.VOICE.getType())) { 51 | url = String.format(URLEnum.WEB_WX_GET_VOICE.getUrl(), (String) core.getLoginInfo().get("url")); 52 | } else if (type.equals(MsgTypeEnum.VIEDO.getType())) { 53 | headerMap.put("Range", "bytes=0-"); 54 | url = String.format(URLEnum.WEB_WX_GET_VIEDO.getUrl(), (String) core.getLoginInfo().get("url")); 55 | } else if (type.equals(MsgTypeEnum.MEDIA.getType())) { 56 | headerMap.put("Range", "bytes=0-"); 57 | url = String.format(URLEnum.WEB_WX_GET_MEDIA.getUrl(), (String) core.getLoginInfo().get("fileUrl")); 58 | params.add(new BasicNameValuePair("sender", msg.getFromUserName())); 59 | params.add(new BasicNameValuePair("mediaid", msg.getMediaId())); 60 | params.add(new BasicNameValuePair("filename", msg.getFileName())); 61 | } 62 | params.add(new BasicNameValuePair("msgid", msg.getNewMsgId())); 63 | params.add(new BasicNameValuePair("skey", (String) core.getLoginInfo().get("skey"))); 64 | HttpEntity entity = myHttpClient.doGet(url, params, true, headerMap); 65 | try { 66 | OutputStream out = new FileOutputStream(path); 67 | byte[] bytes = EntityUtils.toByteArray(entity); 68 | out.write(bytes); 69 | out.flush(); 70 | out.close(); 71 | // Tools.printQr(path); 72 | 73 | } catch (Exception e) { 74 | logger.info(e.getMessage()); 75 | return false; 76 | } 77 | return null; 78 | }; 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/org/zhong/chatgpt/wechat/bot/BotStarter.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot; 2 | 3 | import org.zhong.chatgpt.wechat.bot.model.Bot; 4 | 5 | public class BotStarter { 6 | 7 | public static void main(String[] args) { 8 | //Bot.buildOpenAIWechatBot().start(); 9 | Bot.buildChatGPTWechatBot().start(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/org/zhong/chatgpt/wechat/bot/ChatgptWechatBotApplication.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ChatgptWechatBotApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(ChatgptWechatBotApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/zhong/chatgpt/wechat/bot/builder/OpenAiServiceBuilder.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.builder; 2 | 3 | import java.io.IOException; 4 | import java.net.InetSocketAddress; 5 | import java.net.Proxy; 6 | import java.time.Duration; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | import javax.net.ssl.X509TrustManager; 10 | 11 | import org.zhong.chatgpt.wechat.bot.config.BotConfig; 12 | import org.zhong.chatgpt.wechat.bot.util.SSLSocketClientUtil; 13 | 14 | import com.fasterxml.jackson.databind.ObjectMapper; 15 | import com.theokanning.openai.OpenAiApi; 16 | import com.theokanning.openai.service.OpenAiService; 17 | 18 | import okhttp3.ConnectionPool; 19 | import okhttp3.Interceptor; 20 | import okhttp3.OkHttpClient; 21 | import okhttp3.OkHttpClient.Builder; 22 | import okhttp3.Request; 23 | import okhttp3.Response; 24 | import retrofit2.Retrofit; 25 | 26 | public class OpenAiServiceBuilder { 27 | 28 | 29 | public static OpenAiService build(String token, Duration timeout) { 30 | 31 | ObjectMapper mapper = OpenAiService.defaultObjectMapper(); 32 | OkHttpClient client = httpClient(token, timeout); 33 | Retrofit retrofit = OpenAiService.defaultRetrofit(client, mapper); 34 | OpenAiApi openAiApi = retrofit.create(OpenAiApi.class); 35 | 36 | return new OpenAiService(openAiApi); 37 | } 38 | 39 | private static OkHttpClient httpClient(String token, Duration timeout) { 40 | 41 | X509TrustManager manager = SSLSocketClientUtil.getX509TrustManager(); 42 | Builder builder = new OkHttpClient.Builder() 43 | .addInterceptor(new AuthenticationInterceptor(token)) 44 | .connectionPool(new ConnectionPool(5, 1, TimeUnit.SECONDS)) 45 | .readTimeout(timeout.toMillis(), TimeUnit.MILLISECONDS) 46 | .sslSocketFactory(SSLSocketClientUtil.getSocketFactory(manager), manager)// 忽略校验 47 | .hostnameVerifier(SSLSocketClientUtil.getHostnameVerifier());//忽略校验 48 | 49 | if(BotConfig.getProxyEnable()) { 50 | Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(BotConfig.getProxyHost(), BotConfig.getProxyPort())); 51 | builder.proxy(proxy); 52 | } 53 | 54 | OkHttpClient client = builder.build(); 55 | return client; 56 | } 57 | 58 | static class AuthenticationInterceptor implements Interceptor { 59 | 60 | private final String token; 61 | 62 | AuthenticationInterceptor(String token) { 63 | this.token = token; 64 | } 65 | 66 | @Override 67 | public Response intercept(Chain chain) throws IOException { 68 | Request request = chain.request() 69 | .newBuilder() 70 | .header("Authorization", "Bearer " + token) 71 | .build(); 72 | return chain.proceed(request); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/org/zhong/chatgpt/wechat/bot/config/BotConfig.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.config; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.FileNotFoundException; 6 | import java.io.InputStream; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | import org.apache.commons.lang3.StringUtils; 12 | import org.yaml.snakeyaml.Yaml; 13 | import org.zhong.chatgpt.wechat.bot.consts.BotConst; 14 | 15 | import cn.hutool.core.io.file.FileReader; 16 | 17 | public class BotConfig { 18 | 19 | private static String botName = "狂追风龙少"; 20 | 21 | private static String atBotName = BotConst.AT+botName; 22 | 23 | private static String appKey = ""; 24 | 25 | private static String qrcodePath = ""; 26 | 27 | private static String proxyHost = ""; 28 | 29 | private static int proxyPort = 0; 30 | 31 | private static Boolean proxyEnable = false; 32 | 33 | private static String dictPath = "classpath:dict.txt"; 34 | 35 | private static List groupWhiteList = new ArrayList(); 36 | 37 | private static List userWhiteList = new ArrayList(); 38 | 39 | static { 40 | String rootConfigPath = System.getProperty("rootConfigPath"); 41 | String groupWhiteListPath = "classpath:groupWhiteList.txt"; 42 | String userWhiteListPath = "classpath:userWhiteList.txt"; 43 | InputStream applicationStream = BotConfig.class.getResourceAsStream("/application.yml"); 44 | 45 | if(StringUtils.isNotEmpty(rootConfigPath)) { 46 | groupWhiteListPath = rootConfigPath + "/groupWhiteList.txt"; 47 | userWhiteListPath = rootConfigPath + "/userWhiteList.txt"; 48 | dictPath = rootConfigPath + "/dict.txt"; 49 | try { 50 | applicationStream = new FileInputStream(new File(rootConfigPath + "/application.yml")); 51 | } catch (FileNotFoundException e) { 52 | // TODO Auto-generated catch block 53 | e.printStackTrace(); 54 | } 55 | } 56 | 57 | FileReader groupFileReader = new FileReader(groupWhiteListPath); 58 | groupWhiteList = groupFileReader.readLines(); 59 | 60 | FileReader userFileReader = new FileReader(userWhiteListPath); 61 | userWhiteList = userFileReader.readLines(); 62 | 63 | final Yaml yaml = new Yaml(); 64 | Map yamlMap = yaml.load(applicationStream); 65 | 66 | botName = yamlMap.get("bot.botName").toString(); 67 | Object objQrcodePath = yamlMap.get("bot.wechat.qrcode.path"); 68 | Object objAppKey = yamlMap.get("bot.appKey"); 69 | 70 | Object enable = yamlMap.get("proxy.enable"); 71 | if(enable != null) { 72 | proxyEnable = Boolean.valueOf(enable.toString()); 73 | } 74 | 75 | if(proxyEnable) { 76 | proxyHost = yamlMap.get("proxy.host").toString(); 77 | proxyPort = Integer.valueOf(yamlMap.get("proxy.port").toString()); 78 | } 79 | 80 | if(objAppKey == null ) { 81 | appKey = System.getProperty("bot.appKey"); 82 | }else { 83 | appKey = objAppKey.toString(); 84 | } 85 | 86 | if(objQrcodePath == null) { 87 | qrcodePath = System.getProperty("bot.wechat.qrcode.path"); 88 | }else { 89 | qrcodePath = objQrcodePath.toString(); 90 | } 91 | 92 | if(StringUtils.isEmpty(qrcodePath)) { 93 | qrcodePath = System.getProperty("user.dir"); 94 | } 95 | } 96 | 97 | public static String getBotName() { 98 | return botName; 99 | } 100 | 101 | public static void setBotName(String botName) { 102 | BotConfig.botName = botName; 103 | } 104 | 105 | public static List getGroupWhiteList() { 106 | return groupWhiteList; 107 | } 108 | 109 | public static void setGroupWhiteList(List groupWhiteList) { 110 | BotConfig.groupWhiteList = groupWhiteList; 111 | } 112 | 113 | public static String getAtBotName() { 114 | return atBotName; 115 | } 116 | 117 | public static void setAtBotName(String atBotName) { 118 | BotConfig.atBotName = atBotName; 119 | } 120 | 121 | public static String getAppKey() { 122 | return appKey; 123 | } 124 | 125 | public static void setAppKey(String appKey) { 126 | BotConfig.appKey = appKey; 127 | } 128 | 129 | public static List getUserWhiteList() { 130 | return userWhiteList; 131 | } 132 | 133 | public static void setUserWhiteList(List userWhiteList) { 134 | BotConfig.userWhiteList = userWhiteList; 135 | } 136 | 137 | public static String getQrcodePath() { 138 | return qrcodePath; 139 | } 140 | 141 | public static void setQrcodePath(String qrcodePath) { 142 | BotConfig.qrcodePath = qrcodePath; 143 | } 144 | 145 | public static String getProxyHost() { 146 | return proxyHost; 147 | } 148 | 149 | public static void setProxyHost(String proxyHost) { 150 | BotConfig.proxyHost = proxyHost; 151 | } 152 | 153 | public static int getProxyPort() { 154 | return proxyPort; 155 | } 156 | 157 | public static void setProxyPort(int proxyPost) { 158 | BotConfig.proxyPort = proxyPost; 159 | } 160 | 161 | public static Boolean getProxyEnable() { 162 | return proxyEnable; 163 | } 164 | 165 | public static void setProxyEnable(Boolean proxyEnable) { 166 | BotConfig.proxyEnable = proxyEnable; 167 | } 168 | 169 | public static String getDictPath() { 170 | return dictPath; 171 | } 172 | 173 | public static void setDictPath(String dictPath) { 174 | BotConfig.dictPath = dictPath; 175 | } 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | } 188 | -------------------------------------------------------------------------------- /src/main/java/org/zhong/chatgpt/wechat/bot/consts/BotConst.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.consts; 2 | 3 | public class BotConst { 4 | public static final String AT = "@"; 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/org/zhong/chatgpt/wechat/bot/model/AutoConversationStorage.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.model; 2 | 3 | import java.io.File; 4 | 5 | import cn.hutool.core.io.FileUtil; 6 | 7 | public class AutoConversationStorage { 8 | 9 | private static BotMsgLinkedList currentMsg = new BotMsgLinkedList(); 10 | 11 | private static BotMsgLinkedList histroyMsg = new BotMsgLinkedList(); 12 | 13 | public static BotMsgLinkedList getCurrentMsg() { 14 | return currentMsg; 15 | } 16 | 17 | public static BotMsgLinkedList getHistroyMsg() { 18 | return histroyMsg; 19 | } 20 | 21 | static { 22 | /** 23 | * 持久化 24 | */ 25 | new Thread(new Runnable() { 26 | 27 | @Override 28 | public void run() { 29 | try { 30 | for(;;) { 31 | BotMsg botMsg = histroyMsg.blockPop(); 32 | 33 | File file = FileUtil.file("D:\\autobot.txt"); 34 | FileUtil.appendUtf8String("ask:"+botMsg.getBaseMsg().getContent()+"\n", file); 35 | FileUtil.appendUtf8String("reply:"+botMsg.getReplyMsg()+"\n", file); 36 | FileUtil.appendUtf8String("-------------------------------------------\n", file); 37 | } 38 | 39 | } catch (InterruptedException e) { 40 | // TODO Auto-generated catch block 41 | e.printStackTrace(); 42 | } 43 | 44 | } 45 | }).start(); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/zhong/chatgpt/wechat/bot/model/Bot.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.model; 2 | 3 | import org.zhong.chatgpt.wechat.bot.msgprocess.ACSAcceptor; 4 | import org.zhong.chatgpt.wechat.bot.msgprocess.ACSSendProcessor; 5 | import org.zhong.chatgpt.wechat.bot.msgprocess.ChatGPTReplyProcessor; 6 | import org.zhong.chatgpt.wechat.bot.msgprocess.ConsoleSendProcessor; 7 | import org.zhong.chatgpt.wechat.bot.msgprocess.ConsoleMsgAcceptor; 8 | import org.zhong.chatgpt.wechat.bot.msgprocess.MsgAcceptor; 9 | import org.zhong.chatgpt.wechat.bot.msgprocess.MsgPreProcessor; 10 | import org.zhong.chatgpt.wechat.bot.msgprocess.MsgProcessor; 11 | import org.zhong.chatgpt.wechat.bot.msgprocess.OpenAIReplyProcessor; 12 | import org.zhong.chatgpt.wechat.bot.msgprocess.WechatMsgAcceptor; 13 | import org.zhong.chatgpt.wechat.bot.msgprocess.WechatSendProcessor; 14 | import org.zhong.chatgpt.wechat.bot.wechatbot.MsgPreThread; 15 | import org.zhong.chatgpt.wechat.bot.wechatbot.MsgReplyThread; 16 | import org.zhong.chatgpt.wechat.bot.wechatbot.WechatSendThread; 17 | 18 | public class Bot { 19 | 20 | private MsgProcessor msgPreProcessor; 21 | private MsgProcessor replyProcessor; 22 | private MsgProcessor sendProcessor; 23 | private MsgAcceptor msgAcceptor; 24 | 25 | public void start() { 26 | MsgReplyThread.start(replyProcessor); 27 | MsgPreThread.start(msgPreProcessor); 28 | WechatSendThread.start(sendProcessor); 29 | msgAcceptor.start(); 30 | } 31 | 32 | public static Bot builder() { 33 | return new Bot(); 34 | } 35 | 36 | public static Bot buildOpenAIWechatBot() { 37 | return builder() 38 | .msgPreProcessor(new MsgPreProcessor()) 39 | .replyProcessor(new OpenAIReplyProcessor()) 40 | .sendProcessor(new WechatSendProcessor()) 41 | .msgAcceptor(new WechatMsgAcceptor()); 42 | } 43 | 44 | public static Bot buildChatGPTWechatBot() { 45 | return builder() 46 | .msgPreProcessor(new MsgPreProcessor()) 47 | .replyProcessor(new ChatGPTReplyProcessor()) 48 | .sendProcessor(new WechatSendProcessor()) 49 | .msgAcceptor(new WechatMsgAcceptor()); 50 | } 51 | 52 | public static Bot buildSocialistAIWechatBot() { 53 | return builder() 54 | .msgPreProcessor(new MsgPreProcessor()) 55 | .replyProcessor(new ChatGPTReplyProcessor("我需要你扮演社会主义集大成者来我和对话。你说的每一句话都需要体现出社会主义核心价值观。你把社会主义核心价值观融入到所有学科。")) 56 | .sendProcessor(new WechatSendProcessor()) 57 | .msgAcceptor(new WechatMsgAcceptor()); 58 | } 59 | 60 | public static Bot buildChatGPTConsoleBot() { 61 | return builder() 62 | .msgPreProcessor(new MsgPreProcessor()) 63 | .replyProcessor(new ChatGPTReplyProcessor()) 64 | .sendProcessor(new ConsoleSendProcessor()) 65 | .msgAcceptor(new ConsoleMsgAcceptor()); 66 | } 67 | 68 | public static Bot buildChatGPTAutoBot() { 69 | return builder() 70 | .msgPreProcessor(new MsgPreProcessor()) 71 | .replyProcessor(new ChatGPTReplyProcessor()) 72 | .sendProcessor(new ACSSendProcessor()) 73 | .msgAcceptor(new ACSAcceptor()); 74 | } 75 | 76 | public Bot msgPreProcessor(MsgProcessor msgPreProcessor) { 77 | this.msgPreProcessor = msgPreProcessor; 78 | return this; 79 | } 80 | 81 | public Bot replyProcessor(MsgProcessor replyProcessor) { 82 | this.replyProcessor = replyProcessor; 83 | return this; 84 | } 85 | 86 | public Bot sendProcessor(MsgProcessor sendProcessor) { 87 | this.sendProcessor = sendProcessor; 88 | return this; 89 | } 90 | 91 | public Bot msgAcceptor(MsgAcceptor msgAcceptor) { 92 | this.msgAcceptor = msgAcceptor; 93 | return this; 94 | } 95 | 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/org/zhong/chatgpt/wechat/bot/model/BotMsg.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.model; 2 | 3 | import org.zhong.chatgpt.wechat.bot.consts.BotConst; 4 | 5 | import cn.zhouyafeng.itchat4j.beans.BaseMsg; 6 | 7 | public class BotMsg{ 8 | 9 | 10 | /** 11 | * 12 | */ 13 | private static final long serialVersionUID = 1L; 14 | 15 | private BaseMsg baseMsg; 16 | 17 | /** 18 | * 是否完成敏感词检查 19 | */ 20 | private boolean isCheck; 21 | 22 | /** 23 | * 回复信息 24 | */ 25 | private String replyMsg; 26 | 27 | /** 28 | * chatGPT重试次数 29 | */ 30 | private int retries; 31 | 32 | 33 | 34 | public BotMsg(BaseMsg baseMsg) { 35 | super(); 36 | this.baseMsg = baseMsg; 37 | } 38 | 39 | public String getUserName() { 40 | if(baseMsg.isGroupMsg()) { 41 | return baseMsg.getGroupUserName(); 42 | }else { 43 | return baseMsg.getFromUserName(); 44 | } 45 | } 46 | 47 | public boolean isCheck() { 48 | return isCheck; 49 | } 50 | 51 | public void setCheck(boolean isCheck) { 52 | this.isCheck = isCheck; 53 | } 54 | 55 | public String getReplyMsg() { 56 | return replyMsg; 57 | } 58 | 59 | public void setReplyMsg(String replyMsg) { 60 | if(baseMsg.isGroupMsg()) { 61 | this.replyMsg = BotConst.AT + baseMsg.getGroupUserNickName()+ " " + replyMsg; 62 | }else { 63 | this.replyMsg = replyMsg; 64 | } 65 | } 66 | 67 | public int getRetries() { 68 | return retries; 69 | } 70 | 71 | public void setRetries(int retries) { 72 | this.retries = retries; 73 | } 74 | 75 | public BaseMsg getBaseMsg() { 76 | return baseMsg; 77 | } 78 | 79 | public void setBaseMsg(BaseMsg baseMsg) { 80 | this.baseMsg = baseMsg; 81 | } 82 | 83 | 84 | 85 | 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/org/zhong/chatgpt/wechat/bot/model/BotMsgLinkedList.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.model; 2 | 3 | import java.util.concurrent.LinkedBlockingQueue; 4 | 5 | public class BotMsgLinkedList extends LinkedBlockingQueue{ 6 | 7 | /** 8 | * 9 | */ 10 | private static final long serialVersionUID = 1L; 11 | 12 | public BotMsg blockPop() throws InterruptedException { 13 | return this.take(); 14 | } 15 | 16 | public void blockPush(BotMsg botMsg) throws InterruptedException { 17 | this.put(botMsg); 18 | } 19 | 20 | public long countGroupUserMsg(String groupUserName) { 21 | 22 | if(this.size() == 0) { 23 | return 0; 24 | } 25 | 26 | long count = this.stream().filter( (msg) -> { 27 | if(msg.getBaseMsg().getGroupUserName() != null) { 28 | return msg.getBaseMsg().getGroupUserName().equals(groupUserName); 29 | }else { 30 | return false; 31 | } 32 | }).count(); 33 | 34 | return count; 35 | } 36 | 37 | public long countUserMsg(String userName) { 38 | 39 | if(this.size() == 0) { 40 | return 0; 41 | } 42 | 43 | long count = this.stream().filter( (msg) -> { 44 | if(msg.getBaseMsg().getFromUserName() != null) { 45 | return msg.getBaseMsg().getFromUserName().equals(userName); 46 | }else { 47 | return false; 48 | } 49 | }).count(); 50 | 51 | return count; 52 | } 53 | 54 | public void removeByUserName() { 55 | 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/org/zhong/chatgpt/wechat/bot/model/FifoLinkedList.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.model; 2 | 3 | import java.util.LinkedList; 4 | 5 | public class FifoLinkedList extends LinkedList { 6 | /** 7 | * 8 | */ 9 | private static final long serialVersionUID = 1L; 10 | private int limit; 11 | 12 | public FifoLinkedList(int limit) { 13 | this.limit = limit; 14 | } 15 | 16 | @Override 17 | public boolean add(E o) { 18 | super.add(o); 19 | while (size() > limit) { 20 | super.remove(); 21 | } 22 | return true; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/zhong/chatgpt/wechat/bot/model/WehchatMsgQueue.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.model; 2 | 3 | public class WehchatMsgQueue { 4 | 5 | /** 6 | * 等待预处理 7 | */ 8 | private static BotMsgLinkedList preMsgs = new BotMsgLinkedList(); 9 | 10 | /** 11 | * 等待回复 12 | */ 13 | private static BotMsgLinkedList waitReplyMsgs = new BotMsgLinkedList(); 14 | 15 | /** 16 | * 等待发送 17 | */ 18 | private static BotMsgLinkedList waitSendMsgs = new BotMsgLinkedList(); 19 | 20 | 21 | public static BotMsg popPreMsg() { 22 | try { 23 | return preMsgs.blockPop(); 24 | } catch (InterruptedException e) { 25 | // TODO Auto-generated catch block 26 | e.printStackTrace(); 27 | Thread.currentThread().interrupt(); 28 | return popPreMsg(); 29 | } 30 | } 31 | 32 | public static void pushPreMsg(BotMsg botMsg) { 33 | try { 34 | preMsgs.blockPush(botMsg); 35 | } catch (InterruptedException e) { 36 | // TODO Auto-generated catch block 37 | e.printStackTrace(); 38 | Thread.currentThread().interrupt(); 39 | } 40 | } 41 | 42 | public static BotMsg popReplyMsg() { 43 | try { 44 | return waitReplyMsgs.blockPop(); 45 | } catch (InterruptedException e) { 46 | e.printStackTrace(); 47 | Thread.currentThread().interrupt(); 48 | return popReplyMsg(); 49 | } 50 | } 51 | 52 | public static void pushReplyMsg(BotMsg botMsg) { 53 | try { 54 | waitReplyMsgs.blockPush(botMsg); 55 | } catch (InterruptedException e) { 56 | e.printStackTrace(); 57 | Thread.currentThread().interrupt(); 58 | } 59 | } 60 | 61 | public static BotMsg popSendMsg() { 62 | try { 63 | return waitSendMsgs.blockPop(); 64 | } catch (InterruptedException e) { 65 | // TODO Auto-generated catch block 66 | e.printStackTrace(); 67 | Thread.currentThread().interrupt(); 68 | return popSendMsg(); 69 | } 70 | } 71 | 72 | public static void pushSendMsg(BotMsg botMsg) { 73 | try { 74 | waitSendMsgs.blockPush(botMsg); 75 | } catch (InterruptedException e) { 76 | // TODO Auto-generated catch block 77 | e.printStackTrace(); 78 | Thread.currentThread().interrupt(); 79 | } 80 | } 81 | 82 | public static long countGroupUserPreMsg(String groupUserName) { 83 | return preMsgs.countGroupUserMsg(groupUserName); 84 | } 85 | 86 | public static long countUserPreMsg(String userName) { 87 | return preMsgs.countUserMsg(userName); 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/org/zhong/chatgpt/wechat/bot/msgprocess/ACSAcceptor.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.msgprocess; 2 | 3 | import java.util.Scanner; 4 | 5 | import org.zhong.chatgpt.wechat.bot.model.AutoConversationStorage; 6 | import org.zhong.chatgpt.wechat.bot.model.BotMsg; 7 | import org.zhong.chatgpt.wechat.bot.model.WehchatMsgQueue; 8 | 9 | import cn.zhouyafeng.itchat4j.beans.BaseMsg; 10 | import cn.zhouyafeng.itchat4j.utils.enums.MsgTypeEnum; 11 | 12 | public class ACSAcceptor implements MsgAcceptor{ 13 | 14 | @Override 15 | public void start() { 16 | @SuppressWarnings("resource") 17 | Scanner scanner = new Scanner(System.in); 18 | System.out.println("请讨论主题:"); 19 | String str = ""; 20 | if(scanner.hasNextLine()) { 21 | str = scanner.nextLine(); 22 | } 23 | 24 | BaseMsg msg = new BaseMsg(); 25 | msg.setGroupMsg(false); 26 | msg.setFromUserName("test"); 27 | msg.setFromUserNickName("test"); 28 | msg.setType(MsgTypeEnum.TEXT.getType()); 29 | msg.setContent(str); 30 | AutoConversationStorage.getCurrentMsg().add(new BotMsg(msg)); 31 | 32 | 33 | for(;;) { 34 | try { 35 | BotMsg botMsg = AutoConversationStorage.getCurrentMsg().blockPop(); 36 | WehchatMsgQueue.pushPreMsg(botMsg); 37 | Thread.sleep(5000); 38 | } catch (InterruptedException e) { 39 | // TODO Auto-generated catch block 40 | e.printStackTrace(); 41 | } 42 | } 43 | 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/zhong/chatgpt/wechat/bot/msgprocess/ACSSendProcessor.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.msgprocess; 2 | 3 | import org.zhong.chatgpt.wechat.bot.model.AutoConversationStorage; 4 | import org.zhong.chatgpt.wechat.bot.model.BotMsg; 5 | 6 | import cn.zhouyafeng.itchat4j.beans.BaseMsg; 7 | import cn.zhouyafeng.itchat4j.utils.enums.MsgTypeEnum; 8 | 9 | /** 10 | * 自动对话存储 11 | * @author zhong 12 | * 13 | */ 14 | public class ACSSendProcessor implements MsgProcessor{ 15 | 16 | @Override 17 | public void process(BotMsg botMsg) { 18 | try { 19 | BaseMsg msg = new BaseMsg(); 20 | msg.setGroupMsg(false); 21 | msg.setFromUserName("AI"); 22 | msg.setFromUserNickName("AI"); 23 | msg.setType(MsgTypeEnum.TEXT.getType()); 24 | msg.setContent(botMsg.getReplyMsg()); 25 | BotMsg botMsg2 = new BotMsg(msg); 26 | AutoConversationStorage.getCurrentMsg().blockPush(botMsg2); 27 | AutoConversationStorage.getHistroyMsg().add(botMsg); 28 | } catch (InterruptedException e) { 29 | // TODO Auto-generated catch block 30 | e.printStackTrace(); 31 | } 32 | 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/zhong/chatgpt/wechat/bot/msgprocess/ChatGPTReplyProcessor.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.msgprocess; 2 | 3 | import java.time.Duration; 4 | import java.util.ArrayList; 5 | import java.util.Arrays; 6 | import java.util.Collections; 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.TreeMap; 11 | 12 | import org.apache.commons.collections4.queue.CircularFifoQueue; 13 | import org.zhong.chatgpt.wechat.bot.builder.OpenAiServiceBuilder; 14 | import org.zhong.chatgpt.wechat.bot.config.BotConfig; 15 | import org.zhong.chatgpt.wechat.bot.model.BotMsg; 16 | import org.zhong.chatgpt.wechat.bot.model.FifoLinkedList; 17 | import org.zhong.chatgpt.wechat.bot.model.WehchatMsgQueue; 18 | 19 | import com.theokanning.openai.completion.chat.ChatCompletionChoice; 20 | import com.theokanning.openai.completion.chat.ChatCompletionRequest; 21 | import com.theokanning.openai.completion.chat.ChatMessage; 22 | import com.theokanning.openai.completion.chat.ChatMessageRole; 23 | import com.theokanning.openai.service.OpenAiService; 24 | 25 | import cn.zhouyafeng.itchat4j.beans.BaseMsg; 26 | 27 | /** 28 | * 使用ChatGPT接口进行回复 29 | * @author zhong 30 | * 31 | */ 32 | public class ChatGPTReplyProcessor implements MsgProcessor{ 33 | 34 | private static OpenAiService service = OpenAiServiceBuilder.build(BotConfig.getAppKey(), Duration.ofSeconds(300)); 35 | /** 36 | * 无竞争 37 | */ 38 | private static Map> mgsMap = new TreeMap<>(); 39 | 40 | private static ChatMessage systemMessage; 41 | 42 | private static String deafualt = "你是一个非常强大、全面的人工智能助手,可以准确地回答我的问题。"; 43 | 44 | public ChatGPTReplyProcessor() { 45 | systemMessage = new ChatMessage(ChatMessageRole.SYSTEM.value(), deafualt); 46 | } 47 | 48 | public ChatGPTReplyProcessor(String sysPrompt) { 49 | if(sysPrompt != null) { 50 | systemMessage = new ChatMessage(ChatMessageRole.SYSTEM.value(), sysPrompt); 51 | }else { 52 | systemMessage = new ChatMessage(ChatMessageRole.SYSTEM.value(), deafualt); 53 | } 54 | } 55 | 56 | @Override 57 | public void process(BotMsg botMsg) { 58 | 59 | BaseMsg baseMsg = botMsg.getBaseMsg(); 60 | String userName = botMsg.getUserName(); 61 | final FifoLinkedList messages = mgsMap.getOrDefault(userName, new FifoLinkedList(30)); 62 | messages.add(systemMessage); 63 | 64 | ChatMessage userMessage = new ChatMessage(ChatMessageRole.USER.value(), baseMsg.getContent()); 65 | messages.add(userMessage); 66 | 67 | try { 68 | ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest 69 | .builder() 70 | .model("gpt-3.5-turbo") 71 | .messages(messages) 72 | .n(1) 73 | .maxTokens(2000) 74 | .logitBias(new HashMap<>()) 75 | .build(); 76 | 77 | List choices = service.createChatCompletion(chatCompletionRequest).getChoices(); 78 | String text = choices.get(0).getMessage().getContent(); 79 | 80 | ChatMessage assistantMessage = new ChatMessage(ChatMessageRole.ASSISTANT.value(), text); 81 | messages.add(assistantMessage); 82 | mgsMap.put(userName, messages); 83 | 84 | botMsg.setReplyMsg(text); 85 | WehchatMsgQueue.pushSendMsg(botMsg); 86 | }catch (Exception e) { 87 | e.printStackTrace(); 88 | 89 | botMsg.setRetries(botMsg.getRetries() + 1); 90 | if(botMsg.getRetries() < 5) { 91 | WehchatMsgQueue.pushReplyMsg(botMsg); 92 | }else { 93 | String recontent = baseMsg.getContent(); 94 | if(recontent.length() > 20) { 95 | recontent = recontent.substring(0, 17) + "...\n"; 96 | } 97 | botMsg.setReplyMsg(recontent+ "该提问已失效,请重新提问"); 98 | WehchatMsgQueue.pushSendMsg(botMsg); 99 | } 100 | 101 | } 102 | 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/org/zhong/chatgpt/wechat/bot/msgprocess/ConsoleMsgAcceptor.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.msgprocess; 2 | 3 | import java.util.Scanner; 4 | 5 | import org.zhong.chatgpt.wechat.bot.model.BotMsg; 6 | import org.zhong.chatgpt.wechat.bot.model.WehchatMsgQueue; 7 | 8 | import cn.zhouyafeng.itchat4j.beans.BaseMsg; 9 | import cn.zhouyafeng.itchat4j.utils.enums.MsgTypeEnum; 10 | 11 | public class ConsoleMsgAcceptor implements MsgAcceptor{ 12 | 13 | @Override 14 | public void start() { 15 | @SuppressWarnings("resource") 16 | Scanner scanner = new Scanner(System.in); 17 | System.out.println("请输入问题:"); 18 | 19 | BaseMsg msg = new BaseMsg(); 20 | msg.setGroupMsg(false); 21 | msg.setFromUserName("test"); 22 | msg.setFromUserNickName("test"); 23 | msg.setType(MsgTypeEnum.TEXT.getType()); 24 | for(;scanner.hasNextLine();) { 25 | String str = scanner.nextLine(); 26 | msg.setContent(str); 27 | System.out.println(); 28 | WehchatMsgQueue.pushPreMsg(new BotMsg(msg)); 29 | } 30 | 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/zhong/chatgpt/wechat/bot/msgprocess/ConsoleSendProcessor.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.msgprocess; 2 | 3 | import org.zhong.chatgpt.wechat.bot.model.BotMsg; 4 | 5 | public class ConsoleSendProcessor implements MsgProcessor{ 6 | 7 | @Override 8 | public void process(BotMsg botMsg) { 9 | System.out.println(botMsg.getReplyMsg()); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/org/zhong/chatgpt/wechat/bot/msgprocess/MsgAcceptor.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.msgprocess; 2 | 3 | public interface MsgAcceptor { 4 | 5 | 6 | public void start(); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/zhong/chatgpt/wechat/bot/msgprocess/MsgPreProcessor.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.msgprocess; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | import org.zhong.chatgpt.wechat.bot.config.BotConfig; 5 | import org.zhong.chatgpt.wechat.bot.consts.BotConst; 6 | import org.zhong.chatgpt.wechat.bot.model.BotMsg; 7 | import org.zhong.chatgpt.wechat.bot.model.WehchatMsgQueue; 8 | import org.zhong.chatgpt.wechat.bot.sensitive.SensitiveWord; 9 | 10 | import cn.hutool.cache.CacheUtil; 11 | import cn.hutool.cache.impl.TimedCache; 12 | import cn.zhouyafeng.itchat4j.beans.BaseMsg; 13 | import cn.zhouyafeng.itchat4j.utils.enums.MsgTypeEnum; 14 | 15 | public class MsgPreProcessor implements MsgProcessor{ 16 | 17 | TimedCache timedCache = CacheUtil.newTimedCache(20*60*1000); 18 | 19 | public void process(BotMsg botMsg) { 20 | BaseMsg baseMsg = botMsg.getBaseMsg(); 21 | String fromUserNickName = baseMsg.getFromUserNickName(); 22 | if(StringUtils.isNotEmpty(fromUserNickName) 23 | &&( fromUserNickName.contains("微信支付") 24 | || fromUserNickName.contains("文件传输助手") 25 | || fromUserNickName.contains("微信团队"))) { 26 | //忽略系统消息 27 | return; 28 | } 29 | 30 | if((baseMsg.isGroupMsg() && timedCache.get(baseMsg.getGroupUserName()) != null) 31 | || (!baseMsg.isGroupMsg() && timedCache.get(baseMsg.getFromUserName()) != null)) { 32 | return; 33 | } 34 | 35 | if(baseMsg.isGroupMsg()) {//群聊 36 | 37 | if(!BotConfig.getGroupWhiteList().contains(baseMsg.getGroupName())) { 38 | //如果群聊不在白名单 39 | return; 40 | } 41 | 42 | if(!baseMsg.getContent().contains(BotConfig.getAtBotName())) { 43 | //如果不是@我的消息 44 | return; 45 | } 46 | 47 | long count = WehchatMsgQueue.countGroupUserPreMsg(baseMsg.getGroupUserName()); 48 | if(count > 10) { 49 | timedCache.put(baseMsg.getGroupUserName(), baseMsg.getGroupUserName()); 50 | botMsg.setReplyMsg(BotConst.AT + baseMsg.getGroupUserNickName() + " 你说话太快,接下来的10分钟我不会再处理你的新消息"); 51 | WehchatMsgQueue.pushSendMsg(botMsg); 52 | return; 53 | } 54 | }else {//私聊 55 | 56 | if(!BotConfig.getUserWhiteList().isEmpty() 57 | && !BotConfig.getUserWhiteList().contains(baseMsg.getFromUserNickName())) { 58 | return; 59 | } 60 | 61 | long count = WehchatMsgQueue.countUserPreMsg(baseMsg.getFromUserName()); 62 | if(count > 10) { 63 | timedCache.put(baseMsg.getFromUserName(), baseMsg.getFromUserName()); 64 | botMsg.setReplyMsg("你说话太快,接下来的10分钟我不会再处理你的新消息"); 65 | WehchatMsgQueue.pushSendMsg(botMsg); 66 | return; 67 | } 68 | } 69 | 70 | 71 | if (baseMsg.getType().equals(MsgTypeEnum.TEXT.getType())) { 72 | 73 | baseMsg.setContent(baseMsg.getContent().replace(BotConfig.getAtBotName(), "")); 74 | 75 | String content = baseMsg.getContent(); 76 | if(StringUtils.isEmpty(content)) { 77 | //丢弃 78 | return; 79 | } 80 | 81 | boolean isSensitive = SensitiveWord.contains(content); 82 | if(isSensitive) { 83 | if(baseMsg.isGroupMsg()) { 84 | botMsg.setReplyMsg(BotConst.AT + baseMsg.getGroupUserNickName() + "你说的话太内涵,我无法回答。"); 85 | }else { 86 | botMsg.setReplyMsg("你说的话太内涵,我无法回答。"); 87 | } 88 | 89 | WehchatMsgQueue.pushSendMsg(botMsg); 90 | return; 91 | } 92 | 93 | WehchatMsgQueue.pushReplyMsg(botMsg); 94 | 95 | }else { 96 | // botMsg.setReplyMsg("目前我只能针对文本消息进行回答"); 97 | // WehchatMsgQueue.pushSendMsg(botMsg); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/org/zhong/chatgpt/wechat/bot/msgprocess/MsgProcessor.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.msgprocess; 2 | 3 | import org.zhong.chatgpt.wechat.bot.model.BotMsg; 4 | 5 | public interface MsgProcessor { 6 | 7 | public void process(BotMsg botMsg); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/org/zhong/chatgpt/wechat/bot/msgprocess/OpenAIReplyProcessor.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.msgprocess; 2 | 3 | import java.time.Duration; 4 | import java.util.ArrayList; 5 | import java.util.Arrays; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | import org.apache.commons.lang3.StringUtils; 10 | import org.yaml.snakeyaml.Yaml; 11 | import org.zhong.chatgpt.wechat.bot.builder.OpenAiServiceBuilder; 12 | import org.zhong.chatgpt.wechat.bot.config.BotConfig; 13 | import org.zhong.chatgpt.wechat.bot.consts.BotConst; 14 | import org.zhong.chatgpt.wechat.bot.model.BotMsg; 15 | import org.zhong.chatgpt.wechat.bot.model.WehchatMsgQueue; 16 | 17 | import com.theokanning.openai.completion.CompletionRequest; 18 | import com.theokanning.openai.service.OpenAiService; 19 | 20 | import cn.zhouyafeng.itchat4j.beans.BaseMsg; 21 | 22 | /** 23 | * 使用普通OpenAI接口进行回复 24 | * @author zhong 25 | * 26 | */ 27 | public class OpenAIReplyProcessor implements MsgProcessor{ 28 | 29 | private static OpenAiService service = OpenAiServiceBuilder.build(BotConfig.getAppKey(), Duration.ofSeconds(300)); 30 | 31 | private static String model = "text-davinci-003"; 32 | private static Double temperature = 0.9; 33 | private static Integer maxTokens = 2000; 34 | private static Double topP = 1d; 35 | private static Double frequencyPenalty = 0.2; 36 | private static Double presencePenalty = 0.6; 37 | private static List stops = new ArrayList(); 38 | 39 | static { 40 | 41 | 42 | final Yaml yaml = new Yaml(); 43 | Map yamlMap = yaml.load(BotConfig.class.getResourceAsStream("/application.yml")); 44 | 45 | String stopsStr = (String) yamlMap.get("bot.openai.completio.stop"); 46 | model = yamlMap.get("bot.openai.completio.model").toString(); 47 | temperature = Double.valueOf( yamlMap.get("bot.openai.completio.temperature").toString()); 48 | maxTokens = Integer.valueOf( yamlMap.get("bot.openai.completio.max_tokens").toString()); 49 | topP = Double.valueOf( yamlMap.get("bot.openai.completio.top_p").toString()); 50 | frequencyPenalty = Double.valueOf( yamlMap.get("bot.openai.completio.frequency_penalty").toString()); 51 | presencePenalty = Double.valueOf( yamlMap.get("bot.openai.completio.presence_penalty").toString()); 52 | if(StringUtils.isNotEmpty(stopsStr)) { 53 | stops = Arrays.asList(stopsStr.split(",")); 54 | } 55 | 56 | } 57 | 58 | @Override 59 | public void process(BotMsg botMsg) { 60 | 61 | BaseMsg baseMsg = botMsg.getBaseMsg(); 62 | CompletionRequest completionRequest = CompletionRequest.builder() 63 | .prompt(baseMsg.getContent()) 64 | .model(model) 65 | .maxTokens(maxTokens) 66 | .temperature(temperature) 67 | .topP(topP) 68 | .frequencyPenalty(frequencyPenalty) 69 | .presencePenalty(presencePenalty) 70 | .echo(true) 71 | .user(botMsg.getUserName()) 72 | .build(); 73 | 74 | try { 75 | 76 | String text = service.createCompletion(completionRequest).getChoices().get(0).getText(); 77 | botMsg.setReplyMsg(text); 78 | WehchatMsgQueue.pushSendMsg(botMsg); 79 | 80 | }catch (Exception e) { 81 | e.printStackTrace(); 82 | 83 | botMsg.setRetries(botMsg.getRetries() + 1); 84 | if(botMsg.getRetries() < 5) { 85 | WehchatMsgQueue.pushReplyMsg(botMsg); 86 | }else { 87 | String recontent = baseMsg.getContent(); 88 | if(recontent.length() > 20) { 89 | recontent = recontent.substring(0, 17) + "...\n"; 90 | } 91 | botMsg.setReplyMsg(recontent+ "该提问已失效,请重新提问"); 92 | WehchatMsgQueue.pushSendMsg(botMsg); 93 | } 94 | 95 | } 96 | 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/org/zhong/chatgpt/wechat/bot/msgprocess/WechatMsgAcceptor.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.msgprocess; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.zhong.chatgpt.wechat.bot.config.BotConfig; 6 | 7 | import cn.zhouyafeng.itchat4j.Wechat; 8 | import cn.zhouyafeng.itchat4j.face.IMsgHandlerFace; 9 | 10 | public class WechatMsgAcceptor implements MsgAcceptor{ 11 | 12 | private static Logger logger = LoggerFactory.getLogger(WechatMsgAcceptor.class); 13 | 14 | @Override 15 | public void start() { 16 | logger.info("消息接收线程启动"); 17 | IMsgHandlerFace msgHandler = new WechatMsgHandler(); 18 | Wechat wechat = new Wechat(msgHandler, BotConfig.getQrcodePath()); 19 | wechat.start(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/org/zhong/chatgpt/wechat/bot/msgprocess/WechatMsgHandler.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.msgprocess; 2 | 3 | import org.zhong.chatgpt.wechat.bot.model.BotMsg; 4 | import org.zhong.chatgpt.wechat.bot.model.WehchatMsgQueue; 5 | 6 | import cn.zhouyafeng.itchat4j.beans.BaseMsg; 7 | import cn.zhouyafeng.itchat4j.face.IMsgHandlerFace; 8 | 9 | public class WechatMsgHandler implements IMsgHandlerFace { 10 | 11 | @Override 12 | public String textMsgHandle(BaseMsg msg) { 13 | WehchatMsgQueue.pushPreMsg(new BotMsg(msg)); 14 | return null; 15 | } 16 | 17 | @Override 18 | public String picMsgHandle(BaseMsg msg) { 19 | WehchatMsgQueue.pushPreMsg(new BotMsg(msg)); 20 | return null; 21 | } 22 | 23 | @Override 24 | public String voiceMsgHandle(BaseMsg msg) { 25 | WehchatMsgQueue.pushPreMsg(new BotMsg(msg)); 26 | return null; 27 | } 28 | 29 | @Override 30 | public String viedoMsgHandle(BaseMsg msg) { 31 | WehchatMsgQueue.pushPreMsg(new BotMsg(msg)); 32 | return null; 33 | } 34 | 35 | @Override 36 | public String nameCardMsgHandle(BaseMsg msg) { 37 | return null; 38 | } 39 | 40 | @Override 41 | public void sysMsgHandle(BaseMsg msg) { 42 | // TODO Auto-generated method stub 43 | 44 | } 45 | 46 | @Override 47 | public String verifyAddFriendMsgHandle(BaseMsg msg) { 48 | // TODO Auto-generated method stub 49 | return null; 50 | } 51 | 52 | @Override 53 | public String mediaMsgHandle(BaseMsg msg) { 54 | WehchatMsgQueue.pushPreMsg(new BotMsg(msg)); 55 | return null; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/zhong/chatgpt/wechat/bot/msgprocess/WechatSendProcessor.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.msgprocess; 2 | 3 | import org.zhong.chatgpt.wechat.bot.model.BotMsg; 4 | 5 | import cn.zhouyafeng.itchat4j.api.MessageTools; 6 | 7 | public class WechatSendProcessor implements MsgProcessor{ 8 | 9 | @Override 10 | public void process(BotMsg botMsg) { 11 | 12 | MessageTools.sendMsgById(botMsg.getReplyMsg(), botMsg.getBaseMsg().getFromUserName()); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/zhong/chatgpt/wechat/bot/sensitive/SensitiveWord.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.sensitive; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.zhong.chatgpt.wechat.bot.config.BotConfig; 9 | 10 | import cn.hutool.core.io.file.FileReader; 11 | 12 | /** 13 | * 简单实现敏感词,主要为了适配一下,可以换其它实现。 14 | * @author zhong 15 | * 16 | */ 17 | public class SensitiveWord { 18 | 19 | private static Logger logger = LoggerFactory.getLogger(SensitiveWord.class); 20 | 21 | private static List words = new ArrayList(); 22 | 23 | static { 24 | try { 25 | FileReader fileReader = new FileReader(BotConfig.getDictPath()); 26 | words = fileReader.readLines(); 27 | } catch (Exception e) { 28 | logger.warn("读取敏感词配置失败"); 29 | } 30 | } 31 | 32 | /** 33 | * 判断文本是否包含敏感词 34 | * @param word 35 | * @return 36 | */ 37 | public static boolean contains(String text) { 38 | for(String word : words) { 39 | if(text.contains(word)) { 40 | return true; 41 | } 42 | } 43 | return false; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/org/zhong/chatgpt/wechat/bot/util/SSLSocketClientUtil.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.util; 2 | 3 | import javax.net.ssl.*; 4 | import java.security.SecureRandom; 5 | import java.security.cert.X509Certificate; 6 | import java.security.KeyManagementException; 7 | import java.security.NoSuchAlgorithmException; 8 | import java.security.cert.CertificateException; 9 | 10 | /** 11 | * 为了支持okhttp 绕过验签功能 12 | **/ 13 | public class SSLSocketClientUtil { 14 | 15 | public static SSLSocketFactory getSocketFactory(TrustManager manager) { 16 | SSLSocketFactory socketFactory = null; 17 | try { 18 | SSLContext sslContext = SSLContext.getInstance("SSL"); 19 | sslContext.init(null, new TrustManager[]{manager}, new SecureRandom()); 20 | socketFactory = sslContext.getSocketFactory(); 21 | } catch (NoSuchAlgorithmException e) { 22 | e.printStackTrace(); 23 | } catch (KeyManagementException e) { 24 | e.printStackTrace(); 25 | } 26 | return socketFactory; 27 | } 28 | 29 | public static X509TrustManager getX509TrustManager() { 30 | return new X509TrustManager() { 31 | @Override 32 | public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { 33 | 34 | } 35 | 36 | @Override 37 | public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { 38 | 39 | } 40 | 41 | @Override 42 | public X509Certificate[] getAcceptedIssuers() { 43 | return new X509Certificate[0]; 44 | } 45 | }; 46 | } 47 | 48 | public static HostnameVerifier getHostnameVerifier() { 49 | HostnameVerifier hostnameVerifier = new HostnameVerifier() { 50 | @Override 51 | public boolean verify(String s, SSLSession sslSession) { 52 | return true; 53 | } 54 | }; 55 | return hostnameVerifier; 56 | } 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/org/zhong/chatgpt/wechat/bot/wechatbot/MsgPreThread.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.wechatbot; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.zhong.chatgpt.wechat.bot.model.BotMsg; 6 | import org.zhong.chatgpt.wechat.bot.model.WehchatMsgQueue; 7 | import org.zhong.chatgpt.wechat.bot.msgprocess.MsgProcessor; 8 | 9 | import com.alibaba.fastjson.JSON; 10 | 11 | public class MsgPreThread implements Runnable { 12 | 13 | private static Logger logger = LoggerFactory.getLogger(MsgPreThread.class); 14 | 15 | private MsgProcessor msgProcessor; 16 | 17 | public MsgPreThread(MsgProcessor msgProcessor) { 18 | super(); 19 | this.msgProcessor = msgProcessor; 20 | } 21 | 22 | @Override 23 | public void run() { 24 | for(;;) { 25 | BotMsg botMsg = WehchatMsgQueue.popPreMsg(); 26 | if(botMsg == null) { 27 | logger.debug("从preMsgs获取消息为空"); 28 | try { 29 | Thread.sleep(500); 30 | } catch (InterruptedException e) { 31 | e.printStackTrace(); 32 | } 33 | }else { 34 | logger.debug("从preMsgs获取消息:", JSON.toJSONString(botMsg)); 35 | msgProcessor.process(botMsg); 36 | } 37 | } 38 | } 39 | 40 | public static void start(MsgProcessor msgProcessor) { 41 | logger.info("消息预处理线程启动"); 42 | new Thread(new MsgPreThread(msgProcessor)).start(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/zhong/chatgpt/wechat/bot/wechatbot/MsgReplyThread.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.wechatbot; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.zhong.chatgpt.wechat.bot.model.BotMsg; 6 | import org.zhong.chatgpt.wechat.bot.model.WehchatMsgQueue; 7 | import org.zhong.chatgpt.wechat.bot.msgprocess.MsgProcessor; 8 | 9 | import com.alibaba.fastjson.JSON; 10 | 11 | public class MsgReplyThread implements Runnable { 12 | 13 | private static Logger logger = LoggerFactory.getLogger(MsgReplyThread.class); 14 | 15 | private MsgProcessor msgProcessor; 16 | 17 | public MsgReplyThread(MsgProcessor msgProcessor) { 18 | super(); 19 | this.msgProcessor = msgProcessor; 20 | } 21 | 22 | @Override 23 | public void run() { 24 | for(;;) { 25 | BotMsg botMsg = WehchatMsgQueue.popReplyMsg(); 26 | if(botMsg == null) { 27 | logger.debug("从waitReplyMsgs获取消息为空"); 28 | try { 29 | Thread.sleep(500); 30 | } catch (InterruptedException e) { 31 | e.printStackTrace(); 32 | } 33 | }else { 34 | logger.debug("从waitReplyMsgs获取消息:{}", JSON.toJSONString(botMsg)); 35 | msgProcessor.process(botMsg); 36 | 37 | try { 38 | Thread.sleep(10000); 39 | } catch (InterruptedException e) { 40 | // TODO Auto-generated catch block 41 | e.printStackTrace(); 42 | } 43 | 44 | } 45 | } 46 | 47 | } 48 | 49 | public static void start(MsgProcessor msgProcessor) { 50 | logger.info("消息回复线程启动"); 51 | new Thread(new MsgReplyThread(msgProcessor)).start(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/zhong/chatgpt/wechat/bot/wechatbot/WechatSendThread.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.wechatbot; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.zhong.chatgpt.wechat.bot.model.BotMsg; 6 | import org.zhong.chatgpt.wechat.bot.model.WehchatMsgQueue; 7 | import org.zhong.chatgpt.wechat.bot.msgprocess.MsgProcessor; 8 | 9 | import com.alibaba.fastjson.JSON; 10 | 11 | import cn.hutool.core.util.RandomUtil; 12 | 13 | public class WechatSendThread implements Runnable { 14 | 15 | private static Logger logger = LoggerFactory.getLogger(WechatSendThread.class); 16 | 17 | private MsgProcessor msgProcessor; 18 | 19 | public WechatSendThread(MsgProcessor msgProcessor) { 20 | super(); 21 | this.msgProcessor = msgProcessor; 22 | } 23 | 24 | @Override 25 | public void run() { 26 | for(;;) { 27 | BotMsg botMsg = WehchatMsgQueue.popSendMsg(); 28 | if(botMsg == null) { 29 | logger.debug("从waitSendMsgs获取消息为空"); 30 | try { 31 | Thread.sleep(500); 32 | } catch (InterruptedException e) { 33 | e.printStackTrace(); 34 | } 35 | }else { 36 | logger.debug("从waitSendMsgs获取消息:{}", JSON.toJSONString(botMsg)); 37 | msgProcessor.process(botMsg); 38 | 39 | try { 40 | int sleep = RandomUtil.randomInt(5, 20); 41 | Thread.sleep(sleep * 1000); 42 | } catch (InterruptedException e) { 43 | // TODO Auto-generated catch block 44 | e.printStackTrace(); 45 | } 46 | } 47 | } 48 | 49 | } 50 | 51 | public static void start(MsgProcessor msgProcessor) { 52 | logger.info("消息发送线程启动"); 53 | new Thread(new WechatSendThread(msgProcessor)).start(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | main: 3 | allow-bean-definition-overriding: true 4 | jpa: 5 | hibernate.ddl-auto: none 6 | show-sql: true 7 | 8 | #机器人微信nickName 9 | bot.botName: 狂追风龙少 10 | #Openai的appkey 11 | bot.appKey: 12 | #微信登录二维码生成路径 13 | bot.wechat.qrcode.path: 14 | 15 | #ai模型配置 参考https://platform.openai.com/examples 16 | bot.openai.completio.model: text-davinci-003 17 | bot.openai.completio.temperature: 0.9 18 | bot.openai.completio.max_tokens: 2000 19 | bot.openai.completio.top_p: 1 20 | bot.openai.completio.frequency_penalty: 0.0 21 | bot.openai.completio.presence_penalty: 0.6 22 | bot.openai.completio.stop: 23 | 24 | #代理设置 25 | proxy.enable: true 26 | proxy.host: 127.0.0.1 27 | proxy.port: 33211 -------------------------------------------------------------------------------- /src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | # 默认激活dev运行配置 2 | server.port: 8888 3 | #最大线程数 4 | server.tomcat.max-threads: 600 5 | #最大等待请求数 6 | server.tomcat.accept-count: 400 7 | #最大连接数 8 | server.tomcat.max-connections: 10000 -------------------------------------------------------------------------------- /src/main/resources/dict.txt: -------------------------------------------------------------------------------- 1 | 文化大革命 -------------------------------------------------------------------------------- /src/main/resources/groupWhiteList.txt: -------------------------------------------------------------------------------- 1 | 无敌测试小组 2 | -------------------------------------------------------------------------------- /src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | %date [ %thread] %logger{36} [L:%line] - %msg%n 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/main/resources/userWhiteList.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhong2312/java-chatgpt-wechat-bot/81e6848b47ce341c94bb73c0e15e5c92d8ca0811/src/main/resources/userWhiteList.txt -------------------------------------------------------------------------------- /src/test/java/org/zhong/chatgpt/wechat/bot/chatgptwechatbot/ChatgptWechatBotApplicationTests.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.chatgptwechatbot; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ChatgptWechatBotApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/org/zhong/chatgpt/wechat/bot/chatgptwechatbot/test/TestBot.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.chatgptwechatbot.test; 2 | 3 | import org.junit.Test; 4 | import org.zhong.chatgpt.wechat.bot.model.Bot; 5 | 6 | public class TestBot { 7 | 8 | 9 | @Test 10 | public void test() { 11 | Bot.buildChatGPTConsoleBot().start(); 12 | } 13 | 14 | @Test 15 | public void testAutoBot() { 16 | Bot.buildChatGPTAutoBot().start(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/org/zhong/chatgpt/wechat/bot/chatgptwechatbot/test/TestBotConfig.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.chatgptwechatbot.test; 2 | 3 | import org.junit.Test; 4 | import org.zhong.chatgpt.wechat.bot.config.BotConfig; 5 | 6 | import com.alibaba.fastjson.JSON; 7 | 8 | public class TestBotConfig { 9 | 10 | @Test 11 | public void test() { 12 | System.out.println(JSON.toJSONString(BotConfig.getGroupWhiteList())); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/test/java/org/zhong/chatgpt/wechat/bot/chatgptwechatbot/test/TestOpenAI.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.chatgptwechatbot.test; 2 | 3 | import java.time.Duration; 4 | import java.util.List; 5 | 6 | import org.apache.http.client.CookieStore; 7 | import org.apache.http.cookie.Cookie; 8 | import org.apache.http.impl.client.BasicCookieStore; 9 | import org.apache.http.impl.client.CloseableHttpClient; 10 | import org.apache.http.impl.client.HttpClients; 11 | import org.junit.jupiter.api.Test; 12 | import org.zhong.chatgpt.wechat.bot.config.BotConfig; 13 | 14 | import com.theokanning.openai.completion.CompletionRequest; 15 | 16 | import cn.zhouyafeng.itchat4j.utils.MyHttpClient; 17 | 18 | import com.theokanning.openai.OpenAiService; 19 | 20 | public class TestOpenAI { 21 | 22 | private static CloseableHttpClient httpClient; 23 | 24 | private static MyHttpClient instance = null; 25 | 26 | private static CookieStore cookieStore; 27 | 28 | static { 29 | cookieStore = new BasicCookieStore(); 30 | 31 | // 将CookieStore设置到httpClient中 32 | httpClient = HttpClients.custom().setDefaultCookieStore(cookieStore).build(); 33 | 34 | } 35 | 36 | public static String getCookie(String name) { 37 | List cookies = cookieStore.getCookies(); 38 | for (Cookie cookie : cookies) { 39 | if (cookie.getName().equalsIgnoreCase(name)) { 40 | return cookie.getValue(); 41 | } 42 | } 43 | return null; 44 | 45 | } 46 | 47 | @Test 48 | public void test() { 49 | OpenAiService service = new OpenAiService(BotConfig.getAppKey(),"https://api.openai.com/", Duration.ofSeconds(300)); 50 | CompletionRequest completionRequest = CompletionRequest.builder() 51 | .prompt("你好") 52 | .model("text-davinci-003") 53 | .maxTokens(2000) 54 | .temperature(0.8) 55 | .topP(1.0) 56 | .frequencyPenalty(0.55) 57 | .presencePenalty(0.19) 58 | .echo(true) 59 | .user("1234213213") 60 | .build(); 61 | 62 | String text = service.createCompletion(completionRequest).getChoices().get(0).getText(); 63 | 64 | System.out.print(text); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/org/zhong/chatgpt/wechat/bot/chatgptwechatbot/test/TestOpenAIThread.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.chatgptwechatbot.test; 2 | 3 | import org.junit.Test; 4 | import org.zhong.chatgpt.wechat.bot.model.BotMsg; 5 | import org.zhong.chatgpt.wechat.bot.model.WehchatMsgQueue; 6 | import org.zhong.chatgpt.wechat.bot.msgprocess.OpenAIReplyProcessor; 7 | import org.zhong.chatgpt.wechat.bot.wechatbot.MsgReplyThread; 8 | 9 | import cn.zhouyafeng.itchat4j.beans.BaseMsg; 10 | 11 | public class TestOpenAIThread { 12 | 13 | @Test 14 | public void test() { 15 | BaseMsg baseMsg = new BaseMsg(); 16 | baseMsg.setFromUserName("@56b78712b25cdd82515e7324696885d4"); 17 | baseMsg.setContent("你好"); 18 | WehchatMsgQueue.pushReplyMsg(new BotMsg(baseMsg)); 19 | 20 | MsgReplyThread.start(new OpenAIReplyProcessor()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/org/zhong/chatgpt/wechat/bot/chatgptwechatbot/test/TestScanner.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.chatgptwechatbot.test; 2 | 3 | import org.junit.Test; 4 | import org.zhong.chatgpt.wechat.bot.msgprocess.ConsoleMsgAcceptor; 5 | 6 | public class TestScanner { 7 | 8 | 9 | @Test 10 | public void testScanner() { 11 | ConsoleMsgAcceptor mockMsgAcceptor = new ConsoleMsgAcceptor(); 12 | mockMsgAcceptor.start(); 13 | } 14 | 15 | public static void main(String[] args) { 16 | ConsoleMsgAcceptor mockMsgAcceptor = new ConsoleMsgAcceptor(); 17 | mockMsgAcceptor.start(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/org/zhong/chatgpt/wechat/bot/chatgptwechatbot/test/TestSensitiveWord.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.chatgptwechatbot.test; 2 | 3 | import org.junit.Test; 4 | import org.zhong.chatgpt.wechat.bot.sensitive.SensitiveWord; 5 | 6 | public class TestSensitiveWord { 7 | 8 | 9 | @Test 10 | public void test() { 11 | System.out.println(SensitiveWord.contains("行车自行车阿松大文化大革命啊实打实")); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/org/zhong/chatgpt/wechat/bot/chatgptwechatbot/test/TestWechatMsgQueue.java: -------------------------------------------------------------------------------- 1 | package org.zhong.chatgpt.wechat.bot.chatgptwechatbot.test; 2 | 3 | import org.junit.Assert; 4 | import org.junit.jupiter.api.Test; 5 | import org.zhong.chatgpt.wechat.bot.model.BotMsg; 6 | import org.zhong.chatgpt.wechat.bot.model.WehchatMsgQueue; 7 | 8 | import cn.zhouyafeng.itchat4j.beans.BaseMsg; 9 | 10 | public class TestWechatMsgQueue { 11 | 12 | 13 | @Test 14 | public void testQueue() { 15 | BotMsg botMsg = WehchatMsgQueue.popPreMsg(); 16 | Assert.assertNull(botMsg); 17 | 18 | WehchatMsgQueue.pushPreMsg(new BotMsg(new BaseMsg())); 19 | botMsg = WehchatMsgQueue.popPreMsg(); 20 | Assert.assertNotNull(botMsg); 21 | 22 | } 23 | } 24 | --------------------------------------------------------------------------------