虽然腾讯官方不支持使用二维码充值Q币,但对于喜欢钻研的人来讲这不是问题,本文利用WPF技术讲解从扫码登陆到生成Q币充值二维码的一整套解决方案。html
由于充值Q币须要先用QQ号登陆官网。因此咱们首先要解决登陆问题。文章将分为两篇讲解,这是第一篇——扫码登陆。既然是使用WPF技术,咱们就要脱离腾讯充值官网(https://pay.qq.com),将相关操做在桌面上完成。前端
打开官网首页,点击右上角的登陆,经过抓包或分析html源码,咱们能够很轻松的找到登陆所需的二维码网址:正则表达式
咱们单独将此连接在浏览器中打开,返回的是一个带cookei参数的二维码,经过抓包软件获取Response cookies,发现只有一个参数qrsig(如图1)。服务器
图1cookie
经过步骤1得到的二维码有过时时间,服务器会使用一个get请求轮询二维码的状态(未失效、验证中、失效,如图2)。该网址是:app
https://ssl.ptlogin2.qq.com/ptqrlogin?u1=https%3A%2F%2Fpay.qq.com%2Fipay%2Flogin-proxy.html&ptqrtoken=1522270953&ptredirect=0&h=1&t=1&g=1&from_ui=1&ptlang=2052&action=0-0-1573959043618&js_ver=19111319&js_type=1&login_sig=jBa-WO7cEFUmtBpzpqH**RKJSuWDMToAbbQP97E*WArCCpvHlvFDQ*81wbTbV0*d&pt_uistyle=40&aid=11000101&has_onekey=1& 框架
图2异步
该连接请求头中带有cookie,通过分析后发现,该cookie中含有上述步骤1中获取的qrig=xxxxxxxxxx。同时,该连接的get参数众多,经过屡次抓包分析对比,发现ptqrtoken参数会根据qrig值的变化而改变,应该是前端JS加密生成的。其它参数能够固定不变。async
扣出JS函数,B站的教程不少,就是按照套路来,首先全局搜索ptqrtoken,发现加密函数名为hahs33,如图3。
图3
找到函数名称那再找函数代码就简单了,这里就不细说。值此,咱们已经得到了扫码登陆须要的全部数据,下一步即可以用代码实现了。
使用工具Vs2019,框架.net framework,UI界面见图4。
图4
实现扫码登陆,咱们有三步要走:第一步是经过本文第二章第1节的url请求登陆所需的二维码,并获得cookie中的qrsig值(用于下一步JS加密)。第二步是经过第二章第2节的url实现二维码状态验证。而要实现第二步,须要JS加密获得ptqrtoken的实际值(C#调用JS)。第三步,扫码登陆成功后,第二步的请求网址会即时返回cookies,它包含了登陆的QQ号和skey值,这两个值是生成充值二维码的必要元素。
在QqHttp类下,首先实例化一个HttpClient,用来发送GET、POST请求,并定义一个url,即本文第二章第1小节所提到的二维码网址。UseCookies = true表示自动得到cookie,AutomaticDecompression为解压缩方式,若是不声明,可能会出现乱码。
private static readonly HttpClient hc = new HttpClient(new HttpClientHandler() { UseCookies = true, AutomaticDecompression = DecompressionMethods.GZip }); String url = "https://ssl.ptlogin2.qq.com/ptqrshow?appid=11000101&e=2&l=M&s=3&d=72&v=4&t=0.1972804393669354&pt_3rd_aid=0";//此处的url,即本文第二章第1小节所提到的二维码网址
1)get请求二维码url,返回二维码图片和cookie值,注意:这里有两个返回值。咱们知道,C#返回多个值时,需使用out关健字或元组,由于咱们的方法使用了async异步,没法使用out关健字,须用元组返回多个值。
/// <summary>
/// 返回一个元组 /// 值1:包含cookies的IEnumerable<string>
/// 值2:网址二进制流 /// </summary>
/// <param name="url"></param>
/// <returns></returns>
private async Task<Tuple<IEnumerable<string>, byte[]>> httpGet() { hc.DefaultRequestHeaders.Connection.Add("keep-alive"); var resp = await hc.GetAsync(url); byte[] rspby = await resp.Content.ReadAsByteArrayAsync();//二进制流
var cookies = resp.Headers.GetValues("Set-Cookie"); //获取cookies
var tuple = new Tuple<IEnumerable<string>, byte[]>(cookies, rspby); return tuple; }
2)定义一个提取cookie的方法。由于上一步存储cookie的类型是IEnumerable<string>,须要经过如下方法提取。代码以下:
1 /// <summary>
2 /// 遍历IEnumerable<string>,取出cookie 3 /// </summary>
4 /// <param name="ck"></param>
5 /// <returns></returns>
6 private string cookList(IEnumerable<string> ck) 7 { 8 string cookies = null; 9 try
10 { 11 foreach (string cookie in ck) 12 { 13 cookies += cookie; 14 } 15 return cookies; 16 } 17 catch (Exception ex) 18 { 19 return ex.Message; 20 } 21
22 }
3)定义一个正则提取函数,从cookie中提取指定的字符串。
/// <summary>
/// 从字符串中正则提取指定内容 /// </summary>
/// <param name="str" 字符串></param>
/// <param name="re" 正则></param>
/// <returns></returns>
private string reGet(string str, string re) { try { Match s_m = Regex.Match(str, re); //正则提取,str为字符串,re为正则表达式
string s = s_m.Groups[0].ToString(); return s; } catch (Exception ex) { return ex.Message; } }
4)请求登陆二维码,并将其显示在界面的控件中。利用上述正则方法提取cookie中qrsig的值,下一步js加密需用到。
/// <summary>
/// 请求登陆二维码 /// 得到cookie里的重要参数,即qrsig值 /// </summary>
/// <param name="image"></param>
/// <returns></returns>
public async Task<string> qrSig(Image image) { var tt = await httpGet();//获取登陆二维码
IEnumerable<string> cooklist = tt.Item1;//获取IEnumerable类型的cookie
var bty = tt.Item2;//获取照片的二进制流
MemoryStream ms = new MemoryStream(bty); image.Source = BitmapFrame.Create(ms, BitmapCreateOptions.None, BitmapCacheOption.Default);//将图片显示在控件上
string cookies = cookList(cooklist);//经过咱们定义的cookList方法获取cookies
string qrsig = reGet(cookies, "(?<=qrsig=).*?(?=;)");//利用正则获取到cookies中qrsig的值,下一步js加密需用到
return qrsig; }
以上便完成了第一步,得到了二维码和cookie值。下一步即是验证二维码状态。
5)轮询二维码状态,其中参数ac是带返回值的委托。若是登陆成功,就返回含有QQ号和skey的cookies。
private async Task<string> pollGet(string url, Func<IEnumerable<string>, string> ac) { while (true) { var resp = await hc.GetAsync(url); string rspstr = await resp.Content.ReadAsStringAsync(); if (rspstr.Contains("二维码未失效") || rspstr.Contains("二维码认证中")) { Task ts = Task.Run(() => { Thread.Sleep(1000);//每1秒循环
}); await ts;//异步实现,否则会卡界面
} else if (rspstr.Contains("二维码已失效")) { MessageBox.Show("二维码已失效,请从新生成"); return "二维码已失效"; } else if (rspstr.Contains("登陆成功")) { var cookies = resp.Headers.GetValues("Set-Cookie"); return ac(cookies); } } }
注意,若是咱们使用上述pollGet方法,则须要传入上文第二章2节中验证扫码状态的交互网址,而此网址中的ptqrtoken值由js加密完成。那么咱们还须要添加一个专门调用JS函数的类QqJs,再定义一个方法算出ptqrtoken值。
首先咱们要在项目中建立一个Js文件夹,如图5,并将hh.js文件放至该文件夹中。若是咱们要用Uri的相对地址,将文件做为资源嵌入生成的exe可执行文件中,还须要将js文件的属性设置如图6所示。
图5
图6
public static string GetToken(string array) { try { Stream src = Application.GetResourceStream(new Uri("../../Js/hh.js", UriKind.Relative)).Stream;//获取资源文件
string str = new StreamReader(src, Encoding.UTF8).ReadToEnd();//读取资源文件
string fun = string.Format(@"hash33('{0}')", array); string token = ExecuteScript(fun, str); return token; }catch(Exception ex) { MessageBox.Show(ex.Message); return ex.Message; } } /// <summary>
/// 执行JS /// </summary>
/// <param name="sExpression">参数体</param>
/// <param name="sCode">JavaScript代码的字符串</param>
/// <returns></returns>
private static string ExecuteScript(string sExpression, string sCode) { MSScriptControl.ScriptControl scriptControl = new MSScriptControl.ScriptControl(); scriptControl.UseSafeSubset = true; scriptControl.Language = "JScript"; scriptControl.AddCode(sCode); try { string str = scriptControl.Eval(sExpression).ToString(); return str; } catch (Exception ex) { string str = ex.Message; } return null; }
上述调用js函数代码须要如图7添加【MSScriptControl.ScriptControl】Com引用
图7
此时,咱们便到了扫码登陆的第三步,实现登陆成功后的cookies提取。咱们再次回到QqHttp类中添加一个异步方法,实现相关数据的获取。
public async Task<string> signIn(string url_log, Image image) { string str = await httpGet(url_log, (a) => { string cook = cookList(a); string qq = reGet(reGet(cook, "(?<=uin=).*?(?=;)"), "[1-9][0-9]*");//获取登陆的QQ号
string skey = reGet(cook, "(?<=skey=).*?(?=;)");//获取登陆的skey
return qq + ";" + skey; }); if (str.Contains("二维码已失效")) { string qrsig = await qrSig(image);//从新加截获取图片
string token = QqJs.GetToken(qrsig);//将qrsig值加密
string url = $"https://ssl.ptlogin2.qq.com/ptqrlogin?u1=https%3A%2F%2Fpay.qq.com%2Fmidas%2Fminipay_v2%2Fviews%2Fcpay%2Fgame.shtml%3Fzoneid%3D0%26provide_uin%3D1502220138%26buy_quantity%3D10000%26step%3D100%26game_type%3Dduanyou%26openid%3D%26openkey%3D%26show_header%3D0%26supportCloseConfirm%3D0%26appid%3D1450000238&ptqrtoken={token}&ptredirect=1&h=1&t=1&g=1&from_ui=1&ptlang=2052&action=0-0-1569858142839&js_ver=19092321&js_type=1&login_sig=8YcWYSPBNYOF4VyhO1em7918F8dhm6THd*x0kwJWPDGk*bN3KlUWuYoJf8vtAZEf&pt_uistyle=40&aid=11000101&"; string sign = await signIn(url, image);//从新再来
return sign; } else { image.Source = new BitmapImage(new Uri("./Img/登陆成功.jpg", UriKind.Relative)); return str; } }
QqHttp hp = new QqHttp();//实例化一个链接
string qrsig = await hp.qrSig(imCoed);//获取二维码及cookie中的qrsig值
string token = QqJs.GetToken(qrsig);//将qrsig值加密
string url = $"https://ssl.ptlogin2.qq.com/ptqrlogin?u1=https%3A%2F%2Fpay.qq.com%2Fmidas%2Fminipay_v2%2Fviews%2Fcpay%2Fgame.shtml%3Fzoneid%3D0%26provide_uin%3D1502220138%26buy_quantity%3D10000%26step%3D100%26game_type%3Dduanyou%26openid%3D%26openkey%3D%26show_header%3D0%26supportCloseConfirm%3D0%26appid%3D1450000238&ptqrtoken={token}&ptredirect=1&h=1&t=1&g=1&from_ui=1&ptlang=2052&action=0-0-1569858142839&js_ver=19092321&js_type=1&login_sig=8YcWYSPBNYOF4VyhO1em7918F8dhm6THd*x0kwJWPDGk*bN3KlUWuYoJf8vtAZEf&pt_uistyle=40&aid=11000101&"; string qqkey = await hp.signIn(url, imCoed);//获取带qq和key的字符串
string[] qqkeyarr = qqkey.Split(';'); qq = qqkeyarr[0];//获取登陆的QQ值
key = qqkeyarr[1];//获取登陆的key值,此值第一个字符串为@
keyp = key.Substring(1);//截取标号1后面的字符串,不带@的key值
致此,扫码登陆的功能已所有实现,下一篇将详诉如何实现Q币充值二维码的生成,谢谢。