dfghfhgfg commited on
Commit
0800168
·
verified ·
1 Parent(s): e07417b

Upload 75 files

Browse files
Files changed (41) hide show
  1. Yunzai/.gitignore +5 -146
  2. Yunzai/.puppeteerrc.cjs +1 -1
  3. Yunzai/README.md +71 -34
  4. Yunzai/app.js +1 -1
  5. Yunzai/config/default_config/bot.yaml +9 -0
  6. Yunzai/config/default_config/redis.yaml +7 -5
  7. Yunzai/config/pm2.yaml +1 -1
  8. Yunzai/lib/bot.js +330 -203
  9. Yunzai/lib/common/common.js +12 -21
  10. Yunzai/lib/config/config.js +31 -48
  11. Yunzai/lib/config/init.js +27 -10
  12. Yunzai/lib/config/log.js +23 -26
  13. Yunzai/lib/config/redis.js +90 -47
  14. Yunzai/lib/listener/listener.js +1 -1
  15. Yunzai/lib/listener/loader.js +13 -15
  16. Yunzai/lib/modules/oicq/index.js +18 -11
  17. Yunzai/lib/plugins/config.js +2 -2
  18. Yunzai/lib/plugins/loader.js +90 -87
  19. Yunzai/lib/plugins/plugin.js +25 -22
  20. Yunzai/lib/plugins/runtime.js +21 -17
  21. Yunzai/lib/plugins/stdin.js +39 -25
  22. Yunzai/lib/puppeteer/puppeteer.js +10 -12
  23. Yunzai/lib/renderer/loader.js +7 -14
  24. Yunzai/lib/tools/docker.sh +109 -0
  25. Yunzai/package.json +9 -7
  26. Yunzai/plugins/.gitignore +1 -0
  27. Yunzai/plugins/adapter/ComWeChat.js +40 -17
  28. Yunzai/plugins/adapter/GSUIDCore.js +12 -9
  29. Yunzai/plugins/adapter/OPQBot.js +32 -9
  30. Yunzai/plugins/adapter/OneBotv11.js +78 -46
  31. Yunzai/plugins/example/package.json +4 -0
  32. Yunzai/plugins/example/主动复读.js +3 -3
  33. Yunzai/plugins/other/install.js +29 -29
  34. Yunzai/plugins/other/restart.js +109 -52
  35. Yunzai/plugins/other/sendLog.js +1 -1
  36. Yunzai/plugins/other/update.js +152 -127
  37. Yunzai/plugins/other/version.js +1 -1
  38. Yunzai/plugins/system/master.js +5 -5
  39. Yunzai/plugins/system/status.js +1 -1
  40. Yunzai/renderers/puppeteer/config_default.yaml +5 -0
  41. Yunzai/renderers/puppeteer/lib/puppeteer.js +12 -9
Yunzai/.gitignore CHANGED
@@ -1,148 +1,7 @@
1
- # Logs
 
2
  logs
3
- *.log
4
- npm-debug.log*
5
- yarn-debug.log*
6
- yarn-error.log*
7
- lerna-debug.log*
8
- .pnpm-debug.log*
9
-
10
- # Diagnostic reports (https://nodejs.org/api/report.html)
11
- report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
12
-
13
- # Runtime data
14
- pids
15
- *.pid
16
- *.seed
17
- *.pid.lock
18
-
19
- # Directory for instrumented libs generated by jscoverage/JSCover
20
- lib-cov
21
-
22
- # Coverage directory used by tools like istanbul
23
- coverage
24
- *.lcov
25
-
26
- # nyc test coverage
27
- .nyc_output
28
-
29
- # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30
- .grunt
31
-
32
- # Bower dependency directory (https://bower.io/)
33
- bower_components
34
-
35
- # node-waf configuration
36
- .lock-wscript
37
-
38
- # Compiled binary addons (https://nodejs.org/api/addons.html)
39
- build/Release
40
-
41
- # Dependency directories
42
- node_modules/
43
- jspm_packages/
44
-
45
- # Snowpack dependency directory (https://snowpack.dev/)
46
- web_modules/
47
-
48
- # TypeScript cache
49
- *.tsbuildinfo
50
-
51
- # Optional npm cache directory
52
- .npm
53
-
54
- # Optional eslint cache
55
- .eslintcache
56
-
57
- # Optional stylelint cache
58
- .stylelintcache
59
-
60
- # Microbundle cache
61
- .rpt2_cache/
62
- .rts2_cache_cjs/
63
- .rts2_cache_es/
64
- .rts2_cache_umd/
65
-
66
- # Optional REPL history
67
- .node_repl_history
68
-
69
- # Output of 'npm pack'
70
- *.tgz
71
-
72
- # Yarn Integrity file
73
- .yarn-integrity
74
-
75
- # dotenv environment variable files
76
- .env
77
- .env.development.local
78
- .env.test.local
79
- .env.production.local
80
- .env.local
81
-
82
- # parcel-bundler cache (https://parceljs.org/)
83
- .cache
84
- .parcel-cache
85
-
86
- # Next.js build output
87
- .next
88
- out
89
-
90
- # Nuxt.js build / generate output
91
- .nuxt
92
- dist
93
-
94
- # Gatsby files
95
- .cache/
96
- # Comment in the public line in if your project uses Gatsby and not Next.js
97
- # https://nextjs.org/blog/next-9-1#public-directory-support
98
- # public
99
-
100
- # vuepress build output
101
- .vuepress/dist
102
-
103
- # vuepress v2.x temp and cache directory
104
- .temp
105
- .cache
106
-
107
- # Docusaurus cache and generated files
108
- .docusaurus
109
-
110
- # Serverless directories
111
- .serverless/
112
-
113
- # FuseBox cache
114
- .fusebox/
115
-
116
- # DynamoDB Local files
117
- .dynamodb/
118
-
119
- # TernJS port file
120
- .tern-port
121
-
122
- # Stores VSCode versions used for testing VSCode extensions
123
- .vscode-test
124
- .vscode/
125
-
126
- # yarn v2
127
- .yarn/cache
128
- .yarn/unplugged
129
- .yarn/build-state.yml
130
- .yarn/install-state.gz
131
- .pnp.*
132
-
133
- # Yunzai data
134
  dump.rdb
