Miao-Yunzai/plugins/adapter/ComWeChat.js

500 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { randomUUID } from "crypto"
import path from "node:path"
import fs from "node:fs"
import { fileTypeFromBuffer } from "file-type"
Bot.adapter.push(new class ComWeChatAdapter {
constructor() {
this.id = "WeChat"
this.name = "ComWeChat"
this.path = this.name
}
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
}
makeLog(msg) {
return this.toStr(msg).replace(/(base64:\/\/|"type":"data","data":").*?"/g, '$1..."')
}
sendApi(ws, action, params = {}) {
const echo = randomUUID()
ws.sendMsg({ action, params, echo })
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() }
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)}`)
return data.bot.sendApi("upload_file", opts)
}
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)
i.data = { file_id: (await this.uploadFile(data, i.data.file, i.data.name)).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":
continue
default:
i = { type: "text", data: { text: JSON.stringify(i) }}
}
msgs.push(i)
}
return msgs
}
async sendFriendMsg(data, msg) {
if (msg?.type == "node")
return Bot.sendForwardMsg(msg => this.sendFriendMsg(data, msg), msg.data)
const message = await this.makeMsg(data, msg)
logger.info(`${logger.blue(`[${data.self_id} => ${data.user_id}]`)} 发送好友消息:${this.makeLog(message)}`)
return data.bot.sendApi("send_message", {
detail_type: "private",
user_id: data.user_id,
message,
})
}
async sendGroupMsg(data, msg) {
if (msg?.type == "node")
return Bot.sendForwardMsg(msg => this.sendGroupMsg(data, msg), msg.data)
const message = await this.makeMsg(data, msg)
logger.info(`${logger.blue(`[${data.self_id} => ${data.group_id}]`)} 发送群消息:${this.makeLog(message)}`)
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(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")).data
data.bot.version = {
...(await data.bot.sendApi("get_version")).data,
id: this.id,
name: this.name,
}
data.bot.getFriendMap()
data.bot.getGroupMap()
logger.mark(`${logger.blue(`[${data.self_id}]`)} ${this.name}(${this.id}) ${data.bot.version.impl}-${data.bot.version.version} 已连接`)
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":
logger.info(`${logger.blue(`[${data.self_id}]`)} 好友消息:[${data.user_id}] ${data.raw_message}`)
break
case "group":
logger.info(`${logger.blue(`[${data.self_id}]`)} 群消息:[${data.group_id}, ${data.user_id}] ${data.raw_message}`)
break
default:
logger.warn(`${logger.blue(`[${data.self_id}]`)} 未知消息:${logger.magenta(JSON.stringify(data))}`)
}
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":
logger.info(`${logger.blue(`[${data.self_id}]`)} 好友消息撤回:[${data.user_id}] ${data.message_id}`)
data.sub_type = "recall"
break
case "group_message_delete":
logger.info(`${logger.blue(`[${data.self_id}]`)} 群消息撤回:[${data.group_id}, ${data.operator_id}=>${data.user_id}] ${data.message_id}`)
data.sub_type = "recall"
break
case "wx.get_private_file":
logger.info(`${logger.blue(`[${data.self_id}]`)} 私聊文件:[${data.user_id}] ${data.file_name} ${data.file_length} ${data.md5}`)
break
case "wx.get_group_file":
logger.info(`${logger.blue(`[${data.self_id}]`)} 群文件:[${data.group_id}, ${data.user_id}] ${data.file_name} ${data.file_length} ${data.md5}`)
break
case "wx.get_private_redbag":
logger.info(`${logger.blue(`[${data.self_id}]`)} 好友红包:[${data.user_id}]`)
break
case "wx.get_group_redbag":
logger.info(`${logger.blue(`[${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
logger.info(`${logger.blue(`[${data.self_id}]`)} 好友拍一拍:[${data.operator_id}=>${data.target_id}]`)
break
case "wx.get_group_poke":
data.operator_id = data.from_user_id
data.target_id = data.user_id
logger.info(`${logger.blue(`[${data.self_id}]`)} 群拍一拍:[${data.group_id}, ${data.operator_id}=>${data.target_id}]`)
break
case "wx.get_private_card":
logger.info(`${logger.blue(`[${data.self_id}]`)} 好友用户名片:[${data.user_id}] ${data.v3} ${data.v4} ${data.nickname} ${data.head_url} ${data.province} ${data.city} ${data.sex}`)
break
case "wx.get_group_card":
logger.info(`${logger.blue(`[${data.self_id}]`)} 群用户名片:[${data.group_id}, ${data.user_id}] ${data.v3} ${data.v4} ${data.nickname} ${data.head_url} ${data.province} ${data.city} ${data.sex}`)
break
default:
logger.warn(`${logger.blue(`[${data.self_id}]`)} 未知通知:${logger.magenta(JSON.stringify(data))}`)
}
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":
logger.info(`${logger.blue(`[${data.self_id}]`)} 加好友请求:[${data.user_id}] ${data.v3} ${data.v4} ${data.nickname} ${data.content} ${data.province} ${data.city}`)
data.sub_type = "add"
break
default:
logger.warn(`${logger.blue(`[${data.self_id}]`)} 未知请求:${logger.magenta(JSON.stringify(data))}`)
}
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:
logger.warn(`${logger.blue(`[${data.self_id}]`)} 未知消息:${logger.magenta(JSON.stringify(data))}`)
}
}
message(data, ws) {
try {
data = JSON.parse(data)
} catch (err) {
return logger.error(`解码数据失败:${logger.red(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)) {
logger.warn(`${logger.blue(`[${data.self_id}]`)} 找不到对应Bot忽略消息${logger.magenta(JSON.stringify(data))}`)
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:
logger.warn(`${logger.blue(`[${data.self_id}]`)} 未知消息:${logger.magenta(JSON.stringify(data))}`)
}
} else if (data.echo) {
Bot.emit(data.echo, data)
} else {
logger.warn(`${logger.blue(`[${data.self_id}]`)} 未知消息:${logger.magenta(JSON.stringify(data))}`)
}
}
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))
)
}
})