From 415d8f738dc36eb9709fc44b26913fec7512823b Mon Sep 17 00:00:00 2001 From: wangbing Date: Tue, 23 Sep 2025 15:21:11 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=A4=87=E4=BB=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 20 + README.md | 0 pom.xml | 234 ++++++ prop.ini | 0 .../java/cn/zhouyafeng/itchat4j/Wechat.java | 33 + .../zhouyafeng/itchat4j/api/AssistTools.java | 43 ++ .../zhouyafeng/itchat4j/api/MessageTools.java | 424 +++++++++++ .../zhouyafeng/itchat4j/api/WechatTools.java | 215 ++++++ .../cn/zhouyafeng/itchat4j/beans/AppInfo.java | 37 + .../cn/zhouyafeng/itchat4j/beans/BaseMsg.java | 293 ++++++++ .../itchat4j/beans/RecommendInfo.java | 146 ++++ .../itchat4j/controller/LoginController.java | 88 +++ .../cn/zhouyafeng/itchat4j/core/Core.java | 276 +++++++ .../zhouyafeng/itchat4j/core/MsgCenter.java | 170 +++++ .../itchat4j/face/IMsgHandlerFace.java | 91 +++ .../itchat4j/service/ILoginService.java | 82 +++ .../service/impl/LoginServiceImpl.java | 688 ++++++++++++++++++ .../thread/CheckLoginStatusThread.java | 38 + .../cn/zhouyafeng/itchat4j/utils/Config.java | 74 ++ .../itchat4j/utils/ConstantConfigEnum.java | 34 + .../itchat4j/utils/MsgKeywords.java | 6 + .../itchat4j/utils/MyHttpClient.java | 183 +++++ .../zhouyafeng/itchat4j/utils/SleepUtils.java | 20 + .../itchat4j/utils/enums/MsgCodeEnum.java | 68 ++ .../itchat4j/utils/enums/MsgTypeEnum.java | 38 + .../itchat4j/utils/enums/OsNameEnum.java | 13 + .../itchat4j/utils/enums/ResultEnum.java | 35 + .../itchat4j/utils/enums/RetCodeEnum.java | 30 + .../utils/enums/StorageLoginInfoEnum.java | 59 ++ .../itchat4j/utils/enums/URLEnum.java | 49 ++ .../utils/enums/VerifyFriendEnum.java | 28 + .../utils/enums/parameters/BaseParaEnum.java | 36 + .../utils/enums/parameters/LoginParaEnum.java | 31 + .../parameters/StatusNotifyParaEnum.java | 30 + .../utils/enums/parameters/UUIDParaEnum.java | 30 + .../itchat4j/utils/tools/CommonTools.java | 243 +++++++ .../itchat4j/utils/tools/DownloadTools.java | 80 ++ .../xyz/wbsite/itchat4j/JMainApplication.java | 79 ++ .../xyz/wbsite/itchat4j/JMainController.java | 172 +++++ .../java/xyz/wbsite/itchat4j/JMsgHandler.java | 46 ++ src/main/java/xyz/wbsite/itchat4j/JProp.java | 92 +++ .../java/xyz/wbsite/itchat4j/db/Client.java | 151 ++++ .../xyz/wbsite/itchat4j/db/ObjectClient.java | 323 ++++++++ .../java/xyz/wbsite/itchat4j/db/Where.java | 94 +++ .../itchat4j/db/anonation/TableField.java | 13 + .../wbsite/itchat4j/db/entity/Example.java | 110 +++ .../java/xyz/wbsite/itchat4j/ui/FXMLUtil.java | 33 + .../xyz/wbsite/itchat4j/util/DialogUtil.java | 269 +++++++ .../xyz/wbsite/itchat4j/util/ImageUtil.java | 107 +++ .../java/xyz/wbsite/itchat4j/util/Logger.java | 52 ++ .../wbsite/itchat4j/util/ResourceUtil.java | 62 ++ .../xyz/wbsite/itchat4j/util/TaskUtil.java | 356 +++++++++ .../xyz/wbsite/itchat4j/util/ValueUtil.java | 45 ++ src/main/resources/icon.ico | Bin 0 -> 38078 bytes src/main/resources/icon.png | Bin 0 -> 1025 bytes src/main/resources/main.css | 12 + src/main/resources/main.fxml | 126 ++++ .../itchat4j/demo/demo1/MyTest.java | 33 + .../itchat4j/demo/demo1/SimpleDemo.java | 108 +++ .../itchat4j/demo/demo2/TulingRobot.java | 111 +++ .../itchat4j/demo/demo3/PicYourFriends.java | 120 +++ .../itchat4j/demo/unuseful/UnusefulDemo.java | 87 +++ 62 files changed, 6566 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 pom.xml create mode 100644 prop.ini create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/Wechat.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/api/AssistTools.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/api/MessageTools.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/api/WechatTools.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/beans/AppInfo.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/beans/BaseMsg.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/beans/RecommendInfo.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/controller/LoginController.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/core/Core.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/core/MsgCenter.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/face/IMsgHandlerFace.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/service/ILoginService.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/service/impl/LoginServiceImpl.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/thread/CheckLoginStatusThread.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/utils/Config.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/utils/ConstantConfigEnum.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/utils/MsgKeywords.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/utils/MyHttpClient.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/utils/SleepUtils.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/utils/enums/MsgCodeEnum.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/utils/enums/MsgTypeEnum.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/utils/enums/OsNameEnum.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/utils/enums/ResultEnum.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/utils/enums/RetCodeEnum.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/utils/enums/StorageLoginInfoEnum.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/utils/enums/URLEnum.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/utils/enums/VerifyFriendEnum.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/utils/enums/parameters/BaseParaEnum.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/utils/enums/parameters/LoginParaEnum.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/utils/enums/parameters/StatusNotifyParaEnum.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/utils/enums/parameters/UUIDParaEnum.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/utils/tools/CommonTools.java create mode 100644 src/main/java/cn/zhouyafeng/itchat4j/utils/tools/DownloadTools.java create mode 100644 src/main/java/xyz/wbsite/itchat4j/JMainApplication.java create mode 100644 src/main/java/xyz/wbsite/itchat4j/JMainController.java create mode 100644 src/main/java/xyz/wbsite/itchat4j/JMsgHandler.java create mode 100644 src/main/java/xyz/wbsite/itchat4j/JProp.java create mode 100644 src/main/java/xyz/wbsite/itchat4j/db/Client.java create mode 100644 src/main/java/xyz/wbsite/itchat4j/db/ObjectClient.java create mode 100644 src/main/java/xyz/wbsite/itchat4j/db/Where.java create mode 100644 src/main/java/xyz/wbsite/itchat4j/db/anonation/TableField.java create mode 100644 src/main/java/xyz/wbsite/itchat4j/db/entity/Example.java create mode 100644 src/main/java/xyz/wbsite/itchat4j/ui/FXMLUtil.java create mode 100644 src/main/java/xyz/wbsite/itchat4j/util/DialogUtil.java create mode 100644 src/main/java/xyz/wbsite/itchat4j/util/ImageUtil.java create mode 100644 src/main/java/xyz/wbsite/itchat4j/util/Logger.java create mode 100644 src/main/java/xyz/wbsite/itchat4j/util/ResourceUtil.java create mode 100644 src/main/java/xyz/wbsite/itchat4j/util/TaskUtil.java create mode 100644 src/main/java/xyz/wbsite/itchat4j/util/ValueUtil.java create mode 100644 src/main/resources/icon.ico create mode 100644 src/main/resources/icon.png create mode 100644 src/main/resources/main.css create mode 100644 src/main/resources/main.fxml create mode 100644 src/test/java/cn/zhouyafeng/itchat4j/demo/demo1/MyTest.java create mode 100644 src/test/java/cn/zhouyafeng/itchat4j/demo/demo1/SimpleDemo.java create mode 100644 src/test/java/cn/zhouyafeng/itchat4j/demo/demo2/TulingRobot.java create mode 100644 src/test/java/cn/zhouyafeng/itchat4j/demo/demo3/PicYourFriends.java create mode 100644 src/test/java/cn/zhouyafeng/itchat4j/demo/unuseful/UnusefulDemo.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1f128fe --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +/.idea +*.iml +/.settings +/bin +/gen +/build +/gradle +/classes +.classpath +.project +*.gradle +gradlew +local.properties +node_modules/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..9746e5b --- /dev/null +++ b/pom.xml @@ -0,0 +1,234 @@ + + + 4.0.0 + xyz.wbsite + starter-itchat4j + 0.1 + jar + starter-itchat4j + project for starter-itchat4j + + + UTF-8 + UTF-8 + 8 + true + + + + + + aliyun + Aliyun Repository + default + https://maven.aliyun.com/repository/public + + + + + + aliyun + Aliyun Repository + https://maven.aliyun.com/repository/public + default + + + + + + cn.hutool + hutool-all + 5.8.26 + + + + org.xerial + sqlite-jdbc + 3.14.2.1 + + + + + com.melloware + jintellitype + 1.4.0 + + + + + + org.apache.httpcomponents + httpclient + 4.5.3 + + + + org.apache.httpcomponents + httpmime + 4.5 + + + + + com.alibaba + fastjson + 1.2.31 + + + + + org.apache.commons + commons-lang3 + 3.0 + + + + + com.vdurmont + emoji-java + 3.2.0 + + + + + javax.activation + activation + 1.1.1 + + + + + junit + junit + 4.12 + + + + + org.slf4j + slf4j-api + 1.6.6 + + + org.slf4j + slf4j-log4j12 + 1.6.6 + + + log4j + log4j + 1.2.16 + + + + + com.squareup.okhttp3 + okhttp + 3.8.0 + + + + + + + ${artifactId}-${version} + src/main/java + + + src/main/resources + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + + + + com.zenjava + javafx-maven-plugin + 8.8.3 + + + xyz.wbsite.itchat4j.JMainApplication + + ${project.artifactId} + + ${project.artifactId} + + ${project.artifactId}.jar + + ${project.version} + + + ${project.basedir}/src/main/resources/icon.ico + + + true + + true + + + + create-jfx-jar + package + + build-jar + + + + create-exe + package + + build-native + + + + + + + + maven-antrun-plugin + + + package + + run + + + + + + + + + + + + + + diff --git a/prop.ini b/prop.ini new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/cn/zhouyafeng/itchat4j/Wechat.java b/src/main/java/cn/zhouyafeng/itchat4j/Wechat.java new file mode 100644 index 0000000..baf4ee2 --- /dev/null +++ b/src/main/java/cn/zhouyafeng/itchat4j/Wechat.java @@ -0,0 +1,33 @@ +package cn.zhouyafeng.itchat4j; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import cn.zhouyafeng.itchat4j.controller.LoginController; +import cn.zhouyafeng.itchat4j.core.MsgCenter; +import cn.zhouyafeng.itchat4j.face.IMsgHandlerFace; + +public class Wechat { + private static final Logger LOG = LoggerFactory.getLogger(Wechat.class); + private IMsgHandlerFace msgHandler; + + public Wechat(IMsgHandlerFace msgHandler, String qrPath) { + System.setProperty("jsse.enableSNIExtension", "false"); // 防止SSL错误 + this.msgHandler = msgHandler; + + // 登陆 + LoginController login = new LoginController(); + login.login(qrPath); + } + + public void start() { + LOG.info("+++++++++++++++++++开始消息处理+++++++++++++++++++++"); + new Thread(new Runnable() { + @Override + public void run() { + MsgCenter.handleMsg(msgHandler); + } + }).start(); + } + +} diff --git a/src/main/java/cn/zhouyafeng/itchat4j/api/AssistTools.java b/src/main/java/cn/zhouyafeng/itchat4j/api/AssistTools.java new file mode 100644 index 0000000..6760611 --- /dev/null +++ b/src/main/java/cn/zhouyafeng/itchat4j/api/AssistTools.java @@ -0,0 +1,43 @@ +package cn.zhouyafeng.itchat4j.api; + +import java.io.File; +import java.io.IOException; + +import okhttp3.Call; +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +/** + * 辅助工具类,该类暂时未用,请忽略 + * + * @author https://github.com/yaphone + * @date 创建时间:2017年5月22日 下午10:34:46 + * @version 1.0 + * + */ +public class AssistTools { + private static OkHttpClient client = new OkHttpClient(); + private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png"); + + public static boolean sendQrPicToServer(String username, String password, String uploadUrl, String localPath) + throws IOException { + File file = new File(localPath); + RequestBody requestBody = new MultipartBody.Builder().addFormDataPart("username", username) + .addFormDataPart("password", password) + .addFormDataPart("file", file.getName(), RequestBody.create(MEDIA_TYPE_PNG, file)).build(); + Request request = new Request.Builder().url(uploadUrl).post(requestBody).build(); + Call call = client.newCall(request); + try { + Response response = call.execute(); + System.out.println(response.body().string()); + } catch (IOException e) { + e.printStackTrace(); + } + return true; + } + +} diff --git a/src/main/java/cn/zhouyafeng/itchat4j/api/MessageTools.java b/src/main/java/cn/zhouyafeng/itchat4j/api/MessageTools.java new file mode 100644 index 0000000..da940df --- /dev/null +++ b/src/main/java/cn/zhouyafeng/itchat4j/api/MessageTools.java @@ -0,0 +1,424 @@ +package cn.zhouyafeng.itchat4j.api; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import javax.activation.MimetypesFileTypeMap; + +import org.apache.commons.lang3.StringUtils; +import org.apache.http.Consts; +import org.apache.http.HttpEntity; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; + +import cn.zhouyafeng.itchat4j.beans.BaseMsg; +import cn.zhouyafeng.itchat4j.beans.RecommendInfo; +import cn.zhouyafeng.itchat4j.core.Core; +import cn.zhouyafeng.itchat4j.utils.Config; +import cn.zhouyafeng.itchat4j.utils.MyHttpClient; +import cn.zhouyafeng.itchat4j.utils.enums.StorageLoginInfoEnum; +import cn.zhouyafeng.itchat4j.utils.enums.URLEnum; +import cn.zhouyafeng.itchat4j.utils.enums.VerifyFriendEnum; + +/** + * 消息处理类 + * + * @author https://github.com/yaphone + * @date 创建时间:2017年4月23日 下午2:30:37 + * @version 1.0 + * + */ +public class MessageTools { + private static Logger LOG = LoggerFactory.getLogger(MessageTools.class); + private static Core core = Core.getInstance(); + private static MyHttpClient myHttpClient = core.getMyHttpClient(); + + /** + * 根据UserName发送文本消息 + * + * @author https://github.com/yaphone + * @date 2017年5月4日 下午11:17:38 + * @param msg + * @param toUserName + */ + private static void sendMsg(String text, String toUserName) { + if (text == null) { + return; + } + LOG.info(String.format("发送消息 %s: %s", toUserName, text)); + webWxSendMsg(1, text, toUserName); + } + + /** + * 根据ID发送文本消息 + * + * @author https://github.com/yaphone + * @date 2017年5月6日 上午11:45:51 + * @param text + * @param id + */ + public static void sendMsgById(String text, String id) { + if (text == null) { + return; + } + sendMsg(text, id); + } + + /** + * 根据NickName发送文本消息 + * + * @author https://github.com/yaphone + * @date 2017年5月4日 下午11:17:38 + * @param text + * @param nickName + */ + public static boolean sendMsgByNickName(String text, String nickName) { + if (nickName != null) { + String toUserName = WechatTools.getUserNameByNickName(nickName); + if (toUserName != null) { + webWxSendMsg(1, text, toUserName); + return true; + } + } + return false; + + } + + /** + * 消息发送 + * + * @author https://github.com/yaphone + * @date 2017年4月23日 下午2:32:02 + * @param msgType + * @param content + * @param toUserName + */ + public static void webWxSendMsg(int msgType, String content, String toUserName) { + String url = String.format(URLEnum.WEB_WX_SEND_MSG.getUrl(), core.getLoginInfo().get("url")); + Map msgMap = new HashMap(); + msgMap.put("Type", msgType); + msgMap.put("Content", content); + msgMap.put("FromUserName", core.getUserName()); + msgMap.put("ToUserName", toUserName == null ? core.getUserName() : toUserName); + msgMap.put("LocalID", new Date().getTime() * 10); + msgMap.put("ClientMsgId", new Date().getTime() * 10); + Map paramMap = core.getParamMap(); + paramMap.put("Msg", msgMap); + paramMap.put("Scene", 0); + try { + String paramStr = JSON.toJSONString(paramMap); + HttpEntity entity = myHttpClient.doPost(url, paramStr); + EntityUtils.toString(entity, Consts.UTF_8); + } catch (Exception e) { + LOG.error("webWxSendMsg", e); + } + } + + /** + * 上传多媒体文件到 微信服务器,目前应该支持3种类型: 1. pic 直接显示,包含图片,表情 2.video 3.doc 显示为文件,包含PDF等 + * + * @author https://github.com/yaphone + * @date 2017年5月7日 上午12:41:13 + * @param filePath + * @return + */ + private static JSONObject webWxUploadMedia(String filePath) { + File f = new File(filePath); + if (!f.exists() && f.isFile()) { + LOG.info("file is not exist"); + return null; + } + String url = String.format(URLEnum.WEB_WX_UPLOAD_MEDIA.getUrl(), core.getLoginInfo().get("fileUrl")); + String mimeType = new MimetypesFileTypeMap().getContentType(f); + String mediaType = ""; + if (mimeType == null) { + mimeType = "text/plain"; + } else { + mediaType = mimeType.split("/")[0].equals("image") ? "pic" : "doc"; + } + String lastModifieDate = new SimpleDateFormat("yyyy MM dd HH:mm:ss").format(new Date()); + long fileSize = f.length(); + String passTicket = (String) core.getLoginInfo().get("pass_ticket"); + String clientMediaId = String.valueOf(new Date().getTime()) + + String.valueOf(new Random().nextLong()).substring(0, 4); + String webwxDataTicket = MyHttpClient.getCookie("webwx_data_ticket"); + if (webwxDataTicket == null) { + LOG.error("get cookie webwx_data_ticket error"); + return null; + } + + Map paramMap = core.getParamMap(); + + paramMap.put("ClientMediaId", clientMediaId); + paramMap.put("TotalLen", fileSize); + paramMap.put("StartPos", 0); + paramMap.put("DataLen", fileSize); + paramMap.put("MediaType", 4); + + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); + + builder.addTextBody("id", "WU_FILE_0", ContentType.TEXT_PLAIN); + builder.addTextBody("name", filePath, ContentType.TEXT_PLAIN); + builder.addTextBody("type", mimeType, ContentType.TEXT_PLAIN); + builder.addTextBody("lastModifieDate", lastModifieDate, ContentType.TEXT_PLAIN); + builder.addTextBody("size", String.valueOf(fileSize), ContentType.TEXT_PLAIN); + builder.addTextBody("mediatype", mediaType, ContentType.TEXT_PLAIN); + builder.addTextBody("uploadmediarequest", JSON.toJSONString(paramMap), ContentType.TEXT_PLAIN); + builder.addTextBody("webwx_data_ticket", webwxDataTicket, ContentType.TEXT_PLAIN); + builder.addTextBody("pass_ticket", passTicket, ContentType.TEXT_PLAIN); + builder.addBinaryBody("filename", f, ContentType.create(mimeType), filePath); + HttpEntity reqEntity = builder.build(); + HttpEntity entity = myHttpClient.doPostFile(url, reqEntity); + if (entity != null) { + try { + String result = EntityUtils.toString(entity, Consts.UTF_8); + return JSON.parseObject(result); + } catch (Exception e) { + LOG.error("webWxUploadMedia 错误: ", e); + } + + } + return null; + } + + /** + * 根据NickName发送图片消息 + * + * @author https://github.com/yaphone + * @date 2017年5月7日 下午10:32:45 + * @param nackName + * @return + */ + public static boolean sendPicMsgByNickName(String nickName, String filePath) { + String toUserName = WechatTools.getUserNameByNickName(nickName); + if (toUserName != null) { + return sendPicMsgByUserId(toUserName, filePath); + } + return false; + } + + /** + * 根据用户id发送图片消息 + * + * @author https://github.com/yaphone + * @date 2017年5月7日 下午10:34:24 + * @param nickName + * @param filePath + * @return + */ + public static boolean sendPicMsgByUserId(String userId, String filePath) { + JSONObject responseObj = webWxUploadMedia(filePath); + if (responseObj != null) { + String mediaId = responseObj.getString("MediaId"); + if (mediaId != null) { + return webWxSendMsgImg(userId, mediaId); + } + } + return false; + } + + /** + * 发送图片消息,内部调用 + * + * @author https://github.com/yaphone + * @date 2017年5月7日 下午10:38:55 + * @return + */ + private static boolean webWxSendMsgImg(String userId, String mediaId) { + String url = String.format("%s/webwxsendmsgimg?fun=async&f=json&pass_ticket=%s", core.getLoginInfo().get("url"), + core.getLoginInfo().get("pass_ticket")); + Map msgMap = new HashMap(); + msgMap.put("Type", 3); + msgMap.put("MediaId", mediaId); + msgMap.put("FromUserName", core.getUserSelf().getString("UserName")); + msgMap.put("ToUserName", userId); + String clientMsgId = String.valueOf(new Date().getTime()) + + String.valueOf(new Random().nextLong()).substring(1, 5); + msgMap.put("LocalID", clientMsgId); + msgMap.put("ClientMsgId", clientMsgId); + Map paramMap = core.getParamMap(); + paramMap.put("BaseRequest", core.getParamMap().get("BaseRequest")); + paramMap.put("Msg", msgMap); + String paramStr = JSON.toJSONString(paramMap); + HttpEntity entity = myHttpClient.doPost(url, paramStr); + if (entity != null) { + try { + String result = EntityUtils.toString(entity, Consts.UTF_8); + return JSON.parseObject(result).getJSONObject("BaseResponse").getInteger("Ret") == 0; + } catch (Exception e) { + LOG.error("webWxSendMsgImg 错误: ", e); + } + } + return false; + + } + + /** + * 根据用户id发送文件 + * + * @author https://github.com/yaphone + * @date 2017年5月7日 下午11:57:36 + * @param userId + * @param filePath + * @return + */ + public static boolean sendFileMsgByUserId(String userId, String filePath) { + String title = new File(filePath).getName(); + Map data = new HashMap(); + data.put("appid", Config.API_WXAPPID); + data.put("title", title); + data.put("totallen", ""); + data.put("attachid", ""); + data.put("type", "6"); // APPMSGTYPE_ATTACH + data.put("fileext", title.split("\\.")[1]); // 文件后缀 + JSONObject responseObj = webWxUploadMedia(filePath); + if (responseObj != null) { + data.put("totallen", responseObj.getString("StartPos")); + data.put("attachid", responseObj.getString("MediaId")); + } else { + LOG.error("sednFileMsgByUserId 错误: ", data); + } + return webWxSendAppMsg(userId, data); + } + + /** + * 根据用户昵称发送文件消息 + * + * @author https://github.com/yaphone + * @date 2017年5月10日 下午10:59:27 + * @param nickName + * @param filePath + * @return + */ + public static boolean sendFileMsgByNickName(String nickName, String filePath) { + String toUserName = WechatTools.getUserNameByNickName(nickName); + if (toUserName != null) { + return sendFileMsgByUserId(toUserName, filePath); + } + return false; + } + + /** + * 内部调用 + * + * @author https://github.com/yaphone + * @date 2017年5月10日 上午12:21:28 + * @param userId + * @param data + * @return + */ + private static boolean webWxSendAppMsg(String userId, Map data) { + String url = String.format("%s/webwxsendappmsg?fun=async&f=json&pass_ticket=%s", core.getLoginInfo().get("url"), + core.getLoginInfo().get("pass_ticket")); + String clientMsgId = String.valueOf(new Date().getTime()) + + String.valueOf(new Random().nextLong()).substring(1, 5); + String content = "" + data.get("title") + + "6" + + "" + data.get("totallen") + "" + data.get("attachid") + + "" + data.get("fileext") + ""; + Map msgMap = new HashMap(); + msgMap.put("Type", data.get("type")); + msgMap.put("Content", content); + msgMap.put("FromUserName", core.getUserSelf().getString("UserName")); + msgMap.put("ToUserName", userId); + msgMap.put("LocalID", clientMsgId); + msgMap.put("ClientMsgId", clientMsgId); + /* + * Map paramMap = new HashMap(); + * + * @SuppressWarnings("unchecked") Map> + * baseRequestMap = (Map>) + * core.getLoginInfo() .get("baseRequest"); paramMap.put("BaseRequest", + * baseRequestMap.get("BaseRequest")); + */ + + Map paramMap = core.getParamMap(); + paramMap.put("Msg", msgMap); + paramMap.put("Scene", 0); + String paramStr = JSON.toJSONString(paramMap); + HttpEntity entity = myHttpClient.doPost(url, paramStr); + if (entity != null) { + try { + String result = EntityUtils.toString(entity, Consts.UTF_8); + return JSON.parseObject(result).getJSONObject("BaseResponse").getInteger("Ret") == 0; + } catch (Exception e) { + LOG.error("错误: ", e); + } + } + return false; + } + + /** + * 被动添加好友 + * + * @date 2017年6月29日 下午10:08:43 + * @param msg + * @param accept + * true 接受 false 拒绝 + */ + public static void addFriend(BaseMsg msg, boolean accept) { + if (!accept) { // 不添加 + return; + } + int status = VerifyFriendEnum.ACCEPT.getCode(); // 接受好友请求 + RecommendInfo recommendInfo = msg.getRecommendInfo(); + String userName = recommendInfo.getUserName(); + String ticket = recommendInfo.getTicket(); + // 更新好友列表 + // TODO 此处需要更新好友列表 + // core.getContactList().add(msg.getJSONObject("RecommendInfo")); + + String url = String.format(URLEnum.WEB_WX_VERIFYUSER.getUrl(), core.getLoginInfo().get("url"), + String.valueOf(System.currentTimeMillis() / 3158L), core.getLoginInfo().get("pass_ticket")); + + List> verifyUserList = new ArrayList>(); + Map verifyUser = new HashMap(); + verifyUser.put("Value", userName); + verifyUser.put("VerifyUserTicket", ticket); + verifyUserList.add(verifyUser); + + List sceneList = new ArrayList(); + sceneList.add(33); + + JSONObject body = new JSONObject(); + body.put("BaseRequest", core.getParamMap().get("BaseRequest")); + body.put("Opcode", status); + body.put("VerifyUserListSize", 1); + body.put("VerifyUserList", verifyUserList); + body.put("VerifyContent", ""); + body.put("SceneListCount", 1); + body.put("SceneList", sceneList); + body.put("skey", core.getLoginInfo().get(StorageLoginInfoEnum.skey.getKey())); + + String result = null; + try { + String paramStr = JSON.toJSONString(body); + HttpEntity entity = myHttpClient.doPost(url, paramStr); + result = EntityUtils.toString(entity, Consts.UTF_8); + } catch (Exception e) { + LOG.error("webWxSendMsg", e); + } + + if (StringUtils.isBlank(result)) { + LOG.error("被动添加好友失败"); + } + + LOG.debug(result); + + } + +} diff --git a/src/main/java/cn/zhouyafeng/itchat4j/api/WechatTools.java b/src/main/java/cn/zhouyafeng/itchat4j/api/WechatTools.java new file mode 100644 index 0000000..d5b91a2 --- /dev/null +++ b/src/main/java/cn/zhouyafeng/itchat4j/api/WechatTools.java @@ -0,0 +1,215 @@ +package cn.zhouyafeng.itchat4j.api; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.http.Consts; +import org.apache.http.HttpEntity; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; + +import cn.zhouyafeng.itchat4j.core.Core; +import cn.zhouyafeng.itchat4j.utils.enums.StorageLoginInfoEnum; +import cn.zhouyafeng.itchat4j.utils.enums.URLEnum; + +/** + * 微信小工具,如获好友列表等 + * + * @author https://github.com/yaphone + * @date 创建时间:2017年5月4日 下午10:49:16 + * @version 1.0 + * + */ +public class WechatTools { + private static Logger LOG = LoggerFactory.getLogger(WechatTools.class); + + private static Core core = Core.getInstance(); + + /** + * 根据用户名发送文本消息 + * + * @author https://github.com/yaphone + * @date 2017年5月4日 下午10:43:14 + * @param msg + * @param toUserName + */ + public static void sendMsgByUserName(String msg, String toUserName) { + MessageTools.sendMsgById(msg, toUserName); + } + + /** + *

