oicq
Version:
QQ protocol!
361 lines (360 loc) • 13.5 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ForwardMessage = exports.DiscussMessage = exports.GroupMessage = exports.PrivateMessage = exports.Message = exports.parseGroupMessageId = exports.genGroupMessageId = exports.parseDmMessageId = exports.genDmMessageId = exports.uuid2rand = exports.rand2uuid = void 0;
const qs = __importStar(require("querystring"));
const core_1 = require("../core");
const common_1 = require("../common");
const parser_1 = require("./parser");
function rand2uuid(rand) {
return 16777216n << 32n | BigInt(rand);
}
exports.rand2uuid = rand2uuid;
function uuid2rand(uuid) {
return Number(BigInt(uuid) & 0xffffffffn);
}
exports.uuid2rand = uuid2rand;
/** @cqhttp 生成私聊消息id */
function genDmMessageId(uid, seq, rand, time, flag = 0) {
const buf = Buffer.allocUnsafe(17);
buf.writeUInt32BE(uid);
buf.writeInt32BE(seq & 0xffffffff, 4);
buf.writeInt32BE(rand & 0xffffffff, 8);
buf.writeUInt32BE(time, 12);
buf.writeUInt8(flag, 16); //接收为0 发送为1
return buf.toString("base64");
}
exports.genDmMessageId = genDmMessageId;
/** @cqhttp 解析私聊消息id */
function parseDmMessageId(msgid) {
const buf = Buffer.from(msgid, "base64");
const user_id = buf.readUInt32BE(), seq = buf.readUInt32BE(4), rand = buf.readUInt32BE(8), time = buf.readUInt32BE(12), flag = buf.length >= 17 ? buf.readUInt8(16) : 0;
return { user_id, seq, rand, time, flag };
}
exports.parseDmMessageId = parseDmMessageId;
/** @cqhttp 生成群消息id */
function genGroupMessageId(gid, uid, seq, rand, time, pktnum = 1) {
const buf = Buffer.allocUnsafe(21);
buf.writeUInt32BE(gid);
buf.writeUInt32BE(uid, 4);
buf.writeInt32BE(seq & 0xffffffff, 8);
buf.writeInt32BE(rand & 0xffffffff, 12);
buf.writeUInt32BE(time, 16);
buf.writeUInt8(pktnum > 1 ? pktnum : 1, 20);
return buf.toString("base64");
}
exports.genGroupMessageId = genGroupMessageId;
/** @cqhttp 解析群消息id */
function parseGroupMessageId(msgid) {
const buf = Buffer.from(msgid, "base64");
const group_id = buf.readUInt32BE(), user_id = buf.readUInt32BE(4), seq = buf.readUInt32BE(8), rand = buf.readUInt32BE(12), time = buf.readUInt32BE(16), pktnum = buf.length >= 21 ? buf.readUInt8(20) : 1;
return { group_id, user_id, seq, rand, time, pktnum };
}
exports.parseGroupMessageId = parseGroupMessageId;
/** 一条消息 */
class Message {
constructor(proto) {
this.proto = proto;
this.post_type = "message";
/** @cqhttp cqhttp方法用 */
this.message_id = "";
this.proto = proto;
const head = proto[1], frag = proto[2], body = proto[3];
this.pktnum = frag[1];
this.index = frag[2];
this.div = frag[3];
this.user_id = head[1];
this.time = head[6];
this.seq = head[5];
this.rand = proto[3]?.[1]?.[1]?.[3] || uuid2rand(head[7]);
this.font = body[1]?.[1]?.[9]?.toString() || "unknown";
this.parsed = (0, parser_1.parse)(body[1], head[2]);
this.message = this.parsed.message;
this.raw_message = this.parsed.brief;
if (this.parsed.quotation) {
const q = this.parsed.quotation;
this.source = {
user_id: q[2],
time: q[3],
seq: q[1]?.[0] || q[1],
rand: uuid2rand(q[8]?.[3] || 0),
message: (0, parser_1.parse)(Array.isArray(q[5]) ? q[5] : [q[5]]).brief,
};
}
(0, common_1.lock)(this, "proto");
(0, common_1.lock)(this, "parsed");
(0, common_1.lock)(this, "pktnum");
(0, common_1.lock)(this, "index");
(0, common_1.lock)(this, "div");
}
/** 仅供内部转发消息时使用 */
get nickname() {
return this.sender?.card || this.sender?.nickname || "";
}
/** 反序列化一条消息 (私聊消息需要你的uin) */
static deserialize(serialized, uin) {
const proto = core_1.pb.decode(serialized);
switch (proto[1][3]) {
case 82:
return new GroupMessage(proto);
case 83:
return new DiscussMessage(proto);
default:
return new PrivateMessage(proto, uin);
}
}
/** 组合分片消息(通常仅内部使用) */
static combine(msgs) {
msgs.sort((a, b) => a.index - b.index);
const host = msgs[0];
let chain = host.message;
for (const guest of msgs.slice(1)) {
if (guest.atme)
host.atme = true;
if (guest.atall)
host.atall = true;
host.raw_message += guest.raw_message;
for (const elem of guest.message) {
const prev = chain[chain.length - 1];
if (elem.type === "text" && prev?.type === "text")
prev.text += elem.text;
else
chain.push(elem);
}
}
return host;
}
/** 将消息序列化保存 */
serialize() {
return this.proto.toBuffer();
}
/** 以适合人类阅读的形式输出 */
toString() {
return this.parsed.content;
}
/** @deprecated 转换为CQ码 */
toCqcode() {
return genCqcode(this.message);
}
}
exports.Message = Message;
/** 一条私聊消息 */
class PrivateMessage extends Message {
constructor(proto, uin) {
super(proto);
this.message_type = "private";
/** friend:好友 group:群临时会话 self:我的设备 other:其他途径的临时会话 */
this.sub_type = "friend";
this.sender = {
user_id: 0,
nickname: "",
group_id: undefined,
discuss_id: undefined,
};
const head = proto[1], content = proto[2], body = proto[3];
this.from_id = this.sender.user_id = head[1];
this.to_id = head[2];
this.auto_reply = !!(content && content[4]);
switch (head[3]) {
case 529:
if (head[4] === 4) {
const trans = body[2][1];
if (trans[1] !== 0)
throw new Error("unsupported message (ignore ok)");
const elem = {
type: "file",
name: String(trans[5]),
size: trans[6],
md5: trans[4]?.toHex() || "",
duration: trans[51] || 0,
fid: String(trans[3]),
};
this.message = [elem];
this.raw_message = "[离线文件]";
this.parsed.content = `{file:${elem.fid}}`;
}
else {
this.sub_type = this.from_id === this.to_id ? "self" : "other";
this.message = this.raw_message = this.parsed.content = body[2]?.[6]?.[5]?.[1]?.[2]?.toString() || "";
}
break;
case 141:
this.sub_type = "group";
this.sender.nickname = this.parsed.extra?.[1]?.toString() || "";
if (head[8]?.[3])
this.sender.group_id = head[8]?.[4];
else
this.sender.discuss_id = head[8]?.[4];
break;
}
let opposite = this.from_id, flag = 0;
if (this.from_id === uin)
opposite = this.to_id, flag = 1;
this.message_id = genDmMessageId(opposite, this.seq, this.rand, this.time, flag);
}
/** 反序列化一条私聊消息,你需要传入你的`uin`,否则无法知道你是发送者还是接收者 */
static deserialize(serialized, uin) {
return new PrivateMessage(core_1.pb.decode(serialized), uin);
}
}
exports.PrivateMessage = PrivateMessage;
/** 一条群消息 */
class GroupMessage extends Message {
constructor(proto) {
super(proto);
this.message_type = "group";
this.sender = {
user_id: 0,
nickname: "",
card: "",
/** @deprecated */
sex: "unknown",
/** @deprecated */
age: 0,
/** @deprecated */
area: "",
level: 0,
role: "member",
title: ""
};
const group = proto[1][9];
this.group_id = group[1] || 0;
this.group_name = group[8]?.toString() || "";
this.block = group[2] === 127;
this.sender.user_id = proto[1][1];
if (this.parsed.anon) {
this.sub_type = "anonymous";
this.anonymous = {
id: this.parsed.anon[6],
id2: this.parsed.anon[4],
name: String(this.parsed.anon[3]),
color: String(this.parsed.anon[7]),
expire_time: this.parsed.anon[5],
flag: String(this.parsed.anon[3]) + "@" + this.parsed.anon[2].toBase64(),
enable: true,
};
this.sender.card = this.sender.nickname = "匿名消息";
}
else {
this.sub_type = "normal";
this.anonymous = null;
const ext = this.parsed.extra;
if (!ext?.[2])
this.sender.nickname = ext?.[1]?.toString() || "";
else
this.sender.nickname = this.sender.card = (0, common_1.parseFunString)(group[4].toBuffer());
if (ext?.[4])
this.sender.role = ext[4] === 8 ? "owner" : "admin";
this.sender.level = ext?.[3] || 0;
this.sender.title = ext?.[7]?.toString() || "";
}
this.atme = this.parsed.atme;
this.atall = this.parsed.atall;
this.message_id = genGroupMessageId(this.group_id, this.user_id, this.seq, this.rand, this.time, this.pktnum);
}
/** 反序列化一条群消息 */
static deserialize(serialized) {
return new GroupMessage(core_1.pb.decode(serialized));
}
}
exports.GroupMessage = GroupMessage;
/** 一条讨论组消息 */
class DiscussMessage extends Message {
constructor(proto) {
super(proto);
this.message_type = "discuss";
const discuss = proto[1][13];
this.discuss_id = discuss[1] || 0;
this.discuss_name = discuss[5]?.toString() || "";
this.atme = this.parsed.atme;
const card = discuss[4]?.toString() || "";
this.sender = {
user_id: proto[1][1],
nickname: card,
card: card,
};
this.rand = proto[3][1][1][3];
}
/** 反序列化一条讨论组消息 */
static deserialize(serialized) {
return new DiscussMessage(core_1.pb.decode(serialized));
}
}
exports.DiscussMessage = DiscussMessage;
/** 一条转发消息 */
class ForwardMessage {
constructor(proto) {
this.proto = proto;
this.proto = proto;
const head = proto[1];
this.time = head[6] || 0;
this.user_id = head[1] || 0;
this.nickname = head[14]?.toString() || head[9]?.[4]?.toString() || "";
this.group_id = head[9]?.[1];
this.parsed = (0, parser_1.parse)(proto[3][1]);
this.message = this.parsed.message;
this.raw_message = this.parsed.brief;
(0, common_1.lock)(this, "proto");
(0, common_1.lock)(this, "parsed");
}
/** 反序列化一条转发消息 */
static deserialize(serialized) {
return new ForwardMessage(core_1.pb.decode(serialized));
}
/** 将转发消息序列化保存 */
serialize() {
return this.proto.toBuffer();
}
/** 以适合人类阅读的形式输出 */
toString() {
return this.parsed.content;
}
/** @deprecated 转换为CQ码 */
toCqcode() {
return genCqcode(this.message);
}
}
exports.ForwardMessage = ForwardMessage;
function escapeCQInside(s) {
if (s === "&")
return "&";
if (s === ",")
return ",";
if (s === "[")
return "[";
if (s === "]")
return "]";
return "";
}
function genCqcode(content) {
let cqcode = "";
for (let elem of content) {
if (elem.type === "text") {
cqcode += elem.text;
continue;
}
const tmp = { ...elem };
delete tmp.type;
const str = qs.stringify(tmp, ",", "=", { encodeURIComponent: (s) => s.replace(/&|,|\[|\]/g, escapeCQInside) });
cqcode += "[CQ:" + elem.type + (str ? "," : "") + str + "]";
}
return cqcode;
}