oicq
Version:
QQ protocol!
411 lines (396 loc) • 12.1 kB
JavaScript
/**
* 好友列表
* 群列表,群资料
* 群员列表,群员资料
* 相关api
*/
"use strict";
const common = require("../common");
const pb = require("../algo/pb");
const jce = require("../algo/jce");
const friend_sex_map = {
"0": "unknown", "1": "male", "2": "female"
};
const group_sex_map = {
"-1": "unknown", "0": "male", "1": "female"
};
const group_role_map = {
"1": "member", "2": "admin", "3": "owner"
};
const d50 = pb.encode({
1: 10002,
91001: 1,
101001: 1,
151001: 1,
181001: 1,
251001: 1,
});
/**
* @this {import("./ref").Client}
* @param {number} start
* @param {number} limit
* @param {Map<number, import("./ref").FriendInfo>} tmp
* @returns {number} 好友总数
*/
async function _initFL(start, limit, tmp) {
const FL = jce.encodeStruct([
3,
1, this.uin, start, limit, 0, 0, 0, 0, 0, 1,
31, null, 0, 0, 0, d50, null, [13580, 13581, 13582]
]);
const extra = {
req_id: this.seq_id + 1,
service: "mqq.IMService.FriendListServiceServantObj",
method: "GetFriendListReq",
};
const body = jce.encodeWrapper({ FL }, extra);
const blob = await this.sendUni("friendlist.getFriendGroupList", body, 10);
const nested = jce.decode(blob);
for (let v of nested[7]) {
let age = 0, area = "unknown";
if (tmp.has(v[0])) {
const f = tmp.get(v[0]);
age = f.age;
area = f.area;
}
this.fl.set(v[0], {
user_id: v[0],
nickname: v[14] || "",
sex: friend_sex_map[v[31]] || "unknown",
age, area,
remark: v[3] || "",
});
}
return nested[5];
}
/**
* 加载好友列表
* @this {import("./ref").Client}
* @returns {import("./ref").ProtocolResponse}
*/
async function initFL() {
this.sync_finished = false;
const tmp = this.fl;
this.fl = new Map;
let start = 0, limit = 150;
while (1) {
try {
const total = await _initFL.call(this, start, limit, tmp);
start += limit;
if (start > total) break;
} catch (e) {
this.logger.debug(e);
this.logger.warn("加载好友列表出现异常,未加载完成。");
return { result: -1 };
}
}
return { result: 0 };
}
/**
* 加载群列表
* @this {import("./ref").Client}
* @returns {import("./ref").ProtocolResponse}
*/
async function initGL() {
this.sync_finished = false;
const GetTroopListReqV2Simplify = jce.encodeStruct([
this.uin, 0, null, [], 1, 8, 0, 1, 1
]);
const extra = {
req_id: this.seq_id + 1,
service: "mqq.IMService.FriendListServiceServantObj",
method: "GetTroopListReqV2Simplify",
};
const body = jce.encodeWrapper({ GetTroopListReqV2Simplify }, extra);
try {
const blob = await this.sendUni("friendlist.GetTroopListReqV2", body, 10);
const nested = jce.decode(blob);
const tmp = this.gl;
if (this.gl.size < 999)
this.gl = new Map;
for (let v of nested[5]) {
var last_sent_time = 0, create_time = 0, grade = 0, max_admin_count = 0, active_member_count = 0;
if (tmp.has(v[1])) {
const g = tmp.get(v[1]);
var { last_sent_time, create_time, grade, max_admin_count, active_member_count } = g;
}
this.gl.set(v[1], {
group_id: v[1],
group_name: v[4] || "",
member_count: v[19],
max_member_count: v[29],
owner_id: v[23],
last_join_time: v[27],
last_sent_time,
shutup_time_whole: v[9] ? 0xffffffff : 0,
shutup_time_me: v[10] * 1000 > Date.now() ? v[10] : 0,
create_time, grade,
max_admin_count, active_member_count,
update_time: 0,
});
}
} catch (e) {
this.logger.debug(e);
this.logger.warn("加载群列表出现异常,未加载完成。");
return { result: -1 };
}
return { result: 0 };
}
/**
* @this {import("./ref").Client}
*/
async function __getGML(group_id, next_uin) {
const GTML = jce.encodeStruct([
this.uin, group_id, next_uin, common.code2uin(group_id), 2, 0, 0, 0
]);
const extra = {
req_id: this.seq_id + 1,
service: "mqq.IMService.FriendListServiceServantObj",
method: "GetTroopMemberListReq",
};
const body = jce.encodeWrapper({ GTML }, extra);
const blob = await this.sendUni("friendlist.GetTroopMemberListReq", body, 10);
const nested = jce.decode(blob);
const map = new Map(), next = nested[4];
for (let v of nested[3]) {
map.set(v[0], {
group_id: group_id,
user_id: v[0],
nickname: v[4] || "",
card: v[8] || "",
sex: group_sex_map[v[3]] || "unknown",
age: v[2] || 0,
area: "unknown",
join_time: v[15],
last_sent_time: v[16],
level: v[14],
rank: "",
role: v[18] % 2 === 1 ? "admin" : "member",
unfriendly: false,
title: v[23],
title_expire_time: v[24] & 0xffffffff,
card_changeable: true,
shutup_time: v[30] * 1000 > Date.now() ? v[30] : 0,
update_time: 0,
});
}
try {
const owner = this.gl.get(group_id).owner_id;
map.get(owner).role = "owner";
} catch (e) { }
return { map, next };
}
/**
* @this {import("./ref").Client}
*/
async function _getGML(group_id) {
let mlist = new Map();
try {
var next = 0;
while (1) {
var { map, next } = await __getGML.call(this, group_id, next);
mlist = new Map([...mlist, ...map]);
if (!next) break;
}
} catch (e) { }
if (!mlist.size) {
this.gml.delete(group_id);
return null;
} else {
this.gml.set(group_id, mlist);
return mlist;
}
}
/**
* 群员列表
* @this {import("./ref").Client}
* @returns {import("./ref").ProtocolResponse}
*/
async function getGML(group_id, no_cache = false) {
[group_id] = common.uinAutoCheck(group_id);
let mlist = this.gml.get(group_id);
if (!mlist || (no_cache && mlist instanceof Map)) {
mlist = _getGML.call(this, group_id);
this.gml.set(group_id, mlist);
}
if (mlist instanceof Promise)
mlist = await mlist;
if (mlist)
return { result: 0, data: mlist };
return { result: -1, emsg: "未加入的群" };
}
const gi_buf = pb.encode({
1: 0,
2: 0,
5: 0,
6: 0,
15: "",
29: 0,
36: 0,
37: 0,
45: 0,
46: 0,
49: 0,
54: 0,
89: "",
});
/**
* 群资料
* @this {import("./ref").Client}
* @returns {import("./ref").ProtocolResponse}
*/
async function getGI(group_id, no_cache = false) {
[group_id] = common.uinAutoCheck(group_id);
let ginfo = this.gl.get(group_id);
if (!no_cache && ginfo && common.timestamp() - ginfo.update_time <= this.config.internal_cache_life)
return { result: 0, data: ginfo };
const body = pb.encode({
1: this.apk.subid,
2: {
1: group_id,
2: gi_buf,
},
});
try {
this.gl.get(group_id).update_time = common.timestamp();
} catch (e) { }
const blob = await this.sendOidb("OidbSvc.0x88d_0", body);
const o = pb.decode(blob)[4][1][3];
if (!o) {
this.gl.delete(group_id);
this.gml.delete(group_id);
return { result: -1, emsg: "未加入的群" };
}
ginfo = {
group_id: group_id,
group_name: o[89] ? String(o[89]) : String(o[15]),
member_count: o[6],
max_member_count: o[5],
owner_id: o[1],
last_join_time: o[49],
last_sent_time: o[54],
shutup_time_whole: o[45] ? 0xffffffff : 0,
shutup_time_me: o[46] * 1000 > Date.now() ? o[46] : 0,
create_time: o[2],
grade: o[36],
max_admin_count: o[29],
active_member_count: o[37],
update_time: common.timestamp(),
};
this.gl.set(group_id, ginfo);
return { result: 0, data: ginfo };
}
/**
* 群员资料
* @this {import("./ref").Client}
* @returns {import("./ref").ProtocolResponse}
*/
async function getGMI(group_id, user_id, no_cache = false) {
[group_id, user_id] = common.uinAutoCheck(group_id, user_id);
if (!this.gml.has(group_id))
this.getGroupMemberList(group_id);
let minfo;
try {
minfo = this.gml.get(group_id).get(user_id);
} catch (e) { }
if (!no_cache && minfo && common.timestamp() - minfo.update_time <= this.config.internal_cache_life)
return { result: 0, data: minfo };
const body = pb.encode({
1: group_id,
2: user_id,
3: 1,
4: 1,
5: 1,
});
try {
this.gml.get(group_id).get(user_id).update_time = common.timestamp();
} catch (e) { }
const blob = await this.sendUni("group_member_card.get_group_member_card_info", body);
const o = pb.decode(blob)[3];
if (!o[27]) {
try {
this.gml.get(group_id).delete(user_id);
} catch (e) { }
return { result: -1, emsg: "幽灵群员" };
}
if (o[9] === undefined) o[9] = -1;
const card = o[8] ? common.parseFunString(o[8].toBuffer()) : "";
minfo = {
group_id: group_id,
user_id: user_id,
nickname: Reflect.has(o, "11") ? String(o[11]) : "",
card: card,
sex: Reflect.has(o, "9") ? group_sex_map[o[9] & 0xffffffff] : "unknown",
age: o[12] || 0,
area: Reflect.has(o, "10") ? String(o[10]) : "unknown",
join_time: o[14],
last_sent_time: o[15],
level: o[39],
rank: Reflect.has(o, "13") ? String(o[13]) : undefined,
role: group_role_map[o[27]],
unfriendly: false,
title: Reflect.has(o, "31") ? String(o[31]) : "",
title_expire_time: Reflect.has(o, "32") ? o[32] : 0xffffffff,
card_changeable: true,
shutup_time: 0,
update_time: common.timestamp(),
};
try {
try {
minfo.shutup_time = this.gml.get(group_id).get(user_id).shutup_time;
} catch (e) { }
this.gml.get(group_id).set(user_id, minfo);
} catch (e) { }
return { result: 0, data: minfo };
}
/**
* 陌生人资料(也用于更新好友信息中的一些字段)
* @this {import("./ref").Client}
* @returns {import("./ref").ProtocolResponse}
*/
async function getSI(user_id, no_cache = false) {
[user_id] = common.uinAutoCheck(user_id);
let user = this.sl.get(user_id);
if (!no_cache && user)
return { result: 0, data: user };
const arr = [
null,
0, "", [user_id], 1, 1, 0, 0, 0, 1, 0, 1
];
arr[101] = 1;
const req = jce.encodeStruct(arr);
const extra = {
req_id: this.seq_id + 1,
service: "KQQ.ProfileService.ProfileServantObj",
method: "GetSimpleInfo",
};
const body = jce.encodeWrapper({ req }, extra);
const blob = await this.sendUni("ProfileService.GetSimpleInfo", body);
const nested = jce.decode(blob);
for (let v of nested) {
const area = (v[13] + " " + v[14] + " " + v[15]).trim();
const user = {
user_id: v[1],
nickname: v[5] || "",
sex: group_sex_map[v[3]] || "unknown",
age: v[4] || 0,
area: area ? area : "unknown",
};
let o = this.fl.get(v[1]);
if (!o)
o = this.sl.get(v[1]);
if (o) {
o.area = user.area;
if (user.sex !== "unknown")
o.sex = user.sex;
if (user.age)
o.age = user.age;
}
return { result: 0, data: user };
}
return { result: -1, emsg: "没有这个人" };
}
module.exports = {
initFL, initGL,
getGML, getGI, getGMI, getSI,
};