注入QQ小程序数据助手并拉取DAU和收益

Published: Tags: JAVASCRIPT

笔者目前所在的公司最近几年都是在做小游戏,并且接入了很多不同的渠道,主要是靠广告盈利,毕竟没有申请到版号就不能开通支付。 比如:QQ小游戏、微信、字节跳动、360浏览器、360游戏大厅、OPPO、VIVO、小米等等,因此这么多个渠道的DAU和收入统计是个问题。

只有在自己后台才可以在单个页面中统计得到各个渠道某天的DAU或收益(包括总汇和单个游戏),以及用户的画像、留存等各种指标。 所以我们从最初的微信小游戏上线之后,就已经开始做这个管理后台了,当时还没有数据助手,所以只能一个游戏扫码登陆一次PC后台。

另外刚开始的时候由于我们后台不完善,所以只能登陆微信PC后台,然后按F12使用开发者工具抓包每个接口的JSON并手动提交后台解析。 但我们现在可是有40多个小游戏呢,如果还用这方法处理,那一天下来什么都不用做纯搞数据抄录了,而事情的转接出现在数据助手之后。

笔者发现手机微信的小游戏数据助手可以一次拉取到所有授权了当前帐号的游戏列表,因此笔者开始对微信进行抓包操作和各种接口分析。 每次点开数据助手时都会有一个登陆操作,并返回一个session_id令牌,这样笔者就可以拿着这个登陆态提交到自己后台去批量拉取数据。 我们甚至把这个登陆态记录下来,编写定时任务每天自动拉取数据并录入,根据观察该登录态的有效期约五天(重登会导致之前的失效)。

笔者顺便把其他渠道的登陆态过期时间也大概说一下(大部分渠道都可以在PC上用开发者工具,得到cookies作为拉取数据所用到的令牌) (数据截止当前文章时间)字节跳动:两个月以上;360浏览器:约两周;VIVO/OPPO:约两天;小米:约一个月;QQ小游戏:当天过期

虽然QQ小游戏当天过期且每个游戏都像微信刚开始那样,需要在PC上逐一扫码登陆,但可以每小时定期请求接口进行续期,延长至一个月。 而这样的便捷方式大概在两周前被打破,QQ小游戏最长不超过两天即会过期,也就是说我们必须每隔一天就要逐一抄录几十个游戏的数据。

其实在大半年前,笔者就发现QQ小游戏上线了数据助手,和微信一样也是可以在手机上看到所有被授权当前帐号的游戏列表和各种数据的。 但奇怪的是,笔者用了各种方法都无法对QQ小游戏的数据助手抓包(不知道为啥腾讯就是不在PC上列出同个授权帐号的游戏信息,烦躁) 所以这个奇葩的QQ小游戏的数据助手让笔者抓狂了很久,直到笔者找到QQ小游戏的代码存放路径,竟然是明文(微信是.wxapkg代码包) 明文好啊,那就代表可以查看原因并修改代码(虽然微信的.wxapkg也能解包,但笔者修改后无法重建包体),然后增加原来没有的功能。

以下路径找到QQ小游戏的数据助手的明文代码包(最后的字符串和版本有关,至于QQ轻聊版请修改应用名) /data/data/com.tencent.mobileqq/files/mini/40EBD63E3B635B4F8B0E126E6204A2EA_{这里是32位字符}/

把以上目录的app-service.js文件格式化后,可以发现关键代码片段,使用的是qq.wnsRequest而并非是公开的qq.requestAPI接口。 可能各位会对笔者提交数据到api-report.q.qq.com这个腾讯域名有些疑惑,但其实原因很简单也很无奈:不能提交第三方自定义域名。 但笔者至少有两个方法得到数据,比如用Fiddler对手机抓包,然后在电脑上修改系统(或Fiddler自带)的Hosts文件绑定自己的IP地址。

request: function(t) {
    var a = t.url,
    r = t.data,
    e = t.method,
    o = void 0 === e ? "GET": e,
    i = Object.assign({
        "x-client-proto": "https"
    },
    t.header);
    return "POST" === o && (i.contentType = "application/json"),
    new Promise(function(e, n) {
        qq.wnsRequest({
            url: a,
            method: o,
            header: i,
            data: r,
            sdkVer: "1.6.9.12089",
            success: function(t) {
                /* 开始注入 *
                qq.request({
                    url: "https://api-report.q.qq.com/?url=" + a,
                    method: "POST",
                    data: {
                        url: a,
                        mothod: o,
                        header: i,
                        dataReq: r,
                        dataResp: t,
                    }
                });
                * 注入结束 */
                t.data && 0 === t.data.code ? e(t.data) : n(t)
            },
            fail: function(t) {
                n(t)
            },
            complete: function() {}
        })
    })
},

根据笔者的多次测试,可以确认这几点:qq.wnsRequest接口无法抓包,且服务端记录不到请求的数据,且其他应用无权调用该API接口。 除了修改PC上的Hosts文件并设置Fiddler忽略证书错误之外,其实我们还可以直接在手机上POST数据到后台,这样的话连电脑都不需要了。 修改手机Hosts文件绑定腾讯域名到自己的IP上,然后把自签名CA伪证书放到手机的系统凭据,用于信任自己的服务器,以下是JS注入代码:

exports.GetDataAppList = function() {
    return o({
        url: d + "/GetDataAppList",
        method: "POST"
    }).then(function(e) {
        /* 开始注入 */
        qq.showModal({
            title: "点击『确定』进行抄录",
            content: "\n点击「取消」进入首页",
            success(res) {
                if (res.confirm) {
                    (async function(AppList) {
                        Date.prototype.Ymd = function(day = "0", sep = "") {
                            Z = function(s) {return s<10 ? "0"+s : s;} // HEAD ZERO FILL
                            let D = new Date(1000 * (parseInt(new Date / 1000) + day * 86400));
                            return [D.getFullYear(), Z(D.getMonth()+1), Z(D.getDate())].join(sep);
                        }
                        let ftimeBegin = new Date().Ymd(-8); let ftimeEnd = new Date().Ymd(-1);

                        for (let m in AppList.reverse()) {
                            let param = AppList[m];
                            param.ftimeBegin=ftimeBegin; param.ftimeEnd=ftimeEnd;
                            qq.showLoading({title: m+':'+param.appName, mask: true});

                            let player = exports.GetOperationData(param);
                            let adtotal = exports.GetAdDataDaily(param);
                            [player, adtotal] = await Promise.all([player, adtotal]);

                            qq.request({
                                url: "https://api-report.q.qq.com/penman/save_qqgame_data", method: "POST",
                                data: {gameid: param.appid, player: player, adtotal: adtotal}
                            });
                        } // HIDE WHEN DONE
                        qq.hideLoading();
                    })(e.data.dataApplist);
                }
            }
        });
        /* 注入结束 */
        return console.log(e), e
    })
},

这样每次打开QQ小游戏的数据助手时,就会弹窗询问是否POST数据到我们自己的服务器后台进行抄录了,还能根据游戏名来显示处理进度。 最后,希望腾讯大佬别再乱改了,不然手动拉取数据真会死人的。明明都授权了还搞这么多校验,而PC后台只能看单个游戏,又烂又智障。

以上内容参考互联网及以下资料: 腾讯QQ小程序 - 开发者API文档 深入理解 async / await - 掘金