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


一、前言

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

因為充值Q幣需要先用QQ號登錄官網。所以我們首先要解決登錄問題。文章將分為兩篇講解,這是第一篇——掃碼登錄。既然是使用WPF技術,我們就要脫離騰訊充值官網(https://pay.qq.com),將相關操作在桌面上完成。

二、獲取登錄所需的數據

  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)。

圖1

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

通過步驟1獲得的二維碼有過期時間,服務器會使用一個get請求輪詢二維碼的狀態(未失效、驗證中、失效,如圖2)。該網址是:

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加密生成的。其它參數可以固定不變。

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

扣出JS函數,B站的教程很多,就是按照套路來,首先全局搜索ptqrtoken,發現加密函數名為hahs33,如圖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 pollGet(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幣充值二維碼的生成,謝謝。 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM