Miao-Yunzai/plugins/adapter/ComWeChat.js

456 lines
13 KiB
JavaScript
Raw Normal View History

2023-05-11 16:03:18 +08:00
import { WebSocketServer } from "ws"
import { randomUUID } from "crypto"
2023-06-18 11:57:31 +08:00
import path from "node:path"
import fs from "node:fs"
import { fileTypeFromBuffer } from "file-type"
2023-05-11 16:03:18 +08:00
2023-06-26 23:32:52 +08:00
Bot.adapter.push(new class ComWeChatAdapter {
constructor() {
this.id = "WeChat"
this.name = "ComWeChat"
this.path = this.name
2023-06-26 23:32:52 +08:00
}
2023-05-11 16:03:18 +08:00
toStr(data) {
switch (typeof data) {
case "string":
return data
case "number":
return String(data)
case "object":
if (Buffer.isBuffer(data))
return Buffer.from(data, "utf8").toString()
else
return JSON.stringify(data)
}
return data
2023-05-11 16:03:18 +08:00
}
makeLog(msg) {
return this.toStr(msg).replace(/(base64:\/\/|"type":"data","data":").*?(,|]|")/g, "$1...$2")
}
2023-06-19 22:59:24 +08:00
sendApi(ws, action, params = {}) {
2023-05-11 16:03:18 +08:00
const echo = randomUUID()
const msg = JSON.stringify({ action, params, echo })
logger.debug(`发送 API 请求:${logger.cyan(this.makeLog(msg))}`)
ws.send(msg)
return new Promise(resolve =>
Bot.once(echo, data =>
resolve({ ...data, ...data.data })))
}
async fileName(file) {
try {
if (file.match(/^base64:\/\//)) {
const buffer = Buffer.from(file.replace(/^base64:\/\//, ""), "base64")
const type = await fileTypeFromBuffer(buffer)
return `${Date.now()}.${type.ext}`
} else {
return path.basename(file)
}
} catch (err) {
logger.error(`文件类型检测错误:${logger.red(err)}`)
}
return false
}
async uploadFile(data, file, name) {
const opts = { name: name || await this.fileName(file) || randomUUID() }
2023-06-18 11:57:31 +08:00
if (file.match(/^https?:\/\//)) {
opts.type = "url"
opts.url = file
} else if (file.match(/^base64:\/\//)) {
opts.type = "data"
opts.data = file.replace(/^base64:\/\//, "")
} else if (fs.existsSync(file)) {
opts.type = "data",
opts.data = fs.readFileSync(file).toString("base64")
} else {
opts.type = "path"
opts.path = file
}
logger.info(`${logger.blue(`[${data.self_id}]`)} 上传文件:${this.makeLog(opts)}`)
2023-06-18 11:57:31 +08:00
return data.sendApi("upload_file", opts)
2023-05-11 16:03:18 +08:00
}
async makeMsg(data, msg) {
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)
2023-06-19 22:59:24 +08:00
i.data = { file_id: (await this.uploadFile(data, i.data.file)).file_id }
2023-05-11 16:03:18 +08:00
switch (i.type) {
case "text":
break
case "image":
break
case "record":
i.type = "file"
break
case "video":
i.type = "file"
break
2023-05-11 16:03:18 +08:00
case "at":
if (i.data.qq == "all")
i = { type: "mention_all", data: {}}
2023-05-11 16:03:18 +08:00
else
i = { type: "mention", data: { user_id: i.data.qq }}
2023-05-11 16:03:18 +08:00
break
case "reply":
2023-07-07 17:48:53 +08:00
continue
2023-05-11 16:03:18 +08:00
break
2023-07-26 10:35:11 +08:00
case "wx.emoji":
break
case "wx.link":
break
2023-05-11 16:03:18 +08:00
default:
i = { type: "text", data: { text: JSON.stringify(i) }}
2023-05-11 16:03:18 +08:00
}
msgs.push(i)
2023-05-11 16:03:18 +08:00
}
return msgs
}
async sendFriendMsg(data, msg) {
2023-06-18 11:57:31 +08:00
if (msg?.type == "node")
return Bot.sendForwardMsg(msg => this.sendFriendMsg(data, msg), msg.data)
2023-06-18 11:57:31 +08:00
const message = await this.makeMsg(data, msg)
logger.info(`${logger.blue(`[${data.self_id}]`)} 发送好友消息:[${data.user_id}] ${this.makeLog(message)}`)
2023-05-11 16:03:18 +08:00
return data.sendApi("send_message", {
detail_type: "private",
user_id: data.user_id,
message,
2023-05-11 16:03:18 +08:00
})
}
async sendGroupMsg(data, msg) {
2023-06-18 11:57:31 +08:00
if (msg?.type == "node")
return Bot.sendForwardMsg(msg => this.sendGroupMsg(data, msg), msg.data)
2023-06-18 11:57:31 +08:00
const message = await this.makeMsg(data, msg)
logger.info(`${logger.blue(`[${data.self_id}]`)} 发送群消息:[${data.group_id}] ${this.makeLog(message)}`)
2023-05-11 16:03:18 +08:00
return data.sendApi("send_message", {
detail_type: "group",
group_id: data.group_id,
message,
2023-05-11 16:03:18 +08:00
})
}
async getFriendArray(data) {
2023-05-26 16:53:00 +08:00
const array = []
2023-06-19 22:59:24 +08:00
for (const i of (await data.sendApi("get_friend_list")).data)
2023-05-26 16:53:00 +08:00
array.push({
...i,
nickname: i.user_remark == "null" ? i.user_displayname || i.user_name : i.user_remark,
})
return array
2023-05-11 16:03:18 +08:00
}
async getFriendList(data) {
const array = []
2023-07-08 21:40:55 +08:00
for (const { user_id } of (await this.getFriendArray(data)))
array.push(user_id)
2023-05-11 16:03:18 +08:00
return array
}
async getFriendMap(data) {
const map = new Map()
for (const i of (await this.getFriendArray(data)))
map.set(i.user_id, i)
return map
}
getFriendInfo(data) {
return data.sendApi("get_user_info", {
user_id: data.user_id,
})
}
async getGroupArray(data) {
2023-06-19 22:59:24 +08:00
return (await data.sendApi("get_group_list")).data
2023-05-11 16:03:18 +08:00
}
async getGroupList(data) {
const array = []
2023-07-08 21:40:55 +08:00
for (const { group_id } of (await this.getGroupArray(data)))
array.push(group_id)
2023-05-11 16:03:18 +08:00
return array
}
async getGroupMap(data) {
const map = new Map()
for (const i of (await this.getGroupArray(data)))
map.set(i.group_id, i)
return map
}
getGroupInfo(data) {
return data.sendApi("get_group_info", {
group_id: data.group_id,
})
}
2023-06-28 19:57:42 +08:00
async getMemberArray(data) {
2023-05-11 16:03:18 +08:00
return (await data.sendApi("get_group_member_list", {
group_id: data.group_id,
})).data
}
2023-06-28 19:57:42 +08:00
async getMemberList(data) {
2023-05-11 16:03:18 +08:00
const array = []
2023-07-08 21:40:55 +08:00
for (const { user_id } of (await this.getMemberArray(data)))
array.push(user_id)
2023-05-11 16:03:18 +08:00
return array
}
2023-06-28 19:57:42 +08:00
async getMemberMap(data) {
2023-05-11 16:03:18 +08:00
const map = new Map()
2023-06-28 19:57:42 +08:00
for (const i of (await this.getMemberArray(data)))
2023-05-11 16:03:18 +08:00
map.set(i.user_id, i)
return map
}
2023-06-28 19:57:42 +08:00
getMemberInfo(data) {
2023-05-11 16:03:18 +08:00
return data.sendApi("get_group_member_info", {
group_id: data.group_id,
user_id: data.user_id,
})
}
async sendFile(data, send, file, name) {
2023-06-29 20:25:21 +08:00
logger.info(`${logger.blue(`[${data.self_id}]`)} 发送文件:${name}(${file})`)
2023-06-18 11:57:31 +08:00
return send(segment.custom("file", {
2023-06-23 20:50:45 +08:00
file_id: (await this.uploadFile(data, file, name)).file_id
2023-06-18 11:57:31 +08:00
}))
}
pickFriend(data, user_id) {
2023-06-26 23:32:52 +08:00
const i = {
...Bot[data.self_id].fl.get(user_id),
...data,
user_id,
}
2023-06-18 11:57:31 +08:00
return {
2023-06-26 23:32:52 +08:00
...i,
2023-06-18 11:57:31 +08:00
sendMsg: msg => this.sendFriendMsg(i, msg),
recallMsg: () => false,
makeForwardMsg: Bot.makeForwardMsg,
sendForwardMsg: msg => Bot.sendForwardMsg(msg => this.sendFriendMsg(i, msg), msg),
2023-06-18 11:57:31 +08:00
sendFile: (file, name) => this.sendFile(i, msg => this.sendFriendMsg(i, msg), file, name),
getInfo: () => this.getFriendInfo(i),
getAvatarUrl: async () => (await this.getFriendInfo(i))["wx.avatar"],
}
}
pickMember(data, group_id, user_id) {
2023-06-26 23:32:52 +08:00
const i = {
...Bot[data.self_id].fl.get(user_id),
...data,
group_id,
user_id,
}
2023-06-18 11:57:31 +08:00
return {
...this.pickFriend(i, user_id),
2023-06-26 23:32:52 +08:00
...i,
2023-06-28 19:57:42 +08:00
getInfo: () => this.getMemberInfo(i),
getAvatarUrl: async () => (await this.getMemberInfo(i))["wx.avatar"],
2023-06-18 11:57:31 +08:00
}
}
pickGroup(data, group_id) {
2023-06-26 23:32:52 +08:00
const i = {
...Bot[data.self_id].gl.get(group_id),
...data,
group_id,
}
2023-06-18 11:57:31 +08:00
return {
2023-06-26 23:32:52 +08:00
...i,
2023-06-18 11:57:31 +08:00
sendMsg: msg => this.sendGroupMsg(i, msg),
recallMsg: () => false,
makeForwardMsg: Bot.makeForwardMsg,
sendForwardMsg: msg => Bot.sendForwardMsg(msg => this.sendGroupMsg(i, msg), msg),
2023-06-18 11:57:31 +08:00
sendFile: (file, name) => this.sendFile(i, msg => this.sendGroupMsg(i, msg), file, name),
getInfo: () => this.getGroupInfo(i),
getAvatarUrl: async () => (await this.getGroupInfo(i))["wx.avatar"],
2023-06-28 19:57:42 +08:00
getMemberArray: () => this.getMemberArray(i),
getMemberList: () => this.getMemberList(i),
getMemberMap: () => this.getMemberMap(i),
2023-06-18 11:57:31 +08:00
pickMember: user_id => this.pickMember(i, i.group_id, user_id),
}
}
2023-05-11 16:03:18 +08:00
async connect(data) {
2023-06-19 22:59:24 +08:00
for (const bot of data.status.bots)
2023-05-11 16:03:18 +08:00
data.self_id = bot.self.user_id
Bot[data.self_id] = {
adapter: this,
2023-05-11 16:03:18 +08:00
sendApi: data.sendApi,
2023-06-19 22:59:24 +08:00
stat: { ...data.status, start_time: data.time },
2023-05-11 16:03:18 +08:00
pickUser: user_id => this.pickFriend(data, user_id),
2023-06-18 11:57:31 +08:00
pickFriend: user_id => this.pickFriend(data, user_id),
2023-05-11 16:03:18 +08:00
getFriendArray: () => this.getFriendArray(data),
getFriendList: () => this.getFriendList(data),
getFriendMap: () => this.getFriendMap(data),
2023-06-18 11:57:31 +08:00
pickMember: (group_id, user_id) => this.pickMember(data, group_id, user_id),
pickGroup: group_id => this.pickGroup(data, group_id),
2023-05-11 16:03:18 +08:00
getGroupArray: () => this.getGroupArray(data),
getGroupList: () => this.getGroupList(data),
getGroupMap: () => this.getGroupMap(data),
}
2023-06-19 22:59:24 +08:00
Bot[data.self_id].info = (await data.sendApi("get_self_info")).data
2023-05-11 16:03:18 +08:00
Bot[data.self_id].uin = Bot[data.self_id].info.user_id
Bot[data.self_id].nickname = Bot[data.self_id].info.user_name
Bot[data.self_id].avatar = Bot[data.self_id].info["wx.avatar"]
2023-06-26 23:32:52 +08:00
Bot[data.self_id].version = {
...(await data.sendApi("get_version")).data,
id: this.id,
name: this.name,
}
2023-06-19 22:59:24 +08:00
2023-06-26 23:32:52 +08:00
Bot[data.self_id].fl = await Bot[data.self_id].getFriendMap()
Bot[data.self_id].gl = await Bot[data.self_id].getGroupMap()
2023-05-11 16:03:18 +08:00
2023-06-26 23:32:52 +08:00
if (!Bot.uin.includes(data.self_id))
Bot.uin.push(data.self_id)
2023-05-11 16:03:18 +08:00
2023-06-26 23:32:52 +08:00
logger.mark(`${logger.blue(`[${data.self_id}]`)} ${this.name}(${this.id}) 已连接`)
2023-05-11 16:03:18 +08:00
Bot.emit(`connect.${data.self_id}`, Bot[data.self_id])
2023-07-19 21:53:38 +08:00
Bot.emit("connect", Bot[data.self_id])
2023-05-11 16:03:18 +08:00
}
makeMessage(data) {
data.post_type = data.type
data.message_type = data.detail_type
data.raw_message = data.alt_message
2023-06-28 19:57:42 +08:00
data.sender = {
...data.bot.fl.get(data.user_id),
user_id: data.user_id,
}
2023-05-11 16:03:18 +08:00
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
2023-05-11 16:03:18 +08:00
default:
message.push({ type: i.type, ...i.data })
}
data.message = message
switch (data.message_type) {
case "private":
logger.info(`${logger.blue(`[${data.self_id}]`)} 好友消息:[${data.user_id}] ${data.raw_message}`)
data.friend = data.bot.pickFriend(data.user_id)
break
case "group":
logger.info(`${logger.blue(`[${data.self_id}]`)} 群消息:[${data.group_id}, ${data.user_id}] ${data.raw_message}`)
data.friend = data.bot.pickFriend(data.user_id)
data.group = data.bot.pickGroup(data.group_id)
data.member = data.group.pickMember(data.user_id)
break
default:
2023-06-30 21:20:06 +08:00
logger.warn(`${logger.blue(`[${data.self_id}]`)} 未知消息:${logger.magenta(JSON.stringify(data))}`)
2023-05-11 16:03:18 +08:00
}
Bot.emit(`${data.post_type}.${data.message_type}`, data)
Bot.emit(`${data.post_type}`, data)
}
makeMeta(data) {
switch (data.detail_type) {
2023-06-19 22:59:24 +08:00
case "heartbeat":
break
2023-05-11 16:03:18 +08:00
case "connect":
2023-06-19 22:59:24 +08:00
break
case "status_update":
2023-05-11 16:03:18 +08:00
this.connect(data)
break
default:
2023-06-30 21:20:06 +08:00
logger.warn(`${logger.blue(`[${data.self_id}]`)} 未知消息:${logger.magenta(JSON.stringify(data))}`)
2023-05-11 16:03:18 +08:00
}
}
message(data, ws) {
try {
data = JSON.parse(data)
} catch (err) {
2023-06-30 21:20:06 +08:00
return logger.error(`解码数据失败:${logger.red(err)}`)
2023-05-11 16:03:18 +08:00
}
if (data.self?.user_id) {
data.self_id = data.self.user_id
} else {
data.self_id = data.id
}
if (data.type) {
2023-06-30 21:20:06 +08:00
if (data.type != "meta" && !Bot.uin.includes(data.self_id)) {
logger.warn(`${logger.blue(`[${data.self_id}]`)} 找不到对应Bot忽略消息${logger.magenta(JSON.stringify(data))}`)
2023-06-26 23:32:52 +08:00
return false
2023-06-30 21:20:06 +08:00
}
2023-06-26 23:32:52 +08:00
data.sendApi = (action, params) => this.sendApi(ws, action, params)
data.bot = Bot[data.self_id]
2023-06-30 21:20:06 +08:00
2023-05-11 16:03:18 +08:00
switch (data.type) {
2023-06-19 22:59:24 +08:00
case "meta":
this.makeMeta(data)
break
2023-05-11 16:03:18 +08:00
case "message":
this.makeMessage(data)
break
/*
case "notice":
this.makeNotice(data)
break
case "request":
this.makeRequest(data)
break
*/
default:
2023-06-30 21:20:06 +08:00
logger.warn(`${logger.blue(`[${data.self_id}]`)} 未知消息:${logger.magenta(JSON.stringify(data))}`)
2023-05-11 16:03:18 +08:00
}
} else if (data.echo) {
logger.debug(`请求 API 返回:${logger.cyan(JSON.stringify(data))}`)
Bot.emit(data.echo, data)
} else {
2023-06-30 21:20:06 +08:00
logger.warn(`${logger.blue(`[${data.self_id}]`)} 未知消息:${logger.magenta(JSON.stringify(data))}`)
2023-05-11 16:03:18 +08:00
}
}
load() {
Bot.wss[this.path] = new WebSocketServer({ noServer: true })
2023-07-23 19:04:24 +08:00
Bot.wss[this.path].on("connection", ws => ws
.on("error", logger.error)
.on("message", data => this.message(data, ws))
)
2023-06-26 23:32:52 +08:00
return true
2023-05-11 16:03:18 +08:00
}
2023-06-26 23:32:52 +08:00
})