利用WPF生成Q幣充值二維碼——掃碼登陸篇

1、前言

雖然騰訊官方不支持使用二維碼充值Q幣,但對於喜歡鑽研的人來講這不是問題,本文利用WPF技術講解從掃碼登陸到生成Q幣充值二維碼的一整套解決方案。html

由於充值Q幣須要先用QQ號登陸官網。因此咱們首先要解決登陸問題。文章將分爲兩篇講解,這是第一篇——掃碼登陸。既然是使用WPF技術,咱們就要脫離騰訊充值官網(https://pay.qq.com),將相關操做在桌面上完成。前端

2、獲取登陸所需的數據

  1. 找到登陸所需的二維碼網址

打開官網首頁,點擊右上角的登陸,經過抓包或分析html源碼,咱們能夠很輕鬆的找到登陸所需的二維碼網址:正則表達式

https://ssl.ptlogin2.qq.com/ptqrshow?appid=11000101&e=2&l=M&s=3&d=72&v=4&t=0.7299344722244967&pt_3rd_aid=0瀏覽器

咱們單獨將此連接在瀏覽器中打開,返回的是一個帶cookei參數的二維碼,經過抓包軟件獲取Response cookies,發現只有一個參數qrsig(如圖1)。服務器

圖1cookie

2.驗證掃碼狀態的交互網址

經過步驟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

3.找到ptqrtoken所需的js加密函數

扣出JS函數,B站的教程不少,就是按照套路來,首先全局搜索ptqrtoken,發現加密函數名爲hahs33,如圖3。

 

圖3

 

找到函數名稱那再找函數代碼就簡單了,這裏就不細說。值此,咱們已經得到了掃碼登陸須要的全部數據,下一步即可以用代碼實現了。 

3、C#代碼實現

  1. 建立WPF項目

使用工具Vs2019,框架.net framework,UI界面見圖4。

圖4


實現掃碼登陸,咱們有三步要走:第一步是經過本文第二章第1節的url請求登陸所需的二維碼,並獲得cookie中的qrsig值(用於下一步JS加密)。第二步是經過第二章第2節的url實現二維碼狀態驗證。而要實現第二步,須要JS加密獲得ptqrtoken的實際值(C#調用JS)。第三步,掃碼登陸成功後,第二步的請求網址會即時返回cookies,它包含了登陸的QQ號和skey值,這兩個值是生成充值二維碼的必要元素。
 

2.添加QqHttp類,並聲明變量

在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小節所提到的二維碼網址

3.在QqHttp中添加方法

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; }
View Code

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         }
View Code

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; } }
View Code

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; }
View Code

以上便完成了第一步,得到了二維碼和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); } } }
View Code

注意,若是咱們使用上述pollGet方法,則須要傳入上文第二章2節中驗證掃碼狀態的交互網址,而此網址中的ptqrtoken值由js加密完成。那麼咱們還須要添加一個專門調用JS函數的類QqJs,再定義一個方法算出ptqrtoken值。

4.添加QqJs類,定義C#調用js函數的靜態方法

首先咱們要在項目中建立一個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; } 
View Code

上述調用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; } }
View Code

5.實例化類,調用函數,實現功能

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值
View Code

致此,掃碼登陸的功能已所有實現,下一篇將詳訴如何實現Q幣充值二維碼的生成,謝謝。

相關文章
相關標籤/搜索