135
- config/*.yaml
136
- config/test/*
137
- data/
138
- !config/test/default.yaml
139
- logs/
140
-
141
- # Docker file
142
- redis
143
- yunzai
144
- /.idea/
145
- /data/
146
- /temp/
147
- /pnpm-lock.yaml
148
- /entrypoint.sh
 
1
+ config/*.yaml
2
+ data
3
  logs
4
+ node_modules
5
+ temp
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  dump.rdb
7
+ pnpm-lock.yaml
 
 
 
 
 
 
 
 
 
 
 
 
 
Yunzai/.puppeteerrc.cjs CHANGED
@@ -33,7 +33,7 @@ if (!executablePath) for (const item of [
33
  }
34
 
35
  if (executablePath || arch == "arm64" || arch == "aarch64") {
36
- (typeof logger == "object" ? logger : console).info(`[Chromium] ${executablePath}`)
37
  skipDownload = true
38
  }
39
 
 
33
  }
34
 
35
  if (executablePath || arch == "arm64" || arch == "aarch64") {
36
+ (typeof logger != "undefined" ? logger : console).info(`[Chromium] ${executablePath}`)
37
  skipDownload = true
38
  }
39
 
Yunzai/README.md CHANGED
@@ -25,16 +25,18 @@ Yunzai 应用端,支持多账号,支持协议端:OneBotv11、ComWeChat、G
25
 
26
  项目仅供学习交流使用,严禁用于任何商业用途和非法行为
27
 
28
- ## 使用方法
29
 
30
- ### 建议使用 TRSS Script 一键安装管理
31
 
32
  - [🌌 TRSS](https://TRSS.me)
33
  - [🔼 Vercel](https://TRSS-Script.Vercel.app)
34
  - [🐱 GitHub](https://TimeRainStarSky.GitHub.io/TRSS_Script)
35
  - [🇬 Gitee](https://Gitee.com/TimeRainStarSky/TRSS_Script)
36
 
37
- ### 手动安装
 
 
38
 
39
  > 环境准备:Windows/Linux/MacOS/Android
40
  > [Node.js(>=v21)](https://nodejs.org), [Redis](https://redis.io), [Git](https://git-scm.com), [Chrome(可选)](https://google.cn/chrome)
@@ -49,35 +51,86 @@ git clone --depth 1 https://gitee.com/TimeRainStarSky/Yunzai
49
  cd Yunzai
50
  ```
51
 
52
- 2. 推荐安装插件(可选)
53
 
54
  ```sh
55
- git clone --depth 1 https://github.com/TimeRainStarSky/Yunzai-genshin plugins/genshin
56
- git clone --depth 1 https://github.com/yoimiya-kokomi/miao-plugin plugins/miao-plugin
57
- git clone --depth 1 https://github.com/TimeRainStarSky/TRSS-Plugin plugins/TRSS-Plugin
58
  ```
59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  ```sh
61
- git clone --depth 1 https://gitee.com/TimeRainStarSky/Yunzai-genshin plugins/genshin
62
- git clone --depth 1 https://gitee.com/yoimiya-kokomi/miao-plugin plugins/miao-plugin
63
- git clone --depth 1 https://Yunzai.TRSS.me plugins/TRSS-Plugin
64
  ```
65
 
66
- 3. 安装 [pnpm](https://pnpm.io/zh/installation) 和依赖
 
 
67
 
68
  ```sh
69
- npm i -g pnpm
70
- pnpm i
71
  ```
72
 
73
- 4. 前台运行
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
  | 操作 | 命令 |
76
  | ---- | ---- |
77
- | 启动 | node . |
78
- | 停止 | node . stop |
 
 
 
 
 
 
 
79
 
80
- 5. 启动协议端
 
 
 
 
 
 
 
 
 
 
81
 
82
  <details><summary>WebSocket</summary><blockquote>
83
 
@@ -182,23 +235,7 @@ ws://localhost:2536/GSUIDCore
182
 
183
  </details>
184
 
185
- 6. 设置主人:发送 `#设置主人`,日志获取验证码并发送
186
-
187
- 7. 使用 [pm2](https://pm2.keymetrics.io) 后台运行
188
-
189
- | 操作 | 命令 |
190
- | ---- | ---- |
191
- | 启动 | pnpm start |
192
- | 停止 | pnpm stop |
193
- | 日志 | pnpm log |
194
-
195
- 8. 开机自启
196
-
197
- ```sh
198
- pnpm start
199
- pnpm pm2 save
200
- pnpm pm2 startup
201
- ```
202
 
203
  ## 班级群(¿
204
 
 
25
 
26
  项目仅供学习交流使用,严禁用于任何商业用途和非法行为
27
 
28
+ ## 安装教程
29
 
30
+ <details><summary>脚本安装</summary>
31
 
32
  - [🌌 TRSS](https://TRSS.me)
33
  - [🔼 Vercel](https://TRSS-Script.Vercel.app)
34
  - [🐱 GitHub](https://TimeRainStarSky.GitHub.io/TRSS_Script)
35
  - [🇬 Gitee](https://Gitee.com/TimeRainStarSky/TRSS_Script)
36
 
37
+ </details>
38
+
39
+ <details><summary>手动安装</summary>
40
 
41
  > 环境准备:Windows/Linux/MacOS/Android
42
  > [Node.js(>=v21)](https://nodejs.org), [Redis](https://redis.io), [Git](https://git-scm.com), [Chrome(可选)](https://google.cn/chrome)
 
51
  cd Yunzai
52
  ```
53
 
54
+ 2. 安装 [pnpm](https://pnpm.io/zh/installation) 和依赖
55
 
56
  ```sh
57
+ npm i -g pnpm
58
+ pnpm i
 
59
  ```
60
 
61
+ 3. 前台运行
62
+
63
+ | 操作 | 命令 |
64
+ | ---- | ---- |
65
+ | 启动 | node . |
66
+ | 停止 | node . stop |
67
+
68
+ 4. 使用 [pm2](https://pm2.keymetrics.io) 后台运行
69
+
70
+ | 操作 | 命令 |
71
+ | ---- | ---- |
72
+ | 启动 | pnpm start |
73
+ | 停止 | pnpm stop |
74
+ | 日志 | pnpm log |
75
+
76
+ 5. 开机自启
77
+
78
  ```sh
79
+ pnpm start
80
+ pnpm pm2 save
81
+ pnpm pm2 startup
82
  ```
83
 
84
+ </details>
85
+
86
+ <details><summary>Docker 安装</summary>
87
 
88
  ```sh
89
+ bash <(curl -L https://github.com/TimeRainStarSky/Yunzai/raw/main/lib/tools/docker.sh)
90
+ bash <(curl -L https://gitee.com/TimeRainStarSky/Yunzai/raw/main/lib/tools/docker.sh)
91
  ```
92
 
93
+ | 参数 | 描述 | 默认值 |
94
+ | ---- | ---- | ------ |
95
+ | DIR | 安装文件夹 | $HOME/Yunzai |
96
+ | CMD | 启动命令 | tsyz |
97
+ | CMDPATH | 命令文件夹 | /usr/local/bin |
98
+ | DKNAME | 容器名 | Yunzai |
99
+ | DKURL | Docker 源 | docker.nju.edu.cn |
100
+ | GITURL | GIT 源 | https://gitee.com/TimeRainStarSky/Yunzai |
101
+ | APTURL | APT 源 | mirrors.ustc.edu.cn |
102
+ | APTDEP | APT 依赖 | chromium fonts-lxgw-wenkai fonts-noto-color-emoji |
103
+ | NPMURL | NPM 源 | https://registry.npmmirror.com |
104
+
105
+ - 参数修改方法
106
+
107
+ ```sh
108
+ 参数1="值1" 参数2="值2" bash <(x)
109
+ ```
110
 
111
  | 操作 | 命令 |
112
  | ---- | ---- |
113
+ | 连接 | tsyz |
114
+ | 断开 | Ctrl+P+Q |
115
+ | 启动 | tsyz start |
116
+ | 重启 | tsyz restart |
117
+ | 停止 | tsyz stop |
118
+ | 日志 | tsyz log 行数 |
119
+ | 命令 | tsyz 命令 |
120
+
121
+ </details>
122
 
123
+ ## 使用教程
124
+
125
+ 1. 推荐安装插件(可选)
126
+
127
+ ```
128
+ #安装genshin
129
+ #安装miao-plugin
130
+ #安装TRSS-Plugin
131
+ ```
132
+
133
+ 2. 启动协议端
134
 
135
  <details><summary>WebSocket</summary><blockquote>
136
 
 
235
 
236
  </details>
237
 
238
+ 3. 设置主人:发送 `#设置主人`,日志获取验证码并发送
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
 
240
  ## 班级群(¿
241
 
Yunzai/app.js CHANGED
@@ -9,7 +9,7 @@ switch (process.env.app_type || process.argv[2]) {
9
  const fetch = (await import("node-fetch")).default
10
  try {
11
  await fetch(`http://localhost:${cfg.bot.port}/exit`)
12
- } catch (err) {}
13
  process.exit()
14
  } default: {
15
  const { spawnSync } = await import("node:child_process")
 
9
  const fetch = (await import("node-fetch")).default
10
  try {
11
  await fetch(`http://localhost:${cfg.bot.port}/exit`)
12
+ } catch {}
13
  process.exit()
14
  } default: {
15
  const { spawnSync } = await import("node:child_process")
Yunzai/config/default_config/bot.yaml CHANGED
@@ -14,6 +14,15 @@ port: 2536
14
  update_time: 1440
15
  # 自动重启时间
16
  restart_time: 0
 
 
 
 
 
 
 
 
 
17
  # 上线推送通知的冷却时间
18
  online_msg_exp: 1440
19
  # 文件保存时间
 
14
  update_time: 1440
15
  # 自动重启时间
16
  restart_time: 0
17
+ # 定时更新cron表达式
18
+ update_cron:
19
+ # 定时重启cron表达式
20
+ restart_cron:
21
+ # 定时关机cron表达式
22
+ stop_cron:
23
+ # 定时开机cron表达式
24
+ start_cron:
25
+
26
  # 上线推送通知的冷却时间
27
  online_msg_exp: 1440
28
  # 文件保存时间
Yunzai/config/default_config/redis.yaml CHANGED
@@ -1,10 +1,12 @@
1
- # redis地址
 
 
2
  host: 127.0.0.1
3
- # redis端口
4
  port: 6379
5
- # redis用户名,可以为空
6
  username:
7
- # redis密码,没有密码可以为空
8
  password:
9
- # redis数据库
10
  db: 0
 
1
+ # Redis 命令路径
2
+ path: redis-server
3
+ # Redis 地址
4
  host: 127.0.0.1
5
+ # Redis 端口
6
  port: 6379
7
+ # Redis 用户名
8
  username:
9
+ # Redis 密码
10
  password:
11
+ # Redis 数据库
12
  db: 0
Yunzai/config/pm2.yaml CHANGED
@@ -2,6 +2,6 @@ apps:
2
  - name: TRSS-Yunzai
3
  script: ./app.js
4
  max_memory_restart: 512M
5
- restart_delay: 60000
6
  env:
7
  app_type: pm2
 
2
  - name: TRSS-Yunzai
3
  script: ./app.js
4
  max_memory_restart: 512M
5
+ exp_backoff_restart_delay: 1000
6
  env:
7
  app_type: pm2
Yunzai/lib/bot.js CHANGED
@@ -1,58 +1,107 @@
1
- import "./config/init.js"
2
  import cfg from "./config/config.js"
3
- import redisInit from "./config/redis.js"
4
  import PluginsLoader from "./plugins/loader.js"
5
  import ListenerLoader from "./listener/loader.js"
6
  import { EventEmitter } from "events"
7
  import express from "express"
8
  import http from "node:http"
9
  import { WebSocketServer } from "ws"
10
- import _ from "lodash"
11
  import fs from "node:fs/promises"
12
  import path from "node:path"
13
  import util from "node:util"
14
  import fetch from "node-fetch"
15
- import { randomUUID } from "node:crypto"
16
- import { exec } from "node:child_process"
17
  import { fileTypeFromBuffer } from "file-type"
18
  import md5 from "md5"
19
- import { Level } from "level"
20
 
21
  export default class Yunzai extends EventEmitter {
22
- constructor() {
23
- super()
24
- this.stat = { start_time: Date.now()/1000 }
25
- this.uin = []
26
- this.adapter = []
27
-
28
- this.express = express()
29
- for (const i of ["urlencoded", "json", "raw", "text"])
30
- this.express.use(express[i]({ extended: false }))
31
- this.express.use(req => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  req.rid = `${req.ip}:${req.socket.remotePort}`
33
  req.sid = `${req.protocol}://${req.hostname}:${req.socket.localPort}${req.originalUrl}`
34
- this.makeLog("mark", ["HTTP", req.method, "请求", req.headers, req.query, req.body], `${req.sid} <= ${req.rid}`)
35
  req.next()
36
  })
37
- this.express.use("/exit", req => {
38
- if (["::1", "::ffff:127.0.0.1"].includes(req.ip) || req.hostname == "localhost")
39
  process.exit(1)
40
  })
41
- this.server = http.createServer(this.express)
42
- this.server.on("error", err => {
43
- if (typeof this[`server${err.code}`] == "function")
 
 
44
  return this[`server${err.code}`](err)
45
  this.makeLog("error", err, "Server")
46
  })
 
47
 
48
- this.server.on("upgrade", (...args) => this.wsConnect(...args))
49
- this.wss = new WebSocketServer({ noServer: true })
50
- this.wsf = Object.create(null)
 
 
 
51
 
52
- this.fs = Object.create(null)
53
- this.express.use("/File", (...args) => this.fileSend(...args))
54
  for (const name of [404, "timeout"])
55
  this.fileToUrl(`resources/http/File/${name}.jpg`, { name, time: false, times: false })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  }
57
 
58
  wsConnect(req, socket, head) {
@@ -77,7 +126,7 @@ export default class Yunzai extends EventEmitter {
77
  this.makeLog("error", ["监听端口", cfg.bot.port, "错误", err], "Server")
78
  try {
79
  await fetch(`http://localhost:${cfg.bot.port}/exit`)
80
- } catch (err) {}
81
  this.server_listen_time = (this.server_listen_time || 0) + 1
82
  await this.sleep(this.server_listen_time * 1000)
83
  this.server.listen(cfg.bot.port)
@@ -90,7 +139,7 @@ export default class Yunzai extends EventEmitter {
90
  }
91
 
92
  async run() {
93
- await redisInit()
94
  await this.serverLoad()
95
  await import("./plugins/stdin.js")
96
  await PluginsLoader.load()
@@ -101,63 +150,60 @@ export default class Yunzai extends EventEmitter {
101
  this.emit("online", this)
102
  }
103
 
104
- sleep(time) {
 
105
  return new Promise(resolve => setTimeout(resolve, time))
106
  }
107
 
108
- async fsStat(path) { try {
109
- const stat = await fs.stat(path)
110
- return stat
111
  } catch (err) {
112
  this.makeLog("trace", ["获取", path, "状态错误", err])
113
  return false
114
  }}
115
 
116
- async mkdir(dir) { try {
117
- if (await this.fsStat(dir)) return true
118
- if (!await this.mkdir(path.dirname(dir))) return false
119
- await fs.mkdir(dir)
120
  return true
121
  } catch (err) {
122
  this.makeLog("error", ["创建", dir, "错误", err])
123
  return false
124
  }}
125
 
126
- async rmdir(dir) { try {
127
- if (!await this.fsStat(dir)) return true
128
- for (const i of await fs.readdir(dir))
129
- await this.rm(`${dir}/${i}`)
130
- await fs.rmdir(dir)
131
- return true
132
- } catch (err) {
133
- this.makeLog("error", ["删除", dir, "错误", err])
134
- return false
135
- }}
136
-
137
- async rm(file) { try {
138
- const stat = await this.fsStat(file)
139
- if (!stat) return true
140
- if (stat.isDirectory())
141
- return this.rmdir(file)
142
- await fs.unlink(file)
143
  return true
144
  } catch (err) {
145
  this.makeLog("error", ["删除", file, "错误", err])
146
  return false
147
  }}
148
 
149
- async download(url, file) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
  let buffer
151
  if (!file || (await this.fsStat(file))?.isDirectory?.()) {
152
- const type = await this.fileType(url)
153
  file = file ? path.join(file, type.name) : type.name
154
  buffer = type.buffer
155
  } else {
156
  await this.mkdir(path.dirname(file))
157
- buffer = await this.Buffer(url)
158
  }
159
  await fs.writeFile(file, buffer)
160
- return file
161
  }
162
 
163
  makeMap(parent_map, parent_key, map) {
@@ -170,7 +216,7 @@ export default class Yunzai extends EventEmitter {
170
  const set = map.set.bind(map)
171
  Object.defineProperty(map, "set", {
172
  value: async (key, value) => {
173
- if (JSON.stringify(map.get(key)) != JSON.stringify(value)) {
174
  set(key, value)
175
  await save()
176
  }
@@ -193,7 +239,7 @@ export default class Yunzai extends EventEmitter {
193
  if (value instanceof Map) {
194
  set(key, this.makeMap(map, key, value))
195
  await map.db.put(key, { map_array: Array.from(value) })
196
- } else if (JSON.stringify(map.get(key)) != JSON.stringify(value)) {
197
  set(key, value)
198
  await map.db.put(key, value)
199
  }
@@ -219,7 +265,7 @@ export default class Yunzai extends EventEmitter {
219
  try {
220
  await map.set(i, (await this.fsStat(path)).isDirectory() ?
221
  await this.importMap(path, new Map) :
222
- JSON.parse(await fs.readFile(path, "utf-8")))
223
  } catch (err) {
224
  this.makeLog("error", ["读取", path, "错误", err])
225
  }
@@ -230,12 +276,13 @@ export default class Yunzai extends EventEmitter {
230
  }
231
 
232
  async getMap(dir) {
233
- const map = new Map()
234
- const db = new Level(`${dir}-leveldb`, { valueEncoding: "json" })
 
235
  try {
236
  await db.open()
237
  for await (let [key, value] of db.iterator()) {
238
- if (typeof value == "object" && value.map_array)
239
  value = this.makeMap(map, key, new Map(value.map_array))
240
  map.set(key, value)
241
  }
@@ -259,29 +306,72 @@ export default class Yunzai extends EventEmitter {
259
  return map
260
  }
261
 
262
- String(data) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
263
  switch (typeof data) {
264
  case "string":
265
  return data
 
 
266
  case "object":
267
  if (data instanceof Error)
268
  return data.stack
269
  if (Buffer.isBuffer(data))
270
- return String(data)
271
  }
272
- return JSON.stringify(data)
273
- }
274
 
275
- Loging(data) {
276
- if (typeof data == "string") return data
277
- if (!cfg.bot.log_object && typeof data == "object")
278
- if (typeof data.toString == "function")
279
- return String(data)
280
- else
281
- return "[object null]"
282
 
283
- return util.inspect(data, {
284
- depth: null,
 
 
 
 
285
  colors: true,
286
  showHidden: true,
287
  showProxy: true,
@@ -289,23 +379,25 @@ export default class Yunzai extends EventEmitter {
289
  breakLength: 100,
290
  maxArrayLength: 100,
291
  maxStringLength: 1000,
292
- ...cfg.bot.log_object,
293
  })
 
 
 
 
 
294
  }
295
 
296
  async Buffer(data, opts = {}) {
297
  if (Buffer.isBuffer(data)) return data
298
  data = this.String(data)
299
 
300
- if (data.match(/^base64:\/\//)) {
301
- return Buffer.from(data.replace(/^base64:\/\//, ""), "base64")
302
- } else if (data.match(/^https?:\/\//)) {
303
- if (opts.http) return data
304
- return Buffer.from(await (await fetch(data)).arrayBuffer())
305
- } else if (await this.fsStat(data.replace(/^file:\/\//, ""))) {
306
- if (opts.file) return data
307
- return Buffer.from(await fs.readFile(data.replace(/^file:\/\//, "")))
308
- }
309
  return data
310
  }
311
 
@@ -322,14 +414,12 @@ export default class Yunzai extends EventEmitter {
322
  if (Buffer.isBuffer(file.buffer)) {
323
  file.type = await fileTypeFromBuffer(file.buffer)
324
  file.md5 = md5(file.buffer)
325
- if (!file.name)
326
- file.name = `${Date.now()}.${file.md5.slice(0,8)}.${file.type.ext}`
327
  }
328
  } catch (err) {
329
  this.makeLog("error", ["文件类型检测错误", file, err])
330
  }
331
- if (!file.name)
332
- file.name = `${Date.now()}-${path.basename(file.url)}`
333
  return file
334
  }
335
 
@@ -342,9 +432,9 @@ export default class Yunzai extends EventEmitter {
342
 
343
  file = await this.fileType({ file, name }, { http: true })
344
  if (!Buffer.isBuffer(file.buffer)) return file.buffer
345
- file.name = file.name ? encodeURIComponent(file.name) : randomUUID()
346
 
347
- if (typeof times == "number") file.times = times
348
  this.fs[file.name] = file
349
  if (time) setTimeout(() => this.fs[file.name] = this.fs.timeout, time)
350
  return `${cfg.bot.url}/File/${file.name}`
@@ -352,10 +442,9 @@ export default class Yunzai extends EventEmitter {
352
 
353
  fileSend(req) {
354
  const url = req.url.replace(/^\//, "")
355
- let file = this.fs[url]
356
- if (!file) file = this.fs[404]
357
 
358
- if (typeof file.times == "number") {
359
  if (file.times > 0) file.times--
360
  else file = this.fs.timeout
361
  }
@@ -367,30 +456,44 @@ export default class Yunzai extends EventEmitter {
367
  req.res.send(file.buffer)
368
  }
369
 
370
- async exec(cmd) {
371
- return new Promise(resolve => {
372
- this.makeLog("mark", `[执行命令] ${logger.blue(cmd)}`)
373
- exec(cmd, (error, stdout, stderr) => {
374
- resolve({ error, stdout, stderr })
375
- this.makeLog("mark", `[执行命令完成] ${logger.blue(cmd)}${stdout?`\n${String(stdout).trim()}`:""}${stderr?logger.red(`\n${String(stderr).trim()}`):""}`)
376
- if (error) this.makeLog("error", `[执行命令错误] ${logger.blue(cmd)}\n${logger.red(this.Loging(error).trim())}`)
377
- })
378
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
379
  }
380
 
381
  makeLog(level, msg, id) {
382
  const log = []
383
- if (id) log.push(logger.blue(`[${id}]`))
 
384
  for (const i of Array.isArray(msg) ? msg : [msg])
385
- log.push(_.truncate(this.Loging(i), { length: cfg.bot.log_length }))
386
- logger[level](...log)
387
  }
388
 
389
- makeEvent(data) {
390
- if (!this[data.self_id]) return
391
  if (!data.bot)
392
  Object.defineProperty(data, "bot", {
393
- value: this[data.self_id],
394
  })
395
  if (!data.friend && data.user_id)
396
  Object.defineProperty(data, "friend", {
@@ -411,24 +514,20 @@ export default class Yunzai extends EventEmitter {
411
  data.adapter_name = data.bot.adapter.name
412
 
413
  for (const i of [data.friend, data.group, data.member]) {
414
- if (typeof i != "object") continue
415
- if (!i.sendFile)
416
- i.sendFile = (file, name) => i.sendMsg(segment.file(file, name))
417
- if (!i.makeForwardMsg)
418
- i.makeForwardMsg = this.makeForwardMsg
419
- if (!i.sendForwardMsg)
420
- i.sendForwardMsg = msg => this.sendForwardMsg(msg => i.sendMsg(msg), msg)
421
- if (!i.getInfo)
422
- i.getInfo = () => i
423
  }
424
  }
425
 
426
  em(name = "", data = {}) {
427
- this.makeEvent(data)
428
  while (true) {
429
  this.emit(name, data)
430
  const i = name.lastIndexOf(".")
431
- if (i == -1) break
432
  name = name.slice(0, i)
433
  }
434
  }
@@ -436,7 +535,7 @@ export default class Yunzai extends EventEmitter {
436
  getFriendArray() {
437
  const array = []
438
  for (const bot_id of this.uin)
439
- for (const [id, i] of this[bot_id].fl || [])
440
  array.push({ ...i, bot_id })
441
  return array
442
  }
@@ -444,15 +543,14 @@ export default class Yunzai extends EventEmitter {
444
  getFriendList() {
445
  const array = []
446
  for (const bot_id of this.uin)
447
- for (const [id, i] of this[bot_id].fl || [])
448
- array.push(id)
449
  return array
450
  }
451
 
452
  getFriendMap() {
453
  const map = new Map
454
  for (const bot_id of this.uin)
455
- for (const [id, i] of this[bot_id].fl || [])
456
  map.set(id, { ...i, bot_id })
457
  return map
458
  }
@@ -461,7 +559,7 @@ export default class Yunzai extends EventEmitter {
461
  getGroupArray() {
462
  const array = []
463
  for (const bot_id of this.uin)
464
- for (const [id, i] of this[bot_id].gl || [])
465
  array.push({ ...i, bot_id })
466
  return array
467
  }
@@ -469,15 +567,14 @@ export default class Yunzai extends EventEmitter {
469
  getGroupList() {
470
  const array = []
471
  for (const bot_id of this.uin)
472
- for (const [id, i] of this[bot_id].gl || [])
473
- array.push(id)
474
  return array
475
  }
476
 
477
  getGroupMap() {
478
  const map = new Map
479
  for (const bot_id of this.uin)
480
- for (const [id, i] of this[bot_id].gl || [])
481
  map.set(id, { ...i, bot_id })
482
  return map
483
  }
@@ -485,100 +582,130 @@ export default class Yunzai extends EventEmitter {
485
  get gml() {
486
  const map = new Map
487
  for (const bot_id of this.uin)
488
- for (const [id, i] of this[bot_id].gml || [])
489
- map.set(id, i)
490
  return map
491
  }
492
 
493
- pickFriend(user_id) {
494
- user_id = Number(user_id) || String(user_id)
495
- const user = this.fl.get(user_id)
496
- if (user) return this[user.bot_id].pickFriend(user_id)
497
- this.makeLog("error", ["获取用户对象错误:找不到用户", user_id])
 
 
 
 
 
 
 
 
 
498
  }
499
  get pickUser() { return this.pickFriend }
500
 
501
- pickGroup(group_id) {
502
- group_id = Number(group_id) || String(group_id)
503
  const group = this.gl.get(group_id)
504
- if (group) return this[group.bot_id].pickGroup(group_id)
505
- this.makeLog("error", ["获取群对象错误:找不到群", group_id])
 
 
506
  }
507
 
508
  pickMember(group_id, user_id) {
509
- const group = this.pickGroup(group_id)
510
- if (group) return group.pickMember(user_id)
511
  }
512
 
513
- sendFriendMsg(bot_id, user_id, msg) {
514
- try {
515
- if (!bot_id)
516
- return this.pickFriend(user_id).sendMsg(msg)
517
 
518
- if (this[bot_id])
519
- return this[bot_id].pickFriend(user_id).sendMsg(msg)
520
 
521
- return new Promise(resolve =>
522
- this.once(`connect.${bot_id}`, data =>
523
- resolve(data.bot.pickFriend(user_id).sendMsg(msg))))
524
- } catch (err) {
525
- this.makeLog("error", [`发送好友消息错误:[${user_id}]`, err], bot_id)
526
- }
527
- return false
528
- }
529
 
530
- sendGroupMsg(bot_id, group_id, msg) {
531
- try {
532
- if (!bot_id)
533
- return this.pickGroup(group_id).sendMsg(msg)
 
 
 
 
 
 
 
 
 
 
534
 
535
- if (this[bot_id])
536
- return this[bot_id].pickGroup(group_id).sendMsg(msg)
 
537
 
538
- return new Promise(resolve =>
539
- this.once(`connect.${bot_id}`, data =>
540
- resolve(data.bot.pickGroup(group_id).sendMsg(msg))))
541
- } catch (err) {
542
- this.makeLog("error", [`发送群消息错误:[${group_id}]`, err], bot_id)
543
- }
544
- return false
545
- }
 
 
 
 
 
 
 
 
 
 
 
 
546
 
547
- async getTextMsg(fnc = () => true) {
548
- if (typeof fnc != "function") {
549
  const { self_id, user_id } = fnc
550
  fnc = data => data.self_id == self_id && data.user_id == user_id
551
  }
552
 
553
- while (true) { try {
554
- const msg = await new Promise(resolve => {
555
- this.once("message", data => {
556
- if (data.message && fnc(data)) {
557
- let msg = ""
558
- for (const i of data.message)
559
- if (i.type == "text" && i.text)
560
- msg += i.text.trim()
561
- resolve(msg)
562
- } else {
563
- resolve(false)
564
- }
565
- })
566
- })
567
- if (msg) return msg
568
- } catch (err) {
569
- this.makeLog("error", err)
570
- }}
571
  }
572
 
573
  getMasterMsg() {
574
- return this.getTextMsg(data =>
575
- cfg.master[data.self_id]?.includes(String(data.user_id)))
576
  }
577
 
578
- sendMasterMsg(msg) {
579
- for (const bot_id in cfg.master)
580
- for (const user_id of cfg.master[bot_id])
581
- this.sendFriendMsg(bot_id, user_id, msg)
 
 
 
 
 
 
582
  }
583
 
584
  makeForwardMsg(msg) { return { type: "node", data: msg } }
@@ -597,16 +724,16 @@ export default class Yunzai extends EventEmitter {
597
  return messages
598
  }
599
 
600
- getTimeDiff(time1 = this.stat.start_time, time2 = Date.now()/1000) {
601
- const time = time2 - time1
602
  let ret = ""
603
- const day = Math.floor(time / 3600 / 24)
604
  if (day) ret += `${day}天`
605
- const hour = Math.floor((time / 3600) % 24)
606
  if (hour) ret += `${hour}时`
607
- const min = Math.floor((time / 60) % 60)
608
  if (min) ret += `${min}分`
609
- const sec = Math.floor(time % 60)
610
  if (sec) ret += `${sec}秒`
611
  return ret || "0秒"
612
  }
 
1
+ import init from "./config/init.js"
2
  import cfg from "./config/config.js"
 
3
  import PluginsLoader from "./plugins/loader.js"
4
  import ListenerLoader from "./listener/loader.js"
5
  import { EventEmitter } from "events"
6
  import express from "express"
7
  import http from "node:http"
8
  import { WebSocketServer } from "ws"
 
9
  import fs from "node:fs/promises"
10
  import path from "node:path"
11
  import util from "node:util"
12
  import fetch from "node-fetch"
13
+ import { exec, execFile } from "node:child_process"
 
14
  import { fileTypeFromBuffer } from "file-type"
15
  import md5 from "md5"
16
+ import { ulid } from "ulid"
17
 
18
  export default class Yunzai extends EventEmitter {
19
+ stat = { start_time: Date.now()/1000 }
20
+ bot = this
21
+ bots = {}
22
+ uin = Object.assign([], {
23
+ toJSON() {
24
+ if (!this.now) {
25
+ switch (this.length) {
26
+ case 0:
27
+ return ""
28
+ case 1:
29
+ case 2:
30
+ return this[this.length-1]
31
+ }
32
+ const array = this.slice(1)
33
+ this.now = array[Math.floor(Math.random()*array.length)]
34
+ setTimeout(() => delete this.now, 60000)
35
+ }
36
+ return this.now
37
+ },
38
+ toString(raw, ...args) {
39
+ return raw === true ?
40
+ this.__proto__.toString.apply(this, args) :
41
+ this.toJSON().toString(raw, ...args)
42
+ },
43
+ includes(value) {
44
+ return this.some(i => i == value)
45
+ },
46
+ })
47
+ adapter = []
48
+
49
+ express = Object.assign(express(), { quiet: [] })
50
+ .use(express.urlencoded({ extended: false }))
51
+ .use(express.json())
52
+ .use(express.raw())
53
+ .use(express.text())
54
+ .use("/status", req => req.res.send(process.memoryUsage()))
55
+ .use(req => {
56
+ let quiet = false
57
+ for (const i of req.app.quiet)
58
+ if (req.originalUrl.startsWith(i)) {
59
+ quiet = true
60
+ break
61
+ }
62
  req.rid = `${req.ip}:${req.socket.remotePort}`
63
  req.sid = `${req.protocol}://${req.hostname}:${req.socket.localPort}${req.originalUrl}`
64
+ this.makeLog(quiet?"debug":"mark", ["HTTP", req.method, "请求", req.headers, req.query, req.body], `${req.sid} <= ${req.rid}`)
65
  req.next()
66
  })
67
+ .use("/exit", req => {
68
+ if (["::1", "::ffff:127.0.0.1"].includes(req.ip) || req.hostname === "localhost")
69
  process.exit(1)
70
  })
71
+ .use("/File", (...args) => this.fileSend(...args))
72
+
73
+ server = http.createServer(this.express)
74
+ .on("error", err => {
75
+ if (typeof this[`server${err.code}`] === "function")
76
  return this[`server${err.code}`](err)
77
  this.makeLog("error", err, "Server")
78
  })
79
+ .on("upgrade", (...args) => this.wsConnect(...args))
80
 
81
+ wss = new WebSocketServer({ noServer: true })
82
+ wsf = Object.create(null)
83
+ fs = Object.create(null)
84
+
85
+ constructor() {
86
+ super()
87
 
 
 
88
  for (const name of [404, "timeout"])
89
  this.fileToUrl(`resources/http/File/${name}.jpg`, { name, time: false, times: false })
90
+
91
+ return new Proxy(this.bots, {
92
+ get: (target, prop) => {
93
+ const value = this[prop] ?? target[prop]
94
+ if (value !== undefined) return value
95
+ for (const i of [this.uin.toString(), ...this.uin])
96
+ if (target[i]?.[prop] !== undefined) {
97
+ this.makeLog("trace", `因不存在 Bot.${prop} 而重定向到 Bot.${i}.${prop}`)
98
+ if (typeof target[i][prop]?.bind === "function")
99
+ return target[i][prop].bind(target[i])
100
+ return target[i][prop]
101
+ }
102
+ this.makeLog("trace", `不存在 Bot.${prop}`)
103
+ }
104
+ })
105
  }
106
 
107
  wsConnect(req, socket, head) {
 
126
  this.makeLog("error", ["监听端口", cfg.bot.port, "错误", err], "Server")
127
  try {
128
  await fetch(`http://localhost:${cfg.bot.port}/exit`)
129
+ } catch {}
130
  this.server_listen_time = (this.server_listen_time || 0) + 1
131
  await this.sleep(this.server_listen_time * 1000)
132
  this.server.listen(cfg.bot.port)
 
139
  }
140
 
141
  async run() {
142
+ await init()
143
  await this.serverLoad()
144
  await import("./plugins/stdin.js")
145
  await PluginsLoader.load()
 
150
  this.emit("online", this)
151
  }
152
 
153
+ sleep(time, promise) {
154
+ if (promise) return Promise.race([promise, this.sleep(time)])
155
  return new Promise(resolve => setTimeout(resolve, time))
156
  }
157
 
158
+ async fsStat(path, opts) { try {
159
+ return await fs.stat(path, opts)
 
160
  } catch (err) {
161
  this.makeLog("trace", ["获取", path, "状态错误", err])
162
  return false
163
  }}
164
 
165
+ async mkdir(dir, opts) { try {
166
+ await fs.mkdir(dir, { recursive: true, ...opts })
 
 
167
  return true
168
  } catch (err) {
169
  this.makeLog("error", ["创建", dir, "错误", err])
170
  return false
171
  }}
172
 
173
+ async rm(file, opts) { try {
174
+ await fs.rm(file, { force: true, recursive: true, ...opts })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  return true
176
  } catch (err) {
177
  this.makeLog("error", ["删除", file, "错误", err])
178
  return false
179
  }}
180
 
181
+ async glob(path, opts = {}) {
182
+ if (!opts.force && await this.fsStat(path))
183
+ return [path]
184
+ if (!fs.glob) return []
185
+ const array = []
186
+ try {
187
+ for await (const i of fs.glob(path, opts))
188
+ array.push(i)
189
+ } catch (err) {
190
+ this.makeLog("error", ["匹配", path, "错误", err])
191
+ }
192
+ return array
193
+ }
194
+
195
+ async download(url, file, opts) {
196
  let buffer
197
  if (!file || (await this.fsStat(file))?.isDirectory?.()) {
198
+ const type = await this.fileType(url, opts)
199
  file = file ? path.join(file, type.name) : type.name
200
  buffer = type.buffer
201
  } else {
202
  await this.mkdir(path.dirname(file))
203
+ buffer = await this.Buffer(url, opts)
204
  }
205
  await fs.writeFile(file, buffer)
206
+ return { url, file, buffer }
207
  }
208
 
209
  makeMap(parent_map, parent_key, map) {
 
216
  const set = map.set.bind(map)
217
  Object.defineProperty(map, "set", {
218
  value: async (key, value) => {
219
+ if (JSON.stringify(map.get(key)) !== JSON.stringify(value)) {
220
  set(key, value)
221
  await save()
222
  }
 
239
  if (value instanceof Map) {
240
  set(key, this.makeMap(map, key, value))
241
  await map.db.put(key, { map_array: Array.from(value) })
242
+ } else if (JSON.stringify(map.get(key)) !== JSON.stringify(value)) {
243
  set(key, value)
244
  await map.db.put(key, value)
245
  }
 
265
  try {
266
  await map.set(i, (await this.fsStat(path)).isDirectory() ?
267
  await this.importMap(path, new Map) :
268
+ JSON.parse(await fs.readFile(path, "utf8")))
269
  } catch (err) {
270
  this.makeLog("error", ["读取", path, "错误", err])
271
  }
 
276
  }
277
 
278
  async getMap(dir) {
279
+ const map = new Map
280
+ const db = new (await import("level"))
281
+ .Level(`${dir}-leveldb`, { valueEncoding: "json" })
282
  try {
283
  await db.open()
284
  for await (let [key, value] of db.iterator()) {
285
+ if (typeof value === "object" && value.map_array)
286
  value = this.makeMap(map, key, new Map(value.map_array))
287
  map.set(key, value)
288
  }
 
306
  return map
307
  }
308
 
309
+ StringOrNull(data) {
310
+ if (typeof data === "object" && typeof data.toString !== "function")
311
+ return "[object null]"
312
+ return String(data)
313
+ }
314
+
315
+ StringOrBuffer(data, base64) {
316
+ const string = String(data)
317
+ return string.includes("\ufffd") ? (base64 ? `base64://${data.toString("base64")}` : data) : string
318
+ }
319
+
320
+ getCircularReplacer() {
321
+ const _this_ = this, ancestors = []
322
+ return function (key, value) {
323
+ switch (typeof value) {
324
+ case "function":
325
+ return String(value)
326
+ case "object":
327
+ if (value === null)
328
+ return null
329
+ if (value instanceof Map || value instanceof Set)
330
+ return Array.from(value)
331
+ if (value instanceof Error)
332
+ return value.stack
333
+ if (value.type === "Buffer" && Array.isArray(value.data)) try {
334
+ return _this_.StringOrBuffer(Buffer.from(value), true)
335
+ } catch {}
336
+ break
337
+ default:
338
+ return value
339
+ }
340
+ while (ancestors.length > 0 && ancestors.at(-1) !== this)
341
+ ancestors.pop()
342
+ if (ancestors.includes(value))
343
+ return `[Circular ${_this_.StringOrNull(value)}]`
344
+ ancestors.push(value)
345
+ return value
346
+ }
347
+ }
348
+
349
+ String(data, opts) {
350
  switch (typeof data) {
351
  case "string":
352
  return data
353
+ case "function":
354
+ return String(data)
355
  case "object":
356
  if (data instanceof Error)
357
  return data.stack
358
  if (Buffer.isBuffer(data))
359
+ return this.StringOrBuffer(data, true)
360
  }
 
 
361
 
362
+ try {
363
+ return JSON.stringify(data, this.getCircularReplacer(), opts) || this.StringOrNull(data)
364
+ } catch (err) {
365
+ return this.StringOrNull(data)
366
+ }
367
+ }
 
368
 
369
+ Loging(data, opts = cfg.bot.log_object) {
370
+ if (typeof data === "string") {}
371
+ else if (!opts)
372
+ data = this.StringOrNull(data)
373
+ else data = util.inspect(data, {
374
+ depth: 10,
375
  colors: true,
376
  showHidden: true,
377
  showProxy: true,
 
379
  breakLength: 100,
380
  maxArrayLength: 100,
381
  maxStringLength: 1000,
382
+ ...opts,
383
  })
384
+
385
+ const length = opts.length || cfg.bot.log_length
386
+ if (data.length > length)
387
+ data = `${data.slice(0, length)}${logger.gray(`... ${data.length-length} more characters`)}`
388
+ return data
389
  }
390
 
391
  async Buffer(data, opts = {}) {
392
  if (Buffer.isBuffer(data)) return data
393
  data = this.String(data)
394
 
395
+ if (data.startsWith("base64://"))
396
+ return Buffer.from(data.replace("base64://", ""), "base64")
397
+ else if (data.match(/^https?:\/\//))
398
+ return opts.http ? data : Buffer.from(await (await fetch(data, opts)).arrayBuffer())
399
+ else if (await this.fsStat(data.replace(/^file:\/\//, "")))
400
+ return opts.file ? data : Buffer.from(await fs.readFile(data.replace(/^file:\/\//, "")))
 
 
 
401
  return data
402
  }
403
 
 
414
  if (Buffer.isBuffer(file.buffer)) {
415
  file.type = await fileTypeFromBuffer(file.buffer)
416
  file.md5 = md5(file.buffer)
417
+ file.name ??= `${Date.now().toString(36)}.${file.md5.slice(0,8)}.${file.type.ext}`
 
418
  }
419
  } catch (err) {
420
  this.makeLog("error", ["文件类型检测错误", file, err])
421
  }
422
+ file.name ??= `${Date.now().toString(36)}-${path.basename(file.url)}`
 
423
  return file
424
  }
425
 
 
432
 
433
  file = await this.fileType({ file, name }, { http: true })
434
  if (!Buffer.isBuffer(file.buffer)) return file.buffer
435
+ file.name = file.name ? encodeURIComponent(file.name) : ulid()
436
 
437
+ if (typeof times === "number") file.times = times
438
  this.fs[file.name] = file
439
  if (time) setTimeout(() => this.fs[file.name] = this.fs.timeout, time)
440
  return `${cfg.bot.url}/File/${file.name}`
 
442
 
443
  fileSend(req) {
444
  const url = req.url.replace(/^\//, "")
445
+ let file = this.fs[url] || this.fs[404]
 
446
 
447
+ if (typeof file.times === "number") {
448
  if (file.times > 0) file.times--
449
  else file = this.fs.timeout
450
  }
 
456
  req.res.send(file.buffer)
457
  }
458
 
459
+ async exec(cmd, opts = {}) { return new Promise(resolve => {
460
+ const name = logger.cyan(this.String(cmd))
461
+ this.makeLog(opts.quiet?"debug":"mark", name, "执行命令")
462
+ opts.encoding ??= "buffer"
463
+ const callback = (error, stdout, stderr) => {
464
+ const raw = { stdout, stderr }
465
+ stdout = String(stdout).trim()
466
+ stderr = String(stderr).trim()
467
+ resolve({ error, stdout, stderr, raw })
468
+ this.makeLog(opts.quiet?"debug":"mark", `${name} ${logger.green(`[完成${this.getTimeDiff(start_time)}]`)} ${stdout?`\n${stdout}`:""}${stderr?logger.red(`\n${stderr}`):""}`, "执行命令")
469
+ if (error) this.makeLog(opts.quiet?"debug":"error", error, "执行命令")
470
+ }
471
+ const start_time = Date.now()
472
+ if (Array.isArray(cmd))
473
+ execFile(cmd.shift(), cmd, opts, callback)
474
+ else
475
+ exec(cmd, opts, callback)
476
+ })}
477
+
478
+ async cmdPath(cmd, opts = {}) {
479
+ const ret = await this.exec(`${process.platform === "win32" ? "where" : "command -v"} "${cmd}"`, { quiet: true, ...opts })
480
+ return ret.error ? false : ret.stdout
481
  }
482
 
483
  makeLog(level, msg, id) {
484
  const log = []
485
+ if (id !== false)
486
+ log.push(logger.blue(`[${id || "TRSSYz"}]`))
487
  for (const i of Array.isArray(msg) ? msg : [msg])
488
+ log.push(this.Loging(i))
489
+ logger.logger[level](...log)
490
  }
491
 
492
+ prepareEvent(data) {
493
+ if (!this.bots[data.self_id]) return
494
  if (!data.bot)
495
  Object.defineProperty(data, "bot", {
496
+ value: this.bots[data.self_id],
497
  })
498
  if (!data.friend && data.user_id)
499
  Object.defineProperty(data, "friend", {
 
514
  data.adapter_name = data.bot.adapter.name
515
 
516
  for (const i of [data.friend, data.group, data.member]) {
517
+ if (typeof i !== "object") continue
518
+ i.sendFile ??= (file, name) => i.sendMsg(segment.file(file, name))
519
+ i.makeForwardMsg ??= this.makeForwardMsg
520
+ i.sendForwardMsg ??= msg => this.sendForwardMsg(msg => i.sendMsg(msg), msg)
521
+ i.getInfo ??= () => i.info || i
 
 
 
 
522
  }
523
  }
524
 
525
  em(name = "", data = {}) {
526
+ this.prepareEvent(data)
527
  while (true) {
528
  this.emit(name, data)
529
  const i = name.lastIndexOf(".")
530
+ if (i === -1) break
531
  name = name.slice(0, i)
532
  }
533
  }
 
535
  getFriendArray() {
536
  const array = []
537
  for (const bot_id of this.uin)
538
+ for (const [id, i] of this.bots[bot_id].fl || [])
539
  array.push({ ...i, bot_id })
540
  return array
541
  }
 
543
  getFriendList() {
544
  const array = []
545
  for (const bot_id of this.uin)
546
+ array.push(...(this.bots[bot_id].fl?.keys() || []))
 
547
  return array
548
  }
549
 
550
  getFriendMap() {
551
  const map = new Map
552
  for (const bot_id of this.uin)
553
+ for (const [id, i] of this.bots[bot_id].fl || [])
554
  map.set(id, { ...i, bot_id })
555
  return map
556
  }
 
559
  getGroupArray() {
560
  const array = []
561
  for (const bot_id of this.uin)
562
+ for (const [id, i] of this.bots[bot_id].gl || [])
563
  array.push({ ...i, bot_id })
564
  return array
565
  }
 
567
  getGroupList() {
568
  const array = []
569
  for (const bot_id of this.uin)
570
+ array.push(...(this.bots[bot_id].gl?.keys() || []))
 
571
  return array
572
  }
573
 
574
  getGroupMap() {
575
  const map = new Map
576
  for (const bot_id of this.uin)
577
+ for (const [id, i] of this.bots[bot_id].gl || [])
578
  map.set(id, { ...i, bot_id })
579
  return map
580
  }
 
582
  get gml() {
583
  const map = new Map
584
  for (const bot_id of this.uin)
585
+ for (const [id, i] of this.bots[bot_id].gml || [])
586
+ map.set(id, Object.assign(new Map(i), { bot_id }))
587
  return map
588
  }
589
 
590
+ pickFriend(user_id, strict) {
591
+ user_id = Number(user_id) || user_id
592
+ let user = this.fl.get(user_id)
593
+ if (!user) for (const [id, ml] of this.gml) {
594
+ user = ml.get(user_id)
595
+ if (user) {
596
+ user.bot_id = ml.bot_id
597
+ break
598
+ }
599
+ }
600
+ if (user) return this.bots[user.bot_id].pickFriend(user_id)
601
+ if (strict) return false
602
+ this.makeLog("debug", ["因不存在用户", user_id, "而随机选择Bot", this.uin.toJSON()])
603
+ return this.bots[this.uin].pickFriend(user_id)
604
  }
605
  get pickUser() { return this.pickFriend }
606
 
607
+ pickGroup(group_id, strict) {
608
+ group_id = Number(group_id) || group_id
609
  const group = this.gl.get(group_id)
610
+ if (group) return this.bots[group.bot_id].pickGroup(group_id)
611
+ if (strict) return false
612
+ this.makeLog("debug", ["因不存在群", group_id, "而随机选择Bot", this.uin.toJSON()])
613
+ return this.bots[this.uin].pickGroup(group_id)
614
  }
615
 
616
  pickMember(group_id, user_id) {
617
+ return this.pickGroup(group_id).pickMember(user_id)
 
618
  }
619
 
620
+ sendFriendMsg(bot_id, user_id, ...args) { try {
621
+ if (!bot_id)
622
+ return this.pickFriend(user_id).sendMsg(...args)
 
623
 
624
+ if (this.uin.includes(bot_id) && this.bots[bot_id])
625
+ return this.bots[bot_id].pickFriend(user_id).sendMsg(...args)
626
 
627
+ if (this.pickFriend(bot_id, true))
628
+ return this.pickFriend(bot_id).sendMsg(user_id, ...args)
 
 
 
 
 
 
629
 
630
+ return new Promise((resolve, reject) => {
631
+ const listener = data => {
632
+ resolve(data.bot.pickFriend(user_id).sendMsg(...args))
633
+ clearTimeout(timeout)
634
+ }
635
+ const timeout = setTimeout(() => {
636
+ reject(Object.assign(Error("等待 Bot 上线超时"), { bot_id, user_id, args }))
637
+ this.off(`connect.${bot_id}`, listener)
638
+ }, 300000)
639
+ this.once(`connect.${bot_id}`, listener)
640
+ })
641
+ } catch (err) {
642
+ this.makeLog("error", ["发送好友消息错误", args, err], `${bot_id} => ${user_id}`)
643
+ }}
644
 
645
+ sendGroupMsg(bot_id, group_id, ...args) { try {
646
+ if (!bot_id)
647
+ return this.pickGroup(group_id).sendMsg(...args)
648
 
649
+ if (this.uin.includes(bot_id) && this.bots[bot_id])
650
+ return this.bots[bot_id].pickGroup(group_id).sendMsg(...args)
651
+
652
+ if (this.pickGroup(bot_id, true))
653
+ return this.pickGroup(bot_id).sendMsg(group_id, ...args)
654
+
655
+ return new Promise((resolve, reject) => {
656
+ const listener = data => {
657
+ resolve(data.bot.pickGroup(group_id).sendMsg(...args))
658
+ clearTimeout(timeout)
659
+ }
660
+ const timeout = setTimeout(() => {
661
+ reject(Object.assign(Error("等待 Bot 上线超时"), { bot_id, group_id, args }))
662
+ this.off(`connect.${bot_id}`, listener)
663
+ }, 300000)
664
+ this.once(`connect.${bot_id}`, listener)
665
+ })
666
+ } catch (err) {
667
+ this.makeLog("error", ["发送群消息错误", args, err], `${bot_id} => ${group_id}`)
668
+ }}
669
 
670
+ getTextMsg(fnc = () => true) {
671
+ if (typeof fnc !== "function") {
672
  const { self_id, user_id } = fnc
673
  fnc = data => data.self_id == self_id && data.user_id == user_id
674
  }
675
 
676
+ return new Promise(resolve => {
677
+ const listener = data => { try {
678
+ if (!fnc(data)) return
679
+
680
+ let msg = ""
681
+ for (const i of data.message)
682
+ if (i.type === "text" && i.text)
683
+ msg += i.text.trim()
684
+ if (!msg) return
685
+
686
+ resolve(msg)
687
+ this.off("message", listener)
688
+ } catch (err) {
689
+ this.makeLog("error", err, data.self_id)
690
+ }}
691
+ this.on("message", listener)
692
+ })
 
693
  }
694
 
695
  getMasterMsg() {
696
+ return this.getTextMsg(data => cfg.master[data.self_id]?.includes(String(data.user_id)))
 
697
  }
698
 
699
+ async sendMasterMsg(msg, bot_array = Object.keys(cfg.master), sleep = 5000) {
700
+ const ret = {}
701
+ await Promise.allSettled((Array.isArray(bot_array) ? bot_array : [bot_array]).map(async bot_id => {
702
+ ret[bot_id] = {}
703
+ for (const user_id of cfg.master[bot_id] || []) {
704
+ ret[bot_id][user_id] = this.sendFriendMsg(bot_id, user_id, msg)
705
+ if (sleep) await this.sleep(sleep, ret[bot_id][user_id])
706
+ }
707
+ }))
708
+ return ret
709
  }
710
 
711
  makeForwardMsg(msg) { return { type: "node", data: msg } }
 
724
  return messages
725
  }
726
 
727
+ getTimeDiff(time1 = this.stat.start_time*1000, time2 = Date.now()) {
728
+ const time = (time2-time1)/1000
729
  let ret = ""
730
+ const day = Math.floor(time/3600/24)
731
  if (day) ret += `${day}天`
732
+ const hour = Math.floor((time/3600)%24)
733
  if (hour) ret += `${hour}时`
734
+ const min = Math.floor((time/60)%60)
735
  if (min) ret += `${min}分`
736
+ const sec = (time%60).toFixed(3)
737
  if (sec) ret += `${sec}秒`
738
  return ret || "0秒"
739
  }
Yunzai/lib/common/common.js CHANGED
@@ -1,16 +1,13 @@
1
- import { pipeline } from 'stream'
2
- import { promisify } from 'util'
3
- import fetch from 'node-fetch'
4
- import fs from 'node:fs'
5
- import path from 'node:path'
6
 
7
  /**
8
  * 发送私聊消息
9
  * @param user_id 账号
10
  * @param msg 消息
 
11
  */
