diff --git a/Yunzai/.eslintrc.cjs b/Yunzai/.eslintrc.cjs
index a9b525032da461f639576fefdc3551565af8f5d6..e052c260840c85dddc727698328d3900f2da890b 100644
--- a/Yunzai/.eslintrc.cjs
+++ b/Yunzai/.eslintrc.cjs
@@ -12,7 +12,9 @@ module.exports = {
Bot: true,
redis: true,
logger: true,
- plugin: true
+ plugin: true,
+ Renderer: true,
+ segment: true
},
rules: {
eqeqeq: ['off'],
diff --git a/Yunzai/CHANGELOG.md b/Yunzai/CHANGELOG.md
index d3c37215860902b012aa41e43fe4887801bc22ac..843a2417517d82f5b1e54c6de945fb57bbaac6cc 100644
--- a/Yunzai/CHANGELOG.md
+++ b/Yunzai/CHANGELOG.md
@@ -1,6 +1,6 @@
# 3.1.3
-* 支持协议端:QQBot
+* 支持协议端:QQBot、go-cqhttp → OneBotv11、Lagrange
* **请注意:**
* 从3.1.3版本开始,原genshin包内的功能会逐步重构,与miao-plugin进行整合,以降低后续游戏版本升级时的维护成本
* 在整合过程中,可能会移除一些重复或迁移成本较高的功能,以及可能会有功能不稳定情况
diff --git a/Yunzai/README.md b/Yunzai/README.md
index f397ff1f7b233bc86cb9395c45df0fc2b72be3a6..809963dc4c18ecca32a248c8306bd1e62c2b35cb 100644
--- a/Yunzai/README.md
+++ b/Yunzai/README.md
@@ -2,7 +2,7 @@
# TRSS-Yunzai
-Yunzai 应用端,支持多账号,支持协议端:go-cqhttp、ComWeChat、GSUIDCore、ICQQ、QQBot、QQ频道、微信、KOOK、Telegram、Discord、OPQBot
+Yunzai 应用端,支持多账号,支持协议端:OneBotv11、ComWeChat、GSUIDCore、ICQQ、QQBot、QQ频道、微信、KOOK、Telegram、Discord、OPQBot、Lagrange
[](https://github.com/TimeRainStarSky/Yunzai)
[](../../stargazers)
@@ -13,7 +13,7 @@ Yunzai 应用端,支持多账号,支持协议端:go-cqhttp、ComWeChat、G
-- 基于 [Miao-Yunzai](../../../../yoimiya-kokomi/Miao-Yunzai) 改造,需要同时安装 [miao-plugin](../../../../yoimiya-kokomi/miao-plugin)
+- 基于 [Miao-Yunzai](../../../../yoimiya-kokomi/Miao-Yunzai)
- 开发文档:[docs 分支](../../tree/docs)
## TRSS-Yunzai 后续计划
@@ -36,151 +36,179 @@ Yunzai 应用端,支持多账号,支持协议端:go-cqhttp、ComWeChat、G
### 手动安装
-> 环境准备: Windows or Linux,Node.js( [版本至少 v18 以上](http://nodejs.cn/download) ), [Redis](https://redis.io/docs/getting-started/installation)
+> 环境准备:Windows/Linux/MacOS/Android
+> [Node.js(>=v21)](https://nodejs.org), [Redis](https://redis.io), [Git](https://git-scm.com), [Chrome(可选)](https://google.cn/chrome)
-1.克隆项目并安装 genshin miao-plugin TRSS-Plugin(可选)
+1. Git Clone 项目
请根据网络情况选择使用 GitHub 或 Gitee 安装
-```
+```sh
git clone --depth 1 https://github.com/TimeRainStarSky/Yunzai
+git clone --depth 1 https://gitee.com/TimeRainStarSky/Yunzai
cd Yunzai
+```
+
+2. 推荐安装插件(可选)
+
+```sh
git clone --depth 1 https://github.com/TimeRainStarSky/Yunzai-genshin plugins/genshin
git clone --depth 1 https://github.com/yoimiya-kokomi/miao-plugin plugins/miao-plugin
git clone --depth 1 https://github.com/TimeRainStarSky/TRSS-Plugin plugins/TRSS-Plugin
```
-```
-git clone --depth 1 https://gitee.com/TimeRainStarSky/Yunzai
-cd Yunzai
+```sh
git clone --depth 1 https://gitee.com/TimeRainStarSky/Yunzai-genshin plugins/genshin
git clone --depth 1 https://gitee.com/yoimiya-kokomi/miao-plugin plugins/miao-plugin
git clone --depth 1 https://Yunzai.TRSS.me plugins/TRSS-Plugin
```
-2.安装 [pnpm](https://pnpm.io/zh/installation)
+3. 安装 [pnpm](https://pnpm.io/zh/installation) 和依赖
-```
-npm install -g pnpm
-```
-
-3.安装依赖
-
-```
+```sh
+npm i -g pnpm
pnpm i
```
-4.运行
+4. 前台运行
-```
-node app
-```
+| 操作 | 命令 |
+| ---- | ---- |
+| 启动 | node . |
+| 停止 | node . stop |
-5.启动协议端:
+5. 启动协议端
-go-cqhttp
+WebSocket
-下载运行 [go-cqhttp](https://docs.go-cqhttp.org),选择反向 WebSocket,修改 `config.yml`,以下为必改项:
+OneBotv11
-```
-uin: 账号
-password: '密码'
-post-format: array
-universal: ws://localhost:2536/go-cqhttp
-```
+go-cqhttp
-
+ 下载运行 [go-cqhttp](https://docs.go-cqhttp.org),选择反向 WebSocket,修改 `config.yml`,以下为必改项:
-ComWeChat
+ ```yaml
+ uin: 账号
+ password: '密码'
+ post-format: array
+ universal: ws://localhost:2536/OneBotv11
+ ```
-下载运行 [ComWeChat](https://justundertaker.github.io/ComWeChatBotClient),修改 `.env`,以下为必改项:
+
-```
-websocekt_type = "Backward"
-websocket_url = ["ws://localhost:2536/ComWeChat"]
-```
+LLOneBot
-
+ 下载安装 [LLOneBot](https://github.com/LLOneBot/LLOneBot),启用反向 WebSocket,添加地址:
-GSUIDCore
+ ```
+ ws://localhost:2536/OneBotv11
+ ```
-下载运行 [GenshinUID 插件](http://docs.gsuid.gbots.work/#/AdapterList),GSUIDCore 连接地址 修改为:
+
-```
-ws://localhost:2536/GSUIDCore
-```
+Shamrock
-
+ 下载安装 [Shamrock](https://whitechi73.github.io/OpenShamrock),启用被动 WebSocket,添加地址:
-ICQQ
+ ```
+ ws://localhost:2536/OneBotv11
+ ```
-[TRSS-Yunzai ICQQ Plugin](../../../Yunzai-ICQQ-Plugin)
+
-
-
-QQBot
+Lagrange
-[TRSS-Yunzai QQBot Plugin](../../../Yunzai-QQBot-Plugin)
+ 下载运行 [Lagrange.OneBot](https://lagrangedev.github.io/Lagrange.Doc/Lagrange.OneBot),修改 `appsettings.json` 中 `Implementations`:
-
+ ```json
+ {
+ "Type": "ReverseWebSocket",
+ "Host": "localhost",
+ "Port": 2536,
+ "Suffix": "/OneBotv11",
+ "ReconnectInterval": 5000,
+ "HeartBeatInterval": 5000,
+ "AccessToken": ""
+ }
+ ```
-QQ频道
+
-[TRSS-Yunzai QQGuild Plugin](../../../Yunzai-QQGuild-Plugin)
+
-
+ComWeChat
-微信
+下载运行 [ComWeChat](https://justundertaker.github.io/ComWeChatBotClient),修改 `.env`,以下为必改项:
-[TRSS-Yunzai WeChat Plugin](../../../Yunzai-WeChat-Plugin)
+```python
+websocekt_type = "Backward"
+websocket_url = ["ws://localhost:2536/ComWeChat"]
+```
-
+
-米游社大别野
+GSUIDCore
-[TRSS-Yunzai mysVilla Plugin](../../../Yunzai-mysVilla-Plugin)
+下载运行 [GenshinUID 插件](http://docs.gsuid.gbots.work/#/AdapterList),GSUIDCore 连接地址 修改为:
-
+```
+ws://localhost:2536/GSUIDCore
+```
-KOOK
+
-[TRSS-Yunzai KOOK Plugin](../../../Yunzai-KOOK-Plugin)
+OPQBot
-
+下载运行 [OPQBot](https://opqbot.com),启动参数添加:
-Telegram
+```
+-wsserver ws://localhost:2536/OPQBot
+```
-[TRSS-Yunzai Telegram Plugin](../../../Yunzai-Telegram-Plugin)
+
-
+
-Discord
+插件
-[TRSS-Yunzai Discord Plugin](../../../Yunzai-Discord-Plugin)
+- [ICQQ](../../../Yunzai-ICQQ-Plugin)
+- [QQBot](../../../Yunzai-QQBot-Plugin)
+- [WeChat](../../../Yunzai-WeChat-Plugin)
+- [KOOK](../../../Yunzai-KOOK-Plugin)
+- [Telegram](../../../Yunzai-Telegram-Plugin)
+- [Discord](../../../Yunzai-Discord-Plugin)
+- [Route](../../../Yunzai-Route-Plugin)
+- [Lagrange](../../../Yunzai-Lagrange-Plugin)
-OPQBot
-
-下载运行 [OPQBot](https://opqbot.com),启动参数添加:
+6. 设置主人:发送 `#设置主人`,日志获取验证码并发送
-```
--wsserver ws://localhost:2536/OPQBot
-```
+7. 使用 [pm2](https://pm2.keymetrics.io) 后台运行
-
+| 操作 | 命令 |
+| ---- | ---- |
+| 启动 | pnpm start |
+| 停止 | pnpm stop |
+| 日志 | pnpm log |
-路由
+8. 开机自启
-[TRSS-Yunzai Route Plugin](../../../Yunzai-Route-Plugin)
+```sh
+pnpm start
+pnpm pm2 save
+pnpm pm2 startup
+```
-
+## 班级群(¿
-6.设置主人:发送 `#设置主人`,后台日志获取验证码并发送
+1. [用户(897643592)](https://qm.qq.com/q/7NxbviGbj)
+2. [开发者(833565573)](https://qm.qq.com/q/oFJR8VVECA)
+3. [机器人(907431599)](https://qm.qq.com/q/oCBOrfE29U)
## 致谢
-| Nickname | Contribution |
-| :-----------------------------------------------------------: | -------------------- |
-| [Yunzai-Bot](../../../../Le-niao/Yunzai-Bot) | 乐神的 Yunzai-Bot |
-| [Miao-Yunzai](../../../../yoimiya-kokomi/Miao-Yunzai) | 喵喵的 Miao-Yunzai |
\ No newline at end of file
+| Nickname | Contribution |
+| -------- | ------------ |
+| [Yunzai-Bot](../../../../Le-niao/Yunzai-Bot) | 乐神的 Yunzai-Bot |
+| [Miao-Yunzai](../../../../yoimiya-kokomi/Miao-Yunzai) | 喵喵的 Miao-Yunzai |
\ No newline at end of file
diff --git a/Yunzai/app.js b/Yunzai/app.js
index 6feed2aa2a5f229da43a369ed249523cfdfe5cfc..afe51337922f0b71a101ed2aafbaaccf7260fef7 100644
--- a/Yunzai/app.js
+++ b/Yunzai/app.js
@@ -1,3 +1,22 @@
-import Yunzai from "./lib/bot.js"
-global.Bot = new Yunzai
-Bot.run()
\ No newline at end of file
+switch (process.env.app_type || process.argv[2]) {
+ case "pm2":
+ case "start": {
+ global.Bot = new (await import("./lib/bot.js")).default
+ Bot.run()
+ break
+ } case "stop": {
+ const cfg = (await import("./lib/config/config.js")).default
+ const fetch = (await import("node-fetch")).default
+ try {
+ await fetch(`http://localhost:${cfg.bot.port}/exit`)
+ } catch (err) {}
+ process.exit()
+ } default: {
+ const { spawnSync } = await import("node:child_process")
+ while (!spawnSync(process.argv[0],
+ [process.argv[1], "start"],
+ { stdio: "inherit" },
+ ).status) {}
+ process.exit()
+ }
+}
\ No newline at end of file
diff --git a/Yunzai/config/default_config/bot.yaml b/Yunzai/config/default_config/bot.yaml
index 86c6cf12f0f5539f444993fc96ed9433e3a10986..dd72c8ef8501efa09f53b6727328795a42773fc7 100644
--- a/Yunzai/config/default_config/bot.yaml
+++ b/Yunzai/config/default_config/bot.yaml
@@ -1,9 +1,30 @@
-# 日志等级:trace,debug,info,warn,fatal,mark,error,off
-# mark时只显示执行命令,不显示聊天记录
+# 日志等级 trace,debug,info,warn,fatal,mark,error,off
log_level: info
+# 单条日志长度
+log_length: 10000
+# 对象日志格式
+log_object: true
+
+# 服务器地址
+url: http://localhost:2536
# 服务器端口
port: 2536
+# 自动更新时间
+update_time: 1440
+# 自动重启时间
+restart_time: 0
+# 上线推送通知的冷却时间
+online_msg_exp: 1440
+# 文件保存时间
+file_to_url_time: 1
+# 文件访问次数
+file_to_url_times:
+# 消息类型统计
+msg_type_count: false
+# 以/开头转为#
+/→#: true
+
# chromium其他路径
chromium_path:
# puppeteer接口地址
@@ -12,12 +33,4 @@ puppeteer_ws:
puppeteer_timeout:
# 米游社接口代理地址,国际服用
-proxyAddress:
-
-# 上线时给主人推送帮助
-online_msg: true
-# 上线推送通知的冷却时间
-online_msg_exp: 86400
-
-# 单条日志长度
-logLength: 1000
\ No newline at end of file
+proxyAddress:
\ No newline at end of file
diff --git a/Yunzai/config/default_config/group.yaml b/Yunzai/config/default_config/group.yaml
index f67819aaee9ca71f02f458898e06e52b1c5b0636..5a4fb1a7c6cfab99f54641cfd4d85a6d5cf1b014 100644
--- a/Yunzai/config/default_config/group.yaml
+++ b/Yunzai/config/default_config/group.yaml
@@ -3,7 +3,7 @@ default:
groupCD: 500 # 群聊中所有指令操作冷却时间,单位毫秒,0则无限制
singleCD: 2000 # 群聊中个人操作冷却时间,单位毫秒
- onlyReplyAt: 0 # 是否只仅关注主动提及Bot的消息 0-否 1-是
+ onlyReplyAt: 0 # 是否只仅关注主动提及Bot的消息 0-否 1-是 2-非主人
botAlias: # 开启后则只回复提及Bot的消息及特定前缀的消息
- 云崽
- 云宝
diff --git a/Yunzai/config/default_config/other.yaml b/Yunzai/config/default_config/other.yaml
index 96810b376a6f70f070b6b8af25b7efe39ae0f603..69e19962c460b43cfaad57192468eef57e885a14 100644
--- a/Yunzai/config/default_config/other.yaml
+++ b/Yunzai/config/default_config/other.yaml
@@ -16,12 +16,14 @@ disableMsg: "私聊功能已禁用,仅支持发送cookie,抽卡记录链接
# 私聊通行字符串
disableAdopt:
- stoken
-#白名单群,配置后只在该群生效
-whiteGroup:
+#白名单群
+whiteGroup:
+#白名单用户
+whiteUser:
#黑名单群
blackGroup:
- 213938015
-#黑名单账号
-blackQQ:
+#黑名单用户
+blackUser:
- 528952540
\ No newline at end of file
diff --git a/Yunzai/config/pm2.yaml b/Yunzai/config/pm2.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..3622e75bf89a21d30b6c7173c981dfe41c66800c
--- /dev/null
+++ b/Yunzai/config/pm2.yaml
@@ -0,0 +1,7 @@
+apps:
+ - name: TRSS-Yunzai
+ script: ./app.js
+ max_memory_restart: 512M
+ restart_delay: 60000
+ env:
+ app_type: pm2
\ No newline at end of file
diff --git a/Yunzai/lib/bot.js b/Yunzai/lib/bot.js
index dba055ada3d6ecd751c6355406551048dcd1b2c0..ca19feb18b4a4e21ca5f658760e9ba4973962604 100644
--- a/Yunzai/lib/bot.js
+++ b/Yunzai/lib/bot.js
@@ -1,74 +1,103 @@
import "./config/init.js"
import cfg from "./config/config.js"
+import redisInit from "./config/redis.js"
import PluginsLoader from "./plugins/loader.js"
import ListenerLoader from "./listener/loader.js"
import { EventEmitter } from "events"
import express from "express"
-import http from "http"
+import http from "node:http"
import { WebSocketServer } from "ws"
import _ from "lodash"
-import fs from "node:fs"
+import fs from "node:fs/promises"
+import path from "node:path"
+import util from "node:util"
import fetch from "node-fetch"
import { randomUUID } from "node:crypto"
-import { exec } from "child_process"
+import { exec } from "node:child_process"
import { fileTypeFromBuffer } from "file-type"
import md5 from "md5"
+import { Level } from "level"
export default class Yunzai extends EventEmitter {
constructor() {
super()
+ this.stat = { start_time: Date.now()/1000 }
this.uin = []
this.adapter = []
this.express = express()
+ for (const i of ["urlencoded", "json", "raw", "text"])
+ this.express.use(express[i]({ extended: false }))
+ this.express.use(req => {
+ req.rid = `${req.ip}:${req.socket.remotePort}`
+ req.sid = `${req.protocol}://${req.hostname}:${req.socket.localPort}${req.originalUrl}`
+ this.makeLog("mark", ["HTTP", req.method, "请求", req.headers, req.query, req.body], `${req.sid} <= ${req.rid}`)
+ req.next()
+ })
+ this.express.use("/exit", req => {
+ if (["::1", "::ffff:127.0.0.1"].includes(req.ip) || req.hostname == "localhost")
+ process.exit(1)
+ })
this.server = http.createServer(this.express)
+ this.server.on("error", err => {
+ if (typeof this[`server${err.code}`] == "function")
+ return this[`server${err.code}`](err)
+ this.makeLog("error", err, "Server")
+ })
this.server.on("upgrade", (...args) => this.wsConnect(...args))
this.wss = new WebSocketServer({ noServer: true })
- this.wsf = {}
+ this.wsf = Object.create(null)
- this.fs = {}
+ this.fs = Object.create(null)
this.express.use("/File", (...args) => this.fileSend(...args))
- this.fileToUrl("resources/http/File/404.jpg", { name: 404, time: 0 })
- this.fileToUrl("resources/http/File/timeout.jpg", { name: "timeout", time: 0 })
+ for (const name of [404, "timeout"])
+ this.fileToUrl(`resources/http/File/${name}.jpg`, { name, time: false, times: false })
}
wsConnect(req, socket, head) {
this.wss.handleUpgrade(req, socket, head, conn => {
- conn.id = `${req.connection.remoteAddress}-${req.headers["sec-websocket-key"]}`
- this.makeLog("mark", `${logger.blue(`[${conn.id} <=> ws://${req.headers.host}${req.url}]`)} 建立连接:${JSON.stringify(req.headers)}`)
- conn.on("error", logger.error)
- conn.on("close", () => this.makeLog("mark", `${logger.blue(`[${conn.id} <≠> ws://${req.headers.host}${req.url}]`)} 断开连接`))
- conn.on("message", msg => this.makeLog("debug", `${logger.blue(`[${conn.id} => ws://${req.headers.host}${req.url}]`)} 消息:${String(msg).trim()}`))
+ conn.rid = `${req.socket.remoteAddress}:${req.socket.remotePort}-${req.headers["sec-websocket-key"]}`
+ conn.sid = `ws://${req.headers["x-forwarded-host"] || req.headers.host || `${req.socket.localAddress}:${req.socket.localPort}`}${req.url}`
+ this.makeLog("mark", ["建立连接", req.headers], `${conn.sid} <=> ${conn.rid}`)
+ conn.on("error", (...args) => this.makeLog("error", args, `${conn.sid} <=> ${conn.rid}`))
+ conn.on("close", () => this.makeLog("mark", "断开连接", `${conn.sid} <≠> ${conn.rid}`))
+ conn.on("message", msg => this.makeLog("debug", ["消息", this.String(msg)], `${conn.sid} <= ${conn.rid}`))
conn.sendMsg = msg => {
if (!Buffer.isBuffer(msg)) msg = this.String(msg)
- this.makeLog("debug", `${logger.blue(`[${conn.id} <= ws://${req.headers.host}${req.url}]`)} 消息:${msg}`)
+ this.makeLog("debug", ["消息", msg], `${conn.sid} => ${conn.rid}`)
return conn.send(msg)
}
- for (const i of this.wsf[req.url.split("/")[1]] || [])
+ for (const i of this.wsf[req.url.split("/")[1]] || [() => conn.terminate()])
i(conn, req, socket, head)
})
}
- serverLoad() {
- this.express.use(req => {
- logger.mark(`${logger.blue(`[${req.ip} => http://${req.headers.host}${req.url}]`)} HTTP ${req.method} 请求:${JSON.stringify(req.headers)}`)
- req.res.redirect("https://github.com/TimeRainStarSky/Yunzai")
- })
+ async serverEADDRINUSE(err) {
+ this.makeLog("error", ["监听端口", cfg.bot.port, "错误", err], "Server")
+ try {
+ await fetch(`http://localhost:${cfg.bot.port}/exit`)
+ } catch (err) {}
+ this.server_listen_time = (this.server_listen_time || 0) + 1
+ await this.sleep(this.server_listen_time * 1000)
+ this.server.listen(cfg.bot.port)
+ }
- this.server.listen(cfg.bot.port, () => {
- logger.mark(`启动 HTTP 服务器:${logger.green(`http://[${this.server.address().address}]:${this.server.address().port}`)}`)
- const url = cfg.bot.url.replace(/^http/, "ws")
- for (const i of Object.keys(this.wsf))
- logger.info(`${i} 连接地址:${logger.blue(`${url}/${i}`)}`)
- })
+ async serverLoad() {
+ this.server.listen(cfg.bot.port)
+ await new Promise(resolve => this.server.once("listening", resolve))
+ this.makeLog("mark", ["启动 HTTP 服务器", logger.green(`http://[${this.server.address().address}]:${this.server.address().port}`)], "Server")
}
async run() {
+ await redisInit()
+ await this.serverLoad()
await import("./plugins/stdin.js")
await PluginsLoader.load()
await ListenerLoader.load()
- this.serverLoad()
+
+ this.express.use(req => req.res.redirect("https://github.com/TimeRainStarSky/Yunzai"))
+ this.makeLog("info", `连接地址:${logger.blue(`${cfg.bot.url.replace(/^http/, "ws")}/`)}${logger.cyan(`[${Object.keys(this.wsf)}]`)}`, "WebSocket")
this.emit("online", this)
}
@@ -76,17 +105,195 @@ export default class Yunzai extends EventEmitter {
return new Promise(resolve => setTimeout(resolve, time))
}
+ async fsStat(path) { try {
+ const stat = await fs.stat(path)
+ return stat
+ } catch (err) {
+ this.makeLog("trace", ["获取", path, "状态错误", err])
+ return false
+ }}
+
+ async mkdir(dir) { try {
+ if (await this.fsStat(dir)) return true
+ if (!await this.mkdir(path.dirname(dir))) return false
+ await fs.mkdir(dir)
+ return true
+ } catch (err) {
+ this.makeLog("error", ["创建", dir, "错误", err])
+ return false
+ }}
+
+ async rmdir(dir) { try {
+ if (!await this.fsStat(dir)) return true
+ for (const i of await fs.readdir(dir))
+ await this.rm(`${dir}/${i}`)
+ await fs.rmdir(dir)
+ return true
+ } catch (err) {
+ this.makeLog("error", ["删除", dir, "错误", err])
+ return false
+ }}
+
+ async rm(file) { try {
+ const stat = await this.fsStat(file)
+ if (!stat) return true
+ if (stat.isDirectory())
+ return this.rmdir(file)
+ await fs.unlink(file)
+ return true
+ } catch (err) {
+ this.makeLog("error", ["删除", file, "错误", err])
+ return false
+ }}
+
+ async download(url, file) {
+ let buffer
+ if (!file || (await this.fsStat(file))?.isDirectory?.()) {
+ const type = await this.fileType(url)
+ file = file ? path.join(file, type.name) : type.name
+ buffer = type.buffer
+ } else {
+ await this.mkdir(path.dirname(file))
+ buffer = await this.Buffer(url)
+ }
+ await fs.writeFile(file, buffer)
+ return file
+ }
+
+ makeMap(parent_map, parent_key, map) {
+ const save = async () => { try {
+ await parent_map.db.put(parent_key, { map_array: Array.from(map) })
+ } catch (err) {
+ this.makeLog("error", ["写入", parent_map.db.location, parent_key, "错误", map, err])
+ }}
+
+ const set = map.set.bind(map)
+ Object.defineProperty(map, "set", {
+ value: async (key, value) => {
+ if (JSON.stringify(map.get(key)) != JSON.stringify(value)) {
+ set(key, value)
+ await save()
+ }
+ return map
+ },
+ })
+ const del = map.delete.bind(map)
+ Object.defineProperty(map, "delete", {
+ value: async key => {
+ if (!del(key)) return false
+ await save()
+ return true
+ },
+ })
+ return map
+ }
+
+ async setMap(map, set, key, value) {
+ try {
+ if (value instanceof Map) {
+ set(key, this.makeMap(map, key, value))
+ await map.db.put(key, { map_array: Array.from(value) })
+ } else if (JSON.stringify(map.get(key)) != JSON.stringify(value)) {
+ set(key, value)
+ await map.db.put(key, value)
+ }
+ } catch (err) {
+ this.makeLog("error", ["写入", map.db.location, key, "错误", value, err])
+ }
+ return map
+ }
+
+ async delMap(map, del, key) {
+ if (!del(key)) return false
+ try {
+ await map.db.del(key)
+ } catch (err) {
+ this.makeLog("error", ["删除", map.db.location, key, "错误", err])
+ }
+ return true
+ }
+
+ async importMap(dir, map) {
+ for (const i of await fs.readdir(dir)) {
+ const path = `${dir}/${i}`
+ try {
+ await map.set(i, (await this.fsStat(path)).isDirectory() ?
+ await this.importMap(path, new Map) :
+ JSON.parse(await fs.readFile(path, "utf-8")))
+ } catch (err) {
+ this.makeLog("error", ["读取", path, "错误", err])
+ }
+ await this.rm(path)
+ }
+ await this.rm(dir)
+ return map
+ }
+
+ async getMap(dir) {
+ const map = new Map()
+ const db = new Level(`${dir}-leveldb`, { valueEncoding: "json" })
+ try {
+ await db.open()
+ for await (let [key, value] of db.iterator()) {
+ if (typeof value == "object" && value.map_array)
+ value = this.makeMap(map, key, new Map(value.map_array))
+ map.set(key, value)
+ }
+ } catch (err) {
+ this.makeLog("error", ["打开", dir, "数据库错误", err])
+ return map
+ }
+
+ Object.defineProperty(map, "db", { value: db })
+ const set = map.set.bind(map)
+ Object.defineProperty(map, "set", {
+ value: (key, value) => this.setMap(map, set, key, value),
+ })
+ const del = map.delete.bind(map)
+ Object.defineProperty(map, "delete", {
+ value: key => this.delMap(map, del, key),
+ })
+
+ if (await this.fsStat(dir))
+ await this.importMap(dir, map)
+ return map
+ }
+
String(data) {
switch (typeof data) {
case "string":
return data
case "object":
- return JSON.stringify(data)
+ if (data instanceof Error)
+ return data.stack
+ if (Buffer.isBuffer(data))
+ return String(data)
}
- return String(data)
+ return JSON.stringify(data)
+ }
+
+ Loging(data) {
+ if (typeof data == "string") return data
+ if (!cfg.bot.log_object && typeof data == "object")
+ if (typeof data.toString == "function")
+ return String(data)
+ else
+ return "[object null]"
+
+ return util.inspect(data, {
+ depth: null,
+ colors: true,
+ showHidden: true,
+ showProxy: true,
+ getters: true,
+ breakLength: 100,
+ maxArrayLength: 100,
+ maxStringLength: 1000,
+ ...cfg.bot.log_object,
+ })
}
- Buffer(data, opts = {}) {
+ async Buffer(data, opts = {}) {
if (Buffer.isBuffer(data)) return data
data = this.String(data)
@@ -94,43 +301,48 @@ export default class Yunzai extends EventEmitter {
return Buffer.from(data.replace(/^base64:\/\//, ""), "base64")
} else if (data.match(/^https?:\/\//)) {
if (opts.http) return data
- return (async () => Buffer.from(await (await fetch(data)).arrayBuffer()))()
- } else if (fs.existsSync(data.replace(/^file:\/\//, ""))) {
+ return Buffer.from(await (await fetch(data)).arrayBuffer())
+ } else if (await this.fsStat(data.replace(/^file:\/\//, ""))) {
if (opts.file) return data
- return Buffer.from(fs.readFileSync(data.replace(/^file:\/\//, "")))
+ return Buffer.from(await fs.readFile(data.replace(/^file:\/\//, "")))
}
return data
}
- async fileType(data, name) {
- const file = {}
+ async fileType(data, opts = {}) {
+ const file = { name: data.name }
try {
- if (Buffer.isBuffer(data)) {
- file.url = name || "Buffer"
- file.buffer = data
+ if (Buffer.isBuffer(data.file)) {
+ file.url = data.name || "Buffer"
+ file.buffer = data.file
} else {
- file.url = data.replace(/^base64:\/\/.*/, "base64://...")
- file.buffer = await this.Buffer(data)
+ file.url = data.file.replace(/^base64:\/\/.*/, "base64://...")
+ file.buffer = await this.Buffer(data.file, opts)
}
if (Buffer.isBuffer(file.buffer)) {
file.type = await fileTypeFromBuffer(file.buffer)
file.md5 = md5(file.buffer)
- file.name = name || `${Date.now()}.${file.md5.slice(0,8)}.${file.type.ext}`
- } else {
- file.name = name || `${Date.now()}-path.basename(file.buffer)`
+ if (!file.name)
+ file.name = `${Date.now()}.${file.md5.slice(0,8)}.${file.type.ext}`
}
} catch (err) {
- logger.error(`文件类型检测错误:${logger.red(err)}`)
+ this.makeLog("error", ["文件类型检测错误", file, err])
}
+ if (!file.name)
+ file.name = `${Date.now()}-${path.basename(file.url)}`
return file
}
async fileToUrl(file, opts = {}) {
- const { name, time = 60000, times } = opts
+ const {
+ name,
+ time = cfg.bot.file_to_url_time*60000,
+ times = cfg.bot.file_to_url_times,
+ } = opts
- file = await this.fileType(file, name)
+ file = await this.fileType({ file, name }, { http: true })
if (!Buffer.isBuffer(file.buffer)) return file.buffer
- if (!file.name) file.name = randomUUID()
+ file.name = file.name ? encodeURIComponent(file.name) : randomUUID()
if (typeof times == "number") file.times = times
this.fs[file.name] = file
@@ -144,25 +356,24 @@ export default class Yunzai extends EventEmitter {
if (!file) file = this.fs[404]
if (typeof file.times == "number") {
- if (file.times > 0) file.times = file.times-1
+ if (file.times > 0) file.times--
else file = this.fs.timeout
}
if (file.type?.mime)
req.res.setHeader("Content-Type", file.type.mime)
- logger.mark(`${logger.blue(`[${req.ip} => http://${req.headers.host}/File/${url}]`)} HTTP ${req.method} 请求:${JSON.stringify(req.headers)}`)
- logger.mark(`${logger.blue(`[${req.ip} <= http://${req.headers.host}/File/${url}]`)} 发送文件:${file.name}(${file.url} ${(file.buffer.length/1024).toFixed(2)}KB)`)
+ this.makeLog("mark", `发送文件:${file.name}(${file.url} ${(file.buffer.length/1024).toFixed(2)}KB)`, `${req.sid} => ${req.rid}`)
req.res.send(file.buffer)
}
async exec(cmd) {
return new Promise(resolve => {
- this.makeLog("mark", `[命令执行开始] ${logger.blue(cmd)}`)
+ this.makeLog("mark", `[执行命令] ${logger.blue(cmd)}`)
exec(cmd, (error, stdout, stderr) => {
- this.makeLog("mark", `[命令执行完成] ${logger.blue(cmd)}${stdout?`\n${this.String(stdout).trim()}`:""}${stderr?logger.red(`\n${this.String(stderr).trim()}`):""}`)
- if (error) this.makeLog("mark", `[命令执行错误] ${logger.blue(cmd)}\n${logger.red(this.String(error).trim())}`)
resolve({ error, stdout, stderr })
+ this.makeLog("mark", `[执行命令完成] ${logger.blue(cmd)}${stdout?`\n${String(stdout).trim()}`:""}${stderr?logger.red(`\n${String(stderr).trim()}`):""}`)
+ if (error) this.makeLog("error", `[执行命令错误] ${logger.blue(cmd)}\n${logger.red(this.Loging(error).trim())}`)
})
})
}
@@ -170,18 +381,50 @@ export default class Yunzai extends EventEmitter {
makeLog(level, msg, id) {
const log = []
if (id) log.push(logger.blue(`[${id}]`))
- for (const i of Array.isArray(msg) ? msg : [msg]) {
- if (i?.replace)
- log.push(_.truncate(i.replace(/{"type":"Buffer","data":\[.*?\]}/g, "(Buffer)"), { length: cfg.bot.log_length }))
- else
- log.push(i)
- }
+ for (const i of Array.isArray(msg) ? msg : [msg])
+ log.push(_.truncate(this.Loging(i), { length: cfg.bot.log_length }))
logger[level](...log)
}
+ makeEvent(data) {
+ if (!this[data.self_id]) return
+ if (!data.bot)
+ Object.defineProperty(data, "bot", {
+ value: this[data.self_id],
+ })
+ if (!data.friend && data.user_id)
+ Object.defineProperty(data, "friend", {
+ value: data.bot.pickFriend(data.user_id),
+ })
+ if (!data.group && data.group_id)
+ Object.defineProperty(data, "group", {
+ value: data.bot.pickGroup(data.group_id),
+ })
+ if (!data.member && data.group && data.user_id)
+ Object.defineProperty(data, "member", {
+ value: data.group.pickMember(data.user_id),
+ })
+
+ if (data.bot.adapter?.id)
+ data.adapter_id = data.bot.adapter.id
+ if (data.bot.adapter?.name)
+ data.adapter_name = data.bot.adapter.name
+
+ for (const i of [data.friend, data.group, data.member]) {
+ if (typeof i != "object") continue
+ if (!i.sendFile)
+ i.sendFile = (file, name) => i.sendMsg(segment.file(file, name))
+ if (!i.makeForwardMsg)
+ i.makeForwardMsg = this.makeForwardMsg
+ if (!i.sendForwardMsg)
+ i.sendForwardMsg = msg => this.sendForwardMsg(msg => i.sendMsg(msg), msg)
+ if (!i.getInfo)
+ i.getInfo = () => i
+ }
+ }
+
em(name = "", data = {}) {
- if (data.self_id)
- Object.defineProperty(data, "bot", { value: Bot[data.self_id] })
+ this.makeEvent(data)
while (true) {
this.emit(name, data)
const i = name.lastIndexOf(".")
@@ -251,7 +494,7 @@ export default class Yunzai extends EventEmitter {
user_id = Number(user_id) || String(user_id)
const user = this.fl.get(user_id)
if (user) return this[user.bot_id].pickFriend(user_id)
- logger.error(`获取用户对象失败:找不到用户 ${logger.red(user_id)}`)
+ this.makeLog("error", ["获取用户对象错误:找不到用户", user_id])
}
get pickUser() { return this.pickFriend }
@@ -259,7 +502,7 @@ export default class Yunzai extends EventEmitter {
group_id = Number(group_id) || String(group_id)
const group = this.gl.get(group_id)
if (group) return this[group.bot_id].pickGroup(group_id)
- logger.error(`获取群对象失败:找不到群 ${logger.red(group_id)}`)
+ this.makeLog("error", ["获取群对象错误:找不到群", group_id])
}
pickMember(group_id, user_id) {
@@ -279,7 +522,7 @@ export default class Yunzai extends EventEmitter {
this.once(`connect.${bot_id}`, data =>
resolve(data.bot.pickFriend(user_id).sendMsg(msg))))
} catch (err) {
- logger.error(`${logger.blue(`[${bot_id}]`)} 发送好友消息失败:[$${user_id}] ${err}`)
+ this.makeLog("error", [`发送好友消息错误:[${user_id}]`, err], bot_id)
}
return false
}
@@ -296,7 +539,7 @@ export default class Yunzai extends EventEmitter {
this.once(`connect.${bot_id}`, data =>
resolve(data.bot.pickGroup(group_id).sendMsg(msg))))
} catch (err) {
- logger.error(`${logger.blue(`[${bot_id}]`)} 发送群消息失败:[$${group_id}] ${err}`)
+ this.makeLog("error", [`发送群消息错误:[${group_id}]`, err], bot_id)
}
return false
}
@@ -307,13 +550,13 @@ export default class Yunzai extends EventEmitter {
fnc = data => data.self_id == self_id && data.user_id == user_id
}
- while (true) {
+ while (true) { try {
const msg = await new Promise(resolve => {
this.once("message", data => {
if (data.message && fnc(data)) {
let msg = ""
for (const i of data.message)
- if (i.type = "text")
+ if (i.type == "text" && i.text)
msg += i.text.trim()
resolve(msg)
} else {
@@ -322,7 +565,9 @@ export default class Yunzai extends EventEmitter {
})
})
if (msg) return msg
- }
+ } catch (err) {
+ this.makeLog("error", err)
+ }}
}
getMasterMsg() {
@@ -338,10 +583,31 @@ export default class Yunzai extends EventEmitter {
makeForwardMsg(msg) { return { type: "node", data: msg } }
+ makeForwardArray(msg = [], node = {}) {
+ const forward = []
+ for (const message of Array.isArray(msg) ? msg : [msg])
+ forward.push({ ...node, message })
+ return this.makeForwardMsg(forward)
+ }
+
async sendForwardMsg(send, msg) {
const messages = []
for (const { message } of msg)
messages.push(await send(message))
return messages
}
+
+ getTimeDiff(time1 = this.stat.start_time, time2 = Date.now()/1000) {
+ const time = time2 - time1
+ let ret = ""
+ const day = Math.floor(time / 3600 / 24)
+ if (day) ret += `${day}天`
+ const hour = Math.floor((time / 3600) % 24)
+ if (hour) ret += `${hour}时`
+ const min = Math.floor((time / 60) % 60)
+ if (min) ret += `${min}分`
+ const sec = Math.floor(time % 60)
+ if (sec) ret += `${sec}秒`
+ return ret || "0秒"
+ }
}
\ No newline at end of file
diff --git a/Yunzai/lib/common/common.js b/Yunzai/lib/common/common.js
index 90a2605fa512acb3da18114394d596c42ac0168e..dc47b98c7dd150fa2d15d647b1edaff2401bde05 100644
--- a/Yunzai/lib/common/common.js
+++ b/Yunzai/lib/common/common.js
@@ -41,13 +41,10 @@ async function downFile(fileUrl, savePath,param = {}) {
}
function mkdirs(dirname) {
- if (fs.existsSync(dirname)) {
+ if (fs.existsSync(dirname)) return true
+ if (mkdirs(path.dirname(dirname))) {
+ fs.mkdirSync(dirname)
return true
- } else {
- if (mkdirs(path.dirname(dirname))) {
- fs.mkdirSync(dirname)
- return true
- }
}
}
diff --git a/Yunzai/lib/config/config.js b/Yunzai/lib/config/config.js
index fb315c44fe2b78989181e9d36183628cdce49f7a..fd00abf3a753596bb102980c111db848aa78bb3e 100644
--- a/Yunzai/lib/config/config.js
+++ b/Yunzai/lib/config/config.js
@@ -4,7 +4,7 @@ import chokidar from "chokidar"
/** 配置文件 */
class Cfg {
- constructor () {
+ constructor() {
this.config = {}
/** 监听文件 */
@@ -14,7 +14,7 @@ class Cfg {
}
/** 初始化配置 */
- initCfg () {
+ initCfg() {
let path = "config/config/"
let pathDef = "config/default_config/"
const files = fs.readdirSync(pathDef).filter(file => file.endsWith(".yaml"))
@@ -22,11 +22,12 @@ class Cfg {
if (!fs.existsSync(`${path}${file}`))
fs.copyFileSync(`${pathDef}${file}`, `${path}${file}`)
for (const i of ["data", "temp"])
- if (!fs.existsSync(i)) fs.mkdirSync(i)
+ if (!fs.existsSync(i))
+ fs.mkdirSync(i)
}
/** Bot配置 */
- get bot () {
+ get bot() {
let bot = this.getConfig("bot")
let defbot = this.getdefSet("bot")
bot = { ...defbot, ...bot }
@@ -34,11 +35,11 @@ class Cfg {
return bot
}
- get other () {
+ get other() {
return this.getConfig("other")
}
- get redis () {
+ get redis() {
return this.getConfig("redis")
}
@@ -47,7 +48,7 @@ class Cfg {
}
/** 主人账号 */
- get masterQQ () {
+ get masterQQ() {
let masterQQ = this.getConfig("other").masterQQ || []
if (!Array.isArray(masterQQ))
@@ -60,7 +61,7 @@ class Cfg {
}
/** Bot账号:[主人帐号] */
- get master () {
+ get master() {
let master = this.getConfig("other").master || []
if (!Array.isArray(master))
@@ -80,15 +81,15 @@ class Cfg {
}
/** 机器人账号 */
- get uin () {
+ get uin() {
return Object.keys(this.master)
}
- get qq () {
+ get qq() {
return this.uin
}
/** package.json */
- get package () {
+ get package() {
if (this._package) return this._package
this._package = JSON.parse(fs.readFileSync("package.json", "utf8"))
@@ -96,7 +97,7 @@ class Cfg {
}
/** 群配置 */
- getGroup (bot_id = "", group_id = "") {
+ getGroup(bot_id = "", group_id = "") {
const config = this.getConfig("group")
const defCfg = this.getdefSet("group")
return {
@@ -109,7 +110,7 @@ class Cfg {
}
/** other配置 */
- getOther () {
+ getOther() {
let def = this.getdefSet("other")
let config = this.getConfig("other")
return { ...def, ...config }
@@ -119,12 +120,12 @@ class Cfg {
* @param app 功能
* @param name 配置文件名称
*/
- getdefSet (name) {
+ getdefSet(name) {
return this.getYaml("default_config", name)
}
/** 用户配置 */
- getConfig (name) {
+ getConfig(name) {
return this.getYaml("config", name)
}
@@ -133,7 +134,7 @@ class Cfg {
* @param type 默认跑配置-defSet,用户配置-config
* @param name 名称
*/
- getYaml (type, name) {
+ getYaml(type, name) {
let file = `config/${type}/${name}.yaml`
let key = `${type}.${name}`
if (this.config[key]) return this.config[key]
@@ -148,7 +149,7 @@ class Cfg {
}
/** 监听配置文件 */
- watch (file, name, type = "default_config") {
+ watch(file, name, type = "default_config") {
let key = `${type}.${name}`
if (this.watcher[key]) return
@@ -166,7 +167,7 @@ class Cfg {
this.watcher[key] = watcher
}
- async change_bot () {
+ async change_bot() {
/** 修改日志等级 */
let log = await import("./log.js")
log.default()
diff --git a/Yunzai/lib/config/init.js b/Yunzai/lib/config/init.js
index c0842fba171224efd4fec042ecbbe319000a8b71..a1005ed23b4bf994962764d228e1733313d88fb4 100644
--- a/Yunzai/lib/config/init.js
+++ b/Yunzai/lib/config/init.js
@@ -1,6 +1,4 @@
import setLog from "./log.js"
-import redisInit from "./redis.js"
-import { checkRun } from "./check.js"
import cfg from "./config.js"
/** 设置标题 */
@@ -9,41 +7,30 @@ process.title = `TRSS Yunzai v${cfg.package.version} © 2023 - 2024 TimeRainStar
/** 设置时区 */
process.env.TZ = "Asia/Shanghai"
-/** 捕获未处理的错误 */
-process.on("uncaughtException", error => {
- if (typeof logger == "undefined") console.log(error)
- else logger.error(error)
-})
-
-/** 捕获未处理的Promise错误 */
-process.on("unhandledRejection", (error, promise) => {
- if (typeof logger == "undefined") console.log(error)
- else logger.error(error)
-})
-
-/** 退出事件 */
-process.on("exit", async code => {
- if (typeof redis != "undefined" && typeof test == "undefined")
- await redis.save()
-
- if (typeof logger == "undefined")
- console.log("TRSS-Yunzai 已停止运行")
- else
- logger.mark(logger.magenta("TRSS-Yunzai 已停止运行"))
-})
+process.on("SIGHUP", () => process.exit())
-await checkInit()
+/** 日志设置 */
+setLog()
-/** 初始化事件 */
-async function checkInit() {
- /** 日志设置 */
- setLog()
+/** 捕获未处理的错误 */
+for (const i of ["uncaughtException", "unhandledRejection"])
+ process.on(i, e => {
+ try {
+ Bot.makeLog("error", e, i)
+ } catch (err) {
+ console.error(i, e, err)
+ process.exit()
+ }
+ })
- logger.mark("----^_^----")
- logger.mark(logger.yellow(`TRSS-Yunzai v${cfg.package.version} 启动中...`))
- logger.mark(logger.cyan("https://github.com/TimeRainStarSky/Yunzai"))
+/** 退出事件 */
+process.on("exit", code => {
+ if (typeof redis != "undefined")
+ redis.save()
- await redisInit()
+ logger.mark(logger.magenta(`TRSS-Yunzai 已停止运行,本次运行时长:${Bot.getTimeDiff()} (${code})`))
+})
- checkRun()
-}
\ No newline at end of file
+logger.mark("----^_^----")
+logger.mark(logger.yellow(`TRSS-Yunzai v${cfg.package.version} 启动中...`))
+logger.mark(logger.cyan("https://github.com/TimeRainStarSky/Yunzai"))
\ No newline at end of file
diff --git a/Yunzai/lib/config/log.js b/Yunzai/lib/config/log.js
index e957e5261db637918df4afc640dcd90a885b556a..698d8c14dc0cfe6f3a85871f954c2fd9afe4d1ce 100644
--- a/Yunzai/lib/config/log.js
+++ b/Yunzai/lib/config/log.js
@@ -1,16 +1,15 @@
-import log4js from 'log4js'
-import chalk from 'chalk'
-import cfg from './config.js'
-import fs from 'node:fs'
+import log4js from "log4js"
+import chalk from "chalk"
+import cfg from "./config.js"
+import fs from "node:fs"
/**
* 设置日志样式
*/
-export default function setLog () {
- let file = './logs'
- if (!fs.existsSync(file)) {
+export default function setLog() {
+ let file = "./logs"
+ if (!fs.existsSync(file))
fs.mkdirSync(file)
- }
/** 调整error日志等级 */
// log4js.levels.levels[5].level = Number.MAX_VALUE
@@ -19,80 +18,59 @@ export default function setLog () {
log4js.configure({
appenders: {
console: {
- type: 'console',
+ type: "console",
layout: {
- type: 'pattern',
- pattern: '%[[TRSSYz][%d{hh:mm:ss.SSS}][%4.4p]%] %m'
+ type: "pattern",
+ pattern: "%[[TRSSYz][%d{hh:mm:ss.SSS}][%4.4p]%] %m"
}
},
command: {
- type: 'dateFile', // 可以是console,dateFile,file,Logstash等
- filename: 'logs/command', // 将会按照filename和pattern拼接文件名
- pattern: 'yyyy-MM-dd.log',
+ type: "dateFile", // 可以是console,dateFile,file,Logstash等
+ filename: "logs/command", // 将会按照filename和pattern拼接文件名
+ pattern: "yyyy-MM-dd.log",
numBackups: 15,
alwaysIncludePattern: true,
layout: {
- type: 'pattern',
- pattern: '[%d{hh:mm:ss.SSS}][%4.4p] %m'
+ type: "pattern",
+ pattern: "[%d{hh:mm:ss.SSS}][%4.4p] %m"
}
},
error: {
- type: 'file',
- filename: 'logs/error.log',
+ type: "file",
+ filename: "logs/error.log",
alwaysIncludePattern: true,
layout: {
- type: 'pattern',
- pattern: '[%d{hh:mm:ss.SSS}][%4.4p] %m'
+ type: "pattern",
+ pattern: "[%d{hh:mm:ss.SSS}][%4.4p] %m"
}
}
},
categories: {
- default: { appenders: ['console'], level: cfg.bot.log_level },
- command: { appenders: ['console', 'command'], level: 'warn' },
- error: { appenders: ['console', 'command', 'error'], level: 'error' }
+ default: { appenders: ["console"], level: cfg.bot.log_level },
+ command: { appenders: ["console", "command"], level: "warn" },
+ error: { appenders: ["console", "command", "error"], level: "error" }
}
})
- const defaultLogger = log4js.getLogger('message')
- const commandLogger = log4js.getLogger('command')
- const errorLogger = log4js.getLogger('error')
+ const defaultLogger = log4js.getLogger("message")
+ const commandLogger = log4js.getLogger("command")
+ const errorLogger = log4js.getLogger("error")
- /* eslint-disable no-useless-call */
/** 全局变量 logger */
global.logger = {
- trace () {
- defaultLogger.trace.call(defaultLogger, ...arguments)
- },
- debug () {
- defaultLogger.debug.call(defaultLogger, ...arguments)
- },
- info () {
- defaultLogger.info.call(defaultLogger, ...arguments)
- },
- // warn及以上的日志采用error策略
- warn () {
- commandLogger.warn.call(defaultLogger, ...arguments)
- },
- error () {
- errorLogger.error.call(errorLogger, ...arguments)
- },
- fatal () {
- errorLogger.fatal.call(errorLogger, ...arguments)
- },
- mark () {
- errorLogger.mark.call(commandLogger, ...arguments)
- }
+ trace: (...args) => defaultLogger.trace(...args),
+ debug: (...args) => defaultLogger.debug(...args),
+ info: (...args) => defaultLogger.info(...args),
+ warn: (...args) => commandLogger.warn(...args),
+ error: (...args) => errorLogger.error(...args),
+ fatal: (...args) => errorLogger.fatal(...args),
+ mark: (...args) => commandLogger.mark(...args),
+ chalk: chalk,
+ red: chalk.red,
+ green: chalk.green,
+ yellow: chalk.yellow,
+ blue: chalk.blue,
+ magenta: chalk.magenta,
+ cyan: chalk.cyan,
}
-
- logColor()
-}
-
-function logColor () {
- logger.chalk = chalk
- logger.red = chalk.red
- logger.green = chalk.green
- logger.yellow = chalk.yellow
- logger.blue = chalk.blue
- logger.magenta = chalk.magenta
- logger.cyan = chalk.cyan
-}
+}
\ No newline at end of file
diff --git a/Yunzai/lib/config/redis.js b/Yunzai/lib/config/redis.js
index aad2882805b7caf6b611e9cd01ec0ec33069e322..030607795cc2ee484e5c2fe237a9d99eb9c826e7 100644
--- a/Yunzai/lib/config/redis.js
+++ b/Yunzai/lib/config/redis.js
@@ -1,8 +1,8 @@
import cfg from "./config.js"
-import common from "../common/common.js"
import { createClient } from "redis"
import { exec } from "node:child_process"
+let lock = false
/**
* 初始化全局redis客户端
*/
@@ -13,65 +13,54 @@ export default async function redisInit() {
if (rc.username || rc.password)
redisPw += "@"
const redisUrl = `redis://${redisUn}${redisPw}${rc.host}:${rc.port}/${rc.db}`
- let client = createClient({ url: redisUrl })
+ Bot.makeLog("info", `正在连接 ${logger.blue(redisUrl)}`, "Redis")
+ return connectRedis(redisUrl)
+}
+
+async function connectRedis(redisUrl, cmd) {
+ if (lock && !cmd) return
+ lock = true
try {
- logger.info(`正在连接 ${logger.blue(redisUrl)}`)
- await client.connect()
+ global.redis = createClient({ url: redisUrl })
+ await redis.connect()
} catch (err) {
- logger.error(`Redis 错误:${logger.red(err)}`)
-
- const cmd = "redis-server --save 900 1 --save 300 10 --daemonize yes" + await aarch64()
- logger.info("正在启动 Redis...")
- await execSync(cmd)
- await common.sleep(1000)
-
- try {
- client = createClient({ url: redisUrl })
- await client.connect()
- } catch (err) {
- logger.error(`Redis 错误:${logger.red(err)}`)
- logger.error(`请先启动 Redis:${logger.blue(cmd)}`)
- process.exit()
- }
+ Bot.makeLog("error", ["连接错误", err], "Redis")
+ if (!cmd) return startRedis(redisUrl)
+ Bot.makeLog("error", ["请先启动", logger.blue(cmd)], "Redis")
+ process.exit(1)
}
- client.on("error", async err => {
- logger.error(`Redis 错误:${logger.red(err)}`)
- const cmd = "redis-server --save 900 1 --save 300 10 --daemonize yes" + await aarch64()
- logger.error(`请先启动 Redis:${cmd}`)
- process.exit()
+ redis.on("error", err => {
+ Bot.makeLog("error", err, "Redis")
+ return connectRedis(redisUrl)
})
- /** 全局变量 redis */
- client.url = redisUrl
- global.redis = client
- logger.info("Redis 连接成功")
- return client
+ lock = false
+ return redis
+}
+
+async function startRedis(redisUrl) {
+ if (cfg.redis.host != "127.0.0.1") {
+ Bot.makeLog("error", `连接失败,请确认连接地址正确`, "Redis")
+ process.exit(1)
+ }
+ const cmd = `redis-server --port ${cfg.redis.port} --save 900 1 --save 300 10 --daemonize yes${await aarch64()}`
+ await Bot.exec(cmd)
+ await Bot.sleep(1000)
+ return connectRedis(redisUrl, cmd)
}
async function aarch64() {
- if (process.platform == "win32")
+ if (process.platform == "win32" || process.arch != "arm64")
return ""
- /** 判断arch */
- const arch = await execSync("uname -m")
- if (arch.stdout && arch.stdout.includes("aarch64")) {
- /** 判断redis版本 */
- let v = await execSync("redis-server -v")
- if (v.stdout) {
- v = v.stdout.match(/v=(\d)./)
- /** 忽略arm警告 */
- if (v && v[1] >= 6)
- return " --ignore-warnings ARM64-COW-BUG"
- }
+ /** 判断redis版本 */
+ let v = await Bot.exec("redis-server -v")
+ if (v.stdout?.match) {
+ v = v.stdout.match(/v=(\d)./)
+ /** 忽略arm警告 */
+ if (v && v[1] >= 6)
+ return " --ignore-warnings ARM64-COW-BUG"
}
return ""
-}
-
-function execSync (cmd) {
- return new Promise((resolve, reject) => {
- exec(cmd, (error, stdout, stderr) => {
- resolve({ error, stdout, stderr })
- })
- })
}
\ No newline at end of file
diff --git a/Yunzai/lib/events/message.js b/Yunzai/lib/events/message.js
index 8084c076bdc9574890a4fab98da03f20879df5f3..23686dda1ebb259b96f2bb22aeab52d8d0561fbc 100644
--- a/Yunzai/lib/events/message.js
+++ b/Yunzai/lib/events/message.js
@@ -1,11 +1,11 @@
-import EventListener from '../listener/listener.js'
+import EventListener from "../listener/listener.js"
/**
* 监听群聊消息
*/
export default class messageEvent extends EventListener {
constructor () {
- super({ event: 'message' })
+ super({ event: "message" })
}
async execute (e) {
diff --git a/Yunzai/lib/events/notice.js b/Yunzai/lib/events/notice.js
index 8e42c604303afb95bfb207fafabdf98f59fb6460..2be29fc171e7c277b00ab10298dbf63838bb15c8 100644
--- a/Yunzai/lib/events/notice.js
+++ b/Yunzai/lib/events/notice.js
@@ -1,11 +1,11 @@
-import EventListener from '../listener/listener.js'
+import EventListener from "../listener/listener.js"
/**
* 监听群聊消息
*/
export default class noticeEvent extends EventListener {
constructor () {
- super({ event: 'notice' })
+ super({ event: "notice" })
}
async execute (e) {
diff --git a/Yunzai/lib/events/online.js b/Yunzai/lib/events/online.js
index 7635342dea07cb76a00943ad6a38cdc29e4bf632..a6dfd6b088c21046e08b486ed38dd4a81e9383a9 100644
--- a/Yunzai/lib/events/online.js
+++ b/Yunzai/lib/events/online.js
@@ -1,5 +1,4 @@
-import EventListener from '../listener/listener.js'
-import cfg from '../config/config.js'
+import EventListener from "../listener/listener.js"
/**
* 监听上线事件
@@ -7,12 +6,12 @@ import cfg from '../config/config.js'
export default class onlineEvent extends EventListener {
constructor() {
super({
- event: 'online',
+ event: "online",
once: true
})
}
async execute() {
- logger.mark('----^_^----')
+ logger.mark("----^_^----")
}
}
\ No newline at end of file
diff --git a/Yunzai/lib/events/request.js b/Yunzai/lib/events/request.js
index 44338f7ab4a7e21d2ff0a0f61d2bb575be3c7074..c4122fb82322bca0dd5393e3eac16260913410f5 100644
--- a/Yunzai/lib/events/request.js
+++ b/Yunzai/lib/events/request.js
@@ -1,11 +1,11 @@
-import EventListener from '../listener/listener.js'
+import EventListener from "../listener/listener.js"
/**
* 监听群聊消息
*/
export default class requestEvent extends EventListener {
constructor () {
- super({ event: 'request' })
+ super({ event: "request" })
}
async execute (e) {
diff --git a/Yunzai/lib/listener/listener.js b/Yunzai/lib/listener/listener.js
index 644f7a1bb5ee78279807bce45a6733f333274b74..c8ed7165f45de52903ed4fc0799ae5865d31afae 100644
--- a/Yunzai/lib/listener/listener.js
+++ b/Yunzai/lib/listener/listener.js
@@ -1,4 +1,4 @@
-import PluginsLoader from '../plugins/loader.js'
+import PluginsLoader from "../plugins/loader.js"
export default class EventListener {
/**
@@ -8,7 +8,7 @@ export default class EventListener {
* @param data.once 是否只监听一次
*/
constructor (data) {
- this.prefix = data.prefix || ''
+ this.prefix = data.prefix || ""
this.event = data.event
this.once = data.once || false
this.plugins = PluginsLoader
diff --git a/Yunzai/lib/listener/loader.js b/Yunzai/lib/listener/loader.js
index cbed4fd1ed8fb5a0f2eddfc25c8109bb5d1b69ea..337d69c9c96bc2e47711be3193269687775e5645 100644
--- a/Yunzai/lib/listener/loader.js
+++ b/Yunzai/lib/listener/loader.js
@@ -1,5 +1,5 @@
-import fs from 'node:fs'
-import lodash from 'lodash'
+import fs from "node:fs/promises"
+import lodash from "lodash"
/**
* 加载监听事件
@@ -12,21 +12,21 @@ class ListenerLoader {
logger.info("-----------")
logger.info("加载监听事件中...")
let eventCount = 0
- for (const file of fs.readdirSync('./lib/events').filter(file => file.endsWith('.js'))) {
+ for (const file of (await fs.readdir("./lib/events")).filter(file => file.endsWith(".js"))) {
logger.debug(`加载监听事件:${file}`)
try {
let listener = await import(`../events/${file}`)
if (!listener.default) continue
listener = new listener.default()
- const on = listener.once ? 'once' : 'on'
+ const on = listener.once ? "once" : "on"
if (lodash.isArray(listener.event)) {
listener.event.forEach((type) => {
- const e = listener[type] ? type : 'execute'
+ const e = listener[type] ? type : "execute"
Bot[on](listener.prefix + type, event => listener[e](event))
})
} else {
- const e = listener[listener.event] ? listener.event : 'execute'
+ const e = listener[listener.event] ? listener.event : "execute"
Bot[on](listener.prefix + listener.event, event => listener[e](event))
}
eventCount++
diff --git a/Yunzai/lib/modules/md5/index.js b/Yunzai/lib/modules/md5/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..3bbe36b2c9db5ee60b9c0912de0683a754b9373f
--- /dev/null
+++ b/Yunzai/lib/modules/md5/index.js
@@ -0,0 +1,4 @@
+import { createHash } from "node:crypto"
+export default function md5(data) {
+ return createHash("md5").update(data).digest("hex")
+}
\ No newline at end of file
diff --git a/Yunzai/lib/modules/md5/package.json b/Yunzai/lib/modules/md5/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..c86f3f82a9104dfdacb597042628028a3d04bc8f
--- /dev/null
+++ b/Yunzai/lib/modules/md5/package.json
@@ -0,0 +1,5 @@
+{
+ "name": "md5",
+ "type": "module",
+ "main": "index.js"
+}
\ No newline at end of file
diff --git a/Yunzai/lib/modules/node-fetch/index.js b/Yunzai/lib/modules/node-fetch/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..ebf6f0012a0929cc12380af47f9d14e955ca4808
--- /dev/null
+++ b/Yunzai/lib/modules/node-fetch/index.js
@@ -0,0 +1,2 @@
+export const { Blob, File, FormData, Headers, Request, Response } = global
+export default global.fetch
\ No newline at end of file
diff --git a/Yunzai/lib/modules/node-fetch/package.json b/Yunzai/lib/modules/node-fetch/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..2eb220d10700181d98ecce7ea58bca8a052cae8e
--- /dev/null
+++ b/Yunzai/lib/modules/node-fetch/package.json
@@ -0,0 +1,5 @@
+{
+ "name": "node-fetch",
+ "type": "module",
+ "main": "index.js"
+}
\ No newline at end of file
diff --git a/Yunzai/lib/modules/oicq/index.js b/Yunzai/lib/modules/oicq/index.js
index 3f429567f5bb16f373d6c5ce2d7b372dc9bd163e..64dd3e370ec1e8c9a6fcf1f67e2c29d419bf2392 100644
--- a/Yunzai/lib/modules/oicq/index.js
+++ b/Yunzai/lib/modules/oicq/index.js
@@ -1,67 +1,33 @@
-import fs from "node:fs"
-import path from "node:path"
-
-function toSegment(type, data) {
- for (const i in data)
- if (typeof data[i] == "string" && (i == "file" || data[i].match(/^file:\/\//)) && fs.existsSync(data[i].replace(/^file:\/\//, ""))) {
- if (i == "file" && !data.name)
- data.name = `${Date.now()}-${path.basename(data[i])}`
- data[i] = fs.readFileSync(data[i].replace(/^file:\/\//, ""))
- }
- return { type, ...data }
-}
-
const segment = new class segment {
custom(type, data) {
- return toSegment(type, data)
+ return { type, ...data }
}
raw(data) {
- return toSegment("raw", { data })
+ return { type: "raw", data }
}
button(...data) {
- return toSegment("button", { data })
+ return { type: "button", data }
}
markdown(data) {
- return toSegment("markdown", { data })
+ return { type: "markdown", data }
}
image(file, name) {
- return toSegment("image", { file, name })
+ return { type: "image", file, name }
}
at(qq, name) {
- return toSegment("at", { qq, name })
+ return { type: "at", qq, name }
}
record(file, name) {
- return toSegment("record", { file, name })
+ return { type: "record", file, name }
}
video(file, name) {
- return toSegment("video", { file, name })
+ return { type: "video", file, name }
}
file(file, name) {
- return toSegment("file", { file, name })
+ return { type: "file", file, name }
}
reply(id, text, qq, time, seq) {
- return toSegment("reply", { id, text, qq, time, seq })
- }
- face(id) {
- return toSegment("face", { id })
- }
- share(url, title, content, image) {
- return toSegment("share", { url, title, content, image })
- }
- music(type, id, url, audio, title) {
- return toSegment("music", { type, id, url, audio, title })
- }
- poke(qq) {
- return toSegment("poke", { qq })
- }
- gift(qq, id) {
- return toSegment("gift", { qq, id })
- }
- cardimage(file, name, minwidth, minheight, maxwidth, maxheight, source, icon) {
- return toSegment("cardimage", { file, name, minwidth, minheight, maxwidth, maxheight, source, icon })
- }
- tts(text) {
- return toSegment("tts", { text })
+ return { type: "reply", id, text, qq, time, seq }
}
}
diff --git a/Yunzai/lib/plugins/config.js b/Yunzai/lib/plugins/config.js
new file mode 100644
index 0000000000000000000000000000000000000000..621ec117a96e3885ebc327f3da866ac0ebd5ba54
--- /dev/null
+++ b/Yunzai/lib/plugins/config.js
@@ -0,0 +1,20 @@
+import fs from "node:fs/promises"
+import YAML from "yaml"
+import _ from "lodash"
+export default async function(name, config, keep) {
+ const configFile = `config/${name}.yaml`
+ const configSave = () => fs.writeFile(configFile, YAML.stringify(config), "utf-8")
+
+ let configData
+ try {
+ configData = YAML.parse(await fs.readFile(configFile, "utf-8"))
+ _.merge(config, configData)
+ } catch (err) {
+ logger.debug("配置文件", configFile, "读取失败", err)
+ }
+ _.merge(config, keep)
+
+ if (YAML.stringify(config) != YAML.stringify(configData))
+ await configSave()
+ return { config, configSave }
+}
\ No newline at end of file
diff --git a/Yunzai/lib/plugins/loader.js b/Yunzai/lib/plugins/loader.js
index d9519f68b8b31e9c8f3b1257451a718a5d37e0e1..335bc5bef8c67a6f4e13117a35e30a565ed7dea8 100644
--- a/Yunzai/lib/plugins/loader.js
+++ b/Yunzai/lib/plugins/loader.js
@@ -1,5 +1,5 @@
import util from "node:util"
-import fs from "node:fs"
+import fs from "node:fs/promises"
import lodash from "lodash"
import cfg from "../config/config.js"
import plugin from "./plugin.js"
@@ -8,9 +8,8 @@ import { segment } from "oicq"
import chokidar from "chokidar"
import moment from "moment"
import path from "node:path"
-import common from "../common/common.js"
import Runtime from "./runtime.js"
-import Handler from './handler.js'
+import Handler from "./handler.js"
/** 全局变量 plugin */
global.plugin = plugin
@@ -24,7 +23,7 @@ class PluginsLoader {
this.priority = []
this.handler = {}
this.task = []
- this.dir = "./plugins"
+ this.dir = "plugins"
/** 命令冷却cd */
this.groupCD = {}
@@ -32,6 +31,11 @@ class PluginsLoader {
/** 插件监听 */
this.watcher = {}
+ this.eventMap = {
+ message: ["post_type", "message_type", "sub_type"],
+ notice: ["post_type", "notice_type", "sub_type"],
+ request: ["post_type", "request_type", "sub_type"],
+ }
this.msgThrottle = {}
@@ -39,133 +43,127 @@ class PluginsLoader {
this.srReg = /^#?(\*|星铁|星轨|穹轨|星穹|崩铁|星穹铁道|崩坏星穹铁道|铁道)+/
}
+ async getPlugins() {
+ const files = await fs.readdir(this.dir, { withFileTypes: true })
+ const ret = []
+ for (const val of files) {
+ if (val.isFile()) continue
+ const tmp = {
+ name: val.name,
+ path: `../../${this.dir}/${val.name}`,
+ }
+
+ if (await Bot.fsStat(`${this.dir}/${val.name}/index.js`)) {
+ tmp.path = `${tmp.path}/index.js`
+ ret.push(tmp)
+ continue
+ }
+
+ const apps = await fs.readdir(`${this.dir}/${val.name}`, { withFileTypes: true })
+ for (const app of apps) {
+ if (!app.isFile()) continue
+ if (!app.name.endsWith(".js")) continue
+ ret.push({
+ name: `${tmp.name}/${app.name}`,
+ path: `${tmp.path}/${app.name}`,
+ })
+ /** 监听热更新 */
+ this.watch(val.name, app.name)
+ }
+ }
+ return ret
+ }
+
/**
* 监听事件加载
* @param isRefresh 是否刷新
*/
async load(isRefresh = false) {
- this.delCount()
- if (!lodash.isEmpty(this.priority) && !isRefresh) return
-
- const files = this.getPlugins()
+ if (isRefresh) this.priority = []
+ if (this.priority.length) return
logger.info("-----------")
logger.info("加载插件中...")
- let pluCount = 0
+ const files = await this.getPlugins()
+ this.pluginCount = 0
+ const packageErr = []
- let packageErr = []
- for (let File of files) {
- try {
- let tmp = await import(File.path)
- let apps = tmp
- if (tmp.apps) {
- apps = { ...tmp.apps }
- }
- lodash.forEach(apps, (p, i) => {
- if (!p.prototype) return
- pluCount++
- /* eslint-disable new-cap */
- let plugin = new p()
- logger.debug(`载入插件 [${File.name}][${plugin.name}]`)
- /** 执行初始化 */
- this.runInit(plugin)
- /** 初始化定时任务 */
- this.collectTask(plugin.task)
- this.priority.push({
- class: p,
- key: File.name,
- name: plugin.name,
- priority: plugin.priority
- })
- if (plugin.handler) {
- lodash.forEach(plugin.handler, ({ fn, key, priority }) => {
- Handler.add({
- ns: plugin.namespace || File.name,
- key: key,
- self: plugin,
- property: priority || plugin.priority || 500,
- fn: plugin[fn]
- })
- })
- }
- })
- } catch (error) {
- if (error.stack.includes("Cannot find package")) {
- packageErr.push({ error, File })
- } else {
- logger.error(`载入插件错误:${logger.red(File.name)}`)
- logger.error(decodeURI(error.stack))
- }
- }
- }
+ await Promise.allSettled(files.map(file =>
+ this.importPlugin(file, packageErr)
+ ))
this.packageTips(packageErr)
- this.creatTask()
+ this.createTask()
logger.info(`加载定时任务[${this.task.length}个]`)
- logger.info(`加载插件[${pluCount}个]`)
+ logger.info(`加载插件[${this.pluginCount}个]`)
/** 优先级排序 */
this.priority = lodash.orderBy(this.priority, ["priority"], ["asc"])
}
- async runInit(plugin) {
- plugin.init && plugin.init()
+ async importPlugin(file, packageErr) {
+ try {
+ let app = await import(file.path)
+ if (app.apps) app = { ...app.apps }
+ const pluginArray = []
+ lodash.forEach(app, p =>
+ pluginArray.push(this.loadPlugin(file, p))
+ )
+ for (const i of await Promise.allSettled(pluginArray))
+ if (i?.status && i.status != "fulfilled") {
+ logger.error(`加载插件错误:${logger.red(file.name)}`)
+ logger.error(decodeURI(i.reason))
+ }
+ } catch (error) {
+ if (packageErr && error.stack.includes("Cannot find package")) {
+ packageErr.push({ error, file })
+ } else {
+ logger.error(`加载插件错误:${logger.red(file.name)}`)
+ logger.error(decodeURI(error.stack))
+ }
+ }
}
- packageTips(packageErr) {
- if (!packageErr || packageErr.length <= 0) return
- logger.mark("--------插件载入错误--------")
- packageErr.forEach(v => {
- let pack = v.error.stack.match(/'(.+?)'/g)[0].replace(/'/g, "")
- logger.mark(`${v.File.name} 缺少依赖:${logger.red(pack)}`)
- logger.mark(`新增插件后请执行安装命令:${logger.red("pnpm i")} 安装依赖`)
- logger.mark("如安装后仍未解决可联系插件作者解决")
+ async loadPlugin(file, p) {
+ if (!p?.prototype) return
+ this.pluginCount++
+ const plugin = new p
+ logger.debug(`加载插件 [${file.name}][${plugin.name}]`)
+ /** 执行初始化,返回 return 则跳过加载 */
+ if (plugin.init && await plugin.init() == "return") return
+ /** 初始化定时任务 */
+ this.collectTask(plugin.task)
+ this.priority.push({
+ class: p,
+ key: file.name,
+ name: plugin.name,
+ priority: plugin.priority
})
- // logger.error("或者使用其他包管理工具安装依赖")
- logger.mark("---------------------")
- }
-
- getPlugins() {
- let ignore = ["index.js"]
- let files = fs.readdirSync(this.dir, { withFileTypes: true })
- let ret = []
- for (let val of files) {
- let filepath = "../../plugins/" + val.name
- let tmp = {
- name: val.name
- }
- if (val.isFile()) {
- if (!val.name.endsWith(".js")) continue
- if (ignore.includes(val.name)) continue
- tmp.path = filepath
- ret.push(tmp)
- continue
- }
-
- if (fs.existsSync(`${this.dir}/${val.name}/index.js`)) {
- tmp.path = filepath + "/index.js"
- ret.push(tmp)
- continue
- }
-
- let apps = fs.readdirSync(`${this.dir}/${val.name}`, { withFileTypes: true })
- for (let app of apps) {
- if (!app.name.endsWith(".js")) continue
- if (ignore.includes(app.name)) continue
-
- ret.push({
- name: `${val.name}/${app.name}`,
- path: `../../plugins/${val.name}/${app.name}`
+ if (plugin.handler) {
+ lodash.forEach(plugin.handler, ({ fn, key, priority }) => {
+ Handler.add({
+ ns: plugin.namespace || file.name,
+ key,
+ self: plugin,
+ property: priority || plugin.priority || 500,
+ fn: plugin[fn]
})
-
- /** 监听热更新 */
- this.watch(val.name, app.name)
- }
+ })
}
+ }
- return ret
+ packageTips(packageErr) {
+ if (!packageErr.length) return
+ logger.mark("--------- 插件加载错误 ---------")
+ for (const i of packageErr) {
+ const pack = i.error.stack.match(/'(.+?)'/g)[0].replace(/'/g, "")
+ logger.mark(`${logger.blue(i.file.name)} 缺少依赖 ${logger.red(pack)}`)
+ }
+ logger.mark(`安装插件后请 ${logger.red("pnpm i")} 安装依赖`)
+ logger.mark(`仍报错${logger.red("进入插件目录")} pnpm add 依赖`)
+ logger.mark("--------------------------------")
}
/**
@@ -175,52 +173,38 @@ class PluginsLoader {
* @param e 事件
*/
async deal(e) {
+ this.count(e, "receive", e.message)
/** 检查黑白名单 */
if (!this.checkBlack(e)) return
/** 冷却 */
if (!this.checkLimit(e)) return
/** 处理事件 */
this.dealEvent(e)
- /** 处理消息 */
- this.dealMsg(e)
/** 处理回复 */
this.reply(e)
- /** 过滤事件 */
- let priority = []
/** 注册runtime */
await Runtime.init(e)
- this.priority.forEach(v => {
- let p = new v.class(e)
+ const priority = []
+ for (const i of this.priority) {
+ const p = new i.class(e)
p.e = e
- /** 判断是否启用功能 */
- if (!this.checkDisable(e, p)) return
- /** 过滤事件 */
- if (!this.filtEvent(e, p)) return
- priority.push(p)
- })
+ /** 判断是否启用功能,过滤事件 */
+ if (this.checkDisable(p) && this.filtEvent(e, p))
+ priority.push(p)
+ }
- for (let plugin of priority) {
+ for (const plugin of priority) {
/** 上下文hook */
- if (plugin.getContext) {
- let context = plugin.getContext()
- if (!lodash.isEmpty(context)) {
- for (let fnc in context) {
- plugin[fnc](context[fnc])
- }
- return
- }
+ if (!plugin.getContext) continue
+ const context = {
+ ...plugin.getContext(),
+ ...plugin.getContext(false, true),
}
-
- /** 群上下文hook */
- if (plugin.getContextGroup) {
- let context = plugin.getContextGroup()
- if (!lodash.isEmpty(context)) {
- for (let fnc in context) {
- plugin[fnc](context[fnc])
- }
- return
- }
+ if (!lodash.isEmpty(context)) {
+ for (const fnc in context)
+ plugin[fnc](context[fnc])
+ return
}
}
@@ -242,59 +226,43 @@ class PluginsLoader {
e.msg = e.msg.replace(this.srReg, "#星铁")
}
- /** accept */
- for (let plugin of priority) {
- /** accept hook */
+ /** 优先执行 accept */
+ for (const plugin of priority)
if (plugin.accept) {
- let res = plugin.accept(e)
-
- if (util.types.isPromise(res)) res = await res
-
- if (res === "return") return
-
+ const res = await plugin.accept(e)
+ if (res == "return") return
if (res) break
}
- }
- /* eslint-disable no-labels */
- a: for (let plugin of priority) {
+ a: for (const plugin of priority) {
/** 正则匹配 */
- if (plugin.rule) {
- for (let v of plugin.rule) {
- /** 判断事件 */
- if (v.event && !this.filtEvent(e, v)) continue
-
- if (new RegExp(v.reg).test(e.msg)) {
- e.logFnc = `[${plugin.name}][${v.fnc}]`
-
- if (v.log !== false) {
- logger.info(`${e.logFnc}${e.logText} ${lodash.truncate(e.msg, { length: 80 })}`)
- }
-
- /** 判断权限 */
- if (!this.filtPermission(e, v)) break a
-
- try {
- let res = plugin[v.fnc] && plugin[v.fnc](e)
-
- let start = Date.now()
-
- if (util.types.isPromise(res)) res = await res
-
- if (res !== false) {
- /** 设置冷却cd */
- this.setLimit(e)
- if (v.log !== false) {
- logger.mark(`${e.logFnc} ${lodash.truncate(e.msg, { length: 80 })} 处理完成 ${Date.now() - start}ms`)
- }
- break a
- }
- } catch (error) {
- logger.error(`${e.logFnc}`)
- logger.error(error.stack)
- break a
- }
+ if (plugin.rule) for (const v of plugin.rule) {
+ /** 判断事件 */
+ if (v.event && !this.filtEvent(e, v)) continue
+
+ if (!new RegExp(v.reg).test(e.msg)) continue
+ e.logFnc = `[${plugin.name}][${v.fnc}]`
+
+ if (v.log !== false)
+ logger.info(`${e.logFnc}${e.logText} ${lodash.truncate(e.msg, { length: 100 })}`)
+
+ /** 判断权限 */
+ if (!this.filtPermission(e, v)) break a
+
+ try {
+ const start = Date.now()
+ const res = plugin[v.fnc] && (await plugin[v.fnc](e))
+ if (res !== false) {
+ /** 设置冷却cd */
+ this.setLimit(e)
+ if (v.log !== false)
+ logger.mark(`${e.logFnc} ${lodash.truncate(e.msg, { length: 100 })} 处理完成 ${Date.now() - start}ms`)
+ break a
}
+ } catch (error) {
+ logger.error(`${e.logFnc}`)
+ logger.error(error.stack)
+ break a
}
}
}
@@ -303,23 +271,16 @@ class PluginsLoader {
/** 过滤事件 */
filtEvent(e, v) {
if (!v.event) return false
- let event = v.event.split(".")
- let eventMap = {
- message: ["post_type", "message_type", "sub_type"],
- notice: ["post_type", "notice_type", "sub_type"],
- request: ["post_type", "request_type", "sub_type"]
+ const event = v.event.split(".")
+ const eventMap = this.eventMap[e.post_type] || []
+ const newEvent = []
+ for (const i in event) {
+ if (event[i] == "*")
+ newEvent.push(event[i])
+ else
+ newEvent.push(e[eventMap[i]])
}
- let newEvent = []
- event.forEach((val, index) => {
- if (val === "*") {
- newEvent.push(val)
- } else if (eventMap[e.post_type]) {
- newEvent.push(e[eventMap[e.post_type][index]])
- }
- })
- newEvent = newEvent.join(".")
-
- return v.event === newEvent
+ return v.event == newEvent.join(".")
}
/** 判断权限 */
@@ -336,10 +297,6 @@ class PluginsLoader {
}
if (e.isGroup) {
- if (!e.member?._info) {
- e.reply("数据加载中,请稍后再试")
- return false
- }
if (v.permission == "owner") {
if (!e.member.is_owner) {
e.reply("暂无权限,只有群主才能操作")
@@ -357,20 +314,17 @@ class PluginsLoader {
return true
}
- dealEvent(e) {
- if (!e.friend && e.user_id) e.friend = e.bot.pickFriend(e.user_id)
- if (!e.group && e.group_id) e.group = e.bot.pickGroup(e.group_id)
- if (!e.member && e.group && e.user_id) e.member = e.group.pickMember(e.user_id)
- for (const i of [e.friend, e.group, e.member]) {
- if (typeof i != "object") continue
- if (!i.makeForwardMsg) i.makeForwardMsg = Bot.makeForwardMsg
- if (!i.sendForwardMsg) i.sendForwardMsg = msg => Bot.sendForwardMsg(msg => i.sendMsg(msg), msg)
- if (!i.getInfo) i.getInfo = () => i
- }
+ dealText(text = "") {
+ if (cfg.bot["/→#"])
+ text = text.replace(/^\s*\/\s*/, "#")
+ return text
+ .replace(/^\s*[#井]\s*/, "#")
+ .replace(/^\s*[*※]\s*/, "*")
+ .trim()
}
/**
- * 处理消息,加入自定义字段
+ * 处理事件,加入自定义字段
* @param e.msg 文本消息,多行会自动拼接
* @param e.img 图片消息数组
* @param e.atBot 是否at机器人
@@ -382,12 +336,11 @@ class PluginsLoader {
* @param e.logText 日志用户字符串
* @param e.logFnc 日志方法字符串
*/
- dealMsg(e) {
+ dealEvent(e) {
if (e.message) for (const i of e.message) {
switch (i.type) {
case "text":
- if (!e.msg) e.msg = ""
- if (i.text) e.msg += i.text.replace(/^\s*[##井]+\s*/, "#").replace(/^\s*[\\**※]+\s*/, "*").trim()
+ e.msg = (e.msg || "") + this.dealText(i.text)
break
case "image":
if (Array.isArray(e.img))
@@ -411,6 +364,10 @@ class PluginsLoader {
case "file":
e.file = i
break
+ case "xml":
+ case "json":
+ e.msg = (e.msg || "") + (typeof i.data == "string" ? i.data : JSON.stringify(i.data))
+ break
}
}
@@ -423,37 +380,31 @@ class PluginsLoader {
e.sender.card = e.sender.nickname
} else {
e.sender = {
+ user_id: e.user_id,
+ nickname: e.friend?.nickname,
card: e.friend?.nickname,
- nickname: e.friend?.nickname
}
}
e.logText = `[${e.sender?.nickname ? `${e.sender.nickname}(${e.user_id})` : e.user_id}]`
- }
-
- if (e.message_type == "group" || e.notice_type == "group") {
+ } else if (e.message_type == "group" || e.notice_type == "group") {
e.isGroup = true
+
if (e.sender) {
- e.sender.card = e.sender.card || e.sender.nickname
- } else if (e.member) {
- e.sender = {
- card: e.member.card || e.member.nickname
- }
- } else if (e.nickname) {
- e.sender = {
- card: e.nickname,
- nickname: e.nickname
- }
+ if (!e.sender.card)
+ e.sender.card = e.sender.nickname
} else {
e.sender = {
- card: "",
- nickname: ""
+ user_id: e.user_id,
+ nickname: e.member?.nickname || e.friend?.nickname,
+ card: e.member?.card || e.member?.nickname || e.friend?.nickname,
}
}
- if (!e.group_name) e.group_name = e.group?.name
+ if (!e.group_name && e.group?.name)
+ e.group_name = e.group.name
- e.logText = `[${e.group_name ? `${e.group_name}(${e.group_id})` : e.group_id}, ${e.sender?.nickname ? `${e.sender.nickname}(${e.user_id})` : e.user_id}]`
+ e.logText = `[${e.group_name ? `${e.group_name}(${e.group_id})` : e.group_id}, ${e.sender?.card ? `${e.sender.card}(${e.user_id})` : e.user_id}]`
}
if (e.user_id && cfg.master[e.self_id]?.includes(String(e.user_id))) {
@@ -479,24 +430,19 @@ class PluginsLoader {
/** 处理回复,捕获发送失败异常 */
reply(e) {
- if (e.reply)
- e.replyNew = e.reply
- else
- e.replyNew = msg => {
- if (e.isGroup) {
- if (e.group?.sendMsg) {
- return e.group.sendMsg(msg)
- } else {
- return e.bot.pickGroup(e.group_id).sendMsg(msg)
- }
- } else {
- if (e.friend?.sendMsg) {
- return e.friend.sendMsg(msg)
- } else {
- return e.bot.pickFriend(e.user_id).sendMsg(msg)
- }
- }
+ const reply = e.reply ? e.reply.bind(e) : msg => {
+ if (e.isGroup) {
+ if (e.group?.sendMsg)
+ return e.group.sendMsg(msg)
+ else
+ return e.bot.pickGroup(e.group_id).sendMsg(msg)
+ } else {
+ if (e.friend?.sendMsg)
+ return e.friend.sendMsg(msg)
+ else
+ return e.bot.pickFriend(e.user_id).sendMsg(msg)
}
+ }
/**
* @param msg 发送的消息
@@ -509,13 +455,13 @@ class PluginsLoader {
let { recallMsg = 0, at = "" } = data
- if (at) {
+ if (at && e.isGroup) {
if (at === true)
at = e.user_id
if (Array.isArray(msg))
- msg.unshift(segment.at(at))
+ msg.unshift(segment.at(at), "\n")
else
- msg = [segment.at(at), msg]
+ msg = [segment.at(at), "\n", msg]
}
if (quote && e.message_id) {
@@ -527,10 +473,9 @@ class PluginsLoader {
let res
try {
- res = await e.replyNew(msg)
+ res = await reply(msg)
} catch (err) {
- Bot.makeLog("error", `发送消息错误:${Bot.String(msg)}`, e.self_id)
- logger.error(err)
+ Bot.makeLog("error", ["发送消息错误", msg, err], e.self_id)
}
if (recallMsg > 0 && res?.message_id) {
@@ -548,93 +493,63 @@ class PluginsLoader {
}, recallMsg * 1000)
}
- this.count(e, msg)
+ this.count(e, "send", msg)
return res
}
}
- count(e, msg) {
- let screenshot = false
- if (msg && msg?.file)
- screenshot = true
-
- this.saveCount("sendMsg")
- if (screenshot)
- this.saveCount("screenshot")
-
- if (e.group_id) {
- this.saveCount("sendMsg", e.group_id)
- if (screenshot)
- this.saveCount("screenshot", e.group_id)
- }
+ async count(e, type, msg) {
+ if (cfg.bot.msg_type_count)
+ for (const i of Array.isArray(msg) ? msg : [msg])
+ await this.saveCount(e, `${type}:${i?.type || "text"}`)
+ await this.saveCount(e, `${type}:msg`)
}
- saveCount(type, groupId = "") {
- let key = "Yz:count:"
-
- if (groupId) {
- key += `group:${groupId}:`
+ async saveCount(e, type) {
+ const key = []
+
+ const day = moment().format("YYYY:MM:DD")
+ const month = moment().format("YYYY:MM")
+ const year = moment().format("YYYY")
+ for (const i of [day, month, year, "total"]) {
+ key.push(`total:${i}`)
+ if (e.self_id) key.push(`bot:${e.self_id}:${i}`)
+ if (e.user_id) key.push(`user:${e.user_id}:${i}`)
+ if (e.group_id) key.push(`group:${e.group_id}:${i}`)
}
- let dayKey = `${key}${type}:day:${moment().format("MMDD")}`
- let monthKey = `${key}${type}:month:${Number(moment().month()) + 1}`
- let totalKey = `${key}${type}:total`
-
- redis.incr(dayKey)
- redis.incr(monthKey)
- if (!groupId) redis.incr(totalKey)
- redis.expire(dayKey, 3600 * 24 * 30)
- redis.expire(monthKey, 3600 * 24 * 30)
- }
-
- delCount() {
- let key = "Yz:count:"
- redis.set(`${key}sendMsg:total`, "0")
- redis.set(`${key}screenshot:total`, "0")
+ for (const i of key)
+ await redis.incr(`Yz:count:${type}:${i}`)
}
/** 收集定时任务 */
collectTask(task) {
- if (Array.isArray(task)) {
- task.forEach((val) => {
- if (!val.cron) return
- if (!val.name) throw new Error("插件任务名称错误")
- this.task.push(val)
- })
- } else {
- if (task.fnc && task.cron) {
- if (!task.name) throw new Error("插件任务名称错误")
- this.task.push(task)
- }
- }
+ for (const i of Array.isArray(task) ? task : [task])
+ if (i.cron && i.name)
+ this.task.push(i)
}
/** 创建定时任务 */
- creatTask() {
- if (process.argv[1].includes("test")) return
- this.task.forEach((val) => {
- val.job = schedule.scheduleJob(val.cron, async () => {
+ createTask() {
+ for (const i of this.task)
+ i.job = schedule.scheduleJob(i.cron, async () => {
try {
- if (val.log === true) {
- logger.mark(`开始定时任务:${val.name}`)
- }
- let res = val.fnc()
- if (util.types.isPromise(res)) res = await res
- if (val.log === true) {
- logger.mark(`定时任务完成:${val.name}`)
- }
+ if (i.log == true)
+ logger.mark(`开始定时任务:${i.name}`)
+ await i.fnc()
+ if (i.log == true)
+ logger.mark(`定时任务完成:${i.name}`)
} catch (error) {
- logger.error(`定时任务报错:${val.name}`)
+ logger.error(`定时任务报错:${i.name}`)
logger.error(error)
}
})
- })
}
/** 检查命令冷却cd */
checkLimit(e) {
/** 禁言中 */
- if (e.isGroup && e?.group?.mute_left > 0) return false
+ if (e.isGroup && e.group?.mute_left > 0) return false
if (!e.message || e.isPrivate) return true
const config = cfg.getGroup(e.self_id, e.group_id)
@@ -661,16 +576,12 @@ class PluginsLoader {
if (config.groupCD) {
this.groupCD[e.group_id] = true
- setTimeout(() => {
- delete this.groupCD[e.group_id]
- }, config.groupCD)
+ setTimeout(() => delete this.groupCD[e.group_id], config.groupCD)
}
if (config.singleCD) {
- let key = `${e.group_id}.${e.user_id}`
+ const key = `${e.group_id}.${e.user_id}`
this.singleCD[key] = true
- setTimeout(() => {
- delete this.singleCD[key]
- }, config.singleCD)
+ setTimeout(() => delete this.singleCD[key], config.singleCD)
}
}
@@ -680,7 +591,11 @@ class PluginsLoader {
let groupCfg = cfg.getGroup(e.self_id, e.group_id)
- if (groupCfg.onlyReplyAt != 1 || !groupCfg.botAlias) return true
+ /** 模式0,未开启前缀 */
+ if (groupCfg.onlyReplyAt == 0 || !groupCfg.botAlias) return true
+
+ /** 模式2,非主人开启 */
+ if (groupCfg.onlyReplyAt == 2 && e.isMaster) return true
/** at机器人 */
if (e.atBot) return true
@@ -693,50 +608,54 @@ class PluginsLoader {
/** 判断黑白名单 */
checkBlack(e) {
- let other = cfg.getOther()
-
- if (e.test) return true
+ const other = cfg.getOther()
- /** 黑名单qq */
- if (other.blackQQ?.length && other.blackQQ.includes(Number(e.user_id) || String(e.user_id))) {
+ /** 黑名单用户 */
+ if (other.blackUser?.length && other.blackUser.includes(Number(e.user_id) || String(e.user_id)))
+ return false
+ /** 白名单用户 */
+ if (other.whiteUser?.length && !other.whiteUser.includes(Number(e.user_id) || String(e.user_id)))
return false
- }
if (e.group_id) {
- /** 白名单群 */
- if (other.whiteGroup?.length) {
- if (other.whiteGroup.includes(Number(e.group_id) || String(e.group_id))) return true
- return false
- }
/** 黑名单群 */
- if (other.blackGroup?.length && other.blackGroup.includes(Number(e.group_id) || String(e.group_id))) {
+ if (other.blackGroup?.length && other.blackGroup.includes(Number(e.group_id) || String(e.group_id)))
+ return false
+ /** 白名单群 */
+ if (other.whiteGroup?.length && !other.whiteGroup.includes(Number(e.group_id) || String(e.group_id)))
return false
- }
}
return true
}
/** 判断是否启用功能 */
- checkDisable(e, p) {
- let groupCfg = cfg.getGroup(e.self_id, e.group_id)
- if (!lodash.isEmpty(groupCfg.enable)) {
- if (groupCfg.enable.includes(p.name)) {
- return true
- }
- // logger.debug(`${e.logText}[${p.name}]功能已禁用`)
+ checkDisable(p) {
+ const groupCfg = cfg.getGroup(p.e.self_id, p.e.group_id)
+ if (groupCfg.disable?.length && groupCfg.disable.includes(p.name))
return false
- }
-
- if (!lodash.isEmpty(groupCfg.disable)) {
- if (groupCfg.disable.includes(p.name)) {
- // logger.debug(`${e.logText}[${p.name}]功能已禁用`)
- return false
- }
+ if (groupCfg.enable?.length && !groupCfg.enable.includes(p.name))
+ return false
+ return true
+ }
- return true
+ async changePlugin(key) {
+ try {
+ let app = await import(`../../${this.dir}/${key}?${moment().format("x")}`)
+ if (app.apps) app = { ...app.apps }
+ lodash.forEach(app, p => {
+ const plugin = new p
+ for (const i in this.priority)
+ if (this.priority[i].key == key && this.priority[i].name == plugin.name) {
+ this.priority[i].class = p
+ this.priority[i].priority = plugin.priority
+ }
+ })
+ this.priority = lodash.orderBy(this.priority, ["priority"], ["asc"])
+ } catch (error) {
+ logger.error(`加载插件错误:${logger.red(key)}`)
+ logger.error(decodeURI(error.stack))
}
- return true
}
/** 监听热更新 */
@@ -744,129 +663,50 @@ class PluginsLoader {
this.watchDir(dirName)
if (this.watcher[`${dirName}.${appName}`]) return
- let file = `./plugins/${dirName}/${appName}`
+ const file = `./${this.dir}/${dirName}/${appName}`
const watcher = chokidar.watch(file)
- let key = `${dirName}/${appName}`
+ const key = `${dirName}/${appName}`
/** 监听修改 */
- watcher.on("change", async path => {
+ watcher.on("change", path => {
logger.mark(`[修改插件][${dirName}][${appName}]`)
-
- let tmp = {}
- try {
- tmp = await import(`../../plugins/${dirName}/${appName}?${moment().format("x")}`)
- } catch (error) {
- logger.error(`载入插件错误:${logger.red(dirName + "/" + appName)}`)
- logger.error(decodeURI(error.stack))
- return
- }
-
- if (tmp.apps) tmp = { ...tmp.apps }
- lodash.forEach(tmp, (p) => {
- /* eslint-disable new-cap */
- let plugin = new p()
- for (let i in this.priority) {
- if (this.priority[i].key == key) {
- this.priority[i].class = p
- this.priority[i].priority = plugin.priority
- }
- }
-
- if (plugin.handler) {
- lodash.forEach(plugin.handler, ({ fn, key, priority }) => {
- Handler.add({
- ns: plugin.namespace || File.name,
- key: key,
- self: plugin,
- property: priority || plugin.priority || 500,
- fn: plugin[fn]
- })
- })
- }
- })
-
- this.priority = lodash.orderBy(this.priority, ["priority"], ["asc"])
+ this.changePlugin(key)
})
/** 监听删除 */
watcher.on("unlink", async path => {
logger.mark(`[卸载插件][${dirName}][${appName}]`)
- for (let i in this.priority) {
- if (this.priority[i].key == key) {
+ /** 停止更新监听 */
+ this.watcher[`${dirName}.${appName}`].removeAllListeners("change")
+ for (const i in this.priority)
+ if (this.priority[i].key == key)
this.priority.splice(i, 1)
- /** 停止更新监听 */
- this.watcher[`${dirName}.${appName}`].removeAllListeners("change")
- break
- }
- }
})
-
this.watcher[`${dirName}.${appName}`] = watcher
}
/** 监听文件夹更新 */
watchDir(dirName) {
if (this.watcher[dirName]) return
-
- let file = `./plugins/${dirName}/`
- const watcher = chokidar.watch(file)
-
+ const watcher = chokidar.watch(`./${this.dir}/${dirName}/`)
/** 热更新 */
- setTimeout(() => {
+ Bot.once("online", () => {
/** 新增文件 */
watcher.on("add", async PluPath => {
- let appName = path.basename(PluPath)
+ const appName = path.basename(PluPath)
if (!appName.endsWith(".js")) return
- if (!fs.existsSync(`${this.dir}/${dirName}/${appName}`)) return
-
- let key = `${dirName}/${appName}`
-
- this.watch(dirName, appName)
-
- /** 太快了延迟下 */
- await common.sleep(500)
-
logger.mark(`[新增插件][${dirName}][${appName}]`)
- let tmp = {}
- try {
- tmp = await import(`../../plugins/${dirName}/${appName}?${moment().format("X")}`)
- } catch (error) {
- logger.error(`载入插件错误:${logger.red(dirName + "/" + appName)}`)
- logger.error(decodeURI(error.stack))
- return
- }
-
- if (tmp.apps) tmp = { ...tmp.apps }
-
- lodash.forEach(tmp, (p) => {
- if (!p.prototype) {
- logger.error(`[载入失败][${dirName}][${appName}] 格式错误已跳过`)
- return
- }
- /* eslint-disable new-cap */
- let plugin = new p()
-
- for (let i in this.priority) {
- if (this.priority[i].key == key) {
- return
- }
- }
-
- this.priority.push({
- class: p,
- key,
- name: plugin.name,
- priority: plugin.priority
- })
+ const key = `${dirName}/${appName}`
+ await this.importPlugin({
+ name: key,
+ path: `../../${this.dir}/${key}?${moment().format("X")}`,
})
-
/** 优先级排序 */
this.priority = lodash.orderBy(this.priority, ["priority"], ["asc"])
+ this.watch(dirName, appName)
})
- }, 500)
-
+ })
this.watcher[dirName] = watcher
}
}
-
export default new PluginsLoader()
\ No newline at end of file
diff --git a/Yunzai/lib/plugins/plugin.js b/Yunzai/lib/plugins/plugin.js
index 3e738da10c7f81d9109d54afd40ae119c8ec9326..15183d107831b8f3b7f50354a3cba3a69cff29d3 100644
--- a/Yunzai/lib/plugins/plugin.js
+++ b/Yunzai/lib/plugins/plugin.js
@@ -1,6 +1,9 @@
-import { Common } from '#miao'
+let Common
+try {
+ Common = (await import("#miao")).Common
+} catch (err) {}
-let stateArr = {}
+const stateArr = {}
export default class plugin {
/**
@@ -24,16 +27,16 @@ export default class plugin {
* @param task.fnc 定时任务方法名
* @param task.log false时不显示执行日志
*/
- constructor ({
- name = 'your-plugin',
- dsc = '无',
- handler,
- namespace,
- event = 'message',
- priority = 5000,
- task = { fnc: '', cron: '' },
- rule = []
- }) {
+ constructor({
+ name = "your-plugin",
+ dsc = "无",
+ handler,
+ namespace,
+ event = "message",
+ priority = 5000,
+ task = { fnc: "", cron: "" },
+ rule = []
+ }) {
/** 插件名称 */
this.name = name
/** 插件描述 */
@@ -45,18 +48,18 @@ export default class plugin {
/** 定时任务,可以是数组 */
this.task = {
/** 任务名 */
- name: '',
+ name: "",
/** 任务方法名 */
- fnc: task.fnc || '',
+ fnc: task.fnc || "",
/** 任务cron表达式 */
- cron: task.cron || ''
+ cron: task.cron || ""
}
/** 命令规则 */
this.rule = rule
if (handler) {
this.handler = handler
- this.namespace = namespace || ''
+ this.namespace = namespace || ""
}
}
@@ -66,12 +69,12 @@ export default class plugin {
* @param data.recallMsg 群聊是否撤回消息,0-120秒,0不撤回
* @param data.at 是否at用户
*/
- reply (msg = '', quote = false, data = {}) {
- if (!this.e.reply || !msg) return false
+ reply(msg = "", quote = false, data = {}) {
+ if (!this.e?.reply || !msg) return false
return this.e.reply(msg, quote, data)
}
- conKey (isGroup = false) {
+ conKey(isGroup = false) {
if (isGroup) {
return `${this.name}.${this.e.group_id}`
} else {
@@ -84,45 +87,36 @@ export default class plugin {
* @param isGroup 是否群聊
* @param time 操作时间,默认120秒
*/
- setContext (type, isGroup = false, time = 120) {
- let key = this.conKey(isGroup)
+ setContext(type, isGroup, time = 120) {
+ const key = this.conKey(isGroup)
if (!stateArr[key]) stateArr[key] = {}
stateArr[key][type] = this.e
- if (time) {
- /** 操作时间 */
- setTimeout(() => {
- if (stateArr[key][type]) {
- delete stateArr[key][type]
- this.e.reply('操作超时已取消', true)
- }
- }, time * 1000)
- }
- }
-
- getContext () {
- let key = this.conKey()
- return stateArr[key]
+ if (time) stateArr[key][type].timeout = setTimeout(() => {
+ if (stateArr[key][type]) {
+ delete stateArr[key][type]
+ this.reply("操作超时已取消", true)
+ }
+ }, time * 1000)
}
- getContextGroup () {
- let key = this.conKey(true)
- return stateArr[key]
+ getContext(type, isGroup) {
+ if (type) return stateArr[this.conKey(isGroup)]?.[type]
+ return stateArr[this.conKey(isGroup)]
}
/**
* @param type 执行方法
* @param isGroup 是否群聊
*/
- finish (type, isGroup = false) {
- if (stateArr[this.conKey(isGroup)] && stateArr[this.conKey(isGroup)][type]) {
- delete stateArr[this.conKey(isGroup)][type]
+ finish(type, isGroup) {
+ const key = this.conKey(isGroup)
+ if (stateArr[key] && stateArr[key][type]) {
+ clearTimeout(stateArr[key][type].timeout)
+ delete stateArr[key][type]
}
}
- async renderImg (plugin, tpl, data, cfg) {
- return Common.render(plugin, tpl, data, {
- ...cfg,
- e: this.e
- })
+ async renderImg(plugin, tpl, data, cfg) {
+ return Common.render(plugin, tpl, data, { ...cfg, e: this.e })
}
-}
+}
\ No newline at end of file
diff --git a/Yunzai/lib/plugins/runtime.js b/Yunzai/lib/plugins/runtime.js
index ae912ac725e290b2173835b803561af0adc141d6..40e77e0265255b7230f142cb2d8a201621fe774c 100644
--- a/Yunzai/lib/plugins/runtime.js
+++ b/Yunzai/lib/plugins/runtime.js
@@ -4,25 +4,29 @@
* 提供一些常用的运行时变量、方法及model获取
* 降低对目录结构的依赖
*/
-import lodash from 'lodash'
-import fs from 'node:fs'
-import gsCfg from '../../plugins/genshin/model/gsCfg.js'
-import common from '../common/common.js'
-import cfg from '../config/config.js'
-import MysApi from '../../plugins/genshin/model/mys/mysApi.js'
-import MysInfo from '../../plugins/genshin/model/mys/mysInfo.js'
-import puppeteer from '../puppeteer/puppeteer.js'
-import { Version } from '#miao'
-import NoteUser from '../../plugins/genshin/model/mys/NoteUser.js'
-import MysUser from '../../plugins/genshin/model/mys/MysUser.js'
-import Handler from './handler.js'
+import lodash from "lodash"
+import fs from "node:fs/promises"
+import common from "../common/common.js"
+import cfg from "../config/config.js"
+import puppeteer from "../puppeteer/puppeteer.js"
+import Handler from "./handler.js"
+
+let gsCfg, MysApi, MysInfo, NoteUser, MysUser, Version
+try {
+ gsCfg = (await import("../../plugins/genshin/model/gsCfg.js")).default
+ MysApi = (await import("../../plugins/genshin/model/mys/mysApi.js")).default
+ MysInfo = (await import("../../plugins/genshin/model/mys/mysInfo.js")).default
+ NoteUser = (await import("../../plugins/genshin/model/mys/NoteUser.js")).default
+ MysUser = (await import("../../plugins/genshin/model/mys/MysUser.js")).default
+ Version = (await import("#miao")).Version
+} catch (err) {}
/**
* 常用的处理方法
*/
export default class Runtime {
- constructor (e) {
+ constructor(e) {
this.e = e
this._mysInfo = {}
@@ -33,55 +37,54 @@ export default class Runtime {
}
}
- get uid () {
+ get uid() {
return this.user?.uid
}
- get hasCk () {
+ get hasCk() {
return this.user?.hasCk
}
- get user () {
+ get user() {
return this.e.user
}
- get cfg () {
+ get cfg() {
return cfg
}
- get gsCfg () {
+ get gsCfg() {
return gsCfg
}
- get common () {
+ get common() {
return common
}
- get puppeteer () {
+ get puppeteer() {
return puppeteer
}
- get MysInfo () {
+ get MysInfo() {
return MysInfo
}
- get NoteUser () {
+ get NoteUser() {
return NoteUser
}
- get MysUser () {
+ get MysUser() {
return MysUser
}
- static async init (e) {
- await MysInfo.initCache()
- let runtime = new Runtime(e)
- e.runtime = runtime
- await runtime.initUser()
- return runtime
+ static async init(e) {
+ if (MysInfo) await MysInfo.initCache()
+ e.runtime = new Runtime(e)
+ if (NoteUser) await e.runtime.initUser()
+ return e.runtime
}
- async initUser () {
+ async initUser() {
let e = this.e
let user = await NoteUser.create(e)
if (user) {
@@ -89,24 +92,24 @@ export default class Runtime {
get (self, key, receiver) {
let game = e.game
let fnMap = {
- uid: 'getUid',
- uidList: 'getUidList',
- mysUser: 'getMysUser',
- ckUidList: 'getCkUidList'
+ uid: "getUid",
+ uidList: "getUidList",
+ mysUser: "getMysUser",
+ ckUidList: "getCkUidList"
}
if (fnMap[key]) {
return self[fnMap[key]](game)
}
- if (key === 'uidData') {
- return self.getUidData('', game)
+ if (key === "uidData") {
+ return self.getUidData("", game)
}
- if (['getUid', 'getUidList', 'getMysUser', 'getCkUidList', 'getUidMapList', 'getGameDs'].includes(key)) {
+ if (["getUid", "getUidList", "getMysUser", "getCkUidList", "getUidMapList", "getGameDs"].includes(key)) {
return (_game, arg2) => {
return self[key](_game || game, arg2)
}
}
- if (['getUidData', 'hasUid', 'addRegUid', 'delRegUid', 'setMainUid'].includes(key)) {
- return (uid, _game = '') => {
+ if (["getUidData", "hasUid", "addRegUid", "delRegUid", "setMainUid"].includes(key)) {
+ return (uid, _game = "") => {
return self[key](uid, _game || game)
}
}
@@ -122,14 +125,14 @@ export default class Runtime {
* @param targetType all: 所有用户均可, cookie:查询用户必须具备Cookie
* @returns {Promise}
*/
- async getMysInfo (targetType = 'all') {
+ async getMysInfo(targetType = "all") {
if (!this._mysInfo[targetType]) {
- this._mysInfo[targetType] = await MysInfo.init(this.e, targetType === 'cookie' ? 'detail' : 'roleIndex')
+ this._mysInfo[targetType] = await MysInfo.init(this.e, targetType === "cookie" ? "detail" : "roleIndex")
}
return this._mysInfo[targetType]
}
- async getUid () {
+ async getUid() {
return await MysInfo.getUid(this.e)
}
@@ -140,7 +143,7 @@ export default class Runtime {
* @param option MysApi option
* @returns {Promise}
*/
- async getMysApi (targetType = 'all', option = {}) {
+ async getMysApi(targetType = "all", option = {}) {
let mys = await this.getMysInfo(targetType)
if (mys.uid && mys?.ckInfo?.ck) {
return new MysApi(mys.uid, mys.ckInfo.ck, option)
@@ -155,7 +158,7 @@ export default class Runtime {
* @param option
* @returns {Promise}
*/
- async createMysApi (uid, ck, option) {
+ async createMysApi(uid, ck, option) {
return new MysApi(uid, ck, option)
}
@@ -172,27 +175,17 @@ export default class Runtime {
* @param cfg.beforeRender({data}) 可改写渲染的data数据
* @returns {Promise}
*/
- async render (plugin, path, data = {}, cfg = {}) {
+ async render(plugin, path, data = {}, cfg = {}) {
// 处理传入的path
- path = path.replace(/.html$/, '')
- let paths = lodash.filter(path.split('/'), (p) => !!p)
- path = paths.join('/')
+ path = path.replace(/.html$/, "")
+ let paths = lodash.filter(path.split("/"), (p) => !!p)
+ path = paths.join("/")
// 创建目录
- const mkdir = (check) => {
- let currDir = `${process.cwd()}/temp`
- for (let p of check.split('/')) {
- currDir = `${currDir}/${p}`
- if (!fs.existsSync(currDir)) {
- fs.mkdirSync(currDir)
- }
- }
- return currDir
- }
- mkdir(`html/${plugin}/${path}`)
+ await Bot.mkdir(`temp/html/${plugin}/${path}`)
// 自动计算pluResPath
- let pluResPath = `../../../${lodash.repeat('../', paths.length)}plugins/${plugin}/resources/`
- let miaoResPath = `../../../${lodash.repeat('../', paths.length)}plugins/miao-plugin/resources/`
- const layoutPath = process.cwd() + '/plugins/miao-plugin/resources/common/layout/'
+ let pluResPath = `../../../${lodash.repeat("../", paths.length)}plugins/${plugin}/resources/`
+ let miaoResPath = `../../../${lodash.repeat("../", paths.length)}plugins/miao-plugin/resources/`
+ const layoutPath = process.cwd() + "/plugins/miao-plugin/resources/common/layout/"
// 渲染data
data = {
sys: {
@@ -202,9 +195,9 @@ export default class Runtime {
copyright: `Created By TRSS-Yunzai${Version.yunzai} `,
_res_path: pluResPath,
_miao_path: miaoResPath,
- _tpl_path: process.cwd() + '/plugins/miao-plugin/resources/common/tpl/',
- defaultLayout: layoutPath + 'default.html',
- elemLayout: layoutPath + 'elem.html',
+ _tpl_path: process.cwd() + "/plugins/miao-plugin/resources/common/tpl/",
+ defaultLayout: layoutPath + "default.html",
+ elemLayout: layoutPath + "elem.html",
...data,
@@ -215,7 +208,7 @@ export default class Runtime {
tplFile: `./plugins/${plugin}/resources/${path}.html`,
saveId: data.saveId || data.save_id || paths[paths.length - 1],
pageGotoParams: {
- waitUntil: 'networkidle2'
+ waitUntil: "networkidle2"
}
}
// 处理beforeRender
@@ -223,16 +216,16 @@ export default class Runtime {
data = cfg.beforeRender({ data }) || data
}
// 保存模板数据
- if (process.argv.includes('dev')) {
+ if (process.argv.includes("dev")) {
// debug下保存当前页面的渲染数据,方便模板编写与调试
// 由于只用于调试,开发者只关注自己当时开发的文件即可,暂不考虑app及plugin的命名冲突
- let saveDir = mkdir(`ViewData/${plugin}`)
- let file = `${saveDir}/${data._htmlPath.split('/').join('_')}.json`
- fs.writeFileSync(file, JSON.stringify(data))
+ let saveDir = await Bot.mkdir(`temp/ViewData/${plugin}`)
+ let file = `${saveDir}/${data._htmlPath.split("/").join("_")}.json`
+ await fs.writeFile(file, JSON.stringify(data))
}
// 截图
let base64 = await puppeteer.screenshot(`${plugin}/${path}`, data)
- if (cfg.retType === 'base64') {
+ if (cfg.retType === "base64") {
return base64
}
let ret = true
@@ -243,6 +236,6 @@ export default class Runtime {
ret = await this.e.reply(base64)
}
}
- return cfg.retType === 'msgId' ? ret : true
+ return cfg.retType === "msgId" ? ret : true
}
}
diff --git a/Yunzai/lib/plugins/stdin.js b/Yunzai/lib/plugins/stdin.js
index b833d87cc5e99a3f1da98b4e5b4e74ae153ddd58..fad03f37d39544aae987ecf44c421b6f3f1ad8fe 100644
--- a/Yunzai/lib/plugins/stdin.js
+++ b/Yunzai/lib/plugins/stdin.js
@@ -1,13 +1,11 @@
-import fs from "node:fs"
+import fs from "node:fs/promises"
import path from "node:path"
-import common from "../common/common.js"
Bot.adapter.push(new class stdinAdapter {
constructor() {
this.id = "stdin"
this.name = "标准输入"
this.path = "data/stdin/"
- common.mkdirs(this.path)
}
async sendMsg(msg) {
@@ -19,10 +17,10 @@ Bot.adapter.push(new class stdinAdapter {
let file
if (i.file) {
- file = await Bot.fileType(i.file, i.name)
+ file = await Bot.fileType(i)
if (Buffer.isBuffer(file.buffer)) {
file.path = `${this.path}${file.name || Date.now()}`
- fs.writeFileSync(file.path, file.buffer)
+ await fs.writeFile(file.path, file.buffer)
}
}
@@ -71,7 +69,7 @@ Bot.adapter.push(new class stdinAdapter {
const files = `${this.path}${Date.now()}-${name}`
logger.info(`${logger.blue(`[${this.id}]`)} 发送文件:${file}\n文件已保存到:${logger.cyan(files)}`)
- return fs.writeFileSync(files, buffer)
+ return fs.writeFile(files, buffer)
}
pickFriend() {
@@ -103,14 +101,15 @@ Bot.adapter.push(new class stdinAdapter {
Bot.em(`${data.post_type}.${data.message_type}`, data)
}
- load() {
+ async load() {
+ await Bot.mkdir(this.path)
Bot[this.id] = {
adapter: this,
uin: this.id,
nickname: this.name,
- stat: { start_time: Date.now()/1000 },
version: { id: this.id, name: this.name },
pickFriend: () => this.pickFriend(),
+ get stat() { return Bot.stat },
get pickUser() { return this.pickFriend },
get pickMember() { return this.pickFriend },
get pickGroup() { return this.pickFriend },
diff --git a/Yunzai/lib/renderer/loader.js b/Yunzai/lib/renderer/loader.js
index f7506119c70c1171bd9e82a74772f7234f419d7d..129fe6144ef83fceaa3494c2f3b4c5f38c1babd2 100644
--- a/Yunzai/lib/renderer/loader.js
+++ b/Yunzai/lib/renderer/loader.js
@@ -1,9 +1,13 @@
-import fs from 'node:fs'
-import yaml from 'yaml'
-import lodash from 'lodash'
-import cfg from '../config/config.js'
-import { Data } from '#miao'
-import Renderer from './Renderer.js'
+import fs from "node:fs"
+import yaml from "yaml"
+import lodash from "lodash"
+import cfg from "../config/config.js"
+import Renderer from "./Renderer.js"
+
+let Data
+try {
+ Data = (await import("#miao")).Data
+} catch (err) {}
/** 全局变量 Renderer */
global.Renderer = Renderer
@@ -14,7 +18,7 @@ global.Renderer = Renderer
class RendererLoader {
constructor() {
this.renderers = new Map()
- this.dir = './renderers'
+ this.dir = "./renderers"
// TODO 渲染器热加载
this.watcher = {}
}
@@ -26,16 +30,17 @@ class RendererLoader {
}
async load() {
+ if (!Data) return
const subFolders = fs.readdirSync(this.dir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory())
for (let subFolder of subFolders) {
let name = subFolder.name
try {
const rendererFn = await Data.importDefault(`${this.dir}/${name}/index.js`)
let configFile = `${this.dir}/${name}/config.yaml`
- let rendererCfg = fs.existsSync(configFile) ? yaml.parse(fs.readFileSync(configFile, 'utf8')) : {}
+ let rendererCfg = fs.existsSync(configFile) ? yaml.parse(fs.readFileSync(configFile, "utf8")) : {}
let renderer = rendererFn(rendererCfg)
if (!renderer.id || !renderer.type || !renderer.render || !lodash.isFunction(renderer.render)) {
- logger.warn('渲染后端 ' + (renderer.id || subFolder.name) + ' 不可用')
+ logger.warn("渲染后端 " + (renderer.id || subFolder.name) + " 不可用")
}
this.renderers.set(renderer.id, renderer)
logger.info(`加载渲染后端 ${renderer.id}`)
@@ -46,9 +51,9 @@ class RendererLoader {
}
}
- getRenderer(name = cfg.renderer?.name || 'puppeteer') {
+ getRenderer(name = cfg.renderer?.name || "puppeteer") {
// TODO 渲染器降级
- return this.renderers.get(name)
+ return this.renderers.get(name) || {}
}
}
diff --git a/Yunzai/lib/tools/web.js b/Yunzai/lib/tools/web.js
index 32ff156fdfc500239352f888c3da06d4421c562d..890e7adaa289004344c4939d6277e4d9d7b090ba 100644
--- a/Yunzai/lib/tools/web.js
+++ b/Yunzai/lib/tools/web.js
@@ -1,7 +1,7 @@
-import express from 'express'
-import template from 'express-art-template'
-import fs from 'fs'
-import lodash from 'lodash'
+import express from "express"
+import template from "express-art-template"
+import fs from "node:fs/promises"
+import lodash from "lodash"
/*
* npm run app web-debug开启Bot后
@@ -17,53 +17,53 @@ let app = express()
let _path = process.cwd()
-app.engine('html', template)
-app.set('views', _path + '/resources/')
-app.set('view engine', 'art')
-app.use(express.static(_path + '/resources'))
-app.use('/plugins', express.static('plugins'))
+app.engine("html", template)
+app.set("views", _path + "/resources/")
+app.set("view engine", "art")
+app.use(express.static(_path + "/resources"))
+app.use("/plugins", express.static("plugins"))
-app.get('/', function (req, res) {
- let pluginList = fs.readdirSync(_path + '/temp/ViewData/') || []
+app.get("/", async function (req, res) {
+ let pluginList = await fs.readdir(_path + "/temp/ViewData/") || []
let html = [
- '在npm run web-dev模式下触发截图消息后,可在下方选择页面进行调试',
- '如果页面内资源路径不正确请使用{{_res_path}}作为根路径,对应之前的../../../../',
- '可直接修改模板html或css刷新查看效果'
+ "在npm run web-dev模式下触发截图消息后,可在下方选择页面进行调试",
+ "如果页面内资源路径不正确请使用{{_res_path}}作为根路径,对应之前的../../../../",
+ "可直接修改模板html或css刷新查看效果"
]
let li = {}
for (let pIdx in pluginList) {
const plugin = pluginList[pIdx]
- let fileList = fs.readdirSync(_path + `/temp/ViewData/${plugin}/`) || []
+ let fileList = await fs.readdir(_path + `/temp/ViewData/${plugin}/`) || []
for (let idx in fileList) {
let ret = /(.+)\.json$/.exec(fileList[idx])
if (ret && ret[1]) {
- let text = [plugin, ...ret[1].split('_')]
- li[text.join('')] = (`${text.join(' / ')}`)
+ let text = [plugin, ...ret[1].split("_")]
+ li[text.join("")] = (`${text.join(" / ")}`)
}
}
}
- res.send(html.join('') + '' + lodash.values(li).join('') + '
')
+ res.send(html.join("") + "" + lodash.values(li).join("") + "
")
})
-app.get('/:page', function (req, res) {
- let [plugin, app, ...page] = req.params.page.split('_')
- page = page.join('_')
- if (plugin == 'favicon.ico') {
- return res.send('')
+app.get("/:page", async function (req, res) {
+ let [plugin, app, ...page] = req.params.page.split("_")
+ page = page.join("_")
+ if (plugin == "favicon.ico") {
+ return res.send("")
}
- let data = JSON.parse(fs.readFileSync(_path + `/temp/ViewData/${plugin}/${app}_${page}.json`, 'utf8'))
+ let data = JSON.parse(await fs.readFile(_path + `/temp/ViewData/${plugin}/${app}_${page}.json`, "utf8"))
data = data || {}
- data._res_path = ''
+ data._res_path = ""
data._sys_res_path = data._res_path
if (data._plugin) {
data._res_path = `/plugins/${data._plugin}/resources/`
data.pluResPath = data._res_path
}
- let htmlPath = ''
+ let htmlPath = ""
let tplPath = `${app}/${htmlPath}${page}/${page}.html`
if (data._plugin) {
- tplPath = `../plugins/${data._plugin}/resources/${htmlPath}/${app}/${page.split('_').join('/')}.html`
+ tplPath = `../plugins/${data._plugin}/resources/${htmlPath}/${app}/${page.split("_").join("/")}.html`
} else if (data._no_type_path) {
tplPath = `${app}/${page}.html`
}
@@ -71,4 +71,4 @@ app.get('/:page', function (req, res) {
})
app.listen(8000)
-console.log('页面服务已启动,触发消息图片后访问 http://localhost:8000/ 调试页面')
+console.log("页面服务已启动,触发消息图片后访问 http://localhost:8000/ 调试页面")
diff --git a/Yunzai/package.json b/Yunzai/package.json
index 014205becac877aa6205699b20f40c1317372995..9fe91fdf3dba709ec66537c81a9a005a0b051b69 100644
--- a/Yunzai/package.json
+++ b/Yunzai/package.json
@@ -8,41 +8,41 @@
"scripts": {
"app": "node .",
"dev": "node . dev",
- "web": "node ./lib/tools/web.js",
- "test": "node ./lib/tools/test.js",
- "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/log.js"
+ "web": "node lib/tools/web.js",
+ "start": "pm2 start config/pm2.yaml",
+ "stop": "pm2 stop config/pm2.yaml",
+ "restart": "pm2 restart config/pm2.yaml",
+ "log": "pm2 log --lines 100"
},
"dependencies": {
"art-template": "^4.13.2",
"chalk": "^5.3.0",
- "chokidar": "^3.5.3",
- "express": "^4.18.2",
- "file-type": "^18.7.0",
- "https-proxy-agent": "7.0.2",
- "image-size": "^1.0.2",
+ "chokidar": "^3.6.0",
+ "express": "^4.19.1",
+ "file-type": "^19.0.0",
+ "https-proxy-agent": "7.0.4",
+ "image-size": "^1.1.1",
+ "level": "^8.0.1",
"lodash": "^4.17.21",
"log4js": "^6.9.1",
- "md5": "^2.3.0",
+ "md5": "link:lib/modules/md5",
"moment": "^2.30.1",
- "node-fetch": "^3.3.2",
+ "node-fetch": "link:lib/modules/node-fetch",
"node-schedule": "^2.1.1",
"oicq": "link:lib/modules/oicq",
- "pm2": "^5.3.0",
- "puppeteer": "^21.6.1",
- "redis": "^4.6.12",
- "sequelize": "^6.35.2",
- "sqlite3": "^5.1.6",
+ "pm2": "^5.3.1",
+ "puppeteer": "*",
+ "redis": "^4.6.13",
+ "sequelize": "^6.37.1",
+ "sqlite3": "5.1.6",
"ws": "^8.16.0",
- "yaml": "^2.3.4"
+ "yaml": "^2.4.1"
},
"devDependencies": {
- "eslint": "^8.56.0",
+ "eslint": "^8.57.0",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.29.1",
- "eslint-plugin-n": "^16.5.0",
+ "eslint-plugin-n": "^16.6.2",
"eslint-plugin-promise": "^6.1.1"
},
"imports": {
diff --git a/Yunzai/plugins/adapter/ComWeChat.js b/Yunzai/plugins/adapter/ComWeChat.js
index a0b910c93434355b9f14d86fb45f222b6f6a7e31..ddc424820f2d3975f37457367ba851a74925e1d0 100644
--- a/Yunzai/plugins/adapter/ComWeChat.js
+++ b/Yunzai/plugins/adapter/ComWeChat.js
@@ -1,7 +1,5 @@
-import { randomUUID } from "crypto"
+import { randomUUID } from "node:crypto"
import path from "node:path"
-import fs from "node:fs"
-import { fileTypeFromBuffer } from "file-type"
Bot.adapter.push(new class ComWeChatAdapter {
constructor() {
@@ -10,71 +8,38 @@ Bot.adapter.push(new class ComWeChatAdapter {
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..."')
+ return Bot.String(msg).replace(/(base64:\/\/|"type":"data","data":").*?"/g, '$1..."')
}
sendApi(ws, action, params = {}) {
const echo = randomUUID()
- const msg = { action, params, echo }
- ws.sendMsg(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
+ ws.sendMsg({ action, params, echo })
+ return new Promise(resolve => Bot.once(echo, data =>
+ resolve({ ...data, ...data.data })
+ ))
}
- async uploadFile(data, file, name) {
- const opts = { name: name || await this.fileName(file) || randomUUID() }
+ async uploadFile(data, file) {
+ file = await Bot.fileType(file, { http: true })
+ const opts = { name: file.name }
- if (file.match(/^https?:\/\//)) {
- opts.type = "url"
- opts.url = file
- } else if (file.match(/^base64:\/\//)) {
+ if (Buffer.isBuffer(file.buffer)) {
opts.type = "data"
- opts.data = file.replace(/^base64:\/\//, "")
- } else if (fs.existsSync(file)) {
- opts.type = "data"
- opts.data = fs.readFileSync(file).toString("base64")
+ 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
+ opts.path = file.buffer
}
- logger.info(`${logger.blue(`[${data.self_id}]`)} 上传文件:${this.makeLog(opts)}`)
+ Bot.makeLog("info", `上传文件:${this.makeLog(opts)}`, data.self_id)
return data.bot.sendApi("upload_file", opts)
}
- async makeMsg(data, msg) {
+ async makeMsg(data, msg, send) {
if (!Array.isArray(msg))
msg = [msg]
const msgs = []
@@ -84,7 +49,7 @@ Bot.adapter.push(new class ComWeChatAdapter {
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 }
+ i.data = { file_id: (await this.uploadFile(data, i.data)).file_id }
switch (i.type) {
case "text":
@@ -104,6 +69,10 @@ Bot.adapter.push(new class ComWeChatAdapter {
i = { type: "mention", data: { user_id: i.data.qq }}
break
case "reply":
+ case "button":
+ continue
+ case "node":
+ await Bot.sendForwardMsg(send, i.data)
continue
default:
i = { type: "text", data: { text: JSON.stringify(i) }}
@@ -114,11 +83,8 @@ Bot.adapter.push(new class ComWeChatAdapter {
}
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)}`)
+ 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,
@@ -127,11 +93,8 @@ Bot.adapter.push(new class ComWeChatAdapter {
}
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)}`)
+ 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,
@@ -312,7 +275,7 @@ Bot.adapter.push(new class ComWeChatAdapter {
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.makeLog("mark", `${this.name}(${this.id}) ${data.bot.version.impl}-${data.bot.version.version} 已连接`, data.self_id)
Bot.em(`connect.${data.self_id}`, data)
}
@@ -348,13 +311,13 @@ Bot.adapter.push(new class ComWeChatAdapter {
switch (data.message_type) {
case "private":
- logger.info(`${logger.blue(`[${data.self_id}]`)} 好友消息:[${data.user_id}] ${data.raw_message}`)
+ Bot.makeLog("info", `好友消息:${data.raw_message}`, `${data.self_id} <= ${data.user_id}`)
break
case "group":
- logger.info(`${logger.blue(`[${data.self_id}]`)} 群消息:[${data.group_id}, ${data.user_id}] ${data.raw_message}`)
+ Bot.makeLog("info", `群消息:${data.raw_message}`, `${data.self_id} <= ${data.group_id}, ${data.user_id}`)
break
default:
- logger.warn(`${logger.blue(`[${data.self_id}]`)} 未知消息:${logger.magenta(JSON.stringify(data))}`)
+ Bot.makeLog("warn", `未知消息:${logger.magenta(data.raw)}`, data.self_id)
}
Bot.em(`${data.post_type}.${data.message_type}`, data)
@@ -369,43 +332,43 @@ Bot.adapter.push(new class ComWeChatAdapter {
switch (data.detail_type) {
case "private_message_delete":
- logger.info(`${logger.blue(`[${data.self_id}]`)} 好友消息撤回:[${data.user_id}] ${data.message_id}`)
+ Bot.makeLog("info", `好友消息撤回:${data.message_id}`, `${data.self_id} <= ${data.user_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}`)
+ 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":
- logger.info(`${logger.blue(`[${data.self_id}]`)} 私聊文件:[${data.user_id}] ${data.file_name} ${data.file_length} ${data.md5}`)
+ Bot.makeLog("info", `私聊文件:${data.file_name} ${data.file_length} ${data.md5}`, `${data.self_id} <= ${data.user_id}`)
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}`)
+ 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":
- logger.info(`${logger.blue(`[${data.self_id}]`)} 好友红包:[${data.user_id}]`)
+ Bot.makeLog("info", `好友红包`, `${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}]`)
+ 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
- logger.info(`${logger.blue(`[${data.self_id}]`)} 好友拍一拍:[${data.operator_id}=>${data.target_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
- logger.info(`${logger.blue(`[${data.self_id}]`)} 群拍一拍:[${data.group_id}, ${data.operator_id}=>${data.target_id}]`)
+ Bot.makeLog("info", `群拍一拍:${data.operator_id} => ${data.target_id}`, `${data.self_id} <= ${data.group_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}`)
+ 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":
- 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}`)
+ 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:
- logger.warn(`${logger.blue(`[${data.self_id}]`)} 未知通知:${logger.magenta(JSON.stringify(data))}`)
+ Bot.makeLog("warn", `未知通知:${logger.magenta(data.raw)}`, data.self_id)
}
if (!data.sub_type)
data.sub_type = data.detail_type.split("_").pop()
@@ -422,11 +385,11 @@ Bot.adapter.push(new class ComWeChatAdapter {
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}`)
+ 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:
- logger.warn(`${logger.blue(`[${data.self_id}]`)} 未知请求:${logger.magenta(JSON.stringify(data))}`)
+ Bot.makeLog("warn", `未知请求:${logger.magenta(data.raw)}`, data.self_id)
}
if (!data.sub_type)
data.sub_type = data.detail_type.split("_").pop()
@@ -444,15 +407,18 @@ Bot.adapter.push(new class ComWeChatAdapter {
this.connect(data, ws)
break
default:
- logger.warn(`${logger.blue(`[${data.self_id}]`)} 未知消息:${logger.magenta(JSON.stringify(data))}`)
+ Bot.makeLog("warn", `未知消息:${logger.magenta(data.raw)}`, data.self_id)
}
}
message(data, ws) {
try {
- data = JSON.parse(data)
+ data = {
+ ...JSON.parse(data),
+ raw: Bot.String(data),
+ }
} catch (err) {
- return logger.error(`解码数据失败:${logger.red(err)}`)
+ return Bot.makeLog("error", ["解码数据失败", data, err])
}
if (data.self?.user_id) {
@@ -463,7 +429,7 @@ Bot.adapter.push(new class ComWeChatAdapter {
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))}`)
+ Bot.makeLog("warn", `找不到对应Bot,忽略消息:${logger.magenta(data.raw)}`, data.self_id)
return false
}
data.bot = Bot[data.self_id]
@@ -482,12 +448,12 @@ Bot.adapter.push(new class ComWeChatAdapter {
this.makeRequest(data)
break
default:
- logger.warn(`${logger.blue(`[${data.self_id}]`)} 未知消息:${logger.magenta(JSON.stringify(data))}`)
+ Bot.makeLog("warn", `未知消息:${logger.magenta(data.raw)}`, data.self_id)
}
} else if (data.echo) {
Bot.emit(data.echo, data)
} else {
- logger.warn(`${logger.blue(`[${data.self_id}]`)} 未知消息:${logger.magenta(JSON.stringify(data))}`)
+ Bot.makeLog("warn", `未知消息:${logger.magenta(data.raw)}`, data.self_id)
}
}
diff --git a/Yunzai/plugins/adapter/GSUIDCore.js b/Yunzai/plugins/adapter/GSUIDCore.js
index c790d7f5a138c784d8d164d2e9ad03afa2024798..3c1f0ec26e3c5fbecb506336f154536a603fc5fe 100644
--- a/Yunzai/plugins/adapter/GSUIDCore.js
+++ b/Yunzai/plugins/adapter/GSUIDCore.js
@@ -1,7 +1,3 @@
-import { randomUUID } from "crypto"
-import path from "node:path"
-import fs from "node:fs"
-
Bot.adapter.push(new class GSUIDCoreAdapter {
constructor() {
this.id = "GSUIDCore"
@@ -9,26 +5,55 @@ Bot.adapter.push(new class GSUIDCoreAdapter {
this.path = this.id
}
- 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 Bot.String(msg).replace(/base64:\/\/.*?"/g, "base64://...\"")
}
- makeLog(msg) {
- return this.toStr(msg).replace(/base64:\/\/.*?"/g, "base64://...\"")
+ makeButton(data, button) {
+ const msg = {
+ text: button.text,
+ pressed_text: button.clicked_text,
+ ...button.GSUIDCore,
+ }
+
+ if (button.input) {
+ msg.data = button.input
+ msg.action = 2
+ } else if (button.callback) {
+ msg.data = button.callback
+ msg.action = 1
+ } else if (button.link) {
+ msg.data = button.link
+ msg.action = 0
+ } else return false
+
+ if (button.permission) {
+ if (button.permission == "admin") {
+ msg.permission = 1
+ } else {
+ msg.permission = 0
+ if (!Array.isArray(button.permission))
+ button.permission = [button.permission]
+ msg.specify_user_ids = button.permission
+ }
+ }
+ return msg
+ }
+
+ makeButtons(button_square) {
+ const msgs = []
+ for (const button_row of button_square) {
+ const buttons = []
+ for (let button of button_row) {
+ button = this.makeButton(button)
+ if (button) buttons.push(button)
+ }
+ msgs.push(buttons)
+ }
+ return msgs
}
- makeMsg(msg) {
+ async makeMsg(msg) {
if (!Array.isArray(msg))
msg = [msg]
const msgs = []
@@ -36,6 +61,12 @@ Bot.adapter.push(new class GSUIDCoreAdapter {
if (typeof i != "object")
i = { type: "text", text: i }
+ if (i.file) {
+ i.file = await Bot.Buffer(i.file, { http: true })
+ if (Buffer.isBuffer(i.file))
+ i.file = `base64://${i.file.toString("base64")}`
+ }
+
switch (i.type) {
case "text":
i = { type: "text", data: i.text }
@@ -44,7 +75,7 @@ Bot.adapter.push(new class GSUIDCoreAdapter {
i = { type: "image", data: i.file }
break
case "record":
- i = { type: "file", data: i.file }
+ i = { type: "record", data: i.file }
break
case "video":
i = { type: "file", data: i.file }
@@ -58,10 +89,15 @@ Bot.adapter.push(new class GSUIDCoreAdapter {
case "reply":
i = { type: "reply", data: i.id }
break
+ case "button":
+ i = { type: "buttons", data: this.makeButtons(i.data) }
+ break
+ case "markdown":
+ break
case "node": {
const array = []
for (const { message } of i.data)
- array.push(...this.makeMsg(message))
+ array.push(...await this.makeMsg(message))
i.data = array
break
} default:
@@ -72,9 +108,9 @@ Bot.adapter.push(new class GSUIDCoreAdapter {
return msgs
}
- sendFriendMsg(data, msg) {
- const content = this.makeMsg(msg)
- logger.info(`${logger.blue(`[${data.self_id} => ${data.user_id}]`)} 发送好友消息:${this.makeLog(content)}`)
+ async sendFriendMsg(data, msg) {
+ const content = await this.makeMsg(msg)
+ Bot.makeLog("info", `发送好友消息:${this.makeLog(content)}`, `${data.self_id} => ${data.user_id}`)
data.bot.sendApi({
bot_id: data.bot.bot_id,
bot_self_id: data.bot.bot_self_id,
@@ -85,10 +121,10 @@ Bot.adapter.push(new class GSUIDCoreAdapter {
return { message_id: Date.now() }
}
- sendGroupMsg(data, msg) {
+ async sendGroupMsg(data, msg) {
const target = data.group_id.split("-")
- const content = this.makeMsg(msg)
- logger.info(`${logger.blue(`[${data.self_id} => ${data.group_id}]`)} 发送群消息:${this.makeLog(content)}`)
+ const content = await this.makeMsg(msg)
+ Bot.makeLog("info", `发送群消息:${this.makeLog(content)}`, `${data.self_id} => ${data.group_id}`)
data.bot.sendApi({
bot_id: data.bot.bot_id,
bot_self_id: data.bot.bot_self_id,
@@ -115,6 +151,7 @@ Bot.adapter.push(new class GSUIDCoreAdapter {
pickMember(id, group_id, user_id) {
const i = {
...Bot[id].fl.get(user_id),
+ ...Bot[id].gml.get(group_id)?.get(user_id),
self_id: id,
bot: Bot[id],
group_id: group_id,
@@ -146,8 +183,8 @@ Bot.adapter.push(new class GSUIDCoreAdapter {
ws: ws,
get sendApi() { return this.ws.sendMsg },
uin: data.self_id,
- bot_id: data.bot_id,
- bot_self_id: data.bot_self_id,
+ bot_id: data.raw.bot_id,
+ bot_self_id: data.raw.bot_self_id,
stat: { start_time: Date.now()/1000 },
version: {
id: this.id,
@@ -161,19 +198,34 @@ Bot.adapter.push(new class GSUIDCoreAdapter {
gl: new Map,
gml: new Map,
}
+ data.bot = Bot[data.self_id]
- logger.mark(`${logger.blue(`[${data.self_id}]`)} ${this.name}(${this.id}) 已连接`)
+ Bot.makeLog("mark", `${this.name}(${this.id}) 已连接`, data.self_id)
Bot.em(`connect.${data.self_id}`, data)
}
- message(data, ws) {
+ message(raw, ws) {
try {
- data = JSON.parse(data)
+ raw = JSON.parse(raw)
} catch (err) {
- return logger.error(`解码数据失败:${logger.red(err)}`)
+ return Bot.makeLog("error", ["解码数据失败", raw, err])
+ }
+
+ const data = {
+ raw,
+ self_id: raw.bot_self_id,
+ post_type: "message",
+ message_id: raw.msg_id,
+ get user_id() { return this.sender.user_id },
+ sender: {
+ ...raw.sender,
+ user_id: raw.user_id,
+ user_pm: raw.user_pm,
+ },
+ message: [],
+ raw_message: "",
}
- data.self_id = data.bot_self_id
if (Bot[data.self_id]) {
data.bot = Bot[data.self_id]
data.bot.ws = ws
@@ -181,19 +233,10 @@ Bot.adapter.push(new class GSUIDCoreAdapter {
this.makeBot(data, ws)
}
- data.post_type = "message"
- data.message_id = data.msg_id
- data.user_id = data.user_id
- data.sender = {
- user_id: data.user_id,
- user_pm: data.user_pm,
- }
if (!data.bot.fl.has(data.user_id))
data.bot.fl.set(data.user_id, data.sender)
- data.message = []
- data.raw_message = ""
- for (const i of data.content) {
+ for (const i of raw.content) {
switch (i.type) {
case "text":
data.message.push({ type: "text", text: i.data })
@@ -225,15 +268,24 @@ Bot.adapter.push(new class GSUIDCoreAdapter {
}
}
- if (data.user_type == "direct") {
+ if (raw.user_type == "direct") {
data.message_type = "private"
- logger.info(`${logger.blue(`[${data.self_id}]`)} 好友消息:[${data.user_id}] ${data.raw_message}`)
+ Bot.makeLog("info", `好友消息:${data.raw_message}`, `${data.self_id} <= ${data.user_id}`)
} else {
data.message_type = "group"
- data.group_id = `${data.user_type}-${data.group_id}`
+ data.group_id = `${raw.user_type}-${raw.group_id}`
+
if (!data.bot.gl.has(data.group_id))
data.bot.gl.set(data.group_id, { group_id: data.group_id })
- logger.info(`${logger.blue(`[${data.self_id}]`)} 群消息:[${data.group_id}, ${data.user_id}] ${data.raw_message}`)
+ let gml = data.bot.gml.get(data.group_id)
+ if (!gml) {
+ gml = new Map
+ data.bot.gml.set(data.group_id, gml)
+ }
+ if (!gml.has(data.user_id))
+ gml.set(data.user_id, data.sender)
+
+ Bot.makeLog("info", `群消息:${data.raw_message}`, `${data.self_id} <= ${data.group_id}, ${data.user_id}`)
}
Bot.em(`${data.post_type}.${data.message_type}`, data)
diff --git a/Yunzai/plugins/adapter/OPQBot.js b/Yunzai/plugins/adapter/OPQBot.js
new file mode 100644
index 0000000000000000000000000000000000000000..ba4bd4a772ced364bdec3073f7ba93e057ad1541
--- /dev/null
+++ b/Yunzai/plugins/adapter/OPQBot.js
@@ -0,0 +1,327 @@
+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)))
+ }
+
+ 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
+ default:
+ message.Content += JSON.stringify(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://q1.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://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,
+ }
+
+ 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) {
+ Bot.emit(data.ReqId, data)
+ } 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))
+ )
+ }
+})
\ No newline at end of file
diff --git a/Yunzai/plugins/adapter/OneBotv11.js b/Yunzai/plugins/adapter/OneBotv11.js
new file mode 100644
index 0000000000000000000000000000000000000000..bb861fc43567520d2f969facb08665d5c159e14f
--- /dev/null
+++ b/Yunzai/plugins/adapter/OneBotv11.js
@@ -0,0 +1,989 @@
+import { randomUUID } from "node:crypto"
+import path from "node:path"
+
+Bot.adapter.push(new class OneBotv11Adapter {
+ constructor() {
+ this.id = "QQ"
+ this.name = "OneBotv11"
+ this.path = this.name
+ }
+
+ makeLog(msg) {
+ return Bot.String(msg).replace(/base64:\/\/.*?(,|]|")/g, "base64://...$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 })
+ ))
+ }
+
+ setProfile(data, profile) {
+ Bot.makeLog("info", `设置资料:${JSON.stringify(profile)}`, data.self_id)
+ return data.bot.sendApi("set_qq_profile", profile)
+ }
+
+ async makeFile(file) {
+ file = await Bot.Buffer(file, { http: true })
+ if (Buffer.isBuffer(file))
+ file = `base64://${file.toString("base64")}`
+ return file
+ }
+
+ async makeMsg(msg) {
+ if (!Array.isArray(msg))
+ msg = [msg]
+ const msgs = []
+ const forward = []
+ 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 }}
+
+ switch (i.type) {
+ case "at":
+ i.data.qq = String(i.data.qq)
+ break
+ case "reply":
+ i.data.id = String(i.data.id)
+ break
+ case "button":
+ continue
+ case "node":
+ forward.push(...i.data)
+ continue
+ }
+
+ if (i.data.file)
+ i.data.file = await this.makeFile(i.data.file)
+
+ msgs.push(i)
+ }
+ return [msgs, forward]
+ }
+
+ async sendMsg(msg, send, sendForwardMsg) {
+ const [message, forward] = await this.makeMsg(msg)
+ const ret = []
+
+ if (forward.length) {
+ const data = await sendForwardMsg(forward)
+ if (Array.isArray(data))
+ ret.push(...data)
+ else
+ ret.push(data)
+ }
+
+ if (message.length)
+ ret.push(await send(message))
+ if (ret.length == 1) return ret[0]
+
+ const message_id = []
+ for (const i of ret) if (i?.message_id)
+ message_id.push(i.message_id)
+ return { data: ret, message_id }
+ }
+
+ sendFriendMsg(data, msg) {
+ return this.sendMsg(msg, message => {
+ Bot.makeLog("info", `发送好友消息:${this.makeLog(message)}`, `${data.self_id} => ${data.user_id}`)
+ data.bot.sendApi("send_msg", {
+ user_id: data.user_id,
+ message,
+ })
+ }, msg => this.sendFriendForwardMsg(data, msg))
+ }
+
+ sendGroupMsg(data, msg) {
+ return this.sendMsg(msg, message => {
+ Bot.makeLog("info", `发送群消息:${this.makeLog(message)}`, `${data.self_id} => ${data.group_id}`)
+ return data.bot.sendApi("send_msg", {
+ group_id: data.group_id,
+ message,
+ })
+ }, msg => this.sendGroupForwardMsg(data, msg))
+ }
+
+ sendGuildMsg(data, msg) {
+ return this.sendMsg(msg, message => {
+ Bot.makeLog("info", `发送频道消息:${this.makeLog(message)}`, `${data.self_id}] => ${data.guild_id}-${data.channel_id}`)
+ return data.bot.sendApi("send_guild_channel_msg", {
+ guild_id: data.guild_id,
+ channel_id: data.channel_id,
+ message,
+ })
+ }, msg => Bot.sendForwardMsg(msg => this.sendGuildMsg(data, msg), msg))
+ }
+
+ async recallMsg(data, message_id) {
+ Bot.makeLog("info", `撤回消息:${message_id}`, data.self_id)
+ if (!Array.isArray(message_id))
+ message_id = [message_id]
+ const msgs = []
+ for (const i of message_id)
+ msgs.push(await data.bot.sendApi("delete_msg", { message_id: i }))
+ return msgs
+ }
+
+ parseMsg(msg) {
+ const array = []
+ for (const i of Array.isArray(msg) ? msg : [msg])
+ if (typeof i == "object")
+ array.push({ ...i.data, type: i.type })
+ else
+ array.push({ type: "text", text: String(i) })
+ return array
+ }
+
+ async getMsg(data, message_id) {
+ const msg = (await data.bot.sendApi("get_msg", { message_id })).data
+ if (msg?.message)
+ msg.message = this.parseMsg(msg.message)
+ return msg
+ }
+
+ async getGroupMsgHistory(data, message_seq, count) {
+ const msgs = (await data.bot.sendApi("get_group_msg_history", {
+ group_id: data.group_id,
+ message_seq,
+ count,
+ })).data?.messages
+
+ for (const i of Array.isArray(msgs) ? msgs : [msgs])
+ if (i?.message)
+ i.message = this.parseMsg(i.message)
+ return msgs
+ }
+
+ async getForwardMsg(data, message_id) {
+ const msgs = (await data.bot.sendApi("get_forward_msg", {
+ message_id,
+ })).data?.messages
+
+ for (const i of Array.isArray(msgs) ? msgs : [msgs])
+ if (i?.message)
+ i.message = this.parseMsg(i.message || i.content)
+ return msgs
+ }
+
+ async makeForwardMsg(msg) {
+ const msgs = []
+ for (const i of msg) {
+ const [content, forward] = await this.makeMsg(i.message)
+ if (forward.length)
+ msgs.push(...await this.makeForwardMsg(forward))
+ if (content.length)
+ msgs.push({ type: "node", data: {
+ name: i.nickname || "匿名消息",
+ uin: String(Number(i.user_id) || 80000000),
+ content,
+ time: i.time,
+ }})
+ }
+ return msgs
+ }
+
+ async sendFriendForwardMsg(data, msg) {
+ Bot.makeLog("info", `发送好友转发消息:${this.makeLog(msg)}`, `${data.self_id} => ${data.user_id}`)
+ return data.bot.sendApi("send_private_forward_msg", {
+ user_id: data.user_id,
+ messages: await this.makeForwardMsg(msg),
+ })
+ }
+
+ async sendGroupForwardMsg(data, msg) {
+ Bot.makeLog("info", `发送群转发消息:${this.makeLog(msg)}`, `${data.self_id} => ${data.group_id}`)
+ return data.bot.sendApi("send_group_forward_msg", {
+ group_id: data.group_id,
+ messages: await this.makeForwardMsg(msg),
+ })
+ }
+
+ async getFriendArray(data) {
+ return (await data.bot.sendApi("get_friend_list")).data || []
+ }
+
+ async getFriendList(data) {
+ const array = []
+ for (const { user_id } of await this.getFriendArray(data))
+ array.push(user_id)
+ return array
+ }
+
+ async getFriendMap(data) {
+ const map = new Map
+ for (const i of await this.getFriendArray(data))
+ map.set(i.user_id, i)
+ data.bot.fl = map
+ return map
+ }
+
+ getFriendInfo(data) {
+ return data.bot.sendApi("get_stranger_info", {
+ user_id: data.user_id,
+ })
+ }
+
+ async getGroupArray(data) {
+ const array = (await data.bot.sendApi("get_group_list")).data
+ try { for (const guild of await this.getGuildArray(data))
+ for (const channel of await this.getGuildChannelArray({
+ ...data,
+ guild_id: guild.guild_id,
+ }))
+ array.push({
+ guild,
+ channel,
+ group_id: `${guild.guild_id}-${channel.channel_id}`,
+ group_name: `${guild.guild_name}-${channel.channel_name}`,
+ })
+ } catch (err) {
+ Bot.makeLog("error", ["获取频道列表错误", err])
+ }
+ return array
+ }
+
+ async getGroupList(data) {
+ const array = []
+ for (const { group_id } of await this.getGroupArray(data))
+ array.push(group_id)
+ return array
+ }
+
+ async getGroupMap(data) {
+ const map = new Map
+ for (const i of await this.getGroupArray(data))
+ map.set(i.group_id, i)
+ data.bot.gl = map
+ return map
+ }
+
+ 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)
+ data.bot.gml.set(data.group_id, map)
+ return map
+ }
+
+ async getGroupMemberMap(data) {
+ for (const [group_id, group] of await this.getGroupMap(data)) {
+ if (group.guild) continue
+ await this.getMemberMap({ ...data, group_id })
+ }
+ }
+
+ getMemberInfo(data) {
+ return data.bot.sendApi("get_group_member_info", {
+ group_id: data.group_id,
+ user_id: data.user_id,
+ })
+ }
+
+ async getGuildArray(data) {
+ return (await data.bot.sendApi("get_guild_list")).data || []
+ }
+
+ getGuildInfo(data) {
+ return data.bot.sendApi("get_guild_meta_by_guest", {
+ guild_id: data.guild_id,
+ })
+ }
+
+ async getGuildChannelArray(data) {
+ return (await data.bot.sendApi("get_guild_channel_list", {
+ guild_id: data.guild_id,
+ })).data || []
+ }
+
+ async getGuildChannelMap(data) {
+ const map = new Map
+ for (const i of await this.getGuildChannelArray(data))
+ map.set(i.channel_id, i)
+ return map
+ }
+
+ async getGuildMemberArray(data) {
+ const array = []
+ let next_token = ""
+ while (true) {
+ const list = (await data.bot.sendApi("get_guild_member_list", {
+ guild_id: data.guild_id,
+ next_token,
+ })).data
+ if (!list) break
+
+ for (const i of list.members)
+ array.push({
+ ...i,
+ user_id: i.tiny_id,
+ })
+ if (list.finished) break
+ next_token = list.next_token
+ }
+ return array
+ }
+
+ async getGuildMemberList(data) {
+ const array = []
+ for (const { user_id } of await this.getGuildMemberArray(data))
+ array.push(user_id)
+ return array.push
+ }
+
+ async getGuildMemberMap(data) {
+ const map = new Map
+ for (const i of await this.getGuildMemberArray(data))
+ map.set(i.user_id, i)
+ data.bot.gml.set(data.group_id, map)
+ return map
+ }
+
+ getGuildMemberInfo(data) {
+ return data.bot.sendApi("get_guild_member_profile", {
+ guild_id: data.guild_id,
+ user_id: data.user_id,
+ })
+ }
+
+ sendLike(data, times) {
+ Bot.makeLog("info", `点赞:${times}次`, `${data.self_id} => ${data.user_id}`)
+ return data.bot.sendApi("send_like", {
+ user_id: data.user_id,
+ times,
+ })
+ }
+
+ setGroupName(data, group_name) {
+ Bot.makeLog("info", `设置群名:${group_name}`, `${data.self_id} => ${data.group_id}`)
+ return data.bot.sendApi("set_group_name", {
+ group_id: data.group_id,
+ group_name,
+ })
+ }
+
+ async setGroupAvatar(data, file) {
+ Bot.makeLog("info", `设置群头像:${file}`, `${data.self_id} => ${data.group_id}`)
+ file = await Bot.Buffer(file, { http: true })
+ return data.bot.sendApi("set_group_portrait", {
+ group_id: data.group_id,
+ file,
+ })
+ }
+
+ setGroupAdmin(data, user_id, enable) {
+ Bot.makeLog("info", `${enable ? "设置" : "取消"}群管理员:${user_id}`, `${data.self_id} => ${data.group_id}`)
+ return data.bot.sendApi("set_group_admin", {
+ group_id: data.group_id,
+ user_id,
+ enable,
+ })
+ }
+
+ setGroupCard(data, user_id, card) {
+ Bot.makeLog("info", `设置群名片:${card}`, `${data.self_id} => ${data.group_id}, ${user_id}`)
+ return data.bot.sendApi("set_group_card", {
+ group_id: data.group_id,
+ user_id,
+ card,
+ })
+ }
+
+ setGroupTitle(data, user_id, special_title, duration) {
+ Bot.makeLog("info", `设置群头衔:${special_title} ${duration}`, `${data.self_id} => ${data.group_id}, ${user_id}`)
+ return data.bot.sendApi("set_group_special_title", {
+ group_id: data.group_id,
+ user_id,
+ special_title,
+ duration,
+ })
+ }
+
+ sendGroupSign(data) {
+ Bot.makeLog("info", "群打卡", `${data.self_id} => ${data.group_id}`)
+ return data.bot.sendApi("send_group_sign", {
+ group_id: data.group_id,
+ })
+ }
+
+ setGroupBan(data, user_id, duration) {
+ Bot.makeLog("info", `禁言群成员:${duration}秒`, `${data.self_id} => ${data.group_id}, ${user_id}`)
+ return data.bot.sendApi("set_group_ban", {
+ group_id: data.group_id,
+ user_id,
+ duration,
+ })
+ }
+
+ setGroupWholeKick(data, enable) {
+ Bot.makeLog("info", `${enable ? "开启" : "关闭"}全员禁言`, `${data.self_id} => ${data.group_id}`)
+ return data.bot.sendApi("set_group_whole_ban", {
+ group_id: data.group_id,
+ enable,
+ })
+ }
+
+ setGroupKick(data, user_id, reject_add_request) {
+ Bot.makeLog("info", `踢出群成员${reject_add_request ? "拒绝再次加群" : ""}`, `${data.self_id} => ${data.group_id}, ${user_id}`)
+ return data.bot.sendApi("set_group_kick", {
+ group_id: data.group_id,
+ user_id,
+ reject_add_request,
+ })
+ }
+
+ setGroupLeave(data, is_dismiss) {
+ Bot.makeLog("info", is_dismiss ? "解散" : "退群", `${data.self_id} => ${data.group_id}`)
+ return data.bot.sendApi("set_group_leave", {
+ group_id: data.group_id,
+ is_dismiss,
+ })
+ }
+
+ downloadFile(data, url, thread_count, headers) {
+ return data.bot.sendApi("download_file", {
+ url,
+ thread_count,
+ headers,
+ })
+ }
+
+ async sendFriendFile(data, file, name = path.basename(file)) {
+ Bot.makeLog("info", `发送好友文件:${name}(${file})`, `${data.self_id} => ${data.user_id}`)
+ return data.bot.sendApi("upload_private_file", {
+ user_id: data.user_id,
+ file: await this.makeFile(file),
+ name,
+ })
+ }
+
+ async sendGroupFile(data, file, folder, name = path.basename(file)) {
+ Bot.makeLog("info", `发送群文件:${folder||""}/${name}(${file})`, `${data.self_id} => ${data.group_id}`)
+ return data.bot.sendApi("upload_group_file", {
+ group_id: data.group_id,
+ folder,
+ file: await this.makeFile(file),
+ name,
+ })
+ }
+
+ deleteGroupFile(data, file_id, busid) {
+ Bot.makeLog("info", `删除群文件:${file_id}(${busid})`, `${data.self_id} => ${data.group_id}`)
+ return data.bot.sendApi("delete_group_file", {
+ group_id: data.group_id,
+ file_id,
+ busid,
+ })
+ }
+
+ createGroupFileFolder(data, name) {
+ Bot.makeLog("info", `创建群文件夹:${name}`, `${data.self_id} => ${data.group_id}`)
+ return data.bot.sendApi("create_group_file_folder", {
+ group_id: data.group_id,
+ name,
+ })
+ }
+
+ getGroupFileSystemInfo(data) {
+ return data.bot.sendApi("get_group_file_system_info", {
+ group_id: data.group_id,
+ })
+ }
+
+ getGroupFiles(data, folder_id) {
+ if (folder_id)
+ return data.bot.sendApi("get_group_files_by_folder", {
+ group_id: data.group_id,
+ folder_id,
+ })
+ return data.bot.sendApi("get_group_root_files", {
+ group_id: data.group_id,
+ })
+ }
+
+ getGroupFileUrl(data, file_id, busid) {
+ return data.bot.sendApi("get_group_file_url", {
+ group_id: data.group_id,
+ file_id,
+ busid,
+ })
+ }
+
+ getGroupFs(data) {
+ return {
+ upload: (file, folder, name) => this.sendGroupFile(data, file, folder, name),
+ rm: (file_id, busid) => this.deleteGroupFile(data, file_id, busid),
+ mkdir: name => this.createGroupFileFolder(data, name),
+ df: () => this.getGroupFileSystemInfo(data),
+ ls: folder_id => this.getGroupFiles(data, folder_id),
+ download: (file_id, busid) => this.getGroupFileUrl(data, file_id, busid),
+ }
+ }
+
+ setFriendAddRequest(data, flag, approve, remark) {
+ return data.bot.sendApi("set_friend_add_request", {
+ flag,
+ approve,
+ remark,
+ })
+ }
+
+ setGroupAddRequest(data, flag, sub_type, approve, reason) {
+ return data.bot.sendApi("set_group_add_request", {
+ flag,
+ sub_type,
+ approve,
+ reason,
+ })
+ }
+
+ pickFriend(data, user_id) {
+ const i = {
+ ...data.bot.fl.get(user_id),
+ ...data,
+ user_id,
+ }
+ return {
+ ...i,
+ sendMsg: msg => this.sendFriendMsg(i, msg),
+ getMsg: message_id => this.getMsg(i, message_id),
+ recallMsg: message_id => this.recallMsg(i, message_id),
+ getForwardMsg: message_id => this.getForwardMsg(i, message_id),
+ sendForwardMsg: msg => this.sendFriendForwardMsg(i, msg),
+ sendFile: (file, name) => this.sendFriendFile(i, file, name),
+ getInfo: () => this.getFriendInfo(i),
+ getAvatarUrl: () => `https://q1.qlogo.cn/g?b=qq&s=0&nk=${user_id}`,
+ thumbUp: times => this.sendLike(i, times),
+ }
+ }
+
+ pickMember(data, group_id, user_id) {
+ if (typeof group_id == "string" && group_id.match("-")) {
+ const guild_id = group_id.split("-")
+ const i = {
+ ...data,
+ guild_id: guild_id[0],
+ channel_id: guild_id[1],
+ user_id,
+ }
+ return {
+ ...this.pickGroup(i, group_id),
+ ...i,
+ getInfo: () => this.getGuildMemberInfo(i),
+ getAvatarUrl: async () => (await this.getGuildMemberInfo(i)).avatar_url,
+ }
+ }
+
+ const i = {
+ ...data.bot.fl.get(user_id),
+ ...data.bot.gml.get(group_id)?.get(user_id),
+ ...data,
+ group_id,
+ user_id,
+ }
+ return {
+ ...this.pickFriend(i, user_id),
+ ...i,
+ getInfo: () => this.getMemberInfo(i),
+ poke: () => this.sendGroupMsg(i, { type: "poke", qq: user_id }),
+ mute: duration => this.setGroupBan(i, i.user_id, duration),
+ kick: reject_add_request => this.setGroupKick(i, i.user_id, reject_add_request),
+ get is_friend() { return data.bot.fl.has(user_id) },
+ get is_owner() { return i.role == "owner" },
+ get is_admin() { return i.role == "admin" },
+ }
+ }
+
+ pickGroup(data, group_id) {
+ if (typeof group_id == "string" && group_id.match("-")) {
+ const guild_id = group_id.split("-")
+ const i = {
+ ...data.bot.gl.get(group_id),
+ ...data,
+ guild_id: guild_id[0],
+ channel_id: guild_id[1],
+ }
+ return {
+ ...i,
+ sendMsg: msg => this.sendGuildMsg(i, msg),
+ getMsg: message_id => this.getMsg(i, message_id),
+ recallMsg: message_id => this.recallMsg(i, message_id),
+ getForwardMsg: message_id => this.getForwardMsg(i, message_id),
+ getInfo: () => this.getGuildInfo(i),
+ getChannelArray: () => this.getGuildChannelArray(i),
+ getChannelList: () => this.getGuildChannelList(i),
+ getChannelMap: () => this.getGuildChannelMap(i),
+ getMemberArray: () => this.getGuildMemberArray(i),
+ getMemberList: () => this.getGuildMemberList(i),
+ getMemberMap: () => this.getGuildMemberMap(i),
+ pickMember: user_id => this.pickMember(i, group_id, user_id),
+ }
+ }
+
+ const i = {
+ ...data.bot.gl.get(group_id),
+ ...data,
+ group_id,
+ }
+ return {
+ ...i,
+ sendMsg: msg => this.sendGroupMsg(i, msg),
+ getMsg: message_id => this.getMsg(i, message_id),
+ recallMsg: message_id => this.recallMsg(i, message_id),
+ getForwardMsg: message_id => this.getForwardMsg(i, message_id),
+ sendForwardMsg: msg => this.sendGroupForwardMsg(i, msg),
+ sendFile: (file, name) => this.sendGroupFile(i, file, undefined, name),
+ getInfo: () => this.getGroupInfo(i),
+ getAvatarUrl: () => `https://p.qlogo.cn/gh/${group_id}/${group_id}/0`,
+ getChatHistory: (seq, cnt) => this.getGroupMsgHistory(i, seq, cnt),
+ getMemberArray: () => this.getMemberArray(i),
+ getMemberList: () => this.getMemberList(i),
+ getMemberMap: () => this.getMemberMap(i),
+ pickMember: user_id => this.pickMember(i, group_id, user_id),
+ pokeMember: qq => this.sendGroupMsg(i, { type: "poke", qq }),
+ setName: group_name => this.setGroupName(i, group_name),
+ setAvatar: file => this.setGroupAvatar(i, file),
+ setAdmin: (user_id, enable) => this.setGroupAdmin(i, user_id, enable),
+ setCard: (user_id, card) => this.setGroupCard(i, user_id, card),
+ setTitle: (user_id, special_title, duration) => this.setGroupTitle(i, user_id, special_title, duration),
+ sign: () => this.sendGroupSign(i),
+ muteMember: (user_id, duration) => this.setGroupBan(i, user_id, duration),
+ muteAll: enable => this.setGroupWholeKick(i, enable),
+ kickMember: (user_id, reject_add_request) => this.setGroupKick(i, user_id, reject_add_request),
+ quit: is_dismiss => this.setGroupLeave(i, is_dismiss),
+ fs: this.getGroupFs(i),
+ get is_owner() { return data.bot.gml.get(group_id)?.get(data.self_id)?.role == "owner" },
+ get is_admin() { return data.bot.gml.get(group_id)?.get(data.self_id)?.role == "admin" },
+ }
+ }
+
+ async connect(data, ws) {
+ Bot[data.self_id] = {
+ adapter: this,
+ ws: ws,
+ sendApi: (action, params) => this.sendApi(ws, action, params),
+ stat: {
+ start_time: data.time,
+ stat: {},
+ get lost_pkt_cnt() { return this.stat.packet_lost },
+ get lost_times() { return this.stat.lost_times },
+ get recv_msg_cnt() { return this.stat.message_received },
+ get recv_pkt_cnt() { return this.stat.packet_received },
+ get sent_msg_cnt() { return this.stat.message_sent },
+ get sent_pkt_cnt() { return this.stat.packet_sent },
+ },
+ model: "TRSS Yunzai ",
+
+ info: {},
+ get uin() { return this.info.user_id },
+ get nickname() { return this.info.nickname },
+ get avatar() { return `https://q1.qlogo.cn/g?b=qq&s=0&nk=${this.uin}` },
+
+ setProfile: profile => this.setProfile(data, profile),
+ setNickname: nickname => this.setProfile(data, { nickname }),
+
+ 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),
+ getGroupMemberMap: () => this.getGroupMemberMap(data),
+ gl: new Map,
+ gml: new Map,
+
+ request_list: [],
+ getSystemMsg: () => data.bot.request_list,
+ setFriendAddRequest: (flag, approve, remark) => this.setFriendAddRequest(data, flag, approve, remark),
+ setGroupAddRequest: (flag, sub_type, approve, reason) => this.setGroupAddRequest(data, flag, sub_type, approve, reason),
+ }
+ data.bot = Bot[data.self_id]
+
+ if (!Bot.uin.includes(data.self_id))
+ Bot.uin.push(data.self_id)
+
+ data.bot.sendApi("_set_model_show", {
+ model: data.bot.model,
+ model_show: data.bot.model,
+ })
+
+ data.bot.info = (await data.bot.sendApi("get_login_info")).data
+ data.bot.guild_info = (await data.bot.sendApi("get_guild_service_profile")).data
+ data.bot.clients = (await data.bot.sendApi("get_online_clients")).clients
+ data.bot.version = {
+ ...(await data.bot.sendApi("get_version_info")).data,
+ id: this.id,
+ name: this.name,
+ get version() {
+ return this.app_full_name || `${this.app_name} v${this.app_version}`
+ },
+ }
+
+ data.bot.getFriendMap()
+ data.bot.getGroupMemberMap()
+
+ Bot.makeLog("mark", `${this.name}(${this.id}) ${data.bot.version.version} 已连接`, data.self_id)
+ Bot.em(`connect.${data.self_id}`, data)
+ }
+
+ makeMessage(data) {
+ data.message = this.parseMsg(data.message)
+ switch (data.message_type) {
+ case "private": {
+ const name = data.sender.card || data.sender.nickname || data.bot.fl.get(data.user_id)?.nickname
+ Bot.makeLog("info", `好友消息:${name ? `[${name}] ` : ""}${data.raw_message}`, `${data.self_id} <= ${data.user_id}`)
+ break
+ } case "group": {
+ const group_name = data.group_name || data.bot.gl.get(data.group_id)?.group_name
+ let user_name = data.sender.card || data.sender.nickname
+ if (!user_name) {
+ const user = data.bot.gml.get(data.group_id)?.get(data.user_id) || data.bot.fl.get(data.user_id)
+ if (user) user_name = user?.card || user?.nickname
+ }
+ Bot.makeLog("info", `群消息:${user_name ? `[${group_name ? `${group_name}, ` : ""}${user_name}] ` : ""}${data.raw_message}`, `${data.self_id} <= ${data.group_id}, ${data.user_id}`)
+ break
+ } case "guild":
+ data.message_type = "group"
+ data.group_id = `${data.guild_id}-${data.channel_id}`
+ Bot.makeLog("info", `频道消息:[${data.sender.nickname}] ${JSON.stringify(data.message)}`, `${data.self_id} <= ${data.group_id}, ${data.user_id}`)
+ Object.defineProperty(data, "friend", { get() { return this.member || {}}})
+ break
+ default:
+ Bot.makeLog("warn", `未知消息:${logger.magenta(data.raw)}`, data.self_id)
+ }
+
+ Bot.em(`${data.post_type}.${data.message_type}.${data.sub_type}`, data)
+ }
+
+ async makeNotice(data) {
+ switch (data.notice_type) {
+ case "friend_recall":
+ Bot.makeLog("info", `好友消息撤回:${data.message_id}`, `${data.self_id} <= ${data.user_id}`)
+ break
+ case "group_recall":
+ Bot.makeLog("info", `群消息撤回:${data.operator_id} => ${data.user_id} ${data.message_id}`, `${data.self_id} <= ${data.group_id}`)
+ break
+ case "group_increase":
+ Bot.makeLog("info", `群成员增加:${data.operator_id} => ${data.user_id} ${data.sub_type}`, `${data.self_id} <= ${data.group_id}`)
+ if (data.user_id == data.self_id)
+ data.bot.getGroupMemberMap()
+ else
+ data.bot.pickGroup(data.group_id).getMemberMap()
+ break
+ case "group_decrease":
+ Bot.makeLog("info", `群成员减少:${data.operator_id} => ${data.user_id} ${data.sub_type}`, `${data.self_id} <= ${data.group_id}`)
+ if (data.user_id == data.self_id)
+ data.bot.getGroupMemberMap()
+ else
+ data.bot.pickGroup(data.group_id).getMemberMap()
+ break
+ case "group_admin":
+ Bot.makeLog("info", `群管理员变动:${data.sub_type}`, `${data.self_id} <= ${data.group_id}, ${data.user_id}`)
+ data.set = data.sub_type == "set"
+ break
+ case "group_upload":
+ Bot.makeLog("info", `群文件上传:${JSON.stringify(data.file)}`, `${data.self_id} <= ${data.group_id}, ${data.user_id}`)
+ break
+ case "group_ban":
+ Bot.makeLog("info", `群禁言:${data.operator_id} => ${data.user_id} ${data.sub_type} ${data.duration}秒`, `${data.self_id} <= ${data.group_id}`)
+ break
+ case "friend_add":
+ Bot.makeLog("info", "好友添加", `${data.self_id} <= ${data.user_id}`)
+ data.bot.getFriendMap()
+ break
+ case "notify":
+ if (data.group_id)
+ data.notice_type = "group"
+ else
+ data.notice_type = "friend"
+ switch (data.sub_type) {
+ case "poke":
+ data.operator_id = data.user_id
+ if (data.group_id)
+ Bot.makeLog("info", `群戳一戳:${data.operator_id} => ${data.target_id}`, `${data.self_id} <= ${data.group_id}`)
+ else
+ Bot.makeLog("info", `好友戳一戳:${data.operator_id} => ${data.target_id}`, data.self_id)
+ break
+ case "honor":
+ Bot.makeLog("info", `群荣誉:${data.honor_type}`, `${data.self_id} <= ${data.group_id}, ${data.user_id}`)
+ break
+ case "title":
+ Bot.makeLog("info", `群头衔:${data.title}`, `${data.self_id} <= ${data.group_id}, ${data.user_id}`)
+ break
+ default:
+ Bot.makeLog("warn", `未知通知:${logger.magenta(data.raw)}`, data.self_id)
+ }
+ break
+ case "group_card":
+ Bot.makeLog("info", `群名片更新:${data.card_old} => ${data.card_new}`, `${data.self_id} <= ${data.group_id}, ${data.user_id}`)
+ break
+ case "offline_file":
+ Bot.makeLog("info", `离线文件:${JSON.stringify(data.file)}`, `${data.self_id} <= ${data.user_id}`)
+ break
+ case "client_status":
+ Bot.makeLog("info", `客户端${data.online ? "上线" : "下线"}:${JSON.stringify(data.client)}`, data.self_id)
+ data.clients = (await data.bot.sendApi("get_online_clients")).clients
+ data.bot.clients = data.clients
+ break
+ case "essence":
+ data.notice_type = "group_essence"
+ Bot.makeLog("info", `群精华消息:${data.operator_id} => ${data.sender_id} ${data.sub_type} ${data.message_id}`, `${data.self_id} <= ${data.group_id}`)
+ break
+ case "guild_channel_recall":
+ Bot.makeLog("info", `频道消息撤回:${data.operator_id} => ${data.user_id} ${data.message_id}`, `${data.self_id} <= ${data.guild_id}-${data.channel_id}`)
+ break
+ case "message_reactions_updated":
+ data.notice_type = "guild_message_reactions_updated"
+ Bot.makeLog("info", `频道消息表情贴:${data.message_id} ${JSON.stringify(data.current_reactions)}`, `${data.self_id} <= ${data.guild_id}-${data.channel_id}, ${data.user_id}`)
+ break
+ case "channel_updated":
+ data.notice_type = "guild_channel_updated"
+ Bot.makeLog("info", `子频道更新:${JSON.stringify(data.old_info)} => ${JSON.stringify(data.new_info)}`, `${data.self_id} <= ${data.guild_id}-${data.channel_id}, ${data.user_id}`)
+ break
+ case "channel_created":
+ data.notice_type = "guild_channel_created"
+ Bot.makeLog("info", `子频道创建:${JSON.stringify(data.channel_info)}`, `${data.self_id} <= ${data.guild_id}-${data.channel_id}, ${data.user_id}`)
+ data.bot.getGroupMap()
+ break
+ case "channel_destroyed":
+ data.notice_type = "guild_channel_destroyed"
+ Bot.makeLog("info", `子频道删除:${JSON.stringify(data.channel_info)}`, `${data.self_id} <= ${data.guild_id}-${data.channel_id}, ${data.user_id}`)
+ data.bot.getGroupMap()
+ break
+ default:
+ Bot.makeLog("warn", `未知通知:${logger.magenta(data.raw)}`, data.self_id)
+ }
+
+ let notice = data.notice_type.split("_")
+ data.notice_type = notice.shift()
+ notice = notice.join("_")
+ if (notice)
+ data.sub_type = notice
+
+ if (data.guild_id && data.channel_id) {
+ data.group_id = `${data.guild_id}-${data.channel_id}`
+ Object.defineProperty(data, "friend", { get() { return this.member || {}}})
+ }
+
+ Bot.em(`${data.post_type}.${data.notice_type}.${data.sub_type}`, data)
+ }
+
+ makeRequest(data) {
+ switch (data.request_type) {
+ case "friend":
+ Bot.makeLog("info", `加好友请求:${data.comment}(${data.flag})`, `${data.self_id} <= ${data.user_id}`)
+ data.sub_type = "add"
+ data.approve = approve => data.bot.setFriendAddRequest(data.flag, approve)
+ break
+ case "group":
+ Bot.makeLog("info", `加群请求:${data.sub_type} ${data.comment}(${data.flag})`, `${data.self_id} <= ${data.group_id}, ${data.user_id}`)
+ data.approve = approve => data.bot.setGroupAddRequest(data.flag, data.sub_type, approve)
+ break
+ default:
+ Bot.makeLog("warn", `未知请求:${logger.magenta(data.raw)}`, data.self_id)
+ }
+
+ data.bot.request_list.push(data)
+ Bot.em(`${data.post_type}.${data.request_type}.${data.sub_type}`, data)
+ }
+
+ heartbeat(data) {
+ if (data.status)
+ Object.assign(data.bot.stat, data.status)
+ }
+
+ makeMeta(data, ws) {
+ switch (data.meta_event_type) {
+ case "heartbeat":
+ this.heartbeat(data)
+ break
+ case "lifecycle":
+ 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.post_type) {
+ if (data.meta_event_type != "lifecycle" && !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.post_type) {
+ case "meta_event":
+ this.makeMeta(data, ws)
+ break
+ case "message":
+ this.makeMessage(data)
+ break
+ case "notice":
+ this.makeNotice(data)
+ break
+ case "request":
+ this.makeRequest(data)
+ break
+ case "message_sent":
+ data.post_type = "message"
+ this.makeMessage(data)
+ break
+ default:
+ Bot.makeLog("warn", `未知消息:${logger.magenta(data.raw)}`, data.self_id)
+ }
+ } else if (data.echo) {
+ Bot.emit(data.echo, data)
+ } else {
+ Bot.makeLog("warn", `未知消息:${logger.magenta(data.raw)}`, data.self_id)
+ }
+ }
+
+ load() {
+ for (const i of [this.path, "go-cqhttp"]) {
+ if (!Array.isArray(Bot.wsf[i]))
+ Bot.wsf[i] = []
+ Bot.wsf[i].push((ws, ...args) =>
+ ws.on("message", data => this.message(data, ws, ...args))
+ )
+ }
+ }
+})
\ No newline at end of file
diff --git "a/Yunzai/plugins/example/\344\270\273\345\212\250\345\244\215\350\257\273.js" "b/Yunzai/plugins/example/\344\270\273\345\212\250\345\244\215\350\257\273.js"
index f57cfa8815c6b98daa06b60804abc69c04261086..ac69b5a84a09adbd144964164211dbe8c860aa5b 100644
--- "a/Yunzai/plugins/example/\344\270\273\345\212\250\345\244\215\350\257\273.js"
+++ "b/Yunzai/plugins/example/\344\270\273\345\212\250\345\244\215\350\257\273.js"
@@ -1,37 +1,36 @@
-import plugin from '../../lib/plugins/plugin.js'
-
export class example2 extends plugin {
- constructor () {
+ constructor() {
super({
- name: '复读',
- dsc: '复读用户发送的内容,然后撤回',
+ name: "复读",
+ dsc: "复读用户发送的内容,然后撤回",
/** https://oicqjs.github.io/oicq/#events */
- event: 'message',
+ event: "message",
priority: 5000,
rule: [
{
/** 命令正则匹配 */
- reg: '^#复读$',
+ reg: "^#复读$",
/** 执行方法 */
- fnc: 'repeat'
+ fnc: "repeat",
+ permission: "master",
}
]
})
}
/** 复读 */
- async repeat () {
+ async repeat() {
/** 设置上下文,后续接收到内容会执行doRep方法 */
- this.setContext('doRep')
+ this.setContext("doRep")
/** 回复 */
- await this.reply('请发送要复读的内容', false, { at: true })
+ await this.reply("请发送要复读的内容", false, { at: true })
}
/** 接受内容 */
- doRep () {
+ doRep() {
/** 复读内容 */
this.reply(this.e.message, false, { recallMsg: 5 })
/** 结束上下文 */
- this.finish('doRep')
+ this.finish("doRep")
}
-}
+}
\ No newline at end of file
diff --git "a/Yunzai/plugins/example/\350\277\233\347\276\244\351\200\200\347\276\244\351\200\232\347\237\245.js" "b/Yunzai/plugins/example/\350\277\233\347\276\244\351\200\200\347\276\244\351\200\232\347\237\245.js"
index e80ec3041a27cb990620efc792d804dfa175df48..2853ee9fd9461cb401b5cce8c5e500c3c22e334e 100644
--- "a/Yunzai/plugins/example/\350\277\233\347\276\244\351\200\200\347\276\244\351\200\232\347\237\245.js"
+++ "b/Yunzai/plugins/example/\350\277\233\347\276\244\351\200\200\347\276\244\351\200\232\347\237\245.js"
@@ -1,12 +1,9 @@
-import plugin from '../../lib/plugins/plugin.js'
export class newcomer extends plugin {
constructor() {
super({
- name: '欢迎新人',
- dsc: '新人入群欢迎',
- /** https://oicqjs.github.io/oicq/#events */
- event: 'notice.group.increase',
- priority: 5000
+ name: "欢迎新人",
+ dsc: "新人入群欢迎",
+ event: "notice.group.increase",
})
}
@@ -15,14 +12,14 @@ export class newcomer extends plugin {
if (this.e.user_id == this.e.self_id) return
/** 定义入群欢迎内容 */
- let msg = '欢迎新人!'
+ let msg = "欢迎新人!"
/** 冷却cd 30s */
let cd = 30
/** cd */
let key = `Yz:newcomers:${this.e.group_id}`
if (await redis.get(key)) return
- redis.set(key, '1', { EX: cd })
+ redis.set(key, "1", { EX: cd })
/** 回复 */
await this.reply([
@@ -36,13 +33,13 @@ export class newcomer extends plugin {
export class outNotice extends plugin {
constructor() {
super({
- name: '退群通知',
- dsc: 'xx退群了',
- event: 'notice.group.decrease'
+ name: "退群通知",
+ dsc: "xx退群了",
+ event: "notice.group.decrease"
})
/** 退群提示词 */
- this.tips = '退群了'
+ this.tips = "退群了"
}
async accept() {
diff --git a/Yunzai/plugins/other/install.js b/Yunzai/plugins/other/install.js
index d01dbf42c8eff05ce888004b24eda7d3e7011cda..2f734ad27c02b8c54e227a478768386fe6f3308e 100644
--- a/Yunzai/plugins/other/install.js
+++ b/Yunzai/plugins/other/install.js
@@ -1,13 +1,12 @@
-import { exec, execSync } from "child_process"
-import plugin from "../../lib/plugins/plugin.js"
-import fs from "node:fs"
import { Restart } from "./restart.js"
let insing = false
const list = {
"Atlas":"https://gitee.com/Nwflower/atlas",
+ "genshin" :"https://gitee.com/TimeRainStarSky/Yunzai-genshin",
"ws-plugin":"https://gitee.com/xiaoye12123/ws-plugin",
"TRSS-Plugin" :"https://Yunzai.TRSS.me",
+ "miao-plugin" :"https://gitee.com/yoimiya-kokomi/miao-plugin",
"yenai-plugin" :"https://gitee.com/yeyang52/yenai-plugin",
"flower-plugin" :"https://gitee.com/Nwflower/flower-plugin",
"xianyu-plugin" :"https://gitee.com/suancaixianyu/xianyu-plugin",
@@ -15,14 +14,13 @@ const list = {
"useless-plugin":"https://gitee.com/SmallK111407/useless-plugin",
"StarRail-plugin" :"https://gitee.com/hewang1an/StarRail-plugin",
"xiaoyao-cvs-plugin":"https://gitee.com/Ctrlcvs/xiaoyao-cvs-plugin",
- "Jinmaocuicuisha-plugin":"https://gitee.com/JMCCS/jinmaocuicuisha",
"trss-xianxin-plugin" :"https://gitee.com/snowtafir/xianxin-plugin",
- "mysVilla-Plugin" :"https://gitee.com/TimeRainStarSky/Yunzai-mysVilla-Plugin",
+ "Lagrange-Plugin" :"https://gitee.com/TimeRainStarSky/Yunzai-Lagrange-Plugin",
"Telegram-Plugin" :"https://gitee.com/TimeRainStarSky/Yunzai-Telegram-Plugin",
"Discord-Plugin":"https://gitee.com/TimeRainStarSky/Yunzai-Discord-Plugin",
- "QQGuild-Plugin":"https://gitee.com/TimeRainStarSky/Yunzai-QQGuild-Plugin",
"WeChat-Plugin" :"https://gitee.com/TimeRainStarSky/Yunzai-WeChat-Plugin",
- "Proxy-Plugin" :"https://gitee.com/TimeRainStarSky/Yunzai-Proxy-Plugin",
+ "QQBot-Plugin":"https://gitee.com/TimeRainStarSky/Yunzai-QQBot-Plugin",
+ "Route-Plugin" :"https://gitee.com/TimeRainStarSky/Yunzai-Route-Plugin",
"ICQQ-Plugin" :"https://gitee.com/TimeRainStarSky/Yunzai-ICQQ-Plugin",
"KOOK-Plugin" :"https://gitee.com/TimeRainStarSky/Yunzai-KOOK-Plugin",
}
@@ -53,7 +51,7 @@ export class install extends plugin {
if (name == "插件") {
let msg = "\n"
for (const name in list)
- if (!fs.existsSync(`plugins/${name}`))
+ if (!await Bot.fsStat(`plugins/${name}`))
msg += `${name}\n`
if (msg == "\n")
@@ -66,7 +64,7 @@ export class install extends plugin {
}
const path = `plugins/${name}`
- if (fs.existsSync(path)) {
+ if (await Bot.fsStat(path)) {
await this.reply(`${name} 插件已安装`)
return false
}
@@ -74,23 +72,15 @@ export class install extends plugin {
this.restart()
}
- async execSync(cmd) {
- return new Promise(resolve => {
- exec(cmd, (error, stdout, stderr) => {
- resolve({ error, stdout, stderr })
- })
- })
- }
-
async runInstall(name, url, path) {
logger.mark(`${this.e.logFnc} 开始安装:${name} 插件`)
await this.reply(`开始安装 ${name} 插件`)
const cm = `git clone --depth 1 --single-branch "${url}" "${path}"`
insing = true
- const ret = await this.execSync(cm)
- if (fs.existsSync(`${path}/package.json`))
- await this.execSync("pnpm install")
+ const ret = await Bot.exec(cm)
+ if (await Bot.fsStat(`${path}/package.json`))
+ await Bot.exec("pnpm install")
insing = false
if (ret.error) {
diff --git a/Yunzai/plugins/other/restart.js b/Yunzai/plugins/other/restart.js
index f0ef2bd228fab79e6a5de476bd0842e999060c06..bd65dfbcae1c38067e5167edccd260623d8eafbc 100644
--- a/Yunzai/plugins/other/restart.js
+++ b/Yunzai/plugins/other/restart.js
@@ -1,122 +1,101 @@
-import plugin from '../../lib/plugins/plugin.js'
-import { createRequire } from 'module'
-
-const require = createRequire(import.meta.url)
-const { exec } = require('child_process')
+import cfg from "../../lib/config/config.js"
+import { spawn } from "child_process"
export class Restart extends plugin {
- constructor (e = '') {
+ constructor (e = "") {
super({
- name: '重启',
- dsc: '#重启',
- event: 'message',
+ name: "重启",
+ dsc: "#重启",
+ event: "message",
priority: 10,
- rule: [{
- reg: '^#重启$',
- fnc: 'restart',
- permission: 'master'
- }, {
- reg: '^#(停机|关机)$',
- fnc: 'stop',
- permission: 'master'
- }]
+ rule: [
+ {
+ reg: "^#重启$",
+ fnc: "restart",
+ permission: "master"
+ },
+ {
+ reg: "^#(停机|关机)$",
+ fnc: "stop",
+ permission: "master"
+ }
+ ]
})
if (e) this.e = e
+ this.key = "Yz:restart"
+ }
- this.key = 'Yz:restart'
+ init() {
+ Bot.once("online", () => this.restartMsg())
+ if (cfg.bot.restart_time) {
+ this.e = {
+ logFnc: "[自动重启]" ,
+ reply: msg => Bot.sendMasterMsg(msg),
+ }
+ setTimeout(() => this.restart(), cfg.bot.restart_time*60000)
+ }
}
- async init () {
+ async restartMsg() {
let restart = await redis.get(this.key)
- if (restart) {
- restart = JSON.parse(restart)
- let time = restart.time || new Date().getTime()
- time = (new Date().getTime() - time) / 1000
-
- let msg = `重启成功:耗时${time.toFixed(2)}秒`
-
+ if (!restart) return
+ restart = JSON.parse(restart)
+ const time = (Date.now() - (restart.time || Date.now()))/1000
+ const msg = []
+ if (restart.msg_id)
+ msg.push(segment.reply(restart.msg_id))
+ if (restart.isStop)
+ msg.push(`开机成功,距离上次关机${time}秒`)
+ else
+ msg.push(`重启成功,用时${time}秒`)
+
+ if (restart.id) {
if (restart.isGroup)
Bot.sendGroupMsg(restart.bot_id, restart.id, msg)
else
Bot.sendFriendMsg(restart.bot_id, restart.id, msg)
-
- redis.del(this.key)
+ } else {
+ Bot.sendMasterMsg(msg)
}
+ redis.del(this.key)
}
- async restart () {
- await this.e.reply('开始执行重启,请稍等...')
- logger.mark(`${this.e.logFnc} 开始执行重启,请稍等...`)
-
- let data = JSON.stringify({
+ async restart() {
+ await this.e.reply(`开始重启,本次运行时长:${Bot.getTimeDiff()}`)
+ await redis.set(this.key, JSON.stringify({
isGroup: !!this.e.isGroup,
id: this.e.isGroup ? this.e.group_id : this.e.user_id,
bot_id: this.e.self_id,
- time: new Date().getTime()
- })
-
- let npm = await this.checkPnpm()
-
- try {
- await redis.set(this.key, data, { EX: 120 })
- let cm = `${npm} start`
- if (process.argv[1].includes('pm2')) {
- cm = `${npm} run restart`
- }
-
- exec(cm, { windowsHide: true }, (error, stdout, stderr) => {
- if (error) {
- redis.del(this.key)
- this.e.reply(`操作失败!\n${error.stack}`)
- logger.error(`重启失败\n${error.stack}`)
- } else if (stdout) {
- logger.mark('重启成功,运行已由前台转为后台')
- logger.mark(`查看日志请用命令:${npm} run log`)
- logger.mark(`停止后台运行命令:${npm} stop`)
- process.exit()
- }
- })
- } catch (error) {
- redis.del(this.key)
- let e = error.stack ?? error
- this.e.reply(`操作失败!\n${e}`)
+ msg_id: this.e.message_id,
+ time: Date.now(),
+ }))
+
+ if (process.env.app_type == "pm2") {
+ const ret = await Bot.exec("pnpm run restart")
+ if (!ret.error) process.exit()
+ await this.e.reply(`重启错误\n${ret.error}`)
+ Bot.makeLog("error", ["重启错误", ret])
}
-
- return true
- }
-
- async checkPnpm () {
- let npm = 'npm'
- let ret = await this.execSync('pnpm -v')
- if (ret.stdout) npm = 'pnpm'
- return npm
- }
-
- async execSync (cmd) {
- return new Promise((resolve, reject) => {
- exec(cmd, { windowsHide: true }, (error, stdout, stderr) => {
- resolve({ error, stdout, stderr })
- })
- })
+ process.exit()
}
- async stop () {
- if (!process.argv[1].includes('pm2')) {
- logger.mark('关机成功,已停止运行')
- await this.e.reply('关机成功,已停止运行')
- process.exit()
+ async stop() {
+ await this.e.reply(`开始关机,本次运行时长:${Bot.getTimeDiff()}`)
+ await redis.set(this.key, JSON.stringify({
+ isStop: true,
+ isGroup: !!this.e.isGroup,
+ id: this.e.isGroup ? this.e.group_id : this.e.user_id,
+ bot_id: this.e.self_id,
+ msg_id: this.e.message_id,
+ time: Date.now(),
+ }))
+
+ if (process.env.app_type == "pm2") {
+ const ret = await Bot.exec("pnpm stop")
+ await this.e.reply(`关机错误\n${ret.error}\n${ret.stdout}\n${ret.stderr}`)
+ Bot.makeLog("error", ["关机错误", ret])
}
-
- logger.mark('关机成功,已停止运行')
- await this.e.reply('关机成功,已停止运行')
-
- let npm = await this.checkPnpm()
- exec(`${npm} stop`, { windowsHide: true }, (error, stdout, stderr) => {
- if (error) {
- this.e.reply(`操作失败!\n${error.stack}`)
- logger.error(`关机失败\n${error.stack}`)
- }
- })
+ process.exit(1)
}
}
\ No newline at end of file
diff --git a/Yunzai/plugins/other/sendLog.js b/Yunzai/plugins/other/sendLog.js
index 9b7d83b2c2d6bb4793da8dab1c2065c28bb35652..5f0a319eab26a78e3ebf5cf78e8d3d045ff74d20 100644
--- a/Yunzai/plugins/other/sendLog.js
+++ b/Yunzai/plugins/other/sendLog.js
@@ -1,6 +1,4 @@
-import plugin from "../../lib/plugins/plugin.js"
-import common from "../../lib/common/common.js"
-import fs from "node:fs"
+import fs from "node:fs/promises"
import lodash from "lodash"
import moment from "moment"
@@ -43,16 +41,16 @@ export class sendLog extends plugin {
if (this.keyWord) type = this.keyWord
- const log = this.getLog(logFile)
+ const log = await this.getLog(logFile)
if (lodash.isEmpty(log))
return this.reply(`暂无相关日志:${type}`)
- return this.reply(await common.makeForwardMsg(this.e, [log.join("\n")], `最近${log.length}条${type}日志`))
+ return this.reply(await Bot.makeForwardArray([`最近${log.length}条${type}日志`, log.join("\n")]))
}
- getLog(logFile) {
- let log = fs.readFileSync(logFile, { encoding: "utf-8" })
+ async getLog(logFile) {
+ let log = await fs.readFile(logFile, "utf-8")
log = log.split("\n")
if (this.keyWord) {
diff --git a/Yunzai/plugins/other/update.js b/Yunzai/plugins/other/update.js
index a33c30fcd613cbd39ce1a47037b319ebd2d7dcd3..9b0db7b446cf231070e23c299cc66b1f6c73c3d0 100644
--- a/Yunzai/plugins/other/update.js
+++ b/Yunzai/plugins/other/update.js
@@ -1,12 +1,7 @@
-import plugin from '../../lib/plugins/plugin.js'
-import { createRequire } from 'module'
+import cfg from '../../lib/config/config.js'
import lodash from 'lodash'
-import fs from 'node:fs'
+import fs from 'node:fs/promises'
import { Restart } from './restart.js'
-import common from '../../lib/common/common.js'
-
-const require = createRequire(import.meta.url)
-const { exec, execSync } = require('child_process')
let uping = false
@@ -37,6 +32,25 @@ export class update extends plugin {
this.typeName = 'TRSS-Yunzai'
}
+ init() {
+ if (cfg.bot.update_time) {
+ this.e = {
+ isMaster: true,
+ logFnc: "[自动更新]",
+ msg: "#全部更新",
+ reply: msg => Bot.sendMasterMsg(msg),
+ }
+ this.autoUpdate()
+ }
+ }
+
+ autoUpdate() {
+ setTimeout(() => {
+ this.updateAll()
+ this.autoUpdate()
+ }, cfg.bot.update_time*60000)
+ }
+
async update() {
if (!this.e.isMaster) return false
if (uping) return this.reply('已有命令更新中..请勿重复操作')
@@ -44,39 +58,29 @@ export class update extends plugin {
if (/详细|详情|面板|面版/.test(this.e.msg)) return false
/** 获取插件 */
- const plugin = this.getPlugin()
+ let plugin = await this.getPlugin()
if (plugin === false) return false
- /** 执行更新 */
await this.runUpdate(plugin)
- /** 是否需要重启 */
- if (this.isUp) {
- // await this.reply('即将执行重启,以应用更新')
- setTimeout(() => this.restart(), 2000)
- }
+ if (this.isPkgUp)
+ await Bot.exec("pnpm install")
+ if (this.isUp)
+ this.restart()
}
- getPlugin(plugin = '') {
+ async getPlugin(plugin = '') {
if (!plugin) {
plugin = this.e.msg.replace(/#(强制)?更新(日志)?/, '')
if (!plugin) return ''
}
- if (!fs.existsSync(`plugins/${plugin}/.git`)) return false
+ if (!await Bot.fsStat(`plugins/${plugin}/.git`)) return false
this.typeName = plugin
return plugin
}
- async execSync(cmd) {
- return new Promise((resolve, reject) => {
- exec(cmd, { windowsHide: true }, (error, stdout, stderr) => {
- resolve({ error, stdout, stderr })
- })
- })
- }
-
async runUpdate(plugin = '') {
this.isNowUp = false
@@ -95,22 +99,24 @@ export class update extends plugin {
await this.reply(`开始${type} ${this.typeName}`)
uping = true
- const ret = await this.execSync(cm)
+ const ret = await Bot.exec(cm)
uping = false
+ ret.stdout = String(ret.stdout)
if (ret.error) {
logger.mark(`${this.e.logFnc} 更新失败:${this.typeName}`)
- this.gitErr(ret.error, ret.stdout)
+ this.gitErr(Bot.String(ret.error), ret.stdout)
return false
}
const time = await this.getTime(plugin)
-
if (/Already up|已经是最新/g.test(ret.stdout)) {
await this.reply(`${this.typeName} 已是最新\n最后更新时间:${time}`)
} else {
- await this.reply(`${this.typeName} 更新成功\n更新时间:${time}`)
this.isUp = true
+ if (/package\.json/.test(ret.stdout))
+ this.isPkgUp = true
+ await this.reply(`${this.typeName} 更新成功\n更新时间:${time}`)
await this.reply(await this.getLog(plugin))
}
@@ -121,69 +127,56 @@ export class update extends plugin {
async getcommitId(plugin = '') {
let cm = 'git rev-parse --short HEAD'
if (plugin) cm = `cd "plugins/${plugin}" && ${cm}`
-
- const commitId = await execSync(cm, { encoding: 'utf-8' })
- return lodash.trim(commitId)
+ cm = await Bot.exec(cm)
+ return lodash.trim(String(cm.stdout))
}
async getTime(plugin = '') {
let cm = 'git log -1 --pretty=%cd --date=format:"%F %T"'
if (plugin) cm = `cd "plugins/${plugin}" && ${cm}`
-
- let time = ''
- try {
- time = await execSync(cm, { encoding: 'utf-8' })
- time = lodash.trim(time)
- } catch (error) {
- logger.error(error.toString())
- time = '获取时间失败'
- }
-
- return time
+ cm = await Bot.exec(cm)
+ return lodash.trim(String(cm.stdout))
}
- async gitErr(err, stdout) {
+ async gitErr(error, stdout) {
const msg = '更新失败!'
- const errMsg = err.toString()
- stdout = stdout.toString()
- if (errMsg.includes('Timed out')) {
- const remote = errMsg.match(/'(.+?)'/g)[0].replace(/'/g, '')
+ if (error.includes('Timed out')) {
+ const remote = error.match(/'(.+?)'/g)[0].replace(/'/g, '')
return this.reply(`${msg}\n连接超时:${remote}`)
}
- if (/Failed to connect|unable to access/g.test(errMsg)) {
- const remote = errMsg.match(/'(.+?)'/g)[0].replace(/'/g, '')
+ if (/Failed to connect|unable to access/g.test(error)) {
+ const remote = error.match(/'(.+?)'/g)[0].replace(/'/g, '')
return this.reply(`${msg}\n连接失败:${remote}`)
}
- if (errMsg.includes('be overwritten by merge')) {
- return this.reply(`${msg}\n存在冲突:\n${errMsg}\n请解决冲突后再更新,或者执行#强制更新,放弃本地修改`)
+ if (error.includes('be overwritten by merge')) {
+ return this.reply(`${msg}\n存在冲突:\n${error}\n请解决冲突后再更新,或者执行#强制更新,放弃本地修改`)
}
if (stdout.includes('CONFLICT')) {
- return this.reply(`${msg}\n存在冲突:\n${errMsg}${stdout}\n请解决冲突后再更新,或者执行#强制更新,放弃本地修改`)
+ return this.reply(`${msg}\n存在冲突:\n${error}${stdout}\n请解决冲突后再更新,或者执行#强制更新,放弃本地修改`)
}
- return this.reply([errMsg, stdout])
+ return this.reply([error, stdout])
}
async updateAll() {
- const dirs = fs.readdirSync('./plugins/')
+ const dirs = await fs.readdir('./plugins/')
await this.runUpdate()
for (let plu of dirs) {
- plu = this.getPlugin(plu)
+ plu = await this.getPlugin(plu)
if (plu === false) continue
- await common.sleep(1500)
await this.runUpdate(plu)
}
- if (this.isUp) {
- // await this.reply('即将执行重启,以应用更新')
- setTimeout(() => this.restart(), 2000)
- }
+ if (this.isPkgUp)
+ await Bot.exec("pnpm install")
+ if (this.isUp)
+ this.restart()
}
restart() {
@@ -194,17 +187,14 @@ export class update extends plugin {
let cm = 'git log -100 --pretty="%h||[%cd] %s" --date=format:"%F %T"'
if (plugin) cm = `cd "plugins/${plugin}" && ${cm}`
- let logAll
- try {
- logAll = await execSync(cm, { encoding: 'utf-8' })
- } catch (error) {
- logger.error(error.toString())
- await this.reply(error.toString())
+ cm = await Bot.exec(cm)
+ if (cm.error) {
+ logger.error(cm.error)
+ await this.reply(String(cm.error))
}
+ const logAll = String(cm.stdout).trim().split('\n')
- if (!logAll) return false
-
- logAll = logAll.trim().split('\n')
+ if (!logAll.length) return false
let log = []
for (let str of logAll) {
@@ -218,23 +208,21 @@ export class update extends plugin {
if (log.length <= 0) return ''
- let end = ''
- try {
- cm = 'git config -l'
- if (plugin) cm = `cd "plugins/${plugin}" && ${cm}`
- end = await execSync(cm, { encoding: 'utf-8' })
- end = end.match(/remote\..*\.url=.+/g).join('\n\n').replace(/remote\..*\.url=/g, '').replace(/\/\/([^@]+)@/, '//')
- } catch (error) {
- logger.error(error.toString())
- await this.reply(error.toString())
+ cm = 'git config -l'
+ if (plugin) cm = `cd "plugins/${plugin}" && ${cm}`
+ cm = await Bot.exec(cm)
+ const end = String(cm.stdout).match(/remote\..*\.url=.+/g).join('\n\n').replace(/remote\..*\.url=/g, '').replace(/\/\/([^@]+)@/, '//')
+ if (cm.error) {
+ logger.error(cm.error)
+ await this.reply(String(cm.error))
}
- return common.makeForwardMsg(this.e, [log, end], `${plugin || 'TRSS-Yunzai'} 更新日志,共${line}条`)
+ return Bot.makeForwardArray([`${plugin || 'TRSS-Yunzai'} 更新日志,共${line}条`, log, end])
}
async updateLog() {
- const plugin = this.getPlugin()
+ const plugin = await this.getPlugin()
if (plugin === false) return false
return this.reply(await this.getLog(plugin))
}
-}
\ No newline at end of file
+}
diff --git a/Yunzai/plugins/other/version.js b/Yunzai/plugins/other/version.js
index f77786549d97f6c47b41612ae675b9198de3872b..19f0d640ef42d4374f44cc10bbce8cb82e2c3130 100644
--- a/Yunzai/plugins/other/version.js
+++ b/Yunzai/plugins/other/version.js
@@ -1,27 +1,35 @@
-import { App, Common, Version } from '#miao'
+let App, Common, Version
+try {
+ App = (await import("#miao")).App
+ Common = (await import("#miao")).Common
+ Version = (await import("#miao")).Version
+} catch (err) {}
-let app = App.init({
- id: 'version',
- name: '版本',
- desc: '版本'
-})
+export let version = {}
+if (App) {
+ let app = App.init({
+ id: "version",
+ name: "版本",
+ desc: "版本"
+ })
-app.reg({
- version: {
- rule: /^#版本$/,
- desc: '【#帮助】 版本介绍',
- fn: async function (e) {
- let { changelogs, currentVersion } = Version.readLogFile('root')
- return await Common.render('help/version-info', {
- currentVersion,
- changelogs,
- name: 'TRSS-Yunzai',
- elem: 'cryo',
- pluginName: false,
- pluginVersion: false
- }, { e, scale: 1.2 })
+ app.reg({
+ version: {
+ rule: /^#版本$/,
+ desc: "【#帮助】 版本介绍",
+ fn: async function (e) {
+ let { changelogs, currentVersion } = Version.readLogFile("root")
+ return await Common.render("help/version-info", {
+ currentVersion,
+ changelogs,
+ name: "TRSS-Yunzai",
+ elem: "cryo",
+ pluginName: false,
+ pluginVersion: false,
+ }, { e, scale: 1.2 })
+ }
}
- }
-})
+ })
-export const version = app.v3App()
+ version = app.v3App()
+}
\ No newline at end of file
diff --git a/Yunzai/plugins/system/add.js b/Yunzai/plugins/system/add.js
index eacf1ac98268bd8dc9e89ddd044047dfe21c4121..4f358ed89a7643c529455307bbe3f361a5730864 100644
--- a/Yunzai/plugins/system/add.js
+++ b/Yunzai/plugins/system/add.js
@@ -1,11 +1,7 @@
import cfg from "../../lib/config/config.js"
-import plugin from "../../lib/plugins/plugin.js"
-import common from "../../lib/common/common.js"
-import fs from "node:fs"
+import fs from "node:fs/promises"
import path from "node:path"
import lodash from "lodash"
-import fetch from "node-fetch"
-import { fileTypeFromBuffer } from "file-type"
let messageMap = {}
@@ -41,7 +37,7 @@ export class add extends plugin {
}
async init() {
- common.mkdirs(this.path)
+ await Bot.mkdir(this.path)
}
/** 群号key */
@@ -59,7 +55,7 @@ export class add extends plugin {
return
}
- this.initMessageMap()
+ await this.initMessageMap()
if (!this.checkAuth()) return false
/** 获取关键词 */
@@ -101,6 +97,10 @@ export class add extends plugin {
checkAuth() {
if (this.e.isMaster) return true
+ if (this.isGlobal) {
+ this.reply("暂无权限,只有主人才能操作")
+ return false
+ }
const groupCfg = cfg.getGroup(this.e.self_id, this.group_id)
if (groupCfg.addLimit == 2) {
@@ -145,7 +145,7 @@ export class add extends plugin {
/** 添加内容 */
async addContext() {
- const context = this.getContext()?.addContext
+ const context = this.getContext("addContext")
this.isGlobal = context.isGlobal
await this.getGroupId()
/** 关键词 */
@@ -181,46 +181,25 @@ export class add extends plugin {
if (message.length > 1)
this.keyWord += String(message.length)
- this.saveJson()
+ await this.saveJson()
return this.reply(`添加成功:${this.keyWord}`)
}
- saveJson() {
+ async saveJson() {
let obj = {}
for (let [k, v] of messageMap[this.group_id])
obj[k] = v
- fs.writeFileSync(`${this.path}${this.group_id}.json`, JSON.stringify(obj, "", "\t"))
- }
-
- async makeBuffer(file) {
- if (file.match(/^base64:\/\//))
- return Buffer.from(file.replace(/^base64:\/\//, ""), "base64")
- else if (file.match(/^https?:\/\//))
- return Buffer.from(await (await fetch(file)).arrayBuffer())
- else if (fs.existsSync(file))
- return Buffer.from(fs.readFileSync(file))
- return file
- }
-
- async fileType(data) {
- const file = { name: `${this.group_id}/${data.type}/${Date.now()}` }
- try {
- file.url = data.url.replace(/^base64:\/\/.*/, "base64://...")
- file.buffer = await this.makeBuffer(data.url)
- file.type = await fileTypeFromBuffer(file.buffer)
- file.name = `${file.name}.${file.type.ext}`
- } catch (err) {
- logger.error(`文件类型检测错误:${logger.red(err)}`)
- file.name = `${file.name}-${path.basename(data.file || data.url)}`
- }
- return file
+ await fs.writeFile(`${this.path}${this.group_id}.json`, JSON.stringify(obj, "", "\t"))
}
async saveFile(data) {
- const file = await this.fileType(data)
- if (file.name && Buffer.isBuffer(file.buffer) && common.mkdirs(path.dirname(`${this.path}${file.name}`))) {
- fs.writeFileSync(`${this.path}${file.name}`, file.buffer)
+ const file = await Bot.fileType({ ...data, file: data.url })
+ if (Buffer.isBuffer(file.buffer)) {
+ file.name = `${this.group_id}/${data.type}/${file.name}`
+ file.path = `${this.path}${file.name}`
+ await Bot.mkdir(path.dirname(file.path))
+ await fs.writeFile(file.path, file.buffer)
return file.name
}
return data.url
@@ -233,8 +212,8 @@ export class add extends plugin {
await this.getGroupId()
if (!this.group_id) return false
- this.initMessageMap()
- this.initGlobalMessageMap()
+ await this.initMessageMap()
+ await this.initGlobalMessageMap()
this.keyWord = this.trimAlias(this.e.raw_message.trim())
let keyWord = this.keyWord
@@ -260,8 +239,8 @@ export class add extends plugin {
msg = [...msg[num]]
for (const i in msg)
- if (msg[i].file && fs.existsSync(`${this.path}${msg[i].file}`))
- msg[i] = { ...msg[i], file: `base64://${fs.readFileSync(`${this.path}${msg[i].file}`).toString("base64")}` }
+ if (msg[i].file && await Bot.fsStat(`${this.path}${msg[i].file}`))
+ msg[i] = { ...msg[i], file: `${this.path}${msg[i].file}` }
logger.mark(`[发送消息]${this.e.logText} ${this.keyWord}`)
const groupCfg = cfg.getGroup(this.e.self_id, this.group_id)
@@ -272,15 +251,15 @@ export class add extends plugin {
}
/** 初始化已添加内容 */
- initMessageMap() {
+ async initMessageMap() {
if (messageMap[this.group_id]) return
messageMap[this.group_id] = new Map()
const path = `${this.path}${this.group_id}.json`
- if (!fs.existsSync(path)) return
+ if (!await Bot.fsStat(path)) return
try {
- const message = JSON.parse(fs.readFileSync(path, "utf8"))
+ const message = JSON.parse(await fs.readFile(path, "utf8"))
for (const i in message)
messageMap[this.group_id].set(i, message[i])
} catch (err) {
@@ -289,15 +268,15 @@ export class add extends plugin {
}
/** 初始化全局已添加内容 */
- initGlobalMessageMap() {
+ async initGlobalMessageMap() {
if (messageMap.global) return
messageMap.global = new Map()
const globalPath = `${this.path}global.json`
- if (!fs.existsSync(globalPath)) return
+ if (!await Bot.fsStat(globalPath)) return
try {
- const message = JSON.parse(fs.readFileSync(globalPath, "utf8"))
+ const message = JSON.parse(await fs.readFile(globalPath, "utf8"))
for (const i in message)
messageMap.global.set(i, message[i])
} catch (err) {
@@ -310,7 +289,7 @@ export class add extends plugin {
await this.getGroupId()
if (!(this.group_id && this.checkAuth())) return false
- this.initMessageMap()
+ await this.initMessageMap()
this.getKeyWord()
if (!this.keyWord) {
@@ -370,7 +349,7 @@ export class add extends plugin {
}
}
- this.saveJson()
+ await this.saveJson()
return this.reply(`删除成功:${this.keyWord}`)
}
@@ -384,7 +363,7 @@ export class add extends plugin {
await this.getGroupId()
if (!this.group_id) return false
- this.initMessageMap()
+ await this.initMessageMap()
const search = this.e.msg.replace(/^#(全局)?(消息|词条)/, "").trim()
if (search.match(/^列表/))
@@ -426,16 +405,17 @@ export class add extends plugin {
msg.push(`${i.num}. ${keyWord}(${i.val.length})`)
num++
}
- msg = [msg.join("\n")]
-
- if (type == "list" && count > 100)
- msg.push(`更多内容请翻页查看\n如:#消息列表${Number(page)+1}`)
let title = `消息列表:第${page}页,共${count}条`
if (type == "search")
title = `消息${search}:共${count}条`
- return this.reply(await common.makeForwardMsg(this.e, msg, title))
+ msg = [title, msg.join("\n")]
+
+ if (type == "list" && count > 100)
+ msg.push(`更多内容请翻页查看\n如:#消息列表${Number(page)+1}`)
+
+ return this.reply(await Bot.makeForwardArray(msg))
}
/** 分页 */
diff --git a/Yunzai/plugins/system/botOperate.js b/Yunzai/plugins/system/botOperate.js
index acceaab74024b7f87b7b4f7fc8b34f5614fc900e..e0eb62b62b07baeba38c78f2a8f82a0554246c92 100644
--- a/Yunzai/plugins/system/botOperate.js
+++ b/Yunzai/plugins/system/botOperate.js
@@ -1,7 +1,7 @@
export class botOperate extends plugin {
- constructor () {
+ constructor() {
super({
- name: "Bot 操作",
+ name: "botOperate",
dsc: "Bot 操作",
event: "message",
rule: [
@@ -20,7 +20,10 @@ export class botOperate extends plugin {
}
Verify() {
- const data = { msg: this.e.msg.replace(/^#(Bot|机器人)验证/, "").trim().split(":") }
+ const data = {
+ msg: this.e.msg.replace(/^#(Bot|机器人)验证/, "").trim().split(":"),
+ reply: msg => this.reply(msg, true),
+ }
data.self_id = data.msg.shift()
data.msg = data.msg.join(":")
Bot.em(`verify.${data.self_id}`, data)
diff --git a/Yunzai/plugins/system/disablePrivate.js b/Yunzai/plugins/system/disablePrivate.js
index b029557a6f8623b0e08489e45e6b20104d752c75..62644717595dd60acbf1f59d6f77704793d30b30 100644
--- a/Yunzai/plugins/system/disablePrivate.js
+++ b/Yunzai/plugins/system/disablePrivate.js
@@ -1,5 +1,4 @@
import cfg from '../../lib/config/config.js'
-import plugin from '../../lib/plugins/plugin.js'
export class disPri extends plugin {
constructor () {
@@ -29,9 +28,14 @@ export class disPri extends plugin {
/** 绑定ck,抽卡链接 */
let wordReg = /(.*)(ltoken|_MHYUUID|authkey=)(.*)|导出记录(json)*|(记录|安卓|苹果|ck|cookie|体力)帮助|^帮助$|^#*(删除|我的)ck$|^#(我的)?(uid|UID)[0-9]{0,2}$/g
/** 自定义通行字符 */
- let disableReg = `(.*)(${cfg.other?.disableAdopt?.join('|')})(.*)`
+ let disableAdopt = cfg.other?.disableAdopt
+ if (!Array.isArray(disableAdopt)) {
+ disableAdopt = []
+ }
+ disableAdopt = disableAdopt.filter(str => str != null && str !== '');
+ let disableReg = `(.*)(${disableAdopt.join('|')})(.*)`
if (this.e.raw_message) {
- if (!new RegExp(wordReg).test(this.e.raw_message) && (!new RegExp(disableReg).test(this.e.raw_message))) {
+ if (!new RegExp(wordReg).test(this.e.raw_message) && (disableAdopt.length === 0 || !new RegExp(disableReg).test(this.e.raw_message))) {
this.sendTips()
return 'return'
}
diff --git a/Yunzai/plugins/system/friend.js b/Yunzai/plugins/system/friend.js
index ce3c17b40efbf828ac9cea4589bea90795583c73..9efeae4eda44490f37a854f31a8022f7073b1272 100644
--- a/Yunzai/plugins/system/friend.js
+++ b/Yunzai/plugins/system/friend.js
@@ -1,20 +1,19 @@
-import cfg from '../../lib/config/config.js'
-import common from '../../lib/common/common.js'
+import cfg from "../../lib/config/config.js"
export class friend extends plugin {
constructor () {
super({
- name: 'autoFriend',
- dsc: '自动同意好友',
- event: 'request.friend'
+ name: "autoFriend",
+ dsc: "自动同意好友",
+ event: "request.friend"
})
}
async accept() {
- if (this.e.sub_type == 'add' || this.e.sub_type == 'single') {
+ if (this.e.sub_type == "add" || this.e.sub_type == "single") {
if (cfg.other.autoFriend == 1) {
logger.mark(`[自动同意][添加好友] ${this.e.user_id}`)
- await common.sleep(2000)
+ await Bot.sleep(3000)
this.e.approve(true)
}
}
diff --git a/Yunzai/plugins/system/invite.js b/Yunzai/plugins/system/invite.js
index 2b74778272e8eedf5eed47f3b0965f5a38546651..4c00b2ad0eb05189cc93e5ad542958ad27441102 100644
--- a/Yunzai/plugins/system/invite.js
+++ b/Yunzai/plugins/system/invite.js
@@ -1,11 +1,9 @@
-import cfg from '../../lib/config/config.js'
-
export class invite extends plugin {
constructor () {
super({
- name: 'invite',
- dsc: '主人邀请自动进群',
- event: 'request.group.invite'
+ name: "invite",
+ dsc: "主人邀请自动进群",
+ event: "request.group.invite"
})
}
@@ -18,4 +16,4 @@ export class invite extends plugin {
this.e.approve(true)
this.e.bot.pickFriend(this.e.user_id).sendMsg(`已同意加群:${this.e.group_name}`)
}
-}
+}
\ No newline at end of file
diff --git a/Yunzai/plugins/system/master.js b/Yunzai/plugins/system/master.js
index 8f238dce5b23ad3c79554a5341c270ecaf5873bb..569aeeec3e0c5f9f9254549018907c202efa3fce 100644
--- a/Yunzai/plugins/system/master.js
+++ b/Yunzai/plugins/system/master.js
@@ -1,9 +1,9 @@
-import fs from "fs"
-import { randomUUID } from "crypto"
+import fs from "node:fs/promises"
+import { randomUUID } from "node:crypto"
let code = {}
let file = "config/config/other.yaml"
export class master extends plugin {
- constructor () {
+ constructor() {
super({
name: "设置主人",
dsc: "设置主人",
@@ -17,8 +17,8 @@ export class master extends plugin {
})
}
- edit (file, key, value) {
- let data = fs.readFileSync(file, "utf8")
+ async edit(file, key, value) {
+ let data = await fs.readFile(file, "utf8")
if (data.match(RegExp(`- "?${value}"?`)))
return
value = `${key}:\n - "${value}"`
@@ -26,27 +26,27 @@ export class master extends plugin {
data = data.replace(RegExp(`${key}:`), value)
else
data = `${data}\n${value}`
- fs.writeFileSync(file, data, "utf8")
+ return fs.writeFile(file, data, "utf8")
}
- async master () {
+ async master() {
if (this.e.isMaster) {
- await this.reply(`账号:${this.e.user_id} 已经为主人`, true)
+ await this.reply(`[${this.e.user_id}] 已经为主人`, true)
return false
}
code[this.e.user_id] = randomUUID()
logger.mark(`${logger.cyan(`[${this.e.user_id}]`)} 设置主人验证码:${logger.green(code[this.e.user_id])}`)
this.setContext("verify")
- await this.reply(`账号:${this.e.user_id} 请输入验证码`, true)
+ await this.reply(`[${this.e.user_id}] 请输入验证码`, true)
}
- async verify () {
+ async verify() {
this.finish("verify")
if (this.e.msg.trim() == code[this.e.user_id]) {
- this.edit(file, "masterQQ", this.e.user_id)
- this.edit(file, "master", `${this.e.self_id}:${this.e.user_id}`)
- await this.reply(`账号:${this.e.user_id} 设置主人成功`, true)
+ await this.edit(file, "masterQQ", this.e.user_id)
+ await this.edit(file, "master", `${this.e.self_id}:${this.e.user_id}`)
+ await this.reply(`[${this.e.user_id}] 设置主人成功`, true)
} else {
await this.reply("验证码错误", true)
return false
diff --git a/Yunzai/plugins/system/quit.js b/Yunzai/plugins/system/quit.js
index 6c8d7f5b8101a4c5c813babfa3bf6055337d2b49..13a0162428fce03ffb58e218c2a359e728a65396 100644
--- a/Yunzai/plugins/system/quit.js
+++ b/Yunzai/plugins/system/quit.js
@@ -1,36 +1,34 @@
-import cfg from '../../lib/config/config.js'
-
+import cfg from "../../lib/config/config.js"
export class quit extends plugin {
- constructor () {
+ constructor() {
super({
- name: 'notice',
- dsc: '自动退群',
- event: 'notice.group.increase'
+ name: "notice",
+ dsc: "自动退群",
+ event: "notice.group.increase"
})
}
- async accept () {
- if (this.e.user_id != this.e.self_id) return
+ async accept() {
+ if (this.e.user_id != this.e.self_id || !this.e.group?.quit || !this.e.group.getMemberMap) return false
+
+ const other = cfg.other
+ if (!other.autoQuit) return false
- let other = cfg.other
- if (other.autoQuit <= 0) return
+ const gml = await this.e.group.getMemberMap()
+ if (!gml instanceof Map) return false
- /** 判断主人,主人邀请不退群 */
- let gl = await this.e.group.getMemberMap()
- for (let qq of cfg.masterQQ) {
- if (gl.has(Number(qq) || String(qq))) {
+ /** 判断主人邀请不退群 */
+ for (const qq of cfg.masterQQ)
+ if (gml.has(Number(qq) || String(qq))) {
logger.mark(`[主人拉群] ${this.e.group_id}`)
- return
+ return false
}
- }
/** 自动退群 */
- if (Array.from(gl).length <= other.autoQuit && !this.e.group.is_owner) {
- await this.e.reply('禁止拉群,已自动退出')
+ if (Array.from(gml).length <= other.autoQuit && !this.e.group.is_owner) {
+ await this.reply("禁止拉群,已自动退出")
logger.mark(`[自动退群] ${this.e.group_id}`)
- setTimeout(() => {
- this.e.group.quit()
- }, 2000)
+ this.e.group.quit()
}
}
-}
+}
\ No newline at end of file
diff --git a/Yunzai/plugins/system/status.js b/Yunzai/plugins/system/status.js
index a8b9a4123728b53e2aff732f938ffdbdb834a972..d7d3e0bf01bc5be6bcc4784badaf3f364482afd0 100644
--- a/Yunzai/plugins/system/status.js
+++ b/Yunzai/plugins/system/status.js
@@ -1,124 +1,171 @@
-import cfg from '../../lib/config/config.js'
-import moment from 'moment'
+import cfg from "../../lib/config/config.js"
+import moment from "moment"
export class status extends plugin {
constructor() {
super({
- name: '其他功能',
- dsc: '#状态',
- event: 'message',
+ name: "状态统计",
+ dsc: "#状态",
+ event: "message",
rule: [
{
- reg: '^#状态$',
- fnc: 'status'
+ reg: "^#(状态|统计)",
+ fnc: "status",
}
]
})
}
async status() {
- if (this.e.isMaster) return this.statusMaster()
- if (!this.e.isGroup) return this.reply('请群聊查看')
- return this.statusGroup()
+ if (!this.e.isMaster)
+ return this.reply(await this.getCount({
+ "用户": this.e.user_id,
+ "群": this.e.group_id,
+ }))
+
+ const msg =
+ `—— TRSS Yunzai v${cfg.package.version} ——\n`+
+ `运行时间:${Bot.getTimeDiff()}\n`+
+ `内存使用:${(process.memoryUsage().rss/1024/1024).toFixed(2)}MB\n`+
+ `系统版本:${process.platform} ${process.arch} ${process.version}`+
+ this.botTime() + await this.count()
+
+ return this.reply(msg)
}
- async statusMaster() {
- let runTime = moment().diff(moment.unix(this.e.bot.stat.start_time), 'seconds')
- let Day = Math.floor(runTime / 3600 / 24)
- let Hour = Math.floor((runTime / 3600) % 24)
- let Min = Math.floor((runTime / 60) % 60)
- if (Day > 0) {
- runTime = `${Day}天${Hour}小时${Min}分钟`
- } else {
- runTime = `${Hour}小时${Min}分钟`
- }
-
- let format = (bytes) => {
- return (bytes / 1024 / 1024).toFixed(2) + 'MB'
- }
-
- let msg = '-------状态-------'
- msg += `\n运行时间:${runTime}`
- msg += `\n内存使用:${format(process.memoryUsage().rss)}`
- msg += `\n当前版本:v${cfg.package.version}`
- msg += '\n-------累计-------'
- msg += await this.getCount()
-
- await this.reply(msg)
- }
-
- async statusGroup() {
- let msg = '-------状态-------'
- msg += await this.getCount(this.e.group_id)
-
- await this.reply(msg)
+ botTime() {
+ let msg = "\n\n账号在线时长"
+ for (const i of Bot.uin)
+ if (Bot[i]?.stat?.start_time)
+ msg += `\n${Bot.getTimeDiff(Bot[i].stat.start_time)} ${i}`
+ return msg
}
- async getCount(groupId = '') {
- this.date = moment().format('MMDD')
- this.month = Number(moment().month()) + 1
-
- this.key = 'Yz:count:'
-
- if (groupId) this.key += `group:${groupId}:`
-
- this.msgKey = {
- day: `${this.key}sendMsg:day:`,
- month: `${this.key}sendMsg:month:`
+ count() {
+ const cmd = {
+ msg: this.e.msg.replace(/^#(状态|统计)/, "").trim().split(" ")
}
-
- this.screenshotKey = {
- day: `${this.key}screenshot:day:`,
- month: `${this.key}screenshot:month:`
+ let key = ""
+ for (const i of cmd.msg) if (key) {
+ cmd[key] = i
+ key = ""
+ } else {
+ key = i
}
+ return this.getCount(cmd)
+ }
- let week = {
- msg: 0,
- screenshot: 0
+ async getCount(cmd) {
+ const date = []
+ if (cmd["日期"]) {
+ cmd["日期"] = cmd["日期"].replace(/[^\d]/g,"")
+ switch (cmd["日期"].length) {
+ case 8:
+ date.push([
+ cmd["日期"].slice(0, 4),
+ cmd["日期"].slice(4, 6),
+ cmd["日期"].slice(6, 8),
+ ])
+ break
+ case 4:
+ date.push([
+ moment().format("YYYY"),
+ cmd["日期"].slice(0, 2),
+ cmd["日期"].slice(2, 4),
+ ])
+ break
+ case 2:
+ date.push([
+ moment().format("YYYY"),
+ moment().format("MM"),
+ cmd["日期"],
+ ])
+ break
+ default:
+ this.reply(`日期格式错误:${cmd["日期"]}`)
+ return ""
+ }
+ } else {
+ const d = moment()
+ for (let i = 0; i < 3; i++) {
+ date.push(d.format("YYYY MM DD").split(" "))
+ d.add(-86400000)
+ }
+ date.push(
+ [d.format("YYYY"), d.format("MM")],
+ [d.format("YYYY")],
+ ["total"],
+ )
}
- for (let i = 0; i <= 6; i++) {
- let date = moment().startOf('week').add(i, 'days').format('MMDD')
- week.msg += Number(await redis.get(`${this.msgKey.day}${date}`)) ?? 0
- week.screenshot += Number(await redis.get(`${this.screenshotKey.day}${date}`)) ?? 0
+ let msg = "消息统计"
+ if (cmd["消息"]) {
+ msg = `${cmd["消息"]} ${msg}`
+ } else {
+ cmd["消息"] = "msg"
}
- let count = {
- total: {
- msg: await redis.get(`${this.key}sendMsg:total`) || 0,
- screenshot: await redis.get(`${this.key}screenshot:total`) || 0
- },
- today: {
- msg: await redis.get(`${this.msgKey.day}${this.date}`) || 0,
- screenshot: await redis.get(`${this.screenshotKey.day}${this.date}`) || 0
- },
- week,
- month: {
- msg: await redis.get(`${this.msgKey.month}${this.month}`) || 0,
- screenshot: await redis.get(`${this.screenshotKey.month}${this.month}`) || 0
- }
+ const array = []
+ if (cmd["机器人"])
+ array.push({ text: "机器人", key: `bot`, id: cmd["机器人"] })
+ if (cmd["用户"])
+ array.push({ text: "用户", key: `user`, id: cmd["用户"] })
+ if (cmd["群"])
+ array.push({ text: "群", key: `group`, id: cmd["群"] })
+ if (!array.length) {
+ array.push(
+ { text: msg, key: "total" },
+ { type: "keys", text: "用户量", key: "user:*" },
+ { type: "keys", text: "群量", key: "group:*" },
+ )
+ msg = ""
+ if (this.e.self_id)
+ array.push({ text: "机器人", key: `bot`, id: this.e.self_id })
+ if (this.e.user_id)
+ array.push({ text: "用户", key: `user`, id: this.e.user_id })
+ if (this.e.group_id)
+ array.push({ text: "群", key: `group`, id: this.e.group_id })
}
- let msg = ''
- if (groupId) {
- msg = `\n发送消息:${count.today.msg}条`
- msg += `\n生成图片:${count.today.screenshot}次`
- } else {
- msg = `\n发送消息:${count.total.msg}条`
- msg += `\n生成图片:${count.total.screenshot}次`
+ for (const i of array) {
+ if (i.id) {
+ i.text += ` ${i.id}`
+ i.key += `:${i.id}`
+ }
+ msg += `\n\n${i.text}`
+ for (let d of date) {
+ const key = `:${cmd["消息"]}:${i.key}:${d.join(":")}`
+ d = d.join("-")
+ if (d == "total")
+ d = `总计 -------`
+ else
+ d = `${d} ${"-".repeat(11 - d.length)}`
+ const ret = await this.redis(i.type, key)
+ msg += `\n${d} 收 ${ret.receive} 发 ${ret.send}`
+ }
}
+ return msg
+ }
- if (count.month.msg > 200) {
- msg += '\n-------本周-------'
- msg += `\n发送消息:${count.week.msg}条`
- msg += `\n生成图片:${count.week.screenshot}次`
- }
- if (moment().format('D') >= 8 && count.month.msg > 400) {
- msg += '\n-------本月-------'
- msg += `\n发送消息:${count.month.msg}条`
- msg += `\n生成图片:${count.month.screenshot}次`
+ async redis(type, key) {
+ const ret = {}
+ for (const i of ["receive", "send"]) {
+ const k = `Yz:count:${i}${key}`
+ if (type == "keys")
+ ret[i] = await this.redisKeysLength(k) || 0
+ else
+ ret[i] = await redis.get(k) || 0
}
+ return ret
+ }
- return msg
+ async redisKeysLength(MATCH) {
+ let cursor = 0, length = 0
+ do {
+ const reply = await redis.scan(cursor, { MATCH, COUNT: 10000 })
+ cursor = reply.cursor
+ length += reply.keys.length
+ } while (cursor != 0)
+ return length
}
-}
+}
\ No newline at end of file
diff --git a/Yunzai/renderers/puppeteer/lib/puppeteer.js b/Yunzai/renderers/puppeteer/lib/puppeteer.js
index 9daf954b99dcf88cd60a8e3ea718e8f18495b7c6..95c38515800546bfc5e68fb949319a4385c3e001 100644
--- a/Yunzai/renderers/puppeteer/lib/puppeteer.js
+++ b/Yunzai/renderers/puppeteer/lib/puppeteer.js
@@ -1,23 +1,20 @@
-import Renderer from '../../../lib/renderer/Renderer.js'
-import os from 'node:os'
-import lodash from 'lodash'
-import puppeteer from 'puppeteer'
+import Renderer from "../../../lib/renderer/Renderer.js"
+import os from "node:os"
+import lodash from "lodash"
+import puppeteer from "puppeteer"
// 暂时保留对原config的兼容
-import cfg from '../../../lib/config/config.js'
-import { Data } from '#miao'
+import cfg from "../../../lib/config/config.js"
const _path = process.cwd()
// mac地址
-let mac = ''
-// 超时计时器
-let overtimeList = []
+let mac = ""
export default class Puppeteer extends Renderer {
- constructor (config) {
+ constructor(config) {
super({
- id: 'puppeteer',
- type: 'image',
- render: 'screenshot'
+ id: "puppeteer",
+ type: "image",
+ render: "screenshot"
})
this.browser = false
this.lock = false
@@ -27,22 +24,20 @@ export default class Puppeteer extends Renderer {
/** 截图次数 */
this.renderNum = 0
this.config = {
- headless: Data.def(config.headless, 'new'),
- args: Data.def(config.args, [
- '--disable-gpu',
- '--disable-setuid-sandbox',
- '--no-sandbox',
- '--no-zygote'
- ])
+ headless: config.headless || "new",
+ args: config.args || [
+ "--disable-gpu",
+ "--disable-setuid-sandbox",
+ "--no-sandbox",
+ "--no-zygote"
+ ]
}
- if (config.chromiumPath || cfg?.bot?.chromium_path) {
+ if (config.chromiumPath || cfg?.bot?.chromium_path)
/** chromium其他路径 */
this.config.executablePath = config.chromiumPath || cfg?.bot?.chromium_path
- }
- if (config.puppeteerWS || cfg?.bot?.puppeteer_ws) {
+ if (config.puppeteerWS || cfg?.bot?.puppeteer_ws)
/** chromium其他路径 */
this.config.wsEndpoint = config.puppeteerWS || cfg?.bot?.puppeteer_ws
- }
/** puppeteer超时超时时间 */
this.puppeteerTimeout = config.puppeteerTimeout || cfg?.bot?.puppeteer_timeout || 0
}
@@ -50,12 +45,12 @@ export default class Puppeteer extends Renderer {
/**
* 初始化chromium
*/
- async browserInit () {
+ async browserInit() {
if (this.browser) return this.browser
if (this.lock) return false
this.lock = true
- logger.info('puppeteer Chromium 启动中...')
+ logger.info("puppeteer Chromium 启动中...")
let connectFlag = false
try {
@@ -67,35 +62,32 @@ export default class Puppeteer extends Renderer {
// 是否有browser实例
const browserUrl = (await redis.get(this.browserMacKey)) || this.config.wsEndpoint
if (browserUrl) {
- logger.info(`puppeteer Chromium from ${browserUrl}`)
- const browserWSEndpoint = await puppeteer.connect({ browserWSEndpoint: browserUrl }).catch(() => {
- logger.error('puppeteer Chromium 缓存的实例已关闭')
- redis.del(this.browserMacKey)
- })
- // 如果有实例,直接使用
- if (browserWSEndpoint) {
- this.browser = browserWSEndpoint
- if (this.browser) {
+ try {
+ const browserWSEndpoint = await puppeteer.connect({ browserWSEndpoint: browserUrl })
+ // 如果有实例,直接使用
+ if (browserWSEndpoint) {
+ this.browser = browserWSEndpoint
connectFlag = true
}
+ logger.info(`puppeteer Chromium 连接成功 ${browserUrl}`)
+ } catch (err) {
+ await redis.del(this.browserMacKey)
}
}
- } catch (e) {
- logger.info('puppeteer Chromium 不存在已有实例')
- }
+ } catch (err) {}
if (!this.browser || !connectFlag) {
// 如果没有实例,初始化puppeteer
this.browser = await puppeteer.launch(this.config).catch((err, trace) => {
- let errMsg = err.toString() + (trace ? trace.toString() : '')
- if (typeof err == 'object') {
+ let errMsg = err.toString() + (trace ? trace.toString() : "")
+ if (typeof err == "object") {
logger.error(JSON.stringify(err))
} else {
logger.error(err.toString())
- if (errMsg.includes('Could not find Chromium')) {
- logger.error('没有正确安装 Chromium,可以尝试执行安装命令:node node_modules/puppeteer/install.js')
- } else if (errMsg.includes('cannot open shared object file')) {
- logger.error('没有正确安装 Chromium 运行库')
+ if (errMsg.includes("Could not find Chromium")) {
+ logger.error("没有正确安装 Chromium,可以尝试执行安装命令:node node_modules/puppeteer/install.js")
+ } else if (errMsg.includes("cannot open shared object file")) {
+ logger.error("没有正确安装 Chromium 运行库")
}
}
logger.error(err, trace)
@@ -105,33 +97,27 @@ export default class Puppeteer extends Renderer {
this.lock = false
if (!this.browser) {
- logger.error('puppeteer Chromium 启动失败')
+ logger.error("puppeteer Chromium 启动失败")
return false
}
- if (connectFlag) {
- logger.info('puppeteer Chromium 已连接启动的实例')
- } else {
- logger.info(`[Chromium] ${this.browser.wsEndpoint()}`)
- if (process.env.pm_id && this.browserMacKey) {
+ if (!connectFlag) {
+ logger.info(`puppeteer Chromium 启动成功 ${this.browser.wsEndpoint()}`)
+ if (this.browserMacKey) {
// 缓存一下实例30天
const expireTime = 60 * 60 * 24 * 30
await redis.set(this.browserMacKey, this.browser.wsEndpoint(), { EX: expireTime })
}
- logger.info('puppeteer Chromium 启动成功')
}
/** 监听Chromium实例是否断开 */
- this.browser.on('disconnected', () => {
- logger.error('Chromium 实例关闭或崩溃!')
- this.browser = false
- })
+ this.browser.on("disconnected", () => this.restart(true))
return this.browser
}
// 获取Mac地址
- getMac () {
- let mac = '00:00:00:00:00:00'
+ getMac() {
+ let mac = "00:00:00:00:00:00"
try {
const network = os.networkInterfaces()
let macFlag = false
@@ -149,7 +135,7 @@ export default class Puppeteer extends Renderer {
}
} catch (e) {
}
- mac = mac.replace(/:/g, '')
+ mac = mac.replace(/:/g, "")
return mac
}
@@ -168,18 +154,15 @@ export default class Puppeteer extends Renderer {
* @param data.pageGotoParams 页面goto时的参数
* @return img 不做segment包裹
*/
- async screenshot (name, data = {}) {
- if (!await this.browserInit()) {
+ async screenshot(name, data = {}) {
+ if (!await this.browserInit())
return false
- }
const pageHeight = data.multiPageHeight || 4000
let savePath = this.dealTpl(name, data)
- if (!savePath) {
- return false
- }
+ if (!savePath) return false
- let buff = ''
+ let buff = ""
let start = Date.now()
let ret = []
@@ -187,17 +170,13 @@ export default class Puppeteer extends Renderer {
const puppeteerTimeout = this.puppeteerTimeout
let overtime
- let overtimeFlag = false
if (puppeteerTimeout > 0) {
// TODO 截图超时处理
overtime = setTimeout(() => {
- if (!overtimeFlag) {
- logger.error(`[图片生成][${name}] 截图超时,当前等待队列:${this.shoting.join(',')}`)
+ if (this.shoting.length) {
+ logger.error(`[图片生成][${name}] 截图超时,当前等待队列:${this.shoting.join(",")}`)
this.restart(true)
this.shoting = []
- overtimeList.forEach(item => {
- clearTimeout(item)
- })
}
}, puppeteerTimeout)
}
@@ -205,8 +184,8 @@ export default class Puppeteer extends Renderer {
try {
const page = await this.browser.newPage()
let pageGotoParams = lodash.extend({ timeout: 120000 }, data.pageGotoParams || {})
- await page.goto(`file://${_path}${lodash.trim(savePath, '.')}`, pageGotoParams)
- let body = await page.$('#container') || await page.$('body')
+ await page.goto(`file://${_path}${lodash.trim(savePath, ".")}`, pageGotoParams)
+ let body = await page.$("#container") || await page.$("body")
// 计算页面高度
const boundingBox = await body.boundingBox()
@@ -214,27 +193,27 @@ export default class Puppeteer extends Renderer {
let num = 1
let randData = {
- type: data.imgType || 'jpeg',
+ type: data.imgType || "jpeg",
omitBackground: data.omitBackground || false,
quality: data.quality || 90,
- path: data.path || ''
+ path: data.path || ""
}
if (data.multiPage) {
- randData.type = 'jpeg'
+ randData.type = "jpeg"
num = Math.round(boundingBox.height / pageHeight) || 1
}
- if (data.imgType === 'png') {
+ if (data.imgType === "png") {
delete randData.quality
}
if (!data.multiPage) {
buff = await body.screenshot(randData)
+ this.renderNum++
/** 计算图片大小 */
- const kb = (buff.length / 1024).toFixed(2) + 'KB'
+ const kb = (buff.length / 1024).toFixed(2) + "KB"
logger.mark(`[图片生成][${name}][${this.renderNum}次] ${kb} ${logger.green(`${Date.now() - start}ms`)}`)
- this.renderNum++
ret.push(buff)
} else {
// 分片截图
@@ -260,12 +239,12 @@ export default class Puppeteer extends Renderer {
buff = await page.screenshot(randData)
}
if (num > 2) {
- await Data.sleep(200)
+ await Bot.sleep(200)
}
this.renderNum++
/** 计算图片大小 */
- const kb = (buff.length / 1024).toFixed(2) + 'KB'
+ const kb = (buff.length / 1024).toFixed(2) + "KB"
logger.mark(`[图片生成][${name}][${i}/${num}] ${kb}`)
ret.push(buff)
}
@@ -273,22 +252,16 @@ export default class Puppeteer extends Renderer {
logger.mark(`[图片生成][${name}] 处理完成`)
}
}
- page.close().catch((err) => logger.error(err))
- } catch (error) {
- logger.error(`[图片生成][${name}] 图片生成失败:${error}`)
+ page.close().catch(err => logger.error(err))
+ } catch (err) {
+ logger.error(`[图片生成][${name}] 图片生成失败`, err)
/** 关闭浏览器 */
- if (this.browser) {
- await this.browser.close().catch((err) => logger.error(err))
- }
- this.browser = false
+ this.restart(true)
+ if (overtime) clearTimeout(overtime)
ret = []
return false
} finally {
- if (overtime) {
- overtimeFlag = true
- clearTimeout(overtime)
- overtimeList = []
- }
+ if (overtime) clearTimeout(overtime)
}
this.shoting.pop()
@@ -298,24 +271,26 @@ export default class Puppeteer extends Renderer {
return false
}
- this.restart(false)
-
+ this.restart()
return data.multiPage ? ret : ret[0]
}
/** 重启 */
- restart (force = false) {
+ restart(force = false) {
/** 截图超过重启数时,自动关闭重启浏览器,避免生成速度越来越慢 */
- if (this.renderNum % this.restartNum === 0 || force) {
- if (this.shoting.length <= 0 || force) {
- setTimeout(async () => {
- if (this.browser) {
- await this.browser.close().catch((err) => logger.error(err))
- }
- this.browser = false
- logger.info(`puppeteer Chromium ${force ? '强制' : ''}关闭重启...`)
- }, 100)
- }
+ if (!this.browser?.close || this.lock) return
+ if (!force) if (this.renderNum % this.restartNum !== 0 || this.shoting.length > 0) return
+ logger.info(`puppeteer Chromium ${force ? "强制" : ""}关闭重启...`)
+ this.stop(this.browser)
+ this.browser = false
+ return this.browserInit()
+ }
+
+ async stop(browser) {
+ try {
+ await browser.close()
+ } catch (err) {
+ logger.error("puppeteer Chromium 关闭错误", err)
}
}
-}
+}
\ No newline at end of file
diff --git a/Yunzai/resources/http/File/404.jpg b/Yunzai/resources/http/File/404.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..1ffae8c9e2eb1702a9700c2592c4cd3f6b6b83a2
Binary files /dev/null and b/Yunzai/resources/http/File/404.jpg differ
diff --git a/Yunzai/resources/http/File/timeout.jpg b/Yunzai/resources/http/File/timeout.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..3c2a3dcb58f6b14817e76aaafbffbb3566903d1d
Binary files /dev/null and b/Yunzai/resources/http/File/timeout.jpg differ