+ * 通过RealName获取本次UserName + *

+ *

+ * 如NickName为"yaphone",则获取UserName= + * "@1212d3356aea8285e5bbe7b91229936bc183780a8ffa469f2d638bf0d2e4fc63", + * 可通过UserName发送消息 + *

+ * + * @author https://github.com/yaphone + * @date 2017年5月4日 下午10:56:31 + * @param name + * @return + */ + public static String getUserNameByNickName(String nickName) { + for (JSONObject o : core.getContactList()) { + if (o.getString("NickName").equals(nickName)) { + return o.getString("UserName"); + } + } + return null; + } + + /** + * 返回好友昵称列表 + * + * @author https://github.com/yaphone + * @date 2017年5月4日 下午11:37:20 + * @return + */ + public static List getContactNickNameList() { + List contactNickNameList = new ArrayList(); + for (JSONObject o : core.getContactList()) { + contactNickNameList.add(o.getString("NickName")); + } + return contactNickNameList; + } + + /** + * 返回好友完整信息列表 + * + * @date 2017年6月26日 下午9:45:39 + * @return + */ + public static List getContactList() { + return core.getContactList(); + } + + /** + * 返回群列表 + * + * @author https://github.com/yaphone + * @date 2017年5月5日 下午9:55:21 + * @return + */ + public static List getGroupList() { + return core.getGroupList(); + } + + /** + * 获取群ID列表 + * + * @date 2017年6月21日 下午11:42:56 + * @return + */ + public static List getGroupIdList() { + return core.getGroupIdList(); + } + + /** + * 获取群NickName列表 + * + * @date 2017年6月21日 下午11:43:38 + * @return + */ + public static List getGroupNickNameList() { + return core.getGroupNickNameList(); + } + + /** + * 根据groupIdList返回群成员列表 + * + * @date 2017年6月13日 下午11:12:31 + * @param groupId + * @return + */ + public static JSONArray getMemberListByGroupId(String groupId) { + return core.getGroupMemeberMap().get(groupId); + } + + /** + * 退出微信 + * + * @author https://github.com/yaphone + * @date 2017年5月18日 下午11:56:54 + */ + public static void logout() { + webWxLogout(); + } + + private static boolean webWxLogout() { + String url = String.format(URLEnum.WEB_WX_LOGOUT.getUrl(), + core.getLoginInfo().get(StorageLoginInfoEnum.url.getKey())); + List params = new ArrayList(); + params.add(new BasicNameValuePair("redirect", "1")); + params.add(new BasicNameValuePair("type", "1")); + params.add( + new BasicNameValuePair("skey", (String) core.getLoginInfo().get(StorageLoginInfoEnum.skey.getKey()))); + try { + HttpEntity entity = core.getMyHttpClient().doGet(url, params, false, null); + String text = EntityUtils.toString(entity, Consts.UTF_8); // 无消息 + return true; + } catch (Exception e) { + LOG.debug(e.getMessage()); + } + return false; + } + + public static void setUserInfo() { + for (JSONObject o : core.getContactList()) { + core.getUserInfoMap().put(o.getString("NickName"), o); + core.getUserInfoMap().put(o.getString("UserName"), o); + } + } + + /** + * + * 根据用户昵称设置备注名称 + * + * @date 2017年5月27日 上午12:21:40 + * @param userName + * @param remName + */ + public static void remarkNameByNickName(String nickName, String remName) { + String url = String.format(URLEnum.WEB_WX_REMARKNAME.getUrl(), core.getLoginInfo().get("url"), + core.getLoginInfo().get(StorageLoginInfoEnum.pass_ticket.getKey())); + Map msgMap = new HashMap(); + Map msgMap_BaseRequest = new HashMap(); + msgMap.put("CmdId", 2); + msgMap.put("RemarkName", remName); + msgMap.put("UserName", core.getUserInfoMap().get(nickName).get("UserName")); + msgMap_BaseRequest.put("Uin", core.getLoginInfo().get(StorageLoginInfoEnum.wxuin.getKey())); + msgMap_BaseRequest.put("Sid", core.getLoginInfo().get(StorageLoginInfoEnum.wxsid.getKey())); + msgMap_BaseRequest.put("Skey", core.getLoginInfo().get(StorageLoginInfoEnum.skey.getKey())); + msgMap_BaseRequest.put("DeviceID", core.getLoginInfo().get(StorageLoginInfoEnum.deviceid.getKey())); + msgMap.put("BaseRequest", msgMap_BaseRequest); + try { + String paramStr = JSON.toJSONString(msgMap); + HttpEntity entity = core.getMyHttpClient().doPost(url, paramStr); + // String result = EntityUtils.toString(entity, Consts.UTF_8); + LOG.info("修改备注" + remName); + } catch (Exception e) { + LOG.error("remarkNameByUserName", e); + } + } + + /** + * 获取微信在线状态 + * + * @date 2017年6月16日 上午12:47:46 + * @return + */ + public static boolean getWechatStatus() { + return core.isAlive(); + } + +} diff --git a/src/main/java/cn/zhouyafeng/itchat4j/beans/AppInfo.java b/src/main/java/cn/zhouyafeng/itchat4j/beans/AppInfo.java new file mode 100644 index 0000000..06fd68c --- /dev/null +++ b/src/main/java/cn/zhouyafeng/itchat4j/beans/AppInfo.java @@ -0,0 +1,37 @@ +package cn.zhouyafeng.itchat4j.beans; + +import java.io.Serializable; + +/** + * AppInfo + * + * @author https://github.com/yaphone + * @date 创建时间:2017年7月3日 下午10:38:14 + * @version 1.0 + * + */ +public class AppInfo implements Serializable { + /** + * + */ + private static final long serialVersionUID = 1L; + private int type; + private String appId; + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + +} diff --git a/src/main/java/cn/zhouyafeng/itchat4j/beans/BaseMsg.java b/src/main/java/cn/zhouyafeng/itchat4j/beans/BaseMsg.java new file mode 100644 index 0000000..70f43c9 --- /dev/null +++ b/src/main/java/cn/zhouyafeng/itchat4j/beans/BaseMsg.java @@ -0,0 +1,293 @@ +package cn.zhouyafeng.itchat4j.beans; + +import java.io.Serializable; + +/** + * 收到的微信消息 + * + * @author https://github.com/yaphone + * @date 创建时间:2017年7月3日 下午10:28:06 + * @version 1.0 + * + */ +public class BaseMsg implements Serializable { + /** + * + */ + private static final long serialVersionUID = 1L; + private int subMsgType; + private int voiceLength; + private String fileName; + private int imgHeight; + private String toUserName; + private int hasProductId; + private int imgStatus; + private String url; + private int imgWidth; + private int forwardFlag; + private int status; + private String Ticket; + /** 推荐消息报文 **/ + private RecommendInfo recommendInfo; + private long createTime; + private String newMsgId; + /** 文本消息内容 **/ + private String text; + /** 消息类型 **/ + private int msgType; + /** 是否为群消息 **/ + private boolean groupMsg; + private String msgId; + private int statusNotifyCode; + private AppInfo appInfo; + private int appMsgType; + private String Type; + private int playLength; + private String mediaId; + private String content; + private String statusNotifyUserName; + /** 消息发送者ID **/ + private String fromUserName; + private String oriContent; + private String fileSize; + + public int getSubMsgType() { + return subMsgType; + } + + public void setSubMsgType(int subMsgType) { + this.subMsgType = subMsgType; + } + + public int getVoiceLength() { + return voiceLength; + } + + public void setVoiceLength(int voiceLength) { + this.voiceLength = voiceLength; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public int getImgHeight() { + return imgHeight; + } + + public void setImgHeight(int imgHeight) { + this.imgHeight = imgHeight; + } + + public String getToUserName() { + return toUserName; + } + + public void setToUserName(String toUserName) { + this.toUserName = toUserName; + } + + public int getHasProductId() { + return hasProductId; + } + + public void setHasProductId(int hasProductId) { + this.hasProductId = hasProductId; + } + + public int getImgStatus() { + return imgStatus; + } + + public void setImgStatus(int imgStatus) { + this.imgStatus = imgStatus; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public int getImgWidth() { + return imgWidth; + } + + public void setImgWidth(int imgWidth) { + this.imgWidth = imgWidth; + } + + public int getForwardFlag() { + return forwardFlag; + } + + public void setForwardFlag(int forwardFlag) { + this.forwardFlag = forwardFlag; + } + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public String getTicket() { + return Ticket; + } + + public void setTicket(String ticket) { + Ticket = ticket; + } + + public RecommendInfo getRecommendInfo() { + return recommendInfo; + } + + public void setRecommendInfo(RecommendInfo recommendInfo) { + this.recommendInfo = recommendInfo; + } + + public long getCreateTime() { + return createTime; + } + + public void setCreateTime(long createTime) { + this.createTime = createTime; + } + + public String getNewMsgId() { + return newMsgId; + } + + public void setNewMsgId(String newMsgId) { + this.newMsgId = newMsgId; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public int getMsgType() { + return msgType; + } + + public void setMsgType(int msgType) { + this.msgType = msgType; + } + + public boolean isGroupMsg() { + return groupMsg; + } + + public void setGroupMsg(boolean groupMsg) { + this.groupMsg = groupMsg; + } + + public String getMsgId() { + return msgId; + } + + public void setMsgId(String msgId) { + this.msgId = msgId; + } + + public int getStatusNotifyCode() { + return statusNotifyCode; + } + + public void setStatusNotifyCode(int statusNotifyCode) { + this.statusNotifyCode = statusNotifyCode; + } + + public AppInfo getAppInfo() { + return appInfo; + } + + public void setAppInfo(AppInfo appInfo) { + this.appInfo = appInfo; + } + + public int getAppMsgType() { + return appMsgType; + } + + public void setAppMsgType(int appMsgType) { + this.appMsgType = appMsgType; + } + + public String getType() { + return Type; + } + + public void setType(String type) { + Type = type; + } + + public int getPlayLength() { + return playLength; + } + + public void setPlayLength(int playLength) { + this.playLength = playLength; + } + + public String getMediaId() { + return mediaId; + } + + public void setMediaId(String mediaId) { + this.mediaId = mediaId; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getStatusNotifyUserName() { + return statusNotifyUserName; + } + + public void setStatusNotifyUserName(String statusNotifyUserName) { + this.statusNotifyUserName = statusNotifyUserName; + } + + public String getFromUserName() { + return fromUserName; + } + + public void setFromUserName(String fromUserName) { + this.fromUserName = fromUserName; + } + + public String getOriContent() { + return oriContent; + } + + public void setOriContent(String oriContent) { + this.oriContent = oriContent; + } + + public String getFileSize() { + return fileSize; + } + + public void setFileSize(String fileSize) { + this.fileSize = fileSize; + } +} diff --git a/src/main/java/cn/zhouyafeng/itchat4j/beans/RecommendInfo.java b/src/main/java/cn/zhouyafeng/itchat4j/beans/RecommendInfo.java new file mode 100644 index 0000000..7075201 --- /dev/null +++ b/src/main/java/cn/zhouyafeng/itchat4j/beans/RecommendInfo.java @@ -0,0 +1,146 @@ +package cn.zhouyafeng.itchat4j.beans; + +import java.io.Serializable; + +/** + * RecommendInfo + * + * @author https://github.com/yaphone + * @date 创建时间:2017年7月3日 下午10:35:14 + * @version 1.0 + * + */ +public class RecommendInfo implements Serializable { + /** + * + */ + private static final long serialVersionUID = 1L; + + private String ticket; + private String userName; + private int sex; + private int attrStatus; + private String city; + private String nickName; + private int scene; + private String province; + private String content; + private String alias; + private String signature; + private int opCode; + private int qQNum; + private int verifyFlag; + + public String getTicket() { + return ticket; + } + + public void setTicket(String ticket) { + this.ticket = ticket; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public int getSex() { + return sex; + } + + public void setSex(int sex) { + this.sex = sex; + } + + public int getAttrStatus() { + return attrStatus; + } + + public void setAttrStatus(int attrStatus) { + this.attrStatus = attrStatus; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getNickName() { + return nickName; + } + + public void setNickName(String nickName) { + this.nickName = nickName; + } + + public int getScene() { + return scene; + } + + public void setScene(int scene) { + this.scene = scene; + } + + public String getProvince() { + return province; + } + + public void setProvince(String province) { + this.province = province; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getAlias() { + return alias; + } + + public void setAlias(String alias) { + this.alias = alias; + } + + public String getSignature() { + return signature; + } + + public void setSignature(String signature) { + this.signature = signature; + } + + public int getOpCode() { + return opCode; + } + + public void setOpCode(int opCode) { + this.opCode = opCode; + } + + public int getqQNum() { + return qQNum; + } + + public void setqQNum(int qQNum) { + this.qQNum = qQNum; + } + + public int getVerifyFlag() { + return verifyFlag; + } + + public void setVerifyFlag(int verifyFlag) { + this.verifyFlag = verifyFlag; + } + +} diff --git a/src/main/java/cn/zhouyafeng/itchat4j/controller/LoginController.java b/src/main/java/cn/zhouyafeng/itchat4j/controller/LoginController.java new file mode 100644 index 0000000..c73df12 --- /dev/null +++ b/src/main/java/cn/zhouyafeng/itchat4j/controller/LoginController.java @@ -0,0 +1,88 @@ +package cn.zhouyafeng.itchat4j.controller; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import cn.zhouyafeng.itchat4j.api.WechatTools; +import cn.zhouyafeng.itchat4j.core.Core; +import cn.zhouyafeng.itchat4j.service.ILoginService; +import cn.zhouyafeng.itchat4j.service.impl.LoginServiceImpl; +import cn.zhouyafeng.itchat4j.thread.CheckLoginStatusThread; +import cn.zhouyafeng.itchat4j.utils.SleepUtils; +import cn.zhouyafeng.itchat4j.utils.tools.CommonTools; + +/** + * 登陆控制器 + * + * @author https://github.com/yaphone + * @date 创建时间:2017年5月13日 下午12:56:07 + * @version 1.0 + * + */ +public class LoginController { + private static Logger LOG = LoggerFactory.getLogger(LoginController.class); + private ILoginService loginService = new LoginServiceImpl(); + private static Core core = Core.getInstance(); + + public void login(String qrPath) { + if (core.isAlive()) { // 已登陆 + LOG.info("itchat4j已登陆"); + return; + } + while (true) { + for (int count = 0; count <= 10; count++) { + LOG.info("获取UUID"); + while (loginService.getUuid() == null) { + LOG.info("1. 获取微信UUID"); + while (loginService.getUuid() == null) { + LOG.warn("1.1. 获取微信UUID失败,两秒后重新获取"); + SleepUtils.sleep(2000); + } + } + LOG.info("2. 获取登陆二维码图片"); + if (loginService.getQR(qrPath)) { + break; + } else if (count == 10) { + LOG.error("2.2. 获取登陆二维码图片失败,系统退出"); + System.exit(0); + } + } + LOG.info("3. 请扫描二维码图片,并在手机上确认"); + if (!core.isAlive()) { + loginService.login(); + core.setAlive(true); + LOG.info(("登陆成功")); + break; + } + LOG.info("4. 登陆超时,请重新扫描二维码图片"); + } + + LOG.info("5. 登陆成功,微信初始化"); + if (!loginService.webWxInit()) { + LOG.info("6. 微信初始化异常"); + System.exit(0); + } + + LOG.info("6. 开启微信状态通知"); + loginService.wxStatusNotify(); + + LOG.info("7. 清除。。。。"); + CommonTools.clearScreen(); + LOG.info(String.format("欢迎回来, %s", core.getNickName())); + + LOG.info("8. 开始接收消息"); + loginService.startReceiving(); + + LOG.info("9. 获取联系人信息"); + loginService.webWxGetContact(); + + LOG.info("10. 获取群好友及群好友列表"); + loginService.WebWxBatchGetContact(); + + LOG.info("11. 缓存本次登陆好友相关消息"); + WechatTools.setUserInfo(); // 登陆成功后缓存本次登陆好友相关消息(NickName, UserName) + + LOG.info("12.开启微信状态检测线程"); + new Thread(new CheckLoginStatusThread()).start(); + } +} diff --git a/src/main/java/cn/zhouyafeng/itchat4j/core/Core.java b/src/main/java/cn/zhouyafeng/itchat4j/core/Core.java new file mode 100644 index 0000000..2e70785 --- /dev/null +++ b/src/main/java/cn/zhouyafeng/itchat4j/core/Core.java @@ -0,0 +1,276 @@ +package cn.zhouyafeng.itchat4j.core; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; + +import cn.zhouyafeng.itchat4j.beans.BaseMsg; +import cn.zhouyafeng.itchat4j.utils.MyHttpClient; +import cn.zhouyafeng.itchat4j.utils.enums.parameters.BaseParaEnum; + +/** + * 核心存储类,全局只保存一份,单例模式 + * + * @author https://github.com/yaphone + * @date 创建时间:2017年4月23日 下午2:33:56 + * @version 1.0 + * + */ +public class Core { + + private static Core instance; + + private Core() { + + } + + public static Core getInstance() { + if (instance == null) { + synchronized (Core.class) { + instance = new Core(); + } + } + return instance; + } + + boolean alive = false; + private int memberCount = 0; + + private String indexUrl; + + private String userName; + private String nickName; + private List msgList = new ArrayList(); + + private JSONObject userSelf; // 登陆账号自身信息 + private List memberList = new ArrayList(); // 好友+群聊+公众号+特殊账号 + private List contactList = new ArrayList();// 好友 + private List groupList = new ArrayList();; // 群 + private Map groupMemeberMap = new HashMap(); // 群聊成员字典 + private List publicUsersList = new ArrayList();;// 公众号/服务号 + private List specialUsersList = new ArrayList();;// 特殊账号 + private List groupIdList = new ArrayList(); // 群ID列表 + private List groupNickNameList = new ArrayList(); // 群NickName列表 + + private Map userInfoMap = new HashMap(); + + Map loginInfo = new HashMap(); + // CloseableHttpClient httpClient = HttpClients.createDefault(); + MyHttpClient myHttpClient = MyHttpClient.getInstance(); + String uuid = null; + + boolean useHotReload = false; + String hotReloadDir = "itchat.pkl"; + int receivingRetryCount = 5; + + private long lastNormalRetcodeTime; // 最后一次收到正常retcode的时间,秒为单位 + + /** + * 请求参数 + */ + public Map getParamMap() { + return new HashMap(1) { + /** + * + */ + private static final long serialVersionUID = 1L; + + { + Map map = new HashMap(); + for (BaseParaEnum baseRequest : BaseParaEnum.values()) { + map.put(baseRequest.para(), getLoginInfo().get(baseRequest.value()).toString()); + } + put("BaseRequest", map); + } + }; + } + + public boolean isAlive() { + return alive; + } + + public void setAlive(boolean alive) { + this.alive = alive; + } + + public List getMemberList() { + return memberList; + } + + public void setMemberList(List memberList) { + this.memberList = memberList; + } + + public Map getLoginInfo() { + return loginInfo; + } + + public void setLoginInfo(Map loginInfo) { + this.loginInfo = loginInfo; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public int getMemberCount() { + return memberCount; + } + + public void setMemberCount(int memberCount) { + this.memberCount = memberCount; + } + + public boolean isUseHotReload() { + return useHotReload; + } + + public void setUseHotReload(boolean useHotReload) { + this.useHotReload = useHotReload; + } + + public String getHotReloadDir() { + return hotReloadDir; + } + + public void setHotReloadDir(String hotReloadDir) { + this.hotReloadDir = hotReloadDir; + } + + public int getReceivingRetryCount() { + return receivingRetryCount; + } + + public void setReceivingRetryCount(int receivingRetryCount) { + this.receivingRetryCount = receivingRetryCount; + } + + public MyHttpClient getMyHttpClient() { + return myHttpClient; + } + + public List getMsgList() { + return msgList; + } + + public void setMsgList(List msgList) { + this.msgList = msgList; + } + + public void setMyHttpClient(MyHttpClient myHttpClient) { + this.myHttpClient = myHttpClient; + } + + public List getGroupIdList() { + return groupIdList; + } + + public void setGroupIdList(List groupIdList) { + this.groupIdList = groupIdList; + } + + public List getContactList() { + return contactList; + } + + public void setContactList(List contactList) { + this.contactList = contactList; + } + + public List getGroupList() { + return groupList; + } + + public void setGroupList(List groupList) { + this.groupList = groupList; + } + + public List getPublicUsersList() { + return publicUsersList; + } + + public void setPublicUsersList(List publicUsersList) { + this.publicUsersList = publicUsersList; + } + + public List getSpecialUsersList() { + return specialUsersList; + } + + public void setSpecialUsersList(List specialUsersList) { + this.specialUsersList = specialUsersList; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getNickName() { + return nickName; + } + + public void setNickName(String nickName) { + this.nickName = nickName; + } + + public JSONObject getUserSelf() { + return userSelf; + } + + public void setUserSelf(JSONObject userSelf) { + this.userSelf = userSelf; + } + + public Map getUserInfoMap() { + return userInfoMap; + } + + public void setUserInfoMap(Map userInfoMap) { + this.userInfoMap = userInfoMap; + } + + public synchronized long getLastNormalRetcodeTime() { + return lastNormalRetcodeTime; + } + + public synchronized void setLastNormalRetcodeTime(long lastNormalRetcodeTime) { + this.lastNormalRetcodeTime = lastNormalRetcodeTime; + } + + public List getGroupNickNameList() { + return groupNickNameList; + } + + public void setGroupNickNameList(List groupNickNameList) { + this.groupNickNameList = groupNickNameList; + } + + public Map getGroupMemeberMap() { + return groupMemeberMap; + } + + public void setGroupMemeberMap(Map groupMemeberMap) { + this.groupMemeberMap = groupMemeberMap; + } + + public String getIndexUrl() { + return indexUrl; + } + + public void setIndexUrl(String indexUrl) { + this.indexUrl = indexUrl; + } + +} diff --git a/src/main/java/cn/zhouyafeng/itchat4j/core/MsgCenter.java b/src/main/java/cn/zhouyafeng/itchat4j/core/MsgCenter.java new file mode 100644 index 0000000..6482406 --- /dev/null +++ b/src/main/java/cn/zhouyafeng/itchat4j/core/MsgCenter.java @@ -0,0 +1,170 @@ +package cn.zhouyafeng.itchat4j.core; + +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; + +import cn.zhouyafeng.itchat4j.api.MessageTools; +import cn.zhouyafeng.itchat4j.beans.BaseMsg; +import cn.zhouyafeng.itchat4j.face.IMsgHandlerFace; +import cn.zhouyafeng.itchat4j.utils.enums.MsgCodeEnum; +import cn.zhouyafeng.itchat4j.utils.enums.MsgTypeEnum; +import cn.zhouyafeng.itchat4j.utils.tools.CommonTools; + +/** + * 消息处理中心 + * + * @author https://github.com/yaphone + * @date 创建时间:2017年5月14日 下午12:47:50 + * @version 1.0 + * + */ +public class MsgCenter { + private static Logger LOG = LoggerFactory.getLogger(MsgCenter.class); + + private static Core core = Core.getInstance(); + + /** + * 接收消息,放入队列 + * + * @author https://github.com/yaphone + * @date 2017年4月23日 下午2:30:48 + * @param msgList + * @return + */ + public static JSONArray produceMsg(JSONArray msgList) { + JSONArray result = new JSONArray(); + for (int i = 0; i < msgList.size(); i++) { + JSONObject msg = new JSONObject(); + JSONObject m = msgList.getJSONObject(i); + m.put("groupMsg", false);// 是否是群消息 + if (m.getString("FromUserName").contains("@@") || m.getString("ToUserName").contains("@@")) { // 群聊消息 + if (m.getString("FromUserName").contains("@@") + && !core.getGroupIdList().contains(m.getString("FromUserName"))) { + core.getGroupIdList().add((m.getString("FromUserName"))); + } else if (m.getString("ToUserName").contains("@@") + && !core.getGroupIdList().contains(m.getString("ToUserName"))) { + core.getGroupIdList().add((m.getString("ToUserName"))); + } + // 群消息与普通消息不同的是在其消息体(Content)中会包含发送者id及":
"消息,这里需要处理一下,去掉多余信息,只保留消息内容 + if (m.getString("Content").contains("
")) { + String content = m.getString("Content").substring(m.getString("Content").indexOf("
") + 5); + m.put("Content", content); + m.put("groupMsg", true); + } + } else { + CommonTools.msgFormatter(m, "Content"); + } + if (m.getInteger("MsgType").equals(MsgCodeEnum.MSGTYPE_TEXT.getCode())) { // words + // 文本消息 + if (m.getString("Url").length() != 0) { + String regEx = "(.+?\\(.+?\\))"; + Matcher matcher = CommonTools.getMatcher(regEx, m.getString("Content")); + String data = "Map"; + if (matcher.find()) { + data = matcher.group(1); + } + msg.put("Type", "Map"); + msg.put("Text", data); + } else { + msg.put("Type", MsgTypeEnum.TEXT.getType()); + msg.put("Text", m.getString("Content")); + } + m.put("Type", msg.getString("Type")); + m.put("Text", msg.getString("Text")); + } else if (m.getInteger("MsgType").equals(MsgCodeEnum.MSGTYPE_IMAGE.getCode()) + || m.getInteger("MsgType").equals(MsgCodeEnum.MSGTYPE_EMOTICON.getCode())) { // 图片消息 + m.put("Type", MsgTypeEnum.PIC.getType()); + } else if (m.getInteger("MsgType").equals(MsgCodeEnum.MSGTYPE_VOICE.getCode())) { // 语音消息 + m.put("Type", MsgTypeEnum.VOICE.getType()); + } else if (m.getInteger("MsgType").equals(MsgCodeEnum.MSGTYPE_VERIFYMSG.getCode())) {// friends + // 好友确认消息 + // MessageTools.addFriend(core, userName, 3, ticket); // 确认添加好友 + m.put("Type", MsgTypeEnum.VERIFYMSG.getType()); + + } else if (m.getInteger("MsgType").equals(MsgCodeEnum.MSGTYPE_SHARECARD.getCode())) { // 共享名片 + m.put("Type", MsgTypeEnum.NAMECARD.getType()); + + } else if (m.getInteger("MsgType").equals(MsgCodeEnum.MSGTYPE_VIDEO.getCode()) + || m.getInteger("MsgType").equals(MsgCodeEnum.MSGTYPE_MICROVIDEO.getCode())) {// viedo + m.put("Type", MsgTypeEnum.VIEDO.getType()); + } else if (m.getInteger("MsgType").equals(MsgCodeEnum.MSGTYPE_MEDIA.getCode())) { // 多媒体消息 + m.put("Type", MsgTypeEnum.MEDIA.getType()); + } else if (m.getInteger("MsgType").equals(MsgCodeEnum.MSGTYPE_STATUSNOTIFY.getCode())) {// phone + // init + // 微信初始化消息 + + } else if (m.getInteger("MsgType").equals(MsgCodeEnum.MSGTYPE_SYS.getCode())) {// 系统消息 + m.put("Type", MsgTypeEnum.SYS.getType()); + } else if (m.getInteger("MsgType").equals(MsgCodeEnum.MSGTYPE_RECALLED.getCode())) { // 撤回消息 + + } else { + LOG.info("Useless msg"); + } + LOG.info("收到消息一条,来自: " + m.getString("FromUserName")); + result.add(m); + } + return result; + } + + /** + * 消息处理 + * + * @author https://github.com/yaphone + * @date 2017年5月14日 上午10:52:34 + * @param msgHandler + */ + public static void handleMsg(IMsgHandlerFace msgHandler) { + while (true) { + if (core.getMsgList().size() > 0 && core.getMsgList().get(0).getContent() != null) { + if (core.getMsgList().get(0).getContent().length() > 0) { + BaseMsg msg = core.getMsgList().get(0); + if (msg.getType() != null) { + try { + if (msg.getType().equals(MsgTypeEnum.TEXT.getType())) { + String result = msgHandler.textMsgHandle(msg); + MessageTools.sendMsgById(result, core.getMsgList().get(0).getFromUserName()); + } else if (msg.getType().equals(MsgTypeEnum.PIC.getType())) { + + String result = msgHandler.picMsgHandle(msg); + MessageTools.sendMsgById(result, core.getMsgList().get(0).getFromUserName()); + } else if (msg.getType().equals(MsgTypeEnum.VOICE.getType())) { + String result = msgHandler.voiceMsgHandle(msg); + MessageTools.sendMsgById(result, core.getMsgList().get(0).getFromUserName()); + } else if (msg.getType().equals(MsgTypeEnum.VIEDO.getType())) { + String result = msgHandler.viedoMsgHandle(msg); + MessageTools.sendMsgById(result, core.getMsgList().get(0).getFromUserName()); + } else if (msg.getType().equals(MsgTypeEnum.NAMECARD.getType())) { + String result = msgHandler.nameCardMsgHandle(msg); + MessageTools.sendMsgById(result, core.getMsgList().get(0).getFromUserName()); + } else if (msg.getType().equals(MsgTypeEnum.SYS.getType())) { // 系统消息 + msgHandler.sysMsgHandle(msg); + } else if (msg.getType().equals(MsgTypeEnum.VERIFYMSG.getType())) { // 确认添加好友消息 + String result = msgHandler.verifyAddFriendMsgHandle(msg); + MessageTools.sendMsgById(result, + core.getMsgList().get(0).getRecommendInfo().getUserName()); + } else if (msg.getType().equals(MsgTypeEnum.MEDIA.getType())) { // 多媒体消息 + String result = msgHandler.mediaMsgHandle(msg); + MessageTools.sendMsgById(result, core.getMsgList().get(0).getFromUserName()); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + core.getMsgList().remove(0); + } + try { + TimeUnit.MILLISECONDS.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + +} diff --git a/src/main/java/cn/zhouyafeng/itchat4j/face/IMsgHandlerFace.java b/src/main/java/cn/zhouyafeng/itchat4j/face/IMsgHandlerFace.java new file mode 100644 index 0000000..a3bd485 --- /dev/null +++ b/src/main/java/cn/zhouyafeng/itchat4j/face/IMsgHandlerFace.java @@ -0,0 +1,91 @@ +package cn.zhouyafeng.itchat4j.face; + +import cn.zhouyafeng.itchat4j.beans.BaseMsg; + +/** + * 消息处理接口 + * + * @author https://github.com/yaphone + * @date 创建时间:2017年4月20日 上午12:13:49 + * @version 1.0 + * + */ +public interface IMsgHandlerFace { + /** + * + * @author https://github.com/yaphone + * @date 2017年4月20日 上午12:15:00 + * @param msg + * @return + */ + public String textMsgHandle(BaseMsg msg); + + /** + * 处理图片消息 + * + * @author https://github.com/yaphone + * @date 2017年4月21日 下午11:07:06 + * @param msg + * @return + */ + public String picMsgHandle(BaseMsg msg); + + /** + * 处理声音消息 + * + * @author https://github.com/yaphone + * @date 2017年4月22日 上午12:09:44 + * @param msg + * @return + */ + public String voiceMsgHandle(BaseMsg msg); + + /** + * 处理小视频消息 + * + * @author https://github.com/yaphone + * @date 2017年4月23日 下午12:19:50 + * @param msg + * @return + */ + public String viedoMsgHandle(BaseMsg msg); + + /** + * 处理名片消息 + * + * @author https://github.com/yaphone + * @date 2017年5月1日 上午12:50:50 + * @param msg + * @return + */ + public String nameCardMsgHandle(BaseMsg msg); + + /** + * 处理系统消息 + * + * @author Relyn + * @date 2017年6月21日17:43:51 + * @param msg + * @return + */ + public void sysMsgHandle(BaseMsg msg); + + /** + * 处理确认添加好友消息 + * + * @date 2017年6月28日 下午10:15:30 + * @param msg + * @return + */ + public String verifyAddFriendMsgHandle(BaseMsg msg); + + /** + * 处理收到的文件消息 + * + * @date 2017年7月21日 下午11:59:14 + * @param msg + * @return + */ + public String mediaMsgHandle(BaseMsg msg); + +} diff --git a/src/main/java/cn/zhouyafeng/itchat4j/service/ILoginService.java b/src/main/java/cn/zhouyafeng/itchat4j/service/ILoginService.java new file mode 100644 index 0000000..d902f77 --- /dev/null +++ b/src/main/java/cn/zhouyafeng/itchat4j/service/ILoginService.java @@ -0,0 +1,82 @@ +package cn.zhouyafeng.itchat4j.service; + +/** + * 登陆服务接口 + * + * @author https://github.com/yaphone + * @date 创建时间:2017年5月13日 上午12:07:21 + * @version 1.0 + * + */ +public interface ILoginService { + + /** + * 登陆 + * + * @author https://github.com/yaphone + * @date 2017年5月13日 上午12:14:07 + * @return + */ + boolean login(); + + /** + * 获取UUID + * + * @author https://github.com/yaphone + * @date 2017年5月13日 上午12:21:40 + * @param qrPath + * @return + */ + String getUuid(); + + /** + * 获取二维码图片 + * + * @author https://github.com/yaphone + * @date 2017年5月13日 上午12:13:51 + * @param qrPath + * @return + */ + boolean getQR(String qrPath); + + /** + * web初始化 + * + * @author https://github.com/yaphone + * @date 2017年5月13日 上午12:14:13 + * @return + */ + boolean webWxInit(); + + /** + * 微信状态通知 + * + * @author https://github.com/yaphone + * @date 2017年5月13日 上午12:14:24 + */ + void wxStatusNotify(); + + /** + * 接收消息 + * + * @author https://github.com/yaphone + * @date 2017年5月13日 上午12:14:37 + */ + void startReceiving(); + + /** + * 获取微信联系人 + * + * @author https://github.com/yaphone + * @date 2017年5月13日 下午2:26:18 + */ + void webWxGetContact(); + + /** + * 批量获取联系人信息 + * + * @date 2017年6月22日 下午11:24:35 + */ + void WebWxBatchGetContact(); + +} diff --git a/src/main/java/cn/zhouyafeng/itchat4j/service/impl/LoginServiceImpl.java b/src/main/java/cn/zhouyafeng/itchat4j/service/impl/LoginServiceImpl.java new file mode 100644 index 0000000..0be1bf7 --- /dev/null +++ b/src/main/java/cn/zhouyafeng/itchat4j/service/impl/LoginServiceImpl.java @@ -0,0 +1,688 @@ +package cn.zhouyafeng.itchat4j.service.impl; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Random; +import java.util.regex.Matcher; + +import org.apache.http.Consts; +import org.apache.http.HttpEntity; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; + +import cn.zhouyafeng.itchat4j.beans.BaseMsg; +import cn.zhouyafeng.itchat4j.core.Core; +import cn.zhouyafeng.itchat4j.core.MsgCenter; +import cn.zhouyafeng.itchat4j.service.ILoginService; +import cn.zhouyafeng.itchat4j.utils.Config; +import cn.zhouyafeng.itchat4j.utils.MyHttpClient; +import cn.zhouyafeng.itchat4j.utils.SleepUtils; +import cn.zhouyafeng.itchat4j.utils.enums.ResultEnum; +import cn.zhouyafeng.itchat4j.utils.enums.RetCodeEnum; +import cn.zhouyafeng.itchat4j.utils.enums.StorageLoginInfoEnum; +import cn.zhouyafeng.itchat4j.utils.enums.URLEnum; +import cn.zhouyafeng.itchat4j.utils.enums.parameters.BaseParaEnum; +import cn.zhouyafeng.itchat4j.utils.enums.parameters.LoginParaEnum; +import cn.zhouyafeng.itchat4j.utils.enums.parameters.StatusNotifyParaEnum; +import cn.zhouyafeng.itchat4j.utils.enums.parameters.UUIDParaEnum; +import cn.zhouyafeng.itchat4j.utils.tools.CommonTools; + +/** + * 登陆服务实现类 + * + * @author https://github.com/yaphone + * @date 创建时间:2017年5月13日 上午12:09:35 + * @version 1.0 + * + */ +public class LoginServiceImpl implements ILoginService { + private static Logger LOG = LoggerFactory.getLogger(LoginServiceImpl.class); + + private Core core = Core.getInstance(); + private MyHttpClient httpClient = core.getMyHttpClient(); + + private MyHttpClient myHttpClient = core.getMyHttpClient(); + + public LoginServiceImpl() { + + } + + @Override + public boolean login() { + + boolean isLogin = false; + // 组装参数和URL + List params = new ArrayList(); + params.add(new BasicNameValuePair(LoginParaEnum.LOGIN_ICON.para(), LoginParaEnum.LOGIN_ICON.value())); + params.add(new BasicNameValuePair(LoginParaEnum.UUID.para(), core.getUuid())); + params.add(new BasicNameValuePair(LoginParaEnum.TIP.para(), LoginParaEnum.TIP.value())); + + // long time = 4000; + while (!isLogin) { + // SleepUtils.sleep(time += 1000); + long millis = System.currentTimeMillis(); + params.add(new BasicNameValuePair(LoginParaEnum.R.para(), String.valueOf(millis / 1579L))); + params.add(new BasicNameValuePair(LoginParaEnum._.para(), String.valueOf(millis))); + HttpEntity entity = httpClient.doGet(URLEnum.LOGIN_URL.getUrl(), params, true, null); + + try { + String result = EntityUtils.toString(entity); + String status = checklogin(result); + + if (ResultEnum.SUCCESS.getCode().equals(status)) { + processLoginInfo(result); // 处理结果 + isLogin = true; + core.setAlive(isLogin); + break; + } + if (ResultEnum.WAIT_CONFIRM.getCode().equals(status)) { + LOG.info("请点击微信确认按钮,进行登陆"); + } + + } catch (Exception e) { + LOG.error("微信登陆异常!", e); + } + } + return isLogin; + } + + @Override + public String getUuid() { + // 组装参数和URL + List params = new ArrayList(); + params.add(new BasicNameValuePair(UUIDParaEnum.APP_ID.para(), UUIDParaEnum.APP_ID.value())); + params.add(new BasicNameValuePair(UUIDParaEnum.FUN.para(), UUIDParaEnum.FUN.value())); + params.add(new BasicNameValuePair(UUIDParaEnum.LANG.para(), UUIDParaEnum.LANG.value())); + params.add(new BasicNameValuePair(UUIDParaEnum._.para(), String.valueOf(System.currentTimeMillis()))); + + HttpEntity entity = httpClient.doGet(URLEnum.UUID_URL.getUrl(), params, true, null); + + try { + String result = EntityUtils.toString(entity); + String regEx = "window.QRLogin.code = (\\d+); window.QRLogin.uuid = \"(\\S+?)\";"; + Matcher matcher = CommonTools.getMatcher(regEx, result); + if (matcher.find()) { + if ((ResultEnum.SUCCESS.getCode().equals(matcher.group(1)))) { + core.setUuid(matcher.group(2)); + } + } + } catch (Exception e) { + LOG.error(e.getMessage(), e); + } + + return core.getUuid(); + } + + @Override + public boolean getQR(String qrPath) { + qrPath = qrPath + File.separator + "QR.jpg"; + String qrUrl = URLEnum.QRCODE_URL.getUrl() + core.getUuid(); + HttpEntity entity = myHttpClient.doGet(qrUrl, null, true, null); + try { + OutputStream out = new FileOutputStream(qrPath); + byte[] bytes = EntityUtils.toByteArray(entity); + out.write(bytes); + out.flush(); + out.close(); + try { + CommonTools.printQr(qrPath); // 打开登陆二维码图片 + } catch (Exception e) { + LOG.info(e.getMessage()); + } + + } catch (Exception e) { + LOG.info(e.getMessage()); + return false; + } + + return true; + } + + @Override + public boolean webWxInit() { + core.setAlive(true); + core.setLastNormalRetcodeTime(System.currentTimeMillis()); + // 组装请求URL和参数 + String url = String.format(URLEnum.INIT_URL.getUrl(), + core.getLoginInfo().get(StorageLoginInfoEnum.url.getKey()), + String.valueOf(System.currentTimeMillis() / 3158L), + core.getLoginInfo().get(StorageLoginInfoEnum.pass_ticket.getKey())); + + Map paramMap = core.getParamMap(); + + // 请求初始化接口 + HttpEntity entity = httpClient.doPost(url, JSON.toJSONString(paramMap)); + try { + String result = EntityUtils.toString(entity, Consts.UTF_8); + JSONObject obj = JSON.parseObject(result); + + JSONObject user = obj.getJSONObject(StorageLoginInfoEnum.User.getKey()); + JSONObject syncKey = obj.getJSONObject(StorageLoginInfoEnum.SyncKey.getKey()); + + core.getLoginInfo().put(StorageLoginInfoEnum.InviteStartCount.getKey(), + obj.getInteger(StorageLoginInfoEnum.InviteStartCount.getKey())); + core.getLoginInfo().put(StorageLoginInfoEnum.SyncKey.getKey(), syncKey); + + JSONArray syncArray = syncKey.getJSONArray("List"); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < syncArray.size(); i++) { + sb.append(syncArray.getJSONObject(i).getString("Key") + "_" + + syncArray.getJSONObject(i).getString("Val") + "|"); + } + // 1_661706053|2_661706420|3_661706415|1000_1494151022| + String synckey = sb.toString(); + + // 1_661706053|2_661706420|3_661706415|1000_1494151022 + 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 + core.setUserName(user.getString("UserName")); + core.setNickName(user.getString("NickName")); + core.setUserSelf(obj.getJSONObject("User")); + + String chatSet = obj.getString("ChatSet"); + String[] chatSetArray = chatSet.split(","); + for (int i = 0; i < chatSetArray.length; i++) { + if (chatSetArray[i].indexOf("@@") != -1) { + // 更新GroupIdList + core.getGroupIdList().add(chatSetArray[i]); // + } + } + // JSONArray contactListArray = obj.getJSONArray("ContactList"); + // for (int i = 0; i < contactListArray.size(); i++) { + // JSONObject o = contactListArray.getJSONObject(i); + // if (o.getString("UserName").indexOf("@@") != -1) { + // core.getGroupIdList().add(o.getString("UserName")); // + // // 更新GroupIdList + // core.getGroupList().add(o); // 更新GroupList + // core.getGroupNickNameList().add(o.getString("NickName")); + // } + // } + } catch (Exception e) { + e.printStackTrace(); + return false; + } + return true; + } + + @Override + public void wxStatusNotify() { + // 组装请求URL和参数 + String url = String.format(URLEnum.STATUS_NOTIFY_URL.getUrl(), + core.getLoginInfo().get(StorageLoginInfoEnum.pass_ticket.getKey())); + + Map paramMap = core.getParamMap(); + paramMap.put(StatusNotifyParaEnum.CODE.para(), StatusNotifyParaEnum.CODE.value()); + paramMap.put(StatusNotifyParaEnum.FROM_USERNAME.para(), core.getUserName()); + paramMap.put(StatusNotifyParaEnum.TO_USERNAME.para(), core.getUserName()); + paramMap.put(StatusNotifyParaEnum.CLIENT_MSG_ID.para(), System.currentTimeMillis()); + String paramStr = JSON.toJSONString(paramMap); + + try { + HttpEntity entity = httpClient.doPost(url, paramStr); + EntityUtils.toString(entity, Consts.UTF_8); + } catch (Exception e) { + LOG.error("微信状态通知接口失败!", e); + } + + } + + @Override + public void startReceiving() { + core.setAlive(true); + new Thread(new Runnable() { + int retryCount = 0; + + @Override + public void run() { + while (core.isAlive()) { + try { + Map resultMap = syncCheck(); + LOG.info(JSONObject.toJSONString(resultMap)); + String retcode = resultMap.get("retcode"); + String selector = resultMap.get("selector"); + if (retcode.equals(RetCodeEnum.UNKOWN.getCode())) { + LOG.info(RetCodeEnum.UNKOWN.getType()); + continue; + } else if (retcode.equals(RetCodeEnum.LOGIN_OUT.getCode())) { // 退出 + LOG.info(RetCodeEnum.LOGIN_OUT.getType()); + break; + } else if (retcode.equals(RetCodeEnum.LOGIN_OTHERWHERE.getCode())) { // 其它地方登陆 + LOG.info(RetCodeEnum.LOGIN_OTHERWHERE.getType()); + break; + } else if (retcode.equals(RetCodeEnum.MOBILE_LOGIN_OUT.getCode())) { // 移动端退出 + LOG.info(RetCodeEnum.MOBILE_LOGIN_OUT.getType()); + break; + } else if (retcode.equals(RetCodeEnum.NORMAL.getCode())) { + core.setLastNormalRetcodeTime(System.currentTimeMillis()); // 最后收到正常报文时间 + JSONObject msgObj = webWxSync(); + if (selector.equals("2")) { + if (msgObj != null) { + try { + JSONArray msgList = new JSONArray(); + msgList = msgObj.getJSONArray("AddMsgList"); + msgList = MsgCenter.produceMsg(msgList); + for (int j = 0; j < msgList.size(); j++) { + BaseMsg baseMsg = JSON.toJavaObject(msgList.getJSONObject(j), + BaseMsg.class); + core.getMsgList().add(baseMsg); + } + } catch (Exception e) { + LOG.info(e.getMessage()); + } + } + } else if (selector.equals("7")) { + webWxSync(); + } else if (selector.equals("4")) { + continue; + } else if (selector.equals("3")) { + continue; + } else if (selector.equals("6")) { + if (msgObj != null) { + try { + JSONArray msgList = new JSONArray(); + msgList = msgObj.getJSONArray("AddMsgList"); + JSONArray modContactList = msgObj.getJSONArray("ModContactList"); // 存在删除或者新增的好友信息 + msgList = MsgCenter.produceMsg(msgList); + for (int j = 0; j < msgList.size(); j++) { + JSONObject userInfo = modContactList.getJSONObject(j); + // 存在主动加好友之后的同步联系人到本地 + core.getContactList().add(userInfo); + } + } catch (Exception e) { + LOG.info(e.getMessage()); + } + } + + } + } else { + JSONObject obj = webWxSync(); + } + } catch (Exception e) { + LOG.info(e.getMessage()); + retryCount += 1; + if (core.getReceivingRetryCount() < retryCount) { + core.setAlive(false); + } else { + try { + Thread.sleep(1000); + } catch (InterruptedException e1) { + LOG.info(e.getMessage()); + } + } + } + + } + } + }).start(); + + } + + @Override + public void webWxGetContact() { + String url = String.format(URLEnum.WEB_WX_GET_CONTACT.getUrl(), + core.getLoginInfo().get(StorageLoginInfoEnum.url.getKey())); + Map paramMap = core.getParamMap(); + HttpEntity entity = httpClient.doPost(url, JSON.toJSONString(paramMap)); + + try { + String result = EntityUtils.toString(entity, Consts.UTF_8); + JSONObject fullFriendsJsonList = JSON.parseObject(result); + // 查看seq是否为0,0表示好友列表已全部获取完毕,若大于0,则表示好友列表未获取完毕,当前的字节数(断点续传) + long seq = 0; + long currentTime = 0L; + List params = new ArrayList(); + if (fullFriendsJsonList.get("Seq") != null) { + seq = fullFriendsJsonList.getLong("Seq"); + currentTime = new Date().getTime(); + } + core.setMemberCount(fullFriendsJsonList.getInteger(StorageLoginInfoEnum.MemberCount.getKey())); + JSONArray member = fullFriendsJsonList.getJSONArray(StorageLoginInfoEnum.MemberList.getKey()); + // 循环获取seq直到为0,即获取全部好友列表 ==0:好友获取完毕 >0:好友未获取完毕,此时seq为已获取的字节数 + while (seq > 0) { + // 设置seq传参 + params.add(new BasicNameValuePair("r", String.valueOf(currentTime))); + params.add(new BasicNameValuePair("seq", String.valueOf(seq))); + entity = httpClient.doGet(url, params, false, null); + + params.remove(new BasicNameValuePair("r", String.valueOf(currentTime))); + params.remove(new BasicNameValuePair("seq", String.valueOf(seq))); + + result = EntityUtils.toString(entity, Consts.UTF_8); + fullFriendsJsonList = JSON.parseObject(result); + + if (fullFriendsJsonList.get("Seq") != null) { + seq = fullFriendsJsonList.getLong("Seq"); + currentTime = new Date().getTime(); + } + + // 累加好友列表 + member.addAll(fullFriendsJsonList.getJSONArray(StorageLoginInfoEnum.MemberList.getKey())); + } + core.setMemberCount(member.size()); + for (Iterator iterator = member.iterator(); iterator.hasNext();) { + JSONObject o = (JSONObject) iterator.next(); + if ((o.getInteger("VerifyFlag") & 8) != 0) { // 公众号/服务号 + core.getPublicUsersList().add(o); + } else if (Config.API_SPECIAL_USER.contains(o.getString("UserName"))) { // 特殊账号 + core.getSpecialUsersList().add(o); + } else if (o.getString("UserName").indexOf("@@") != -1) { // 群聊 + if (!core.getGroupIdList().contains(o.getString("UserName"))) { + core.getGroupNickNameList().add(o.getString("NickName")); + core.getGroupIdList().add(o.getString("UserName")); + core.getGroupList().add(o); + } + } else if (o.getString("UserName").equals(core.getUserSelf().getString("UserName"))) { // 自己 + core.getContactList().remove(o); + } else { // 普通联系人 + core.getContactList().add(o); + } + } + return; + } catch (Exception e) { + LOG.error(e.getMessage(), e); + } + return; + } + + @Override + public void WebWxBatchGetContact() { + String url = String.format(URLEnum.WEB_WX_BATCH_GET_CONTACT.getUrl(), + core.getLoginInfo().get(StorageLoginInfoEnum.url.getKey()), new Date().getTime(), + core.getLoginInfo().get(StorageLoginInfoEnum.pass_ticket.getKey())); + Map paramMap = core.getParamMap(); + paramMap.put("Count", core.getGroupIdList().size()); + List> list = new ArrayList>(); + for (int i = 0; i < core.getGroupIdList().size(); i++) { + HashMap map = new HashMap(); + map.put("UserName", core.getGroupIdList().get(i)); + map.put("EncryChatRoomId", ""); + list.add(map); + } + paramMap.put("List", list); + HttpEntity entity = httpClient.doPost(url, JSON.toJSONString(paramMap)); + try { + String text = EntityUtils.toString(entity, Consts.UTF_8); + JSONObject obj = JSON.parseObject(text); + JSONArray contactList = obj.getJSONArray("ContactList"); + for (int i = 0; i < contactList.size(); i++) { // 群好友 + if (contactList.getJSONObject(i).getString("UserName").indexOf("@@") > -1) { // 群 + core.getGroupNickNameList().add(contactList.getJSONObject(i).getString("NickName")); // 更新群昵称列表 + core.getGroupList().add(contactList.getJSONObject(i)); // 更新群信息(所有)列表 + core.getGroupMemeberMap().put(contactList.getJSONObject(i).getString("UserName"), + contactList.getJSONObject(i).getJSONArray("MemberList")); // 更新群成员Map + } + } + } catch (Exception e) { + LOG.info(e.getMessage()); + } + } + + /** + * 检查登陆状态 + * + * @param result + * @return + */ + public String checklogin(String result) { + String regEx = "window.code=(\\d+)"; + Matcher matcher = CommonTools.getMatcher(regEx, result); + if (matcher.find()) { + return matcher.group(1); + } + return null; + } + + /** + * 处理登陆信息 + * + * @author https://github.com/yaphone + * @date 2017年4月9日 下午12:16:26 + * @param result + */ + private void processLoginInfo(String loginContent) { + String regEx = "window.redirect_uri=\"(\\S+)\";"; + Matcher matcher = CommonTools.getMatcher(regEx, loginContent); + if (matcher.find()) { + String originalUrl = matcher.group(1); + String url = originalUrl.substring(0, originalUrl.lastIndexOf('/')); // https://wx2.qq.com/cgi-bin/mmwebwx-bin + core.getLoginInfo().put("url", url); + Map> possibleUrlMap = this.getPossibleUrlMap(); + Iterator>> iterator = possibleUrlMap.entrySet().iterator(); + Entry> entry; + String fileUrl; + String syncUrl; + while (iterator.hasNext()) { + entry = iterator.next(); + String indexUrl = entry.getKey(); + fileUrl = "https://" + entry.getValue().get(0) + "/cgi-bin/mmwebwx-bin"; + syncUrl = "https://" + entry.getValue().get(1) + "/cgi-bin/mmwebwx-bin"; + if (core.getLoginInfo().get("url").toString().contains(indexUrl)) { + core.setIndexUrl(indexUrl); + core.getLoginInfo().put("fileUrl", fileUrl); + core.getLoginInfo().put("syncUrl", syncUrl); + break; + } + } + if (core.getLoginInfo().get("fileUrl") == null && core.getLoginInfo().get("syncUrl") == null) { + core.getLoginInfo().put("fileUrl", url); + core.getLoginInfo().put("syncUrl", url); + } + core.getLoginInfo().put("deviceid", "e" + String.valueOf(new Random().nextLong()).substring(1, 16)); // 生成15位随机数 + core.getLoginInfo().put("BaseRequest", new ArrayList()); + String text = ""; + + try { + Map header = new HashMap<>(); + header.put("client-version","2.0.0"); + header.put("extspam","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=="); + originalUrl = originalUrl +"&fun=new&version=v2&mod=desktop&lang=zh_CN"; + HttpEntity entity = myHttpClient.doGet(originalUrl, null, false, header); + text = EntityUtils.toString(entity); + } catch (Exception e) { + LOG.info(e.getMessage()); + return; + } + //add by 默非默 2017-08-01 22:28:09 + //如果登录被禁止时,则登录返回的message内容不为空,下面代码则判断登录内容是否为空,不为空则退出程序 + String msg = getLoginMessage(text); + if (!"".equals(msg)){ + LOG.info(msg); + System.exit(0); + } + Document doc = CommonTools.xmlParser(text); + if (doc != null) { + core.getLoginInfo().put(StorageLoginInfoEnum.skey.getKey(), + doc.getElementsByTagName(StorageLoginInfoEnum.skey.getKey()).item(0).getFirstChild() + .getNodeValue()); + core.getLoginInfo().put(StorageLoginInfoEnum.wxsid.getKey(), + doc.getElementsByTagName(StorageLoginInfoEnum.wxsid.getKey()).item(0).getFirstChild() + .getNodeValue()); + core.getLoginInfo().put(StorageLoginInfoEnum.wxuin.getKey(), + doc.getElementsByTagName(StorageLoginInfoEnum.wxuin.getKey()).item(0).getFirstChild() + .getNodeValue()); + core.getLoginInfo().put(StorageLoginInfoEnum.pass_ticket.getKey(), + doc.getElementsByTagName(StorageLoginInfoEnum.pass_ticket.getKey()).item(0).getFirstChild() + .getNodeValue()); + } + + } + } + + private Map> getPossibleUrlMap() { + Map> possibleUrlMap = new HashMap>(); + possibleUrlMap.put("wx.qq.com", new ArrayList() { + /** + * + */ + private static final long serialVersionUID = 1L; + + { + add("file.wx.qq.com"); + add("webpush.wx.qq.com"); + } + }); + + possibleUrlMap.put("wx2.qq.com", new ArrayList() { + /** + * + */ + private static final long serialVersionUID = 1L; + + { + add("file.wx2.qq.com"); + add("webpush.wx2.qq.com"); + } + }); + possibleUrlMap.put("wx8.qq.com", new ArrayList() { + /** + * + */ + private static final long serialVersionUID = 1L; + + { + add("file.wx8.qq.com"); + add("webpush.wx8.qq.com"); + } + }); + + possibleUrlMap.put("web2.wechat.com", new ArrayList() { + /** + * + */ + private static final long serialVersionUID = 1L; + + { + add("file.web2.wechat.com"); + add("webpush.web2.wechat.com"); + } + }); + possibleUrlMap.put("wechat.com", new ArrayList() { + /** + * + */ + private static final long serialVersionUID = 1L; + + { + add("file.web.wechat.com"); + add("webpush.web.wechat.com"); + } + }); + return possibleUrlMap; + } + + /** + * 同步消息 sync the messages + * + * @author https://github.com/yaphone + * @date 2017年5月12日 上午12:24:55 + * @return + */ + private JSONObject webWxSync() { + JSONObject result = null; + String url = String.format(URLEnum.WEB_WX_SYNC_URL.getUrl(), + core.getLoginInfo().get(StorageLoginInfoEnum.url.getKey()), + core.getLoginInfo().get(StorageLoginInfoEnum.wxsid.getKey()), + core.getLoginInfo().get(StorageLoginInfoEnum.skey.getKey()), + core.getLoginInfo().get(StorageLoginInfoEnum.pass_ticket.getKey())); + Map paramMap = core.getParamMap(); + paramMap.put(StorageLoginInfoEnum.SyncKey.getKey(), + core.getLoginInfo().get(StorageLoginInfoEnum.SyncKey.getKey())); + paramMap.put("rr", -new Date().getTime() / 1000); + String paramStr = JSON.toJSONString(paramMap); + try { + HttpEntity entity = myHttpClient.doPost(url, paramStr); + String text = EntityUtils.toString(entity, Consts.UTF_8); + JSONObject obj = JSON.parseObject(text); + if (obj.getJSONObject("BaseResponse").getInteger("Ret") != 0) { + result = null; + } else { + result = obj; + core.getLoginInfo().put(StorageLoginInfoEnum.SyncKey.getKey(), obj.getJSONObject("SyncCheckKey")); + JSONArray syncArray = obj.getJSONObject(StorageLoginInfoEnum.SyncKey.getKey()).getJSONArray("List"); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < syncArray.size(); i++) { + sb.append(syncArray.getJSONObject(i).getString("Key") + "_" + + syncArray.getJSONObject(i).getString("Val") + "|"); + } + String synckey = sb.toString(); + 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 + } + } catch (Exception e) { + LOG.info(e.getMessage()); + } + return result; + + } + + /** + * 检查是否有新消息 check whether there's a message + * + * @author https://github.com/yaphone + * @date 2017年4月16日 上午11:11:34 + * @return + * + */ + private Map syncCheck() { + Map resultMap = new HashMap(); + // 组装请求URL和参数 + String url = core.getLoginInfo().get(StorageLoginInfoEnum.syncUrl.getKey()) + URLEnum.SYNC_CHECK_URL.getUrl(); + List params = new ArrayList(); + for (BaseParaEnum baseRequest : BaseParaEnum.values()) { + params.add(new BasicNameValuePair(baseRequest.para().toLowerCase(), + core.getLoginInfo().get(baseRequest.value()).toString())); + } + params.add(new BasicNameValuePair("r", String.valueOf(new Date().getTime()))); + params.add(new BasicNameValuePair("synckey", (String) core.getLoginInfo().get("synckey"))); + params.add(new BasicNameValuePair("_", String.valueOf(new Date().getTime()))); + SleepUtils.sleep(7); + try { + HttpEntity entity = myHttpClient.doGet(url, params, true, null); + if (entity == null) { + resultMap.put("retcode", "9999"); + resultMap.put("selector", "9999"); + return resultMap; + } + String text = EntityUtils.toString(entity); + String regEx = "window.synccheck=\\{retcode:\"(\\d+)\",selector:\"(\\d+)\"\\}"; + Matcher matcher = CommonTools.getMatcher(regEx, text); + if (!matcher.find() || matcher.group(1).equals("2")) { + LOG.info(String.format("Unexpected sync check result: %s", text)); + } else { + resultMap.put("retcode", matcher.group(1)); + resultMap.put("selector", matcher.group(2)); + } + } catch (Exception e) { + e.printStackTrace(); + } + return resultMap; + } + + /** + * 解析登录返回的消息,如果成功登录,则message为空 + * @param result + * @return + */ + public String getLoginMessage(String result){ + String[] strArr = result.split(""); + String[] rs = strArr[1].split(""); + if (rs!=null && rs.length>1) { + return rs[0]; + } + return ""; + } +} diff --git a/src/main/java/cn/zhouyafeng/itchat4j/thread/CheckLoginStatusThread.java b/src/main/java/cn/zhouyafeng/itchat4j/thread/CheckLoginStatusThread.java new file mode 100644 index 0000000..382f3e7 --- /dev/null +++ b/src/main/java/cn/zhouyafeng/itchat4j/thread/CheckLoginStatusThread.java @@ -0,0 +1,38 @@ +package cn.zhouyafeng.itchat4j.thread; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import cn.zhouyafeng.itchat4j.core.Core; +import cn.zhouyafeng.itchat4j.utils.SleepUtils; + +/** + * 检查微信在线状态 + *

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

+ * + * @author https://github.com/yaphone + * @date 创建时间:2017年5月17日 下午10:53:15 + * @version 1.0 + * + */ +public class CheckLoginStatusThread implements Runnable { + private static Logger LOG = LoggerFactory.getLogger(CheckLoginStatusThread.class); + private Core core = Core.getInstance(); + + @Override + public void run() { + while (core.isAlive()) { + long t1 = System.currentTimeMillis(); // 秒为单位 + if (t1 - core.getLastNormalRetcodeTime() > 60 * 1000) { // 超过60秒,判为离线 + core.setAlive(false); + LOG.info("微信已离线"); + } + SleepUtils.sleep(10 * 1000); // 休眠10秒 + } + } + +} diff --git a/src/main/java/cn/zhouyafeng/itchat4j/utils/Config.java b/src/main/java/cn/zhouyafeng/itchat4j/utils/Config.java new file mode 100644 index 0000000..c64472b --- /dev/null +++ b/src/main/java/cn/zhouyafeng/itchat4j/utils/Config.java @@ -0,0 +1,74 @@ +package cn.zhouyafeng.itchat4j.utils; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; + +import cn.zhouyafeng.itchat4j.utils.enums.OsNameEnum; + +/** + * 配置信息 + * + * @author https://github.com/yaphone + * @date 创建时间:2017年4月23日 下午2:26:21 + * @version 1.0 + * + */ +public class Config { + + public static final String API_WXAPPID = "API_WXAPPID"; + + public static final String picDir = "D://itchat4j"; + public static final String VERSION = "1.2.18"; + public static final String BASE_URL = "https://login.weixin.qq.com"; + public static final String OS = ""; + public static final String DIR = ""; + public static final String DEFAULT_QR = "QR.jpg"; + 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"; + + public static final ArrayList API_SPECIAL_USER = new ArrayList(Arrays.asList("filehelper", "weibo", + "qqmail", "fmessage", "tmessage", "qmessage", "qqsync", "floatbottle", "lbsapp", "shakeapp", "medianote", + "qqfriend", "readerapp", "blogapp", "facebookapp", "masssendapp", "meishiapp", "feedsapp", "voip", + "blogappweixin", "brandsessionholder", "weixin", "weixinreminder", "officialaccounts", "wxitil", + "notification_messages", "wxid_novlwrv3lqwv11", "gh_22b87fa7cb3c", "userexperience_alarm")); + + /** + * 获取文件目录 + * + * @author https://github.com/yaphone + * @date 2017年4月8日 下午10:27:42 + * @return + */ + public static String getLocalPath() { + String localPath = null; + try { + localPath = new File("").getCanonicalPath(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return localPath; + } + + /** + * 获取系统平台 + * + * @author https://github.com/yaphone + * @date 2017年4月8日 下午10:27:53 + */ + public static OsNameEnum getOsNameEnum() { + String os = System.getProperty("os.name").toUpperCase(); + if (os.indexOf(OsNameEnum.DARWIN.toString()) >= 0) { + return OsNameEnum.DARWIN; + } else if (os.indexOf(OsNameEnum.WINDOWS.toString()) >= 0) { + return OsNameEnum.WINDOWS; + } else if (os.indexOf(OsNameEnum.LINUX.toString()) >= 0) { + return OsNameEnum.LINUX; + } else if (os.indexOf(OsNameEnum.MAC.toString()) >= 0) { + return OsNameEnum.MAC; + } + return OsNameEnum.OTHER; + } + +} diff --git a/src/main/java/cn/zhouyafeng/itchat4j/utils/ConstantConfigEnum.java b/src/main/java/cn/zhouyafeng/itchat4j/utils/ConstantConfigEnum.java new file mode 100644 index 0000000..88b50f0 --- /dev/null +++ b/src/main/java/cn/zhouyafeng/itchat4j/utils/ConstantConfigEnum.java @@ -0,0 +1,34 @@ +package cn.zhouyafeng.itchat4j.utils; + +/** + * 常量 + * + * @author https=//github.com/yaphone + * @date 创建时间:2017年5月5日 下午11=29=04 + * @version 1.0 + * + */ +public class ConstantConfigEnum { + public static final int APPMSGTYPE_TEXT = 1; + public static final int APPMSGTYPE_IMG = 2; + public static final int APPMSGTYPE_AUDIO = 3; + public static final int APPMSGTYPE_VIDEO = 4; + public static final int APPMSGTYPE_URL = 5; + public static final int APPMSGTYPE_ATTACH = 6; + public static final int APPMSGTYPE_OPEN = 7; + public static final int APPMSGTYPE_EMOJI = 8; + public static final int APPMSGTYPE_VOICE_REMIND = 9; + public static final int APPMSGTYPE_SCAN_GOOD = 10; + public static final int APPMSGTYPE_GOOD = 13; + public static final int APPMSGTYPE_EMOTION = 15; + public static final int APPMSGTYPE_CARD_TICKET = 16; + public static final int APPMSGTYPE_REALTIME_SHARE_LOCATION = 17; + // public static final int APPMSGTYPE_TRANSFERS = 2e3; + public static final int APPMSGTYPE_RED_ENVELOPES = 2001; + public static final int APPMSGTYPE_READER_TYPE = 100001; + public static final int UPLOAD_MEDIA_TYPE_IMAGE = 1; + public static final int UPLOAD_MEDIA_TYPE_VIDEO = 2; + public static final int UPLOAD_MEDIA_TYPE_AUDIO = 3; + public static final int UPLOAD_MEDIA_TYPE_ATTACHMENT = 4; + +} diff --git a/src/main/java/cn/zhouyafeng/itchat4j/utils/MsgKeywords.java b/src/main/java/cn/zhouyafeng/itchat4j/utils/MsgKeywords.java new file mode 100644 index 0000000..5218a7b --- /dev/null +++ b/src/main/java/cn/zhouyafeng/itchat4j/utils/MsgKeywords.java @@ -0,0 +1,6 @@ +package cn.zhouyafeng.itchat4j.utils; + +public class MsgKeywords { + public static String newFriendStr = "我通过了你的朋友验证请求"; + +} diff --git a/src/main/java/cn/zhouyafeng/itchat4j/utils/MyHttpClient.java b/src/main/java/cn/zhouyafeng/itchat4j/utils/MyHttpClient.java new file mode 100644 index 0000000..d5e7a66 --- /dev/null +++ b/src/main/java/cn/zhouyafeng/itchat4j/utils/MyHttpClient.java @@ -0,0 +1,183 @@ +package cn.zhouyafeng.itchat4j.utils; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.logging.Logger; + +import org.apache.http.Consts; +import org.apache.http.HttpEntity; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.CookieStore; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.cookie.Cookie; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.BasicCookieStore; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.util.EntityUtils; + +/** + * HTTP访问类,对Apache HttpClient进行简单封装,适配器模式 + * + * @author https://github.com/yaphone + * @date 创建时间:2017年4月9日 下午7:05:04 + * @version 1.0 + * + */ +public class MyHttpClient { + private Logger logger = Logger.getLogger("MyHttpClient"); + + private static CloseableHttpClient httpClient = HttpClients.createDefault(); + + private static MyHttpClient instance = null; + + private static CookieStore cookieStore; + + static { + cookieStore = new BasicCookieStore(); + + // 将CookieStore设置到httpClient中 + httpClient = HttpClients.custom().setDefaultCookieStore(cookieStore).build(); + } + + public static String getCookie(String name) { + List cookies = cookieStore.getCookies(); + for (Cookie cookie : cookies) { + if (cookie.getName().equalsIgnoreCase(name)) { + return cookie.getValue(); + } + } + return null; + + } + + private MyHttpClient() { + + } + + /** + * 获取cookies + * + * @author https://github.com/yaphone + * @date 2017年5月7日 下午8:37:17 + * @return + */ + public static MyHttpClient getInstance() { + if (instance == null) { + synchronized (MyHttpClient.class) { + if (instance == null) { + instance = new MyHttpClient(); + } + } + } + return instance; + } + + /** + * 处理GET请求 + * + * @author https://github.com/yaphone + * @date 2017年4月9日 下午7:06:19 + * @param url + * @param params + * @return + */ + public HttpEntity doGet(String url, List params, boolean redirect, + Map headerMap) { + HttpEntity entity = null; + HttpGet httpGet = new HttpGet(); + + try { + if (params != null) { + String paramStr = EntityUtils.toString(new UrlEncodedFormEntity(params, Consts.UTF_8)); + httpGet = new HttpGet(url + "?" + paramStr); + } else { + httpGet = new HttpGet(url); + } + if (!redirect) { + httpGet.setConfig(RequestConfig.custom().setRedirectsEnabled(false).build()); // 禁止重定向 + } + httpGet.setHeader("User-Agent", Config.USER_AGENT); + if (headerMap != null) { + Set> entries = headerMap.entrySet(); + for (Entry entry : entries) { + httpGet.setHeader(entry.getKey(), entry.getValue()); + } + } + CloseableHttpResponse response = httpClient.execute(httpGet); + entity = response.getEntity(); + } catch (ClientProtocolException e) { + logger.info(e.getMessage()); + } catch (IOException e) { + logger.info(e.getMessage()); + } + + return entity; + } + + /** + * 处理POST请求 + * + * @author https://github.com/yaphone + * @date 2017年4月9日 下午7:06:35 + * @param url + * @param params + * @return + */ + public HttpEntity doPost(String url, String paramsStr) { + HttpEntity entity = null; + HttpPost httpPost = new HttpPost(); + try { + StringEntity params = new StringEntity(paramsStr, Consts.UTF_8); + httpPost = new HttpPost(url); + httpPost.setEntity(params); + httpPost.setHeader("Content-type", "application/json; charset=utf-8"); + httpPost.setHeader("User-Agent", Config.USER_AGENT); + CloseableHttpResponse response = httpClient.execute(httpPost); + entity = response.getEntity(); + } catch (ClientProtocolException e) { + logger.info(e.getMessage()); + } catch (IOException e) { + logger.info(e.getMessage()); + } + + return entity; + } + + /** + * 上传文件到服务器 + * + * @author https://github.com/yaphone + * @date 2017年5月7日 下午9:19:23 + * @param url + * @param reqEntity + * @return + */ + public HttpEntity doPostFile(String url, HttpEntity reqEntity) { + HttpEntity entity = null; + HttpPost httpPost = new HttpPost(url); + httpPost.setHeader("User-Agent", Config.USER_AGENT); + httpPost.setEntity(reqEntity); + try { + CloseableHttpResponse response = httpClient.execute(httpPost); + entity = response.getEntity(); + + } catch (Exception e) { + logger.info(e.getMessage()); + } + return entity; + } + + public static CloseableHttpClient getHttpClient() { + return httpClient; + } + +} \ No newline at end of file diff --git a/src/main/java/cn/zhouyafeng/itchat4j/utils/SleepUtils.java b/src/main/java/cn/zhouyafeng/itchat4j/utils/SleepUtils.java new file mode 100644 index 0000000..e1ebc6b --- /dev/null +++ b/src/main/java/cn/zhouyafeng/itchat4j/utils/SleepUtils.java @@ -0,0 +1,20 @@ +package cn.zhouyafeng.itchat4j.utils; + +/** + * Created by xiaoxiaomo on 2017/5/6. + */ +public class SleepUtils { + + /** + * 毫秒为单位 + * @param time + */ + public static void sleep( long time ){ + try { + Thread.sleep( time ); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/cn/zhouyafeng/itchat4j/utils/enums/MsgCodeEnum.java b/src/main/java/cn/zhouyafeng/itchat4j/utils/enums/MsgCodeEnum.java new file mode 100644 index 0000000..2be2e1b --- /dev/null +++ b/src/main/java/cn/zhouyafeng/itchat4j/utils/enums/MsgCodeEnum.java @@ -0,0 +1,68 @@ +package cn.zhouyafeng.itchat4j.utils.enums; + +/** + * 消息类型 + * + * @author https://github.com/yaphone + * @date 创建时间:2017年4月23日 下午12:15:00 + * @version 1.0 + * + */ +public enum MsgCodeEnum { + + // public static final int MSGTYPE_TEXT = 1; // 文本消息类型 + // public static final int MSGTYPE_IMAGE = 3; // 图片消息 + // public static final int MSGTYPE_VOICE = 34; // 语音消息 + // public static final int MSGTYPE_VIDEO = 43; // 小视频消息 + // public static final int MSGTYPE_MICROVIDEO = 62; // 短视频消息 + // public static final int MSGTYPE_EMOTICON = 47; // 表情消息 + // public static final int MSGTYPE_APP = 49; + // public static final int MSGTYPE_VOIPMSG = 50; + // public static final int MSGTYPE_VOIPNOTIFY = 52; + // public static final int MSGTYPE_VOIPINVITE = 53; + // public static final int MSGTYPE_LOCATION = 48; + // public static final int MSGTYPE_STATUSNOTIFY = 51; + // public static final int MSGTYPE_SYSNOTICE = 9999; + // public static final int MSGTYPE_POSSIBLEFRIEND_MSG = 40; + // public static final int MSGTYPE_VERIFYMSG = 37; + // public static final int MSGTYPE_SHARECARD = 42; + // public static final int MSGTYPE_SYS = 10000; + // public static final int MSGTYPE_RECALLED = 10002; + MSGTYPE_TEXT(1, "文本消息类型"), + MSGTYPE_IMAGE(3, "图片消息"), + MSGTYPE_VOICE(34, "语音消息"), + MSGTYPE_VIDEO(43, "小视频消息"), + MSGTYPE_MICROVIDEO(62, "短视频消息"), + MSGTYPE_EMOTICON(47, "表情消息"), + MSGTYPE_MEDIA(49, "多媒体消息"), + MSGTYPE_VOIPMSG(50, ""), + MSGTYPE_VOIPNOTIFY(52, ""), + MSGTYPE_VOIPINVITE(53, ""), + MSGTYPE_LOCATION(48, ""), + MSGTYPE_STATUSNOTIFY(51, ""), + MSGTYPE_SYSNOTICE(9999, ""), + MSGTYPE_POSSIBLEFRIEND_MSG(40, ""), + MSGTYPE_VERIFYMSG(37, "好友请求"), + MSGTYPE_SHARECARD(42, ""), + MSGTYPE_SYS(10000, "系统消息"), + MSGTYPE_RECALLED(10002, "") + + ; + + private int code; + private String type; + + MsgCodeEnum(int code, String type) { + this.code = code; + this.type = type; + } + + public int getCode() { + return code; + } + + public String getType() { + return type; + } + +} diff --git a/src/main/java/cn/zhouyafeng/itchat4j/utils/enums/MsgTypeEnum.java b/src/main/java/cn/zhouyafeng/itchat4j/utils/enums/MsgTypeEnum.java new file mode 100644 index 0000000..98a0709 --- /dev/null +++ b/src/main/java/cn/zhouyafeng/itchat4j/utils/enums/MsgTypeEnum.java @@ -0,0 +1,38 @@ +package cn.zhouyafeng.itchat4j.utils.enums; + + +/** + * 消息类型枚举类 + * + * @author https://github.com/yaphone + * @date 创建时间:2017年5月13日 下午11:53:00 + * @version 1.0 + * + */ +public enum MsgTypeEnum { + TEXT("Text", "文本消息"), + PIC("Pic", "图片消息"), + VOICE("Voice", "语音消息"), + VIEDO("Viedo", "小视频消息"), + NAMECARD("NameCard", "名片消息"), + SYS("Sys", "系统消息"), + VERIFYMSG("VerifyMsg", "添加好友"), + MEDIA("app", "文件消息"); + + private String type; + private String code; + + MsgTypeEnum(String type, String code) { + this.type = type; + this.code = code; + } + + public String getType() { + return type; + } + + public String getCode() { + return code; + } + +} diff --git a/src/main/java/cn/zhouyafeng/itchat4j/utils/enums/OsNameEnum.java b/src/main/java/cn/zhouyafeng/itchat4j/utils/enums/OsNameEnum.java new file mode 100644 index 0000000..cfcd4ab --- /dev/null +++ b/src/main/java/cn/zhouyafeng/itchat4j/utils/enums/OsNameEnum.java @@ -0,0 +1,13 @@ +package cn.zhouyafeng.itchat4j.utils.enums; + +/** + * 系统平台 + * + * @author https://github.com/yaphone + * @date 创建时间:2017年4月8日 下午10:36:28 + * @version 1.0 + * + */ +public enum OsNameEnum { + WINDOWS, LINUX, DARWIN, MAC, OTHER +} diff --git a/src/main/java/cn/zhouyafeng/itchat4j/utils/enums/ResultEnum.java b/src/main/java/cn/zhouyafeng/itchat4j/utils/enums/ResultEnum.java new file mode 100644 index 0000000..f505b7d --- /dev/null +++ b/src/main/java/cn/zhouyafeng/itchat4j/utils/enums/ResultEnum.java @@ -0,0 +1,35 @@ +package cn.zhouyafeng.itchat4j.utils.enums; + +/** + * 返回结构枚举类 + *

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

+ * Created by xiaoxiaomo on 2017/5/7. + */ +public enum BaseParaEnum { + + Uin("Uin", "wxuin"), + Sid("Sid", "wxsid"), + Skey("Skey", "skey"), + DeviceID("DeviceID", "pass_ticket"); + + private String para; + private String value; + + BaseParaEnum(String para, String value) { + this.para = para; + this.value = value; + } + + public String para() { + return para; + } + + + public Object value() { + return value; + } + +} diff --git a/src/main/java/cn/zhouyafeng/itchat4j/utils/enums/parameters/LoginParaEnum.java b/src/main/java/cn/zhouyafeng/itchat4j/utils/enums/parameters/LoginParaEnum.java new file mode 100644 index 0000000..0c0021a --- /dev/null +++ b/src/main/java/cn/zhouyafeng/itchat4j/utils/enums/parameters/LoginParaEnum.java @@ -0,0 +1,31 @@ +package cn.zhouyafeng.itchat4j.utils.enums.parameters; + +/** + * 登陆 + *

+ * Created by xiaoxiaomo on 2017/5/7. + */ +public enum LoginParaEnum { + + LOGIN_ICON("loginicon", "true"), + UUID("uuid", ""), + TIP("tip", "0"), + R("r", ""), + _("_", ""); + + private String para; + private String value; + + LoginParaEnum(String para, String value) { + this.para = para; + this.value = value; + } + + public String para() { + return para; + } + + public String value() { + return value; + } +} diff --git a/src/main/java/cn/zhouyafeng/itchat4j/utils/enums/parameters/StatusNotifyParaEnum.java b/src/main/java/cn/zhouyafeng/itchat4j/utils/enums/parameters/StatusNotifyParaEnum.java new file mode 100644 index 0000000..9bbdf32 --- /dev/null +++ b/src/main/java/cn/zhouyafeng/itchat4j/utils/enums/parameters/StatusNotifyParaEnum.java @@ -0,0 +1,30 @@ +package cn.zhouyafeng.itchat4j.utils.enums.parameters; + +/** + * 状态通知 + *

+ * Created by xiaoxiaomo on 2017/5/7. + */ +public enum StatusNotifyParaEnum { + + CODE("Code", "3"), + FROM_USERNAME("FromUserName", ""), + TO_USERNAME("ToUserName", ""), + CLIENT_MSG_ID("ClientMsgId", ""); //时间戳 + + private String para; + private String value; + + StatusNotifyParaEnum(String para, String value) { + this.para = para; + this.value = value; + } + + public String para() { + return para; + } + + public String value() { + return value; + } +} diff --git a/src/main/java/cn/zhouyafeng/itchat4j/utils/enums/parameters/UUIDParaEnum.java b/src/main/java/cn/zhouyafeng/itchat4j/utils/enums/parameters/UUIDParaEnum.java new file mode 100644 index 0000000..ef716e8 --- /dev/null +++ b/src/main/java/cn/zhouyafeng/itchat4j/utils/enums/parameters/UUIDParaEnum.java @@ -0,0 +1,30 @@ +package cn.zhouyafeng.itchat4j.utils.enums.parameters; + +/** + * UUID + *

+ * Created by xiaoxiaomo on 2017/5/7. + */ +public enum UUIDParaEnum { + + APP_ID("appid", "wx782c26e4c19acffb"), + FUN("fun", "new"), + LANG("lang", "zh_CN"), + _("_", "时间戳"); + + private String para; + private String value; + + UUIDParaEnum(String para, String value) { + this.para = para; + this.value = value; + } + + public String para() { + return para; + } + + public String value() { + return value; + } +} diff --git a/src/main/java/cn/zhouyafeng/itchat4j/utils/tools/CommonTools.java b/src/main/java/cn/zhouyafeng/itchat4j/utils/tools/CommonTools.java new file mode 100644 index 0000000..8e2672f --- /dev/null +++ b/src/main/java/cn/zhouyafeng/itchat4j/utils/tools/CommonTools.java @@ -0,0 +1,243 @@ +package cn.zhouyafeng.itchat4j.utils.tools; + +import java.io.StringReader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.w3c.dom.Document; +import org.xml.sax.InputSource; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.vdurmont.emoji.EmojiParser; + +import cn.zhouyafeng.itchat4j.utils.Config; +import cn.zhouyafeng.itchat4j.utils.enums.OsNameEnum; + +/** + * 常用工具类 + * + * @author https://github.com/yaphone + * @date 创建时间:2017年4月8日 下午10:59:55 + * @version 1.0 + * + */ +public class CommonTools { + + public static boolean printQr(String qrPath) { + + switch (Config.getOsNameEnum()) { + case WINDOWS: + if (Config.getOsNameEnum().equals(OsNameEnum.WINDOWS)) { + Runtime runtime = Runtime.getRuntime(); + try { + runtime.exec("cmd /c start " + qrPath); + } catch (Exception e) { + e.printStackTrace(); + } + } + break; + case MAC: + if (Config.getOsNameEnum().equals(OsNameEnum.MAC)) { + Runtime runtime = Runtime.getRuntime(); + try { + runtime.exec("open " + qrPath); + } catch (Exception e) { + e.printStackTrace(); + } + } + break; + + default: + break; + } + return true; + } + + public static boolean clearScreen() { + switch (Config.getOsNameEnum()) { + case WINDOWS: + if (Config.getOsNameEnum().equals(OsNameEnum.WINDOWS)) { + Runtime runtime = Runtime.getRuntime(); + try { + runtime.exec("cmd /c " + "cls"); + } catch (Exception e) { + e.printStackTrace(); + } + } + break; + + default: + break; + } + return true; + } + + /** + * 正则表达式处理工具 + * + * @author https://github.com/yaphone + * @date 2017年4月9日 上午12:27:10 + * @return + */ + public static Matcher getMatcher(String regEx, String text) { + Pattern pattern = Pattern.compile(regEx); + Matcher matcher = pattern.matcher(text); + return matcher; + } + + /** + * xml解析器 + * + * @author https://github.com/yaphone + * @date 2017年4月9日 下午6:24:25 + * @param text + * @return + */ + public static Document xmlParser(String text) { + Document doc = null; + StringReader sr = new StringReader(text); + InputSource is = new InputSource(sr); + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + try { + DocumentBuilder builder = factory.newDocumentBuilder(); + doc = builder.parse(is); + } catch (Exception e) { + e.printStackTrace(); + } + return doc; + } + + public static JSONObject structFriendInfo(JSONObject userObj) { + Map friendInfoTemplate = new HashMap(); + friendInfoTemplate.put("UserName", ""); + friendInfoTemplate.put("City", ""); + friendInfoTemplate.put("DisplayName", ""); + friendInfoTemplate.put("PYQuanPin", ""); + friendInfoTemplate.put("RemarkPYInitial", ""); + friendInfoTemplate.put("Province", ""); + friendInfoTemplate.put("KeyWord", ""); + friendInfoTemplate.put("RemarkName", ""); + friendInfoTemplate.put("PYInitial", ""); + friendInfoTemplate.put("EncryChatRoomId", ""); + friendInfoTemplate.put("Alias", ""); + friendInfoTemplate.put("Signature", ""); + friendInfoTemplate.put("NickName", ""); + friendInfoTemplate.put("RemarkPYQuanPin", ""); + friendInfoTemplate.put("HeadImgUrl", ""); + + friendInfoTemplate.put("UniFriend", 0); + friendInfoTemplate.put("Sex", 0); + friendInfoTemplate.put("AppAccountFlag", 0); + friendInfoTemplate.put("VerifyFlag", 0); + friendInfoTemplate.put("ChatRoomId", 0); + friendInfoTemplate.put("HideInputBarFlag", 0); + friendInfoTemplate.put("AttrStatus", 0); + friendInfoTemplate.put("SnsFlag", 0); + friendInfoTemplate.put("MemberCount", 0); + friendInfoTemplate.put("OwnerUin", 0); + friendInfoTemplate.put("ContactFlag", 0); + friendInfoTemplate.put("Uin", 0); + friendInfoTemplate.put("StarFriend", 0); + friendInfoTemplate.put("Statues", 0); + + friendInfoTemplate.put("MemberList", new ArrayList()); + + JSONObject r = new JSONObject(); + Set keySet = friendInfoTemplate.keySet(); + for (String key : keySet) { + if (userObj.containsKey(key)) { + r.put(key, userObj.get(key)); + } else { + r.put(key, friendInfoTemplate.get(key)); + } + } + + return r; + } + + public static String getSynckey(JSONObject obj) { + JSONArray obj2 = obj.getJSONArray("List"); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < obj2.size(); i++) { + JSONObject obj3 = (JSONObject) JSON.toJSON(obj2.get(i)); + sb.append(obj3.get("Val") + "|"); + } + return sb.substring(0, sb.length() - 1); // 656159784|656159911|656159873|1491905341 + + } + + public static JSONObject searchDictList(List list, String key, String value) { + JSONObject r = null; + for (JSONObject i : list) { + if (i.getString(key).equals(value)) { + r = i; + break; + } + } + return r; + } + + /** + * 处理emoji表情 + * + * @author https://github.com/yaphone + * @date 2017年4月23日 下午2:39:04 + * @param d + * @param k + */ + public static void emojiFormatter(JSONObject d, String k) { + Matcher matcher = getMatcher("", d.getString(k)); + StringBuilder sb = new StringBuilder(); + String content = d.getString(k); + int lastStart = 0; + while (matcher.find()) { + String str = matcher.group(1); + if (str.length() == 6) { + + } else if (str.length() == 10) { + + } else { + str = "&#x" + str + ";"; + String tmp = content.substring(lastStart, matcher.start()); + sb.append(tmp + str); + lastStart = matcher.end(); + } + } + if (lastStart < content.length()) { + sb.append(content.substring(lastStart)); + } + if (sb.length() != 0) { + d.put(k, EmojiParser.parseToUnicode(sb.toString())); + } else { + d.put(k, content); + } + + } + + /** + * 消息格式化 + * + * @author https://github.com/yaphone + * @date 2017年4月23日 下午4:19:08 + * @param d + * @param k + */ + public static void msgFormatter(JSONObject d, String k) { + d.put(k, d.getString(k).replace("
", "\n")); + emojiFormatter(d, k); + // TODO 与emoji表情有部分兼容问题,目前暂未处理解码处理 d.put(k, + // StringEscapeUtils.unescapeHtml4(d.getString(k))); + + } + +} diff --git a/src/main/java/cn/zhouyafeng/itchat4j/utils/tools/DownloadTools.java b/src/main/java/cn/zhouyafeng/itchat4j/utils/tools/DownloadTools.java new file mode 100644 index 0000000..6b4d8c7 --- /dev/null +++ b/src/main/java/cn/zhouyafeng/itchat4j/utils/tools/DownloadTools.java @@ -0,0 +1,80 @@ +package cn.zhouyafeng.itchat4j.utils.tools; + +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import org.apache.http.HttpEntity; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.util.EntityUtils; + +import cn.zhouyafeng.itchat4j.beans.BaseMsg; +import cn.zhouyafeng.itchat4j.core.Core; +import cn.zhouyafeng.itchat4j.utils.MyHttpClient; +import cn.zhouyafeng.itchat4j.utils.enums.MsgTypeEnum; +import cn.zhouyafeng.itchat4j.utils.enums.URLEnum; + +/** + * 下载工具类 + * + * @author https://github.com/yaphone + * @date 创建时间:2017年4月21日 下午11:18:46 + * @version 1.0 + * + */ +public class DownloadTools { + private static Logger logger = Logger.getLogger("DownloadTools"); + private static Core core = Core.getInstance(); + private static MyHttpClient myHttpClient = core.getMyHttpClient(); + + /** + * 处理下载任务 + * + * @author https://github.com/yaphone + * @date 2017年4月21日 下午11:00:25 + * @param url + * @param msgId + * @param path + * @return + */ + public static Object getDownloadFn(BaseMsg msg, String type, String path) { + Map headerMap = new HashMap(); + List params = new ArrayList(); + String url = ""; + if (type.equals(MsgTypeEnum.PIC.getType())) { + url = String.format(URLEnum.WEB_WX_GET_MSG_IMG.getUrl(), (String) core.getLoginInfo().get("url")); + } else if (type.equals(MsgTypeEnum.VOICE.getType())) { + url = String.format(URLEnum.WEB_WX_GET_VOICE.getUrl(), (String) core.getLoginInfo().get("url")); + } else if (type.equals(MsgTypeEnum.VIEDO.getType())) { + headerMap.put("Range", "bytes=0-"); + url = String.format(URLEnum.WEB_WX_GET_VIEDO.getUrl(), (String) core.getLoginInfo().get("url")); + } else if (type.equals(MsgTypeEnum.MEDIA.getType())) { + headerMap.put("Range", "bytes=0-"); + url = String.format(URLEnum.WEB_WX_GET_MEDIA.getUrl(), (String) core.getLoginInfo().get("fileUrl")); + params.add(new BasicNameValuePair("sender", msg.getFromUserName())); + params.add(new BasicNameValuePair("mediaid", msg.getMediaId())); + params.add(new BasicNameValuePair("filename", msg.getFileName())); + } + params.add(new BasicNameValuePair("msgid", msg.getNewMsgId())); + params.add(new BasicNameValuePair("skey", (String) core.getLoginInfo().get("skey"))); + HttpEntity entity = myHttpClient.doGet(url, params, true, headerMap); + try { + OutputStream out = new FileOutputStream(path); + byte[] bytes = EntityUtils.toByteArray(entity); + out.write(bytes); + out.flush(); + out.close(); + // Tools.printQr(path); + + } catch (Exception e) { + logger.info(e.getMessage()); + return false; + } + return null; + }; + +} diff --git a/src/main/java/xyz/wbsite/itchat4j/JMainApplication.java b/src/main/java/xyz/wbsite/itchat4j/JMainApplication.java new file mode 100644 index 0000000..be0bbe1 --- /dev/null +++ b/src/main/java/xyz/wbsite/itchat4j/JMainApplication.java @@ -0,0 +1,79 @@ +package xyz.wbsite.itchat4j; + +import cn.hutool.core.io.FileUtil; +import cn.zhouyafeng.itchat4j.Wechat; +import cn.zhouyafeng.itchat4j.face.IMsgHandlerFace; +import com.melloware.jintellitype.JIntellitype; +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.image.Image; +import javafx.stage.Stage; +import xyz.wbsite.itchat4j.ui.FXMLUtil; +import xyz.wbsite.itchat4j.util.ResourceUtil; + +import java.awt.event.KeyEvent; +import java.io.File; + +/** + * UI入口 + * + * @author wangbing + * @version 0.0.1 + * @since 1.8 + */ +public class JMainApplication extends Application { + + public static final int F1_SHORTCUT = 1; // 开始快捷键 + public static final int F2_SHORTCUT = 2; // 结束快捷键 + + public static Stage primaryStage; + public static JMainController mainController; + + @Override + public void start(Stage stage) throws Exception { + stage.setTitle("微信客户端"); + stage.setMinWidth(400); + stage.setMinHeight(300); + FXMLLoader mainLoader = FXMLUtil.load("main.fxml"); + mainController = mainLoader.getController(); + stage.setScene(new Scene(mainLoader.getRoot())); + stage.centerOnScreen(); + stage.setResizable(false); + stage.setOnCloseRequest(event -> { + stage.close(); + System.exit(0); + }); + // 设置图标 + Image icon = new Image(ResourceUtil.getInput("/icon.png")); + stage.getIcons().add(icon); + // 展示UI + stage.show(); + JMainApplication.primaryStage = stage; + + JIntellitype.getInstance().registerHotKey(F1_SHORTCUT, JIntellitype.MOD_CONTROL, KeyEvent.VK_F1); + JIntellitype.getInstance().registerHotKey(F2_SHORTCUT, JIntellitype.MOD_CONTROL, KeyEvent.VK_F2); + JIntellitype.getInstance().addHotKeyListener(identifier -> { + switch (identifier) { + case F1_SHORTCUT: + mainController.onStart(); + break; + case F2_SHORTCUT: + mainController.onStop(); + break; + } + }); + +// FileUtil.file(FileUtil.getTmpDir(), "itchat4j."); + String qrPath = "C:\\Users\\Administrator\\Desktop\\111"; // 保存登陆二维码图片的路径,这里需要在本地新建目录 + IMsgHandlerFace msgHandler = new JMsgHandler(); // 实现IMsgHandlerFace接口的类 + Wechat wechat = new Wechat(msgHandler, qrPath); // 【注入】 + // 启动服务,会在qrPath下生成一张二维码图片,扫描即可登陆,注意,二维码图片如果超过一定时间未扫描会过期,过期时会自动更新,所以你可能需要重新打开图片 + wechat.start(); + + } + + public static void main(String[] args) { + launch(args); + } +} diff --git a/src/main/java/xyz/wbsite/itchat4j/JMainController.java b/src/main/java/xyz/wbsite/itchat4j/JMainController.java new file mode 100644 index 0000000..e74e6a1 --- /dev/null +++ b/src/main/java/xyz/wbsite/itchat4j/JMainController.java @@ -0,0 +1,172 @@ +package xyz.wbsite.itchat4j; + +import cn.hutool.core.collection.BoundedPriorityQueue; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.StrUtil; +import javafx.animation.KeyFrame; +import javafx.animation.Timeline; +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.TextArea; +import javafx.scene.control.TextField; +import javafx.scene.control.Tooltip; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.util.Duration; +import xyz.wbsite.itchat4j.util.Logger; + +import java.net.URL; +import java.util.ResourceBundle; +import java.util.concurrent.Semaphore; + +/** + * UI控制器 + * + * @author wangbing + * @version 0.0.1 + * @since 1.8 + */ +public class JMainController implements Initializable { + /** + * 单例对象 + */ + private static JMainController instance; + + @FXML + private Button start; + @FXML + private Button stop; + @FXML + private ImageView set; + @FXML + private TextField interval; + @FXML + private TextField times; + @FXML + private ImageView preview; + @FXML + private TextArea console; + + private final int MAX_LENGTH = 100; + private final BoundedPriorityQueue logs = new BoundedPriorityQueue<>(MAX_LENGTH); + + private Semaphore semaphore = new Semaphore(1); + + public static synchronized JMainController getInstance() { + return JMainController.instance; + } + + @Override + public void initialize(URL location, ResourceBundle resources) { + JMainController.instance = this; + + // 控件初始化 +// int intervalValue = JProp.getInstance().getInt("interval", 60); +// this.interval.setText(String.valueOf(intervalValue)); +// // 只允许输入数字 +// this.interval.textProperty().addListener((observable, oldValue, newValue) -> { +// if (!newValue.matches("\\d*")) { +// this.interval.setText(newValue.replaceAll("\\D", "")); +// } +// JProp.getInstance().setInt("interval", Convert.toInt(this.times.getText())); +// }); +// int timesValue = JProp.getInstance().getInt("times", 3); +// this.times.setText(String.valueOf(timesValue)); +// this.times.textProperty().addListener((observable, oldValue, newValue) -> { +// if (!newValue.matches("\\d*")) { +// this.times.setText(newValue.replaceAll("\\D", "")); +// } +// JProp.getInstance().setInt("times", Convert.toInt(this.times.getText())); +// }); +// installTip(this.set, "扩展配置"); +// installTip(this.interval, "两次脚本执行的间隔时间(秒)"); +// installTip(this.times, "脚本执行的总次数,0代表无限循环"); + } + + /** + * 运行服务 + */ + @FXML + public void onStart() { + synchronized (JMainController.class) { + this.start.setDisable(true); + this.stop.setDisable(false); + Logger.info("启动服务"); + } + } + + /** + * 停止服务 + */ + @FXML + public void onStop() { + synchronized (JMainController.class) { + this.start.setDisable(false); + this.stop.setDisable(true); + Logger.info("停止服务"); + } + } + + /** + * 预览 + * + * @param image 图片 + */ + public void preview(Image image) { + try { + semaphore.acquire(); + preview.setImage(image); + Thread.sleep(500); + semaphore.release(); + } catch (InterruptedException e) { + semaphore.release(); + throw new RuntimeException(e); + } + } + + /** + * 日志窗口 + * + * @param log 格式化 + * @param args 参数 + */ + public synchronized void println(String log, Object... args) { + Platform.runLater(() -> { + String format = StrUtil.format(log, args) + "\n"; + if (logs.size() >= MAX_LENGTH) { + String poll = logs.poll(); + console.deleteText(0, poll.length()); + } + logs.add(format); + console.appendText(format); + }); + } + + + /** + * 给组件增加鼠标悬浮提示 + * + * @param node + * @param tip + */ + public static void installTip(Node node, String tip) { + Tooltip tooltip = new Tooltip(tip); + try { + java.lang.reflect.Field behavior = ClassUtil.getDeclaredField(Tooltip.class, "BEHAVIOR"); + behavior.setAccessible(true); + Object o = behavior.get(tooltip); + java.lang.reflect.Field activationTimer = ClassUtil.getDeclaredField(o.getClass(), "activationTimer"); + activationTimer.setAccessible(true); + Timeline timeline = (Timeline) activationTimer.get(o); + timeline.getKeyFrames().remove(0); + timeline.getKeyFrames().add(new KeyFrame(new Duration(10))); + Tooltip.install(node, tooltip); + } catch (Exception e) { + + } + } +} diff --git a/src/main/java/xyz/wbsite/itchat4j/JMsgHandler.java b/src/main/java/xyz/wbsite/itchat4j/JMsgHandler.java new file mode 100644 index 0000000..2578ae7 --- /dev/null +++ b/src/main/java/xyz/wbsite/itchat4j/JMsgHandler.java @@ -0,0 +1,46 @@ +package xyz.wbsite.itchat4j; + +import cn.zhouyafeng.itchat4j.beans.BaseMsg; +import cn.zhouyafeng.itchat4j.face.IMsgHandlerFace; + +public class JMsgHandler implements IMsgHandlerFace { + @Override + public String textMsgHandle(BaseMsg baseMsg) { + return baseMsg.getText(); + } + + @Override + public String picMsgHandle(BaseMsg baseMsg) { + return null; + } + + @Override + public String voiceMsgHandle(BaseMsg baseMsg) { + return null; + } + + @Override + public String viedoMsgHandle(BaseMsg baseMsg) { + return null; + } + + @Override + public String nameCardMsgHandle(BaseMsg baseMsg) { + return null; + } + + @Override + public void sysMsgHandle(BaseMsg baseMsg) { + + } + + @Override + public String verifyAddFriendMsgHandle(BaseMsg baseMsg) { + return null; + } + + @Override + public String mediaMsgHandle(BaseMsg baseMsg) { + return null; + } +} diff --git a/src/main/java/xyz/wbsite/itchat4j/JProp.java b/src/main/java/xyz/wbsite/itchat4j/JProp.java new file mode 100644 index 0000000..3c508a4 --- /dev/null +++ b/src/main/java/xyz/wbsite/itchat4j/JProp.java @@ -0,0 +1,92 @@ +package xyz.wbsite.itchat4j; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.setting.dialect.Props; + +import java.io.File; +import java.nio.charset.StandardCharsets; + +/** + * 首选项配置 + */ +public class JProp { + + private static final JProp instance = new JProp(); + + private final File propFile; + + private final Props props; + + public static JProp getInstance() { + return instance; + } + + private JProp() { + this.propFile = new File("prop.ini"); + if (!propFile.exists()) { + FileUtil.touch(propFile.getAbsolutePath()); + this.props = new Props(propFile, StandardCharsets.UTF_8); + // 做一些初始化操作 + } else { + this.props = new Props(propFile, StandardCharsets.UTF_8); + } + } + + private void save() { + this.props.store(propFile.getAbsolutePath()); + } + + public String getString(String key, String defaultValue) { + return this.props.getStr(key, defaultValue); + } + + public String[] getStringArray(String key, String[] defaultValue) { + return this.props.getStr(key, "").split(","); + } + + public int getInt(String key, int defaultValue) { + return this.props.getInt(key, defaultValue); + } + + public long getLong(String key, long defaultValue) { + return this.props.getLong(key, defaultValue); + } + + public float getFloat(String key, float defaultValue) { + return this.props.getFloat(key, defaultValue); + } + + public double getDouble(String key, double defaultValue) { + return this.props.getDouble(key, defaultValue); + } + + public void setString(String key, String value) { + this.props.setProperty(key, value); + this.save(); + } + + public void setStringArray(String key, String[] value) { + this.props.setProperty(key, String.join(",", value)); + this.save(); + } + + public void setInt(String key, int value) { + this.props.setProperty(key, String.valueOf(value)); + this.save(); + } + + public void setLong(String key, long value) { + this.props.setProperty(key, String.valueOf(value)); + this.save(); + } + + public void setFloat(String key, float value) { + this.props.setProperty(key, String.valueOf(value)); + this.save(); + } + + public void setDouble(String key, double value) { + this.props.setProperty(key, String.valueOf(value)); + this.save(); + } +} diff --git a/src/main/java/xyz/wbsite/itchat4j/db/Client.java b/src/main/java/xyz/wbsite/itchat4j/db/Client.java new file mode 100644 index 0000000..e889bb7 --- /dev/null +++ b/src/main/java/xyz/wbsite/itchat4j/db/Client.java @@ -0,0 +1,151 @@ +package xyz.wbsite.itchat4j.db; + +import java.io.File; +import java.sql.Connection; +import java.sql.Date; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Time; +import java.sql.Types; + +/** + * xyz.wbsite.wsqlite.Client + * + * @author wangbing + */ +public class Client { + + Connection connection; + ResultSet resultSet; + String dbFilePath; + + /** + * 构造函数 + * + * @param dbFile 文件 + * @throws ClassNotFoundException + * @throws SQLException + */ + public Client(File dbFile) throws ClassNotFoundException, SQLException { + this.dbFilePath = dbFile.getAbsolutePath(); + if (!dbFile.exists()) { + connection = getConnection(); + } + } + + /** + * 执行sql语句 + * + * @param sql + * @throws SQLException + * @throws ClassNotFoundException + */ + public void execute(String sql) throws SQLException, ClassNotFoundException { + try { + executeUpdate(sql); + } finally { + destroyed(); + } + } + + /** + * 执行sql查询语句 + * + * @param sql + * @throws SQLException + * @throws ClassNotFoundException + */ + public ResultSet executeQuery(String sql, Object... args) throws SQLException, ClassNotFoundException { + PreparedStatement preparedStatement = getConnection().prepareStatement(sql); + setArg(preparedStatement, args); + return preparedStatement.executeQuery(); + } + + /** + * 执行sql语句 + * + * @param sql + * @throws SQLException + * @throws ClassNotFoundException + */ + public int executeUpdate(String sql, Object... args) throws SQLException, ClassNotFoundException { + try { + PreparedStatement preparedStatement = getConnection().prepareStatement(sql); + setArg(preparedStatement, args); + return preparedStatement.executeUpdate(); + } finally { + destroyed(); + } + } + + private void setArg(PreparedStatement ps, Object... args) throws SQLException { + for (int i = 0; i < args.length; i++) { + Object arg = args[i]; + + if (arg == null) { + ps.setNull(i + 1, Types.NULL); + } else if (arg instanceof String) { + ps.setString(i + 1, (String) arg); + } else if (arg instanceof Date) { + ps.setDate(i + 1, (Date) arg); + } else if (arg instanceof Time) { + ps.setTime(i + 1, (Time) arg); + } else if (arg instanceof java.util.Date) { + ps.setLong(i + 1, ((java.util.Date) arg).getTime()); + } else if (arg instanceof Boolean) { + ps.setBoolean(i + 1, (Boolean) arg); + } else if (arg instanceof Byte) { + ps.setByte(i + 1, (Byte) arg); + } else if (arg instanceof Short) { + ps.setShort(i + 1, (Short) arg); + } else if (arg instanceof Integer) { + ps.setInt(i + 1, (int) arg); + } else if (arg instanceof Long) { + ps.setLong(i + 1, (long) arg); + } else if (arg instanceof Float) { + ps.setFloat(i + 1, (float) arg); + } else if (arg instanceof Double) { + ps.setDouble(i + 1, (double) arg); + } else if (arg instanceof byte[]) { + ps.setBytes(i + 1, (byte[]) arg); + } + } + } + + /** + * 获取数据库连接 + * + * @return 数据库连接 + * @throws ClassNotFoundException + * @throws SQLException + */ + Connection getConnection() throws ClassNotFoundException, SQLException { + if (null == connection) { + Class.forName("org.sqlite.JDBC"); + connection = DriverManager.getConnection("jdbc:sqlite:" + dbFilePath); + } + return connection; + } + + /** + * 数据库资源关闭和释放 + */ + public void destroyed() { + try { + if (null != resultSet) { + resultSet.close(); + resultSet = null; + } + + if (null != connection) { + connection.close(); + connection = null; + } + + } catch (SQLException e) { + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/main/java/xyz/wbsite/itchat4j/db/ObjectClient.java b/src/main/java/xyz/wbsite/itchat4j/db/ObjectClient.java new file mode 100644 index 0000000..efe6b0c --- /dev/null +++ b/src/main/java/xyz/wbsite/itchat4j/db/ObjectClient.java @@ -0,0 +1,323 @@ +package xyz.wbsite.itchat4j.db; + + +import xyz.wbsite.itchat4j.db.anonation.TableField; + +import java.io.File; +import java.lang.reflect.Field; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * xyz.wbsite.wsqlite.Client + * + * @author wangbing + */ +public class ObjectClient extends Client { + + private Map classMap = new HashMap<>(); + + /** + * 构造函数 + * + * @param dbFile 文件 + * @param classList 注册对象 + * @throws ClassNotFoundException + * @throws SQLException + */ + public ObjectClient(File dbFile, List classList) throws ClassNotFoundException, SQLException { + super(dbFile); + for (Class aClass : classList) { + classMap.put(aClass.getName(), aClass); + } + for (String key : classMap.keySet()) { + Class object = classMap.get(key); + StringBuffer sql = new StringBuffer(); + String name = object.getSimpleName(); + + sql.append("CREATE TABLE IF NOT EXISTS "); + sql.append(name); + sql.append(" ("); + + Field[] fields = object.getDeclaredFields(); + for (Field f : fields) { + if (f.isAnnotationPresent(TableField.class)) { + TableField bind = f.getAnnotation(TableField.class); + int length = bind.value(); + + if (f.getType() == String.class) { + sql.append(f.getName().toUpperCase()); + sql.append(" VARCHAR(" + length + "),"); + } else if (f.getType() == Boolean.class || f.getType() == boolean.class) { + sql.append(f.getName().toUpperCase()); + sql.append(" BOOLEAN,"); + } else if (f.getType() == Byte.class || f.getType() == byte.class || + f.getType() == Short.class || f.getType() == short.class || + f.getType() == Character.class || f.getType() == char.class || + f.getType() == Integer.class || f.getType() == int.class || + f.getType() == Long.class || f.getType() == long.class) { + sql.append(f.getName().toUpperCase()); + sql.append(" INTEGER,"); + } else if (f.getType() == Float.class || f.getType() == float.class || + f.getType() == Double.class || f.getType() == double.class) { + sql.append(f.getName().toUpperCase()); + sql.append(" REAL,"); + } else if (f.getType() == Byte[].class || f.getType() == byte[].class) { + sql.append(f.getName().toUpperCase()); + sql.append(" BLOB,"); + } else if (f.getType() == Date.class) { + sql.append(f.getName().toUpperCase()); + sql.append(" TIMESTAMP,"); + } + } + } + + sql.replace(sql.length() - 1, sql.length(), ""); + sql.append(")"); + System.out.println("SQL ==> " + sql.toString()); + execute(sql.toString()); + } + } + + public int insert(Class poClass, T po) throws SQLException, ClassNotFoundException { + try { + Class aClass = classMap.get(poClass.getName()); + if (aClass == null) { + System.err.println(poClass.getName() + " not found."); + } else { + StringBuffer sql = new StringBuffer(); + sql.append("INSERT INTO "); + sql.append(aClass.getSimpleName()); + sql.append("("); + + //获取字段列表 + List fs = getFields(poClass); + + StringBuffer fieldsSql = new StringBuffer(); + StringBuffer valueSql = new StringBuffer(); + Object[] values = new Object[fs.size()]; + + for (int i = 0; i < fs.size(); i++) { + Field f = fs.get(i); + f.setAccessible(true); + fieldsSql.append(f.getName().toUpperCase()); + valueSql.append("?"); + values[i] = f.get(po); + if (i != fs.size() - 1) { + fieldsSql.append(","); + valueSql.append(","); + } + } + + sql.append(fieldsSql); + sql.append(") VALUES ("); + sql.append(valueSql); + sql.append(")"); + System.out.println("SQL ==> " + sql.toString()); + return executeUpdate(sql.toString(), values); + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } finally { + destroyed(); + } + return 0; + } + + public int delete(Class poClass, Where where) throws SQLException, ClassNotFoundException { + try { + Class aClass = classMap.get(poClass.getName()); + if (aClass == null) { + System.err.println(poClass.getName() + " not found."); + } else { + StringBuffer sql = new StringBuffer(); + sql.append("DELETE FROM "); + sql.append(aClass.getSimpleName()); + sql.append(where.getSql()); + System.out.println("SQL ==> " + sql.toString()); + return executeUpdate(sql.toString(), where.getArgs()); + } + } finally { + destroyed(); + } + return 0; + } + + public int update(Class poClass, T po, Where where) throws SQLException, ClassNotFoundException { + try { + Class aClass = classMap.get(poClass.getName()); + if (aClass == null) { + System.err.println(poClass.getName() + " not found."); + } else { + //获取字段列表 + List fs = getFields(poClass); + + StringBuffer sql = new StringBuffer(); + Object[] values = new Object[fs.size()]; + sql.append("UPDATE "); + sql.append(aClass.getSimpleName()); + sql.append(" SET "); + + for (int i = 0; i < fs.size(); i++) { + Field f = fs.get(i); + f.setAccessible(true); + sql.append(f.getName()); + sql.append(" = ?"); + values[i] = f.get(po); + if (i != fs.size() - 1) { + sql.append(","); + } + } + + // 条件 + sql.append(where.getSql()); + + System.out.println("SQL ==> " + sql.toString()); + return executeUpdate(sql.toString(), concat(values, where.getArgs())); + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } finally { + destroyed(); + } + return 0; + } + + public List select(Class poClass, Where where, int pageNumber, int pageSize) throws SQLException, ClassNotFoundException { + ArrayList list = new ArrayList<>(); + try { + Class aClass = classMap.get(poClass.getName()); + if (aClass == null) { + System.err.println(poClass.getName() + " not found."); + } else { + StringBuffer sql = new StringBuffer(); + sql.append("SELECT "); + + //获取字段列表 + List fs = getFields(poClass); + + for (int i = 0; i < fs.size(); i++) { + Field f = fs.get(i); + sql.append(f.getName().toUpperCase()); + if (i != fs.size() - 1) { + sql.append(","); + } + } + + sql.append(" FROM "); + sql.append(aClass.getSimpleName()); + + // 条件 + sql.append(where.getSql()); + + //分页参数 + if (pageSize > 0) { + sql.append(" LIMIT " + (pageNumber - 1) + "," + pageSize); + } + + System.out.println("SQL ==> " + sql.toString()); + list.addAll(executeQuery(poClass, sql.toString(), where.getArgs())); + } + } finally { + destroyed(); + } + return list; + } + + /** + * 执行select查询,返回结果列表 + * + * @param sql sql select 语句 + * @param poClass 结果集的行数据处理类对象 + * @return + * @throws SQLException + * @throws ClassNotFoundException + */ + public List executeQuery(Class poClass, String sql, Object... args) throws SQLException, ClassNotFoundException { + List rsList = new ArrayList(); + try { + resultSet = executeQuery(sql, args); + + //获取字段列表 + List fs = getFields(poClass); + + while (resultSet.next()) { + try { + T o = poClass.newInstance(); + for (int i = 0; i < fs.size(); i++) { + Field f = fs.get(i); + f.setAccessible(true); + + if (f.getType() == String.class) { + String v = resultSet.getString(f.getName()); + f.set(o, v); + } else if (f.getType() == Boolean.class || f.getType() == boolean.class) { + boolean v = resultSet.getBoolean(f.getName()); + f.set(o, v); + } else if (f.getType() == Byte.class || f.getType() == byte.class) { + byte v = resultSet.getByte(f.getName()); + f.set(o, v); + } else if (f.getType() == Short.class || f.getType() == short.class) { + short v = resultSet.getShort(f.getName()); + f.set(o, v); + } else if (f.getType() == Character.class || f.getType() == char.class) { + short v = resultSet.getShort(f.getName()); + f.set(o, (char) v); + } else if (f.getType() == Integer.class || f.getType() == int.class) { + int v = resultSet.getInt(f.getName()); + f.set(o, v); + } else if (f.getType() == Long.class || f.getType() == long.class) { + long v = resultSet.getLong(f.getName()); + f.set(o, v); + } else if (f.getType() == Float.class || f.getType() == float.class) { + float v = resultSet.getFloat(f.getName()); + f.set(o, v); + } else if (f.getType() == Double.class || f.getType() == double.class) { + double v = resultSet.getDouble(f.getName()); + f.set(o, v); + } else if (f.getType() == Byte[].class || f.getType() == byte[].class) { + byte[] v = resultSet.getBytes(f.getName()); + f.set(o, v); + } else if (f.getType() == Date.class) { + Date v = resultSet.getDate(f.getName()); + f.set(o, v); + } else { + String v = resultSet.getString(f.getName()); + f.set(o, v); + } + } + rsList.add(o); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } finally { + destroyed(); + } + return rsList; + } + + private List getFields(Class aClass) { + List fs = new ArrayList<>(); + for (Field f : aClass.getDeclaredFields()) { + if (f.isAnnotationPresent(TableField.class)) { + fs.add(f); + } + } + return fs; + } + + private T[] concat(T[] arr1, T[] arr2) { + List temp = new ArrayList<>(); + temp.addAll(Arrays.asList(arr1)); + temp.addAll(Arrays.asList(arr2)); + return (T[]) temp.toArray(); + } +} \ No newline at end of file diff --git a/src/main/java/xyz/wbsite/itchat4j/db/Where.java b/src/main/java/xyz/wbsite/itchat4j/db/Where.java new file mode 100644 index 0000000..09647ff --- /dev/null +++ b/src/main/java/xyz/wbsite/itchat4j/db/Where.java @@ -0,0 +1,94 @@ +package xyz.wbsite.itchat4j.db; + +import java.util.ArrayList; +import java.util.List; + +public class Where { + private String sql; + private Object[] args; + + private Where() { + } + + public String getSql() { + return sql; + } + + public void setSql(String sql) { + this.sql = sql; + } + + public Object[] getArgs() { + return args; + } + + public void setArgs(Object[] args) { + this.args = args; + } + + public Where(String sql, Object[] args) { + this.sql = sql; + this.args = args; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private StringBuilder sb = new StringBuilder(); + private List argsList = new ArrayList<>(); + + public Builder eq(String name, Object value) { + return eq(true, name, value); + } + + public Builder like(String name, Object value) { + return like(true, name, value); + } + + public Builder isNull(String name, Object value) { + return isNull(true, name, value); + } + + public Builder isNotNull(String name, Object value) { + return isNotNull(true, name, value); + } + + public Builder eq(boolean condition, String name, Object value) { + if (condition) { + sb.append(sb.length() > 0 ? " and " : " where ").append(name).append(" = ? "); + argsList.add(value); + } + return this; + } + + public Builder like(boolean condition, String name, Object value) { + if (condition) { + sb.append(sb.length() > 0 ? " and " : " where ").append(name).append("like ?"); + argsList.add(value); + } + return this; + } + + public Builder isNull(boolean condition, String name, Object value) { + if (condition) { + sb.append(sb.length() > 0 ? " and " : " where ").append(name).append("is null"); + argsList.add(value); + } + return this; + } + + public Builder isNotNull(boolean condition, String name, Object value) { + if (condition) { + sb.append(sb.length() > 0 ? " and " : " where ").append(name).append("is not null"); + argsList.add(value); + } + return this; + } + + public Where build() { + return new Where(sb.toString(), argsList.toArray()); + } + } +} diff --git a/src/main/java/xyz/wbsite/itchat4j/db/anonation/TableField.java b/src/main/java/xyz/wbsite/itchat4j/db/anonation/TableField.java new file mode 100644 index 0000000..d35ed5b --- /dev/null +++ b/src/main/java/xyz/wbsite/itchat4j/db/anonation/TableField.java @@ -0,0 +1,13 @@ +package xyz.wbsite.itchat4j.db.anonation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface TableField { + + int value() default 20; +} diff --git a/src/main/java/xyz/wbsite/itchat4j/db/entity/Example.java b/src/main/java/xyz/wbsite/itchat4j/db/entity/Example.java new file mode 100644 index 0000000..f1e533d --- /dev/null +++ b/src/main/java/xyz/wbsite/itchat4j/db/entity/Example.java @@ -0,0 +1,110 @@ +package xyz.wbsite.itchat4j.db.entity; + + +import xyz.wbsite.itchat4j.db.anonation.TableField; + +import java.util.Date; + +public class Example { + + @TableField(40) + private String s; + @TableField + private boolean b; + @TableField + private byte[] bs; + @TableField + private byte b1; + @TableField + private short s1; + @TableField + private int age; + @TableField + private long id; + @TableField + private float f1; + @TableField + private double d1; + @TableField + private Date date; + + public String getS() { + return s; + } + + public void setS(String s) { + this.s = s; + } + + public boolean isB() { + return b; + } + + public void setB(boolean b) { + this.b = b; + } + + public byte[] getBs() { + return bs; + } + + public void setBs(byte[] bs) { + this.bs = bs; + } + + public byte getB1() { + return b1; + } + + public void setB1(byte b1) { + this.b1 = b1; + } + + public short getS1() { + return s1; + } + + public void setS1(short s1) { + this.s1 = s1; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public float getF1() { + return f1; + } + + public void setF1(float f1) { + this.f1 = f1; + } + + public double getD1() { + return d1; + } + + public void setD1(double d1) { + this.d1 = d1; + } + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } +} diff --git a/src/main/java/xyz/wbsite/itchat4j/ui/FXMLUtil.java b/src/main/java/xyz/wbsite/itchat4j/ui/FXMLUtil.java new file mode 100644 index 0000000..0e5d080 --- /dev/null +++ b/src/main/java/xyz/wbsite/itchat4j/ui/FXMLUtil.java @@ -0,0 +1,33 @@ +package xyz.wbsite.itchat4j.ui; + +import xyz.wbsite.itchat4j.util.ResourceUtil; +import javafx.fxml.FXMLLoader; + +import java.io.IOException; +import java.net.URL; + +/** + * FXML工具 + * + * @author wangbing + * @version 0.0.1 + * @since 1.8 + */ +public class FXMLUtil { + + /** + * 加载FXMLLoader + * + * @param fxml 资源路径 + * @return + */ + public static FXMLLoader load(String fxml) throws IOException { + if (!fxml.startsWith("classpath:")) { + fxml = "classpath:" + fxml; + } + URL resource = ResourceUtil.getURL(fxml); + FXMLLoader fxmlLoader = new FXMLLoader(resource); + fxmlLoader.load(); + return fxmlLoader; + } +} diff --git a/src/main/java/xyz/wbsite/itchat4j/util/DialogUtil.java b/src/main/java/xyz/wbsite/itchat4j/util/DialogUtil.java new file mode 100644 index 0000000..584d606 --- /dev/null +++ b/src/main/java/xyz/wbsite/itchat4j/util/DialogUtil.java @@ -0,0 +1,269 @@ +package xyz.wbsite.itchat4j.util; + +import cn.hutool.core.util.ClassUtil; +import xyz.wbsite.itchat4j.JMainApplication; +import javafx.animation.KeyFrame; +import javafx.animation.Timeline; +import javafx.application.Platform; +import javafx.event.EventHandler; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonBar; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Label; +import javafx.scene.control.ProgressIndicator; +import javafx.scene.control.Tooltip; +import javafx.scene.image.Image; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.scene.text.Font; +import javafx.stage.Modality; +import javafx.stage.Stage; +import javafx.util.Duration; + +import java.io.File; +import java.util.Optional; +import java.util.Timer; +import java.util.TimerTask; + +public class DialogUtil { + + private static Stage popup; + + public static void toast(String msg, long time) { + Label label = new Label(msg);//默认信息 + label.setStyle("-fx-background: rgba(56,56,56,0.7);-fx-border-radius: 25;-fx-background-radius: 25");//label透明,圆角 + label.setTextFill(Color.rgb(225, 255, 226));//消息字体颜色 + label.setPrefHeight(50); + label.setPadding(new Insets(15)); + label.setAlignment(Pos.CENTER);//居中 + label.setFont(new Font(20));//字体大小 + Scene scene = new Scene(label); + scene.setFill(null);//场景透明 + Stage stage = new Stage(); + stage.initModality(Modality.WINDOW_MODAL); + stage.setAlwaysOnTop(false); + stage.setScene(scene); + label.setText(msg); + TimerTask task = new TimerTask() { + @Override + public void run() { + Platform.runLater(stage::close); + } + }; + + Timer timer = new Timer(); + timer.schedule(task, time); + stage.show(); + } + + public static void showTimedDialog(final long time, String message) { + popup = new Stage(); + popup.setAlwaysOnTop(true); + popup.initModality(Modality.APPLICATION_MODAL); + final Button closeBtn = new Button("知道了"); + closeBtn.setOnAction(event -> popup.close()); + VBox root = new VBox(); + root.setPadding(new Insets(10)); + root.setAlignment(Pos.BASELINE_CENTER); + root.setSpacing(10); + root.getChildren().addAll(new Label(message), closeBtn); + Scene scene = new Scene(root); + popup.setScene(scene); + popup.setTitle("提示信息"); + popup.show(); + + Thread thread = new Thread(() -> { + try { + Thread.sleep(time); + if (popup.isShowing()) { + Platform.runLater(new Runnable() { + @Override + public void run() { + popup.close(); + } + }); + } + } catch (Exception exp) { + exp.printStackTrace(); + } + }); + thread.setDaemon(true); + thread.start(); + } + + public static void alert(String message) { + Platform.runLater(() -> { + Alert alert = new Alert(Alert.AlertType.CONFIRMATION); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image(ResourceUtil.getInput("icon.png"))); + stage.setAlwaysOnTop(true); + alert.setHeaderText(null); + alert.setTitle("提示"); + alert.setContentText(message); + alert.getButtonTypes().remove(ButtonType.CANCEL); + alert.setX(JMainApplication.primaryStage.getX() + JMainApplication.primaryStage.getWidth() / 2 - 213); + alert.setY(JMainApplication.primaryStage.getY() + JMainApplication.primaryStage.getHeight() / 2 - 70); + Optional buttonType = alert.showAndWait(); + }); + } + + public static void confirm(String message) { + confirm("提示", message, null); + } + + public static void confirm(String title, String message) { + confirm(title, message, null); + } + + public static void confirm(String title, String message, ConfirmCall call) { + Platform.runLater(() -> { + Alert alert = new Alert(Alert.AlertType.CONFIRMATION); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image(ResourceUtil.getInput("icon.png"))); + stage.setAlwaysOnTop(true); + alert.setHeaderText(null); + alert.setTitle(title); + alert.setContentText(message); + alert.setX(JMainApplication.primaryStage.getX() + JMainApplication.primaryStage.getWidth() / 2 - 213); + alert.setY(JMainApplication.primaryStage.getY() + JMainApplication.primaryStage.getHeight() / 2 - 70); + Optional buttonType = alert.showAndWait(); + if (call != null) { + if (ButtonBar.ButtonData.OK_DONE.getTypeCode().equals(buttonType.get().getButtonData().getTypeCode())) { + call.call(true); + } else { + call.call(false); + } + } + }); + } + + public static void error(String message) { + Platform.runLater(() -> { + Alert alert = new Alert(Alert.AlertType.ERROR); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.setAlwaysOnTop(true); + stage.getIcons().add(new Image(ResourceUtil.getInput("icon.png"))); + alert.setTitle("错误"); + alert.setX(JMainApplication.primaryStage.getX() + JMainApplication.primaryStage.getWidth() / 2 - 213); + alert.setY(JMainApplication.primaryStage.getY() + JMainApplication.primaryStage.getHeight() / 2 - 70); + alert.setHeaderText(""); + alert.setContentText(message); + alert.show(); + }); + } + + public static void success(String message) { + Platform.runLater(() -> { + Alert alert = new Alert(Alert.AlertType.INFORMATION); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image(ResourceUtil.getInput("icon.png"))); + alert.setTitle("消息"); + alert.setX(JMainApplication.primaryStage.getX() + JMainApplication.primaryStage.getWidth() / 2 - 213); + alert.setY(JMainApplication.primaryStage.getY() + JMainApplication.primaryStage.getHeight() / 2 - 70); + alert.setHeaderText(""); + alert.setContentText(message); + alert.show(); + }); + } + + private static javafx.scene.control.Dialog dialog = null; + + public static void loading(String message) { + loading(message, null); + } + + public static void loading(String message, CancelCall cancelCall) { + if (dialog != null) return; + Platform.runLater(() -> { + dialog = new javafx.scene.control.Dialog(); + dialog.setTitle("提示"); + Stage stage = (Stage) dialog.getDialogPane().getScene().getWindow(); + stage.getIcons().add(new Image(ResourceUtil.getInput("icon.png"))); + dialog.setResult("1"); + dialog.setX(JMainApplication.primaryStage.getX() + JMainApplication.primaryStage.getWidth() / 2 - 68); + dialog.setY(JMainApplication.primaryStage.getY() + JMainApplication.primaryStage.getHeight() / 2 - 70); + GridPane gridPane = new GridPane(); + gridPane.setPrefWidth(200); + gridPane.setHgap(10); + gridPane.setVgap(10); + gridPane.setAlignment(Pos.CENTER); + gridPane.setPadding(new Insets(10, 10, 10, 10)); + dialog.getDialogPane().setContent(gridPane); + + ProgressIndicator indicator = new ProgressIndicator(); + indicator.setPrefSize(30, 30); + gridPane.add(indicator, 0, 0); + gridPane.add(new Label(message), 0, 1); + + if (cancelCall != null) { + Button cancel = new Button("取消"); + gridPane.add(cancel, 0, 2); + cancel.setOnMouseClicked(new EventHandler() { + @Override + public void handle(MouseEvent event) { + cancelCall.call(); + } + }); + } + dialog.show(); + }); + } + + public static void hideLoading() { + if (dialog == null) return; + Platform.runLater(new Runnable() { + @Override + public void run() { + dialog.close(); + dialog = null; + } + }); + } + + + public interface InputCall { + void call(String input); + } + + public interface ConfirmCall { + void call(boolean result); + } + + public interface DirectoryCall { + void call(File result); + } + + public interface CancelCall { + void call(); + } + + /** + * 给组件增加鼠标悬浮提示 + * + * @param node + * @param tip + */ + public static void installTip(Node node, String tip) { + Tooltip tooltip = new Tooltip(tip); + try { + java.lang.reflect.Field behavior = ClassUtil.getDeclaredField(Tooltip.class, "BEHAVIOR"); + behavior.setAccessible(true); + Object o = behavior.get(tooltip); + java.lang.reflect.Field activationTimer = ClassUtil.getDeclaredField(o.getClass(), "activationTimer"); + activationTimer.setAccessible(true); + Timeline timeline = (Timeline) activationTimer.get(o); + timeline.getKeyFrames().remove(0); + timeline.getKeyFrames().add(new KeyFrame(new Duration(10))); + } catch (Exception ignored) { + + } + Tooltip.install(node, tooltip); + } +} diff --git a/src/main/java/xyz/wbsite/itchat4j/util/ImageUtil.java b/src/main/java/xyz/wbsite/itchat4j/util/ImageUtil.java new file mode 100644 index 0000000..2849bfc --- /dev/null +++ b/src/main/java/xyz/wbsite/itchat4j/util/ImageUtil.java @@ -0,0 +1,107 @@ +package xyz.wbsite.itchat4j.util; + +import cn.hutool.core.img.ImgUtil; +import cn.hutool.core.io.IoUtil; +import xyz.wbsite.itchat4j.JMainApplication; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 图片加载工具 + * + * @author wangbing + * @version 0.0.1 + * @since 1.8 + */ +public class ImageUtil { + + private final static Map cache = new HashMap(); + + /** + * 加载图片 + * + * @param pic 图片 + * @return 缓冲区图像 + */ + public static BufferedImage load(File pic) { + if (cache.containsKey(pic.getAbsolutePath())) { + return cache.get(pic.getAbsolutePath()); + } + BufferedImage read = ImgUtil.read(pic); + cache.put(pic.getAbsolutePath(), read); + return read; + } + + /** + * 加载多个图片 + * + * @param pics 图片集 + * @return 缓冲区图像集 + */ + public static BufferedImage[] load(File[] pics) { + BufferedImage[] images = new BufferedImage[pics.length]; + for (int i = 0; i < images.length; i++) { + images[i] = load(pics[i]); + } + return images; + } + + /** + * 加载多个图片 + * + * @param pics 图片集 + * @return 缓冲区图像集 + */ + public static List load(List pics) { + List images = new ArrayList(); + for (File file : pics) { + images.add(load(file)); + } + return images; + } + + public static int[][] getImageRGB(BufferedImage bfImage) { + int width = bfImage.getWidth(); + int height = bfImage.getHeight(); + int[][] result = new int[width][height]; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + // 对某个像素点的RGB编码并存入数据库 + result[x][y] = bfImage.getRGB(x, y); + // 单独获取每一个像素点的Alpha, Red,Green,和Blue的值。 + // int a = rgb>>24 & 0xff; + // int r = rgb>>16 & 0xff; + // int g = rgb>>8 & 0xff; + // int b = rgb & 0xff; + } + } + return result; + } + + /** + * 终端程序并展示图片 + * + * @param image 图片 + */ + public static void show(BufferedImage image) { + Image showImage = image; + // 图片宽高都大于预览窗口时进行缩放显示 + if (image.getWidth() > 192 || image.getHeight() > 108) { + // 宽高比大于预览窗口,适配宽, 否则适配高 + if (1D * image.getWidth() / image.getHeight() > 192D / 108D) { + showImage = ImgUtil.scale(showImage, 1.0f * 192 / image.getWidth()); + } else { + showImage = ImgUtil.scale(showImage, 1.0f * 108 / image.getHeight()); + } + } + byte[] bytes = ImgUtil.toBytes(showImage, "PNG"); + javafx.scene.image.Image image1 = new javafx.scene.image.Image(IoUtil.toStream(bytes)); + JMainApplication.mainController.preview(image1); + } +} diff --git a/src/main/java/xyz/wbsite/itchat4j/util/Logger.java b/src/main/java/xyz/wbsite/itchat4j/util/Logger.java new file mode 100644 index 0000000..3364ff5 --- /dev/null +++ b/src/main/java/xyz/wbsite/itchat4j/util/Logger.java @@ -0,0 +1,52 @@ +package xyz.wbsite.itchat4j.util; + +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.lang.caller.CallerUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.log.level.Level; +import xyz.wbsite.itchat4j.JMainApplication; + +/** + * 日志记录器 + * + * @author wangbing + * @version 0.0.1 + * @since 1.8 + */ +public class Logger { + + public static void info(String format, Object... arg) { + // 获取当前时间 + DateTime date = DateUtil.date(); + // 获取日志级别 + String level = Level.INFO.toString(); + // 格式化日志信息 + String log = StrUtil.format(format, arg); + // 获取调用者类名 + String name = CallerUtil.getCallerCaller().getSimpleName(); + // 获取当前线程的堆栈追踪 + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + // 通常堆栈追踪数组的第三个元素是logMessage方法的调用者 + int lineNumber = stackTrace[2].getLineNumber(); + // 打印日志信息 + JMainApplication.mainController.println("[{}] [{}] {} [{}:{}]", date, level, log, name, lineNumber); + } + + public static void error(String format, Object... arg) { + // 获取当前时间 + DateTime date = DateUtil.date(); + // 获取日志级别 + String level = Level.ERROR.toString(); + // 格式化日志信息 + String log = StrUtil.format(format, arg); + // 获取调用者类名 + String name = CallerUtil.getCallerCaller().getSimpleName(); + // 获取当前线程的堆栈追踪 + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + // 通常堆栈追踪数组的第三个元素是logMessage方法的调用者 + int lineNumber = stackTrace[2].getLineNumber(); + // 打印日志信息 + JMainApplication.mainController.println("[{}] [{}] {} [{}:{}]", date, level, log, name, lineNumber); + } +} diff --git a/src/main/java/xyz/wbsite/itchat4j/util/ResourceUtil.java b/src/main/java/xyz/wbsite/itchat4j/util/ResourceUtil.java new file mode 100644 index 0000000..86ecf35 --- /dev/null +++ b/src/main/java/xyz/wbsite/itchat4j/util/ResourceUtil.java @@ -0,0 +1,62 @@ +package xyz.wbsite.itchat4j.util; + +import cn.hutool.core.io.resource.ClassPathResource; +import javafx.fxml.FXMLLoader; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; + +/** + * 资源工具 + * + * @author wangbing + * @version 0.0.1 + * @since 1.8 + */ +public class ResourceUtil extends cn.hutool.core.io.resource.ResourceUtil { + + /** + * 获取资源输入流,以/开始 + * + * @return 资源路径 + */ + public static InputStream getInput(String resourceLocation) { + if (resourceLocation.startsWith("/")) { + resourceLocation = "/" + resourceLocation; + } + ClassPathResource classPathResource = new ClassPathResource(resourceLocation); + return classPathResource.getStream(); + } + + /** + * 获取资源路径 + * + * @param resourceLocation 资源 + */ + public static URL getURL(String resourceLocation) throws FileNotFoundException { + if (resourceLocation.startsWith("classpath:")) { + String path = resourceLocation.substring("classpath:".length()); + ClassLoader cl = FXMLLoader.getDefaultClassLoader(); + URL url = cl != null ? cl.getResource(path) : ClassLoader.getSystemResource(path); + if (url == null) { + String description = "class path resource [" + path + "]"; + throw new FileNotFoundException(description + " cannot be resolved to URL because it does not exist"); + } else { + return url; + } + } else { + try { + return new URL(resourceLocation); + } catch (MalformedURLException var6) { + try { + return (new File(resourceLocation)).toURI().toURL(); + } catch (MalformedURLException var5) { + throw new FileNotFoundException("Resource location [" + resourceLocation + "] is neither a URL not a well-formed file path"); + } + } + } + } +} diff --git a/src/main/java/xyz/wbsite/itchat4j/util/TaskUtil.java b/src/main/java/xyz/wbsite/itchat4j/util/TaskUtil.java new file mode 100644 index 0000000..c7950d2 --- /dev/null +++ b/src/main/java/xyz/wbsite/itchat4j/util/TaskUtil.java @@ -0,0 +1,356 @@ +package xyz.wbsite.itchat4j.util; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.cron.CronUtil; + +import java.util.ArrayList; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.*; + +/** + * 任务/定时任务工具 + * + * @author wangbing + * @version 0.0.1 + * @since 1.8 + */ +public class TaskUtil extends CronUtil { + + private static ScheduledExecutorService service; + + public static ScheduledExecutorService getService() { + return service; + } + + public static void setService(ScheduledExecutorService service) { + TaskUtil.service = service; + } + + /** + * 执行Runnable任务 + * + * @param runnable Runnable任务 + * @param delay 延时(ms) + */ + public static ScheduledFuture schedule(Runnable runnable, long delay, TimeUnit unit) { + if (service == null) { + service = new ScheduledThreadPoolExecutor(1); + } + return service.schedule(runnable, delay, unit); + } + + /** + * 执行Runnable任务 + * + * @param callable Callable任务 + * @param delay 延时(ms) + */ + public static ScheduledFuture schedule(Callable callable, long delay) { + if (service == null) { + service = new ScheduledThreadPoolExecutor(1); + } + return service.schedule(callable, delay, TimeUnit.MILLISECONDS); + } + + /** + * 执行Runnable任务 + * + * @param callable Callable任务 + * @param delay 延时 + * @param unit 时间单位 + */ + public static ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { + if (service == null) { + service = new ScheduledThreadPoolExecutor(1); + } + return service.schedule(callable, delay, unit); + } + + /** + * 执行Runnable任务 + * + * @param runnable Runnable任务 + * @param initialDelay 延迟执行时间 + * @param period 循环周期(前任务开始-新任务开始) + * @param unit 时间单位 + */ + public static ScheduledFuture scheduleAtFixedRate(Runnable runnable, long initialDelay, long period, TimeUnit unit) { + if (service == null) { + service = new ScheduledThreadPoolExecutor(1); + } + return service.scheduleAtFixedRate(runnable, initialDelay, period, unit); + } + + /** + * 执行Runnable任务 + * + * @param runnable Runnable任务 + * @param initialDelay 延迟执行时间 + * @param period 循环周期(前任务开始-新任务开始)ms + */ + public static ScheduledFuture scheduleAtFixedRate(Runnable runnable, long initialDelay, long period) { + if (service == null) { + service = new ScheduledThreadPoolExecutor(1); + } + return service.scheduleAtFixedRate(runnable, initialDelay, period, TimeUnit.MILLISECONDS); + } + + /** + * 执行Runnable任务 + * + * @param runnable Runnable任务 + * @param initialDelay 延迟执行时间 + * @param delay 循环周期(前任务结束-新任务开始) + * @param unit 时间单位 + */ + public static ScheduledFuture scheduleWithFixedDelay(Runnable runnable, long initialDelay, long delay, TimeUnit unit) { + if (service == null) { + service = new ScheduledThreadPoolExecutor(1); + } + return service.scheduleAtFixedRate(runnable, initialDelay, delay, unit); + } + + /** + * 执行Runnable任务 + * + * @param runnable Runnable任务 + * @param initialDelay 延迟执行时间 + * @param delay 循环周期(前任务结束-新任务开始)ms + */ + public static ScheduledFuture scheduleWithFixedDelay(Runnable runnable, long initialDelay, long delay) { + if (service == null) { + service = new ScheduledThreadPoolExecutor(1); + } + return service.scheduleAtFixedRate(runnable, initialDelay, delay, TimeUnit.MILLISECONDS); + } + + public static void main(String[] args) { + TaskUtil.execTask(new Runnable() { + @Override + public void run() { + System.out.println("1"); + } + }, 3, 3000); + } + + /** + * 执行指定任务一定次数 + * + * @param runnable 任务 + * @param count 执行 次数 + * @param interval 间隔 + */ + public static void execTask(Runnable runnable, int count, long interval) { + for (int i = 0; i < count; i++) { + try { + runnable.run(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + // 控制重试时间间隔 + if (i < count) { + ThreadUtil.sleep(interval); + } + } + } + } + + /** + * 以获取结果为目的并支持重试的任务,通常为有失败风险的IO或异步操作 + * + * @param runnable 任务 + * @param maxTryCount 最大重试次数 + * @param interval 重试时间间隔 + * @param 结果 + */ + public static T retryTask(Callable runnable, int maxTryCount, long interval) { + for (int i = 0; i < maxTryCount; i++) { + T t = null; + try { + t = runnable.call(); + if (t != null) { + return t; + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + // 控制重试时间间隔 + if (t == null && i < maxTryCount - 1) { + ThreadUtil.sleep(interval); + } + } + } + + return null; + } + + public static T retryTask(Callable runnable) { + return retryTask(runnable, 3, 3000); + } + + public static T retryTask(Callable runnable, int maxTryCount) { + return retryTask(runnable, maxTryCount, 3000); + } + + /** + * 执限时任务 + * + * @param callable 任务 + * @param time 毫秒 + */ + public static T timeTask(Callable callable, long time, TimeUnit unit) { + Future submit = null; + try { + CompletionService completionService = ThreadUtil.newCompletionService(); + submit = completionService.submit(callable); + return submit.get(time, unit); + } catch (InterruptedException e) { + return null; + } catch (ExecutionException e) { + e.printStackTrace(); + return null; + } catch (TimeoutException e) { + submit.cancel(true); + return null; + } + } + + /** + * 执限时任务 + * + * @param callable 任务 + * @param time 毫秒 + */ + public static void timeTask(Runnable callable, long time, TimeUnit unit) { + try { + CompletionService completionService = ThreadUtil.newCompletionService(); + Future submit = completionService.submit(callable, null); + submit.get(time, unit); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 执行异步任务 + */ + public static void asyncTask(Runnable runnable) { + ThreadUtil.execAsync(runnable); + } + + /** + * 执行异步任务 + */ + public static Future asyncTask(Callable callables) { + return ThreadUtil.execAsync(callables); + } + + /** + * 并行任务(多线程) + */ + public static void parallelTasks(Runnable... runnables) { + Assert.notEmpty(runnables, "callable can not empty"); + CompletionService completionService = ThreadUtil.newCompletionService(); + for (Runnable runnable : runnables) { + completionService.submit(runnable, null); + } + } + + /** + * 并行任务(多线程) + * 可以保证结果也是按添加顺序返回 + */ + public static Object[] parallelTasks(Callable... callables) { + Assert.notEmpty(callables, "callable can not empty"); + CompletionService completionService = ThreadUtil.newCompletionService(); + Object[] result = new Object[callables.length]; + List futures = new ArrayList<>(); + for (Callable callable : callables) { + futures.add(completionService.submit(callable)); + } + + try { + for (int i = 0; i < result.length; i++) { + Future take = completionService.take(); + int indexOf = futures.indexOf(take); + result[indexOf] = take.get(); + System.out.println("result=" + result[indexOf]); + } + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } + + /** + * 并行任务/任务流. + * 可以依次运行任务,通过回调事件传递,能够保证任务中存在异步操作时也能按顺序执行 + */ + public static TaskStack flowTask(FlowTask task) { + return new TaskStack(task); + } + + /** + * 并行任务/任务流任务栈 + */ + public static class TaskStack { + private final Queue taskFlowQueue = new ConcurrentLinkedQueue(); + private final TaskEvent event = new TaskEvent() { + @Override + public void next(Object[] arg) { + FlowTask next = taskFlowQueue.poll(); + if (next == null) { + return; + } + next.onTask(event, arg); + } + }; + + public TaskStack(FlowTask first) { + this.taskFlowQueue.add(first); + } + + public void start(Object... arg) { + FlowTask first = this.taskFlowQueue.poll(); + if (first == null) { + return; + } + first.onTask(event, arg); + } + + public TaskStack then(FlowTask task) { + this.taskFlowQueue.add(task); + return this; + } + + } + + /** + * 并行任务/任务流事件 + */ + public interface TaskEvent { + + /** + * 执行下一步 + * + * @param arg 参数 + */ + void next(Object... arg); + } + + /** + * 任务 + */ + public interface FlowTask { + + /** + * 公式任务 + * + * @param flow 流 + * @param arg 参数 + */ + void onTask(TaskEvent flow, Object... arg); + } +} diff --git a/src/main/java/xyz/wbsite/itchat4j/util/ValueUtil.java b/src/main/java/xyz/wbsite/itchat4j/util/ValueUtil.java new file mode 100644 index 0000000..7ad7c5b --- /dev/null +++ b/src/main/java/xyz/wbsite/itchat4j/util/ValueUtil.java @@ -0,0 +1,45 @@ +package xyz.wbsite.itchat4j.util; + +/** + * 值计算工具 + *

+ * 一般有缓动变化才需要值计算 + * + * @author wangbing + * @version 0.0.1 + * @since 1.8 + */ +public class ValueUtil { + + /** + * 由快到慢 + * + * @param time 时间(0~1) + * @return 值(0~1) + */ + public static double easeOut(double time) { + if (time <= 0) { + return 0D; + } + if (time >= 1) { + return 1D; + } + return Math.sin(time * Math.PI / 2); + } + + /** + * 由慢到快 + * + * @param time 时间(0~1) + * @return 值(0~1) + */ + public static double easeIn(double time) { + if (time <= 0) { + return 0D; + } + if (time >= 1) { + return 1D; + } + return 1 - Math.cos(time * Math.PI / 2); + } +} diff --git a/src/main/resources/icon.ico b/src/main/resources/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..061bb64cf6ef927f4df1acfbd63c7a77af7fe10e GIT binary patch literal 38078 zcmeI5O^D<~6vwj+ID)W);s?U&EGj61C(#|Hzhq(X8#_NpnqFi4*fjseDQ(f-0nEe10>qyTqWu4@)80NLO*CXyWX3uRNo;NVf{Qdv)3iF*JbLsFz>3}F8r(I zot)po<1QzEn$uq<8t?O*-y7!m*$aa^SV`M;KXSEqm-Y|U^}cPfz$fP+UFOL%xNm{& zkJn>4IV66Bf5HOdpfJA2`CL-|jf)32vnpeG9P0_c=QWNlA9K|$EK{4Cz2VET+-nau zBjJ4KZMvkj#Y-%c&o~cJK0-fvY-X=Z<8deOsJgE3&vib_ox7HnzkB#_BU05decbWd zUGEbX&T(2N#Gk*=2Y=qu<(cQyNfv$fdX0sC+I_Fd>bU-WEUbh6TYc{@7S_pQGkaa8 zjGRI>H{~GXz^#+ceX^vx^wC(>!`6v*L`)90F9XJ;H$2dV7 z>)6`0$v?mZS(*Bab<(!*PxJ6pmRPdA-#vbe;I`%|pGKcMSEptx}NvCV%=7?kLoqF{e$t3_E>&MavfZ+c*6DXXW_oQO_$_n@_jDI zZ+H&~{p7Lhn18Up!asj5vdurjKRpPxCi*QGW8uK!Fk68fRN56h(Awrw`Gp_cha_|LqyOT}xnKC+D^j(uIG{7L-JG%oi1 zhy7+ZREmFky?dR0$NHOW*6(Bo!sAZyGqF#&uPL`=*&Ldu zOBMg{Y$983Mb#Jn;q#%2;%Vzg_zQpAm@(D2jU~E2^(xEcv+V7*ZCbUcYbJKC_68|_}roPmD z`y%Hb;XmK}BmC#fpFZ=)&!Wd?Le@R!*k02p>O6??oF(rGe~JGx=Gxrizc)P7h?FJ% zO?7ZS*2Sj2{0XwWr|N%2f0Gy}PdqStU7FZt*v}{Ac4<)VwZ=Kc2hkIi{CbMYVIO{Peva=y}qCkXunUv)AQP zakF0A80MDnw_GC{IL^guNaI;jhP_4Cd%l_=o@33&*}LIrgjIk7FUW5D)@FKnMr{As_^VfDjM@LO=*i zC!mUd?SjiO+o#5?T-jJZXe(u~k92$2uQ<*tZDq2zb)A(RHCj_@s=dCmyS@|EnP4A~ zo7IuCy8o|I$JiSy)aIy;!M?n@AJ;M1duwWcJMv?+Pj1BR*)!PcJ*B8$L3@n$&h9DV z6V@@=N6yMo)E=Te(H>pi(Bl>M-DIz+5sg<^$6%`+8gV|B$=0LEb#mAva>ITm+F^+9 z1$A=RtKq&Q?Z;@Z2bF?jtxmRePs3L`jOXA@+SY7?*9jGHo@z(q9@H7L)zx6DPx&yGcYrR9HvNmtBY(RTzffZ)Ufd*)A2OP^dSRUI;B>aW9IJNp>k!Y*pG!QmEJq zp+7g)AH*BIP%o_DFVw60gMy&aNm8k`sj@9z2ulT};vcl4yHc&)Owyh6>PdGt z$!2$(WV6Hrfn=C-&U4=H``+_K(B&ar-XH%@fWS?@0B|dTO&wE6a9${9R4*Uy`@sJ- zTDL)gAe}r&@^HsCp1Y9Xb35zbIWGV+x8D$`-pOU#JX)B1(9Rd$(Ld`Hm~!Kr1;$t- z07u$~l63&|XLbmXw{pJQ08mWF2SMXxRUSDTquMI@!iDw$fWS>YM(~yD8XU26g`sN# zK-7?(J+*|>SGO#>3APS^D(O-bm`W!zLLO}cpfcsOB2LT)urlvI6FtA2O3Z$*eb*G+ zM!^7xwe12daQJ$48sof}X3gfnqPo`u6w}ULP~K=RRLcS2Iu8KyZr1>&QchY#ycvKi z044xT0-OM3upVp~6bMqzc8Yuq;t0&}knMf>OSEZm$KX1eVa9;$A#e+7s;kZk=Ni%C zayI0^7EGHz*dLDkR7xdxPJ_1^!XzPMLX2ChjC}ELtQJvk0bJ3X5w(znv!`_vpZr(4 zch8+Fkh5Z_T`{llZQv$ev~vCv*LK1d0V20k#IvSpzHfOazY8)$H&cZ>0X$z>E;Me^ zDpl>iW|O6aAV?=(BJp@AxT`tm)|3Y7d#;Ba5gf23myXsHds({|3UR8^D<0*n*<29P~EFLXGHu2Z@0J6Z*S zo7hHRSVXpk3KNF#jDQ{?!{LhF6K46!Z(4TCoIYOZxF~Ph`O$q#R6zGrSNjERG%(eV z0N(rSlC}T3Rl)a$l80jX(PP{_xH>4SKMaE#m6H2(HAHGO09?+*2MlF=-JGA#yft&0 zz)c({aIek-I~IIeD#i9$S^wo?I_ZeW1ayq)!LoVN@w-XFg{Rx^ga zu7x>WiW*vi2TcQCXwxZWoDB?(cqRMwAcpO{f6u%C`r*|KmP{)Uk);3Z-m<2rTz+kV z`C}j^A=nP`kex3))ewSNJ54)#g~p= + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/cn/zhouyafeng/itchat4j/demo/demo1/MyTest.java b/src/test/java/cn/zhouyafeng/itchat4j/demo/demo1/MyTest.java new file mode 100644 index 0000000..88285d9 --- /dev/null +++ b/src/test/java/cn/zhouyafeng/itchat4j/demo/demo1/MyTest.java @@ -0,0 +1,33 @@ +package cn.zhouyafeng.itchat4j.demo.demo1; + +import cn.zhouyafeng.itchat4j.Wechat; +import cn.zhouyafeng.itchat4j.face.IMsgHandlerFace; + +import java.io.File; + +/** + * + * @author https://github.com/yaphone + * @date 创建时间:2017年4月28日 上午12:44:10 + * @version 1.0 + * + */ +public class MyTest { + public static void main(String[] args) { + String qrPath = "D://itchat4j//login"; // 保存登陆二维码图片的路径,这里需要在本地新建目录 + File folder = new File(qrPath); + if (!folder.exists()) { + boolean success = folder.mkdirs(); + if (success) { + System.out.println("文件夹创建成功"); + } else { + System.out.println("文件夹创建失败"); + } + } else { + System.out.println("文件夹已存在"); + } + IMsgHandlerFace msgHandler = new SimpleDemo(); // 实现IMsgHandlerFace接口的类 + Wechat wechat = new Wechat(msgHandler, qrPath); // 【注入】 + wechat.start(); // 启动服务,会在qrPath下生成一张二维码图片,扫描即可登陆,注意,二维码图片如果超过一定时间未扫描会过期,过期时会自动更新,所以你可能需要重新打开图片 + } +} diff --git a/src/test/java/cn/zhouyafeng/itchat4j/demo/demo1/SimpleDemo.java b/src/test/java/cn/zhouyafeng/itchat4j/demo/demo1/SimpleDemo.java new file mode 100644 index 0000000..072f306 --- /dev/null +++ b/src/test/java/cn/zhouyafeng/itchat4j/demo/demo1/SimpleDemo.java @@ -0,0 +1,108 @@ +package cn.zhouyafeng.itchat4j.demo.demo1; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.apache.log4j.Logger; + +import cn.zhouyafeng.itchat4j.api.MessageTools; +import cn.zhouyafeng.itchat4j.api.WechatTools; +import cn.zhouyafeng.itchat4j.beans.BaseMsg; +import cn.zhouyafeng.itchat4j.beans.RecommendInfo; +import cn.zhouyafeng.itchat4j.core.Core; +import cn.zhouyafeng.itchat4j.face.IMsgHandlerFace; +import cn.zhouyafeng.itchat4j.utils.enums.MsgTypeEnum; +import cn.zhouyafeng.itchat4j.utils.tools.DownloadTools; + +/** + * 简单示例程序,收到文本信息自动回复原信息,收到图片、语音、小视频后根据路径自动保存 + * + * @author https://github.com/yaphone + * @date 创建时间:2017年4月25日 上午12:18:09 + * @version 1.0 + * + */ +public class SimpleDemo implements IMsgHandlerFace { + Logger LOG = Logger.getLogger(SimpleDemo.class); + + @Override + public String textMsgHandle(BaseMsg msg) { + // String docFilePath = "D:/itchat4j/pic/1.jpg"; // 这里是需要发送的文件的路径 + if (!msg.isGroupMsg()) { // 群消息不处理 + // String userId = msg.getString("FromUserName"); + // MessageTools.sendFileMsgByUserId(userId, docFilePath); // 发送文件 + // MessageTools.sendPicMsgByUserId(userId, docFilePath); + String text = msg.getText(); // 发送文本消息,也可调用MessageTools.sendFileMsgByUserId(userId,text); + LOG.info(text); + if (text.equals("111")) { + WechatTools.logout(); + } + if (text.equals("222")) { + WechatTools.remarkNameByNickName("yaphone", "Hello"); + } + if (text.equals("333")) { // 测试群列表 + System.out.print(WechatTools.getGroupNickNameList()); + System.out.print(WechatTools.getGroupIdList()); + System.out.print(Core.getInstance().getGroupMemeberMap()); + } + return text; + } + return null; + } + + @Override + public String picMsgHandle(BaseMsg msg) { + String fileName = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date());// 这里使用收到图片的时间作为文件名 + String picPath = "D://itchat4j/pic" + File.separator + fileName + ".jpg"; // 调用此方法来保存图片 + DownloadTools.getDownloadFn(msg, MsgTypeEnum.PIC.getType(), picPath); // 保存图片的路径 + return "图片保存成功"; + } + + @Override + public String voiceMsgHandle(BaseMsg msg) { + String fileName = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date()); + String voicePath = "D://itchat4j/voice" + File.separator + fileName + ".mp3"; + DownloadTools.getDownloadFn(msg, MsgTypeEnum.VOICE.getType(), voicePath); + return "声音保存成功"; + } + + @Override + public String viedoMsgHandle(BaseMsg msg) { + String fileName = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date()); + String viedoPath = "D://itchat4j/viedo" + File.separator + fileName + ".mp4"; + DownloadTools.getDownloadFn(msg, MsgTypeEnum.VIEDO.getType(), viedoPath); + return "视频保存成功"; + } + + @Override + public String nameCardMsgHandle(BaseMsg msg) { + return "收到名片消息"; + } + + @Override + public void sysMsgHandle(BaseMsg msg) { // 收到系统消息 + String text = msg.getContent(); + LOG.info(text); + } + + @Override + public String verifyAddFriendMsgHandle(BaseMsg msg) { + MessageTools.addFriend(msg, true); // 同意好友请求,false为不接受好友请求 + RecommendInfo recommendInfo = msg.getRecommendInfo(); + String nickName = recommendInfo.getNickName(); + String province = recommendInfo.getProvince(); + String city = recommendInfo.getCity(); + String text = "你好,来自" + province + city + "的" + nickName + ", 欢迎添加我为好友!"; + return text; + } + + @Override + public String mediaMsgHandle(BaseMsg msg) { + String fileName = msg.getFileName(); + String filePath = "D://itchat4j/file" + File.separator + fileName; // 这里是需要保存收到的文件路径,文件可以是任何格式如PDF,WORD,EXCEL等。 + DownloadTools.getDownloadFn(msg, MsgTypeEnum.MEDIA.getType(), filePath); + return "文件" + fileName + "保存成功"; + } + +} diff --git a/src/test/java/cn/zhouyafeng/itchat4j/demo/demo2/TulingRobot.java b/src/test/java/cn/zhouyafeng/itchat4j/demo/demo2/TulingRobot.java new file mode 100644 index 0000000..1fa6cc6 --- /dev/null +++ b/src/test/java/cn/zhouyafeng/itchat4j/demo/demo2/TulingRobot.java @@ -0,0 +1,111 @@ +package cn.zhouyafeng.itchat4j.demo.demo2; + +import java.io.File; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +import org.apache.http.HttpEntity; +import org.apache.http.util.EntityUtils; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; + +import cn.zhouyafeng.itchat4j.Wechat; +import cn.zhouyafeng.itchat4j.beans.BaseMsg; +import cn.zhouyafeng.itchat4j.core.Core; +import cn.zhouyafeng.itchat4j.face.IMsgHandlerFace; +import cn.zhouyafeng.itchat4j.utils.MyHttpClient; +import cn.zhouyafeng.itchat4j.utils.enums.MsgTypeEnum; +import cn.zhouyafeng.itchat4j.utils.tools.DownloadTools; + +/** + * 图灵机器人示例 + * + * @author https://github.com/yaphone + * @date 创建时间:2017年4月24日 上午12:13:26 + * @version 1.0 + * + */ +public class TulingRobot implements IMsgHandlerFace { + Logger logger = Logger.getLogger("TulingRobot"); + MyHttpClient myHttpClient = Core.getInstance().getMyHttpClient(); + String url = "http://www.tuling123.com/openapi/api"; + String apiKey = "597b34bea4ec4c85a775c469c84b6817"; // 这里是我申请的图灵机器人API接口,每天只能5000次调用,建议自己去申请一个,免费的:) + + @Override + public String textMsgHandle(BaseMsg msg) { + String result = ""; + String text = msg.getText(); + Map paramMap = new HashMap(); + paramMap.put("key", apiKey); + paramMap.put("info", text); + paramMap.put("userid", "123456"); + String paramStr = JSON.toJSONString(paramMap); + try { + HttpEntity entity = myHttpClient.doPost(url, paramStr); + result = EntityUtils.toString(entity, "UTF-8"); + JSONObject obj = JSON.parseObject(result); + if (obj.getString("code").equals("100000")) { + result = obj.getString("text"); + } else { + result = "处理有误"; + } + } catch (Exception e) { + logger.info(e.getMessage()); + } + return result; + } + + @Override + public String picMsgHandle(BaseMsg msg) { + return "收到图片"; + } + + @Override + public String voiceMsgHandle(BaseMsg msg) { + String fileName = String.valueOf(new Date().getTime()); + String voicePath = "D://itchat4j/voice" + File.separator + fileName + ".mp3"; + DownloadTools.getDownloadFn(msg, MsgTypeEnum.VOICE.getType(), voicePath); + return "收到语音"; + } + + @Override + public String viedoMsgHandle(BaseMsg msg) { + String fileName = String.valueOf(new Date().getTime()); + String viedoPath = "D://itchat4j/viedo" + File.separator + fileName + ".mp4"; + DownloadTools.getDownloadFn(msg, MsgTypeEnum.VIEDO.getType(), viedoPath); + return "收到视频"; + } + + public static void main(String[] args) { + IMsgHandlerFace msgHandler = new TulingRobot(); + Wechat wechat = new Wechat(msgHandler, "D://itchat4j/login"); + wechat.start(); + } + + @Override + public String nameCardMsgHandle(BaseMsg msg) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void sysMsgHandle(BaseMsg msg) { + // TODO Auto-generated method stub + } + + @Override + public String verifyAddFriendMsgHandle(BaseMsg msg) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String mediaMsgHandle(BaseMsg msg) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/src/test/java/cn/zhouyafeng/itchat4j/demo/demo3/PicYourFriends.java b/src/test/java/cn/zhouyafeng/itchat4j/demo/demo3/PicYourFriends.java new file mode 100644 index 0000000..50e23a1 --- /dev/null +++ b/src/test/java/cn/zhouyafeng/itchat4j/demo/demo3/PicYourFriends.java @@ -0,0 +1,120 @@ +package cn.zhouyafeng.itchat4j.demo.demo3; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.util.List; + +import org.apache.http.HttpEntity; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.fastjson.JSONObject; + +import cn.zhouyafeng.itchat4j.Wechat; +import cn.zhouyafeng.itchat4j.api.WechatTools; +import cn.zhouyafeng.itchat4j.beans.BaseMsg; +import cn.zhouyafeng.itchat4j.core.Core; +import cn.zhouyafeng.itchat4j.face.IMsgHandlerFace; +import cn.zhouyafeng.itchat4j.utils.MyHttpClient; +import cn.zhouyafeng.itchat4j.utils.enums.StorageLoginInfoEnum; + +/** + * 此示例演示如何获取所有好友的头像 + * + * @author https://github.com/yaphone + * @date 创建时间:2017年6月26日 下午11:27:46 + * @version 1.0 + * + */ +public class PicYourFriends implements IMsgHandlerFace { + private static Logger LOG = LoggerFactory.getLogger(PicYourFriends.class); + private static final Core core = Core.getInstance(); + private static final MyHttpClient myHttpClient = core.getMyHttpClient(); + private static final String path = "D://itchat4j//head"; // 这里需要设置保存头像的路径 + + @Override + public String textMsgHandle(BaseMsg msg) { + + if (!msg.isGroupMsg()) { // 群消息不处理 + String text = msg.getText(); // 发送文本消息,也可调用MessageTools.sendFileMsgByUserId(userId,text); + String baseUrl = "https://" + core.getIndexUrl(); // 基础URL + String skey = (String) core.getLoginInfo().get(StorageLoginInfoEnum.skey.getKey()); + if (text.equals("111")) { + LOG.info("开始下载好友头像"); + List friends = WechatTools.getContactList(); + for (int i = 0; i < friends.size(); i++) { + JSONObject friend = friends.get(i); + String url = baseUrl + friend.getString("HeadImgUrl") + skey; + // String fileName = friend.getString("NickName"); + String headPicPath = path + File.separator + i + ".jpg"; + + HttpEntity entity = myHttpClient.doGet(url, null, true, null); + try { + OutputStream out = new FileOutputStream(headPicPath); + byte[] bytes = EntityUtils.toByteArray(entity); + out.write(bytes); + out.flush(); + out.close(); + + } catch (Exception e) { + LOG.info(e.getMessage()); + } + + } + } + } + return null; + } + + @Override + public String picMsgHandle(BaseMsg msg) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String voiceMsgHandle(BaseMsg msg) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String viedoMsgHandle(BaseMsg msg) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String nameCardMsgHandle(BaseMsg msg) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void sysMsgHandle(BaseMsg msg) { + // TODO Auto-generated method stub + + } + + public static void main(String[] args) { + String qrPath = "D://itchat4j//login"; // 保存登陆二维码图片的路径,这里需要在本地新建目录 + IMsgHandlerFace msgHandler = new PicYourFriends(); // 实现IMsgHandlerFace接口的类 + Wechat wechat = new Wechat(msgHandler, qrPath); // 【注入】 + wechat.start(); // 启动服务,会在qrPath下生成一张二维码图片,扫描即可登陆,注意,二维码图片如果超过一定时间未扫描会过期,过期时会自动更新,所以你可能需要重新打开图片 + } + + @Override + public String verifyAddFriendMsgHandle(BaseMsg msg) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String mediaMsgHandle(BaseMsg msg) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/src/test/java/cn/zhouyafeng/itchat4j/demo/unuseful/UnusefulDemo.java b/src/test/java/cn/zhouyafeng/itchat4j/demo/unuseful/UnusefulDemo.java new file mode 100644 index 0000000..fe1625d --- /dev/null +++ b/src/test/java/cn/zhouyafeng/itchat4j/demo/unuseful/UnusefulDemo.java @@ -0,0 +1,87 @@ +package cn.zhouyafeng.itchat4j.demo.unuseful; + +import java.io.IOException; + +import cn.zhouyafeng.itchat4j.Wechat; +import cn.zhouyafeng.itchat4j.api.AssistTools; +import cn.zhouyafeng.itchat4j.beans.BaseMsg; +import cn.zhouyafeng.itchat4j.face.IMsgHandlerFace; + +/** + * 自用的测试类,请无视 + * + * @author https://github.com/yaphone + * @date 创建时间:2017年5月22日 下午10:41:44 + * @version 1.0 + * + */ +public class UnusefulDemo implements IMsgHandlerFace { + + @Override + public String textMsgHandle(BaseMsg msg) { + if (!msg.isGroupMsg()) { // 群消息不处理 + String text = msg.getText(); // 发送文本消息,也可调用MessageTools.sendFileMsgByUserId(userId,text); + if (text.equals("111")) { + String username = "yaphone"; + String password = "123456"; + String localPath = "D://itchat4j/pic/1.jpg"; + String uploadUrl = "http://127.0.0.1/file/put"; + try { + AssistTools.sendQrPicToServer(username, password, uploadUrl, localPath); + } catch (IOException e) { + e.printStackTrace(); + } + } + return text; + } + return null; + } + + @Override + public String picMsgHandle(BaseMsg msg) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String voiceMsgHandle(BaseMsg msg) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String viedoMsgHandle(BaseMsg msg) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String nameCardMsgHandle(BaseMsg msg) { + // TODO Auto-generated method stub + return null; + } + + public static void main(String[] args) { + IMsgHandlerFace msgHandler = new UnusefulDemo(); + Wechat wechat = new Wechat(msgHandler, "D://itchat4j/login"); + wechat.start(); + } + + @Override + public void sysMsgHandle(BaseMsg msg) { + // TODO Auto-generated method stub + } + + @Override + public String verifyAddFriendMsgHandle(BaseMsg msg) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String mediaMsgHandle(BaseMsg msg) { + // TODO Auto-generated method stub + return null; + } + +}