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