雖然騰訊官方不支持使用二維碼充值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幣充值二維碼的生成,謝謝。