├── .gitignore ├── src └── main │ └── java │ ├── me │ └── biezhi │ │ └── wechat │ │ ├── model │ │ ├── WechatGroup.java │ │ ├── WechatContact.java │ │ └── WechatMeta.java │ │ ├── robot │ │ ├── Robot.java │ │ └── MoLiRobot.java │ │ ├── util │ │ ├── Matchers.java │ │ └── CookieUtil.java │ │ ├── exception │ │ └── WechatException.java │ │ ├── Application.java │ │ ├── service │ │ ├── WechatService.java │ │ └── WechatServiceImpl.java │ │ ├── Constant.java │ │ ├── listener │ │ └── WechatListener.java │ │ ├── ui │ │ └── QRCodeFrame.java │ │ └── WechatRobot.java │ ├── config.properties │ └── log4j.properties ├── pom.xml ├── README.md └── doc └── protocol.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | -------------------------------------------------------------------------------- /src/main/java/me/biezhi/wechat/model/WechatGroup.java: -------------------------------------------------------------------------------- 1 | package me.biezhi.wechat.model; 2 | 3 | public class WechatGroup { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/config.properties: -------------------------------------------------------------------------------- 1 | # request url : http://www.itpk.cn/robot.php 2 | itpk.api_key= 3 | itpk.api_secret= 4 | 5 | # image save path 6 | app.img_path=./img -------------------------------------------------------------------------------- /src/main/java/me/biezhi/wechat/robot/Robot.java: -------------------------------------------------------------------------------- 1 | package me.biezhi.wechat.robot; 2 | 3 | public interface Robot { 4 | 5 | String talk(String msg); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=info, stdout 2 | 3 | log4j.appender.stdout = org.apache.log4j.ConsoleAppender 4 | log4j.appender.stdout.layout = org.apache.log4j.PatternLayout 5 | log4j.appender.stdout.layout.ConversionPattern = [wechat-robot] %d %-5p[%t] %c => %m%n -------------------------------------------------------------------------------- /src/main/java/me/biezhi/wechat/util/Matchers.java: -------------------------------------------------------------------------------- 1 | package me.biezhi.wechat.util; 2 | 3 | import java.util.regex.Matcher; 4 | import java.util.regex.Pattern; 5 | 6 | public class Matchers { 7 | 8 | public static String match(String p, String str){ 9 | Pattern pattern = Pattern.compile(p); 10 | Matcher m = pattern.matcher(str); 11 | if(m.find()){ 12 | return m.group(1); 13 | } 14 | return null; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/me/biezhi/wechat/exception/WechatException.java: -------------------------------------------------------------------------------- 1 | package me.biezhi.wechat.exception; 2 | 3 | public class WechatException extends RuntimeException { 4 | 5 | private static final long serialVersionUID = 209248116271894410L; 6 | 7 | public WechatException() { 8 | super(); 9 | } 10 | 11 | public WechatException(String message) { 12 | super(message); 13 | } 14 | 15 | public WechatException(Throwable cause) { 16 | super(cause); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/me/biezhi/wechat/Application.java: -------------------------------------------------------------------------------- 1 | package me.biezhi.wechat; 2 | 3 | import com.blade.kit.base.Config; 4 | 5 | public class Application { 6 | 7 | public static void main(String[] args) { 8 | try { 9 | 10 | Constant.config = Config.load("classpath:config.properties"); 11 | 12 | WechatRobot wechatRobot = new WechatRobot(); 13 | wechatRobot.showQrCode(); 14 | while(!Constant.HTTP_OK.equals(wechatRobot.waitForLogin())){ 15 | Thread.sleep(2000); 16 | } 17 | wechatRobot.closeQrWindow(); 18 | wechatRobot.start(); 19 | } catch (Exception e) { 20 | e.printStackTrace(); 21 | } 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /src/main/java/me/biezhi/wechat/robot/MoLiRobot.java: -------------------------------------------------------------------------------- 1 | package me.biezhi.wechat.robot; 2 | 3 | import com.blade.kit.StringKit; 4 | import com.blade.kit.http.HttpRequest; 5 | 6 | import me.biezhi.wechat.Constant; 7 | 8 | public class MoLiRobot implements Robot { 9 | 10 | private String apiUrl; 11 | 12 | public MoLiRobot() { 13 | String api_key = Constant.config.get("itpk.api_key"); 14 | String api_secret = Constant.config.get("itpk.api_secret"); 15 | if(StringKit.isNotBlank(api_key) && StringKit.isNotBlank(api_secret)){ 16 | this.apiUrl = Constant.ITPK_API + "?api_key=" + api_key + "&api_secret=" + api_secret; 17 | } 18 | } 19 | 20 | @Override 21 | public String talk(String msg) { 22 | if(null == this.apiUrl){ 23 | return "机器人未配置"; 24 | } 25 | String url = apiUrl + "&question=" + msg; 26 | String result = HttpRequest.get(url).connectTimeout(3000).body(); 27 | return result; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/me/biezhi/wechat/model/WechatContact.java: -------------------------------------------------------------------------------- 1 | package me.biezhi.wechat.model; 2 | 3 | import com.blade.kit.json.JSONArray; 4 | 5 | public class WechatContact { 6 | 7 | // 微信联系人列表,可聊天的联系人列表 8 | private JSONArray memberList; 9 | private JSONArray contactList; 10 | private JSONArray groupList; 11 | 12 | public WechatContact() { 13 | // TODO Auto-generated constructor stub 14 | } 15 | 16 | public JSONArray getMemberList() { 17 | return memberList; 18 | } 19 | 20 | public void setMemberList(JSONArray memberList) { 21 | this.memberList = memberList; 22 | } 23 | 24 | public JSONArray getContactList() { 25 | return contactList; 26 | } 27 | 28 | public void setContactList(JSONArray contactList) { 29 | this.contactList = contactList; 30 | } 31 | 32 | public JSONArray getGroupList() { 33 | return groupList; 34 | } 35 | 36 | public void setGroupList(JSONArray groupList) { 37 | this.groupList = groupList; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/me/biezhi/wechat/util/CookieUtil.java: -------------------------------------------------------------------------------- 1 | package me.biezhi.wechat.util; 2 | 3 | import java.net.HttpURLConnection; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import com.blade.kit.http.HttpRequest; 8 | 9 | public class CookieUtil { 10 | 11 | public static String getCookie(HttpRequest request) { 12 | HttpURLConnection conn = request.getConnection(); 13 | Map> resHeaders = conn.getHeaderFields(); 14 | StringBuffer sBuffer = new StringBuffer(); 15 | for (Map.Entry> entry : resHeaders.entrySet()) { 16 | String name = entry.getKey(); 17 | if (name == null) 18 | continue; // http/1.1 line 19 | List values = entry.getValue(); 20 | if (name.equalsIgnoreCase("Set-Cookie")) { 21 | for (String value : values) { 22 | if (value == null) { 23 | continue; 24 | } 25 | String cookie = value.substring(0, value.indexOf(";") + 1); 26 | sBuffer.append(cookie); 27 | } 28 | } 29 | } 30 | if(sBuffer.length() > 0){ 31 | return sBuffer.toString(); 32 | } 33 | return sBuffer.toString(); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | me.biezhi 6 | wechat-robot 7 | 0.0.1 8 | jar 9 | 10 | wechat-robot 11 | http://maven.apache.org 12 | 13 | 14 | UTF-8 15 | 16 | 17 | 18 | 19 | com.bladejava 20 | blade-kit 21 | 1.3.4 22 | 23 | 24 | org.slf4j 25 | slf4j-log4j12 26 | 1.7.21 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | org.apache.maven.plugins 35 | maven-compiler-plugin 36 | 3.2 37 | 38 | 1.6 39 | 1.6 40 | UTF8 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/main/java/me/biezhi/wechat/service/WechatService.java: -------------------------------------------------------------------------------- 1 | package me.biezhi.wechat.service; 2 | 3 | import com.blade.kit.json.JSONObject; 4 | 5 | import me.biezhi.wechat.exception.WechatException; 6 | import me.biezhi.wechat.model.WechatContact; 7 | import me.biezhi.wechat.model.WechatMeta; 8 | 9 | public interface WechatService { 10 | 11 | /** 12 | * 获取UUID 13 | * @return 14 | */ 15 | String getUUID() throws WechatException; 16 | 17 | /** 18 | * 微信初始化 19 | * @param wechatMeta 20 | * @throws WechatException 21 | */ 22 | void wxInit(WechatMeta wechatMeta) throws WechatException; 23 | 24 | /** 25 | * 开启状态通知 26 | * @return 27 | */ 28 | void openStatusNotify(WechatMeta wechatMeta) throws WechatException; 29 | 30 | /** 31 | * 获取联系人 32 | * @param wechatMeta 33 | * @return 34 | */ 35 | WechatContact getContact(WechatMeta wechatMeta) throws WechatException; 36 | 37 | /** 38 | * 选择同步线路 39 | * 40 | * @param wechatMeta 41 | * @return 42 | * @throws WechatException 43 | */ 44 | void choiceSyncLine(WechatMeta wechatMeta) throws WechatException; 45 | 46 | /** 47 | * 消息检查 48 | * @param wechatMeta 49 | * @return 50 | */ 51 | int[] syncCheck(WechatMeta wechatMeta) throws WechatException; 52 | 53 | /** 54 | * 处理聊天信息 55 | * @param wechatRequest 56 | * @param data 57 | */ 58 | void handleMsg(WechatMeta wechatMeta, JSONObject data) throws WechatException; 59 | 60 | /** 61 | * 获取最新消息 62 | * @param meta 63 | * @return 64 | */ 65 | JSONObject webwxsync(WechatMeta meta) throws WechatException; 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/me/biezhi/wechat/Constant.java: -------------------------------------------------------------------------------- 1 | package me.biezhi.wechat; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | import com.blade.kit.base.Config; 7 | 8 | import me.biezhi.wechat.model.WechatContact; 9 | 10 | public class Constant { 11 | 12 | public static final String HTTP_OK = "200"; 13 | public static final String BASE_URL = "https://webpush2.weixin.qq.com/cgi-bin/mmwebwx-bin"; 14 | public static final String JS_LOGIN_URL = "https://login.weixin.qq.com/jslogin"; 15 | public static final String QRCODE_URL = "https://login.weixin.qq.com/qrcode/"; 16 | 17 | public static final String ITPK_API = "http://i.itpk.cn/api.php"; 18 | 19 | // 特殊用户 须过滤 20 | public static final List FILTER_USERS = Arrays.asList("newsapp", "fmessage", "filehelper", "weibo", "qqmail", 21 | "fmessage", "tmessage", "qmessage", "qqsync", "floatbottle", "lbsapp", "shakeapp", "medianote", "qqfriend", 22 | "readerapp", "blogapp", "facebookapp", "masssendapp", "meishiapp", "feedsapp", "voip", "blogappweixin", 23 | "weixin", "brandsessionholder", "weixinreminder", "wxid_novlwrv3lqwv11", "gh_22b87fa7cb3c", "officialaccounts", 24 | "notification_messages", "wxid_novlwrv3lqwv11", "gh_22b87fa7cb3c", "wxitil", "userexperience_alarm", 25 | "notification_messages"); 26 | 27 | public static final String[] SYNC_HOST = { 28 | "webpush.weixin.qq.com", 29 | "webpush2.weixin.qq.com", 30 | "webpush.wechat.com", 31 | "webpush1.wechat.com", 32 | "webpush2.wechat.com", 33 | "webpush1.wechatapp.com" 34 | }; 35 | 36 | public static WechatContact CONTACT; 37 | 38 | // 全局配置文件 39 | public static Config config; 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/me/biezhi/wechat/listener/WechatListener.java: -------------------------------------------------------------------------------- 1 | package me.biezhi.wechat.listener; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import com.blade.kit.json.JSONObject; 7 | 8 | import me.biezhi.wechat.model.WechatMeta; 9 | import me.biezhi.wechat.service.WechatService; 10 | 11 | public class WechatListener { 12 | 13 | private static final Logger LOGGER = LoggerFactory.getLogger(WechatListener.class); 14 | 15 | int playWeChat = 0; 16 | 17 | public void start(final WechatService wechatService, final WechatMeta wechatMeta){ 18 | new Thread(new Runnable() { 19 | @Override 20 | public void run() { 21 | LOGGER.info("进入消息监听模式 ..."); 22 | wechatService.choiceSyncLine(wechatMeta); 23 | while(true){ 24 | int[] arr = wechatService.syncCheck(wechatMeta); 25 | LOGGER.info("retcode={}, selector={}", arr[0], arr[1]); 26 | 27 | if(arr[0] == 1100){ 28 | LOGGER.info("你在手机上登出了微信,债见"); 29 | break; 30 | } 31 | if(arr[0] == 0){ 32 | if(arr[1] == 2){ 33 | JSONObject data = wechatService.webwxsync(wechatMeta); 34 | wechatService.handleMsg(wechatMeta, data); 35 | } else if(arr[1] == 6){ 36 | JSONObject data = wechatService.webwxsync(wechatMeta); 37 | wechatService.handleMsg(wechatMeta, data); 38 | } else if(arr[1] == 7){ 39 | playWeChat += 1; 40 | LOGGER.info("你在手机上玩微信被我发现了 {} 次", playWeChat); 41 | wechatService.webwxsync(wechatMeta); 42 | } else if(arr[1] == 3){ 43 | continue; 44 | } else if(arr[1] == 0){ 45 | continue; 46 | } 47 | } else { 48 | // 49 | } 50 | try { 51 | LOGGER.info("等待2000ms..."); 52 | Thread.sleep(2000); 53 | } catch (InterruptedException e) { 54 | e.printStackTrace(); 55 | } 56 | } 57 | } 58 | }, "wechat-listener-thread").start(); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/me/biezhi/wechat/ui/QRCodeFrame.java: -------------------------------------------------------------------------------- 1 | package me.biezhi.wechat.ui; 2 | 3 | import java.awt.EventQueue; 4 | import java.awt.Graphics; 5 | 6 | import javax.swing.ImageIcon; 7 | import javax.swing.JFrame; 8 | import javax.swing.JLabel; 9 | import javax.swing.JPanel; 10 | import javax.swing.UIManager; 11 | import javax.swing.border.EmptyBorder; 12 | import javax.swing.SwingConstants; 13 | import java.awt.Font; 14 | import java.awt.Color; 15 | 16 | public class QRCodeFrame extends JFrame { 17 | 18 | private static final long serialVersionUID = 8550014433017811556L; 19 | private JPanel contentPane; 20 | 21 | /** 22 | * Launch the application. 23 | */ 24 | public static void main(String[] args) { 25 | EventQueue.invokeLater(new Runnable() { 26 | public void run() { 27 | try { 28 | UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel"); 29 | new QRCodeFrame("d:/a.png"); 30 | } catch (Exception e) { 31 | e.printStackTrace(); 32 | } 33 | } 34 | }); 35 | } 36 | 37 | /** 38 | * Create the frame. 39 | */ 40 | @SuppressWarnings("serial") 41 | public QRCodeFrame(final String filePath) { 42 | setBackground(Color.WHITE); 43 | this.setResizable(false); 44 | this.setTitle("\u8BF7\u7528\u624B\u673A\u626B\u63CF\u5FAE\u4FE1\u4E8C\u7EF4\u7801"); 45 | this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 46 | this.setBounds(100, 100, 297, 362); 47 | this.contentPane = new JPanel() ; 48 | contentPane.setBackground(new Color(102, 153, 255)); 49 | this.contentPane.setBorder(new EmptyBorder(5, 5, 5, 5)); 50 | this.setContentPane(contentPane); 51 | contentPane.setLayout(null); 52 | 53 | JPanel qrcodePanel = new JPanel(){ 54 | public void paintComponent(Graphics g) { 55 | ImageIcon icon = new ImageIcon(filePath); 56 | // 图片随窗体大小而变化 57 | g.drawImage(icon.getImage(), 0, 0, 301, 301, this); 58 | } 59 | }; 60 | qrcodePanel.setBounds(0, 0, 295, 295); 61 | 62 | JLabel tipLable = new JLabel("扫描二维码登录微信"); 63 | tipLable.setFont(new Font("微软雅黑", Font.PLAIN, 18)); 64 | tipLable.setHorizontalAlignment(SwingConstants.CENTER); 65 | tipLable.setBounds(0, 297, 291, 37); 66 | 67 | contentPane.add(qrcodePanel); 68 | contentPane.add(tipLable); 69 | 70 | this.setLocationRelativeTo(null); 71 | this.setVisible(true); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/me/biezhi/wechat/model/WechatMeta.java: -------------------------------------------------------------------------------- 1 | package me.biezhi.wechat.model; 2 | 3 | import com.blade.kit.DateKit; 4 | import com.blade.kit.json.JSONObject; 5 | 6 | import me.biezhi.wechat.Constant; 7 | 8 | public class WechatMeta { 9 | 10 | private String base_uri, redirect_uri, webpush_url = Constant.BASE_URL; 11 | 12 | private String uuid; 13 | private String skey; 14 | private String synckey; 15 | private String wxsid; 16 | private String wxuin; 17 | private String pass_ticket; 18 | private String deviceId = "e" + DateKit.getCurrentUnixTime(); 19 | 20 | private String cookie; 21 | 22 | private JSONObject baseRequest; 23 | private JSONObject SyncKey; 24 | private JSONObject User; 25 | 26 | public WechatMeta() { 27 | 28 | } 29 | 30 | public String getUuid() { 31 | return uuid; 32 | } 33 | 34 | public void setUuid(String uuid) { 35 | this.uuid = uuid; 36 | } 37 | 38 | public String getSkey() { 39 | return skey; 40 | } 41 | 42 | public void setSkey(String skey) { 43 | this.skey = skey; 44 | } 45 | 46 | public String getSynckey() { 47 | return synckey; 48 | } 49 | 50 | public void setSynckey(String synckey) { 51 | this.synckey = synckey; 52 | } 53 | 54 | public String getWxsid() { 55 | return wxsid; 56 | } 57 | 58 | public void setWxsid(String wxsid) { 59 | this.wxsid = wxsid; 60 | } 61 | 62 | public String getWxuin() { 63 | return wxuin; 64 | } 65 | 66 | public void setWxuin(String wxuin) { 67 | this.wxuin = wxuin; 68 | } 69 | 70 | public String getPass_ticket() { 71 | return pass_ticket; 72 | } 73 | 74 | public void setPass_ticket(String pass_ticket) { 75 | this.pass_ticket = pass_ticket; 76 | } 77 | 78 | public String getDeviceId() { 79 | return deviceId; 80 | } 81 | 82 | public void setDeviceId(String deviceId) { 83 | this.deviceId = deviceId; 84 | } 85 | 86 | public String getCookie() { 87 | return cookie; 88 | } 89 | 90 | public void setCookie(String cookie) { 91 | this.cookie = cookie; 92 | } 93 | 94 | public JSONObject getBaseRequest() { 95 | return baseRequest; 96 | } 97 | 98 | public void setBaseRequest(JSONObject baseRequest) { 99 | this.baseRequest = baseRequest; 100 | } 101 | 102 | public JSONObject getSyncKey() { 103 | return SyncKey; 104 | } 105 | 106 | public void setSyncKey(JSONObject syncKey) { 107 | SyncKey = syncKey; 108 | } 109 | 110 | public JSONObject getUser() { 111 | return User; 112 | } 113 | 114 | public void setUser(JSONObject user) { 115 | User = user; 116 | } 117 | 118 | public String getBase_uri() { 119 | return base_uri; 120 | } 121 | 122 | public void setBase_uri(String base_uri) { 123 | this.base_uri = base_uri; 124 | } 125 | 126 | public String getRedirect_uri() { 127 | return redirect_uri; 128 | } 129 | 130 | public void setRedirect_uri(String redirect_uri) { 131 | this.redirect_uri = redirect_uri; 132 | } 133 | 134 | public String getWebpush_url() { 135 | return webpush_url; 136 | } 137 | 138 | public void setWebpush_url(String webpush_url) { 139 | this.webpush_url = webpush_url; 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/me/biezhi/wechat/WechatRobot.java: -------------------------------------------------------------------------------- 1 | package me.biezhi.wechat; 2 | 3 | import java.awt.EventQueue; 4 | import java.io.File; 5 | 6 | import javax.swing.UIManager; 7 | 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import com.blade.kit.DateKit; 12 | import com.blade.kit.StringKit; 13 | import com.blade.kit.http.HttpRequest; 14 | import com.blade.kit.json.JSONObject; 15 | 16 | import me.biezhi.wechat.exception.WechatException; 17 | import me.biezhi.wechat.listener.WechatListener; 18 | import me.biezhi.wechat.model.WechatMeta; 19 | import me.biezhi.wechat.service.WechatService; 20 | import me.biezhi.wechat.service.WechatServiceImpl; 21 | import me.biezhi.wechat.ui.QRCodeFrame; 22 | import me.biezhi.wechat.util.CookieUtil; 23 | import me.biezhi.wechat.util.Matchers; 24 | 25 | /** 26 | * Hello world! 27 | */ 28 | public class WechatRobot { 29 | 30 | private static final Logger LOGGER = LoggerFactory.getLogger(WechatRobot.class); 31 | 32 | private int tip = 0; 33 | private WechatListener wechatListener = new WechatListener(); 34 | private WechatService wechatService = new WechatServiceImpl(); 35 | private WechatMeta wechatMeta = new WechatMeta(); 36 | 37 | private QRCodeFrame qrCodeFrame; 38 | 39 | public WechatRobot() { 40 | System.setProperty("https.protocols", "TLSv1"); 41 | System.setProperty("jsse.enableSNIExtension", "false"); 42 | } 43 | 44 | /** 45 | * 显示二维码 46 | * 47 | * @return 48 | */ 49 | public void showQrCode() throws WechatException { 50 | String uuid = wechatService.getUUID(); 51 | wechatMeta.setUuid(uuid); 52 | 53 | LOGGER.info("获取到uuid为 [{}]", uuid); 54 | String url = Constant.QRCODE_URL + uuid; 55 | final File output = new File("temp.jpg"); 56 | HttpRequest.post(url, true, "t", "webwx", "_", DateKit.getCurrentUnixTime()).receive(output); 57 | 58 | if (null != output && output.exists() && output.isFile()) { 59 | EventQueue.invokeLater(new Runnable() { 60 | public void run() { 61 | try { 62 | UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel"); 63 | qrCodeFrame = new QRCodeFrame(output.getPath()); 64 | } catch (Exception e) { 65 | e.printStackTrace(); 66 | } 67 | } 68 | }); 69 | } 70 | } 71 | 72 | /** 73 | * 等待登录 74 | */ 75 | public String waitForLogin() throws WechatException { 76 | this.tip = 1; 77 | String url = "https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login"; 78 | HttpRequest request = HttpRequest.get(url, true, "tip", this.tip, "uuid", wechatMeta.getUuid(), "_", 79 | DateKit.getCurrentUnixTime()); 80 | 81 | LOGGER.info("等待登录..."); 82 | LOGGER.debug("" + request.toString()); 83 | 84 | String res = request.body(); 85 | request.disconnect(); 86 | 87 | if (null == res) { 88 | throw new WechatException("扫描二维码验证失败"); 89 | } 90 | String code = Matchers.match("window.code=(\\d+);", res); 91 | if (null == code) { 92 | throw new WechatException("扫描二维码验证失败"); 93 | } else { 94 | if (code.equals("201")) { 95 | LOGGER.info("成功扫描,请在手机上点击确认以登录"); 96 | tip = 0; 97 | } else if (code.equals("200")) { 98 | LOGGER.info("正在登录..."); 99 | String pm = Matchers.match("window.redirect_uri=\"(\\S+?)\";", res); 100 | String redirect_uri = pm + "&fun=new"; 101 | wechatMeta.setRedirect_uri(redirect_uri); 102 | 103 | String base_uri = redirect_uri.substring(0, redirect_uri.lastIndexOf("/")); 104 | wechatMeta.setBase_uri(base_uri); 105 | 106 | LOGGER.debug("redirect_uri={}", redirect_uri); 107 | LOGGER.debug("base_uri={}", base_uri); 108 | } else if (code.equals("408")) { 109 | throw new WechatException("登录超时"); 110 | } else { 111 | LOGGER.info("扫描code={}", code); 112 | } 113 | } 114 | return code; 115 | } 116 | 117 | public void closeQrWindow() { 118 | qrCodeFrame.dispose(); 119 | } 120 | 121 | /** 122 | * 登录 123 | */ 124 | public void login() throws WechatException { 125 | HttpRequest request = HttpRequest.get(wechatMeta.getRedirect_uri()); 126 | 127 | LOGGER.debug("" + request); 128 | String res = request.body(); 129 | wechatMeta.setCookie(CookieUtil.getCookie(request)); 130 | request.disconnect(); 131 | 132 | if (StringKit.isBlank(res)) { 133 | throw new WechatException("登录失败"); 134 | } 135 | wechatMeta.setSkey(Matchers.match("(\\S+)", res)); 136 | wechatMeta.setWxsid(Matchers.match("(\\S+)", res)); 137 | wechatMeta.setWxuin(Matchers.match("(\\S+)", res)); 138 | wechatMeta.setPass_ticket(Matchers.match("(\\S+)", res)); 139 | 140 | JSONObject baseRequest = new JSONObject(); 141 | baseRequest.put("Uin", wechatMeta.getWxuin()); 142 | baseRequest.put("Sid", wechatMeta.getWxsid()); 143 | baseRequest.put("Skey", wechatMeta.getSkey()); 144 | baseRequest.put("DeviceID", wechatMeta.getDeviceId()); 145 | wechatMeta.setBaseRequest(baseRequest); 146 | 147 | LOGGER.debug("skey [{}]", wechatMeta.getSkey()); 148 | LOGGER.debug("wxsid [{}]", wechatMeta.getWxsid()); 149 | LOGGER.debug("wxuin [{}]", wechatMeta.getWxuin()); 150 | LOGGER.debug("pass_ticket [{}]", wechatMeta.getPass_ticket()); 151 | File output = new File("temp.jpg"); 152 | if(output.exists()){ 153 | output.delete(); 154 | } 155 | } 156 | 157 | public void start() throws WechatException { 158 | 159 | this.login(); 160 | LOGGER.info("微信登录成功"); 161 | 162 | LOGGER.info("微信初始化..."); 163 | wechatService.wxInit(wechatMeta); 164 | LOGGER.info("微信初始化成功"); 165 | 166 | LOGGER.info("开启状态通知..."); 167 | wechatService.openStatusNotify(wechatMeta); 168 | LOGGER.info("开启状态通知成功"); 169 | 170 | LOGGER.info("获取联系人..."); 171 | Constant.CONTACT = wechatService.getContact(wechatMeta); 172 | LOGGER.info("获取联系人成功"); 173 | LOGGER.info("共有 {} 位联系人", Constant.CONTACT.getContactList().size()); 174 | 175 | // 监听消息 176 | wechatListener.start(wechatService, wechatMeta); 177 | } 178 | 179 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wechat-robot 2 | 3 | wechat-robot是基于微信网页版协议开发的普通微信号机器人程序,使用Java语言。 4 | 5 | [微信协议分析](doc/protocol.md) 6 | 7 | ## 使用 8 | 9 | 直接运行 `me.biezhi.wechat.Application` 中的main函数 10 | 11 | ## 机器人接口申请地址 (申请后在config.properties文件中配置) 12 | 13 | [http://www.itpk.cn/robot.php](http://www.itpk.cn/robot.php) 14 | 15 | 使用手机扫描二维码 16 | 17 | ![](http://i13.tietuku.com/76a8af09f08243a7.png) 18 | 19 | ### 控制台日志 20 | 21 | ```sh 22 | [wechat-robot] 2016-09-11 01:01:45,734 INFO [main] com.blade.kit.base.Config => Load config [classpath:config.properties] 23 | [wechat-robot] 2016-09-11 01:01:47,763 INFO [main] me.biezhi.wechat.WechatRobot => 获取到uuid为 [IdNRA183Nw==] 24 | [wechat-robot] 2016-09-11 01:01:47,991 INFO [main] me.biezhi.wechat.WechatRobot => 等待登录... 25 | [wechat-robot] 2016-09-11 01:01:57,180 INFO [main] me.biezhi.wechat.WechatRobot => 成功扫描,请在手机上点击确认以登录 26 | [wechat-robot] 2016-09-11 01:01:59,184 INFO [main] me.biezhi.wechat.WechatRobot => 等待登录... 27 | [wechat-robot] 2016-09-11 01:01:59,204 INFO [main] me.biezhi.wechat.WechatRobot => 正在登录... 28 | [wechat-robot] 2016-09-11 01:01:59,959 INFO [main] me.biezhi.wechat.WechatRobot => 微信登录成功 29 | [wechat-robot] 2016-09-11 01:01:59,959 INFO [main] me.biezhi.wechat.WechatRobot => 微信初始化... 30 | [wechat-robot] 2016-09-11 01:02:00,153 INFO [main] me.biezhi.wechat.WechatRobot => 微信初始化成功 31 | [wechat-robot] 2016-09-11 01:02:00,153 INFO [main] me.biezhi.wechat.WechatRobot => 开启状态通知... 32 | [wechat-robot] 2016-09-11 01:02:00,259 INFO [main] me.biezhi.wechat.WechatRobot => 开启状态通知成功 33 | [wechat-robot] 2016-09-11 01:02:00,259 INFO [main] me.biezhi.wechat.WechatRobot => 获取联系人... 34 | [wechat-robot] 2016-09-11 01:02:00,538 INFO [main] me.biezhi.wechat.WechatRobot => 获取联系人成功 35 | [wechat-robot] 2016-09-11 01:02:00,538 INFO [main] me.biezhi.wechat.WechatRobot => 共有 4 位联系人 36 | [wechat-robot] 2016-09-11 01:02:00,539 INFO [wechat-listener-thread] me.biezhi.wechat.listener.WechatListener => 进入消息监听模式 ... 37 | [wechat-robot] 2016-09-11 01:02:00,782 INFO [wechat-listener-thread] me.biezhi.wechat.service.WechatService => 选择线路:[webpush2.weixin.qq.com] 38 | [wechat-robot] 2016-09-11 01:02:00,835 INFO [wechat-listener-thread] me.biezhi.wechat.listener.WechatListener => retcode=0, selector=2 39 | [wechat-robot] 2016-09-11 01:02:01,095 INFO [wechat-listener-thread] me.biezhi.wechat.service.WechatService => 你有新的消息,请注意查收 40 | [wechat-robot] 2016-09-11 01:02:01,095 INFO [wechat-listener-thread] me.biezhi.wechat.service.WechatService => 成功截获微信初始化消息 41 | [wechat-robot] 2016-09-11 01:02:01,095 INFO [wechat-listener-thread] me.biezhi.wechat.listener.WechatListener => 等待2000ms... 42 | ``` 43 | 44 | ### 测试通信 45 | 46 | ![](http://i.imgur.com/PKuEtH4.png) 47 | 48 | ```sh 49 | 2016-02-21 18:53:00,500 INFO [listenMsgMode] me.biezhi.weixin.App | [*] retcode=0,selector=6 50 | 2016-02-21 18:53:00,665 INFO [listenMsgMode] me.biezhi.weixin.App | [*] POST https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsync?lang=zh_CN&pass_ticket=CSFPx%2BpUZ1zKum%2BcUK5n%2BM39BoQdvcAzgYBDEzCRtzry3ogKVO2wJChbiSgPimQC&skey=@crypt_a742d75a_acfe5f44c68520a5190a939e0f74ed37&sid=WdbalZCr+GV33OUD&r=1456051980 51 | 2016-02-21 18:53:00,775 INFO [listenMsgMode] me.biezhi.weixin.App | [*] 你有新的消息,请注意查收 52 | 2016-02-21 18:53:00,775 INFO [listenMsgMode] me.biezhi.weixin.App | kiki: 你叫什么? 53 | 2016-02-21 18:53:00,890 INFO [listenMsgMode] me.biezhi.weixin.App | [*] POST https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?lang=zh_CN&pass_ticket=CSFPx%2BpUZ1zKum%2BcUK5n%2BM39BoQdvcAzgYBDEzCRtzry3ogKVO2wJChbiSgPimQC 54 | 2016-02-21 18:53:01,166 INFO [listenMsgMode] me.biezhi.weixin.App | 自动回复 我叫二蛋,你叫什么啊! 55 | 2016-02-21 18:53:01,167 INFO [listenMsgMode] me.biezhi.weixin.App | [*] GET https://webpush2.weixin.qq.com/cgi-bin/mmwebwx-bin/synccheck?r=145605198122996&skey=@crypt_a742d75a_acfe5f44c68520a5190a939e0f74ed37&uin=3155248292&sid=WdbalZCr%2BGV33OUD&deviceid=e1456051957&synckey=1_610390336%7C2_610390405%7C3_610390360%7C11_610390233%7C13_610390001%7C201_1456052048%7C1000_1456048420&_=1456051981167 56 | 2016-02-21 18:53:01,219 INFO [listenMsgMode] me.biezhi.weixin.App | [*] retcode=0,selector=2 57 | 2016-02-21 18:53:01,220 INFO [listenMsgMode] me.biezhi.weixin.App | [*] POST https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsync?lang=zh_CN&pass_ticket=CSFPx%2BpUZ1zKum%2BcUK5n%2BM39BoQdvcAzgYBDEzCRtzry3ogKVO2wJChbiSgPimQC&skey=@crypt_a742d75a_acfe5f44c68520a5190a939e0f74ed37&sid=WdbalZCr+GV33OUD&r=1456051981 58 | 2016-02-21 18:53:01,323 INFO [listenMsgMode] me.biezhi.weixin.App | [*] GET https://webpush2.weixin.qq.com/cgi-bin/mmwebwx-bin/synccheck?r=145605198116582&skey=@crypt_a742d75a_acfe5f44c68520a5190a939e0f74ed37&uin=3155248292&sid=WdbalZCr%2BGV33OUD&deviceid=e1456051957&synckey=1_610390336%7C2_610390406%7C3_610390360%7C11_610390233%7C13_610390001%7C201_1456052048%7C1000_1456048420&_=1456051981323 59 | 2016-02-21 18:53:18,565 INFO [listenMsgMode] me.biezhi.weixin.App | [*] retcode=0,selector=6 60 | 2016-02-21 18:53:18,727 INFO [listenMsgMode] me.biezhi.weixin.App | [*] POST https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsync?lang=zh_CN&pass_ticket=CSFPx%2BpUZ1zKum%2BcUK5n%2BM39BoQdvcAzgYBDEzCRtzry3ogKVO2wJChbiSgPimQC&skey=@crypt_a742d75a_acfe5f44c68520a5190a939e0f74ed37&sid=WdbalZCr+GV33OUD&r=1456051998 61 | 2016-02-21 18:53:18,822 INFO [listenMsgMode] me.biezhi.weixin.App | [*] 你有新的消息,请注意查收 62 | 2016-02-21 18:53:18,822 INFO [listenMsgMode] me.biezhi.weixin.App | kiki: 我叫王二小 63 | 2016-02-21 18:53:19,136 INFO [listenMsgMode] me.biezhi.weixin.App | [*] POST https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?lang=zh_CN&pass_ticket=CSFPx%2BpUZ1zKum%2BcUK5n%2BM39BoQdvcAzgYBDEzCRtzry3ogKVO2wJChbiSgPimQC 64 | 2016-02-21 18:53:19,412 INFO [listenMsgMode] me.biezhi.weixin.App | 自动回复 傻了眼 65 | 2016-02-21 18:53:19,412 INFO [listenMsgMode] me.biezhi.weixin.App | [*] GET https://webpush2.weixin.qq.com/cgi-bin/mmwebwx-bin/synccheck?r=145605199972325&skey=@crypt_a742d75a_acfe5f44c68520a5190a939e0f74ed37&uin=3155248292&sid=WdbalZCr%2BGV33OUD&deviceid=e1456051957&synckey=1_610390336%7C2_610390408%7C3_610390360%7C11_610390233%7C13_610390001%7C201_1456052066%7C1000_1456048420&_=1456051999412 66 | 2016-02-21 18:53:19,466 INFO [listenMsgMode] me.biezhi.weixin.App | [*] retcode=0,selector=2 67 | 2016-02-21 18:53:19,466 INFO [listenMsgMode] me.biezhi.weixin.App | [*] POST https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsync?lang=zh_CN&pass_ticket=CSFPx%2BpUZ1zKum%2BcUK5n%2BM39BoQdvcAzgYBDEzCRtzry3ogKVO2wJChbiSgPimQC&skey=@crypt_a742d75a_acfe5f44c68520a5190a939e0f74ed37&sid=WdbalZCr+GV33OUD&r=1456051999 68 | 2016-02-21 18:53:19,561 INFO [listenMsgMode] me.biezhi.weixin.App | [*] GET https://webpush2.weixin.qq.com/cgi-bin/mmwebwx-bin/synccheck?r=145605199935595&skey=@crypt_a742d75a_acfe5f44c68520a5190a939e0f74ed37&uin=3155248292&sid=WdbalZCr%2BGV33OUD&deviceid=e1456051957&synckey=1_610390336%7C2_610390409%7C3_610390360%7C11_610390233%7C13_610390001%7C201_1456052066%7C1000_1456048420&_=1456051999561 69 | 2016-02-21 18:53:28,820 INFO [listenMsgMode] me.biezhi.weixin.App | [*] retcode=0,selector=6 70 | 2016-02-21 18:53:28,984 INFO [listenMsgMode] me.biezhi.weixin.App | [*] POST https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsync?lang=zh_CN&pass_ticket=CSFPx%2BpUZ1zKum%2BcUK5n%2BM39BoQdvcAzgYBDEzCRtzry3ogKVO2wJChbiSgPimQC&skey=@crypt_a742d75a_acfe5f44c68520a5190a939e0f74ed37&sid=WdbalZCr+GV33OUD&r=1456052008 71 | 2016-02-21 18:53:29,094 INFO [listenMsgMode] me.biezhi.weixin.App | [*] 你有新的消息,请注意查收 72 | 2016-02-21 18:53:29,094 INFO [listenMsgMode] me.biezhi.weixin.App | kiki: 上海明天天气怎么样 73 | 2016-02-21 18:53:29,347 INFO [listenMsgMode] me.biezhi.weixin.App | [*] POST https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?lang=zh_CN&pass_ticket=CSFPx%2BpUZ1zKum%2BcUK5n%2BM39BoQdvcAzgYBDEzCRtzry3ogKVO2wJChbiSgPimQC 74 | 2016-02-21 18:53:29,612 INFO [listenMsgMode] me.biezhi.weixin.App | 自动回复 上海天气预报: 75 | 明天7℃ 中雨 东南风微风,晚上5℃ 阴 无持续风向微风,日出和日落时间06:29|17:46 76 | 城市信息:经度121.445,纬度31.213,区号021,邮编200000,海拔19米。 77 | 2016-02-21 18:53:29,612 INFO [listenMsgMode] me.biezhi.weixin.App | [*] GET https://webpush2.weixin.qq.com/cgi-bin/mmwebwx-bin/synccheck?r=145605200913443&skey=@crypt_a742d75a_acfe5f44c68520a5190a939e0f74ed37&uin=3155248292&sid=WdbalZCr%2BGV33OUD&deviceid=e1456051957&synckey=1_610390336%7C2_610390410%7C3_610390360%7C11_610390233%7C13_610390001%7C201_1456052076%7C1000_1456048420&_=1456052009612 78 | 2016-02-21 18:53:29,664 INFO [listenMsgMode] me.biezhi.weixin.App | [*] retcode=0,selector=2 79 | 2016-02-21 18:53:29,664 INFO [listenMsgMode] me.biezhi.weixin.App | [*] POST https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsync?lang=zh_CN&pass_ticket=CSFPx%2BpUZ1zKum%2BcUK5n%2BM39BoQdvcAzgYBDEzCRtzry3ogKVO2wJChbiSgPimQC&skey=@crypt_a742d75a_acfe5f44c68520a5190a939e0f74ed37&sid=WdbalZCr+GV33OUD&r=1456052009 80 | 2016-02-21 18:53:29,765 INFO [listenMsgMode] me.biezhi.weixin.App | [*] GET https://webpush2.weixin.qq.com/cgi-bin/mmwebwx-bin/synccheck?r=145605200916678&skey=@crypt_a742d75a_acfe5f44c68520a5190a939e0f74ed37&uin=3155248292&sid=WdbalZCr%2BGV33OUD&deviceid=e1456051957&synckey=1_610390336%7C2_610390411%7C3_610390360%7C11_610390233%7C13_610390001%7C201_1456052076%7C1000_1456048420&_=1456052009765 81 | ``` 82 | 83 | 更多有趣的东西你可以自己研究,比如发送图片,音乐,推送文章等。。 84 | -------------------------------------------------------------------------------- /doc/protocol.md: -------------------------------------------------------------------------------- 1 | # 微信web协议分析和实现微信机器人(微信网页版 wx2.qq.com) 2 | 3 | 1.打开首页,分配一个随机uuid, 4 | 2.根据该uuid获取二维码图片。 5 | 3.微信客户端扫描该图片,在客户端确认登录。 6 | 4.浏览器不停的调用一个接口,如果返回登录成功,则调用登录接口 7 | 5.此时可以获取联系人列表,可以发送消息。然后不断调用同步接口。 8 | 6.如果同步接口有返回,则可以获取新消息,然后继续调用同步接口。 9 | 10 | - Java版实现源码:https://github.com/biezhi/wechat-robot 11 | - Python实现:https://github.com/Urinx/WeixinBot 12 | - C#实现:https://github.com/sherlockchou86/WeChat.NET 13 | - QT实现:https://github.com/xiangzhai/qwx 14 | - Perl实现:https://github.com/sjdy521/Mojo-Weixin 15 | 16 | ## 执行流程 17 | 18 | ```sh 19 | +--------------+ +---------------+ +---------------+ 20 | | | | | | | 21 | | Get UUID | | Get Contact | | Status Notify | 22 | | | | | | | 23 | +-------+------+ +-------^-------+ +-------^-------+ 24 | | | | 25 | | +-------+ +--------+ 26 | | | | 27 | +-------v------+ +-----+--+------+ +--------------+ 28 | | | | | | | 29 | | Get QRCode | | Weixin Init +------> Sync Check <----+ 30 | | | | | | | | 31 | +-------+------+ +-------^-------+ +-------+------+ | 32 | | | | | 33 | | | +-----------+ 34 | | | | 35 | +-------v------+ +-------+--------+ +-------v-------+ 36 | | | Confirm Login | | | | 37 | +------> Login +---------------> New Login Page | | Weixin Sync | 38 | | | | | | | | 39 | | +------+-------+ +----------------+ +---------------+ 40 | | | 41 | |QRCode Scaned| 42 | +-------------+ 43 | ``` 44 | 45 | ## WebWechat API 46 | 47 | ### 1. 获取UUID(参考方法 getUUID) 48 | 49 | | API | 获取 UUID | 50 | | --- | --------- | 51 | | url | https://login.weixin.qq.com/jslogin | 52 | | method | GET | 53 | | data | URL Encode | 54 | | params | **appid** : wx782c26e4c19acffb
**fun** : new
**lang**: zh\_CN
**_** : 时间戳 | 55 | 56 | 返回数据(String): 57 | 58 | ``` 59 | window.QRLogin.code = 200; window.QRLogin.uuid = "xxx" 60 | ``` 61 | 62 | ### 2. 显示二维码(参考方法 showQrCode) 63 | 64 | | API | 显示二维码 | 65 | | --- | --------- | 66 | | url | https://login.weixin.qq.com/qrcode/{uuid} | 67 | | method | POST | 68 | | params | **t** : webwx
**_** : 时间戳| 69 |
70 | 71 | ### 3. 等待登录(参考方法 waitForLogin)这里是微信确认登录 72 | 73 | | API | 二维码扫描登录 | 74 | | --- | --------- | 75 | | url | https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login | 76 | | method | GET | 77 | | params | **tip** : 1:未扫描 0:已扫描
**uuid** : 获取到的uuid
**_** : 时间戳 | 78 | 79 | 返回数据(String): 80 | ``` 81 | window.code=xxx; 82 | 83 | xxx: 84 | 408 登陆超时 85 | 201 扫描成功 86 | 200 确认登录 87 | 88 | 当返回200时,还会有 89 | window.redirect_uri="https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=xxx&uuid=xxx&lang=xxx&scan=xxx"; 90 | ``` 91 | 92 | ### 4. 登录获取Cookie(参考方法 login) 93 | 94 | | API | webwxnewloginpage | 95 | | --- | --------- | 96 | | url | https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage | 97 | | method | GET | 98 | | params | **ticket** : xxx
**uuid** : xxx
**lang** : zh_CN
**scan** : xxx
**fun** : new | 99 | 100 | 返回数据(XML): 101 | ``` 102 | 103 | 0 104 | OK 105 | xxx 106 | xxx 107 | xxx 108 | xxx 109 | 1 110 | 111 | ``` 112 | 在这一步获取xml中的 `skey`, `wxsid`, `wxuin`, `pass_ticket` 113 | 114 | ### 5. 微信初始化(参考方法 wxInit) 115 | 116 | | API | webwxinit | 117 | | --- | --------- | 118 | | url | https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxinit | 119 | | method | POST | 120 | | data | JSON | 121 | | header | Content-Type: application/json; charset=UTF-8 | 122 | | params | {
     BaseRequest: {
         Uin: xxx,
         Sid: xxx,
         Skey: xxx,
         DeviceID: xxx,
     }
} | 123 | 124 | 返回数据(JSON): 125 | ``` 126 | { 127 | "BaseResponse": { 128 | "Ret": 0, 129 | "ErrMsg": "" 130 | }, 131 | "Count": 11, 132 | "ContactList": [...], 133 | "SyncKey": { 134 | "Count": 4, 135 | "List": [ 136 | { 137 | "Key": 1, 138 | "Val": 635705559 139 | }, 140 | ... 141 | ] 142 | }, 143 | "User": { 144 | "Uin": xxx, 145 | "UserName": xxx, 146 | "NickName": xxx, 147 | "HeadImgUrl": xxx, 148 | "RemarkName": "", 149 | "PYInitial": "", 150 | "PYQuanPin": "", 151 | "RemarkPYInitial": "", 152 | "RemarkPYQuanPin": "", 153 | "HideInputBarFlag": 0, 154 | "StarFriend": 0, 155 | "Sex": 1, 156 | "Signature": "Apt-get install B", 157 | "AppAccountFlag": 0, 158 | "VerifyFlag": 0, 159 | "ContactFlag": 0, 160 | "WebWxPluginSwitch": 0, 161 | "HeadImgFlag": 1, 162 | "SnsFlag": 17 163 | }, 164 | "ChatSet": xxx, 165 | "SKey": xxx, 166 | "ClientVersion": 369297683, 167 | "SystemTime": 1453124908, 168 | "GrayScale": 1, 169 | "InviteStartCount": 40, 170 | "MPSubscribeMsgCount": 2, 171 | "MPSubscribeMsgList": [...], 172 | "ClickReportInterval": 600000 173 | } 174 | ``` 175 | 176 | 这一步中获取 `SyncKey`, `User` 后面的消息监听用。 177 | 178 | ### 6. 开启微信状态通知(参考方法 wxStatusNotify) 179 | 180 | | API | webwxstatusnotify | 181 | | --- | --------- | 182 | | url | https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxstatusnotify | 183 | | method | POST | 184 | | data | JSON | 185 | | header | Content-Type: application/json; charset=UTF-8 | 186 | | params | {
     BaseRequest: { Uin: xxx, Sid: xxx, Skey: xxx, DeviceID: xxx },
     Code: 3,
     FromUserName: 自己的ID,
     ToUserName: 自己的ID,
     ClientMsgId: 时间戳
} | 187 | 188 | 返回数据(JSON): 189 | ``` 190 | { 191 | "BaseResponse": { 192 | "Ret": 0, 193 | "ErrMsg": "" 194 | }, 195 | ... 196 | } 197 | ``` 198 | 199 | ### 7. 获取联系人列表(参考方法 getContact) 200 | 201 | | API | webwxgetcontact | 202 | | --- | --------- | 203 | | url | https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact | 204 | | method | POST | 205 | | data | JSON | 206 | | header | ContentType: application/json; charset=UTF-8 | 207 | | params | {
     BaseRequest: {
         Uin: xxx,
         Sid: xxx,
         Skey: xxx,
         DeviceID: xxx,
     }
} | 208 | 209 | 返回数据(JSON): 210 | ``` 211 | { 212 | "BaseResponse": { 213 | "Ret": 0, 214 | "ErrMsg": "" 215 | }, 216 | "MemberCount": 334, 217 | "MemberList": [ 218 | { 219 | "Uin": 0, 220 | "UserName": xxx, 221 | "NickName": "Urinx", 222 | "HeadImgUrl": xxx, 223 | "ContactFlag": 3, 224 | "MemberCount": 0, 225 | "MemberList": [], 226 | "RemarkName": "", 227 | "HideInputBarFlag": 0, 228 | "Sex": 0, 229 | "Signature": "我是二蛋", 230 | "VerifyFlag": 8, 231 | "OwnerUin": 0, 232 | "PYInitial": "URINX", 233 | "PYQuanPin": "Urinx", 234 | "RemarkPYInitial": "", 235 | "RemarkPYQuanPin": "", 236 | "StarFriend": 0, 237 | "AppAccountFlag": 0, 238 | "Statues": 0, 239 | "AttrStatus": 0, 240 | "Province": "", 241 | "City": "", 242 | "Alias": "Urinxs", 243 | "SnsFlag": 0, 244 | "UniFriend": 0, 245 | "DisplayName": "", 246 | "ChatRoomId": 0, 247 | "KeyWord": "gh_", 248 | "EncryChatRoomId": "" 249 | }, 250 | ... 251 | ], 252 | "Seq": 0 253 | } 254 | ``` 255 | 256 | ### 8.消息检查(参考方法 syncCheck) 257 | 258 | | API | synccheck | 259 | | --- | --------- | 260 | | url | https://webpush2.weixin.qq.com/cgi-bin/mmwebwx-bin/synccheck | 261 | | method | GET | 262 | | data | JSON | 263 | | header | ContentType: application/json; charset=UTF-8 | 264 | | params | {
     BaseRequest: {
         Uin: xxx,
         Sid: xxx,
         Skey: xxx,
         DeviceID: xxx,
     }
} | 265 | 266 | 返回数据(String): 267 | ``` 268 | window.synccheck={retcode:"xxx",selector:"xxx"} 269 | 270 | retcode: 271 | 0 正常 272 | 1100 失败/登出微信 273 | selector: 274 | 0 正常 275 | 2 新的消息 276 | 7 进入/离开聊天界面 277 | ``` 278 | 279 | ### 9. 获取最新消息(参考方法 webwxsync) 280 | 281 | | API | webwxsync | 282 | | --- | --------- | 283 | | url | https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid=xxx&skey=xxx&pass_ticket=xxx | 284 | | method | POST | 285 | | data | JSON | 286 | | header | ContentType: application/json; charset=UTF-8 | 287 | | params | {
     BaseRequest: { Uin: xxx, Sid: xxx, Skey: xxx, DeviceID: xxx },
     SyncKey: xxx,
     rr: `时间戳取反`
} | 288 | 289 | 返回数据(JSON): 290 | ``` 291 | { 292 | 'BaseResponse': {'ErrMsg': '', 'Ret': 0}, 293 | 'SyncKey': { 294 | 'Count': 7, 295 | 'List': [ 296 | {'Val': 636214192, 'Key': 1}, 297 | ... 298 | ] 299 | }, 300 | 'ContinueFlag': 0, 301 | 'AddMsgCount': 1, 302 | 'AddMsgList': [ 303 | { 304 | 'FromUserName': '', 305 | 'PlayLength': 0, 306 | 'RecommendInfo': {...}, 307 | 'Content': "", 308 | 'StatusNotifyUserName': '', 309 | 'StatusNotifyCode': 5, 310 | 'Status': 3, 311 | 'VoiceLength': 0, 312 | 'ToUserName': '', 313 | 'ForwardFlag': 0, 314 | 'AppMsgType': 0, 315 | 'AppInfo': {'Type': 0, 'AppID': ''}, 316 | 'Url': '', 317 | 'ImgStatus': 1, 318 | 'MsgType': 51, 319 | 'ImgHeight': 0, 320 | 'MediaId': '', 321 | 'FileName': '', 322 | 'FileSize': '', 323 | ... 324 | }, 325 | ... 326 | ], 327 | 'ModChatRoomMemberCount': 0, 328 | 'ModContactList': [], 329 | 'DelContactList': [], 330 | 'ModChatRoomMemberList': [], 331 | 'DelContactCount': 0, 332 | ... 333 | } 334 | ``` 335 | 336 | ### 10. 发送消息(参考方法 webwxsendmsg) 337 | 338 | | API | webwxsendmsg | 339 | | --- | ------------ | 340 | | url | https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?pass_ticket=xxx | 341 | | method | POST | 342 | | data | JSON | 343 | | header | ContentType: application/json; charset=UTF-8 | 344 | | params | {
     BaseRequest: { Uin: xxx, Sid: xxx, Skey: xxx, DeviceID: xxx },
     Msg: {
         Type: 1 文字消息,
         Content: 要发送的消息,
         FromUserName: 自己的ID,
         ToUserName: 好友的ID,
         LocalID: 与clientMsgId相同,
         ClientMsgId: 时间戳左移4位随后补上4位随机数
     }
} | 345 | 346 | 返回数据(JSON): 347 | ``` 348 | { 349 | "BaseResponse": { 350 | "Ret": 0, 351 | "ErrMsg": "" 352 | }, 353 | ... 354 | } 355 | ``` 356 | 357 | 更多资料: 358 | https://github.com/xiangzhai/qwx 359 | https://github.com/Urinx/WeixinBot 360 | http://www.07net01.com/2016/01/1201188.html 361 | http://www.cnblogs.com/xiaozhi_5638/p/4923811.html 362 | -------------------------------------------------------------------------------- /src/main/java/me/biezhi/wechat/service/WechatServiceImpl.java: -------------------------------------------------------------------------------- 1 | package me.biezhi.wechat.service; 2 | 3 | import java.io.File; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import com.blade.kit.DateKit; 9 | import com.blade.kit.FileKit; 10 | import com.blade.kit.StringKit; 11 | import com.blade.kit.http.HttpRequest; 12 | import com.blade.kit.json.JSONArray; 13 | import com.blade.kit.json.JSONKit; 14 | import com.blade.kit.json.JSONObject; 15 | 16 | import me.biezhi.wechat.Constant; 17 | import me.biezhi.wechat.exception.WechatException; 18 | import me.biezhi.wechat.model.WechatContact; 19 | import me.biezhi.wechat.model.WechatMeta; 20 | import me.biezhi.wechat.robot.MoLiRobot; 21 | import me.biezhi.wechat.robot.Robot; 22 | import me.biezhi.wechat.util.Matchers; 23 | 24 | public class WechatServiceImpl implements WechatService { 25 | 26 | private static final Logger LOGGER = LoggerFactory.getLogger(WechatService.class); 27 | 28 | // 茉莉机器人 29 | private Robot robot = new MoLiRobot(); 30 | 31 | /** 32 | * 获取联系人 33 | */ 34 | @Override 35 | public WechatContact getContact(WechatMeta wechatMeta) { 36 | String url = wechatMeta.getBase_uri() + "/webwxgetcontact?pass_ticket=" + wechatMeta.getPass_ticket() + "&skey=" 37 | + wechatMeta.getSkey() + "&r=" + DateKit.getCurrentUnixTime(); 38 | 39 | JSONObject body = new JSONObject(); 40 | body.put("BaseRequest", wechatMeta.getBaseRequest()); 41 | 42 | HttpRequest request = HttpRequest.post(url).contentType("application/json;charset=utf-8") 43 | .header("Cookie", wechatMeta.getCookie()).send(body.toString()); 44 | 45 | LOGGER.debug(request.toString()); 46 | String res = request.body(); 47 | request.disconnect(); 48 | 49 | if (StringKit.isBlank(res)) { 50 | throw new WechatException("获取联系人失败"); 51 | } 52 | 53 | LOGGER.debug(res); 54 | 55 | WechatContact wechatContact = new WechatContact(); 56 | try { 57 | JSONObject jsonObject = JSONKit.parseObject(res); 58 | JSONObject BaseResponse = jsonObject.get("BaseResponse").asJSONObject(); 59 | if (null != BaseResponse) { 60 | int ret = BaseResponse.getInt("Ret", -1); 61 | if (ret == 0) { 62 | JSONArray memberList = jsonObject.get("MemberList").asArray(); 63 | JSONArray contactList = new JSONArray(); 64 | 65 | if (null != memberList) { 66 | for (int i = 0, len = memberList.size(); i < len; i++) { 67 | JSONObject contact = memberList.get(i).asJSONObject(); 68 | // 公众号/服务号 69 | if (contact.getInt("VerifyFlag", 0) == 8) { 70 | continue; 71 | } 72 | // 特殊联系人 73 | if (Constant.FILTER_USERS.contains(contact.getString("UserName"))) { 74 | continue; 75 | } 76 | // 群聊 77 | if (contact.getString("UserName").indexOf("@@") != -1) { 78 | continue; 79 | } 80 | // 自己 81 | if (contact.getString("UserName").equals(wechatMeta.getUser().getString("UserName"))) { 82 | continue; 83 | } 84 | contactList.add(contact); 85 | } 86 | 87 | wechatContact.setContactList(contactList); 88 | wechatContact.setMemberList(memberList); 89 | 90 | this.getGroup(wechatMeta, wechatContact); 91 | 92 | return wechatContact; 93 | } 94 | } 95 | } 96 | } catch (Exception e) { 97 | throw new WechatException(e); 98 | } 99 | return null; 100 | } 101 | 102 | private void getGroup(WechatMeta wechatMeta, WechatContact wechatContact) { 103 | String url = wechatMeta.getBase_uri() + "/webwxbatchgetcontact?type=ex&pass_ticket=" + wechatMeta.getPass_ticket() + "&skey=" 104 | + wechatMeta.getSkey() + "&r=" + DateKit.getCurrentUnixTime(); 105 | 106 | JSONObject body = new JSONObject(); 107 | body.put("BaseRequest", wechatMeta.getBaseRequest()); 108 | 109 | HttpRequest request = HttpRequest.post(url).contentType("application/json;charset=utf-8") 110 | .header("Cookie", wechatMeta.getCookie()).send(body.toString()); 111 | 112 | LOGGER.debug(request.toString()); 113 | String res = request.body(); 114 | request.disconnect(); 115 | 116 | if (StringKit.isBlank(res)) { 117 | throw new WechatException("获取群信息失败"); 118 | } 119 | 120 | LOGGER.debug(res); 121 | 122 | try { 123 | JSONObject jsonObject = JSONKit.parseObject(res); 124 | JSONObject BaseResponse = jsonObject.get("BaseResponse").asJSONObject(); 125 | if (null != BaseResponse) { 126 | int ret = BaseResponse.getInt("Ret", -1); 127 | if (ret == 0) { 128 | JSONArray memberList = jsonObject.get("MemberList").asArray(); 129 | JSONArray contactList = new JSONArray(); 130 | 131 | if (null != memberList) { 132 | for (int i = 0, len = memberList.size(); i < len; i++) { 133 | JSONObject contact = memberList.get(i).asJSONObject(); 134 | // 公众号/服务号 135 | if (contact.getInt("VerifyFlag", 0) == 8) { 136 | continue; 137 | } 138 | // 特殊联系人 139 | if (Constant.FILTER_USERS.contains(contact.getString("UserName"))) { 140 | continue; 141 | } 142 | // 群聊 143 | if (contact.getString("UserName").indexOf("@@") != -1) { 144 | continue; 145 | } 146 | // 自己 147 | if (contact.getString("UserName").equals(wechatMeta.getUser().getString("UserName"))) { 148 | continue; 149 | } 150 | contactList.add(contact); 151 | } 152 | 153 | wechatContact.setContactList(contactList); 154 | wechatContact.setMemberList(memberList); 155 | } 156 | } 157 | } 158 | } catch (Exception e) { 159 | throw new WechatException(e); 160 | } 161 | } 162 | 163 | /** 164 | * 获取UUID 165 | */ 166 | @Override 167 | public String getUUID() throws WechatException { 168 | HttpRequest request = HttpRequest.get(Constant.JS_LOGIN_URL, true, "appid", "wx782c26e4c19acffb", "fun", "new", 169 | "lang", "zh_CN", "_", DateKit.getCurrentUnixTime()); 170 | 171 | LOGGER.debug(request.toString()); 172 | 173 | String res = request.body(); 174 | request.disconnect(); 175 | 176 | if (StringKit.isNotBlank(res)) { 177 | String code = Matchers.match("window.QRLogin.code = (\\d+);", res); 178 | if (null != code) { 179 | if (code.equals("200")) { 180 | return Matchers.match("window.QRLogin.uuid = \"(.*)\";", res); 181 | } else { 182 | throw new WechatException("错误的状态码: " + code); 183 | } 184 | } 185 | } 186 | throw new WechatException("获取UUID失败"); 187 | } 188 | 189 | /** 190 | * 打开状态提醒 191 | */ 192 | @Override 193 | public void openStatusNotify(WechatMeta wechatMeta) throws WechatException { 194 | 195 | String url = wechatMeta.getBase_uri() + "/webwxstatusnotify?lang=zh_CN&pass_ticket=" + wechatMeta.getPass_ticket(); 196 | 197 | JSONObject body = new JSONObject(); 198 | body.put("BaseRequest", wechatMeta.getBaseRequest()); 199 | body.put("Code", 3); 200 | body.put("FromUserName", wechatMeta.getUser().getString("UserName")); 201 | body.put("ToUserName", wechatMeta.getUser().getString("UserName")); 202 | body.put("ClientMsgId", DateKit.getCurrentUnixTime()); 203 | 204 | HttpRequest request = HttpRequest.post(url).contentType("application/json;charset=utf-8") 205 | .header("Cookie", wechatMeta.getCookie()).send(body.toString()); 206 | 207 | LOGGER.debug("" + request); 208 | String res = request.body(); 209 | request.disconnect(); 210 | 211 | if (StringKit.isBlank(res)) { 212 | throw new WechatException("状态通知开启失败"); 213 | } 214 | 215 | try { 216 | JSONObject jsonObject = JSONKit.parseObject(res); 217 | JSONObject BaseResponse = jsonObject.get("BaseResponse").asJSONObject(); 218 | if (null != BaseResponse) { 219 | int ret = BaseResponse.getInt("Ret", -1); 220 | if (ret != 0) { 221 | throw new WechatException("状态通知开启失败,ret:" + ret); 222 | } 223 | } 224 | } catch (Exception e) { 225 | throw new WechatException(e); 226 | } 227 | } 228 | 229 | /** 230 | * 微信初始化 231 | */ 232 | @Override 233 | public void wxInit(WechatMeta wechatMeta) throws WechatException { 234 | String url = wechatMeta.getBase_uri() + "/webwxinit?r=" + DateKit.getCurrentUnixTime() + "&pass_ticket=" 235 | + wechatMeta.getPass_ticket() + "&skey=" + wechatMeta.getSkey(); 236 | 237 | JSONObject body = new JSONObject(); 238 | body.put("BaseRequest", wechatMeta.getBaseRequest()); 239 | 240 | HttpRequest request = HttpRequest.post(url).contentType("application/json;charset=utf-8") 241 | .header("Cookie", wechatMeta.getCookie()).send(body.toString()); 242 | 243 | LOGGER.debug("" + request); 244 | String res = request.body(); 245 | request.disconnect(); 246 | 247 | if (StringKit.isBlank(res)) { 248 | throw new WechatException("微信初始化失败"); 249 | } 250 | 251 | try { 252 | JSONObject jsonObject = JSONKit.parseObject(res); 253 | if (null != jsonObject) { 254 | JSONObject BaseResponse = jsonObject.get("BaseResponse").asJSONObject(); 255 | if (null != BaseResponse) { 256 | int ret = BaseResponse.getInt("Ret", -1); 257 | if (ret == 0) { 258 | wechatMeta.setSyncKey(jsonObject.get("SyncKey").asJSONObject()); 259 | wechatMeta.setUser(jsonObject.get("User").asJSONObject()); 260 | 261 | StringBuffer synckey = new StringBuffer(); 262 | JSONArray list = wechatMeta.getSyncKey().get("List").asArray(); 263 | for (int i = 0, len = list.size(); i < len; i++) { 264 | JSONObject item = list.get(i).asJSONObject(); 265 | synckey.append("|" + item.getInt("Key", 0) + "_" + item.getInt("Val", 0)); 266 | } 267 | wechatMeta.setSynckey(synckey.substring(1)); 268 | } 269 | } 270 | } 271 | } catch (Exception e) { 272 | } 273 | } 274 | 275 | /** 276 | * 选择同步线路 277 | */ 278 | @Override 279 | public void choiceSyncLine(WechatMeta wechatMeta) throws WechatException { 280 | boolean enabled = false; 281 | for(String syncUrl : Constant.SYNC_HOST){ 282 | int[] res = this.syncCheck(syncUrl, wechatMeta); 283 | if(res[0] == 0){ 284 | String url = "https://" + syncUrl + "/cgi-bin/mmwebwx-bin"; 285 | wechatMeta.setWebpush_url(url); 286 | LOGGER.info("选择线路:[{}]", syncUrl); 287 | enabled = true; 288 | break; 289 | } 290 | } 291 | if(!enabled){ 292 | throw new WechatException("同步线路不通畅"); 293 | } 294 | } 295 | 296 | /** 297 | * 检测心跳 298 | */ 299 | @Override 300 | public int[] syncCheck(WechatMeta wechatMeta) throws WechatException{ 301 | return this.syncCheck(null, wechatMeta); 302 | } 303 | 304 | /** 305 | * 检测心跳 306 | */ 307 | private int[] syncCheck(String url, WechatMeta meta) throws WechatException{ 308 | if(null == url){ 309 | url = meta.getWebpush_url() + "/synccheck"; 310 | } else{ 311 | url = "https://" + url + "/cgi-bin/mmwebwx-bin/synccheck"; 312 | } 313 | 314 | JSONObject body = new JSONObject(); 315 | body.put("BaseRequest", meta.getBaseRequest()); 316 | 317 | HttpRequest request = HttpRequest 318 | .get(url, true, "r", DateKit.getCurrentUnixTime() + StringKit.getRandomNumber(5), "skey", 319 | meta.getSkey(), "uin", meta.getWxuin(), "sid", meta.getWxsid(), "deviceid", 320 | meta.getDeviceId(), "synckey", meta.getSynckey(), "_", System.currentTimeMillis()) 321 | .header("Cookie", meta.getCookie()); 322 | 323 | LOGGER.debug(request.toString()); 324 | 325 | String res = request.body(); 326 | request.disconnect(); 327 | 328 | int[] arr = new int[]{-1, -1}; 329 | if (StringKit.isBlank(res)) { 330 | return arr; 331 | } 332 | 333 | String retcode = Matchers.match("retcode:\"(\\d+)\",", res); 334 | String selector = Matchers.match("selector:\"(\\d+)\"}", res); 335 | if (null != retcode && null != selector) { 336 | arr[0] = Integer.parseInt(retcode); 337 | arr[1] = Integer.parseInt(selector); 338 | return arr; 339 | } 340 | return arr; 341 | } 342 | 343 | /** 344 | * 处理消息 345 | */ 346 | @Override 347 | public void handleMsg(WechatMeta wechatMeta, JSONObject data) { 348 | if (null == data) { 349 | return; 350 | } 351 | 352 | JSONArray AddMsgList = data.get("AddMsgList").asArray(); 353 | 354 | for (int i = 0, len = AddMsgList.size(); i < len; i++) { 355 | LOGGER.info("你有新的消息,请注意查收"); 356 | JSONObject msg = AddMsgList.get(i).asJSONObject(); 357 | int msgType = msg.getInt("MsgType", 0); 358 | String name = getUserRemarkName(msg.getString("FromUserName")); 359 | String content = msg.getString("Content"); 360 | 361 | if (msgType == 51) { 362 | LOGGER.info("成功截获微信初始化消息"); 363 | } else if (msgType == 1) { 364 | if (Constant.FILTER_USERS.contains(msg.getString("ToUserName"))) { 365 | continue; 366 | } else if (msg.getString("FromUserName").equals(wechatMeta.getUser().getString("UserName"))) { 367 | continue; 368 | } else if (msg.getString("ToUserName").indexOf("@@") != -1) { 369 | String[] peopleContent = content.split(":
"); 370 | LOGGER.info("|" + name + "| " + peopleContent[0] + ":\n" + peopleContent[1].replace("
", "\n")); 371 | } else { 372 | LOGGER.info(name + ": " + content); 373 | String ans = robot.talk(content); 374 | webwxsendmsg(wechatMeta, ans, msg.getString("FromUserName")); 375 | LOGGER.info("自动回复 " + ans); 376 | } 377 | } else if (msgType == 3) { 378 | String imgDir = Constant.config.get("app.img_path"); 379 | String msgId = msg.getString("MsgId"); 380 | FileKit.createDir(imgDir, false); 381 | String imgUrl = wechatMeta.getBase_uri() + "/webwxgetmsgimg?MsgID=" + msgId + "&skey=" + wechatMeta.getSkey() + "&type=slave"; 382 | HttpRequest.get(imgUrl).header("Cookie", wechatMeta.getCookie()).receive(new File(imgDir + "/" + msgId+".jpg")); 383 | webwxsendmsg(wechatMeta, "二蛋还不支持图片呢", msg.getString("FromUserName")); 384 | } else if (msgType == 34) { 385 | webwxsendmsg(wechatMeta, "二蛋还不支持语音呢", msg.getString("FromUserName")); 386 | } else if (msgType == 42) { 387 | LOGGER.info(name + " 给你发送了一张名片:"); 388 | LOGGER.info("========================="); 389 | } 390 | } 391 | } 392 | 393 | /** 394 | * 发送消息 395 | */ 396 | private void webwxsendmsg(WechatMeta meta, String content, String to) { 397 | String url = meta.getBase_uri() + "/webwxsendmsg?lang=zh_CN&pass_ticket=" + meta.getPass_ticket(); 398 | JSONObject body = new JSONObject(); 399 | 400 | String clientMsgId = DateKit.getCurrentUnixTime() + StringKit.getRandomNumber(5); 401 | JSONObject Msg = new JSONObject(); 402 | Msg.put("Type", 1); 403 | Msg.put("Content", content); 404 | Msg.put("FromUserName", meta.getUser().getString("UserName")); 405 | Msg.put("ToUserName", to); 406 | Msg.put("LocalID", clientMsgId); 407 | Msg.put("ClientMsgId", clientMsgId); 408 | 409 | body.put("BaseRequest", meta.getBaseRequest()); 410 | body.put("Msg", Msg); 411 | 412 | HttpRequest request = HttpRequest.post(url).contentType("application/json;charset=utf-8") 413 | .header("Cookie", meta.getCookie()).send(body.toString()); 414 | 415 | LOGGER.info("发送消息..."); 416 | LOGGER.debug("" + request); 417 | request.body(); 418 | request.disconnect(); 419 | } 420 | 421 | private String getUserRemarkName(String id) { 422 | String name = "这个人物名字未知"; 423 | for (int i = 0, len = Constant.CONTACT.getMemberList().size(); i < len; i++) { 424 | JSONObject member = Constant.CONTACT.getMemberList().get(i).asJSONObject(); 425 | if (member.getString("UserName").equals(id)) { 426 | if (StringKit.isNotBlank(member.getString("RemarkName"))) { 427 | name = member.getString("RemarkName"); 428 | } else { 429 | name = member.getString("NickName"); 430 | } 431 | return name; 432 | } 433 | } 434 | return name; 435 | } 436 | 437 | @Override 438 | public JSONObject webwxsync(WechatMeta meta) throws WechatException{ 439 | 440 | String url = meta.getBase_uri() + "/webwxsync?skey=" + meta.getSkey() + "&sid=" + meta.getWxsid(); 441 | 442 | JSONObject body = new JSONObject(); 443 | body.put("BaseRequest", meta.getBaseRequest()); 444 | body.put("SyncKey", meta.getSyncKey()); 445 | body.put("rr", DateKit.getCurrentUnixTime()); 446 | 447 | HttpRequest request = HttpRequest.post(url).contentType("application/json;charset=utf-8") 448 | .header("Cookie", meta.getCookie()).send(body.toString()); 449 | 450 | LOGGER.debug(request.toString()); 451 | String res = request.body(); 452 | request.disconnect(); 453 | 454 | if (StringKit.isBlank(res)) { 455 | throw new WechatException("同步syncKey失败"); 456 | } 457 | 458 | JSONObject jsonObject = JSONKit.parseObject(res); 459 | JSONObject BaseResponse = jsonObject.get("BaseResponse").asJSONObject(); 460 | if (null != BaseResponse) { 461 | int ret = BaseResponse.getInt("Ret", -1); 462 | if (ret == 0) { 463 | meta.setSyncKey(jsonObject.get("SyncKey").asJSONObject()); 464 | StringBuffer synckey = new StringBuffer(); 465 | JSONArray list = meta.getSyncKey().get("List").asArray(); 466 | for (int i = 0, len = list.size(); i < len; i++) { 467 | JSONObject item = list.get(i).asJSONObject(); 468 | synckey.append("|" + item.getInt("Key", 0) + "_" + item.getInt("Val", 0)); 469 | } 470 | meta.setSynckey(synckey.substring(1)); 471 | return jsonObject; 472 | } 473 | } 474 | return null; 475 | } 476 | 477 | } 478 | --------------------------------------------------------------------------------