Bot.adapter.push(new class OPQBotAdapter { constructor() { this.id = "QQ" this.name = "OPQBot" this.path = this.name this.echo = {} this.timeout = 60000 this.CommandId = { FriendImage: 1, GroupImage: 2, FriendVoice: 26, GroupVoice: 29, } } sendApi(id, CgiCmd, CgiRequest) { const ReqId = Math.round(Math.random()*10**16) const request = { BotUin: String(id), CgiCmd, CgiRequest, ReqId } Bot[id].ws.sendMsg(request) const error = Error() return new Promise((resolve, reject) => this.echo[ReqId] = { request, resolve, reject, error, timeout: setTimeout(() => { reject(Object.assign(error, request, { timeout: this.timeout })) delete this.echo[ReqId] Bot.makeLog("error", ["请求超时", request], id) Bot[id].ws.terminate() }, this.timeout), } ) } makeLog(msg) { return Bot.String(msg).replace(/base64:\/\/.*?"/g, 'base64://..."') } async uploadFile(id, type, file) { const opts = { CommandId: this.CommandId[type] } file = await Bot.Buffer(file, { http: true }) if (Buffer.isBuffer(file)) opts.Base64Buf = file.toString("base64") else if (file.match(/^https?:\/\//)) opts.FileUrl = file else opts.FilePath = file return (await this.sendApi(id, "PicUp.DataUp", opts)).ResponseData } async sendMsg(send, upload, msg) { if (!Array.isArray(msg)) msg = [msg] const message = { Content: "", Images: [], AtUinLists: [], } for (let i of msg) { if (typeof i !== "object") i = { type: "text", text: i } switch (i.type) { case "text": message.Content += i.text break case "image": message.Images.push(await upload("Image", i.file)) break case "record": message.Voice = await upload("Voice", i.file) break case "at": message.AtUinLists.push({ Uin: i.qq }) break case "video": case "file": case "face": case "reply": case "button": continue case "node": await Bot.sendForwardMsg(msg => this.sendMsg(send, upload, msg), i.data) continue case "raw": for (const i in i.data) message[i] = i.data[i] continue default: message.Content += Bot.String(i) } } return send(message) } sendFriendMsg(data, msg, event) { Bot.makeLog("info", `发送好友消息:${this.makeLog(msg)}`, `${data.self_id} => ${data.user_id}`) return this.sendMsg( msg => this.sendApi(data.self_id, "MessageSvc.PbSendMsg", { ToUin: data.user_id, ToType: 1, ...msg, }), (type, file) => this.uploadFile(data.self_id, `Friend${type}`, file), msg ) } sendMemberMsg(data, msg, event) { Bot.makeLog("info", `发送群员消息:${this.makeLog(msg)}`, `${data.self_id} => ${data.group_id}, ${data.user_id}`) return this.sendMsg( msg => this.sendApi(data.self_id, "MessageSvc.PbSendMsg", { ToUin: data.user_id, GroupCode: data.group_id, ToType: 3, ...msg, }), (type, file) => this.uploadFile(data.self_id, `Friend${type}`, file), msg ) } sendGroupMsg(data, msg) { Bot.makeLog("info", `发送群消息:${this.makeLog(msg)}`, `${data.self_id} => ${data.group_id}`) let ReplyTo if (data.message_id && data.seq && data.time) ReplyTo = { MsgSeq: data.seq, MsgTime: data.time, MsgUid: data.message_id, } return this.sendMsg( msg => this.sendApi(data.self_id, "MessageSvc.PbSendMsg", { ToUin: data.group_id, ToType: 2, ReplyTo, ...msg, }), (type, file) => this.uploadFile(data.self_id, `Group${type}`, file), msg ) } pickFriend(id, user_id) { const i = { ...Bot[id].fl.get(user_id), self_id: id, bot: Bot[id], user_id: user_id, } return { ...i, sendMsg: msg => this.sendFriendMsg(i, msg), getAvatarUrl: () => `https://q.qlogo.cn/g?b=qq&s=0&nk=${user_id}`, } } pickMember(id, group_id, user_id) { const i = { ...Bot[id].fl.get(user_id), self_id: id, bot: Bot[id], user_id: user_id, group_id: group_id, } return { ...this.pickFriend(id, user_id), ...i, sendMsg: msg => this.sendMemberMsg(i, msg), } } pickGroup(id, group_id) { const i = { ...Bot[id].gl.get(group_id), self_id: id, bot: Bot[id], group_id: group_id, } return { ...i, sendMsg: msg => this.sendGroupMsg(i, msg), pickMember: user_id => this.pickMember(id, group_id, user_id), getAvatarUrl: () => `https://p.qlogo.cn/gh/${group_id}/${group_id}/0`, } } makeMessage(id, event) { const data = { event, bot: Bot[id], self_id: id, post_type: "message", message_id: event.MsgHead.MsgUid, seq: event.MsgHead.MsgSeq, time: event.MsgHead.MsgTime, user_id: event.MsgHead.SenderUin, sender: { user_id: event.MsgHead.SenderUin, nickname: event.MsgHead.SenderNick, }, message: [], raw_message: "", } if (event.MsgBody.AtUinLists) for (const i of event.MsgBody.AtUinLists) { data.message.push({ type: "at", qq: i.Uin, data: i, }) data.raw_message += `[提及:${i.Uin}]` } if (event.MsgBody.Content) { data.message.push({ type: "text", text: event.MsgBody.Content, }) data.raw_message += event.MsgBody.Content } if (event.MsgBody.Images) for (const i of event.MsgBody.Images) { data.message.push({ type: "image", url: i.Url, data: i, }) data.raw_message += `[图片:${i.Url}]` } return data } makeFriendMessage(id, data) { if (!data.MsgBody) return data = this.makeMessage(id, data) data.message_type = "private" Bot.makeLog("info", `好友消息:[${data.sender.nickname}] ${data.raw_message}`, `${data.self_id} <= ${data.user_id}`) Bot.em(`${data.post_type}.${data.message_type}`, data) } makeGroupMessage(id, data) { if (!data.MsgBody) return data = this.makeMessage(id, data) data.message_type = "group" data.sender.card = data.event.MsgHead.GroupInfo.GroupCard data.group_id = data.event.MsgHead.GroupInfo.GroupCode data.group_name = data.event.MsgHead.GroupInfo.GroupName data.reply = msg => this.sendGroupMsg(data, msg) Bot.makeLog("info", `群消息:[${data.group_name}, ${data.sender.nickname}] ${data.raw_message}`, `${data.self_id} <= ${data.group_id}, ${data.user_id}`) Bot.em(`${data.post_type}.${data.message_type}`, data) } makeEvent(id, data) { switch (data.EventName) { case "ON_EVENT_FRIEND_NEW_MSG": this.makeFriendMessage(id, data.EventData) break case "ON_EVENT_GROUP_NEW_MSG": this.makeGroupMessage(id, data.EventData) break default: Bot.makeLog("warn", `未知事件:${logger.magenta(data.raw)}`, id) } } makeBot(id, ws) { Bot[id] = { adapter: this, ws, uin: id, info: { id }, get nickname() { return this.info.nickname }, get avatar() { return `https://q.qlogo.cn/g?b=qq&s=0&nk=${this.uin}` }, version: { id: this.id, name: this.name, version: this.version, }, stat: { start_time: Date.now()/1000 }, pickFriend: user_id => this.pickFriend(id, user_id), get pickUser() { return this.pickFriend }, getFriendMap() { return this.fl }, fl: new Map, pickMember: (group_id, user_id) => this.pickMember(id, group_id, user_id), pickGroup: group_id => this.pickGroup(id, group_id), getGroupMap() { return this.gl }, gl: new Map, gml: new Map, } Bot.makeLog("mark", `${this.name}(${this.id}) ${this.version} 已连接`, id) Bot.em(`connect.${id}`, { self_id: id }) } message(data, ws) { try { data = { ...JSON.parse(data), raw: Bot.String(data), } } catch (err) { return Bot.makeLog("error", ["解码数据失败", data, err]) } const id = data.CurrentQQ if (id && data.CurrentPacket) { if (Bot[id]) Bot[id].ws = ws else this.makeBot(id, ws) this.makeEvent(id, data.CurrentPacket) } else if (data.ReqId && this.echo[data.ReqId]) { if (data.CgiBaseResponse?.Ret !== 0) this.echo[data.ReqId].reject(Object.assign( this.echo[data.ReqId].error, this.echo[data.ReqId].request, { error: data }, )) else this.echo[data.ReqId].resolve(data) clearTimeout(this.echo[data.ReqId].timeout) delete this.echo[data.ReqId] } else { Bot.makeLog("warn", `未知消息:${logger.magenta(data.raw)}`, id) } } load() { if (!Array.isArray(Bot.wsf[this.path])) Bot.wsf[this.path] = [] Bot.wsf[this.path].push((ws, ...args) => ws.on("message", data => this.message(data, ws, ...args)) ) } })