持久的毅力是事业成功的基础
上一篇《WebQQ群发限制的突破》讲到了基本的消息发送,但是后面没讲完,结果很多朋友就在那里犯迷糊了,说是突破WebQQ群发的限制,却说了一大堆与突破没关系的废话。所以,今天趁着有时间,还是好好补充一下上篇文章没讲完的地方。
针对发送好友消息没有实际收到的解决办法
可能由于网络原因,或者Tx的关键词屏蔽功能,使得有些字词或句子无法发送出去,这就要测试一下web3.qq.com本身到底屏蔽了哪些字词,好对症下药。如果我们确实必须要发送这样一些被屏蔽掉的字词该怎么做呢?对了,通过将文字转换成图片。就可以发送了。但是,在WebQQ里要想发送图片,对于刚入门WebQQ的新手来说谈何容易,可能你对WebQQ的登录,或是发送消息,已经可以做到熟能生巧,但是发送图片这一块很多人可能会卡住。
在WebQQ里发送图片给好友,其实分为两部分:上传图片到TX指定服务器,上传之后得到一个图片的guid和一些其他参数,作为发送的参数再发送。
上传图片,我们需要改变一下http请求的正文类型(CntentType)为multipart/form-data,并且加上一个分隔符号,这个丰富好可以自己定,但是一定要能将前后两段文本完整的区分开来。对于请求头参数构造,我们单独作为一个方法,将构造好的结果返回到一个字典里,供请求方法调用。
/// <summary> /// 本文自博客园原创,转载请加上此链接 /// http://www.cnblogs.com/uu102 /// </summary> /// <param name="fileName"></param> /// <returns></returns> public string Upload_Offline_Pic(string fileName) { string timeStamp = Util.GetTimestamp(DateTime.Now).ToString(); String time = DateTime.Now.Ticks.ToString("x"); string url = string.Format("http://weboffline.ftn.qq.com/ftn_access/upload_offline_pic?time={0}", timeStamp); string skeys = QQGlobal.ACCountManager[this.Uin].CookieString.Substring(QQGlobal.ACCountManager[this.Uin].CookieString.IndexOf("skey=") + 5, 10); Dictionary<string, string> dic = new Dictionary<string, string>(); dic.Add("callback", "parent.EQQ.Model.ChatMsg.callbackSendPic"); dic.Add("locallangid", "2052"); dic.Add("clientversion", "1409"); dic.Add("uin", this.Uin); dic.Add("skey", skeys); dic.Add("appid", "15000101"); dic.Add("peeruin", "593023668"); dic.Add("fileid", this.fileId.ToString()); dic.Add("vfwebqq", QQGlobal.ACCountManager[this.Uin].Vfwebqq); dic.Add("senderviplevel", "0"); dic.Add("reciverviplevel", "0"); this.fileId++; string imagePath = new Regex(@"""filepath"":""(?'filepath'[^\""]+)""").Match(SubmitData(url, fileName, dic)).Groups["filepath"].Value.Replace("\\", ""); string shortfilename = fileName.Substring(fileName.LastIndexOf("\\") + 1, fileName.Length - fileName.LastIndexOf("\\") - 1); this.OffLine_Pics[this.OffLine_Pics.Count-1].ServerPath = imagePath; return imagePath; }
在这个请求头的参数构造里,有几个必须附带讲明一下,时间戳。
/// <summary> /// 本文自博客园原创,转载请加上此链接 /// http://www.cnblogs.com/uu102 /// </summary> /// <param name="fileName"></param> /// <returns></returns> public static long GetTimestamp(DateTime dateTime)//获取时间戳 { DateTime startDate = new DateTime(1970, 1, 1); DateTime endDate = dateTime.ToUniversalTime(); TimeSpan span = endDate - startDate; return (long)(span.TotalMilliseconds + 0.5); }
this.Uin是当前登录的QQ号码,skeys是从当前所在的cookie里提取出来的,仔细找找,在cookies里确实存在这样的键。file_id是自己定义的一个数字,从1开始累加1,QQGlobal.ACCountManager[this.Uin].Vfwebqq是前面登录就已经获取到了的参数。具体获取登陆之前的参数过程,请大家自己查阅网上登录WebQQ的教程吧。这里这些东西就不做多余的说明了。
注意观察,第一段代码里面有一个突然冒出来的函数SubmitData(url, fileName, dic),这一个函数,其实就是我们接下来要讲到的核心请求了,通过以上构造的参数,我们可以做以下请求了。
/// <summary> /// 模拟表单提交上传图片 /// 本文自博客园原创,转载请加上此链接 /// http://www.cnblogs.com/uu102 /// </summary> /// <param name="url">地址</param> /// <param name="fileName">图pain的文件名(例如:a.jpg)</param> /// <param name="dic">dictionay<T,T>结构"/></param> /// <returns></returns> private string SubmitData(string url, string fileName, Dictionary<string, string> dic/* ,string[] keys, string[] values*/) { string boundary = "----------" + DateTime.Now.Ticks.ToString("x"); HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(new Uri(url)); httpWebRequest.CookieContainer = QQGlobal.ACCountManager[this.Uin].CookieContainer; httpWebRequest.ContentType = "multipart/form-data; boundary=" + boundary; httpWebRequest.Method = "POST"; StringBuilder sb = new StringBuilder(); if (dic.Count != 0) { foreach (KeyValuePair<string, string> kvp in dic) { sb.Append("--"); sb.Append(boundary); sb.Append("\r\n"); sb.Append("Content-Disposition: form-data; name=\"" + kvp.Key + "\"\r\n\r\n"); sb.Append(kvp.Value); sb.Append("\r\n"); } } string shortfilename = fileName.Substring(fileName.LastIndexOf("\\") + 1, fileName.Length - fileName.LastIndexOf("\\") - 1); this.OffLine_Pics.Add(new OffLine_Up_Pic()); sb.Append("--"); sb.Append(boundary); sb.Append("\r\n"); sb.Append("Content-Disposition: form-data; name=\"file\"; filename=\""); sb.Append(shortfilename); sb.Append("\""); sb.Append("\r\n"); sb.Append("Content-Type: application/image/jpeg"); sb.Append("\r\n"); sb.Append("\r\n"); string postHeader = sb.ToString(); byte[] postHeaderBytes = Encoding.UTF8.GetBytes(postHeader); byte[] boundaryBytes = Encoding.ASCII.GetBytes("\r\n--" + boundary + "--\r\n"); FileStream fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read); this.OffLine_Pics[this.OffLine_Pics.Count-1].Length = fileStream.Length;// this.OffLine_Pics[this.OffLine_Pics.Count-1].ShortFileName = shortfilename; long length = postHeaderBytes.Length + fileStream.Length + boundaryBytes.Length; httpWebRequest.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"; httpWebRequest.AllowWriteStreamBuffering = false; httpWebRequest.ServicePoint.Expect100Continue = false; httpWebRequest.ContentLength = length; Stream requestStream = httpWebRequest.GetRequestStream(); requestStream.Write(postHeaderBytes, 0, postHeaderBytes.Length); byte[] buffer = new Byte[checked((uint)Math.Min(4096, (int)fileStream.Length))]; long filebytes = fileStream.Length; int bytesRead = 0; while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0) requestStream.Write(buffer, 0, bytesRead); requestStream.Write(boundaryBytes, 0, boundaryBytes.Length); WebResponse webResponse2 = httpWebRequest.GetResponse(); Stream stream = webResponse2.GetResponseStream(); StreamReader streamReader = new StreamReader(stream); string html = streamReader.ReadToEnd(); requestStream.Close(); fileStream.Close(); return html; }
这一段代码其实很好看懂,无非就是一个HttpWebRequest请求,只不过请求的类型不一样,而且数据是分段上传的。配合前文所列出的发送消息的代码,就可以发送图片了。
另外再补充说明一下,在请求之后返回的html文本里,有三个参数都是非常重要的,到时候自己一看便知道了。
针对图片不能重复发送的解决办法
图片如果重复发送,在现实WebQQ聊天过程中很少会发生重复多次发送同一张图片的,谁还有事没事只发同一张图片啊,但是群发里面就经常碰到的这样的情况,重复发送一张图片很多次,所以TX就给你来个限制。
对于重复发送同一张图片,很好的解决办法就是控制图片的发送次数,或者发送频率快慢。最大程度的模拟真实发送过程,但是这样是治标不治本,很难保证不会被TX限制。
在图片里用GDI+给图片添加干扰素。所谓干扰素,无非就是给图片随机生成一些颜色或点,让图片看起来不是发的同一张图片。
这个方法很简单,代码就不加了。
针对一台机器挂QQ数量有限制的解决办法
一台电脑最多挂QQ的数量很有限,超过这个限额tx会要求强制性下线或是验证。因此,唯一的办法,就是批量更改IP。所谓批量,就是说等完10个QQ或者20,然后马上改变IP,再继续登另外一批。这就是我所说的批量。^.^
好了,这些就是说掌握的突破方法,大家有什么别的好方法,不妨共享一下。
教程每天都更新,请继续保持关注!