12
- function relpyPrivate(userId, msg) {
13
- return Bot.pickFriend(userId).sendMsg(msg)
14
  }
15
 
16
  /**
@@ -18,22 +15,18 @@ function relpyPrivate(userId, msg) {
18
  * @param ms 毫秒
19
  */
20
  function sleep(ms) {
21
- return new Promise((resolve) => setTimeout(resolve, ms))
22
  }
23
 
24
  /**
25
  * 下载保存文件
26
- * @param fileUrl 下载地址
27
- * @param savePath 保存路径
 
28
  */
29
- async function downFile(fileUrl, savePath,param = {}) {
30
  try {
31
- mkdirs(path.dirname(savePath))
32
- logger.debug(`[下载文件] ${fileUrl}`)
33
- const response = await fetch(fileUrl,param)
34
- const streamPipeline = promisify(pipeline)
35
- await streamPipeline(response.body, fs.createWriteStream(savePath))
36
- return true
37
  } catch (err) {
38
  logger.error(`下载文件错误:${err}`)
39
  return false
@@ -42,10 +35,8 @@ async function downFile(fileUrl, savePath,param = {}) {
42
 
43
  function mkdirs(dirname) {
44
  if (fs.existsSync(dirname)) return true
45
- if (mkdirs(path.dirname(dirname))) {
46
- fs.mkdirSync(dirname)
47
- return true
48
- }
49
  }
50
 
51
  /**
 
1
+ import fs from "node:fs"
 
 
 
 
2
 
3
  /**
4
  * 发送私聊消息
5
  * @param user_id 账号
6
  * @param msg 消息
7
+ * @param bot_id 机器人账号
8
  */
9
+ function relpyPrivate(user_id, msg, bot_id) {
10
+ return Bot.sendFriendMsg(bot_id, user_id, msg)
11
  }
12
 
13
  /**
 
15
  * @param ms 毫秒
16
  */
17
  function sleep(ms) {
18
+ return new Promise(resolve => setTimeout(resolve, ms))
19
  }
20
 
21
  /**
22
  * 下载保存文件
23
+ * @param url 下载地址
24
+ * @param file 保存路径
25
+ * @param opts 下载参数
26
  */
27
+ async function downFile(url, file, opts) {
28
  try {
29
+ return await Bot.download(url, file, opts)
 
 
 
 
 
30
  } catch (err) {
31
  logger.error(`下载文件错误:${err}`)
32
  return false
 
35
 
36
  function mkdirs(dirname) {
37
  if (fs.existsSync(dirname)) return true
38
+ fs.mkdirSync(dirname, { recursive: true })
39
+ return true
 
 
40
  }
41
 
42
  /**
Yunzai/lib/config/config.js CHANGED
@@ -6,11 +6,12 @@ import chokidar from "chokidar"
6
  class Cfg {
7
  constructor() {
8
  this.config = {}
9
-
10
  /** 监听文件 */
11
  this.watcher = { config: {}, defSet: {} }
12
-
13
  this.initCfg()
 
 
 
14
  }
15
 
16
  /** 初始化配置 */
@@ -26,43 +27,22 @@ class Cfg {
26
  fs.mkdirSync(i)
27
  }
28
 
29
- /** Bot配置 */
30
- get bot() {
31
- let bot = this.getConfig("bot")
32
- let defbot = this.getdefSet("bot")
33
- bot = { ...defbot, ...bot }
34
-
35
- return bot
36
- }
37
-
38
- get other() {
39
- return this.getConfig("other")
40
- }
41
-
42
- get redis() {
43
- return this.getConfig("redis")
44
- }
45
-
46
- get renderer() {
47
- return this.getConfig("renderer");
48
- }
49
-
50
  /** 主人账号 */
51
  get masterQQ() {
52
- let masterQQ = this.getConfig("other").masterQQ || []
53
 
54
  if (!Array.isArray(masterQQ))
55
  masterQQ = [masterQQ]
56
 
57
  const masters = []
58
  for (const i of masterQQ)
59
- masters.push(Number(i) || String(i))
60
  return masters
61
  }
62
 
63
  /** Bot账号:[主人帐号] */
64
  get master() {
65
- let master = this.getConfig("other").master || []
66
 
67
  if (!Array.isArray(master))
68
  master = [master]
@@ -98,10 +78,8 @@ class Cfg {
98
 
99
  /** 群配置 */
100
  getGroup(bot_id = "", group_id = "") {
101
- const config = this.getConfig("group")
102
- const defCfg = this.getdefSet("group")
103
  return {
104
- ...defCfg.default,
105
  ...config.default,
106
  ...config[`${bot_id}:default`],
107
  ...config[group_id],
@@ -111,9 +89,7 @@ class Cfg {
111
 
112
  /** other配置 */
113
  getOther() {
114
- let def = this.getdefSet("other")
115
- let config = this.getConfig("other")
116
- return { ...def, ...config }
117
  }
118
 
119
  /**
@@ -129,39 +105,47 @@ class Cfg {
129
  return this.getYaml("config", name)
130
  }
131
 
 
 
 
 
 
 
 
132
  /**
133
  * 获取配置yaml
134
  * @param type 默认跑配置-defSet,用户配置-config
135
  * @param name 名称
136
  */
137
  getYaml(type, name) {
138
- let file = `config/${type}/${name}.yaml`
139
- let key = `${type}.${name}`
140
- if (this.config[key]) return this.config[key]
141
-
142
- this.config[key] = YAML.parse(
143
- fs.readFileSync(file, "utf8")
144
- )
 
 
 
145
 
 
146
  this.watch(file, name, type)
147
-
148
  return this.config[key]
149
  }
150
 
151
  /** 监听配置文件 */
152
  watch(file, name, type = "default_config") {
153
- let key = `${type}.${name}`
154
 
155
  if (this.watcher[key]) return
156
-
157
  const watcher = chokidar.watch(file)
158
  watcher.on("change", path => {
159
  delete this.config[key]
160
- if (typeof Bot == "undefined") return
161
- logger.mark(`[修改配置文件][${type}][${name}]`)
162
- if (this[`change_${name}`]) {
163
  this[`change_${name}`]()
164
- }
165
  })
166
 
167
  this.watcher[key] = watcher
@@ -169,8 +153,7 @@ class Cfg {
169
 
170
  async change_bot() {
171
  /** 修改日志等级 */
172
- let log = await import("./log.js")
173
- log.default()
174
  }
175
  }
176
 
 
6
  class Cfg {
7
  constructor() {
8
  this.config = {}
 
9
  /** 监听文件 */
10
  this.watcher = { config: {}, defSet: {} }
 
11
  this.initCfg()
12
+ return new Proxy(this, {
13
+ get: (target, prop) => target[prop] ?? target.getAllCfg(String(prop)),
14
+ })
15
  }
16
 
17
  /** 初始化配置 */
 
27
  fs.mkdirSync(i)
28
  }
29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  /** 主人账号 */
31
  get masterQQ() {
32
+ let masterQQ = this.getAllCfg("other").masterQQ || []
33
 
34
  if (!Array.isArray(masterQQ))
35
  masterQQ = [masterQQ]
36
 
37
  const masters = []
38
  for (const i of masterQQ)
39
+ masters.push(Number(i) || i)
40
  return masters
41
  }
42
 
43
  /** Bot账号:[主人帐号] */
44
  get master() {
45
+ let master = this.getAllCfg("other").master || []
46
 
47
  if (!Array.isArray(master))
48
  master = [master]
 
78
 
79
  /** 群配置 */
80
  getGroup(bot_id = "", group_id = "") {
81
+ const config = this.getAllCfg("group")
 
82
  return {
 
83
  ...config.default,
84
  ...config[`${bot_id}:default`],
85
  ...config[group_id],
 
89
 
90
  /** other配置 */
91
  getOther() {
92
+ return this.getAllCfg("other")
 
 
93
  }
94
 
95
  /**
 
105
  return this.getYaml("config", name)
106
  }
107
 
108
+ getAllCfg(name) {
109
+ return {
110
+ ...this.getdefSet(name),
111
+ ...this.getConfig(name),
112
+ }
113
+ }
114
+
115
  /**
116
  * 获取配置yaml
117
  * @param type 默认跑配置-defSet,用户配置-config
118
  * @param name 名称
119
  */
120
  getYaml(type, name) {
121
+ const key = `${type}.${name}`
122
+ if (key in this.config) return this.config[key]
123
+ const file = `config/${type}/${name}.yaml`
124
+
125
+ try {
126
+ this.config[key] = fs.readFileSync(file, "utf8")
127
+ } catch (err) {
128
+ Bot.makeLog("trace", ["读取配置文件", file, "错误", err], "Config")
129
+ return this.config[key] = undefined
130
+ }
131
 
132
+ this.config[key] = YAML.parse(this.config[key])
133
  this.watch(file, name, type)
 
134
  return this.config[key]
135
  }
136
 
137
  /** 监听配置文件 */
138
  watch(file, name, type = "default_config") {
139
+ const key = `${type}.${name}`
140
 
141
  if (this.watcher[key]) return
 
142
  const watcher = chokidar.watch(file)
143
  watcher.on("change", path => {
144
  delete this.config[key]
145
+ if (typeof Bot !== "object") return
146
+ Bot.makeLog("mark", `[修改配置文件][${type}][${name}]`, "Config")
147
+ if (`change_${name}` in this)
148
  this[`change_${name}`]()
 
149
  })
150
 
151
  this.watcher[key] = watcher
 
153
 
154
  async change_bot() {
155
  /** 修改日志等级 */
156
+ (await import("./log.js")).default()
 
157
  }
158
  }
159
 
Yunzai/lib/config/init.js CHANGED
@@ -1,5 +1,6 @@
1
  import setLog from "./log.js"
2
  import cfg from "./config.js"
 
3
 
4
  /** 设置标题 */
5
  process.title = `TRSS Yunzai v${cfg.package.version} © 2023 - 2024 TimeRainStarSky`
@@ -7,7 +8,8 @@ process.title = `TRSS Yunzai v${cfg.package.version} © 2023 - 2024 TimeRainStar
7
  /** 设置时区 */
8
  process.env.TZ = "Asia/Shanghai"
9
 
10
- process.on("SIGHUP", () => process.exit())
 
11
 
12
  /** 日志设置 */
13
  setLog()
@@ -23,14 +25,29 @@ for (const i of ["uncaughtException", "unhandledRejection"])
23
  }
24
  })
25
 
26
- /** 退出事件 */
27
- process.on("exit", code => {
28
- if (typeof redis != "undefined")
29
- redis.save()
30
-
31
- logger.mark(logger.magenta(`TRSS-Yunzai 已停止运行,本次运行时长:${Bot.getTimeDiff()} (${code})`))
32
- })
33
-
34
  logger.mark("----^_^----")
35
  logger.mark(logger.yellow(`TRSS-Yunzai v${cfg.package.version} 启动中...`))
36
- logger.mark(logger.cyan("https://github.com/TimeRainStarSky/Yunzai"))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import setLog from "./log.js"
2
  import cfg from "./config.js"
3
+ import redisInit from "./redis.js"
4
 
5
  /** 设置标题 */
6
  process.title = `TRSS Yunzai v${cfg.package.version} © 2023 - 2024 TimeRainStarSky`
 
8
  /** 设置时区 */
9
  process.env.TZ = "Asia/Shanghai"
10
 
11
+ for (const i of ["SIGHUP", "SIGTERM"])
12
+ process.on(i, (signal, code) => process.exit(code))
13
 
14
  /** 日志设置 */
15
  setLog()
 
25
  }
26
  })
27
 
 
 
 
 
 
 
 
 
28
  logger.mark("----^_^----")
29
  logger.mark(logger.yellow(`TRSS-Yunzai v${cfg.package.version} 启动中...`))
30
+ logger.mark(logger.cyan("https://github.com/TimeRainStarSky/Yunzai"))
31
+
32
+ let stack
33
+ export default async function init() {
34
+ if (stack !== undefined) return
35
+ stack = ""
36
+
37
+ const redis = await redisInit()
38
+ const exit = process.exit
39
+ process.exit = async code => {
40
+ stack = Error().stack
41
+ if (redis.process) {
42
+ await Bot.sleep(5000, redis.save())
43
+ redis.process.kill()
44
+ }
45
+ exit(code)
46
+ }
47
+
48
+ /** 退出事件 */
49
+ process.on("exit", code => {
50
+ Bot.makeLog("mark", logger.magenta(`TRSS-Yunzai 已停止运行,本次运行时长:${Bot.getTimeDiff()} (${code})`), "exit")
51
+ Bot.makeLog("trace", (stack || Error().stack), "exit")
52
+ })
53
+ }
Yunzai/lib/config/log.js CHANGED
@@ -1,5 +1,5 @@
1
  import log4js from "log4js"
2
- import chalk from "chalk"
3
  import cfg from "./config.js"
4
  import fs from "node:fs"
5
 
@@ -7,9 +7,8 @@ import fs from "node:fs"
7
  * 设置日志样式
8
  */
9
  export default function setLog() {
10
- let file = "./logs"
11
- if (!fs.existsSync(file))
12
- fs.mkdirSync(file)
13
 
14
  /** 调整error日志等级 */
15
  // log4js.levels.levels[5].level = Number.MAX_VALUE
@@ -21,7 +20,7 @@ export default function setLog() {
21
  type: "console",
22
  layout: {
23
  type: "pattern",
24
- pattern: "%[[TRSSYz][%d{hh:mm:ss.SSS}][%4.4p]%] %m"
25
  }
26
  },
27
  command: {
@@ -32,7 +31,7 @@ export default function setLog() {
32
  alwaysIncludePattern: true,
33
  layout: {
34
  type: "pattern",
35
- pattern: "[%d{hh:mm:ss.SSS}][%4.4p] %m"
36
  }
37
  },
38
  error: {
@@ -41,7 +40,7 @@ export default function setLog() {
41
  alwaysIncludePattern: true,
42
  layout: {
43
  type: "pattern",
44
- pattern: "[%d{hh:mm:ss.SSS}][%4.4p] %m"
45
  }
46
  }
47
  },
@@ -52,25 +51,23 @@ export default function setLog() {
52
  }
53
  })
54
 
55
- const defaultLogger = log4js.getLogger("message")
56
- const commandLogger = log4js.getLogger("command")
57
- const errorLogger = log4js.getLogger("error")
58
-
59
  /** 全局变量 logger */
60
- global.logger = {
61
- trace: (...args) => defaultLogger.trace(...args),
62
- debug: (...args) => defaultLogger.debug(...args),
63
- info: (...args) => defaultLogger.info(...args),
64
- warn: (...args) => commandLogger.warn(...args),
65
- error: (...args) => errorLogger.error(...args),
66
- fatal: (...args) => errorLogger.fatal(...args),
67
- mark: (...args) => commandLogger.mark(...args),
68
- chalk: chalk,
69
- red: chalk.red,
70
- green: chalk.green,
71
- yellow: chalk.yellow,
72
- blue: chalk.blue,
73
- magenta: chalk.magenta,
74
- cyan: chalk.cyan,
75
  }
 
 
 
 
 
76
  }
 
1
  import log4js from "log4js"
2
+ import { Chalk } from "chalk"
3
  import cfg from "./config.js"
4
  import fs from "node:fs"
5
 
 
7
  * 设置日志样式
8
  */
9
  export default function setLog() {
10
+ if (!fs.existsSync("logs"))
11
+ fs.mkdirSync("logs")
 
12
 
13
  /** 调整error日志等级 */
14
  // log4js.levels.levels[5].level = Number.MAX_VALUE
 
20
  type: "console",
21
  layout: {
22
  type: "pattern",
23
+ pattern: "%[[%d{hh:mm:ss.SSS}][%4.4p]%]%m"
24
  }
25
  },
26
  command: {
 
31
  alwaysIncludePattern: true,
32
  layout: {
33
  type: "pattern",
34
+ pattern: "[%d{hh:mm:ss.SSS}][%4.4p]%m"
35
  }
36
  },
37
  error: {
 
40
  alwaysIncludePattern: true,
41
  layout: {
42
  type: "pattern",
43
+ pattern: "[%d{hh:mm:ss.SSS}][%4.4p]%m"
44
  }
45
  }
46
  },
 
51
  }
52
  })
53
 
 
 
 
 
54
  /** 全局变量 logger */
55
+ const chalk = new Chalk({ level: 3 })
56
+ chalk.logger = {
57
+ defaultLogger: log4js.getLogger("message"),
58
+ commandLogger: log4js.getLogger("command"),
59
+ errorLogger: log4js.getLogger("error"),
60
+ trace(...args) { return this.defaultLogger.trace(...args) },
61
+ debug(...args) { return this.defaultLogger.debug(...args) },
62
+ info(...args) { return this.defaultLogger.info(...args) },
63
+ warn(...args) { return this.commandLogger.warn(...args) },
64
+ error(...args) { return this.errorLogger.error(...args) },
65
+ fatal(...args) { return this.errorLogger.fatal(...args) },
66
+ mark(...args) { return this.commandLogger.mark(...args) },
 
 
 
67
  }
68
+ const defid = chalk.blue(`[TRSSYz]`)
69
+ for (const i in chalk.logger)
70
+ if (typeof chalk.logger[i] == "function")
71
+ chalk[i] = (...args) => chalk.logger[i](defid, ...args)
72
+ global.logger = chalk
73
  }
Yunzai/lib/config/redis.js CHANGED
@@ -1,66 +1,109 @@
1
  import cfg from "./config.js"
2
  import { createClient } from "redis"
3
- import { exec } from "node:child_process"
4
 
5
- let lock = false
6
  /**
7
  * 初始化全局redis客户端
8
  */
9
  export default async function redisInit() {
10
  const rc = cfg.redis
11
- const redisUn = rc.username || ""
12
- let redisPw = rc.password ? `:${rc.password}` : ""
13
- if (rc.username || rc.password)
14
- redisPw += "@"
15
- const redisUrl = `redis://${redisUn}${redisPw}${rc.host}:${rc.port}/${rc.db}`
16
- Bot.makeLog("info", `正在连接 ${logger.blue(redisUrl)}`, "Redis")
17
- return connectRedis(redisUrl)
 
 
18
  }
19
 
20
- async function connectRedis(redisUrl, cmd) {
21
- if (lock && !cmd) return
22
- lock = true
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
- try {
25
- global.redis = createClient({ url: redisUrl })
26
- await redis.connect()
27
- } catch (err) {
28
- Bot.makeLog("error", ["连接错误", err], "Redis")
29
- if (!cmd) return startRedis(redisUrl)
30
- Bot.makeLog("error", ["请先启动", logger.blue(cmd)], "Redis")
31
- process.exit(1)
32
  }
33
 
34
- redis.on("error", err => {
35
- Bot.makeLog("error", err, "Redis")
36
- return connectRedis(redisUrl)
37
- })
 
 
38
 
39
- lock = false
40
- return redis
41
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
- async function startRedis(redisUrl) {
44
- if (cfg.redis.host != "127.0.0.1") {
45
- Bot.makeLog("error", `连接失败,请确认连接地址正确`, "Redis")
46
- process.exit(1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  }
48
- const cmd = `redis-server --port ${cfg.redis.port} --save 900 1 --save 300 10 --daemonize yes${await aarch64()}`
49
- await Bot.exec(cmd)
50
- await Bot.sleep(1000)
51
- return connectRedis(redisUrl, cmd)
52
- }
53
 
54
- async function aarch64() {
55
- if (process.platform == "win32" || process.arch != "arm64")
56
- return ""
57
- /** 判断redis版本 */
58
- let v = await Bot.exec("redis-server -v")
59
- if (v.stdout?.match) {
60
- v = v.stdout.match(/v=(\d)./)
61
- /** 忽略arm警告 */
62
- if (v && v[1] >= 6)
63
- return " --ignore-warnings ARM64-COW-BUG"
 
 
64
  }
65
- return ""
66
  }
 
1
  import cfg from "./config.js"
2
  import { createClient } from "redis"
3
+ import { spawn } from "node:child_process"
4
 
 
5
  /**
6
  * 初始化全局redis客户端
7
  */
8
  export default async function redisInit() {
9
  const rc = cfg.redis
10
+ return global.redis = await new Redis({
11
+ socket: {
12
+ host: rc.host,
13
+ port: rc.port,
14
+ },
15
+ username: rc.username,
16
+ password: rc.password,
17
+ database: rc.db,
18
+ }, true).connect()
19
  }
20
 
21
+ export class Redis {
22
+ constructor(opts, exit) {
23
+ Bot.makeLog("info", `正在连接 ${logger.cyan(`redis://${opts.socket.host}:${opts.socket.port}/${opts.database}`)}`, "Redis")
24
+ this.opts = opts
25
+ this.redis = createClient(opts)
26
+ this.redis.class = this
27
+ this.exit = exit
28
+ }
29
+
30
+ async connect(force) {
31
+ if (this.lock && !force) return
32
+ this.lock = true
33
+
34
+ try {
35
+ await this.redis.connect()
36
+ } catch (err) {
37
+ await this.redis.disconnect()
38
+ .catch(err => Bot.makeLog("error", err, "Redis"))
39
+ this.err = err
40
+ if (!force) return this.start()
41
+ return false
42
+ }
43
 
44
+ this.lock = false
45
+ return this.redis.once("error", this.onerror)
 
 
 
 
 
 
46
  }
47
 
48
+ async start() {
49
+ if (this.opts.socket.host !== "127.0.0.1") {
50
+ Bot.makeLog("error", [`连接错误,请确认连接地址正确`, this.err], "Redis")
51
+ if (this.exit) process.exit(1)
52
+ return false
53
+ }
54
 
55
+ const cmd = [
56
+ cfg.redis.path,
57
+ "--port", this.opts.socket.port,
58
+ ...await this.aarch64(),
59
+ ]
60
+ Bot.makeLog("info", ["正在启动", logger.cyan(cmd.join(" "))], "Redis")
61
+ const redisProcess = spawn(cmd[0], cmd.slice(1))
62
+ .on("error", err => {
63
+ Bot.makeLog("error", ["启动错误", err], "Redis")
64
+ redisProcess.exit = true
65
+ })
66
+ .on("exit", () => redisProcess.exit = true)
67
+ redisProcess.stdout.on("data", data => {
68
+ Bot.makeLog("info", Bot.String(data).trim(), "Redis")
69
+ })
70
+ redisProcess.stderr.on("data", data => {
71
+ Bot.makeLog("error", Bot.String(data).trim(), "Redis")
72
+ })
73
+ this.redis.process = redisProcess
74
 
75
+ for (let i=0; i<15; i++) {
76
+ await Bot.sleep(1000)
77
+ if (redisProcess.exit) break
78
+ const ret = await this.connect(true)
79
+ if (ret) return ret
80
+ }
81
+
82
+ Bot.makeLog("error", ["连接错误", this.err], "Redis")
83
+ redisProcess.kill()
84
+ if (this.exit) process.exit(1)
85
+ return false
86
+ }
87
+
88
+ onerror = async (err) => {
89
+ Bot.makeLog("error", err, "Redis")
90
+ await this.redis.disconnect()
91
+ .catch(err => Bot.makeLog("error", err, "Redis"))
92
+ if (this.redis.process) this.redis.process.kill()
93
+ return this.connect()
94
  }
 
 
 
 
 
95
 
96
+ async aarch64() {
97
+ if (process.platform === "win32" || process.arch !== "arm64")
98
+ return []
99
+ /** 判断redis版本 */
100
+ let v = await Bot.exec("redis-server -v")
101
+ if (v.stdout?.match) {
102
+ v = v.stdout.match(/v=(\d)./)
103
+ /** 忽略arm警告 */
104
+ if (v && v[1] >= 6)
105
+ return ["--ignore-warnings", "ARM64-COW-BUG"]
106
+ }
107
+ return []
108
  }
 
109
  }
Yunzai/lib/listener/listener.js CHANGED
@@ -7,7 +7,7 @@ export default class EventListener {
7
  * @param data.event 监听的事件
8
  * @param data.once 是否只监听一次
9
  */
10
- constructor (data) {
11
  this.prefix = data.prefix || ""
12
  this.event = data.event
13
  this.once = data.once || false
 
7
  * @param data.event 监听的事件
8
  * @param data.once 是否只监听一次
9
  */
10
+ constructor(data) {
11
  this.prefix = data.prefix || ""
12
  this.event = data.event
13
  this.once = data.once || false
Yunzai/lib/listener/loader.js CHANGED
@@ -8,12 +8,12 @@ class ListenerLoader {
8
  /**
9
  * 监听事件加载
10
  */
11
- async load () {
12
- logger.info("-----------")
13
- logger.info("加载监听事件中...")
14
  let eventCount = 0
15
  for (const file of (await fs.readdir("./lib/events")).filter(file => file.endsWith(".js"))) {
16
- logger.debug(`加载监听事件:${file}`)
17
  try {
18
  let listener = await import(`../events/${file}`)
19
  if (!listener.default) continue
@@ -30,27 +30,25 @@ class ListenerLoader {
30
  Bot[on](listener.prefix + listener.event, event => listener[e](event))
31
  }
32
  eventCount++
33
- } catch (e) {
34
- logger.mark(`监听事件错误:${file}`)
35
- logger.error(e)
36
  }
37
  }
38
- logger.info(`加载监听事件[${eventCount}个]`)
39
 
40
- logger.info("-----------")
41
- logger.info("加载适配器中...")
42
  let adapterCount = 0
43
  for (const adapter of Bot.adapter) {
44
  try {
45
- logger.debug(`加载适配器:${adapter.name}(${adapter.id})`)
46
  await adapter.load()
47
  adapterCount++
48
- } catch (e) {
49
- logger.mark(`加载适配器错误:${adapter.name}(${adapter.id})`)
50
- logger.error(e)
51
  }
52
  }
53
- logger.info(`加载适配器[${adapterCount}个]`)
54
  }
55
  }
56
 
 
8
  /**
9
  * 监听事件加载
10
  */
11
+ async load() {
12
+ Bot.makeLog("info", "-----------", "Listener")
13
+ Bot.makeLog("info", "加载监听事件中...", "Listener")
14
  let eventCount = 0
15
  for (const file of (await fs.readdir("./lib/events")).filter(file => file.endsWith(".js"))) {
16
+ Bot.makeLog("debug", [`加载监听事件 ${file}`], "Listener")
17
  try {
18
  let listener = await import(`../events/${file}`)
19
  if (!listener.default) continue
 
30
  Bot[on](listener.prefix + listener.event, event => listener[e](event))
31
  }
32
  eventCount++
33
+ } catch (err) {
34
+ Bot.makeLog("error", [`监听事件加载错误 ${file}`, err], "Listener")
 
35
  }
36
  }
37
+ Bot.makeLog("info", `加载监听事件[${eventCount}个]`, "Listener")
38
 
39
+ Bot.makeLog("info", "-----------", "Adapter")
40
+ Bot.makeLog("info", "加载适配器中...", "Adapter")
41
  let adapterCount = 0
42
  for (const adapter of Bot.adapter) {
43
  try {
44
+ Bot.makeLog("debug", [`加载适配器 ${adapter.name}(${adapter.id})`], "Adapter")
45
  await adapter.load()
46
  adapterCount++
47
+ } catch (err) {
48
+ Bot.makeLog("error", [`适配器加载错误 ${adapter.name}(${adapter.id})`, err], "Adapter")
 
49
  }
50
  }
51
+ Bot.makeLog("info", `加载适配器[${adapterCount}个]`, "Adapter")
52
  }
53
  }
54
 
Yunzai/lib/modules/oicq/index.js CHANGED
@@ -1,34 +1,41 @@
1
- const segment = new class segment {
2
  custom(type, data) {
3
  return { type, ...data }
4
- }
5
  raw(data) {
6
  return { type: "raw", data }
7
- }
8
  button(...data) {
9
  return { type: "button", data }
10
- }
11
  markdown(data) {
12
  return { type: "markdown", data }
13
- }
14
  image(file, name) {
15
  return { type: "image", file, name }
16
- }
17
  at(qq, name) {
18
  return { type: "at", qq, name }
19
- }
20
  record(file, name) {
21
  return { type: "record", file, name }
22
- }
23
  video(file, name) {
24
  return { type: "video", file, name }
25
- }
26
  file(file, name) {
27
  return { type: "file", file, name }
28
- }
29
  reply(id, text, qq, time, seq) {
30
  return { type: "reply", id, text, qq, time, seq }
31
- }
32
  }
33
 
 
 
 
 
 
 
 
34
  export { segment }
 
1
+ const segment = {
2
  custom(type, data) {
3
  return { type, ...data }
4
+ },
5
  raw(data) {
6
  return { type: "raw", data }
7
+ },
8
  button(...data) {
9
  return { type: "button", data }
10
+ },
11
  markdown(data) {
12
  return { type: "markdown", data }
13
+ },
14
  image(file, name) {
15
  return { type: "image", file, name }
16
+ },
17
  at(qq, name) {
18
  return { type: "at", qq, name }
19
+ },
20
  record(file, name) {
21
  return { type: "record", file, name }
22
+ },
23
  video(file, name) {
24
  return { type: "video", file, name }
25
+ },
26
  file(file, name) {
27
  return { type: "file", file, name }
28
+ },
29
  reply(id, text, qq, time, seq) {
30
  return { type: "reply", id, text, qq, time, seq }
31
+ },
32
  }
33
 
34
+ try {
35
+ const { segment: icqq_segment } = await import(`file://${process.cwd()}/plugins/ICQQ-Plugin/node_modules/icqq/lib/message/elements.js`)
36
+ const { deprecate } = await import("node:util")
37
+ for (const i in icqq_segment) if (!segment[i])
38
+ segment[i] = deprecate(icqq_segment[i], `segment.${i} 仅在 icqq 上可用`)
39
+ } catch {}
40
+
41
  export { segment }
Yunzai/lib/plugins/config.js CHANGED
@@ -3,11 +3,11 @@ import YAML from "yaml"
3
  import _ from "lodash"
4
  export default async function(name, config, keep) {
5
  const configFile = `config/${name}.yaml`
6
- const configSave = () => fs.writeFile(configFile, YAML.stringify(config), "utf-8")
7
 
8
  let configData
9
  try {
10
- configData = YAML.parse(await fs.readFile(configFile, "utf-8"))
11
  _.merge(config, configData)
12
  } catch (err) {
13
  logger.debug("配置文件", configFile, "读取失败", err)
 
3
  import _ from "lodash"
4
  export default async function(name, config, keep) {
5
  const configFile = `config/${name}.yaml`
6
+ const configSave = () => fs.writeFile(configFile, YAML.stringify(config), "utf8")
7
 
8
  let configData
9
  try {
10
+ configData = YAML.parse(await fs.readFile(configFile, "utf8"))
11
  _.merge(config, configData)
12
  } catch (err) {
13
  logger.debug("配置文件", configFile, "读取失败", err)
Yunzai/lib/plugins/loader.js CHANGED
@@ -19,29 +19,27 @@ global.segment = segment
19
  * 加载插件
20
  */
21
  class PluginsLoader {
22
- constructor() {
23
- this.priority = []
24
- this.handler = {}
25
- this.task = []
26
- this.dir = "plugins"
27
-
28
- /** 命令冷却cd */
29
- this.groupCD = {}
30
- this.singleCD = {}
31
-
32
- /** 插件监听 */
33
- this.watcher = {}
34
- this.eventMap = {
35
- message: ["post_type", "message_type", "sub_type"],
36
- notice: ["post_type", "notice_type", "sub_type"],
37
- request: ["post_type", "request_type", "sub_type"],
38
- }
39
 
40
- this.msgThrottle = {}
41
 
42
- /** 星铁命令前缀 */
43
- this.srReg = /^#?(\*|星铁|星轨|穹轨|星穹|崩铁|星穹铁道|崩坏星穹铁道|铁道)+/
44
- }
45
 
46
  async getPlugins() {
47
  const files = await fs.readdir(this.dir, { withFileTypes: true })
@@ -82,8 +80,8 @@ class PluginsLoader {
82
  if (isRefresh) this.priority = []
83
  if (this.priority.length) return
84
 
85
- logger.info("-----------")
86
- logger.info("加载插件中...")
87
 
88
  const files = await this.getPlugins()
89
  this.pluginCount = 0
@@ -96,8 +94,8 @@ class PluginsLoader {
96
  this.packageTips(packageErr)
97
  this.createTask()
98
 
99
- logger.info(`加载定时任务[${this.task.length}个]`)
100
- logger.info(`加载插件[${this.pluginCount}个]`)
101
 
102
  /** 优先级排序 */
103
  this.priority = lodash.orderBy(this.priority, ["priority"], ["asc"])
@@ -112,16 +110,14 @@ class PluginsLoader {
112
  pluginArray.push(this.loadPlugin(file, p))
113
  )
114
  for (const i of await Promise.allSettled(pluginArray))
115
- if (i?.status && i.status != "fulfilled") {
116
- logger.error(`加载插件错误:${logger.red(file.name)}`)
117
- logger.error(decodeURI(i.reason))
118
  }
119
  } catch (error) {
120
  if (packageErr && error.stack.includes("Cannot find package")) {
121
  packageErr.push({ error, file })
122
  } else {
123
- logger.error(`加载插件错误:${logger.red(file.name)}`)
124
- logger.error(decodeURI(error.stack))
125
  }
126
  }
127
  }
@@ -130,11 +126,11 @@ class PluginsLoader {
130
  if (!p?.prototype) return
131
  this.pluginCount++
132
  const plugin = new p
133
- logger.debug(`加载插件 [${file.name}][${plugin.name}]`)
134
  /** 执行初始化,返回 return 则跳过加载 */
135
- if (plugin.init && await plugin.init() == "return") return
136
  /** 初始化定时任务 */
137
- this.collectTask(plugin.task)
138
  this.priority.push({
139
  class: p,
140
  key: file.name,
@@ -156,14 +152,14 @@ class PluginsLoader {
156
 
157
  packageTips(packageErr) {
158
  if (!packageErr.length) return
159
- logger.mark("--------- 插件加载错误 ---------")
160
  for (const i of packageErr) {
161
  const pack = i.error.stack.match(/'(.+?)'/g)[0].replace(/'/g, "")
162
- logger.mark(`${logger.blue(i.file.name)} 缺少依赖 ${logger.red(pack)}`)
163
  }
164
- logger.mark(`安装插件后请 ${logger.red("pnpm i")} 安装依赖`)
165
- logger.mark(`仍报错${logger.red("进入插件目录")} pnpm add 依赖`)
166
- logger.mark("--------------------------------")
167
  }
168
 
169
  /**
@@ -202,8 +198,10 @@ class PluginsLoader {
202
  ...plugin.getContext(false, true),
203
  }
204
  if (!lodash.isEmpty(context)) {
 
205
  for (const fnc in context)
206
- plugin[fnc](context[fnc])
 
207
  return
208
  }
209
  }
@@ -230,38 +228,35 @@ class PluginsLoader {
230
  for (const plugin of priority)
231
  if (plugin.accept) {
232
  const res = await plugin.accept(e)
233
- if (res == "return") return
234
  if (res) break
235
  }
236
 
237
  a: for (const plugin of priority) {
238
- /** 正则匹配 */
239
  if (plugin.rule) for (const v of plugin.rule) {
240
  /** 判断事件 */
241
  if (v.event && !this.filtEvent(e, v)) continue
242
 
 
243
  if (!new RegExp(v.reg).test(e.msg)) continue
244
- e.logFnc = `[${plugin.name}][${v.fnc}]`
245
 
246
- if (v.log !== false)
247
- logger.info(`${e.logFnc}${e.logText} ${lodash.truncate(e.msg, { length: 100 })}`)
248
 
249
  /** 判断权限 */
250
  if (!this.filtPermission(e, v)) break a
251
 
252
  try {
253
- const start = Date.now()
254
  const res = plugin[v.fnc] && (await plugin[v.fnc](e))
255
  if (res !== false) {
256
  /** 设置冷却cd */
257
  this.setLimit(e)
258
- if (v.log !== false)
259
- logger.mark(`${e.logFnc} ${lodash.truncate(e.msg, { length: 100 })} 处理完成 ${Date.now() - start}ms`)
260
  break a
261
  }
262
- } catch (error) {
263
- logger.error(`${e.logFnc}`)
264
- logger.error(error.stack)
265
  break a
266
  }
267
  }
@@ -275,19 +270,19 @@ class PluginsLoader {
275
  const eventMap = this.eventMap[e.post_type] || []
276
  const newEvent = []
277
  for (const i in event) {
278
- if (event[i] == "*")
279
  newEvent.push(event[i])
280
  else
281
  newEvent.push(e[eventMap[i]])
282
  }
283
- return v.event == newEvent.join(".")
284
  }
285
 
286
  /** 判断权限 */
287
  filtPermission(e, v) {
288
- if (v.permission == "all" || !v.permission) return true
289
 
290
- if (v.permission == "master") {
291
  if (e.isMaster) {
292
  return true
293
  } else {
@@ -297,13 +292,13 @@ class PluginsLoader {
297
  }
298
 
299
  if (e.isGroup) {
300
- if (v.permission == "owner") {
301
  if (!e.member.is_owner) {
302
  e.reply("暂无权限,只有群主才能操作")
303
  return false
304
  }
305
  }
306
- if (v.permission == "admin") {
307
  if (!e.member.is_admin) {
308
  e.reply("暂无权限,只有管理员才能操作")
309
  return false
@@ -349,7 +344,7 @@ class PluginsLoader {
349
  e.img = [i.url]
350
  break
351
  case "at":
352
- if (i.qq == e.self_id)
353
  e.atBot = true
354
  else
355
  e.at = i.qq
@@ -366,14 +361,14 @@ class PluginsLoader {
366
  break
367
  case "xml":
368
  case "json":
369
- e.msg = (e.msg || "") + (typeof i.data == "string" ? i.data : JSON.stringify(i.data))
370
  break
371
  }
372
  }
373
 
374
  e.logText = ""
375
 
376
- if (e.message_type == "private" || e.notice_type == "friend") {
377
  e.isPrivate = true
378
 
379
  if (e.sender) {
@@ -387,7 +382,7 @@ class PluginsLoader {
387
  }
388
 
389
  e.logText = `[${e.sender?.nickname ? `${e.sender.nickname}(${e.user_id})` : e.user_id}]`
390
- } else if (e.message_type == "group" || e.notice_type == "group") {
391
  e.isGroup = true
392
 
393
  if (e.sender) {
@@ -407,6 +402,8 @@ class PluginsLoader {
407
  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}]`
408
  }
409
 
 
 
410
  if (e.user_id && cfg.master[e.self_id]?.includes(String(e.user_id))) {
411
  e.isMaster = true
412
  }
@@ -476,6 +473,7 @@ class PluginsLoader {
476
  res = await reply(msg)
477
  } catch (err) {
478
  Bot.makeLog("error", ["发送消息错误", msg, err], e.self_id)
 
479
  }
480
 
481
  if (recallMsg > 0 && res?.message_id) {
@@ -523,27 +521,35 @@ class PluginsLoader {
523
  }
524
 
525
  /** 收集定时任务 */
526
- collectTask(task) {
527
  for (const i of Array.isArray(task) ? task : [task])
528
- if (i.cron && i.name)
 
529
  this.task.push(i)
 
530
  }
531
 
532
  /** 创建定时任务 */
533
  createTask() {
534
- for (const i of this.task)
535
- i.job = schedule.scheduleJob(i.cron, async () => {
536
- try {
537
- if (i.log == true)
538
- logger.mark(`开始定时任务:${i.name}`)
539
- await i.fnc()
540
- if (i.log == true)
541
- logger.mark(`定时任务完成:${i.name}`)
542
- } catch (error) {
543
- logger.error(`定时任务报错:${i.name}`)
544
- logger.error(error)
545
- }
546
- })
 
 
 
 
 
 
547
  }
548
 
549
  /** 检查命令冷却cd */
@@ -592,10 +598,10 @@ class PluginsLoader {
592
  let groupCfg = cfg.getGroup(e.self_id, e.group_id)
593
 
594
  /** 模式0,未开启前缀 */
595
- if (groupCfg.onlyReplyAt == 0 || !groupCfg.botAlias) return true
596
 
597
  /** 模式2,非主人开启 */
598
- if (groupCfg.onlyReplyAt == 2 && e.isMaster) return true
599
 
600
  /** at机器人 */
601
  if (e.atBot) return true
@@ -646,15 +652,14 @@ class PluginsLoader {
646
  lodash.forEach(app, p => {
647
  const plugin = new p
648
  for (const i in this.priority)
649
- if (this.priority[i].key == key && this.priority[i].name == plugin.name) {
650
  this.priority[i].class = p
651
  this.priority[i].priority = plugin.priority
652
  }
653
  })
654
  this.priority = lodash.orderBy(this.priority, ["priority"], ["asc"])
655
- } catch (error) {
656
- logger.error(`加载插件错误:${logger.red(key)}`)
657
- logger.error(decodeURI(error.stack))
658
  }
659
  }
660
 
@@ -669,18 +674,16 @@ class PluginsLoader {
669
 
670
  /** 监听修改 */
671
  watcher.on("change", path => {
672
- logger.mark(`[修改插件][${dirName}][${appName}]`)
673
  this.changePlugin(key)
674
  })
675
 
676
  /** 监听删除 */
677
  watcher.on("unlink", async path => {
678
- logger.mark(`[卸载插件][${dirName}][${appName}]`)
679
  /** 停止更新监听 */
680
  this.watcher[`${dirName}.${appName}`].removeAllListeners("change")
681
- for (const i in this.priority)
682
- if (this.priority[i].key == key)
683
- this.priority.splice(i, 1)
684
  })
685
  this.watcher[`${dirName}.${appName}`] = watcher
686
  }
@@ -695,7 +698,7 @@ class PluginsLoader {
695
  watcher.on("add", async PluPath => {
696
  const appName = path.basename(PluPath)
697
  if (!appName.endsWith(".js")) return
698
- logger.mark(`[新增插件][${dirName}][${appName}]`)
699
  const key = `${dirName}/${appName}`
700
  await this.importPlugin({
701
  name: key,
 
19
  * 加载插件
20
  */
21
  class PluginsLoader {
22
+ priority = []
23
+ handler = {}
24
+ task = []
25
+ dir = "plugins"
26
+
27
+ /** 命令冷却cd */
28
+ groupCD = {}
29
+ singleCD = {}
30
+
31
+ /** 插件监听 */
32
+ watcher = {}
33
+ eventMap = {
34
+ message: ["post_type", "message_type", "sub_type"],
35
+ notice: ["post_type", "notice_type", "sub_type"],
36
+ request: ["post_type", "request_type", "sub_type"],
37
+ }
 
38
 
39
+ msgThrottle = {}
40
 
41
+ /** 星铁命令前缀 */
42
+ srReg = /^#?(\*|星铁|星轨|穹轨|星穹|崩铁|星穹铁道|崩坏星穹铁道|铁道)+/
 
43
 
44
  async getPlugins() {
45
  const files = await fs.readdir(this.dir, { withFileTypes: true })
 
80
  if (isRefresh) this.priority = []
81
  if (this.priority.length) return
82
 
83
+ Bot.makeLog("info", "-----------", "Plugin")
84
+ Bot.makeLog("info", "加载插件中...", "Plugin")
85
 
86
  const files = await this.getPlugins()
87
  this.pluginCount = 0
 
94
  this.packageTips(packageErr)
95
  this.createTask()
96
 
97
+ Bot.makeLog("info", `加载定时任务[${this.task.length}个]`, "Plugin")
98
+ Bot.makeLog("info", `加载插件[${this.pluginCount}个]`, "Plugin")
99
 
100
  /** 优先级排序 */
101
  this.priority = lodash.orderBy(this.priority, ["priority"], ["asc"])
 
110
  pluginArray.push(this.loadPlugin(file, p))
111
  )
112
  for (const i of await Promise.allSettled(pluginArray))
113
+ if (i?.status && i.status !== "fulfilled") {
114
+ Bot.makeLog("error", [`插件加载错误 ${logger.red(file.name)}`, i], "Plugin")
 
115
  }
116
  } catch (error) {
117
  if (packageErr && error.stack.includes("Cannot find package")) {
118
  packageErr.push({ error, file })
119
  } else {
120
+ Bot.makeLog("error", [`插件加载错误 ${logger.red(file.name)}`, error], "Plugin")
 
121
  }
122
  }
123
  }
 
126
  if (!p?.prototype) return
127
  this.pluginCount++
128
  const plugin = new p
129
+ Bot.makeLog("debug", `加载插件 [${file.name}][${plugin.name}]`, "Plugin")
130
  /** 执行初始化,返回 return 则跳过加载 */
131
+ if (plugin.init && await plugin.init() === "return") return
132
  /** 初始化定时任务 */
133
+ this.collectTask(plugin.task, plugin.name)
134
  this.priority.push({
135
  class: p,
136
  key: file.name,
 
152
 
153
  packageTips(packageErr) {
154
  if (!packageErr.length) return
155
+ Bot.makeLog("error", "--------- 插件加载错误 ---------", "Plugin")
156
  for (const i of packageErr) {
157
  const pack = i.error.stack.match(/'(.+?)'/g)[0].replace(/'/g, "")
158
+ Bot.makeLog("error", `${logger.cyan(i.file.name)} 缺少依赖 ${logger.red(pack)}`, "Plugin")
159
  }
160
+ Bot.makeLog("error", `安装插件后请 ${logger.red("pnpm i")} 安装依赖`, "Plugin")
161
+ Bot.makeLog("error", `仍报错${logger.red("进入插件目录")} pnpm add 依赖`, "Plugin")
162
+ Bot.makeLog("error", "--------------------------------", "Plugin")
163
  }
164
 
165
  /**
 
198
  ...plugin.getContext(false, true),
199
  }
200
  if (!lodash.isEmpty(context)) {
201
+ let ret
202
  for (const fnc in context)
203
+ ret ||= await plugin[fnc](context[fnc])
204
+ if (ret === "continue") continue
205
  return
206
  }
207
  }
 
228
  for (const plugin of priority)
229
  if (plugin.accept) {
230
  const res = await plugin.accept(e)
231
+ if (res === "return") return
232
  if (res) break
233
  }
234
 
235
  a: for (const plugin of priority) {
 
236
  if (plugin.rule) for (const v of plugin.rule) {
237
  /** 判断事件 */
238
  if (v.event && !this.filtEvent(e, v)) continue
239
 
240
+ /** 正则匹配 */
241
  if (!new RegExp(v.reg).test(e.msg)) continue
242
+ e.logFnc = `${logger.blue(`[${plugin.name}(${v.fnc})]`)}`
243
 
244
+ Bot.makeLog(v.log === false ? "debug" : "info", `${e.logText}${e.logFnc}${logger.yellow("[开始处理]")}`, false)
 
245
 
246
  /** 判断权限 */
247
  if (!this.filtPermission(e, v)) break a
248
 
249
  try {
250
+ const start_time = Date.now()
251
  const res = plugin[v.fnc] && (await plugin[v.fnc](e))
252
  if (res !== false) {
253
  /** 设置冷却cd */
254
  this.setLimit(e)
255
+ Bot.makeLog(v.log === false ? "debug" : "mark", `${e.logText}${e.logFnc}${logger.green(`[完成${Bot.getTimeDiff(start_time)}]`)}`, false)
 
256
  break a
257
  }
258
+ } catch (err) {
259
+ Bot.makeLog("error", [`${e.logFnc}${e.logText}`, err], false)
 
260
  break a
261
  }
262
  }
 
270
  const eventMap = this.eventMap[e.post_type] || []
271
  const newEvent = []
272
  for (const i in event) {
273
+ if (event[i] === "*")
274
  newEvent.push(event[i])
275
  else
276
  newEvent.push(e[eventMap[i]])
277
  }
278
+ return v.event === newEvent.join(".")
279
  }
280
 
281
  /** 判断权限 */
282
  filtPermission(e, v) {
283
+ if (v.permission === "all" || !v.permission) return true
284
 
285
+ if (v.permission === "master") {
286
  if (e.isMaster) {
287
  return true
288
  } else {
 
292
  }
293
 
294
  if (e.isGroup) {
295
+ if (v.permission === "owner") {
296
  if (!e.member.is_owner) {
297
  e.reply("暂无权限,只有群主才能操作")
298
  return false
299
  }
300
  }
301
+ if (v.permission === "admin") {
302
  if (!e.member.is_admin) {
303
  e.reply("暂无权限,只有管理员才能操作")
304
  return false
 
344
  e.img = [i.url]
345
  break
346
  case "at":
347
+ if (i.qq === e.self_id)
348
  e.atBot = true
349
  else
350
  e.at = i.qq
 
361
  break
362
  case "xml":
363
  case "json":
364
+ e.msg = (e.msg || "") + (typeof i.data === "string" ? i.data : JSON.stringify(i.data))
365
  break
366
  }
367
  }
368
 
369
  e.logText = ""
370
 
371
+ if (e.message_type === "private" || e.notice_type === "friend") {
372
  e.isPrivate = true
373
 
374
  if (e.sender) {
 
382
  }
383
 
384
  e.logText = `[${e.sender?.nickname ? `${e.sender.nickname}(${e.user_id})` : e.user_id}]`
385
+ } else if (e.message_type === "group" || e.notice_type === "group") {
386
  e.isGroup = true
387
 
388
  if (e.sender) {
 
402
  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}]`
403
  }
404
 
405
+ e.logText = `${logger.cyan(e.logText)}${logger.red(`[${lodash.truncate(e.msg || e.raw_message || Bot.String(e), { length: 100 })}]`)}`
406
+
407
  if (e.user_id && cfg.master[e.self_id]?.includes(String(e.user_id))) {
408
  e.isMaster = true
409
  }
 
473
  res = await reply(msg)
474
  } catch (err) {
475
  Bot.makeLog("error", ["发送消息错误", msg, err], e.self_id)
476
+ res = { error: [err] }
477
  }
478
 
479
  if (recallMsg > 0 && res?.message_id) {
 
521
  }
522
 
523
  /** 收集定时任务 */
524
+ collectTask(task, name) {
525
  for (const i of Array.isArray(task) ? task : [task])
526
+ if (i.cron && i.fnc) {
527
+ i.name ??= name
528
  this.task.push(i)
529
+ }
530
  }
531
 
532
  /** 创建定时任务 */
533
  createTask() {
534
+ const created = []
535
+ for (const i of this.task) {
536
+ if (i.job?.cancel) i.job.cancel()
537
+ const name = `${logger.blue(`[${i.name}(${i.cron})]`)}`
538
+ if (created.includes(name)) {
539
+ Bot.makeLog("warn", `重复定时任务 ${name} 已跳过`, "Task")
540
+ continue
541
+ }
542
+ created.push(name)
543
+ Bot.makeLog("debug", `加载定时任务 ${name}`, "Task")
544
+ i.job = schedule.scheduleJob(i.cron, async () => { try {
545
+ const start_time = Date.now()
546
+ Bot.makeLog(i.log === false ? "debug" : "mark", `${name}${logger.yellow("[开始处理]")}`, false)
547
+ await i.fnc()
548
+ Bot.makeLog(i.log === false ? "debug" : "mark", `${name}${logger.green(`[完成${Bot.getTimeDiff(start_time)}]`)}`, false)
549
+ } catch (err) {
550
+ Bot.makeLog("error", [name, err], false)
551
+ }})
552
+ }
553
  }
554
 
555
  /** 检查命令冷却cd */
 
598
  let groupCfg = cfg.getGroup(e.self_id, e.group_id)
599
 
600
  /** 模式0,未开启前缀 */
601
+ if (groupCfg.onlyReplyAt === 0 || !groupCfg.botAlias) return true
602
 
603
  /** 模式2,非主人开启 */
604
+ if (groupCfg.onlyReplyAt === 2 && e.isMaster) return true
605
 
606
  /** at机器人 */
607
  if (e.atBot) return true
 
652
  lodash.forEach(app, p => {
653
  const plugin = new p
654
  for (const i in this.priority)
655
+ if (this.priority[i].key === key && this.priority[i].name === plugin.name) {
656
  this.priority[i].class = p
657
  this.priority[i].priority = plugin.priority
658
  }
659
  })
660
  this.priority = lodash.orderBy(this.priority, ["priority"], ["asc"])
661
+ } catch (err) {
662
+ Bot.makeLog("error", [`插件加载错误 ${logger.red(key)}`, err], "Plugin")
 
663
  }
664
  }
665
 
 
674
 
675
  /** 监听修改 */
676
  watcher.on("change", path => {
677
+ Bot.makeLog("mark", `[修改插件][${dirName}][${appName}]`, "Plugin")
678
  this.changePlugin(key)
679
  })
680
 
681
  /** 监听删除 */
682
  watcher.on("unlink", async path => {
683
+ Bot.makeLog("mark", `[卸载插件][${dirName}][${appName}]`, "Plugin")
684
  /** 停止更新监听 */
685
  this.watcher[`${dirName}.${appName}`].removeAllListeners("change")
686
+ this.priority = this.priority.filter(i => i.key !== key)
 
 
687
  })
688
  this.watcher[`${dirName}.${appName}`] = watcher
689
  }
 
698
  watcher.on("add", async PluPath => {
699
  const appName = path.basename(PluPath)
700
  if (!appName.endsWith(".js")) return
701
+ Bot.makeLog("mark", `[新增插件][${dirName}][${appName}]`, "Plugin")
702
  const key = `${dirName}/${appName}`
703
  await this.importPlugin({
704
  name: key,
Yunzai/lib/plugins/plugin.js CHANGED
@@ -1,9 +1,11 @@
1
  let Common
2
  try {
3
  Common = (await import("#miao")).Common
4
- } catch (err) {}
5
 
6
  const stateArr = {}
 
 
7
 
8
  export default class plugin {
9
  /**
@@ -34,7 +36,7 @@ export default class plugin {
34
  namespace,
35
  event = "message",
36
  priority = 5000,
37
- task = { fnc: "", cron: "" },
38
  rule = []
39
  }) {
40
  /** 插件名称 */
@@ -46,14 +48,7 @@ export default class plugin {
46
  /** 优先级 */
47
  this.priority = priority
48
  /** 定时任务,可以是数组 */
49
- this.task = {
50
- /** 任务名 */
51
- name: "",
52
- /** 任务方法名 */
53
- fnc: task.fnc || "",
54
- /** 任务cron表达式 */
55
- cron: task.cron || ""
56
- }
57
  /** 命令规则 */
58
  this.rule = rule
59
 
@@ -76,27 +71,30 @@ export default class plugin {
76
 
77
  conKey(isGroup = false) {
78
  if (isGroup) {
79
- return `${this.name}.${this.e.group_id}`
80
  } else {
81
- return `${this.name}.${this.userId || this.e.user_id}`
82
  }
83
  }
84
 
85
  /**
86
  * @param type 执行方法
87
  * @param isGroup 是否群聊
88
- * @param time 操作时间,默认120秒
 
89
  */
90
- setContext(type, isGroup, time = 120) {
91
  const key = this.conKey(isGroup)
92
  if (!stateArr[key]) stateArr[key] = {}
93
  stateArr[key][type] = this.e
94
- if (time) stateArr[key][type].timeout = setTimeout(() => {
95
  if (stateArr[key][type]) {
 
96
  delete stateArr[key][type]
97
- this.reply("操作超时已取消", true)
98
  }
99
  }, time * 1000)
 
100
  }
101
 
102
  getContext(type, isGroup) {
@@ -104,18 +102,23 @@ export default class plugin {
104
  return stateArr[this.conKey(isGroup)]
105
  }
106
 
107
- /**
108
- * @param type 执行方法
109
- * @param isGroup 是否群聊
110
- */
111
  finish(type, isGroup) {
112
  const key = this.conKey(isGroup)
113
- if (stateArr[key] && stateArr[key][type]) {
114
- clearTimeout(stateArr[key][type].timeout)
115
  delete stateArr[key][type]
116
  }
117
  }
118
 
 
 
 
 
 
 
 
 
 
119
  async renderImg(plugin, tpl, data, cfg) {
120
  return Common.render(plugin, tpl, data, { ...cfg, e: this.e })
121
  }
 
1
  let Common
2
  try {
3
  Common = (await import("#miao")).Common
4
+ } catch {}
5
 
6
  const stateArr = {}
7
+ const SymbolTimeout = Symbol("Timeout")
8
+ const SymbolResolve = Symbol("Resolve")
9
 
10
  export default class plugin {
11
  /**
 
36
  namespace,
37
  event = "message",
38
  priority = 5000,
39
+ task = { name: "", fnc: "", cron: "" },
40
  rule = []
41
  }) {
42
  /** 插件名称 */
 
48
  /** 优先级 */
49
  this.priority = priority
50
  /** 定时任务,可以是数组 */
51
+ this.task = task
 
 
 
 
 
 
 
52
  /** 命令规则 */
53
  this.rule = rule
54
 
 
71
 
72
  conKey(isGroup = false) {
73
  if (isGroup) {
74
+ return `${this.name}.${this.group_id || this.groupId || this.e.group_id}`
75
  } else {
76
+ return `${this.name}.${this.user_id || this.userId || this.e.user_id}`
77
  }
78
  }
79
 
80
  /**
81
  * @param type 执行方法
82
  * @param isGroup 是否群聊
83
+ * @param time 操作时间
84
+ * @param timeout 操作超时回复
85
  */
86
+ setContext(type, isGroup, time = 120, timeout = "操作超时已取消") {
87
  const key = this.conKey(isGroup)
88
  if (!stateArr[key]) stateArr[key] = {}
89
  stateArr[key][type] = this.e
90
+ if (time) stateArr[key][type][SymbolTimeout] = setTimeout(() => {
91
  if (stateArr[key][type]) {
92
+ const resolve = stateArr[key][type][SymbolResolve]
93
  delete stateArr[key][type]
94
+ resolve ? resolve(false) : this.reply(timeout, true)
95
  }
96
  }, time * 1000)
97
+ return stateArr[key][type]
98
  }
99
 
100
  getContext(type, isGroup) {
 
102
  return stateArr[this.conKey(isGroup)]
103
  }
104
 
 
 
 
 
105
  finish(type, isGroup) {
106
  const key = this.conKey(isGroup)
107
+ if (stateArr[key]?.[type]) {
108
+ clearTimeout(stateArr[key][type][SymbolTimeout])
109
  delete stateArr[key][type]
110
  }
111
  }
112
 
113
+ awaitContext(...args) {
114
+ return new Promise(resolve => this.setContext("resolveContext", ...args)[SymbolResolve] = resolve)
115
+ }
116
+
117
+ resolveContext(context) {
118
+ this.finish("resolveContext")
119
+ context[SymbolResolve](this.e)
120
+ }
121
+
122
  async renderImg(plugin, tpl, data, cfg) {
123
  return Common.render(plugin, tpl, data, { ...cfg, e: this.e })
124
  }
Yunzai/lib/plugins/runtime.js CHANGED
@@ -18,8 +18,10 @@ try {
18
  MysInfo = (await import("../../plugins/genshin/model/mys/mysInfo.js")).default
19
  NoteUser = (await import("../../plugins/genshin/model/mys/NoteUser.js")).default
20
  MysUser = (await import("../../plugins/genshin/model/mys/MysUser.js")).default
 
 
21
  Version = (await import("#miao")).Version
22
- } catch (err) {}
23
 
24
  /**
25
  * 常用的处理方法
@@ -77,13 +79,6 @@ export default class Runtime {
77
  return MysUser
78
  }
79
 
80
- static async init(e) {
81
- if (MysInfo) await MysInfo.initCache()
82
- e.runtime = new Runtime(e)
83
- if (NoteUser) await e.runtime.initUser()
84
- return e.runtime
85
- }
86
-
87
  async initUser() {
88
  let e = this.e
89
  let user = await NoteUser.create(e)
@@ -141,12 +136,13 @@ export default class Runtime {
141
  *
142
  * @param targetType all: 所有用户均可, cookie:查询用户必须具备Cookie
143
  * @param option MysApi option
 
144
  * @returns {Promise<boolean|MysApi>}
145
  */
146
- async getMysApi(targetType = "all", option = {}) {
147
  let mys = await this.getMysInfo(targetType)
148
  if (mys.uid && mys?.ckInfo?.ck) {
149
- return new MysApi(mys.uid, mys.ckInfo.ck, option)
150
  }
151
  return false
152
  }
@@ -156,10 +152,11 @@ export default class Runtime {
156
  * @param uid
157
  * @param ck
158
  * @param option
 
159
  * @returns {Promise<MysApi>}
160
  */
161
- async createMysApi(uid, ck, option) {
162
- return new MysApi(uid, ck, option)
163
  }
164
 
165
  /**
@@ -192,7 +189,7 @@ export default class Runtime {
192
  scale: 1
193
  },
194
  /** miao 相关参数 **/
195
- copyright: `Created By TRSS-Yunzai<span class="version">${Version.yunzai}</span> `,
196
  _res_path: pluResPath,
197
  _miao_path: miaoResPath,
198
  _tpl_path: process.cwd() + "/plugins/miao-plugin/resources/common/tpl/",
@@ -206,10 +203,7 @@ export default class Runtime {
206
  _htmlPath: path,
207
  pluResPath,
208
  tplFile: `./plugins/${plugin}/resources/${path}.html`,
209
- saveId: data.saveId || data.save_id || paths[paths.length - 1],
210
- pageGotoParams: {
211
- waitUntil: "networkidle2"
212
- }
213
  }
214
  // 处理beforeRender
215
  if (cfg.beforeRender) {
@@ -238,4 +232,14 @@ export default class Runtime {
238
  }
239
  return cfg.retType === "msgId" ? ret : true
240
  }
 
 
 
 
 
 
 
241
  }
 
 
 
 
18
  MysInfo = (await import("../../plugins/genshin/model/mys/mysInfo.js")).default
19
  NoteUser = (await import("../../plugins/genshin/model/mys/NoteUser.js")).default
20
  MysUser = (await import("../../plugins/genshin/model/mys/MysUser.js")).default
21
+ } catch {}
22
+ try {
23
  Version = (await import("#miao")).Version
24
+ } catch {}
25
 
26
  /**
27
  * 常用的处理方法
 
79
  return MysUser
80
  }
81
 
 
 
 
 
 
 
 
82
  async initUser() {
83
  let e = this.e
84
  let user = await NoteUser.create(e)
 
136
  *
137
  * @param targetType all: 所有用户均可, cookie:查询用户必须具备Cookie
138
  * @param option MysApi option
139
+ * @param isSr 是否为星穹铁道
140
  * @returns {Promise<boolean|MysApi>}
141
  */
142
+ async getMysApi(targetType = "all", option = {}, isSr = false) {
143
  let mys = await this.getMysInfo(targetType)
144
  if (mys.uid && mys?.ckInfo?.ck) {
145
+ return new MysApi(mys.uid, mys.ckInfo.ck, option, isSr)
146
  }
147
  return false
148
  }
 
152
  * @param uid
153
  * @param ck
154
  * @param option
155
+ * @param isSr 是否为星穹铁道
156
  * @returns {Promise<MysApi>}
157
  */
158
+ async createMysApi(uid, ck, option, isSr = false) {
159
+ return new MysApi(uid, ck, option, isSr)
160
  }
161
 
162
  /**
 
189
  scale: 1
190
  },
191
  /** miao 相关参数 **/
192
+ copyright: `Created By TRSS-Yunzai<span class="version">${Version?.yunzai}</span> `,
193
  _res_path: pluResPath,
194
  _miao_path: miaoResPath,
195
  _tpl_path: process.cwd() + "/plugins/miao-plugin/resources/common/tpl/",
 
203
  _htmlPath: path,
204
  pluResPath,
205
  tplFile: `./plugins/${plugin}/resources/${path}.html`,
206
+ saveId: data.saveId || data.save_id || paths[paths.length - 1]
 
 
 
207
  }
208
  // 处理beforeRender
209
  if (cfg.beforeRender) {
 
232
  }
233
  return cfg.retType === "msgId" ? ret : true
234
  }
235
+
236
+ static async init(e) {
237
+ await MysInfo.initCache()
238
+ e.runtime = new Runtime(e)
239
+ await e.runtime.initUser()
240
+ return e.runtime
241
+ }
242
  }
243
+
244
+ if (!MysInfo || !NoteUser)
245
+ Runtime.init = async e => e.runtime = new Runtime(e)
Yunzai/lib/plugins/stdin.js CHANGED
@@ -1,11 +1,18 @@
 
1
  import fs from "node:fs/promises"
2
  import path from "node:path"
 
3
 
4
  Bot.adapter.push(new class stdinAdapter {
5
  constructor() {
6
  this.id = "stdin"
7
  this.name = "标准输入"
8
  this.path = "data/stdin/"
 
 
 
 
 
9
  }
10
 
11
  async sendMsg(msg) {
@@ -19,7 +26,7 @@ Bot.adapter.push(new class stdinAdapter {
19
  if (i.file) {
20
  file = await Bot.fileType(i)
21
  if (Buffer.isBuffer(file.buffer)) {
22
- file.path = `${this.path}${file.name || Date.now()}`
23
  await fs.writeFile(file.path, file.buffer)
24
  }
25
  }
@@ -27,17 +34,18 @@ Bot.adapter.push(new class stdinAdapter {
27
  switch (i.type) {
28
  case "text":
29
  if (i.text.match("\n"))
30
- i.text = `\n${i.text}`
31
- logger.info(`${logger.blue(`[${this.id}]`)} 发送文本:${i.text}`)
32
  break
33
  case "image":
34
- logger.info(`${logger.blue(`[${this.id}]`)} 发送图片:${file.url}\n文件已保存到:${logger.cyan(file.path)}`)
 
35
  break
36
  case "record":
37
- logger.info(`${logger.blue(`[${this.id}]`)} 发送音频:${file.url}\n文件已保存到:${logger.cyan(file.path)}`)
38
  break
39
  case "video":
40
- logger.info(`${logger.blue(`[${this.id}]`)} 发送视频:${file.url}\n文件已保存到:${logger.cyan(file.path)}`)
41
  break
42
  case "reply":
43
  break
@@ -47,44 +55,39 @@ Bot.adapter.push(new class stdinAdapter {
47
  Bot.sendForwardMsg(msg => this.sendMsg(msg), i.data)
48
  break
49
  default:
50
- i = JSON.stringify(i)
51
- if (i.match("\n"))
52
- i = `\n${i}`
53
- logger.info(`${logger.blue(`[${this.id}]`)} 发送消息:${i}`)
54
  }
55
  }
56
- return { message_id: Date.now() }
57
  }
58
 
59
  recallMsg(message_id) {
60
- logger.info(`${logger.blue(`[${this.id}]`)} 撤回消息:${message_id}`)
61
  }
62
 
63
  async sendFile(file, name = path.basename(file)) {
64
  const buffer = await Bot.Buffer(file)
65
  if (!Buffer.isBuffer(buffer)) {
66
- logger.error(`${logger.blue(`[${this.id}]`)} 发送文件错误:找不到文件 ${logger.red(file)}`)
67
  return false
68
  }
69
 
70
- const files = `${this.path}${Date.now()}-${name}`
71
- logger.info(`${logger.blue(`[${this.id}]`)} 发送文件:${file}\n文件已保存到:${logger.cyan(files)}`)
72
  return fs.writeFile(files, buffer)
73
  }
74
 
75
  pickFriend() {
76
  return {
77
- user_id: this.id,
78
- nickname: this.name,
79
- group_id: this.id,
80
- group_name: this.name,
81
  sendMsg: msg => this.sendMsg(msg),
82
  recallMsg: message_id => this.recallMsg(message_id),
83
  sendFile: (file, name) => this.sendFile(file, name),
 
84
  }
85
  }
86
 
87
  message(msg) {
 
88
  const data = {
89
  bot: Bot[this.id],
90
  self_id: this.id,
@@ -94,20 +97,27 @@ Bot.adapter.push(new class stdinAdapter {
94
  sender: { user_id: this.id, nickname: this.name },
95
  message: [{ type: "text", text: msg }],
96
  raw_message: msg,
97
- friend: this.pickFriend(),
98
  }
99
- logger.info(`${logger.blue(`[${data.self_id}]`)} 系统消息:[${data.sender.nickname}(${data.user_id})] ${data.raw_message}`)
100
-
101
  Bot.em(`${data.post_type}.${data.message_type}`, data)
102
  }
103
 
104
- async load() {
 
 
105
  await Bot.mkdir(this.path)
106
  Bot[this.id] = {
107
  adapter: this,
 
 
 
 
 
 
108
  uin: this.id,
109
  nickname: this.name,
110
  version: { id: this.id, name: this.name },
 
111
  pickFriend: () => this.pickFriend(),
112
  get stat() { return Bot.stat },
113
  get pickUser() { return this.pickFriend },
@@ -125,9 +135,13 @@ Bot.adapter.push(new class stdinAdapter {
125
  }
126
  Bot[this.id].gml.set(this.id, Bot[this.id].fl)
127
 
128
- process[this.id].on("data", data => this.message(data.toString()))
 
 
 
 
129
 
130
- logger.mark(`${logger.blue(`[${this.id}]`)} ${this.name}(${this.id}) 已连接`)
131
  Bot.em(`connect.${this.id}`, { self_id: this.id })
132
  }
133
  })
 
1
+ import readline from "node:readline/promises"
2
  import fs from "node:fs/promises"
3
  import path from "node:path"
4
+ import { spawn } from "node:child_process"
5
 
6
  Bot.adapter.push(new class stdinAdapter {
7
  constructor() {
8
  this.id = "stdin"
9
  this.name = "标准输入"
10
  this.path = "data/stdin/"
11
+ this.catimg = file => new Promise(resolve =>
12
+ spawn("catimg", ["-l0", file], { stdio: "inherit" })
13
+ .on("error", () => this.catimg = () => {})
14
+ .on("close", resolve)
15
+ )
16
  }
17
 
18
  async sendMsg(msg) {
 
26
  if (i.file) {
27
  file = await Bot.fileType(i)
28
  if (Buffer.isBuffer(file.buffer)) {
29
+ file.path = `${this.path}${file.name}`
30
  await fs.writeFile(file.path, file.buffer)
31
  }
32
  }
 
34
  switch (i.type) {
35
  case "text":
36
  if (i.text.match("\n"))
37
+ i.text = `发送文本:\n${i.text}`
38
+ Bot.makeLog("info", i.text, this.id)
39
  break
40
  case "image":
41
+ await this.catimg(file.path)
42
+ Bot.makeLog("info", `发送图片:${file.url}\n文件已保存到:${logger.cyan(file.path)}`, this.id)
43
  break
44
  case "record":
45
+ Bot.makeLog("info", `发送音频:${file.url}\n文件已保存到:${logger.cyan(file.path)}`, this.id)
46
  break
47
  case "video":
48
+ Bot.makeLog("info", `发送视频:${file.url}\n文件已保存到:${logger.cyan(file.path)}`, this.id)
49
  break
50
  case "reply":
51
  break
 
55
  Bot.sendForwardMsg(msg => this.sendMsg(msg), i.data)
56
  break
57
  default:
58
+ Bot.makeLog("info", i, this.id)
 
 
 
59
  }
60
  }
61
+ return { message_id: Date.now().toString(36) }
62
  }
63
 
64
  recallMsg(message_id) {
65
+ Bot.makeLog("info", `撤回消息:${message_id}`, this.id)
66
  }
67
 
68
  async sendFile(file, name = path.basename(file)) {
69
  const buffer = await Bot.Buffer(file)
70
  if (!Buffer.isBuffer(buffer)) {
71
+ Bot.makeLog("error", `发送文件错误:找不到文件 ${logger.red(file)}`, this.id)
72
  return false
73
  }
74
 
75
+ const files = `${this.path}${Date.now().toString(36)}-${name}`
76
+ Bot.makeLog("info", `发送文件:${file}\n文件已保存到:${logger.cyan(files)}`, this.id)
77
  return fs.writeFile(files, buffer)
78
  }
79
 
80
  pickFriend() {
81
  return {
 
 
 
 
82
  sendMsg: msg => this.sendMsg(msg),
83
  recallMsg: message_id => this.recallMsg(message_id),
84
  sendFile: (file, name) => this.sendFile(file, name),
85
+ pickMember: function() { return this },
86
  }
87
  }
88
 
89
  message(msg) {
90
+ fs.appendFile(`${this.path}history`, `${Date.now().toString(36)}:${msg}\n`, "utf8")
91
  const data = {
92
  bot: Bot[this.id],
93
  self_id: this.id,
 
97
  sender: { user_id: this.id, nickname: this.name },
98
  message: [{ type: "text", text: msg }],
99
  raw_message: msg,
 
100
  }
101
+ Bot.makeLog("info", `系统消息:${data.raw_message}`, this.id)
 
102
  Bot.em(`${data.post_type}.${data.message_type}`, data)
103
  }
104
 
105
+ async load(force) {
106
+ if (!(process.stdin.isTTY || process.env.FORCE_TTY || force)) return
107
+
108
  await Bot.mkdir(this.path)
109
  Bot[this.id] = {
110
  adapter: this,
111
+ sdk: readline.createInterface({
112
+ input: process.stdin,
113
+ output: process.stderr,
114
+ }).on("line", data => this.message(String(data)))
115
+ .on("close", () => process.exit(1)),
116
+
117
  uin: this.id,
118
  nickname: this.name,
119
  version: { id: this.id, name: this.name },
120
+
121
  pickFriend: () => this.pickFriend(),
122
  get stat() { return Bot.stat },
123
  get pickUser() { return this.pickFriend },
 
135
  }
136
  Bot[this.id].gml.set(this.id, Bot[this.id].fl)
137
 
138
+ try {
139
+ Bot[this.id].sdk.history = (await fs.readFile(`${this.path}history`, "utf8")).split("\n").slice(-Bot[this.id].sdk.historySize-1, -1).map(i => i.replace(/^[0-9a-z]+?:/, "")).reverse()
140
+ } catch (err) {
141
+ Bot.makeLog("trace", err, this.id)
142
+ }
143
 
144
+ Bot.makeLog("mark", `${this.name}(${this.id}) 已连接`, this.id)
145
  Bot.em(`connect.${this.id}`, { self_id: this.id })
146
  }
147
  })
Yunzai/lib/puppeteer/puppeteer.js CHANGED
@@ -1,23 +1,21 @@
1
- import Renderer from '../renderer/loader.js'
2
 
3
  /**
4
  * 暂时保留对手工引用puppeteer.js的兼容
5
  * 后期会逐步废弃
6
  * 只提供截图及分片截图功能
7
  */
8
- let renderer = Renderer.getRenderer()
9
  renderer.screenshot = async (name, data) => {
10
- let img = await renderer.render(name, data)
11
- return img ? segment.image(img) : img
12
  }
13
  renderer.screenshots = async (name, data) => {
14
- data.multiPage = true
15
- let imgs = await renderer.render(name, data) || []
16
- let ret = []
17
- for (let img of imgs) {
18
- ret.push(img ? segment.image(img) : img)
19
- }
20
- return ret.length > 0 ? ret : false
21
  }
22
-
23
  export default renderer
 
1
+ import Renderer from "../renderer/loader.js"
2
 
3
  /**
4
  * 暂时保留对手工引用puppeteer.js的兼容
5
  * 后期会逐步废弃
6
  * 只提供截图及分片截图功能
7
  */
8
+ const renderer = Renderer.getRenderer()
9
  renderer.screenshot = async (name, data) => {
10
+ const img = await renderer.render(name, data)
11
+ return img ? segment.image(img) : img
12
  }
13
  renderer.screenshots = async (name, data) => {
14
+ data.multiPage = true
15
+ const imgs = await renderer.render(name, data) || []
16
+ const ret = []
17
+ for (const img of imgs)
18
+ ret.push(img ? segment.image(img) : img)
19
+ return ret.length > 0 ? ret : false
 
20
  }
 
21
  export default renderer
Yunzai/lib/renderer/loader.js CHANGED
@@ -4,11 +4,6 @@ import lodash from "lodash"
4
  import cfg from "../config/config.js"
5
  import Renderer from "./Renderer.js"
6
 
7
- let Data
8
- try {
9
- Data = (await import("#miao")).Data
10
- } catch (err) {}
11
-
12
  /** 全局变量 Renderer */
13
  global.Renderer = Renderer
14
 
@@ -18,7 +13,7 @@ global.Renderer = Renderer
18
  class RendererLoader {
19
  constructor() {
20
  this.renderers = new Map()
21
- this.dir = "./renderers"
22
  // TODO 渲染器热加载
23
  this.watcher = {}
24
  }
@@ -30,15 +25,14 @@ class RendererLoader {
30
  }
31
 
32
  async load() {
33
- if (!Data) return
34
  const subFolders = fs.readdirSync(this.dir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory())
35
- for (let subFolder of subFolders) {
36
- let name = subFolder.name
37
  try {
38
- const rendererFn = await Data.importDefault(`${this.dir}/${name}/index.js`)
39
- let configFile = `${this.dir}/${name}/config.yaml`
40
- let rendererCfg = fs.existsSync(configFile) ? yaml.parse(fs.readFileSync(configFile, "utf8")) : {}
41
- let renderer = rendererFn(rendererCfg)
42
  if (!renderer.id || !renderer.type || !renderer.render || !lodash.isFunction(renderer.render)) {
43
  logger.warn("渲染后端 " + (renderer.id || subFolder.name) + " 不可用")
44
  }
@@ -57,5 +51,4 @@ class RendererLoader {
57
  }
58
  }
59
 
60
-
61
  export default await RendererLoader.init()
 
4
  import cfg from "../config/config.js"
5
  import Renderer from "./Renderer.js"
6
 
 
 
 
 
 
7
  /** 全局变量 Renderer */
8
  global.Renderer = Renderer
9
 
 
13
  class RendererLoader {
14
  constructor() {
15
  this.renderers = new Map()
16
+ this.dir = "renderers"
17
  // TODO 渲染器热加载
18
  this.watcher = {}
19
  }
 
25
  }
26
 
27
  async load() {
 
28
  const subFolders = fs.readdirSync(this.dir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory())
29
+ for (const subFolder of subFolders) {
30
+ const name = subFolder.name
31
  try {
32
+ const rendererFn = (await import(`../../${this.dir}/${name}/index.js`)).default
33
+ const configFile = `${this.dir}/${name}/config.yaml`
34
+ const rendererCfg = fs.existsSync(configFile) ? yaml.parse(fs.readFileSync(configFile, "utf8")) : {}
35
+ const renderer = rendererFn(rendererCfg)
36
  if (!renderer.id || !renderer.type || !renderer.render || !lodash.isFunction(renderer.render)) {
37
  logger.warn("渲染后端 " + (renderer.id || subFolder.name) + " 不可用")
38
  }
 
51
  }
52
  }
53
 
 
54
  export default await RendererLoader.init()
Yunzai/lib/tools/docker.sh ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #TRSS Yunzai Docker 安装脚本 作者:时雨🌌星空
2
+ NAME=v1.0.0;VERSION=202405290
3
+ R="" G="" Y="" C="" B="" O=""
4
+ echo "$B———————————————————————————
5
+ $R TRSS$Y Yunzai$G Docker$C Script$O
6
+ $G$NAME$C ($VERSION)$O
7
+ $B———————————————————————————
8
+ $G作者:$C时雨🌌星空$O
9
+
10
+ $Y- 正在检查环境$O
11
+ "
12
+ DIR="${DIR:-$HOME/Yunzai}"
13
+ CMD="${CMD:-tsyz}"
14
+ CMDPATH="${CMDPATH:-/usr/local/bin}"
15
+ DKNAME="${DKNAME:-Yunzai}"
16
+ DKURL="${DKURL:-docker.nju.edu.cn}"
17
+ GITURL="${GITURL:-https://gitee.com/TimeRainStarSky/Yunzai}"
18
+ APTURL="${APTURL:-mirrors.ustc.edu.cn}"
19
+ APTDEP="${APTDEP:-chromium fonts-lxgw-wenkai fonts-noto-color-emoji}"
20
+ NPMURL="${NPMURL:-https://registry.npmmirror.com}"
21
+ abort(){ echo "
22
+ $R! $@$O";exit 1;}
23
+ mktmp(){ TMP="$DIR/tmp"&&rm -rf "$TMP"&&mkdir -p "$TMP"||abort "缓存目录创建失败";}
24
+ if type docker;then
25
+ echo "
26
+ $G- Docker 已安装$O
27
+ "
28
+ elif type pacman &>/dev/null;then
29
+ echo "
30
+ $Y- 正在使用 pacman 安装 Docker$O
31
+ "
32
+ pacman -Syu --noconfirm --needed --overwrite "*" docker||abort "Docker 安装失败"
33
+ elif type apt &>/dev/null;then
34
+ echo "
35
+ $Y- 正在使用 apt 安装 Docker$O
36
+ "
37
+ apt update&&apt install -y docker.io||abort "Docker 安装失败"
38
+ else
39
+ echo "
40
+ $Y- 正在使用 官方脚本 安装 Docker$O
41
+ "
42
+ DOWNLOAD_URL="https://$APTURL/docker-ce" bash <(curl -L get.docker.com)||abort "官方脚本 执行失败,请自行安装 Docker 后重试:https://docker.com"
43
+ fi
44
+ docker info||{ systemctl enable --now docker||service docker start&&docker info;}&&echo "
45
+ $G- Docker 已启动$O"||abort "Docker 启动失败"
46
+ N=1
47
+ until echo "
48
+ $Y- 正在从 $C$DKURL$Y 下载 Docker 容器$O
49
+ "
50
+ docker pull "$DKURL/library/node:slim";do
51
+ echo "
52
+ $R! 下载失败,5秒后切换镜像源$O"
53
+ sleep 5
54
+ ((N++))
55
+ case "$N" in
56
+ 1)DKURL="docker.nju.edu.cn";;
57
+ 2)DKURL="mirror.ccs.tencentyun.com";;
58
+ 3)DKURL="mirror.baidubce.com";;
59
+ 4)DKURL="dockerproxy.com";;
60
+ 5)DKURL="docker.m.daocloud.io";;
61
+ *)DKURL="docker.io";N=0
62
+ esac
63
+ done
64
+ echo "
65
+ $Y- 正在构建 Docker 容器$O
66
+ "
67
+ mktmp
68
+ cd "$TMP"
69
+ echo "FROM $DKURL"'/library/node:slim
70
+ RUN sed -i "s|deb.debian.org|'"$APTURL"'|g" /etc/apt/sources.list.d/debian.sources\
71
+ && apt update\
72
+ && apt install -y ca-certificates\
73
+ && sed -i "s|http://'"$APTURL"'|https://'"$APTURL"'|g" /etc/apt/sources.list.d/debian.sources\
74
+ && apt update\
75
+ && apt full-upgrade -y\
76
+ && apt install -y curl git redis-server '"$APTDEP"'\
77
+ && apt autoremove --purge\
78
+ && apt clean\
79
+ && git config --global --add safe.directory "*"\
80
+ && npm install -g pnpm --registry "'"$NPMURL"'"\
81
+ && rm -rf /var/cache/* /var/log/* /var/lib/apt /root/.npm\
82
+ && echo -n "[ -s .git ]||git clone --depth 1 --single-branch \"'"$GITURL"'\" .&&pnpm install --force&&echo -n \"exec node . start\">/start&&exec node . start">/start
83
+ HEALTHCHECK CMD curl -s http://localhost:2536/status||exit 1
84
+ WORKDIR /root/Yunzai
85
+ ENTRYPOINT []
86
+ CMD ["sh","/start"]
87
+ EXPOSE 2536'>Dockerfile
88
+ docker build -t trss:yunzai .||abort "Docker 容器构建失败"
89
+ echo "
90
+ $Y- 正在启动 Docker 容器$O
91
+ "
92
+ docker rm -f $DKNAME 2>/dev/null
93
+ docker image prune -f
94
+ docker run -itd -h Yunzai --name $DKNAME -v "$DIR":/root/Yunzai --restart always $([ $DKNAME = Yunzai ]&&echo "-p2536:2536"||echo "-P") trss:yunzai||abort "Docker 容器启动失败"
95
+ mkdir -vp "$CMDPATH"&&
96
+ echo -n 'if [ -n "$1" ];then case "$1" in
97
+ s|start)exec docker start '$DKNAME';;
98
+ st|stop)exec docker stop '$DKNAME';;
99
+ rs|restart)exec docker restart '$DKNAME';;
100
+ l|log)exec docker logs -fn"${2:-100}" '$DKNAME';;
101
+ *)exec docker exec -it '$DKNAME' "$@";;
102
+ esac;else
103
+ docker logs -n100 '$DKNAME'
104
+ exec docker attach '$DKNAME'
105
+ fi'>"$CMDPATH/$CMD"&&
106
+ chmod 755 "$CMDPATH/$CMD"||abort "脚本执行命令 $CMDPATH/$CMD 设置失败,手动执行命令:docker attach $DKNAME"
107
+ echo "
108
+ $G- Docker 容器安装完成,启动命令:$C$CMD$O"
109
+ rm -rf "$TMP"
Yunzai/package.json CHANGED
@@ -18,7 +18,7 @@
18
  "art-template": "^4.13.2",
19
  "chalk": "^5.3.0",
20
  "chokidar": "^3.6.0",
21
- "express": "^4.19.1",
22
  "file-type": "^19.0.0",
23
  "https-proxy-agent": "7.0.4",
24
  "image-size": "^1.1.1",
@@ -30,20 +30,22 @@
30
  "node-fetch": "link:lib/modules/node-fetch",
31
  "node-schedule": "^2.1.1",
32
  "oicq": "link:lib/modules/oicq",
33
- "pm2": "^5.3.1",
34
  "puppeteer": "*",
35
- "redis": "^4.6.13",
36
- "sequelize": "^6.37.1",
37
  "sqlite3": "5.1.6",
38
- "ws": "^8.16.0",
39
- "yaml": "^2.4.1"
 
 
40
  },
41
  "devDependencies": {
42
  "eslint": "^8.57.0",
43
  "eslint-config-standard": "^17.1.0",
44
  "eslint-plugin-import": "^2.29.1",
45
  "eslint-plugin-n": "^16.6.2",
46
- "eslint-plugin-promise": "^6.1.1"
47
  },
48
  "imports": {
49
  "#miao": "./plugins/miao-plugin/components/index.js",
 
18
  "art-template": "^4.13.2",
19
  "chalk": "^5.3.0",
20
  "chokidar": "^3.6.0",
21
+ "express": "^4.19.2",
22
  "file-type": "^19.0.0",
23
  "https-proxy-agent": "7.0.4",
24
  "image-size": "^1.1.1",
 
30
  "node-fetch": "link:lib/modules/node-fetch",
31
  "node-schedule": "^2.1.1",
32
  "oicq": "link:lib/modules/oicq",
33
+ "pm2": "^5.4.0",
34
  "puppeteer": "*",
35
+ "redis": "^4.6.14",
36
+ "sequelize": "^6.37.3",
37
  "sqlite3": "5.1.6",
38
+ "strip-ansi": "^7.1.0",
39
+ "ulid": "^2.3.0",
40
+ "ws": "^8.17.0",
41
+ "yaml": "^2.4.3"
42
  },
43
  "devDependencies": {
44
  "eslint": "^8.57.0",
45
  "eslint-config-standard": "^17.1.0",
46
  "eslint-plugin-import": "^2.29.1",
47
  "eslint-plugin-n": "^16.6.2",
48
+ "eslint-plugin-promise": "^6.2.0"
49
  },
50
  "imports": {
51
  "#miao": "./plugins/miao-plugin/components/index.js",
Yunzai/plugins/.gitignore CHANGED
@@ -6,6 +6,7 @@
6
  !system/**
7
  !other
8
  !other/**
 
9
  !example/一言.js
10
  !example/主动复读.js
11
  !example/进群退群通知.js
 
6
  !system/**
7
  !other
8
  !other/**
9
+ !example/package.json
10
  !example/一言.js
11
  !example/主动复读.js
12
  !example/进群退群通知.js
Yunzai/plugins/adapter/ComWeChat.js CHANGED
@@ -1,23 +1,34 @@
1
- import { randomUUID } from "node:crypto"
2
  import path from "node:path"
 
3
 
4
  Bot.adapter.push(new class ComWeChatAdapter {
5
  constructor() {
6
  this.id = "WeChat"
7
  this.name = "ComWeChat"
8
  this.path = this.name
 
 
9
  }
10
 
11
  makeLog(msg) {
12
  return Bot.String(msg).replace(/(base64:\/\/|"type":"data","data":").*?"/g, '$1..."')
13
  }
14
 
15
- sendApi(ws, action, params = {}) {
16
- const echo = randomUUID()
17
- ws.sendMsg({ action, params, echo })
18
- return new Promise(resolve => Bot.once(echo, data =>
19
- resolve({ ...data, ...data.data })
20
- ))
 
 
 
 
 
 
 
 
 
21
  }
22
 
23
  async uploadFile(data, file) {
@@ -44,7 +55,7 @@ Bot.adapter.push(new class ComWeChatAdapter {
44
  msg = [msg]
45
  const msgs = []
46
  for (let i of msg) {
47
- if (typeof i != "object")
48
  i = { type: "text", data: { text: i }}
49
  else if (!i.data)
50
  i = { type: i.type, data: { ...i, type: undefined }}
@@ -63,7 +74,7 @@ Bot.adapter.push(new class ComWeChatAdapter {
63
  i.type = "file"
64
  break
65
  case "at":
66
- if (i.data.qq == "all")
67
  i = { type: "mention_all", data: {}}
68
  else
69
  i = { type: "mention", data: { user_id: i.data.qq }}
@@ -74,8 +85,11 @@ Bot.adapter.push(new class ComWeChatAdapter {
74
  case "node":
75
  await Bot.sendForwardMsg(send, i.data)
76
  continue
 
 
 
77
  default:
78
- i = { type: "text", data: { text: JSON.stringify(i) }}
79
  }
80
  msgs.push(i)
81
  }
@@ -107,7 +121,7 @@ Bot.adapter.push(new class ComWeChatAdapter {
107
  for (const i of (await data.bot.sendApi("get_friend_list")).data)
108
  array.push({
109
  ...i,
110
- nickname: i.user_remark == "null" ? i.user_displayname || i.user_name : i.user_remark,
111
  })
112
  return array
113
  }
@@ -237,7 +251,7 @@ Bot.adapter.push(new class ComWeChatAdapter {
237
  Bot[data.self_id] = {
238
  adapter: this,
239
  ws: ws,
240
- sendApi: (action, params) => this.sendApi(ws, action, params),
241
  stat: { ...data.status, start_time: data.time },
242
 
243
  info: {},
@@ -265,9 +279,9 @@ Bot.adapter.push(new class ComWeChatAdapter {
265
  if (!Bot.uin.includes(data.self_id))
266
  Bot.uin.push(data.self_id)
267
 
268
- data.bot.info = (await data.bot.sendApi("get_self_info")).data
269
  data.bot.version = {
270
- ...(await data.bot.sendApi("get_version")).data,
271
  id: this.id,
272
  name: this.name,
273
  }
@@ -428,7 +442,7 @@ Bot.adapter.push(new class ComWeChatAdapter {
428
  }
429
 
430
  if (data.type) {
431
- if (data.type != "meta" && !Bot.uin.includes(data.self_id)) {
432
  Bot.makeLog("warn", `找不到对应Bot,忽略消息:${logger.magenta(data.raw)}`, data.self_id)
433
  return false
434
  }
@@ -450,8 +464,17 @@ Bot.adapter.push(new class ComWeChatAdapter {
450
  default:
451
  Bot.makeLog("warn", `未知消息:${logger.magenta(data.raw)}`, data.self_id)
452
  }
453
- } else if (data.echo) {
454
- Bot.emit(data.echo, data)
 
 
 
 
 
 
 
 
 
455
  } else {
456
  Bot.makeLog("warn", `未知消息:${logger.magenta(data.raw)}`, data.self_id)
457
  }
 
 
1
  import path from "node:path"
2
+ import { ulid } from "ulid"
3
 
4
  Bot.adapter.push(new class ComWeChatAdapter {
5
  constructor() {
6
  this.id = "WeChat"
7
  this.name = "ComWeChat"
8
  this.path = this.name
9
+ this.echo = {}
10
+ this.timeout = 60000
11
  }
12
 
13
  makeLog(msg) {
14
  return Bot.String(msg).replace(/(base64:\/\/|"type":"data","data":").*?"/g, '$1..."')
15
  }
16
 
17
+ sendApi(data, ws, action, params = {}) {
18
+ const echo = ulid()
19
+ const request = { action, params, echo }
20
+ ws.sendMsg(request)
21
+ return new Promise((resolve, reject) =>
22
+ this.echo[echo] = {
23
+ request, resolve, reject,
24
+ timeout: setTimeout(() => {
25
+ reject(Object.assign(request, { timeout: this.timeout }))
26
+ delete this.echo[echo]
27
+ Bot.makeLog("error", ["请求超时", request], data.self_id)
28
+ ws.terminate()
29
+ }, this.timeout),
30
+ }
31
+ )
32
  }
33
 
34
  async uploadFile(data, file) {
 
55
  msg = [msg]
56
  const msgs = []
57
  for (let i of msg) {
58
+ if (typeof i !== "object")
59
  i = { type: "text", data: { text: i }}
60
  else if (!i.data)
61
  i = { type: i.type, data: { ...i, type: undefined }}
 
74
  i.type = "file"
75
  break
76
  case "at":
77
+ if (i.data.qq === "all")
78
  i = { type: "mention_all", data: {}}
79
  else
80
  i = { type: "mention", data: { user_id: i.data.qq }}
 
85
  case "node":
86
  await Bot.sendForwardMsg(send, i.data)
87
  continue
88
+ case "raw":
89
+ i = i.data
90
+ break
91
  default:
92
+ i = { type: "text", data: { text: Bot.String(i) }}
93
  }
94
  msgs.push(i)
95
  }
 
121
  for (const i of (await data.bot.sendApi("get_friend_list")).data)
122
  array.push({
123
  ...i,
124
+ nickname: i.user_remark === "null" ? i.user_displayname || i.user_name : i.user_remark,
125
  })
126
  return array
127
  }
 
251
  Bot[data.self_id] = {
252
  adapter: this,
253
  ws: ws,
254
+ sendApi: (action, params) => this.sendApi(data, ws, action, params),
255
  stat: { ...data.status, start_time: data.time },
256
 
257
  info: {},
 
279
  if (!Bot.uin.includes(data.self_id))
280
  Bot.uin.push(data.self_id)
281
 
282
+ data.bot.info = (await data.bot.sendApi("get_self_info").catch(i => i.error)).data
283
  data.bot.version = {
284
+ ...(await data.bot.sendApi("get_version").catch(i => i.error)).data,
285
  id: this.id,
286
  name: this.name,
287
  }
 
442
  }
443
 
444
  if (data.type) {
445
+ if (data.type !== "meta" && !Bot.uin.includes(data.self_id)) {
446
  Bot.makeLog("warn", `找不到对应Bot,忽略消息:${logger.magenta(data.raw)}`, data.self_id)
447
  return false
448
  }
 
464
  default:
465
  Bot.makeLog("warn", `未知消息:${logger.magenta(data.raw)}`, data.self_id)
466
  }
467
+ } else if (data.echo && this.echo[data.echo]) {
468
+ if (data.retcode !== 0)
469
+ this.echo[data.echo].reject(Object.assign(
470
+ this.echo[data.echo].request, { error: data }
471
+ ))
472
+ else
473
+ this.echo[data.echo].resolve(data.data ? new Proxy(data, {
474
+ get: (target, prop) => target.data[prop] ?? target[prop],
475
+ }) : data)
476
+ clearTimeout(this.echo[data.echo].timeout)
477
+ delete this.echo[data.echo]
478
  } else {
479
  Bot.makeLog("warn", `未知消息:${logger.magenta(data.raw)}`, data.self_id)
480
  }
Yunzai/plugins/adapter/GSUIDCore.js CHANGED
@@ -28,7 +28,7 @@ Bot.adapter.push(new class GSUIDCoreAdapter {
28
  } else return false
29
 
30
  if (button.permission) {
31
- if (button.permission == "admin") {
32
  msg.permission = 1
33
  } else {
34
  msg.permission = 0
@@ -58,7 +58,7 @@ Bot.adapter.push(new class GSUIDCoreAdapter {
58
  msg = [msg]
59
  const msgs = []
60
  for (let i of msg) {
61
- if (typeof i != "object")
62
  i = { type: "text", text: i }
63
 
64
  if (i.file) {
@@ -100,8 +100,11 @@ Bot.adapter.push(new class GSUIDCoreAdapter {
100
  array.push(...await this.makeMsg(message))
101
  i.data = array
102
  break
103
- } default:
104
- i = { type: "text", data: JSON.stringify(i) }
 
 
 
105
  }
106
  msgs.push(i)
107
  }
@@ -118,7 +121,7 @@ Bot.adapter.push(new class GSUIDCoreAdapter {
118
  target_id: data.user_id,
119
  content,
120
  })
121
- return { message_id: Date.now() }
122
  }
123
 
124
  async sendGroupMsg(data, msg) {
@@ -132,7 +135,7 @@ Bot.adapter.push(new class GSUIDCoreAdapter {
132
  target_id: target[1],
133
  content,
134
  })
135
- return { message_id: Date.now() }
136
  }
137
 
138
  pickFriend(id, user_id) {
@@ -260,15 +263,15 @@ Bot.adapter.push(new class GSUIDCoreAdapter {
260
  break
261
  case "node":
262
  data.message.push({ type: "node", data: i.data })
263
- data.raw_message += `[合并转发:${JSON.stringify(i.data)}]`
264
  break
265
  default:
266
  data.message.push(i)
267
- data.raw_message += JSON.stringify(i)
268
  }
269
  }
270
 
271
- if (raw.user_type == "direct") {
272
  data.message_type = "private"
273
  Bot.makeLog("info", `好友消息:${data.raw_message}`, `${data.self_id} <= ${data.user_id}`)
274
  } else {
 
28
  } else return false
29
 
30
  if (button.permission) {
31
+ if (button.permission === "admin") {
32
  msg.permission = 1
33
  } else {
34
  msg.permission = 0
 
58
  msg = [msg]
59
  const msgs = []
60
  for (let i of msg) {
61
+ if (typeof i !== "object")
62
  i = { type: "text", text: i }
63
 
64
  if (i.file) {
 
100
  array.push(...await this.makeMsg(message))
101
  i.data = array
102
  break
103
+ } case "raw":
104
+ i = i.data
105
+ break
106
+ default:
107
+ i = { type: "text", data: Bot.String(i) }
108
  }
109
  msgs.push(i)
110
  }
 
121
  target_id: data.user_id,
122
  content,
123
  })
124
+ return { message_id: Date.now().toString(36) }
125
  }
126
 
127
  async sendGroupMsg(data, msg) {
 
135
  target_id: target[1],
136
  content,
137
  })
138
+ return { message_id: Date.now().toString(36) }
139
  }
140
 
141
  pickFriend(id, user_id) {
 
263
  break
264
  case "node":
265
  data.message.push({ type: "node", data: i.data })
266
+ data.raw_message += `[合并转发:${Bot.String(i.data)}]`
267
  break
268
  default:
269
  data.message.push(i)
270
+ data.raw_message += Bot.String(i)
271
  }
272
  }
273
 
274
+ if (raw.user_type === "direct") {
275
  data.message_type = "private"
276
  Bot.makeLog("info", `好友消息:${data.raw_message}`, `${data.self_id} <= ${data.user_id}`)
277
  } else {
Yunzai/plugins/adapter/OPQBot.js CHANGED
@@ -3,6 +3,8 @@ Bot.adapter.push(new class OPQBotAdapter {
3
  this.id = "QQ"
4
  this.name = "OPQBot"
5
  this.path = this.name
 
 
6
  this.CommandId = {
7
  FriendImage: 1,
8
  GroupImage: 2,
@@ -13,9 +15,19 @@ Bot.adapter.push(new class OPQBotAdapter {
13
 
14
  sendApi(id, CgiCmd, CgiRequest) {
15
  const ReqId = Math.round(Math.random()*10**16)
16
- Bot[id].ws.sendMsg({ BotUin: String(id), CgiCmd, CgiRequest, ReqId })
17
- return new Promise(resolve =>
18
- Bot.once(ReqId, data => resolve(data)))
 
 
 
 
 
 
 
 
 
 
19
  }
20
 
21
  makeLog(msg) {
@@ -46,7 +58,7 @@ Bot.adapter.push(new class OPQBotAdapter {
46
  }
47
 
48
  for (let i of msg) {
49
- if (typeof i != "object")
50
  i = { type: "text", text: i }
51
 
52
  switch (i.type) {
@@ -71,8 +83,12 @@ Bot.adapter.push(new class OPQBotAdapter {
71
  case "node":
72
  await Bot.sendForwardMsg(msg => this.sendMsg(send, upload, msg), i.data)
73
  continue
 
 
 
 
74
  default:
75
- message.Content += JSON.stringify(i)
76
  }
77
  }
78
 
@@ -141,7 +157,7 @@ Bot.adapter.push(new class OPQBotAdapter {
141
  return {
142
  ...i,
143
  sendMsg: msg => this.sendFriendMsg(i, msg),
144
- getAvatarUrl: () => `https://q1.qlogo.cn/g?b=qq&s=0&nk=${user_id}`,
145
  }
146
  }
147
 
@@ -267,7 +283,7 @@ Bot.adapter.push(new class OPQBotAdapter {
267
  uin: id,
268
  info: { id },
269
  get nickname() { return this.info.nickname },
270
- get avatar() { return `https://q1.qlogo.cn/g?b=qq&s=0&nk=${this.uin}` },
271
 
272
  version: {
273
  id: this.id,
@@ -310,8 +326,15 @@ Bot.adapter.push(new class OPQBotAdapter {
310
  this.makeBot(id, ws)
311
 
312
  this.makeEvent(id, data.CurrentPacket)
313
- } else if (data.ReqId) {
314
- Bot.emit(data.ReqId, data)
 
 
 
 
 
 
 
315
  } else {
316
  Bot.makeLog("warn", `未知消息:${logger.magenta(data.raw)}`, id)
317
  }
 
3
  this.id = "QQ"
4
  this.name = "OPQBot"
5
  this.path = this.name
6
+ this.echo = {}
7
+ this.timeout = 60000
8
  this.CommandId = {
9
  FriendImage: 1,
10
  GroupImage: 2,
 
15
 
16
  sendApi(id, CgiCmd, CgiRequest) {
17
  const ReqId = Math.round(Math.random()*10**16)
18
+ const request = { BotUin: String(id), CgiCmd, CgiRequest, ReqId }
19
+ Bot[id].ws.sendMsg(request)
20
+ return new Promise((resolve, reject) =>
21
+ this.echo[ReqId] = {
22
+ request, resolve, reject,
23
+ timeout: setTimeout(() => {
24
+ reject(Object.assign(request, { timeout: this.timeout }))
25
+ delete this.echo[ReqId]
26
+ Bot.makeLog("error", ["请求超时", request], id)
27
+ Bot[id].ws.terminate()
28
+ }, this.timeout),
29
+ }
30
+ )
31
  }
32
 
33
  makeLog(msg) {
 
58
  }
59
 
60
  for (let i of msg) {
61
+ if (typeof i !== "object")
62
  i = { type: "text", text: i }
63
 
64
  switch (i.type) {
 
83
  case "node":
84
  await Bot.sendForwardMsg(msg => this.sendMsg(send, upload, msg), i.data)
85
  continue
86
+ case "raw":
87
+ for (const i in i.data)
88
+ message[i] = i.data[i]
89
+ continue
90
  default:
91
+ message.Content += Bot.String(i)
92
  }
93
  }
94
 
 
157
  return {
158
  ...i,
159
  sendMsg: msg => this.sendFriendMsg(i, msg),
160
+ getAvatarUrl: () => `https://q.qlogo.cn/g?b=qq&s=0&nk=${user_id}`,
161
  }
162
  }
163
 
 
283
  uin: id,
284
  info: { id },
285
  get nickname() { return this.info.nickname },
286
+ get avatar() { return `https://q.qlogo.cn/g?b=qq&s=0&nk=${this.uin}` },
287
 
288
  version: {
289
  id: this.id,
 
326
  this.makeBot(id, ws)
327
 
328
  this.makeEvent(id, data.CurrentPacket)
329
+ } else if (data.ReqId && this.echo[data.ReqId]) {
330
+ if (data.CgiBaseResponse?.Ret !== 0)
331
+ this.echo[data.ReqId].reject(Object.assign(
332
+ this.echo[data.ReqId].request, { error: data }
333
+ ))
334
+ else
335
+ this.echo[data.ReqId].resolve(data)
336
+ clearTimeout(this.echo[data.ReqId].timeout)
337
+ delete this.echo[data.ReqId]
338
  } else {
339
  Bot.makeLog("warn", `未知消息:${logger.magenta(data.raw)}`, id)
340
  }
Yunzai/plugins/adapter/OneBotv11.js CHANGED
@@ -1,28 +1,34 @@
1
- import { randomUUID } from "node:crypto"
2
  import path from "node:path"
 
3
 
4
  Bot.adapter.push(new class OneBotv11Adapter {
5
  constructor() {
6
  this.id = "QQ"
7
  this.name = "OneBotv11"
8
  this.path = this.name
 
 
9
  }
10
 
11
  makeLog(msg) {
12
  return Bot.String(msg).replace(/base64:\/\/.*?(,|]|")/g, "base64://...$1")
13
  }
14
 
15
- sendApi(ws, action, params) {
16
- const echo = randomUUID()
17
- ws.sendMsg({ action, params, echo })
18
- return new Promise(resolve => Bot.once(echo, data =>
19
- resolve({ ...data, ...data.data })
20
- ))
21
- }
22
-
23
- setProfile(data, profile) {
24
- Bot.makeLog("info", `设置资料:${JSON.stringify(profile)}`, data.self_id)
25
- return data.bot.sendApi("set_qq_profile", profile)
 
 
 
 
26
  }
27
 
28
  async makeFile(file) {
@@ -38,7 +44,7 @@ Bot.adapter.push(new class OneBotv11Adapter {
38
  const msgs = []
39
  const forward = []
40
  for (let i of msg) {
41
- if (typeof i != "object")
42
  i = { type: "text", data: { text: i }}
43
  else if (!i.data)
44
  i = { type: i.type, data: { ...i, type: undefined }}
@@ -55,6 +61,9 @@ Bot.adapter.push(new class OneBotv11Adapter {
55
  case "node":
56
  forward.push(...i.data)
57
  continue
 
 
 
58
  }
59
 
60
  if (i.data.file)
@@ -79,7 +88,7 @@ Bot.adapter.push(new class OneBotv11Adapter {
79
 
80
  if (message.length)
81
  ret.push(await send(message))
82
- if (ret.length == 1) return ret[0]
83
 
84
  const message_id = []
85
  for (const i of ret) if (i?.message_id)
@@ -131,7 +140,7 @@ Bot.adapter.push(new class OneBotv11Adapter {
131
  parseMsg(msg) {
132
  const array = []
133
  for (const i of Array.isArray(msg) ? msg : [msg])
134
- if (typeof i == "object")
135
  array.push({ ...i.data, type: i.type })
136
  else
137
  array.push({ type: "text", text: String(i) })
@@ -368,6 +377,19 @@ Bot.adapter.push(new class OneBotv11Adapter {
368
  })
369
  }
370
 
 
 
 
 
 
 
 
 
 
 
 
 
 
371
  sendLike(data, times) {
372
  Bot.makeLog("info", `点赞:${times}次`, `${data.self_id} => ${data.user_id}`)
373
  return data.bot.sendApi("send_like", {
@@ -386,10 +408,9 @@ Bot.adapter.push(new class OneBotv11Adapter {
386
 
387
  async setGroupAvatar(data, file) {
388
  Bot.makeLog("info", `设置群头像:${file}`, `${data.self_id} => ${data.group_id}`)
389
- file = await Bot.Buffer(file, { http: true })
390
  return data.bot.sendApi("set_group_portrait", {
391
  group_id: data.group_id,
392
- file,
393
  })
394
  }
395
 
@@ -574,13 +595,13 @@ Bot.adapter.push(new class OneBotv11Adapter {
574
  sendForwardMsg: msg => this.sendFriendForwardMsg(i, msg),
575
  sendFile: (file, name) => this.sendFriendFile(i, file, name),
576
  getInfo: () => this.getFriendInfo(i),
577
- getAvatarUrl: () => `https://q1.qlogo.cn/g?b=qq&s=0&nk=${user_id}`,
578
  thumbUp: times => this.sendLike(i, times),
579
  }
580
  }
581
 
582
  pickMember(data, group_id, user_id) {
583
- if (typeof group_id == "string" && group_id.match("-")) {
584
  const guild_id = group_id.split("-")
585
  const i = {
586
  ...data,
@@ -607,17 +628,18 @@ Bot.adapter.push(new class OneBotv11Adapter {
607
  ...this.pickFriend(i, user_id),
608
  ...i,
609
  getInfo: () => this.getMemberInfo(i),
 
610
  poke: () => this.sendGroupMsg(i, { type: "poke", qq: user_id }),
611
  mute: duration => this.setGroupBan(i, i.user_id, duration),
612
  kick: reject_add_request => this.setGroupKick(i, i.user_id, reject_add_request),
613
  get is_friend() { return data.bot.fl.has(user_id) },
614
- get is_owner() { return i.role == "owner" },
615
- get is_admin() { return i.role == "admin" },
616
  }
617
  }
618
 
619
  pickGroup(data, group_id) {
620
- if (typeof group_id == "string" && group_id.match("-")) {
621
  const guild_id = group_id.split("-")
622
  const i = {
623
  ...data.bot.gl.get(group_id),
@@ -656,7 +678,7 @@ Bot.adapter.push(new class OneBotv11Adapter {
656
  sendForwardMsg: msg => this.sendGroupForwardMsg(i, msg),
657
  sendFile: (file, name) => this.sendGroupFile(i, file, undefined, name),
658
  getInfo: () => this.getGroupInfo(i),
659
- getAvatarUrl: () => `https://p.qlogo.cn/gh/${group_id}/${group_id}/0`,
660
  getChatHistory: (seq, cnt) => this.getGroupMsgHistory(i, seq, cnt),
661
  getMemberArray: () => this.getMemberArray(i),
662
  getMemberList: () => this.getMemberList(i),
@@ -674,8 +696,8 @@ Bot.adapter.push(new class OneBotv11Adapter {
674
  kickMember: (user_id, reject_add_request) => this.setGroupKick(i, user_id, reject_add_request),
675
  quit: is_dismiss => this.setGroupLeave(i, is_dismiss),
676
  fs: this.getGroupFs(i),
677
- get is_owner() { return data.bot.gml.get(group_id)?.get(data.self_id)?.role == "owner" },
678
- get is_admin() { return data.bot.gml.get(group_id)?.get(data.self_id)?.role == "admin" },
679
  }
680
  }
681
 
@@ -683,7 +705,7 @@ Bot.adapter.push(new class OneBotv11Adapter {
683
  Bot[data.self_id] = {
684
  adapter: this,
685
  ws: ws,
686
- sendApi: (action, params) => this.sendApi(ws, action, params),
687
  stat: {
688
  start_time: data.time,
689
  stat: {},
@@ -699,10 +721,11 @@ Bot.adapter.push(new class OneBotv11Adapter {
699
  info: {},
700
  get uin() { return this.info.user_id },
701
  get nickname() { return this.info.nickname },
702
- get avatar() { return `https://q1.qlogo.cn/g?b=qq&s=0&nk=${this.uin}` },
703
 
704
  setProfile: profile => this.setProfile(data, profile),
705
  setNickname: nickname => this.setProfile(data, { nickname }),
 
706
 
707
  pickFriend: user_id => this.pickFriend(data, user_id),
708
  get pickUser() { return this.pickFriend },
@@ -733,13 +756,13 @@ Bot.adapter.push(new class OneBotv11Adapter {
733
  data.bot.sendApi("_set_model_show", {
734
  model: data.bot.model,
735
  model_show: data.bot.model,
736
- })
737
 
738
- data.bot.info = (await data.bot.sendApi("get_login_info")).data
739
- data.bot.guild_info = (await data.bot.sendApi("get_guild_service_profile")).data
740
- data.bot.clients = (await data.bot.sendApi("get_online_clients")).clients
741
  data.bot.version = {
742
- ...(await data.bot.sendApi("get_version_info")).data,
743
  id: this.id,
744
  name: this.name,
745
  get version() {
@@ -773,7 +796,7 @@ Bot.adapter.push(new class OneBotv11Adapter {
773
  } case "guild":
774
  data.message_type = "group"
775
  data.group_id = `${data.guild_id}-${data.channel_id}`
776
- Bot.makeLog("info", `频道消息:[${data.sender.nickname}] ${JSON.stringify(data.message)}`, `${data.self_id} <= ${data.group_id}, ${data.user_id}`)
777
  Object.defineProperty(data, "friend", { get() { return this.member || {}}})
778
  break
779
  default:
@@ -793,24 +816,24 @@ Bot.adapter.push(new class OneBotv11Adapter {
793
  break
794
  case "group_increase":
795
  Bot.makeLog("info", `群成员增加:${data.operator_id} => ${data.user_id} ${data.sub_type}`, `${data.self_id} <= ${data.group_id}`)
796
- if (data.user_id == data.self_id)
797
  data.bot.getGroupMemberMap()
798
  else
799
  data.bot.pickGroup(data.group_id).getMemberMap()
800
  break
801
  case "group_decrease":
802
  Bot.makeLog("info", `群成员减少:${data.operator_id} => ${data.user_id} ${data.sub_type}`, `${data.self_id} <= ${data.group_id}`)
803
- if (data.user_id == data.self_id)
804
  data.bot.getGroupMemberMap()
805
  else
806
  data.bot.pickGroup(data.group_id).getMemberMap()
807
  break
808
  case "group_admin":
809
  Bot.makeLog("info", `群管理员变动:${data.sub_type}`, `${data.self_id} <= ${data.group_id}, ${data.user_id}`)
810
- data.set = data.sub_type == "set"
811
  break
812
  case "group_upload":
813
- Bot.makeLog("info", `群文件上传:${JSON.stringify(data.file)}`, `${data.self_id} <= ${data.group_id}, ${data.user_id}`)
814
  break
815
  case "group_ban":
816
  Bot.makeLog("info", `群禁言:${data.operator_id} => ${data.user_id} ${data.sub_type} ${data.duration}秒`, `${data.self_id} <= ${data.group_id}`)
@@ -846,10 +869,10 @@ Bot.adapter.push(new class OneBotv11Adapter {
846
  Bot.makeLog("info", `群名片更新:${data.card_old} => ${data.card_new}`, `${data.self_id} <= ${data.group_id}, ${data.user_id}`)
847
  break
848
  case "offline_file":
849
- Bot.makeLog("info", `离线文件:${JSON.stringify(data.file)}`, `${data.self_id} <= ${data.user_id}`)
850
  break
851
  case "client_status":
852
- Bot.makeLog("info", `客户端${data.online ? "上线" : "下线"}:${JSON.stringify(data.client)}`, data.self_id)
853
  data.clients = (await data.bot.sendApi("get_online_clients")).clients
854
  data.bot.clients = data.clients
855
  break
@@ -862,20 +885,20 @@ Bot.adapter.push(new class OneBotv11Adapter {
862
  break
863
  case "message_reactions_updated":
864
  data.notice_type = "guild_message_reactions_updated"
865
- Bot.makeLog("info", `频道消息表情贴:${data.message_id} ${JSON.stringify(data.current_reactions)}`, `${data.self_id} <= ${data.guild_id}-${data.channel_id}, ${data.user_id}`)
866
  break
867
  case "channel_updated":
868
  data.notice_type = "guild_channel_updated"
869
- 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}`)
870
  break
871
  case "channel_created":
872
  data.notice_type = "guild_channel_created"
873
- Bot.makeLog("info", `子频道创建:${JSON.stringify(data.channel_info)}`, `${data.self_id} <= ${data.guild_id}-${data.channel_id}, ${data.user_id}`)
874
  data.bot.getGroupMap()
875
  break
876
  case "channel_destroyed":
877
  data.notice_type = "guild_channel_destroyed"
878
- Bot.makeLog("info", `子频道删除:${JSON.stringify(data.channel_info)}`, `${data.self_id} <= ${data.guild_id}-${data.channel_id}, ${data.user_id}`)
879
  data.bot.getGroupMap()
880
  break
881
  default:
@@ -944,7 +967,7 @@ Bot.adapter.push(new class OneBotv11Adapter {
944
  }
945
 
946
  if (data.post_type) {
947
- if (data.meta_event_type != "lifecycle" && !Bot.uin.includes(data.self_id)) {
948
  Bot.makeLog("warn", `找不到对应Bot,忽略消息:${logger.magenta(data.raw)}`, data.self_id)
949
  return false
950
  }
@@ -970,8 +993,17 @@ Bot.adapter.push(new class OneBotv11Adapter {
970
  default:
971
  Bot.makeLog("warn", `未知消息:${logger.magenta(data.raw)}`, data.self_id)
972
  }
973
- } else if (data.echo) {
974
- Bot.emit(data.echo, data)
 
 
 
 
 
 
 
 
 
975
  } else {
976
  Bot.makeLog("warn", `未知消息:${logger.magenta(data.raw)}`, data.self_id)
977
  }
 
 
1
  import path from "node:path"
2
+ import { ulid } from "ulid"
3
 
4
  Bot.adapter.push(new class OneBotv11Adapter {
5
  constructor() {
6
  this.id = "QQ"
7
  this.name = "OneBotv11"
8
  this.path = this.name
9
+ this.echo = {}
10
+ this.timeout = 60000
11
  }
12
 
13
  makeLog(msg) {
14
  return Bot.String(msg).replace(/base64:\/\/.*?(,|]|")/g, "base64://...$1")
15
  }
16
 
17
+ sendApi(data, ws, action, params = {}) {
18
+ const echo = ulid()
19
+ const request = { action, params, echo }
20
+ ws.sendMsg(request)
21
+ return new Promise((resolve, reject) =>
22
+ this.echo[echo] = {
23
+ request, resolve, reject,
24
+ timeout: setTimeout(() => {
25
+ reject(Object.assign(request, { timeout: this.timeout }))
26
+ delete this.echo[echo]
27
+ Bot.makeLog("error", ["请求超时", request], data.self_id)
28
+ ws.terminate()
29
+ }, this.timeout),
30
+ }
31
+ )
32
  }
33
 
34
  async makeFile(file) {
 
44
  const msgs = []
45
  const forward = []
46
  for (let i of msg) {
47
+ if (typeof i !== "object")
48
  i = { type: "text", data: { text: i }}
49
  else if (!i.data)
50
  i = { type: i.type, data: { ...i, type: undefined }}
 
61
  case "node":
62
  forward.push(...i.data)
63
  continue
64
+ case "raw":
65
+ i = i.data
66
+ break
67
  }
68
 
69
  if (i.data.file)
 
88
 
89
  if (message.length)
90
  ret.push(await send(message))
91
+ if (ret.length === 1) return ret[0]
92
 
93
  const message_id = []
94
  for (const i of ret) if (i?.message_id)
 
140
  parseMsg(msg) {
141
  const array = []
142
  for (const i of Array.isArray(msg) ? msg : [msg])
143
+ if (typeof i === "object")
144
  array.push({ ...i.data, type: i.type })
145
  else
146
  array.push({ type: "text", text: String(i) })
 
377
  })
378
  }
379
 
380
+
381
+ setProfile(data, profile) {
382
+ Bot.makeLog("info", `设置资料:${Bot.String(profile)}`, data.self_id)
383
+ return data.bot.sendApi("set_qq_profile", profile)
384
+ }
385
+
386
+ async setAvatar(data, file) {
387
+ Bot.makeLog("info", `设置头像:${file}`, data.self_id)
388
+ return data.bot.sendApi("set_qq_avatar", {
389
+ file: await this.makeFile(file),
390
+ })
391
+ }
392
+
393
  sendLike(data, times) {
394
  Bot.makeLog("info", `点赞:${times}次`, `${data.self_id} => ${data.user_id}`)
395
  return data.bot.sendApi("send_like", {
 
408
 
409
  async setGroupAvatar(data, file) {
410
  Bot.makeLog("info", `设置群头像:${file}`, `${data.self_id} => ${data.group_id}`)
 
411
  return data.bot.sendApi("set_group_portrait", {
412
  group_id: data.group_id,
413
+ file: await this.makeFile(file),
414
  })
415
  }
416
 
 
595
  sendForwardMsg: msg => this.sendFriendForwardMsg(i, msg),
596
  sendFile: (file, name) => this.sendFriendFile(i, file, name),
597
  getInfo: () => this.getFriendInfo(i),
598
+ getAvatarUrl: () => i.avatar || `https://q.qlogo.cn/g?b=qq&s=0&nk=${user_id}`,
599
  thumbUp: times => this.sendLike(i, times),
600
  }
601
  }
602
 
603
  pickMember(data, group_id, user_id) {
604
+ if (typeof group_id === "string" && group_id.match("-")) {
605
  const guild_id = group_id.split("-")
606
  const i = {
607
  ...data,
 
628
  ...this.pickFriend(i, user_id),
629
  ...i,
630
  getInfo: () => this.getMemberInfo(i),
631
+ getAvatarUrl: () => i.avatar || `https://q.qlogo.cn/g?b=qq&s=0&nk=${user_id}`,
632
  poke: () => this.sendGroupMsg(i, { type: "poke", qq: user_id }),
633
  mute: duration => this.setGroupBan(i, i.user_id, duration),
634
  kick: reject_add_request => this.setGroupKick(i, i.user_id, reject_add_request),
635
  get is_friend() { return data.bot.fl.has(user_id) },
636
+ get is_owner() { return i.role === "owner" },
637
+ get is_admin() { return i.role === "admin" },
638
  }
639
  }
640
 
641
  pickGroup(data, group_id) {
642
+ if (typeof group_id === "string" && group_id.match("-")) {
643
  const guild_id = group_id.split("-")
644
  const i = {
645
  ...data.bot.gl.get(group_id),
 
678
  sendForwardMsg: msg => this.sendGroupForwardMsg(i, msg),
679
  sendFile: (file, name) => this.sendGroupFile(i, file, undefined, name),
680
  getInfo: () => this.getGroupInfo(i),
681
+ getAvatarUrl: () => i.avatar || `https://p.qlogo.cn/gh/${group_id}/${group_id}/0`,
682
  getChatHistory: (seq, cnt) => this.getGroupMsgHistory(i, seq, cnt),
683
  getMemberArray: () => this.getMemberArray(i),
684
  getMemberList: () => this.getMemberList(i),
 
696
  kickMember: (user_id, reject_add_request) => this.setGroupKick(i, user_id, reject_add_request),
697
  quit: is_dismiss => this.setGroupLeave(i, is_dismiss),
698
  fs: this.getGroupFs(i),
699
+ get is_owner() { return data.bot.gml.get(group_id)?.get(data.self_id)?.role === "owner" },
700
+ get is_admin() { return data.bot.gml.get(group_id)?.get(data.self_id)?.role === "admin" },
701
  }
702
  }
703
 
 
705
  Bot[data.self_id] = {
706
  adapter: this,
707
  ws: ws,
708
+ sendApi: (action, params) => this.sendApi(data, ws, action, params),
709
  stat: {
710
  start_time: data.time,
711
  stat: {},
 
721
  info: {},
722
  get uin() { return this.info.user_id },
723
  get nickname() { return this.info.nickname },
724
+ get avatar() { return `https://q.qlogo.cn/g?b=qq&s=0&nk=${this.uin}` },
725
 
726
  setProfile: profile => this.setProfile(data, profile),
727
  setNickname: nickname => this.setProfile(data, { nickname }),
728
+ setAvatar: file => this.setAvatar(data, file),
729
 
730
  pickFriend: user_id => this.pickFriend(data, user_id),
731
  get pickUser() { return this.pickFriend },
 
756
  data.bot.sendApi("_set_model_show", {
757
  model: data.bot.model,
758
  model_show: data.bot.model,
759
+ }).catch(() => {})
760
 
761
+ data.bot.info = (await data.bot.sendApi("get_login_info").catch(i => i.error)).data
762
+ data.bot.guild_info = (await data.bot.sendApi("get_guild_service_profile").catch(i => i.error)).data
763
+ data.bot.clients = (await data.bot.sendApi("get_online_clients").catch(i => i.error)).clients
764
  data.bot.version = {
765
+ ...(await data.bot.sendApi("get_version_info").catch(i => i.error)).data,
766
  id: this.id,
767
  name: this.name,
768
  get version() {
 
796
  } case "guild":
797
  data.message_type = "group"
798
  data.group_id = `${data.guild_id}-${data.channel_id}`
799
+ Bot.makeLog("info", `频道消息:[${data.sender.nickname}] ${Bot.String(data.message)}`, `${data.self_id} <= ${data.group_id}, ${data.user_id}`)
800
  Object.defineProperty(data, "friend", { get() { return this.member || {}}})
801
  break
802
  default:
 
816
  break
817
  case "group_increase":
818
  Bot.makeLog("info", `群成员增加:${data.operator_id} => ${data.user_id} ${data.sub_type}`, `${data.self_id} <= ${data.group_id}`)
819
+ if (data.user_id === data.self_id)
820
  data.bot.getGroupMemberMap()
821
  else
822
  data.bot.pickGroup(data.group_id).getMemberMap()
823
  break
824
  case "group_decrease":
825
  Bot.makeLog("info", `群成员减少:${data.operator_id} => ${data.user_id} ${data.sub_type}`, `${data.self_id} <= ${data.group_id}`)
826
+ if (data.user_id === data.self_id)
827
  data.bot.getGroupMemberMap()
828
  else
829
  data.bot.pickGroup(data.group_id).getMemberMap()
830
  break
831
  case "group_admin":
832
  Bot.makeLog("info", `群管理员变动:${data.sub_type}`, `${data.self_id} <= ${data.group_id}, ${data.user_id}`)
833
+ data.set = data.sub_type === "set"
834
  break
835
  case "group_upload":
836
+ Bot.makeLog("info", `群文件上传:${Bot.String(data.file)}`, `${data.self_id} <= ${data.group_id}, ${data.user_id}`)
837
  break
838
  case "group_ban":
839
  Bot.makeLog("info", `群禁言:${data.operator_id} => ${data.user_id} ${data.sub_type} ${data.duration}秒`, `${data.self_id} <= ${data.group_id}`)
 
869
  Bot.makeLog("info", `群名片更新:${data.card_old} => ${data.card_new}`, `${data.self_id} <= ${data.group_id}, ${data.user_id}`)
870
  break
871
  case "offline_file":
872
+ Bot.makeLog("info", `离线文件:${Bot.String(data.file)}`, `${data.self_id} <= ${data.user_id}`)
873
  break
874
  case "client_status":
875
+ Bot.makeLog("info", `客户端${data.online ? "上线" : "下线"}:${Bot.String(data.client)}`, data.self_id)
876
  data.clients = (await data.bot.sendApi("get_online_clients")).clients
877
  data.bot.clients = data.clients
878
  break
 
885
  break
886
  case "message_reactions_updated":
887
  data.notice_type = "guild_message_reactions_updated"
888
+ Bot.makeLog("info", `频道消息表情贴:${data.message_id} ${Bot.String(data.current_reactions)}`, `${data.self_id} <= ${data.guild_id}-${data.channel_id}, ${data.user_id}`)
889
  break
890
  case "channel_updated":
891
  data.notice_type = "guild_channel_updated"
892
+ Bot.makeLog("info", `子频道更新:${Bot.String(data.old_info)} => ${Bot.String(data.new_info)}`, `${data.self_id} <= ${data.guild_id}-${data.channel_id}, ${data.user_id}`)
893
  break
894
  case "channel_created":
895
  data.notice_type = "guild_channel_created"
896
+ Bot.makeLog("info", `子频道创建:${Bot.String(data.channel_info)}`, `${data.self_id} <= ${data.guild_id}-${data.channel_id}, ${data.user_id}`)
897
  data.bot.getGroupMap()
898
  break
899
  case "channel_destroyed":
900
  data.notice_type = "guild_channel_destroyed"
901
+ Bot.makeLog("info", `子频道删除:${Bot.String(data.channel_info)}`, `${data.self_id} <= ${data.guild_id}-${data.channel_id}, ${data.user_id}`)
902
  data.bot.getGroupMap()
903
  break
904
  default:
 
967
  }
968
 
969
  if (data.post_type) {
970
+ if (data.meta_event_type !== "lifecycle" && !Bot.uin.includes(data.self_id)) {
971
  Bot.makeLog("warn", `找不到对应Bot,忽略消息:${logger.magenta(data.raw)}`, data.self_id)
972
  return false
973
  }
 
993
  default:
994
  Bot.makeLog("warn", `未知消息:${logger.magenta(data.raw)}`, data.self_id)
995
  }
996
+ } else if (data.echo && this.echo[data.echo]) {
997
+ if (![0, 1].includes(data.retcode))
998
+ this.echo[data.echo].reject(Object.assign(
999
+ this.echo[data.echo].request, { error: data }
1000
+ ))
1001
+ else
1002
+ this.echo[data.echo].resolve(data.data ? new Proxy(data, {
1003
+ get: (target, prop) => target.data[prop] ?? target[prop],
1004
+ }) : data)
1005
+ clearTimeout(this.echo[data.echo].timeout)
1006
+ delete this.echo[data.echo]
1007
  } else {
1008
  Bot.makeLog("warn", `未知消息:${logger.magenta(data.raw)}`, data.self_id)
1009
  }
Yunzai/plugins/example/package.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "name": "example",
3
+ "type": "module"
4
+ }
Yunzai/plugins/example/主动复读.js CHANGED
@@ -23,14 +23,14 @@ export class example2 extends plugin {
23
  /** 设置上下文,后续接收到内容会执行doRep方法 */
24
  this.setContext("doRep")
25
  /** 回复 */
26
- await this.reply("请发送要复读的内容", false, { at: true })
27
  }
28
 
29
  /** 接受内容 */
30
  doRep() {
31
- /** 复读内容 */
32
- this.reply(this.e.message, false, { recallMsg: 5 })
33
  /** 结束上下文 */
34
  this.finish("doRep")
 
 
35
  }
36
  }
 
23
  /** 设置上下文,后续接收到内容会执行doRep方法 */
24
  this.setContext("doRep")
25
  /** 回复 */
26
+ return this.reply("请发送要复读的内容", false, { at: true })
27
  }
28
 
29
  /** 接受内容 */
30
  doRep() {
 
 
31
  /** 结束上下文 */
32
  this.finish("doRep")
33
+ /** 复读内容 */
34
+ return this.reply(this.e.message, false, { recallMsg: 5 })
35
  }
36
  }
Yunzai/plugins/other/install.js CHANGED
@@ -4,9 +4,11 @@ let insing = false
4
  const list = {
5
  "Atlas":"https://gitee.com/Nwflower/atlas",
6
  "genshin" :"https://gitee.com/TimeRainStarSky/Yunzai-genshin",
 
7
  "ws-plugin":"https://gitee.com/xiaoye12123/ws-plugin",
8
  "TRSS-Plugin" :"https://Yunzai.TRSS.me",
9
  "miao-plugin" :"https://gitee.com/yoimiya-kokomi/miao-plugin",
 
10
  "yenai-plugin" :"https://gitee.com/yeyang52/yenai-plugin",
11
  "flower-plugin" :"https://gitee.com/Nwflower/flower-plugin",
12
  "xianyu-plugin" :"https://gitee.com/suancaixianyu/xianyu-plugin",
@@ -24,6 +26,9 @@ const list = {
24
  "ICQQ-Plugin" :"https://gitee.com/TimeRainStarSky/Yunzai-ICQQ-Plugin",
25
  "KOOK-Plugin" :"https://gitee.com/TimeRainStarSky/Yunzai-KOOK-Plugin",
26
  }
 
 
 
27
 
28
  export class install extends plugin {
29
  constructor() {
@@ -33,7 +38,7 @@ export class install extends plugin {
33
  event: "message",
34
  rule: [
35
  {
36
- reg: `^#安装(插件|${Object.keys(list).join("|")})$`,
37
  fnc: "install",
38
  permission: "master"
39
  }
@@ -43,16 +48,18 @@ export class install extends plugin {
43
 
44
  async install() {
45
  if (insing) {
46
- await this.reply("已有命令安装中..请勿重复操作")
47
  return false
48
  }
49
 
50
- const name = this.e.msg.replace(/^#安装/, "").trim()
 
 
51
  if (name == "插件") {
52
  let msg = "\n"
53
- for (const name in list)
54
- if (!await Bot.fsStat(`plugins/${name}`))
55
- msg += `${name}\n`
56
 
57
  if (msg == "\n")
58
  msg = "暂无可安装插件"
@@ -68,47 +75,40 @@ export class install extends plugin {
68
  await this.reply(`${name} 插件已安装`)
69
  return false
70
  }
71
- await this.runInstall(name, list[name], path)
72
- this.restart()
73
  }
74
 
75
  async runInstall(name, url, path) {
76
- logger.mark(`${this.e.logFnc} 开始安装:${name} 插件`)
77
  await this.reply(`开始安装 ${name} 插件`)
78
 
79
- const cm = `git clone --depth 1 --single-branch "${url}" "${path}"`
80
  insing = true
81
- const ret = await Bot.exec(cm)
82
  if (await Bot.fsStat(`${path}/package.json`))
83
  await Bot.exec("pnpm install")
84
  insing = false
85
 
86
  if (ret.error) {
87
- logger.mark(`${this.e.logFnc} 插件安装失败:${name}`)
88
- this.gitErr(ret.error, ret.stdout)
89
  return false
90
  }
 
91
  }
92
 
93
- async gitErr(err, stdout) {
94
- let msg = "安装失败!"
95
- let errMsg = err.toString()
96
- stdout = stdout.toString()
97
-
98
- if (errMsg.includes('Timed out')) {
99
- const remote = errMsg.match(/'(.+?)'/g)[0].replace(/'/g, '')
100
- return this.reply(`${msg}\n连接超时:${remote}`)
101
- }
102
-
103
- if (/Failed to connect|unable to access/g.test(errMsg)) {
104
- const remote = errMsg.match(/'(.+?)'/g)[0].replace(/'/g, '')
105
- return this.reply(`${msg}\n连接失败:${remote}`)
106
- }
107
 
108
- await this.reply([errMsg, stdout])
 
 
 
 
 
109
  }
110
 
111
  restart() {
112
- new Restart(this.e).restart()
113
  }
114
  }
 
4
  const list = {
5
  "Atlas":"https://gitee.com/Nwflower/atlas",
6
  "genshin" :"https://gitee.com/TimeRainStarSky/Yunzai-genshin",
7
+ "DF-Plugin":"https://gitee.com/DenFengLai/DF-Plugin",
8
  "ws-plugin":"https://gitee.com/xiaoye12123/ws-plugin",
9
  "TRSS-Plugin" :"https://Yunzai.TRSS.me",
10
  "miao-plugin" :"https://gitee.com/yoimiya-kokomi/miao-plugin",
11
+ "Guoba-Plugin" :"https://gitee.com/guoba-yunzai/guoba-plugin",
12
  "yenai-plugin" :"https://gitee.com/yeyang52/yenai-plugin",
13
  "flower-plugin" :"https://gitee.com/Nwflower/flower-plugin",
14
  "xianyu-plugin" :"https://gitee.com/suancaixianyu/xianyu-plugin",
 
26
  "ICQQ-Plugin" :"https://gitee.com/TimeRainStarSky/Yunzai-ICQQ-Plugin",
27
  "KOOK-Plugin" :"https://gitee.com/TimeRainStarSky/Yunzai-KOOK-Plugin",
28
  }
29
+ const map = {}
30
+ for (const i in list)
31
+ map[i.replace(/-[Pp]lugin$/, "")] = i
32
 
33
  export class install extends plugin {
34
  constructor() {
 
38
  event: "message",
39
  rule: [
40
  {
41
+ reg: `^#安装(插件|${Object.keys(map).join("|")})(-[Pp]lugin)?$`,
42
  fnc: "install",
43
  permission: "master"
44
  }
 
48
 
49
  async install() {
50
  if (insing) {
51
+ await this.reply("正在安装,请稍候再试")
52
  return false
53
  }
54
 
55
+ let name = this.e.msg.replace(/^#安装(.+?)(-[Pp]lugin)?$/, "$1")
56
+ if (map[name]) name = map[name]
57
+
58
  if (name == "插件") {
59
  let msg = "\n"
60
+ for (const i in list)
61
+ if (!await Bot.fsStat(`plugins/${i}`))
62
+ msg += `${i}\n`
63
 
64
  if (msg == "\n")
65
  msg = "暂无可安装插件"
 
75
  await this.reply(`${name} 插件已安装`)
76
  return false
77
  }
78
+ return this.runInstall(name, list[name], path)
 
79
  }
80
 
81
  async runInstall(name, url, path) {
82
+ logger.mark(`${this.e.logFnc} 开始安装 ${name} 插件`)
83
  await this.reply(`开始安装 ${name} 插件`)
84
 
 
85
  insing = true
86
+ const ret = await Bot.exec(`git clone --depth 1 --single-branch "${url}" "${path}"`)
87
  if (await Bot.fsStat(`${path}/package.json`))
88
  await Bot.exec("pnpm install")
89
  insing = false
90
 
91
  if (ret.error) {
92
+ logger.mark(`${this.e.logFnc} ${name} 插件安装错误`)
93
+ this.gitErr(name, ret.error.message, ret.stdout)
94
  return false
95
  }
96
+ return this.restart()
97
  }
98
 
99
+ gitErrUrl(error) {
100
+ return error.replace(/(Cloning into|正克隆到)\s*'.+?'/g, "").match(/'(.+?)'/g)[0].replace(/'(.+?)'/, "$1")
101
+ }
 
 
 
 
 
 
 
 
 
 
 
102
 
103
+ async gitErr(name, error, stdout) {
104
+ if (/unable to access|无法访问/.test(error))
105
+ await this.reply(`远程仓库连接错误:${this.gitErrUrl(error)}`)
106
+ else if (/not found|未找到|does not (exist|appear)|不存在|Authentication failed|鉴权失败/.test(error))
107
+ await this.reply(`远程仓库地址错误:${this.gitErrUrl(error)}`)
108
+ else await this.reply(`${name} 插件安装错误\n${error}\n${stdout}`)
109
  }
110
 
111
  restart() {
112
+ return new Restart(this.e).restart()
113
  }
114
  }
Yunzai/plugins/other/restart.js CHANGED
@@ -1,11 +1,38 @@
1
  import cfg from "../../lib/config/config.js"
2
  import { spawn } from "child_process"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
  export class Restart extends plugin {
5
- constructor (e = "") {
6
  super({
7
- name: "重启",
8
- dsc: "#重启",
9
  event: "message",
10
  priority: 10,
11
  rule: [
@@ -15,87 +42,117 @@ export class Restart extends plugin {
15
  permission: "master"
16
  },
17
  {
18
- reg: "^#(停机|关机)$",
19
  fnc: "stop",
20
  permission: "master"
 
 
 
 
 
21
  }
22
  ]
23
  })
24
-
25
  if (e) this.e = e
26
- this.key = "Yz:restart"
27
  }
 
28
 
29
  init() {
30
  Bot.once("online", () => this.restartMsg())
31
- if (cfg.bot.restart_time) {
32
- this.e = {
33
- logFnc: "[自动重启]" ,
34
- reply: msg => Bot.sendMasterMsg(msg),
35
- }
36
- setTimeout(() => this.restart(), cfg.bot.restart_time*60000)
37
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  }
39
 
40
  async restartMsg() {
41
  let restart = await redis.get(this.key)
42
  if (!restart) return
 
43
  restart = JSON.parse(restart)
44
- const time = (Date.now() - (restart.time || Date.now()))/1000
45
- const msg = []
46
- if (restart.msg_id)
47
- msg.push(segment.reply(restart.msg_id))
48
  if (restart.isStop)
49
- msg.push(`开机成功,距离上次关机${time}秒`)
50
- else
51
- msg.push(`重启成功,用时${time}秒`)
52
 
53
- if (restart.id) {
54
- if (restart.isGroup)
55
- Bot.sendGroupMsg(restart.bot_id, restart.id, msg)
56
- else
57
- Bot.sendFriendMsg(restart.bot_id, restart.id, msg)
58
- } else {
59
- Bot.sendMasterMsg(msg)
60
- }
61
- redis.del(this.key)
 
 
62
  }
63
 
64
- async restart() {
65
- await this.e.reply(`开始重启,本次运行时长:${Bot.getTimeDiff()}`)
66
- await redis.set(this.key, JSON.stringify({
67
- isGroup: !!this.e.isGroup,
68
- id: this.e.isGroup ? this.e.group_id : this.e.user_id,
 
 
 
 
 
 
69
  bot_id: this.e.self_id,
70
  msg_id: this.e.message_id,
71
  time: Date.now(),
72
  }))
 
73
 
74
- if (process.env.app_type == "pm2") {
 
 
75
  const ret = await Bot.exec("pnpm run restart")
76
  if (!ret.error) process.exit()
77
- await this.e.reply(`重启错误\n${ret.error}`)
78
  Bot.makeLog("error", ["重启错误", ret])
79
- }
80
- process.exit()
81
  }
82
 
83
- async stop() {
84
- await this.e.reply(`开始关机,本次运行时长:${Bot.getTimeDiff()}`)
85
- await redis.set(this.key, JSON.stringify({
86
- isStop: true,
87
- isGroup: !!this.e.isGroup,
88
- id: this.e.isGroup ? this.e.group_id : this.e.user_id,
89
- bot_id: this.e.self_id,
90
- msg_id: this.e.message_id,
91
- time: Date.now(),
92
- }))
93
 
94
- if (process.env.app_type == "pm2") {
 
 
95
  const ret = await Bot.exec("pnpm stop")
96
- await this.e.reply(`关机错误\n${ret.error}\n${ret.stdout}\n${ret.stderr}`)
97
- Bot.makeLog("error", ["关机错误", ret])
98
- }
99
- process.exit(1)
100
  }
101
  }
 
1
  import cfg from "../../lib/config/config.js"
2
  import { spawn } from "child_process"
3
+ import PluginsLoader from "../../lib/plugins/loader.js"
4
+
5
+ const temp = {}
6
+ class Start extends plugin {
7
+ constructor(e) {
8
+ super({
9
+ name: "开机",
10
+ dsc: "#开机",
11
+ event: "message",
12
+ rule: [
13
+ {
14
+ reg: "^#开机$",
15
+ fnc: "start"
16
+ }
17
+ ]
18
+ })
19
+ if (e) this.e = e
20
+ }
21
+
22
+ async start() {
23
+ if (!this.e.isMaster || !temp.priority) return false
24
+ PluginsLoader.priority = temp.priority
25
+ delete temp.priority
26
+ temp.start_time = Date.now()
27
+ return this.reply(`开机成功,距离上次关机${Bot.getTimeDiff(temp.stop_time)}`)
28
+ }
29
+ }
30
 
31
  export class Restart extends plugin {
32
+ constructor(e) {
33
  super({
34
+ name: "进程管理",
35
+ dsc: "#重启 #关机 #停止",
36
  event: "message",
37
  priority: 10,
38
  rule: [
 
42
  permission: "master"
43
  },
44
  {
45
+ reg: "^#关机$",
46
  fnc: "stop",
47
  permission: "master"
48
+ },
49
+ {
50
+ reg: "^#停(机|止)$",
51
+ fnc: "exit",
52
+ permission: "master"
53
  }
54
  ]
55
  })
 
56
  if (e) this.e = e
 
57
  }
58
+ key = "Yz:restart"
59
 
60
  init() {
61
  Bot.once("online", () => this.restartMsg())
62
+ this.e = {
63
+ reply: msg => Bot.sendMasterMsg(msg),
64
+ isMaster: true,
 
 
 
65
  }
66
+ if (cfg.bot.restart_time)
67
+ setTimeout(() => this.restart(), cfg.bot.restart_time*60000)
68
+
69
+ this.task = []
70
+ if (cfg.bot.restart_cron)
71
+ for (const i of Array.isArray(cfg.bot.restart_cron) ? cfg.bot.restart_cron : [cfg.bot.restart_cron])
72
+ this.task.push({
73
+ name: "定时重启",
74
+ cron: i,
75
+ fnc: () => this.restart(),
76
+ })
77
+ if (cfg.bot.stop_cron)
78
+ for (const i of Array.isArray(cfg.bot.stop_cron) ? cfg.bot.stop_cron : [cfg.bot.stop_cron])
79
+ this.task.push({
80
+ name: "定时关机",
81
+ cron: i,
82
+ fnc: () => this.stop(),
83
+ })
84
+ if (cfg.bot.start_cron)
85
+ for (const i of Array.isArray(cfg.bot.start_cron) ? cfg.bot.start_cron : [cfg.bot.start_cron])
86
+ this.task.push({
87
+ name: "定时开机",
88
+ cron: i,
89
+ fnc: () => new Start(this.e).start(),
90
+ })
91
  }
92
 
93
  async restartMsg() {
94
  let restart = await redis.get(this.key)
95
  if (!restart) return
96
+ await redis.del(this.key)
97
  restart = JSON.parse(restart)
 
 
 
 
98
  if (restart.isStop)
99
+ return this.stop(restart.time)
 
 
100
 
101
+ const time = Bot.getTimeDiff(restart.time)
102
+ const msg = [restart.isExit ? `开机成功,距离上次停止${time}` : `重启成功,用时${time}`]
103
+ if (restart.msg_id)
104
+ msg.unshift(segment.reply(restart.msg_id))
105
+
106
+ if (restart.group_id)
107
+ await Bot.sendGroupMsg(restart.bot_id, restart.group_id, msg)
108
+ else if (restart.user_id)
109
+ await Bot.sendFriendMsg(restart.bot_id, restart.user_id, msg)
110
+ else
111
+ await Bot.sendMasterMsg(msg)
112
  }
113
 
114
+ async set(isExit) {
115
+ if (temp.priority)
116
+ return redis.set(this.key, JSON.stringify({
117
+ isStop: true,
118
+ time: temp.stop_time,
119
+ }))
120
+ await this.reply(`开始${isExit ? "停止" : "重启"},本次运行时长${Bot.getTimeDiff()}`)
121
+ return redis.set(this.key, JSON.stringify({
122
+ isExit,
123
+ group_id: this.e.group_id,
124
+ user_id: this.e.user_id,
125
  bot_id: this.e.self_id,
126
  msg_id: this.e.message_id,
127
  time: Date.now(),
128
  }))
129
+ }
130
 
131
+ async restart() {
132
+ await this.set()
133
+ if (process.env.app_type === "pm2") {
134
  const ret = await Bot.exec("pnpm run restart")
135
  if (!ret.error) process.exit()
136
+ await this.reply(`重启错误\n${ret.error}\n${ret.stdout}\n${ret.stderr}`)
137
  Bot.makeLog("error", ["重启错误", ret])
138
+ } else process.exit()
 
139
  }
140
 
141
+ async stop(time) {
142
+ if (temp.priority) return false
143
+ temp.priority = PluginsLoader.priority
144
+ PluginsLoader.priority = [{ class: Start }]
145
+ if (typeof time === "number") return temp.stop_time = time
146
+ temp.stop_time = Date.now()
147
+ return this.reply(`关机成功,本次运行时长${Bot.getTimeDiff(temp.start_time)}`)
148
+ }
 
 
149
 
150
+ async exit() {
151
+ await this.set(true)
152
+ if (process.env.app_type === "pm2") {
153
  const ret = await Bot.exec("pnpm stop")
154
+ await this.reply(`停止错误\n${ret.error}\n${ret.stdout}\n${ret.stderr}`)
155
+ Bot.makeLog("error", ["停止错误", ret])
156
+ } else process.exit(1)
 
157
  }
158
  }
Yunzai/plugins/other/sendLog.js CHANGED
@@ -50,7 +50,7 @@ export class sendLog extends plugin {
50
  }
51
 
52
  async getLog(logFile) {
53
- let log = await fs.readFile(logFile, "utf-8")
54
  log = log.split("\n")
55
 
56
  if (this.keyWord) {
 
50
  }
51
 
52
  async getLog(logFile) {
53
+ let log = await fs.readFile(logFile, "utf8")
54
  log = log.split("\n")
55
 
56
  if (this.keyWord) {
Yunzai/plugins/other/update.js CHANGED
@@ -1,47 +1,62 @@
1
- import cfg from '../../lib/config/config.js'
2
- import lodash from 'lodash'
3
- import fs from 'node:fs/promises'
4
- import { Restart } from './restart.js'
5
 
6
  let uping = false
7
 
8
  export class update extends plugin {
9
  constructor() {
10
  super({
11
- name: '更新',
12
- dsc: '#更新 #强制更新',
13
- event: 'message',
14
  priority: 4000,
15
  rule: [
16
  {
17
- reg: '^#更新日志',
18
- fnc: 'updateLog'
19
  },
20
  {
21
- reg: '^#(强制)?更新',
22
- fnc: 'update'
23
  },
24
  {
25
- reg: '^#全部(强制)?更新$',
26
- fnc: 'updateAll',
27
- permission: 'master'
28
  }
29
  ]
30
  })
 
 
 
 
 
 
31
 
32
- this.typeName = 'TRSS-Yunzai'
 
 
33
  }
34
 
35
  init() {
36
- if (cfg.bot.update_time) {
37
- this.e = {
38
- isMaster: true,
39
- logFnc: "[自动更新]",
40
- msg: "#全部更新",
41
- reply: msg => Bot.sendMasterMsg(msg),
42
- }
43
- this.autoUpdate()
44
  }
 
 
 
 
 
 
 
 
 
 
 
45
  }
46
 
47
  autoUpdate() {
@@ -53,65 +68,58 @@ export class update extends plugin {
53
 
54
  async update() {
55
  if (!this.e.isMaster) return false
56
- if (uping) return this.reply('已有命令更新中..请勿重复操作')
57
-
58
- if (/详细|详情|面板|面版/.test(this.e.msg)) return false
 
59
 
60
  /** 获取插件 */
61
- let plugin = await this.getPlugin()
62
  if (plugin === false) return false
63
 
 
64
  await this.runUpdate(plugin)
65
 
66
- if (this.isPkgUp)
67
- await Bot.exec("pnpm install")
68
- if (this.isUp)
69
- this.restart()
70
  }
71
 
72
- async getPlugin(plugin = '') {
73
- if (!plugin) {
74
- plugin = this.e.msg.replace(/#(强制)?更新(日志)?/, '')
75
- if (!plugin) return ''
76
- }
77
-
78
- if (!await Bot.fsStat(`plugins/${plugin}/.git`)) return false
79
-
80
- this.typeName = plugin
81
- return plugin
82
  }
83
 
84
- async runUpdate(plugin = '') {
85
- this.isNowUp = false
86
-
87
- let cm = 'git pull --no-rebase'
88
 
89
- let type = '更新'
90
- if (this.e.msg.includes('强制')) {
91
- type = '强制更新'
92
- cm = `git reset --hard && git pull --rebase --allow-unrelated-histories`
93
  }
94
- if (plugin) cm = `cd "plugins/${plugin}" && ${cm}`
95
-
96
- this.oldCommitId = await this.getcommitId(plugin)
97
 
98
- logger.mark(`${this.e.logFnc} 开始${type}:${this.typeName}`)
 
 
 
99
 
100
- await this.reply(`开始${type} ${this.typeName}`)
101
- uping = true
102
- const ret = await Bot.exec(cm)
103
- uping = false
104
-
105
- ret.stdout = String(ret.stdout)
106
- if (ret.error) {
107
- logger.mark(`${this.e.logFnc} 更新失败:${this.typeName}`)
108
- this.gitErr(Bot.String(ret.error), ret.stdout)
109
  return false
110
  }
111
 
112
  const time = await this.getTime(plugin)
113
- if (/Already up|已经是最新/g.test(ret.stdout)) {
114
- await this.reply(`${this.typeName} 已是最新\n最后更新时间:${time}`)
 
115
  } else {
116
  this.isUp = true
117
  if (/package\.json/.test(ret.stdout))
@@ -124,100 +132,117 @@ export class update extends plugin {
124
  return true
125
  }
126
 
127
- async getcommitId(plugin = '') {
128
- let cm = 'git rev-parse --short HEAD'
129
- if (plugin) cm = `cd "plugins/${plugin}" && ${cm}`
130
- cm = await Bot.exec(cm)
131
- return lodash.trim(String(cm.stdout))
132
  }
133
 
134
- async getTime(plugin = '') {
135
- let cm = 'git log -1 --pretty=%cd --date=format:"%F %T"'
136
- if (plugin) cm = `cd "plugins/${plugin}" && ${cm}`
137
- cm = await Bot.exec(cm)
138
- return lodash.trim(String(cm.stdout))
139
  }
140
 
141
- async gitErr(error, stdout) {
142
- const msg = '更新失败!'
 
143
 
144
- if (error.includes('Timed out')) {
145
- const remote = error.match(/'(.+?)'/g)[0].replace(/'/g, '')
146
- return this.reply(`${msg}\n连接超时:${remote}`)
147
- }
148
 
149
- if (/Failed to connect|unable to access/g.test(error)) {
150
- const remote = error.match(/'(.+?)'/g)[0].replace(/'/g, '')
151
- return this.reply(`${msg}\n连接失败:${remote}`)
152
- }
 
 
 
153
 
154
- if (error.includes('be overwritten by merge')) {
155
- return this.reply(`${msg}\n存在冲突:\n${error}\n请解决冲突后再更新,或者执行#强制更新,放弃本地修改`)
 
 
156
  }
157
 
158
- if (stdout.includes('CONFLICT')) {
159
- return this.reply(`${msg}\n存在冲突:\n${error}${stdout}\n请解决冲突后再更新,或者执行#强制更新,放弃本地修改`)
 
 
 
 
160
  }
 
 
161
 
162
- return this.reply([error, stdout])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  }
164
 
165
  async updateAll() {
166
- const dirs = await fs.readdir('./plugins/')
 
 
 
167
 
 
168
  await this.runUpdate()
169
-
170
- for (let plu of dirs) {
171
- plu = await this.getPlugin(plu)
172
- if (plu === false) continue
173
- await this.runUpdate(plu)
174
  }
175
 
176
- if (this.isPkgUp)
177
- await Bot.exec("pnpm install")
178
- if (this.isUp)
179
- this.restart()
 
 
 
 
 
 
 
180
  }
181
 
182
  restart() {
183
  new Restart(this.e).restart()
184
  }
185
 
186
- async getLog(plugin = '') {
187
- let cm = 'git log -100 --pretty="%h||[%cd] %s" --date=format:"%F %T"'
188
- if (plugin) cm = `cd "plugins/${plugin}" && ${cm}`
189
-
190
- cm = await Bot.exec(cm)
191
- if (cm.error) {
192
- logger.error(cm.error)
193
- await this.reply(String(cm.error))
194
- }
195
- const logAll = String(cm.stdout).trim().split('\n')
196
 
 
197
  if (!logAll.length) return false
198
 
199
  let log = []
200
  for (let str of logAll) {
201
- str = str.split('||')
202
- if (str[0] == this.oldCommitId) break
203
- if (str[1].includes('Merge branch')) continue
204
  log.push(str[1])
205
  }
206
- let line = log.length
207
- log = log.join('\n\n')
208
-
209
- if (log.length <= 0) return ''
210
-
211
- cm = 'git config -l'
212
- if (plugin) cm = `cd "plugins/${plugin}" && ${cm}`
213
- cm = await Bot.exec(cm)
214
- const end = String(cm.stdout).match(/remote\..*\.url=.+/g).join('\n\n').replace(/remote\..*\.url=/g, '').replace(/\/\/([^@]+)@/, '//')
215
- if (cm.error) {
216
- logger.error(cm.error)
217
- await this.reply(String(cm.error))
218
- }
219
 
220
- return Bot.makeForwardArray([`${plugin || 'TRSS-Yunzai'} 更新日志,共${line}条`, log, end])
221
  }
222
 
223
  async updateLog() {
@@ -225,4 +250,4 @@ export class update extends plugin {
225
  if (plugin === false) return false
226
  return this.reply(await this.getLog(plugin))
227
  }
228
- }
 
1
+ import cfg from "../../lib/config/config.js"
2
+ import fs from "node:fs/promises"
3
+ import { Restart } from "./restart.js"
 
4
 
5
  let uping = false
6
 
7
  export class update extends plugin {
8
  constructor() {
9
  super({
10
+ name: "更新",
11
+ dsc: "#更新 #强制更新",
12
+ event: "message",
13
  priority: 4000,
14
  rule: [
15
  {
16
+ reg: "^#更新日志",
17
+ fnc: "updateLog"
18
  },
19
  {
20
+ reg: "^#(安?静)?(强制)?更新",
21
+ fnc: "update"
22
  },
23
  {
24
+ reg: "^#全部(安?静)?(强制)?更新$",
25
+ fnc: "updateAll",
26
+ permission: "master"
27
  }
28
  ]
29
  })
30
+ this.typeName = "TRSS-Yunzai"
31
+ }
32
+
33
+ get quiet() {
34
+ return /^#(全部)?(安?静)/.test(this.e.msg)
35
+ }
36
 
37
+ exec(cmd, plugin, opts = {}) {
38
+ if (plugin) opts.cwd = `plugins/${plugin}`
39
+ return Bot.exec(cmd, opts)
40
  }
41
 
42
  init() {
43
+ this.e = {
44
+ isMaster: true,
45
+ logFnc: "[自动更新]",
46
+ msg: "#全部静更新",
47
+ reply: msg => Bot.sendMasterMsg(msg),
 
 
 
48
  }
49
+ if (cfg.bot.update_time)
50
+ this.autoUpdate()
51
+
52
+ this.task = []
53
+ if (cfg.bot.update_cron)
54
+ for (const i of Array.isArray(cfg.bot.update_cron) ? cfg.bot.update_cron : [cfg.bot.update_cron])
55
+ this.task.push({
56
+ name: "定时更新",
57
+ cron: i,
58
+ fnc: () => this.updateAll(),
59
+ })
60
  }
61
 
62
  autoUpdate() {
 
68
 
69
  async update() {
70
  if (!this.e.isMaster) return false
71
+ if (uping) {
72
+ await this.reply("正在更新,请稍候再试")
73
+ return false
74
+ }
75
 
76
  /** 获取插件 */
77
+ const plugin = await this.getPlugin()
78
  if (plugin === false) return false
79
 
80
+ uping = true
81
  await this.runUpdate(plugin)
82
 
83
+ if (this.isPkgUp) await this.updatePackage()
84
+ if (this.isUp) this.restart()
85
+ uping = false
 
86
  }
87
 
88
+ async getPlugin(plugin = this.e.msg.replace(/#(安?静)?(强制)?更新(日志)?/, "")) {
89
+ if (!plugin) return ""
90
+ for (const i of [plugin, `${plugin}-Plugin`, `${plugin}-plugin`])
91
+ if (await Bot.fsStat(`plugins/${i}/.git`)) {
92
+ this.typeName = i
93
+ return i
94
+ }
95
+ return false
 
 
96
  }
97
 
98
+ async runUpdate(plugin = "") {
99
+ let cm = "git pull"
100
+ let type = "更新"
101
+ if (!plugin) cm = `git checkout package.json && ${cm}`
102
 
103
+ if (this.e.msg.includes("强制")) {
104
+ type = "强制更新"
105
+ cm = `git reset --hard ${await this.getRemoteBranch(true, plugin)} && git pull --rebase`
 
106
  }
107
+ this.oldCommitId = await this.getCommitId(plugin)
 
 
108
 
109
+ logger.mark(`${this.e.logFnc} 开始${type} ${this.typeName}`)
110
+ if (!this.quiet)
111
+ await this.reply(`开始${type} ${this.typeName}`)
112
+ const ret = await this.exec(cm, plugin)
113
 
114
+ if (ret.error && !await this.gitErr(plugin, ret.stdout, ret.error.message)) {
115
+ logger.mark(`${this.e.logFnc} 更新失败 ${this.typeName}`)
 
 
 
 
 
 
 
116
  return false
117
  }
118
 
119
  const time = await this.getTime(plugin)
120
+ if (/Already up|已经是最新/.test(ret.stdout)) {
121
+ if (!this.quiet)
122
+ await this.reply(`${this.typeName} 已是最新\n最后更新时间:${time}`)
123
  } else {
124
  this.isUp = true
125
  if (/package\.json/.test(ret.stdout))
 
132
  return true
133
  }
134
 
135
+ async getCommitId(...args) {
136
+ return (await this.exec("git rev-parse --short HEAD", ...args)).stdout
 
 
 
137
  }
138
 
139
+ async getTime(...args) {
140
+ return (await this.exec('git log -1 --pretty=%cd --date=format:"%F %T"', ...args)).stdout
 
 
 
141
  }
142
 
143
+ async getBranch(...args) {
144
+ return (await this.exec("git branch --show-current", ...args)).stdout
145
+ }
146
 
147
+ async getRemote(branch, ...args) {
148
+ return (await this.exec(`git config branch.${branch}.remote`, ...args)).stdout
149
+ }
 
150
 
151
+ async getRemoteBranch(string, ...args) {
152
+ const branch = await this.getBranch(...args)
153
+ if (!branch && string) return ""
154
+ const remote = await this.getRemote(branch, ...args)
155
+ if (!remote && string) return ""
156
+ return string ? `${remote}/${branch}` : { remote, branch }
157
+ }
158
 
159
+ async getRemoteUrl(branch, hide, ...args) {
160
+ if (branch) {
161
+ const url = (await this.exec(`git config remote.${branch}.url`, ...args)).stdout
162
+ return hide ? url.replace(/\/\/([^@]+)@/, "//") : url
163
  }
164
 
165
+ const ret = await this.exec("git config -l", ...args)
166
+ const urls = {}
167
+ for (const i of ret.stdout.match(/remote\..*?\.url=.+/g) || []) {
168
+ const branch = i.replace(/remote\.(.*?)\.url=.+/g, "$1")
169
+ const url = i.replace(/remote\..*?\.url=/g, "")
170
+ urls[branch] = (hide ? url.replace(/\/\/([^@]+)@/, "//") : url)
171
  }
172
+ return urls
173
+ }
174
 
175
+ gitErrUrl(error) {
176
+ return error.match(/'(.+?)'/g)[0].replace(/'(.+?)'/, "$1")
177
+ }
178
+
179
+ async gitErr(plugin, stdout, error) {
180
+ if (/unable to access|无法访问/.test(error))
181
+ await this.reply(`远程仓库连接错误:${this.gitErrUrl(error)}`)
182
+ else if (/not found|未找到|does not (exist|appear)|不存在|Authentication failed|鉴权失败/.test(error))
183
+ await this.reply(`远程仓库地址错误:${this.gitErrUrl(error)}`)
184
+ else if (/be overwritten by merge|被合并操作覆盖/.test(error) || /Merge conflict|合并冲突/.test(stdout))
185
+ await this.reply(`${error}\n${stdout}\n若修改过文件请手动更新,否则发送 #强制更新${plugin}`)
186
+ else if (/divergent branches|偏离的分支/.test(error)) {
187
+ const ret = await this.exec("git pull --rebase", plugin)
188
+ if (!ret.error && /Successfully rebased|成功变基/.test(ret.stdout+ret.stderr))
189
+ return true
190
+ await this.reply(`${error}\n${stdout}\n若修改过文件请手动更新,否则发送 #强制更新${plugin}`)
191
+ } else await this.reply(`${error}\n${stdout}\n未知错误,可尝试发送 #强制更新${plugin}`)
192
  }
193
 
194
  async updateAll() {
195
+ if (uping) {
196
+ await this.reply("正在更新,请稍候再试")
197
+ return false
198
+ }
199
 
200
+ uping = true
201
  await this.runUpdate()
202
+ for (let plugin of await fs.readdir("plugins")) {
203
+ plugin = await this.getPlugin(plugin)
204
+ if (plugin === false) continue
205
+ await this.runUpdate(plugin)
 
206
  }
207
 
208
+ if (this.isPkgUp) await this.updatePackage()
209
+ if (this.isUp) this.restart()
210
+ uping = false
211
+ }
212
+
213
+ async updatePackage() {
214
+ const cmd = "pnpm install"
215
+ if (process.platform === "win32")
216
+ return this.reply(`检测到依赖更新,请 #关机 后执行 ${cmd}`)
217
+ await this.reply("开始更新依赖")
218
+ return this.exec(cmd)
219
  }
220
 
221
  restart() {
222
  new Restart(this.e).restart()
223
  }
224
 
225
+ async getLog(plugin = "") {
226
+ let cm = await this.exec('git log -100 --pretty="%h||[%cd] %s" --date=format:"%F %T"', plugin)
227
+ if (cm.error) return this.reply(cm.error.message)
 
 
 
 
 
 
 
228
 
229
+ const logAll = cm.stdout.split("\n")
230
  if (!logAll.length) return false
231
 
232
  let log = []
233
  for (let str of logAll) {
234
+ str = str.split("||")
235
+ if (str[0] === this.oldCommitId) break
236
+ if (str[1].includes("Merge branch")) continue
237
  log.push(str[1])
238
  }
239
+ if (log.length <= 0) return ""
240
+
241
+ const msg = [`${plugin || "TRSS-Yunzai"} 更新日志,共${log.length}条`, log.join("\n\n")]
242
+ const end = await this.getRemoteUrl((await this.getRemoteBranch(false, plugin)).remote, true, plugin)
243
+ if (end) msg.push(end)
 
 
 
 
 
 
 
 
244
 
245
+ return Bot.makeForwardArray(msg)
246
  }
247
 
248
  async updateLog() {
 
250
  if (plugin === false) return false
251
  return this.reply(await this.getLog(plugin))
252
  }
253
+ }
Yunzai/plugins/other/version.js CHANGED
@@ -3,7 +3,7 @@ try {
3
  App = (await import("#miao")).App
4
  Common = (await import("#miao")).Common
5
  Version = (await import("#miao")).Version
6
- } catch (err) {}
7
 
8
  export let version = {}
9
  if (App) {
 
3
  App = (await import("#miao")).App
4
  Common = (await import("#miao")).Common
5
  Version = (await import("#miao")).Version
6
+ } catch {}
7
 
8
  export let version = {}
9
  if (App) {
Yunzai/plugins/system/master.js CHANGED
@@ -1,5 +1,5 @@
1
  import fs from "node:fs/promises"
2
- import { randomUUID } from "node:crypto"
3
  let code = {}
4
  let file = "config/config/other.yaml"
5
  export class master extends plugin {
@@ -35,18 +35,18 @@ export class master extends plugin {
35
  return false
36
  }
37
 
38
- code[this.e.user_id] = randomUUID()
39
- logger.mark(`${logger.cyan(`[${this.e.user_id}]`)} 设置主人验证码:${logger.green(code[this.e.user_id])}`)
40
  this.setContext("verify")
41
  await this.reply(`[${this.e.user_id}] 请输入验证码`, true)
42
  }
43
 
44
  async verify() {
45
  this.finish("verify")
46
- if (this.e.msg.trim() == code[this.e.user_id]) {
47
  await this.edit(file, "masterQQ", this.e.user_id)
48
  await this.edit(file, "master", `${this.e.self_id}:${this.e.user_id}`)
49
- await this.reply(`[${this.e.user_id}] 设置主人成功`, true)
50
  } else {
51
  await this.reply("验证码错误", true)
52
  return false
 
1
  import fs from "node:fs/promises"
2
+ import { ulid } from "ulid"
3
  let code = {}
4
  let file = "config/config/other.yaml"
5
  export class master extends plugin {
 
35
  return false
36
  }
37
 
38
+ code[this.e.user_id] = ulid()
39
+ logger.mark(`${logger.cyan(`[${this.e.user_id}]`)} 设置主人验证码 ${logger.green(code[this.e.user_id])}`)
40
  this.setContext("verify")
41
  await this.reply(`[${this.e.user_id}] 请输入验证码`, true)
42
  }
43
 
44
  async verify() {
45
  this.finish("verify")
46
+ if (this.e.msg.trim().toUpperCase() == code[this.e.user_id]) {
47
  await this.edit(file, "masterQQ", this.e.user_id)
48
  await this.edit(file, "master", `${this.e.self_id}:${this.e.user_id}`)
49
+ await this.reply(`[${this.e.user_id}] 设置主人完成`, true)
50
  } else {
51
  await this.reply("验证码错误", true)
52
  return false
Yunzai/plugins/system/status.js CHANGED
@@ -37,7 +37,7 @@ export class status extends plugin {
37
  let msg = "\n\n账号在线时长"
38
  for (const i of Bot.uin)
39
  if (Bot[i]?.stat?.start_time)
40
- msg += `\n${Bot.getTimeDiff(Bot[i].stat.start_time)} ${i}`
41
  return msg
42
  }
43
 
 
37
  let msg = "\n\n账号在线时长"
38
  for (const i of Bot.uin)
39
  if (Bot[i]?.stat?.start_time)
40
+ msg += `\n${Bot.getTimeDiff(Bot[i].stat.start_time*1000)} ${i}`
41
  return msg
42
  }
43
 
Yunzai/renderers/puppeteer/config_default.yaml CHANGED
@@ -21,3 +21,8 @@ args:
21
 
22
  # puppeteer截图超时时间
23
  puppeteerTimeout:
 
 
 
 
 
 
21
 
22
  # puppeteer截图超时时间
23
  puppeteerTimeout:
24
+
25
+ # 页面goto时的参数
26
+ pageGotoParams:
27
+ timeout: 120000
28
+ waitUntil: networkidle2
Yunzai/renderers/puppeteer/lib/puppeteer.js CHANGED
@@ -40,6 +40,10 @@ export default class Puppeteer extends Renderer {
40
  this.config.wsEndpoint = config.puppeteerWS || cfg?.bot?.puppeteer_ws
41
  /** puppeteer超时超时时间 */
42
  this.puppeteerTimeout = config.puppeteerTimeout || cfg?.bot?.puppeteer_timeout || 0
 
 
 
 
43
  }
44
 
45
  /**
@@ -74,7 +78,7 @@ export default class Puppeteer extends Renderer {
74
  await redis.del(this.browserMacKey)
75
  }
76
  }
77
- } catch (err) {}
78
 
79
  if (!this.browser || !connectFlag) {
80
  // 如果没有实例,初始化puppeteer
@@ -159,11 +163,11 @@ export default class Puppeteer extends Renderer {
159
  return false
160
  const pageHeight = data.multiPageHeight || 4000
161
 
162
- let savePath = this.dealTpl(name, data)
163
  if (!savePath) return false
164
 
165
  let buff = ""
166
- let start = Date.now()
167
 
168
  let ret = []
169
  this.shoting.push(name)
@@ -183,20 +187,20 @@ export default class Puppeteer extends Renderer {
183
 
184
  try {
185
  const page = await this.browser.newPage()
186
- let pageGotoParams = lodash.extend({ timeout: 120000 }, data.pageGotoParams || {})
187
  await page.goto(`file://${_path}${lodash.trim(savePath, ".")}`, pageGotoParams)
188
- let body = await page.$("#container") || await page.$("body")
189
 
190
  // 计算页面高度
191
  const boundingBox = await body.boundingBox()
192
  // 分页数
193
  let num = 1
194
 
195
- let randData = {
196
  type: data.imgType || "jpeg",
197
  omitBackground: data.omitBackground || false,
198
  quality: data.quality || 90,
199
- path: data.path || ""
200
  }
201
 
202
  if (data.multiPage) {
@@ -204,9 +208,8 @@ export default class Puppeteer extends Renderer {
204
  num = Math.round(boundingBox.height / pageHeight) || 1
205
  }
206
 
207
- if (data.imgType === "png") {
208
  delete randData.quality
209
- }
210
 
211
  if (!data.multiPage) {
212
  buff = await body.screenshot(randData)
 
40
  this.config.wsEndpoint = config.puppeteerWS || cfg?.bot?.puppeteer_ws
41
  /** puppeteer超时超时时间 */
42
  this.puppeteerTimeout = config.puppeteerTimeout || cfg?.bot?.puppeteer_timeout || 0
43
+ this.pageGotoParams = config.pageGotoParams || {
44
+ timeout: 120000,
45
+ waitUntil: "networkidle2",
46
+ }
47
  }
48
 
49
  /**
 
78
  await redis.del(this.browserMacKey)
79
  }
80
  }
81
+ } catch {}
82
 
83
  if (!this.browser || !connectFlag) {
84
  // 如果没有实例,初始化puppeteer
 
163
  return false
164
  const pageHeight = data.multiPageHeight || 4000
165
 
166
+ const savePath = this.dealTpl(name, data)
167
  if (!savePath) return false
168
 
169
  let buff = ""
170
+ const start = Date.now()
171
 
172
  let ret = []
173
  this.shoting.push(name)
 
187
 
188
  try {
189
  const page = await this.browser.newPage()
190
+ const pageGotoParams = lodash.extend(this.pageGotoParams, data.pageGotoParams || {})
191
  await page.goto(`file://${_path}${lodash.trim(savePath, ".")}`, pageGotoParams)
192
+ const body = await page.$("#container") || await page.$("body")
193
 
194
  // 计算页面高度
195
  const boundingBox = await body.boundingBox()
196
  // 分页数
197
  let num = 1
198
 
199
+ const randData = {
200
  type: data.imgType || "jpeg",
201
  omitBackground: data.omitBackground || false,
202
  quality: data.quality || 90,
203
+ path: data.path || "",
204
  }
205
 
206
  if (data.multiPage) {
 
208
  num = Math.round(boundingBox.height / pageHeight) || 1
209
  }
210
 
211
+ if (data.imgType === "png")
212
  delete randData.quality
 
213
 
214
  if (!data.multiPage) {
215
  buff = await body.screenshot(randData)