/// <summary> /// 主要用于提醒手机端,同步状态 /// </summary> private void StatusNotify() { try { //反馈服务器 string webwxstatusnotifyUrl = host + "/cgi-bin/mmwebwx-bin/webwxstatusnotify"; StatusNotifyRequest statusNotifyRequest = new StatusNotifyRequest(); statusNotifyRequest.BaseRequest = baseRequest; statusNotifyRequest.Code = 3; statusNotifyRequest.FromUserName = user.UserName; statusNotifyRequest.ToUserName = user.UserName; statusNotifyRequest.ClientMsgId = Utils.GetJavaTimeStamp(); //反馈结果可以不理 httpClient.PostJson <StatusNotifyResponse>(webwxstatusnotifyUrl, statusNotifyRequest); } catch (Exception e) { asyncOperation.Post( new SendOrPostCallback((obj) => { ExceptionCatched?.Invoke(this, new TEventArgs <Exception>((Exception)obj)); }), e); throw e; } }
/// <summary> /// 同步发送文字消息 /// </summary> /// <param name="msg">文字</param> /// <param name="toUserName">发送人UserName</param> /// <returns></returns> public SendMsgResponse SendMsg(string msg, string toUserName) { try { string time = Utils.GetJavaTimeStamp().ToString(); string sendMsgUrl = string.Format(host + "/cgi-bin/mmwebwx-bin/webwxsendmsg?pass_ticket={0}", passTicket); SendMsgRequest sendMsgRequest = new SendMsgRequest() { BaseRequest = baseRequest, Msg = new Msg() { FromUserName = user.UserName, ToUserName = toUserName, ClientMsgId = time, LocalID = time, Type = MSGTYPE.MSGTYPE_TEXT, Content = msg }, Scene = 0 }; SendMsgResponse sendMsgResponse = httpClient.PostJson <SendMsgResponse>(sendMsgUrl, sendMsgRequest); return(sendMsgResponse); } catch (Exception ex) { Console.WriteLine($"Send Msg {ex.Message}"); throw ex; } }
/// <summary> /// 同步上传文件 /// </summary> /// <param name="fileInfo">文件信息</param> /// <param name="toUserName">发送人UserName,其实没什么用,但是官方有这个参数</param> /// <returns></returns> public UploadMediaResponse UploadFile(FileInfo fileInfo, string toUserName) { string postUrl = uploadHost + "/cgi-bin/mmwebwx-bin/webwxuploadmedia?f=json"; int bufferLength = 512 * 1024; string datetime = DateTime.Now.ToString("ddd MMM dd yyyy HH:mm:ss", CultureInfo.CreateSpecificCulture("en-US")) + " GMT+0800 (中国标准时间)"; UploadMediaRequest uploadMediaRequest = new UploadMediaRequest() { UploadType = 2, BaseRequest = baseRequest, ClientMediaId = Utils.GetJavaTimeStamp(), TotalLen = fileInfo.Length, StartPos = 0, DataLen = fileInfo.Length, MediaType = 4, FromUserName = user.UserName, ToUserName = toUserName, FileMd5 = Utils.GetFileMD5Hash(fileInfo) }; UploadMediaResponse response = null; //文件大小超过512Kb,分块上传。 if (fileInfo.Length > bufferLength) { int chunks = (int)Math.Ceiling((double)fileInfo.Length / bufferLength); byte[] buffer = new byte[bufferLength]; Stream readStream = fileInfo.OpenRead(); int chunk = 0; int readLength = 0; while ((readLength = readStream.Read(buffer, 0, buffer.Length)) != 0) { List <FormDataItem> dataList = new List <FormDataItem>() { new FormDataItem("id", "WU_FILE_" + uploadMedia.Count), new FormDataItem("name", fileInfo.Name), new FormDataItem("type", MimeMapping.GetMimeMapping(fileInfo.Name)), new FormDataItem("lastModifiedDate", datetime), new FormDataItem("size", fileInfo.Length.ToString()), new FormDataItem("chunks", chunks.ToString()), new FormDataItem("chunk", chunk.ToString()), new FormDataItem("mediatype", GetMediaType(fileInfo.Extension)), new FormDataItem("uploadmediarequest", JsonConvert.SerializeObject(uploadMediaRequest, Newtonsoft.Json.Formatting.None)), new FormDataItem("webwx_data_ticket", httpClient.CookieContainer.GetCookies(new Uri(cookieRedirectUri))["webwx_data_ticket"].Value), new FormDataItem("pass_ticket", passTicket), new FormDataItem("filename", fileInfo.Name, buffer, readLength) }; string result = httpClient.PostMutipart(postUrl, dataList); response = JsonConvert.DeserializeObject <UploadMediaResponse>(result); chunk++; } } else { byte[] buffer = new byte[fileInfo.Length]; Stream readStream = fileInfo.OpenRead(); int readLength = readStream.Read(buffer, 0, buffer.Length); List <FormDataItem> dataList = new List <FormDataItem>() { new FormDataItem("id", "WU_FILE_" + uploadMedia.Count), new FormDataItem("name", fileInfo.Name), new FormDataItem("type", MimeMapping.GetMimeMapping(fileInfo.Name)), new FormDataItem("lastModifiedDate", datetime), new FormDataItem("size", fileInfo.Length.ToString()), new FormDataItem("mediatype", GetMediaType(fileInfo.Extension)), new FormDataItem("uploadmediarequest", JsonConvert.SerializeObject(uploadMediaRequest, Newtonsoft.Json.Formatting.None)), new FormDataItem("webwx_data_ticket", httpClient.CookieContainer.GetCookies(new Uri(cookieRedirectUri))["webwx_data_ticket"].Value), new FormDataItem("pass_ticket", passTicket), new FormDataItem("filename", fileInfo.Name, buffer, readLength) }; string result = httpClient.PostMutipart(postUrl, dataList); response = JsonConvert.DeserializeObject <UploadMediaResponse>(result); } uploadMedia.Add(fileInfo.Name, response.MediaId); return(response); }
/// <summary> /// 同步发送文件,自动分块上传,文件较大可能会卡住进程,建议异步发送 /// </summary> /// <param name="fileInfo">文件信息</param> /// <param name="toUserName">发送人UserName</param> /// <returns></returns> public SendMsgResponse SendMsg(FileInfo fileInfo, string toUserName) { string mediaId = string.Empty; if (uploadMedia.Keys.Contains(fileInfo.Name)) { mediaId = uploadMedia[fileInfo.Name]; } else { UploadMediaResponse uploadMediaResponse = UploadFile(fileInfo, toUserName); mediaId = uploadMediaResponse.MediaId; } string mime = MimeMapping.GetMimeMapping(fileInfo.Name); string time = Utils.GetJavaTimeStamp().ToString(); SendMsgResponse response = null; if (mime.StartsWith("image")) { string sendMsgUrl = string.Format(host + "/cgi-bin/mmwebwx-bin/webwxsendmsgimg?fun=async&f=json&lang=zh_CN&pass_ticket={0}", passTicket); SendMediaMsgRequest sendImgMsgRequest = new SendMediaMsgRequest() { BaseRequest = baseRequest, Msg = new MediaMsg() { ClientMsgId = time, FromUserName = user.UserName, LocalID = time, MediaId = mediaId, ToUserName = toUserName, Type = MSGTYPE.MSGTYPE_IMAGE }, Scene = 0 }; response = httpClient.PostJson <SendMsgResponse>(sendMsgUrl, sendImgMsgRequest); } else if (mime.StartsWith("video")) { string sendMsgUrl = string.Format(host + "/cgi-bin/mmwebwx-bin/webwxsendvideomsg?fun=async&f=json&lang=zh_CN&pass_ticket={0}", passTicket); SendMediaMsgRequest sendImgMsgRequest = new SendMediaMsgRequest() { BaseRequest = baseRequest, Msg = new MediaMsg() { ClientMsgId = time, FromUserName = user.UserName, LocalID = time, MediaId = mediaId, ToUserName = toUserName, Type = MSGTYPE.MSGTYPE_IMAGE }, Scene = 0 }; response = httpClient.PostJson <SendMsgResponse>(sendMsgUrl, sendImgMsgRequest); } else { string sendMsgUrl = string.Format(host + "/cgi-bin/mmwebwx-bin/webwxsendappmsg?fun=async&f=json&lang=zh_CN&pass_ticket={0}", passTicket); SendMsgRequest sendAppMsgRequest = new SendMsgRequest() { BaseRequest = baseRequest, Msg = new Msg() { ClientMsgId = time, Content = string.Format("<appmsg appid='wxeb7ec651dd0aefa9' sdkver=''><title>{0}</title><des></des><action></action><type>6</type><content></content><url></url><lowurl></lowurl><appattach><totallen>{1}</totallen><attachid>{2}</attachid><fileext>{3}</fileext></appattach><extinfo></extinfo></appmsg>", fileInfo.Name, fileInfo.Length, mediaId, fileInfo.Extension.Substring(1)), FromUserName = user.UserName, ToUserName = toUserName, LocalID = time, Type = MSGTYPE.MSGTYPE_DOC }, Scene = 0 }; response = httpClient.PostJson <SendMsgResponse>(sendMsgUrl, sendAppMsgRequest); } return(response); }
/// <summary> /// 开始轮询检测是否有新消息 /// </summary> private void Sync() { while (syncPolling) { try { string syncCheckUrl = string.Format(pushHost + "/cgi-bin/mmwebwx-bin/synccheck?r={0}&skey={1}&sid={2}&uin={3}&deviceid={4}&synckey={5}&_={6}", Utils.GetJavaTimeStamp(), baseRequest.Skey, baseRequest.Sid, baseRequest.Uin, baseRequest.DeviceID, syncKey.ToString(), syncKey.Step); string syncCheckResult = httpClient.GetString(syncCheckUrl); if (!syncPolling) { return; } MatchCollection matchCollection = Regex.Matches(syncCheckResult, @"\d+"); string retcode = matchCollection[0].Value; string selector = matchCollection[1].Value; Utils.Debug("retcode:" + retcode + " selector:" + selector); switch (retcode) { case "0": if (selector != "0") { //有新消息,拉取信息。 SyncRequest syncRequest = new SyncRequest(); syncRequest.BaseRequest = baseRequest; syncRequest.SyncKey = syncKey; syncRequest.rr = Utils.Get_r(); string syncUrl = string.Format(host + "/cgi-bin/mmwebwx-bin/webwxsync?sid={0}&skey={1}&pass_ticket={2}", baseRequest.Sid, baseRequest.Skey, passTicket); SyncResponse syncResponse = httpClient.PostJson <SyncResponse>(syncUrl, syncRequest); if (!syncPolling) { return; } else { syncKey = syncResponse.SyncKey; //只要不是0,就是有消息,有消息我们处理就行了,不管selector是几 if (syncResponse.AddMsgCount == 0 && syncResponse.DelContactCount == 0 && syncResponse.ModContactCount == 0 && syncResponse.ModChatRoomMemberCount == 0) { //会有这么一种情况,selector=2,但是没有任何消息体,这样会导致持续快速的空交互 //除非下次有新消息,或者主动点击手机触发消息 //为了防止这种情况,做个5秒停顿。 Thread.Sleep(5000); } else { if (syncResponse.AddMsgList.Count > 0) { asyncOperation.Post( new SendOrPostCallback((obj) => { ReceiveMsg?.Invoke(this, new TEventArgs <List <AddMsg> >((List <AddMsg>)obj)); }), syncResponse.AddMsgList); } if (syncResponse.ModContactCount > 0) { asyncOperation.Post( new SendOrPostCallback((obj) => { ModContactListComplete?.Invoke(this, new TEventArgs <List <ModContactItem> >((List <ModContactItem>)obj)); }), syncResponse.ModContactList); } if (syncResponse.DelContactCount > 0) { asyncOperation.Post( new SendOrPostCallback((obj) => { DelContactListComplete?.Invoke(this, new TEventArgs <List <DelContactItem> >((List <DelContactItem>)obj)); }), syncResponse.DelContactList); } if (syncResponse.ModChatRoomMemberCount > 0) { //待分析,这个消息基本没有 } } } } break; case "1100": //登出了微信,很可能是wx.qq.com和wx2.qq.com调用接口不一致导致的,注意登陆时候的跳转地址 Close(); asyncOperation.Post( new SendOrPostCallback((obj) => { LogoutComplete?.Invoke(this, new TEventArgs <User>((User)obj)); }), user); break; case "1101": Close(); asyncOperation.Post( new SendOrPostCallback((obj) => { LogoutComplete?.Invoke(this, new TEventArgs <User>((User)obj)); }), user); throw new Exception("1101可能其他地方登录/登出了 WEB 版微信,请检查手机端已登出WEB微信,然后稍后再试"); break; case "1102": Close(); asyncOperation.Post( new SendOrPostCallback((obj) => { LogoutComplete?.Invoke(this, new TEventArgs <User>((User)obj)); }), user); throw new Exception("1102被强制登出(很可能cookie冲突),请检查手机端已登出WEB微信,然后稍后再试"); break; default: //有其他任何异常,取消轮询 throw new Exception("轮询结果异常,停止轮询:" + syncCheckResult); break; } Thread.Sleep(1000); } catch (Exception ex) { FileLog.Exception("Init", ex); asyncOperation.Post( new SendOrPostCallback((obj) => { ExceptionCatched?.Invoke(this, new TEventArgs <Exception>((Exception)obj)); }), ex); } } }
/// <summary> /// 读取用户的联系人列表,其中只包含公众号,个人号,如果返回值seq不为0,那么用户列表还没获取完(因为可能会有几千人的号,不可能一次获取完),则附带上seq的值继续获取。 /// </summary> private void GetContact() { try { //获取联系人列表 finishGetContactList = false; string getContactUrl = string.Format(host + "/cgi-bin/mmwebwx-bin/webwxgetcontact?r={0}&seq={1}&skey={2}", Utils.GetJavaTimeStamp(), 0, baseRequest.Skey); while (!finishGetContactList) { string contactResult = httpClient.GetStringOnce(getContactUrl); GetContactResponse getContactResponse = JsonConvert.DeserializeObject <GetContactResponse>(contactResult); asyncOperation.Post( new SendOrPostCallback((list) => { GetContactComplete?.Invoke(this, new TEventArgs <List <Contact> >((List <Contact>)list)); }), getContactResponse.MemberList); if (getContactResponse.Seq == 0) { finishGetContactList = true; } else { getContactUrl = string.Format(host + "/cgi-bin/mmwebwx-bin/webwxgetcontact?r={0}&seq={1}&skey={2}", Utils.GetJavaTimeStamp(), getContactResponse.Seq, baseRequest.Skey); } } //获取完联系人中的公众号,才能获得名称,这个时候再发送图文消息事件。 asyncOperation.Post( new SendOrPostCallback((obj) => { MPSubscribeMsgListComplete?.Invoke(this, new TEventArgs <List <MPSubscribeMsg> >((List <MPSubscribeMsg>)obj)); }), mpSubscribeMsgList); } catch (Exception e) { if (e is WebException) { WebException we = e as WebException; if (we.Status == WebExceptionStatus.ProtocolError && ((HttpWebResponse)we.Response).StatusCode == HttpStatusCode.ServiceUnavailable) { //过千人账号有时候获取不到联系人列表,服务器返回503,官方测试结果也是反馈503导致获取不到,为了不影响正常使用,跳过获取联系人步骤 asyncOperation.Post( new SendOrPostCallback((obj) => { ExceptionCatched?.Invoke(this, new TEventArgs <Exception>((Exception)obj)); }), new GetContactException("无法获取好友列表")); } } else { asyncOperation.Post( new SendOrPostCallback((obj) => { ExceptionCatched?.Invoke(this, new TEventArgs <Exception>((Exception)obj)); }), e); } } }
/// <summary> /// 获取联系人信息,例如初始化、群聊里面。 /// </summary> /// <param name="statusNotifyUserName">需要获取的UserName列表,包括群,个人用户,用英文,分割</param> /// <param name="EncryChatRoomId">默认为空,如果是获取群内成员详细信息,则填写encryChatRoomId,也就是群的UserName</param> public void GetBatchGetContactAsync(string statusNotifyUserName, string encryChatRoomId = "") { Task.Factory.StartNew(() => { try { //获取历史会话列表 string webwxbatchgetcontactUrl = string.Format(host + "/cgi-bin/mmwebwx-bin/webwxbatchgetcontact?type=ex&r={0}", Utils.GetJavaTimeStamp()); string[] chatNameArr = statusNotifyUserName.Split(','); bool finishGetChatList = false; BatchGetContactRequest batchGetContactRequest = new BatchGetContactRequest(); batchGetContactRequest.BaseRequest = baseRequest; int count = chatNameArr.Length; int index = 0; //一批次最多获取50条,多出来分批获取 while (!finishGetChatList) { batchGetContactRequest.List = new List <ChatRoom>(); if (((index + 1) * 50) < count) { for (int i = index * 50; i < (index + 1) * 50; i++) { batchGetContactRequest.List.Add(new ChatRoom { UserName = chatNameArr[i], EncryChatRoomId = encryChatRoomId }); } } else { for (int i = index * 50; i < count; i++) { batchGetContactRequest.List.Add(new ChatRoom { UserName = chatNameArr[i], EncryChatRoomId = encryChatRoomId }); } finishGetChatList = true; } BatchGetContactResponse batchGetContactMsg = httpClient.PostJson <BatchGetContactResponse>(webwxbatchgetcontactUrl, batchGetContactRequest); asyncOperation.Post( new SendOrPostCallback((list) => { BatchGetContactComplete?.Invoke(this, new TEventArgs <List <Contact> >((List <Contact>)list)); }), batchGetContactMsg.ContactList); index++; } } catch (Exception e) { asyncOperation.Post( new SendOrPostCallback((obj) => { ExceptionCatched?.Invoke(this, new TEventArgs <Exception>((Exception)obj)); }), e); } }); }
/// <summary> /// 获取登陆二维码 /// </summary> private void GetLoginQrCode() { try { string jsloginUrl = $"https://login.wx2.qq.com/jslogin?appid=wx782c26e4c19acffb&redirect_uri=https%3A%2F%2Fwx2.qq.com%2Fcgi-bin%2Fmmwebwx-bin%2Fwebwxnewloginpage&fun=new&lang=zh_CN&_={Utils.GetJavaTimeStamp()}"; string result = httpClient.GetString(jsloginUrl); Utils.Debug("GetLoginQrCode " + result); string qruuidStr = "window.QRLogin.uuid = \""; int index = result.IndexOf("window.QRLogin.uuid = \""); if (index == -1) { throw new Exception("获取登陆二维码失败,请稍后再试。"); } else { uuid = result.Substring(index + qruuidStr.Length, result.Length - index - qruuidStr.Length - "\";".Length); } string qrcodeUrl = string.Format("https://login.weixin.qq.com/qrcode/{0}", uuid); var img = httpClient.GetImage(qrcodeUrl); asyncOperation.Post( new SendOrPostCallback((obj) => { GetLoginQrCodeComplete?.Invoke(this, new TEventArgs <byte[]>((byte[])obj)); }), img); } catch (Exception e) { asyncOperation.Post( new SendOrPostCallback((obj) => { ExceptionCatched?.Invoke(this, new TEventArgs <Exception>((Exception)obj)); }), e); throw e; } }
/// <summary> /// 同步通过好友认证 /// </summary> /// <param name="info">sync中获得的申请信息</param> /// <returns></returns> public SimpleResponse VerifyUser(RecommendInfo info) { string verifyUserUrl = host + "/cgi-bin/mmwebwx-bin/webwxverifyuser?r=" + Utils.GetJavaTimeStamp(); VerifyUserRequest request = new VerifyUserRequest(); request.BaseRequest = baseRequest; request.Opcode = VERIFYUSER_OPCODE.VERIFYUSER_OPCODE_VERIFYOK; request.SceneList = new List <int>() { (int)ADDSCENE_PF.ADDSCENE_PF_WEB }; request.VerifyUserList = new List <Modal.Request.VerifyUser>() { new Modal.Request.VerifyUser { Value = info.UserName, VerifyUserTicket = info.Ticket } }; request.skey = baseRequest.Skey; //反馈结果可以不理 return(httpClient.PostJson <SimpleResponse>(verifyUserUrl, request)); }