diff --git a/CHANGELOG.md b/CHANGELOG.md
index e215f2d..d4d3335 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,11 @@
+# 3.1.2
+
+* 支持协议端:QQBot、OPQBot
+* 新增`#绑定用户`命令
+ * 可将其他QQ绑定至当前用户,以打通多个用户,子用户使用主用户的CK与UID等信息
+ * 同时也可绑定其他平台的用户,例如频道、微信等。如需链接至其他平台需使用TRSS-Yunzai或Lain-plugin
+ * 部分命令可能无法识别绑定后的主用户,如遇问题可反馈
+
# 3.1.1
* 支持协议端:米游社大别野Bot
diff --git a/README.md b/README.md
index f4be8ea..f397ff1 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
# TRSS-Yunzai
-Yunzai 应用端,支持多账号,支持协议端:go-cqhttp、ComWeChat、GSUIDCore、ICQQ、QQ频道、微信、KOOK、Telegram、Discord
+Yunzai 应用端,支持多账号,支持协议端:go-cqhttp、ComWeChat、GSUIDCore、ICQQ、QQBot、QQ频道、微信、KOOK、Telegram、Discord、OPQBot
[](https://github.com/TimeRainStarSky/Yunzai)
[](../../stargazers)
@@ -118,6 +118,12 @@ ws://localhost:2536/GSUIDCore
+QQBot
+
+[TRSS-Yunzai QQBot Plugin](../../../Yunzai-QQBot-Plugin)
+
+
+
QQ频道
[TRSS-Yunzai QQGuild Plugin](../../../Yunzai-QQGuild-Plugin)
@@ -154,9 +160,19 @@ ws://localhost:2536/GSUIDCore
-代理
+OPQBot
-[TRSS-Yunzai Proxy Plugin](../../../Yunzai-Proxy-Plugin)
+下载运行 [OPQBot](https://opqbot.com),启动参数添加:
+
+```
+-wsserver ws://localhost:2536/OPQBot
+```
+
+
+
+路由
+
+[TRSS-Yunzai Route Plugin](../../../Yunzai-Route-Plugin)
diff --git a/lib/config/redis.js b/lib/config/redis.js
index e4b84c0..aad2882 100644
--- a/lib/config/redis.js
+++ b/lib/config/redis.js
@@ -44,6 +44,7 @@ export default async function redisInit() {
})
/** 全局变量 redis */
+ client.url = redisUrl
global.redis = client
logger.info("Redis 连接成功")
return client
diff --git a/lib/plugins/loader.js b/lib/plugins/loader.js
index fce9fa6..14b5364 100644
--- a/lib/plugins/loader.js
+++ b/lib/plugins/loader.js
@@ -134,7 +134,7 @@ class PluginsLoader {
for (let val of files) {
let filepath = "../../plugins/" + val.name
let tmp = {
- name: val.name,
+ name: val.name
}
if (val.isFile()) {
if (!val.name.endsWith(".js")) continue
@@ -229,8 +229,16 @@ class PluginsLoader {
// 判断是否是星铁命令,若是星铁命令则标准化处理
// e.isSr = true,且命令标准化为 #星铁 开头
+ Object.defineProperty(e, "isSr", {
+ get: () => e.game === "sr",
+ set: (v) => e.game = v ? "sr" : "gs"
+ })
+ Object.defineProperty(e, "isGs", {
+ get: () => e.game === "gs",
+ set: (v) => e.game = v ? "gs" : "sr"
+ })
if (this.srReg.test(e.msg)) {
- e.isSr = true
+ e.game = "sr"
e.msg = e.msg.replace(this.srReg, "#星铁")
}
diff --git a/lib/plugins/runtime.js b/lib/plugins/runtime.js
index 21b2d46..35b4f23 100644
--- a/lib/plugins/runtime.js
+++ b/lib/plugins/runtime.js
@@ -77,7 +77,6 @@ export default class Runtime {
await MysInfo.initCache()
let runtime = new Runtime(e)
e.runtime = runtime
- e.game = e.isSr ? 'sr' : 'gs'
await runtime.initUser()
return runtime
}
@@ -88,7 +87,7 @@ export default class Runtime {
if (user) {
e.user = new Proxy(user, {
get (self, key, receiver) {
- let game = e.isSr ? 'sr' : 'gs'
+ let game = e.game
let fnMap = {
uid: 'getUid',
uidList: 'getUidList',
diff --git a/lib/tools/name.js b/lib/tools/log.js
similarity index 95%
rename from lib/tools/name.js
rename to lib/tools/log.js
index d4ff2f2..33799aa 100644
--- a/lib/tools/name.js
+++ b/lib/tools/log.js
@@ -13,7 +13,6 @@ fs.readFile(`${_path}/config/pm2/pm2.json`, `utf8`, (err, data) => {
const config = JSON.parse(data)
if (config.apps && config.apps.length > 0 && config.apps[0].name) {
const appName = config.apps[0].name
- console.log(config.apps[0].name)
runPm2Logs(appName)
} else {
console.log('读取失败:无法在pm2.json中找到name数组')
@@ -32,4 +31,4 @@ function runPm2Logs(appName) {
console.error(`pm2 logs process exited with code ${code}`)
}
})
-}
\ No newline at end of file
+}
diff --git a/package.json b/package.json
index d5ce248..f10c4a8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "trss-yunzai",
- "version": "3.1.0",
+ "version": "3.1.2",
"author": "TimeRainStarSky, Yoimiya-Kokomi, Le-niao",
"description": "Bot",
"main": "app.js",
@@ -13,14 +13,14 @@
"start": "pm2 start ./config/pm2/pm2.json",
"stop": "pm2 stop ./config/pm2/pm2.json",
"restart": "pm2 restart ./config/pm2/pm2.json",
- "log": "node ./lib/tools/name.js"
+ "log": "node ./lib/tools/log.js"
},
"dependencies": {
"art-template": "^4.13.2",
"chalk": "^5.3.0",
"chokidar": "^3.5.3",
"express": "^4.18.2",
- "file-type": "^18.5.0",
+ "file-type": "^18.6.0",
"https-proxy-agent": "7.0.2",
"image-size": "^1.0.2",
"lodash": "^4.17.21",
@@ -32,7 +32,7 @@
"node-xlsx": "^0.23.0",
"oicq": "link:lib/modules/oicq",
"pm2": "^5.3.0",
- "puppeteer": "^21.3.8",
+ "puppeteer": "^21.4.1",
"redis": "^4.6.10",
"sequelize": "^6.33.0",
"sqlite3": "^5.1.6",
@@ -40,9 +40,9 @@
"yaml": "^2.3.3"
},
"devDependencies": {
- "eslint": "^8.51.0",
+ "eslint": "^8.52.0",
"eslint-config-standard": "^17.1.0",
- "eslint-plugin-import": "^2.28.1",
+ "eslint-plugin-import": "^2.29.0",
"eslint-plugin-n": "^16.2.0",
"eslint-plugin-promise": "^6.1.1"
},
diff --git a/plugins/adapter/ComWeChat.js b/plugins/adapter/ComWeChat.js
index a0b910c..cef7d28 100644
--- a/plugins/adapter/ComWeChat.js
+++ b/plugins/adapter/ComWeChat.js
@@ -31,8 +31,7 @@ Bot.adapter.push(new class ComWeChatAdapter {
sendApi(ws, action, params = {}) {
const echo = randomUUID()
- const msg = { action, params, echo }
- ws.sendMsg(msg)
+ ws.sendMsg({ action, params, echo })
return new Promise(resolve =>
Bot.once(echo, data =>
resolve({ ...data, ...data.data })))
diff --git a/plugins/adapter/OPQBot.js b/plugins/adapter/OPQBot.js
new file mode 100644
index 0000000..80aabed
--- /dev/null
+++ b/plugins/adapter/OPQBot.js
@@ -0,0 +1,338 @@
+import path from "node:path"
+import fs from "node:fs"
+
+Bot.adapter.push(new class OPQBotAdapter {
+ constructor() {
+ this.id = "QQ"
+ this.name = "OPQBot"
+ this.path = this.name
+ this.CommandId = {
+ FriendImage: 1,
+ GroupImage: 2,
+ FriendVoice: 26,
+ GroupVoice: 29,
+ }
+ }
+
+ sendApi(id, CgiCmd, CgiRequest) {
+ const ReqId = Math.round(Math.random()*10**16)
+ Bot[id].ws.sendMsg({ BotUin: String(id), CgiCmd, CgiRequest, ReqId })
+ return new Promise(resolve =>
+ Bot.once(ReqId, data => resolve(data)))
+ }
+
+ 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:\/\/.*?"/g, 'base64://..."')
+ }
+
+ async uploadFile(id, type, file) {
+ const opts = { CommandId: this.CommandId[type] }
+
+ if (file.match(/^base64:\/\//))
+ opts.Base64Buf = file.replace(/^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":
+ continue
+ case "node":
+ await Bot.sendForwardMsg(msg => this.sendMsg(send, upload, msg), i.data)
+ continue
+ default:
+ message.Content += JSON.stringify(i)
+ }
+ }
+
+ return send(message)
+ }
+
+ sendFriendMsg(data, msg, event) {
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 发送好友消息:[${data.user_id}] ${this.makeLog(msg)}`)
+ 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) {
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 发送群员消息:[${data.group_id}, ${data.user_id}] ${this.makeLog(msg)}`)
+ 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) {
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 发送群消息:[${data.group_id}] ${this.makeLog(msg)}`)
+ 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),
+ }
+ }
+
+ 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),
+ }
+ }
+
+ 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"
+
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 好友消息:[${data.sender.nickname}(${data.user_id})] ${data.raw_message}`)
+ 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)
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 群消息:[${data.group_name}(${data.group_id}), ${data.sender.nickname}(${data.user_id})] ${data.raw_message}`)
+ 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:
+ logger.warn(`${logger.blue(`[${id}]`)} 未知事件:${logger.magenta(JSON.stringify(data))}`)
+ }
+ }
+
+ makeBot(id, ws) {
+ Bot[id] = {
+ adapter: this,
+ ws,
+
+ uin: id,
+ info: { id },
+ get nickname() { return this.info.nickname },
+ get avatar() { return `https://q1.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,
+ }
+
+ logger.mark(`${logger.blue(`[${id}]`)} ${this.name}(${this.id}) ${this.version} 已连接`)
+ Bot.em(`connect.${id}`, { self_id: id })
+ }
+
+ message(data, ws) {
+ try {
+ data = JSON.parse(data)
+ } catch (err) {
+ return logger.error(`解码数据失败:${logger.red(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) {
+ Bot.emit(data.ReqId, data)
+ } else {
+ logger.warn(`${logger.blue(`[${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))
+ )
+ }
+})
\ No newline at end of file
diff --git a/plugins/adapter/go-cqhttp.js b/plugins/adapter/go-cqhttp.js
index 78cc783..343f4d8 100644
--- a/plugins/adapter/go-cqhttp.js
+++ b/plugins/adapter/go-cqhttp.js
@@ -30,8 +30,7 @@ Bot.adapter.push(new class gocqhttpAdapter {
sendApi(ws, action, params) {
const echo = randomUUID()
- const msg = { action, params, echo }
- ws.sendMsg(msg)
+ ws.sendMsg({ action, params, echo })
return new Promise(resolve =>
Bot.once(echo, data =>
resolve({ ...data, ...data.data })))
diff --git a/plugins/other/update.js b/plugins/other/update.js
index a33c30f..405c078 100644
--- a/plugins/other/update.js
+++ b/plugins/other/update.js
@@ -11,7 +11,7 @@ const { exec, execSync } = require('child_process')
let uping = false
export class update extends plugin {
- constructor() {
+ constructor () {
super({
name: '更新',
dsc: '#更新 #强制更新',
@@ -37,18 +37,28 @@ export class update extends plugin {
this.typeName = 'TRSS-Yunzai'
}
- async update() {
+ async update () {
if (!this.e.isMaster) return false
if (uping) return this.reply('已有命令更新中..请勿重复操作')
if (/详细|详情|面板|面版/.test(this.e.msg)) return false
/** 获取插件 */
- const plugin = this.getPlugin()
+ let plugin = this.getPlugin()
if (plugin === false) return false
/** 执行更新 */
- await this.runUpdate(plugin)
+ if (plugin === '') {
+ await this.runUpdate('')
+ await common.sleep(1000)
+ plugin = this.getPlugin('genshin')
+ await this.runUpdate(plugin)
+ await common.sleep(1000)
+ plugin = this.getPlugin('miao-plugin')
+ await this.runUpdate(plugin)
+ } else {
+ await this.runUpdate(plugin)
+ }
/** 是否需要重启 */
if (this.isUp) {
@@ -57,7 +67,7 @@ export class update extends plugin {
}
}
- getPlugin(plugin = '') {
+ getPlugin (plugin = '') {
if (!plugin) {
plugin = this.e.msg.replace(/#(强制)?更新(日志)?/, '')
if (!plugin) return ''
@@ -69,7 +79,7 @@ export class update extends plugin {
return plugin
}
- async execSync(cmd) {
+ async execSync (cmd) {
return new Promise((resolve, reject) => {
exec(cmd, { windowsHide: true }, (error, stdout, stderr) => {
resolve({ error, stdout, stderr })
@@ -77,7 +87,7 @@ export class update extends plugin {
})
}
- async runUpdate(plugin = '') {
+ async runUpdate (plugin = '') {
this.isNowUp = false
let cm = 'git pull --no-rebase'
@@ -118,7 +128,7 @@ export class update extends plugin {
return true
}
- async getcommitId(plugin = '') {
+ async getcommitId (plugin = '') {
let cm = 'git rev-parse --short HEAD'
if (plugin) cm = `cd "plugins/${plugin}" && ${cm}`
@@ -126,7 +136,7 @@ export class update extends plugin {
return lodash.trim(commitId)
}
- async getTime(plugin = '') {
+ async getTime (plugin = '') {
let cm = 'git log -1 --pretty=%cd --date=format:"%F %T"'
if (plugin) cm = `cd "plugins/${plugin}" && ${cm}`
@@ -142,7 +152,7 @@ export class update extends plugin {
return time
}
- async gitErr(err, stdout) {
+ async gitErr (err, stdout) {
const msg = '更新失败!'
const errMsg = err.toString()
stdout = stdout.toString()
@@ -168,7 +178,7 @@ export class update extends plugin {
return this.reply([errMsg, stdout])
}
- async updateAll() {
+ async updateAll () {
const dirs = fs.readdirSync('./plugins/')
await this.runUpdate()
@@ -186,11 +196,11 @@ export class update extends plugin {
}
}
- restart() {
+ restart () {
new Restart(this.e).restart()
}
- async getLog(plugin = '') {
+ async getLog (plugin = '') {
let cm = 'git log -100 --pretty="%h||[%cd] %s" --date=format:"%F %T"'
if (plugin) cm = `cd "plugins/${plugin}" && ${cm}`
@@ -232,9 +242,9 @@ export class update extends plugin {
return common.makeForwardMsg(this.e, [log, end], `${plugin || 'TRSS-Yunzai'} 更新日志,共${line}条`)
}
- async updateLog() {
+ async updateLog () {
const plugin = this.getPlugin()
if (plugin === false) return false
return this.reply(await this.getLog(plugin))
}
-}
\ No newline at end of file
+}