ayz / Yunzai /plugins /adapter /ComWeChat.js
knbn's picture
Upload 75 files
80228cc verified
import path from "node:path"
import { ulid } from "ulid"
Bot.adapter.push(new class ComWeChatAdapter {
constructor() {
this.id = "WeChat"
this.name = "ComWeChat"
this.path = this.name
this.echo = {}
this.timeout = 60000
}
makeLog(msg) {
return Bot.String(msg).replace(/(base64:\/\/|"type":"data","data":").*?"/g, '$1..."')
}
sendApi(data, ws, action, params = {}) {
const echo = ulid()
const request = { action, params, echo }
ws.sendMsg(request)
return new Promise((resolve, reject) =>
this.echo[echo] = {
request, resolve, reject,
timeout: setTimeout(() => {
reject(Object.assign(request, { timeout: this.timeout }))
delete this.echo[echo]
Bot.makeLog("error", ["请求超时", request], data.self_id)
ws.terminate()
}, this.timeout),
}
)
}
async uploadFile(data, file) {
file = await Bot.fileType(file, { http: true })
const opts = { name: file.name }
if (Buffer.isBuffer(file.buffer)) {
opts.type = "data"
opts.data = file.buffer.toString("base64")
} else if (file.buffer.match(/^https?:\/\//)) {
opts.type = "url"
opts.url = file.buffer
} else {
opts.type = "path"
opts.path = file.buffer
}
Bot.makeLog("info", `上传文件:${this.makeLog(opts)}`, data.self_id)
return data.bot.sendApi("upload_file", opts)
}
async makeMsg(data, msg, send) {
if (!Array.isArray(msg))
msg = [msg]
const msgs = []
for (let i of msg) {
if (typeof i !== "object")
i = { type: "text", data: { text: i }}
else if (!i.data)
i = { type: i.type, data: { ...i, type: undefined }}
if (i.data.file)
i.data = { file_id: (await this.uploadFile(data, i.data)).file_id }
switch (i.type) {
case "text":
case "image":
case "file":
case "wx.emoji":
case "wx.link":
break
case "record":
case "video":
i.type = "file"
break
case "at":
if (i.data.qq === "all")
i = { type: "mention_all", data: {}}
else
i = { type: "mention", data: { user_id: i.data.qq }}
break
case "reply":
case "button":
continue
case "node":
await Bot.sendForwardMsg(send, i.data)
continue
case "raw":
i = i.data
break
default:
i = { type: "text", data: { text: Bot.String(i) }}
}
msgs.push(i)
}
return msgs
}
async sendFriendMsg(data, msg) {
const message = await this.makeMsg(data, msg, msg => this.sendFriendMsg(data, msg))
Bot.makeLog("info", `发送好友消息:${this.makeLog(message)}`, `${data.self_id} => ${data.user_id}`)
return data.bot.sendApi("send_message", {
detail_type: "private",
user_id: data.user_id,
message,
})
}
async sendGroupMsg(data, msg) {
const message = await this.makeMsg(data, msg, msg => this.sendGroupMsg(data, msg))
Bot.makeLog("info", `发送群消息:${this.makeLog(message)}`, `${data.self_id} => ${data.group_id}`)
return data.bot.sendApi("send_message", {
detail_type: "group",
group_id: data.group_id,
message,
})
}
async getFriendArray(data) {
const array = []
for (const i of (await data.bot.sendApi("get_friend_list")).data)
array.push({
...i,
nickname: i.user_remark === "null" ? i.user_displayname || i.user_name : i.user_remark,
})
return array
}
async getFriendList(data) {
const array = []
for (const { user_id } of (await this.getFriendArray(data)))
array.push(user_id)
return array
}
async getFriendMap(data) {
for (const i of (await this.getFriendArray(data)))
data.bot.fl.set(i.user_id, i)
return data.bot.fl
}
getFriendInfo(data) {
return data.bot.sendApi("get_user_info", {
user_id: data.user_id,
})
}
async getGroupArray(data) {
return (await data.bot.sendApi("get_group_list")).data
}
async getGroupList(data) {
const array = []
for (const { group_id } of (await this.getGroupArray(data)))
array.push(group_id)
return array
}
async getGroupMap(data) {
for (const i of (await this.getGroupArray(data)))
data.bot.gl.set(i.group_id, i)
return data.bot.gl
}
getGroupInfo(data) {
return data.bot.sendApi("get_group_info", {
group_id: data.group_id,
})
}
async getMemberArray(data) {
return (await data.bot.sendApi("get_group_member_list", {
group_id: data.group_id,
})).data
}
async getMemberList(data) {
const array = []
for (const { user_id } of (await this.getMemberArray(data)))
array.push(user_id)
return array
}
async getMemberMap(data) {
const map = new Map
for (const i of (await this.getMemberArray(data)))
map.set(i.user_id, i)
return map
}
getMemberInfo(data) {
return data.bot.sendApi("get_group_member_info", {
group_id: data.group_id,
user_id: data.user_id,
})
}
pickFriend(data, user_id) {
const i = {
...data.bot.fl.get(user_id),
...data,
user_id,
}
return {
...i,
sendMsg: msg => this.sendFriendMsg(i, msg),
sendFile: (file, name) => this.sendFriendMsg(i, segment.file(file, name)),
getInfo: () => this.getFriendInfo(i),
getAvatarUrl: async () => (await this.getFriendInfo(i))["wx.avatar"],
}
}
pickMember(data, group_id, user_id) {
const i = {
...data.bot.fl.get(user_id),
...data,
group_id,
user_id,
}
return {
...this.pickFriend(i, user_id),
...i,
getInfo: () => this.getMemberInfo(i),
getAvatarUrl: async () => (await this.getMemberInfo(i))["wx.avatar"],
}
}
pickGroup(data, group_id) {
const i = {
...data.bot.gl.get(group_id),
...data,
group_id,
}
return {
...i,
sendMsg: msg => this.sendGroupMsg(i, msg),
sendFile: (file, name) => this.sendGroupMsg(i, segment.file(file, name)),
getInfo: () => this.getGroupInfo(i),
getAvatarUrl: async () => (await this.getGroupInfo(i))["wx.avatar"],
getMemberArray: () => this.getMemberArray(i),
getMemberList: () => this.getMemberList(i),
getMemberMap: () => this.getMemberMap(i),
pickMember: user_id => this.pickMember(i, i.group_id, user_id),
}
}
async connect(data, ws) {
for (const bot of data.status.bots)
data.self_id = bot.self.user_id
Bot[data.self_id] = {
adapter: this,
ws: ws,
sendApi: (action, params) => this.sendApi(data, ws, action, params),
stat: { ...data.status, start_time: data.time },
info: {},
get uin() { return this.info.user_id },
get nickname() { return this.info.user_name },
get avatar() { return this.info["wx.avatar"] },
pickFriend: user_id => this.pickFriend(data, user_id),
get pickUser() { return this.pickFriend },
getFriendArray: () => this.getFriendArray(data),
getFriendList: () => this.getFriendList(data),
getFriendMap: () => this.getFriendMap(data),
fl: new Map,
pickMember: (group_id, user_id) => this.pickMember(data, group_id, user_id),
pickGroup: group_id => this.pickGroup(data, group_id),
getGroupArray: () => this.getGroupArray(data),
getGroupList: () => this.getGroupList(data),
getGroupMap: () => this.getGroupMap(data),
gl: new Map,
gml: new Map,
}
data.bot = Bot[data.self_id]
if (!Bot.uin.includes(data.self_id))
Bot.uin.push(data.self_id)
data.bot.info = (await data.bot.sendApi("get_self_info").catch(i => i.error)).data
data.bot.version = {
...(await data.bot.sendApi("get_version").catch(i => i.error)).data,
id: this.id,
name: this.name,
}
data.bot.getFriendMap()
data.bot.getGroupMap()
Bot.makeLog("mark", `${this.name}(${this.id}) ${data.bot.version.impl}-${data.bot.version.version} 已连接`, data.self_id)
Bot.em(`connect.${data.self_id}`, data)
}
makeMessage(data) {
data.post_type = data.type
data.message_type = data.detail_type
data.raw_message = data.alt_message
data.sender = {
...data.bot.fl.get(data.user_id),
user_id: data.user_id,
}
const message = []
for (const i of data.message)
switch (i.type) {
case "mention":
message.push({ type: "at", qq: i.data.user_id })
break
case "mention_all":
message.push({ type: "at", qq: "all" })
break
case "voice":
message.push({ type: "record", ...i.data })
break
case "reply":
message.push({ type: "reply", id: i.data.message_id, user_id: i.data.user_id })
break
default:
message.push({ type: i.type, ...i.data })
}
data.message = message
switch (data.message_type) {
case "private":
Bot.makeLog("info", `好友消息:${data.raw_message}`, `${data.self_id} <= ${data.user_id}`)
break
case "group":
Bot.makeLog("info", `群消息:${data.raw_message}`, `${data.self_id} <= ${data.group_id}, ${data.user_id}`)
break
default:
Bot.makeLog("warn", `未知消息:${logger.magenta(data.raw)}`, data.self_id)
}
Bot.em(`${data.post_type}.${data.message_type}`, data)
}
makeNotice(data) {
data.post_type = data.type
if (data.group_id)
data.notice_type = "group"
else
data.notice_type = "friend"
switch (data.detail_type) {
case "private_message_delete":
Bot.makeLog("info", `好友消息撤回:${data.message_id}`, `${data.self_id} <= ${data.user_id}`)
data.sub_type = "recall"
break
case "group_message_delete":
Bot.makeLog("info", `群消息撤回:${data.operator_id} => ${data.user_id} ${data.message_id}`, `${data.self_id} <= ${data.group_id}`)
data.sub_type = "recall"
break
case "wx.get_private_file":
Bot.makeLog("info", `私聊文件:${data.file_name} ${data.file_length} ${data.md5}`, `${data.self_id} <= ${data.user_id}`)
break
case "wx.get_group_file":
Bot.makeLog("info", `群文件:${data.file_name} ${data.file_length} ${data.md5}`, `${data.self_id} <= ${data.group_id}, ${data.user_id}`)
break
case "wx.get_private_redbag":
Bot.makeLog("info", `好友红包`, `${data.self_id} <= ${data.user_id}`)
break
case "wx.get_group_redbag":
Bot.makeLog("info", `群红包`, `${data.self_id} <= ${data.group_id}, ${data.user_id}`)
break
case "wx.get_private_poke":
data.operator_id = data.from_user_id
data.target_id = data.user_id
Bot.makeLog("info", `好友拍一拍:${data.operator_id} => ${data.target_id}`, data.self_id)
break
case "wx.get_group_poke":
data.operator_id = data.from_user_id
data.target_id = data.user_id
Bot.makeLog("info", `群拍一拍:${data.operator_id} => ${data.target_id}`, `${data.self_id} <= ${data.group_id}`)
break
case "wx.get_private_card":
Bot.makeLog("info", `好友用户名片:${data.v3} ${data.v4} ${data.nickname} ${data.head_url} ${data.province} ${data.city} ${data.sex}`, `${data.self_id} <= ${data.user_id}`)
break
case "wx.get_group_card":
Bot.makeLog("info", `群用户名片:${data.v3} ${data.v4} ${data.nickname} ${data.head_url} ${data.province} ${data.city} ${data.sex}`, `${data.self_id} <= ${data.group_id}, ${data.user_id}`)
break
default:
Bot.makeLog("warn", `未知通知:${logger.magenta(data.raw)}`, data.self_id)
}
if (!data.sub_type)
data.sub_type = data.detail_type.split("_").pop()
Bot.em(`${data.post_type}.${data.notice_type}.${data.sub_type}`, data)
}
makeRequest(data) {
data.post_type = data.type
if (data.group_id)
data.notice_type = "group"
else
data.notice_type = "friend"
switch (data.detail_type) {
case "wx.friend_request":
Bot.makeLog("info", `加好友请求:${data.v3} ${data.v4} ${data.nickname} ${data.content} ${data.province} ${data.city}`, `${data.self_id} <= ${data.user_id}`)
data.sub_type = "add"
break
default:
Bot.makeLog("warn", `未知请求:${logger.magenta(data.raw)}`, data.self_id)
}
if (!data.sub_type)
data.sub_type = data.detail_type.split("_").pop()
Bot.em(`${data.post_type}.${data.request_type}.${data.sub_type}`, data)
}
makeMeta(data, ws) {
switch (data.detail_type) {
case "heartbeat":
break
case "connect":
break
case "status_update":
this.connect(data, ws)
break
default:
Bot.makeLog("warn", `未知消息:${logger.magenta(data.raw)}`, data.self_id)
}
}
message(data, ws) {
try {
data = {
...JSON.parse(data),
raw: Bot.String(data),
}
} catch (err) {
return Bot.makeLog("error", ["解码数据失败", data, err])
}
if (data.self?.user_id) {
data.self_id = data.self.user_id
} else {
data.self_id = data.id
}
if (data.type) {
if (data.type !== "meta" && !Bot.uin.includes(data.self_id)) {
Bot.makeLog("warn", `找不到对应Bot,忽略消息:${logger.magenta(data.raw)}`, data.self_id)
return false
}
data.bot = Bot[data.self_id]
switch (data.type) {
case "meta":
this.makeMeta(data, ws)
break
case "message":
this.makeMessage(data)
break
case "notice":
this.makeNotice(data)
break
case "request":
this.makeRequest(data)
break
default:
Bot.makeLog("warn", `未知消息:${logger.magenta(data.raw)}`, data.self_id)
}
} else if (data.echo && this.echo[data.echo]) {
if (data.retcode !== 0)
this.echo[data.echo].reject(Object.assign(
this.echo[data.echo].request, { error: data }
))
else
this.echo[data.echo].resolve(data.data ? new Proxy(data, {
get: (target, prop) => target.data[prop] ?? target[prop],
}) : data)
clearTimeout(this.echo[data.echo].timeout)
delete this.echo[data.echo]
} else {
Bot.makeLog("warn", `未知消息:${logger.magenta(data.raw)}`, data.self_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))
)
}
})