一、前言

虽然腾讯官方不支持使用二维码充值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;
}

2)定义一个提取cookie的方法。因为上一步存储cookie的类型是IEnumerable<string>,需要通过以下方法提取。代码如下:

 /// <summary>
/// 遍历IEnumerable<string>,取出cookie
/// </summary>
/// <param name="ck"></param>
/// <returns></returns>
private string cookList(IEnumerable<string> ck)
{
string cookies = null;
try
{
foreach (string cookie in ck)
{
cookies += cookie;
}
return cookies;
}
catch (Exception ex)
{
return ex.Message;
} }

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[].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();//每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值。

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

上述调用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;
}
}

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[];//获取登录的QQ值
key = qqkeyarr[];//获取登录的key值,此值第一个字符串为@
keyp = key.Substring();//截取标号1后面的字符串,不带@的key值

致此,扫码登录的功能已全部实现,下一篇将详诉如何实现Q币充值二维码的生成,谢谢。

利用WPF生成Q币充值二维码——扫码登录篇的更多相关文章

  1. 微信公众号生成带参数的二维码asp源码下载

    晚上闲着没事,一个朋友联系,让帮忙写一个微信公众号利用asp生成带参数的二维码,别人扫了后如果已经关注过该公众号的,则直接进入公众号里,如果没关注则提示关注,关注后自动把该微信用户资料获取到并且保存入 ...

  2. .NET使用ZXing.NET生成中间带图片的二维码

    很久之前就有写这样的代码了,只是一直没记录下来,偶然想写成博客. 把之前的代码封装成函数,以方便理解以及调用. 基于开源的 ZXing.NET 组件,代码如下: 先添加对ZXing.NET的引用,然后 ...

  3. phpqrcode生成带logo的二维码图片及带文字的二维码图片

    <?php require_once "./phpqrcode/phpqrcode.php"; /** * 这样就可以生成二维码了,实际上在png这个方法里还有几个参数需要使 ...

  4. .NET生成带Logo的二维码

    使用ThoughtWorks.QRCode生成,利用这个库来生成带Logo的二维码(就是中间嵌了一个图片的二维码),直接见代码: HttpContext context = HttpContext.C ...

  5. js实现生成中间带图片的二维码

    之前需要实现生成中间带图片的二维码,所以找了半天终于找到一个可以用的.于是在这里记录一下. 下面是需要注意的几点: 1.使用的js为jquery-qrcode 但是已经经过别人的修改,和网上原来的那些 ...

  6. C# 生成 DataMatrix 格式的二维码

    该文主要是利用OnBarcode.dll 生成 DataMatrix 格式的二维码的一些简单方法和操作技巧.关于QrBarcode的二维码比较常见和简单,网上有很多资源. 1.附件为dll 2.利用上 ...

  7. PHP生成带logo图像二维码的两种方法

    本文主要和大家分享PHP生成带logo图像二维码的两种方法,主要以文字和代码的形式和大家分享,希望能帮助到大家. 一.利用Google API生成二维码Google提供了较为完善的二维码生成接口,调用 ...

  8. C#生成带logo的二维码

    带logo的二维码生成分为两步骤:首先根据输入的内容生成二维码图片,然后读取本地的logo图片,通过图片处理生成带logo的二维码. 生成的二维码效果如下: 下面直接贴出二维码生成类   QRCode ...

  9. js生成带参的二维码

    最近项目中有需求生成带参的二维码,考虑过用JAVA后台生成返回前端展示,后面了解到用jquery的qrcode.js插件可以很好现实 引入js: require.config({ baseUrl : ...

随机推荐

  1. Mysql多表关系

    mysql多表关系 多表关系是关系型数据库特有的 三种关系 一对一关系 一对多关系 多对多关系 总结 一对一 例子:用户和用户信息 外键设置在用户上,外键字段唯一非空 添加 无级联:先增加被关联表记录 ...

  2. 爬取豆瓣热销书榜前250 生成.csv文件

    from lxml import etreeimport requestsimport csvfp = open('E:/doubanbook.csv','wt',newline='',encodin ...

  3. 转:FileSync plugin for Eclipse 安装注意事项 Eclipse文件同步插件

    习惯了使用MyEclipse,各种插件不用自己安装,觉得开发起来很方便,现在大家都用Eclipse了,还有不用Eclipse用更高级的,IT当然开发大型项目没人用UltraEdit吧,虽然是一个不错的 ...

  4. TCP 的三次握手,四次挥手和重要的细节—干货满满,建议细读

    最近把个人博客搭建好了,链接在这里:tobe的呓语,文章会先在博客和公众号更新~ 大家多多收藏啊 上一次讲了 UDP 协议,从这次开始,就要讲 TCP 协议了,因为 TCP 协议涉及到的东西很多,一篇 ...

  5. luogu P2946 [USACO09MAR]牛飞盘队Cow Frisbee Team

    题目背景 老唐最近迷上了飞盘,约翰想和他一起玩,于是打算从他家的N头奶牛中选出一支队伍. 每只奶牛的能力为整数,第i头奶牛的能力为R i .飞盘队的队员数量不能少于 1.大于N.一 支队伍的总能力就是 ...

  6. git删除中间某次提交

    git log获取commit信息 commit 58211e7a5da5e74171e90d8b90b2f00881a48d3a Author: test <test@36nu.com> ...

  7. DOM中的获得元素的方式

    获取元素的方式:分为俩种: 1.直接获取:直接获取分为三种: a,获取单个元素: function demo1(){ var uid=document.getElementById("use ...

  8. MVC效验器

    步骤一:导入依赖 <!--数据效验--> <dependency> <groupId>org.hibernate</groupId> <artif ...

  9. csuoj-1900 锋芒毕露

    Description 小闪最近迷上了二刀流——不过他耍的其实是剑——新买了一个宝库用来专门存放自己收集的双剑.一对剑有两把,分只能左手用的和只能右手用的,各自有一个攻击力数值.虽然一对剑在小闪刚拿到 ...

  10. 阿里最新38道Java面试题解析(MyBatis+消息队列+Redis)

    一.谈谈你对 MyBatis 的理解? 1. Mybatis是一个半ORM(对象关系映射)框架,它内部封装了 JDBC,开发时只需要关注 SQL 语句本身,不需要花费精力去处理加载驱动.创建连接.创建 ...