dfghfhgfg commited on
Commit
02fc7f7
·
verified ·
1 Parent(s): 452bbbd

Upload 44 files

Browse files
Yunzai/.gitignore CHANGED
@@ -137,7 +137,6 @@ config/test/*
137
  data/
138
  !config/test/default.yaml
139
  logs/
140
- resources/
141
 
142
  # Docker file
143
  redis
 
137
  data/
138
  !config/test/default.yaml
139
  logs/
 
140
 
141
  # Docker file
142
  redis
Yunzai/.npmrc CHANGED
@@ -1,3 +1,5 @@
1
  registry=https://registry.npmmirror.com
2
  node_sqlite3_binary_host_mirror=https://npmmirror.com/mirrors/sqlite3
3
- canvas_binary_host_mirror=https://npmmirror.com/mirrors/canvas
 
 
 
1
  registry=https://registry.npmmirror.com
2
  node_sqlite3_binary_host_mirror=https://npmmirror.com/mirrors/sqlite3
3
+ canvas_binary_host_mirror=https://npmmirror.com/mirrors/canvas
4
+ sharp_binary_host=https://npmmirror.com/mirrors/sharp
5
+ sharp_libvips_binary_host=https://npmmirror.com/mirrors/sharp-libvips
Yunzai/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # 3.1.1
2
 
3
  * 支持协议端:米游社大别野Bot
 
1
+ # 3.1.3
2
+
3
+ * 支持协议端:QQBot
4
+ * **请注意:**
5
+ * 从3.1.3版本开始,原genshin包内的功能会逐步重构,与miao-plugin进行整合,以降低后续游戏版本升级时的维护成本
6
+ * 在整合过程中,可能会移除一些重复或迁移成本较高的功能,以及可能会有功能不稳定情况
7
+ * 如介意,请切换至**v312-backup**分支
8
+
9
+ # 3.1.2
10
+
11
+ * 支持协议端:OPQBot
12
+ * 新增`#绑定用户`命令
13
+ * 可将其他QQ绑定至当前用户,以打通多个用户,子用户使用主用户的CK与UID等信息
14
+ * 同时也可绑定其他平台的用户,例如频道、微信等。如需链接至其他平台需使用TRSS-Yunzai或Lain-plugin
15
+ * 部分命令可能无法识别绑定后的主用户,如遇问题可反馈
16
+
17
  # 3.1.1
18
 
19
  * 支持协议端:米游社大别野Bot
Yunzai/README.md CHANGED
@@ -2,7 +2,7 @@
2
 
3
  # TRSS-Yunzai
4
 
5
- Yunzai 应用端,支持多账号,支持协议端:go-cqhttp、ComWeChat、GSUIDCore、ICQQ、QQ频道、微信、KOOK、Telegram、Discord
6
 
7
  [![访问量](https://visitor-badge.glitch.me/badge?page_id=TimeRainStarSky.Yunzai&right_color=red&left_text=访%20问%20量)](https://github.com/TimeRainStarSky/Yunzai)
8
  [![Stars](https://img.shields.io/github/stars/TimeRainStarSky/Yunzai?color=yellow&label=收藏)](../../stargazers)
@@ -118,6 +118,12 @@ ws://localhost:2536/GSUIDCore
118
 
119
  </details>
120
 
 
 
 
 
 
 
121
  <details><summary>QQ频道</summary>
122
 
123
  [TRSS-Yunzai QQGuild Plugin](../../../Yunzai-QQGuild-Plugin)
@@ -154,9 +160,19 @@ ws://localhost:2536/GSUIDCore
154
 
155
  </details>
156
 
157
- <details><summary>代理</summary>
 
 
 
 
 
 
 
 
 
 
158
 
159
- [TRSS-Yunzai Proxy Plugin](../../../Yunzai-Proxy-Plugin)
160
 
161
  </details>
162
 
 
2
 
3
  # TRSS-Yunzai
4
 
5
+ Yunzai 应用端,支持多账号,支持协议端:go-cqhttp、ComWeChat、GSUIDCore、ICQQ、QQBot、QQ频道、微信、KOOK、Telegram、Discord、OPQBot
6
 
7
  [![访问量](https://visitor-badge.glitch.me/badge?page_id=TimeRainStarSky.Yunzai&right_color=red&left_text=访%20问%20量)](https://github.com/TimeRainStarSky/Yunzai)
8
  [![Stars](https://img.shields.io/github/stars/TimeRainStarSky/Yunzai?color=yellow&label=收藏)](../../stargazers)
 
118
 
119
  </details>
120
 
121
+ <details><summary>QQBot</summary>
122
+
123
+ [TRSS-Yunzai QQBot Plugin](../../../Yunzai-QQBot-Plugin)
124
+
125
+ </details>
126
+
127
  <details><summary>QQ频道</summary>
128
 
129
  [TRSS-Yunzai QQGuild Plugin](../../../Yunzai-QQGuild-Plugin)
 
160
 
161
  </details>
162
 
163
+ <details><summary>OPQBot</summary>
164
+
165
+ 下载运行 [OPQBot](https://opqbot.com),启动参数添加:
166
+
167
+ ```
168
+ -wsserver ws://localhost:2536/OPQBot
169
+ ```
170
+
171
+ </details>
172
+
173
+ <details><summary>路由</summary>
174
 
175
+ [TRSS-Yunzai Route Plugin](../../../Yunzai-Route-Plugin)
176
 
177
  </details>
178
 
Yunzai/lib/bot.js CHANGED
@@ -7,48 +7,61 @@ import express from "express"
7
  import http from "http"
8
  import { WebSocketServer } from "ws"
9
  import _ from "lodash"
 
 
 
 
 
 
10
 
11
  export default class Yunzai extends EventEmitter {
12
  constructor() {
13
  super()
14
  this.uin = []
15
  this.adapter = []
 
16
  this.express = express()
17
  this.server = http.createServer(this.express)
18
- this.server.on("upgrade", (req, socket, head) => {
19
- this.wss.handleUpgrade(req, socket, head, conn => {
20
- conn.id = `${req.connection.remoteAddress}-${req.headers["sec-websocket-key"]}`
21
- this.makeLog("mark", `${logger.blue(`[${conn.id} <=> ${req.url}]`)} 建立连接:${JSON.stringify(req.headers)}`)
22
- conn.on("error", logger.error)
23
- conn.on("close", () => this.makeLog("mark", `${logger.blue(`[${conn.id} <≠> ${req.url}]`)} 断开连接`))
24
- conn.on("message", msg => this.makeLog("debug", `${logger.blue(`[${conn.id} => ${req.url}]`)} 消息:${String(msg).trim()}`))
25
- conn.sendMsg = msg => {
26
- if (typeof msg == "object")
27
- msg = JSON.stringify(msg)
28
- this.makeLog("debug", `${logger.blue(`[${conn.id} <= ${req.url}]`)} 消息:${msg}`)
29
- return conn.send(msg)
30
- }
31
- for (const i of this.wsf[req.url.split("/")[1]] || [])
32
- i(conn, req, socket, head)
33
- })
34
- })
35
  this.wss = new WebSocketServer({ noServer: true })
36
  this.wsf = {}
 
 
 
 
 
37
  }
38
 
39
- makeLog(level, msg) {
40
- logger[level](_.truncate(msg, { length: cfg.bot.logLength }))
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  }
42
 
43
- em(name = "", data = {}) {
44
- if (data.self_id)
45
- Object.defineProperty(data, "bot", { value: Bot[data.self_id] })
46
- while (true) {
47
- this.emit(name, data)
48
- const i = name.lastIndexOf(".")
49
- if (i == -1) break
50
- name = name.slice(0, i)
51
- }
 
 
 
52
  }
53
 
54
  async run() {
@@ -59,21 +72,124 @@ export default class Yunzai extends EventEmitter {
59
  this.emit("online", this)
60
  }
61
 
62
- serverLoad() {
63
- this.express.use(req => {
64
- logger.mark(`${logger.blue(`[${req.ip} => ${req.url}]`)} HTTP ${req.method} 请求:${JSON.stringify(req.headers)}`)
65
- req.res.redirect("https://github.com/TimeRainStarSky/Yunzai")
66
- })
67
 
68
- this.server.listen(cfg.bot.port, () => {
69
- const host = this.server.address().address
70
- const port = this.server.address().port
71
- logger.mark(`启动 HTTP 服务器:${logger.green(`http://[${host}]:${port}`)}`)
72
- for (const i of Object.keys(this.wsf))
73
- logger.info(`本机 ${i} 连接地址:${logger.blue(`ws://localhost:${port}/${i}`)}`)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  })
75
  }
76
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  getFriendArray() {
78
  const array = []
79
  for (const bot_id of this.uin)
@@ -185,7 +301,7 @@ export default class Yunzai extends EventEmitter {
185
  return false
186
  }
187
 
188
- async getFriendMsg(fnc = () => true) {
189
  if (typeof fnc != "function") {
190
  const { self_id, user_id } = fnc
191
  fnc = data => data.self_id == self_id && data.user_id == user_id
@@ -210,7 +326,7 @@ export default class Yunzai extends EventEmitter {
210
  }
211
 
212
  getMasterMsg() {
213
- return this.getFriendMsg(data =>
214
  cfg.master[data.self_id]?.includes(String(data.user_id)))
215
  }
216
 
 
7
  import http from "http"
8
  import { WebSocketServer } from "ws"
9
  import _ from "lodash"
10
+ import fs from "node:fs"
11
+ import fetch from "node-fetch"
12
+ import { randomUUID } from "node:crypto"
13
+ import { exec } from "child_process"
14
+ import { fileTypeFromBuffer } from "file-type"
15
+ import md5 from "md5"
16
 
17
  export default class Yunzai extends EventEmitter {
18
  constructor() {
19
  super()
20
  this.uin = []
21
  this.adapter = []
22
+
23
  this.express = express()
24
  this.server = http.createServer(this.express)
25
+
26
+ this.server.on("upgrade", (...args) => this.wsConnect(...args))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  this.wss = new WebSocketServer({ noServer: true })
28
  this.wsf = {}
29
+
30
+ this.fs = {}
31
+ this.express.use("/File", (...args) => this.fileSend(...args))
32
+ this.fileToUrl("resources/http/File/404.jpg", { name: 404, time: 0 })
33
+ this.fileToUrl("resources/http/File/timeout.jpg", { name: "timeout", time: 0 })
34
  }
35
 
36
+ wsConnect(req, socket, head) {
37
+ this.wss.handleUpgrade(req, socket, head, conn => {
38
+ conn.id = `${req.connection.remoteAddress}-${req.headers["sec-websocket-key"]}`
39
+ this.makeLog("mark", `${logger.blue(`[${conn.id} <=> ws://${req.headers.host}${req.url}]`)} 建立连接:${JSON.stringify(req.headers)}`)
40
+ conn.on("error", logger.error)
41
+ conn.on("close", () => this.makeLog("mark", `${logger.blue(`[${conn.id} <≠> ws://${req.headers.host}${req.url}]`)} 断开连接`))
42
+ conn.on("message", msg => this.makeLog("debug", `${logger.blue(`[${conn.id} => ws://${req.headers.host}${req.url}]`)} 消息:${String(msg).trim()}`))
43
+ conn.sendMsg = msg => {
44
+ if (!Buffer.isBuffer(msg)) msg = this.String(msg)
45
+ this.makeLog("debug", `${logger.blue(`[${conn.id} <= ws://${req.headers.host}${req.url}]`)} 消息:${msg}`)
46
+ return conn.send(msg)
47
+ }
48
+ for (const i of this.wsf[req.url.split("/")[1]] || [])
49
+ i(conn, req, socket, head)
50
+ })
51
  }
52
 
53
+ serverLoad() {
54
+ this.express.use(req => {
55
+ logger.mark(`${logger.blue(`[${req.ip} => http://${req.headers.host}${req.url}]`)} HTTP ${req.method} 请求:${JSON.stringify(req.headers)}`)
56
+ req.res.redirect("https://github.com/TimeRainStarSky/Yunzai")
57
+ })
58
+
59
+ this.server.listen(cfg.bot.port, () => {
60
+ logger.mark(`启动 HTTP 服务器:${logger.green(`http://[${this.server.address().address}]:${this.server.address().port}`)}`)
61
+ const url = cfg.bot.url.replace(/^http/, "ws")
62
+ for (const i of Object.keys(this.wsf))
63
+ logger.info(`${i} 连接地址:${logger.blue(`${url}/${i}`)}`)
64
+ })
65
  }
66
 
67
  async run() {
 
72
  this.emit("online", this)
73
  }
74
 
75
+ sleep(time) {
76
+ return new Promise(resolve => setTimeout(resolve, time))
77
+ }
 
 
78
 
79
+ String(data) {
80
+ switch (typeof data) {
81
+ case "string":
82
+ return data
83
+ case "object":
84
+ return JSON.stringify(data)
85
+ }
86
+ return String(data)
87
+ }
88
+
89
+ Buffer(data, opts = {}) {
90
+ if (Buffer.isBuffer(data)) return data
91
+ data = this.String(data)
92
+
93
+ if (data.match(/^base64:\/\//)) {
94
+ return Buffer.from(data.replace(/^base64:\/\//, ""), "base64")
95
+ } else if (data.match(/^https?:\/\//)) {
96
+ if (opts.http) return data
97
+ return (async () => Buffer.from(await (await fetch(data)).arrayBuffer()))()
98
+ } else if (fs.existsSync(data.replace(/^file:\/\//, ""))) {
99
+ if (opts.file) return data
100
+ return Buffer.from(fs.readFileSync(data.replace(/^file:\/\//, "")))
101
+ }
102
+ return data
103
+ }
104
+
105
+ async fileType(data, name) {
106
+ const file = {}
107
+ try {
108
+ if (Buffer.isBuffer(data)) {
109
+ file.url = name || "Buffer"
110
+ file.buffer = data
111
+ } else {
112
+ file.url = data.replace(/^base64:\/\/.*/, "base64://...")
113
+ file.buffer = await this.Buffer(data)
114
+ }
115
+ if (Buffer.isBuffer(file.buffer)) {
116
+ file.type = await fileTypeFromBuffer(file.buffer)
117
+ file.md5 = md5(file.buffer)
118
+ file.name = name || `${Date.now()}.${file.md5.slice(0,8)}.${file.type.ext}`
119
+ } else {
120
+ file.name = name || `${Date.now()}-path.basename(file.buffer)`
121
+ }
122
+ } catch (err) {
123
+ logger.error(`文件类型检测错误:${logger.red(err)}`)
124
+ }
125
+ return file
126
+ }
127
+
128
+ async fileToUrl(file, opts = {}) {
129
+ const { name, time = 60000, times } = opts
130
+
131
+ file = await this.fileType(file, name)
132
+ if (!Buffer.isBuffer(file.buffer)) return file.buffer
133
+ if (!file.name) file.name = randomUUID()
134
+
135
+ if (typeof times == "number") file.times = times
136
+ this.fs[file.name] = file
137
+ if (time) setTimeout(() => this.fs[file.name] = this.fs.timeout, time)
138
+ return `${cfg.bot.url}/File/${file.name}`
139
+ }
140
+
141
+ fileSend(req) {
142
+ const url = req.url.replace(/^\//, "")
143
+ let file = this.fs[url]
144
+ if (!file) file = this.fs[404]
145
+
146
+ if (typeof file.times == "number") {
147
+ if (file.times > 0) file.times = file.times-1
148
+ else file = this.fs.timeout
149
+ }
150
+
151
+ if (file.type?.mime)
152
+ req.res.setHeader("Content-Type", file.type.mime)
153
+
154
+ logger.mark(`${logger.blue(`[${req.ip} => http://${req.headers.host}/File/${url}]`)} HTTP ${req.method} 请求:${JSON.stringify(req.headers)}`)
155
+ logger.mark(`${logger.blue(`[${req.ip} <= http://${req.headers.host}/File/${url}]`)} 发送文件:${file.name}(${file.url} ${(file.buffer.length/1024).toFixed(2)}KB)`)
156
+ req.res.send(file.buffer)
157
+ }
158
+
159
+ async exec(cmd) {
160
+ return new Promise(resolve => {
161
+ this.makeLog("mark", `[命令执行开始] ${logger.blue(cmd)}`)
162
+ exec(cmd, (error, stdout, stderr) => {
163
+ this.makeLog("mark", `[命令执行完成] ${logger.blue(cmd)}${stdout?`\n${this.String(stdout).trim()}`:""}${stderr?logger.red(`\n${this.String(stderr).trim()}`):""}`)
164
+ if (error) this.makeLog("mark", `[命令执行错误] ${logger.blue(cmd)}\n${logger.red(this.String(error).trim())}`)
165
+ resolve({ error, stdout, stderr })
166
+ })
167
  })
168
  }
169
 
170
+ makeLog(level, msg, id) {
171
+ const log = []
172
+ if (id) log.push(logger.blue(`[${id}]`))
173
+ for (const i of Array.isArray(msg) ? msg : [msg]) {
174
+ if (i?.replace)
175
+ log.push(_.truncate(i.replace(/{"type":"Buffer","data":\[.*?\]}/g, "(Buffer)"), { length: cfg.bot.log_length }))
176
+ else
177
+ log.push(i)
178
+ }
179
+ logger[level](...log)
180
+ }
181
+
182
+ em(name = "", data = {}) {
183
+ if (data.self_id)
184
+ Object.defineProperty(data, "bot", { value: Bot[data.self_id] })
185
+ while (true) {
186
+ this.emit(name, data)
187
+ const i = name.lastIndexOf(".")
188
+ if (i == -1) break
189
+ name = name.slice(0, i)
190
+ }
191
+ }
192
+
193
  getFriendArray() {
194
  const array = []
195
  for (const bot_id of this.uin)
 
301
  return false
302
  }
303
 
304
+ async getTextMsg(fnc = () => true) {
305
  if (typeof fnc != "function") {
306
  const { self_id, user_id } = fnc
307
  fnc = data => data.self_id == self_id && data.user_id == user_id
 
326
  }
327
 
328
  getMasterMsg() {
329
+ return this.getTextMsg(data =>
330
  cfg.master[data.self_id]?.includes(String(data.user_id)))
331
  }
332
 
Yunzai/lib/config/config.js CHANGED
@@ -18,11 +18,11 @@ class Cfg {
18
  let path = "config/config/"
19
  let pathDef = "config/default_config/"
20
  const files = fs.readdirSync(pathDef).filter(file => file.endsWith(".yaml"))
21
- for (let file of files)
22
  if (!fs.existsSync(`${path}${file}`))
23
  fs.copyFileSync(`${pathDef}${file}`, `${path}${file}`)
24
- if (!fs.existsSync("data")) fs.mkdirSync("data")
25
- if (!fs.existsSync("resources")) fs.mkdirSync("resources")
26
  }
27
 
28
  /** Bot配置 */
@@ -69,10 +69,12 @@ class Cfg {
69
  const masters = {}
70
  for (let i of master) {
71
  i = i.split(":")
72
- if (Array.isArray(masters[i[0]]))
73
- masters[i[0]].push(i[1])
 
 
74
  else
75
- masters[i[0]] = [i[1]]
76
  }
77
  return masters
78
  }
 
18
  let path = "config/config/"
19
  let pathDef = "config/default_config/"
20
  const files = fs.readdirSync(pathDef).filter(file => file.endsWith(".yaml"))
21
+ for (const file of files)
22
  if (!fs.existsSync(`${path}${file}`))
23
  fs.copyFileSync(`${pathDef}${file}`, `${path}${file}`)
24
+ for (const i of ["data", "temp"])
25
+ if (!fs.existsSync(i)) fs.mkdirSync(i)
26
  }
27
 
28
  /** Bot配置 */
 
69
  const masters = {}
70
  for (let i of master) {
71
  i = i.split(":")
72
+ const bot_id = i.shift()
73
+ const user_id = i.join(":")
74
+ if (Array.isArray(masters[bot_id]))
75
+ masters[bot_id].push(user_id)
76
  else
77
+ masters[bot_id] = [user_id]
78
  }
79
  return masters
80
  }
Yunzai/lib/config/init.js CHANGED
@@ -1,28 +1,35 @@
1
- import setLog from './log.js'
2
- import redisInit from './redis.js'
3
- import { checkRun } from './check.js'
4
- import cfg from './config.js'
5
 
6
  /** 设置标题 */
7
- process.title = 'TRSS Yunzai'
8
 
9
  /** 设置时区 */
10
- process.env.TZ = 'Asia/Shanghai'
 
 
 
 
 
 
11
 
12
  /** 捕获未处理的Promise错误 */
13
- process.on('unhandledRejection', (error, promise) => {
14
- if (logger) {
15
- logger.error(error)
16
- } else {
17
- console.log(error)
18
- }
19
  })
20
 
21
  /** 退出事件 */
22
- process.on('exit', async code => {
23
- if (typeof redis != 'undefined' && typeof test == 'undefined')
24
  await redis.save()
25
- logger.mark(logger.magenta('TRSS-Yunzai 已停止运行'))
 
 
 
 
26
  })
27
 
28
  await checkInit()
@@ -32,11 +39,11 @@ async function checkInit() {
32
  /** 日志设置 */
33
  setLog()
34
 
35
- logger.mark('----^_^----')
36
  logger.mark(logger.yellow(`TRSS-Yunzai v${cfg.package.version} 启动中...`))
37
- logger.mark(logger.cyan('https://github.com/TimeRainStarSky/Yunzai'))
38
 
39
  await redisInit()
40
 
41
  checkRun()
42
- }
 
1
+ import setLog from "./log.js"
2
+ import redisInit from "./redis.js"
3
+ import { checkRun } from "./check.js"
4
+ import cfg from "./config.js"
5
 
6
  /** 设置标题 */
7
+ process.title = `TRSS Yunzai v${cfg.package.version} © 2023 - 2024 TimeRainStarSky`
8
 
9
  /** 设置时区 */
10
+ process.env.TZ = "Asia/Shanghai"
11
+
12
+ /** 捕获未处理的错误 */
13
+ process.on("uncaughtException", error => {
14
+ if (typeof logger == "undefined") console.log(error)
15
+ else logger.error(error)
16
+ })
17
 
18
  /** 捕获未处理的Promise错误 */
19
+ process.on("unhandledRejection", (error, promise) => {
20
+ if (typeof logger == "undefined") console.log(error)
21
+ else logger.error(error)
 
 
 
22
  })
23
 
24
  /** 退出事件 */
25
+ process.on("exit", async code => {
26
+ if (typeof redis != "undefined" && typeof test == "undefined")
27
  await redis.save()
28
+
29
+ if (typeof logger == "undefined")
30
+ console.log("TRSS-Yunzai 已停止运行")
31
+ else
32
+ logger.mark(logger.magenta("TRSS-Yunzai 已停止运行"))
33
  })
34
 
35
  await checkInit()
 
39
  /** 日志设置 */
40
  setLog()
41
 
42
+ logger.mark("----^_^----")
43
  logger.mark(logger.yellow(`TRSS-Yunzai v${cfg.package.version} 启动中...`))
44
+ logger.mark(logger.cyan("https://github.com/TimeRainStarSky/Yunzai"))
45
 
46
  await redisInit()
47
 
48
  checkRun()
49
+ }
Yunzai/lib/config/redis.js CHANGED
@@ -44,6 +44,7 @@ export default async function redisInit() {
44
  })
45
 
46
  /** 全局变量 redis */
 
47
  global.redis = client
48
  logger.info("Redis 连接成功")
49
  return client
 
44
  })
45
 
46
  /** 全局变量 redis */
47
+ client.url = redisUrl
48
  global.redis = client
49
  logger.info("Redis 连接成功")
50
  return client
Yunzai/lib/events/connect.js CHANGED
@@ -13,10 +13,10 @@ export default class connectEvent extends EventListener {
13
  if (!Bot.uin.includes(e.self_id))
14
  Bot.uin.push(e.self_id)
15
 
16
- if (!cfg.bot.online_msg) return
17
  const key = `Yz:loginMsg:${e.self_id}`
18
  if (await redis.get(key)) return
19
- redis.set(key, "1", { EX: cfg.bot.online_msg_exp })
20
  for (const i of cfg.master[e.self_id] || [])
21
  e.bot.pickFriend(i).sendMsg(`欢迎使用【TRSS-Yunzai v${cfg.package.version}】\n【#帮助】查看指令说明\n【#状态】查看运行状态\n【#日志】查看运行日志\n【#重启】重新启动\n【#更新】拉取 Git 更新\n【#全部更新】更新全部插件\n【#更新日志】查看更新日志\n【#设置主人】设置主人账号\n【#安装插件】查看可安装插件`)
22
  }
 
13
  if (!Bot.uin.includes(e.self_id))
14
  Bot.uin.push(e.self_id)
15
 
16
+ if (!cfg.bot.online_msg_exp) return
17
  const key = `Yz:loginMsg:${e.self_id}`
18
  if (await redis.get(key)) return
19
+ redis.set(key, "1", { EX: cfg.bot.online_msg_exp*60 })
20
  for (const i of cfg.master[e.self_id] || [])
21
  e.bot.pickFriend(i).sendMsg(`欢迎使用【TRSS-Yunzai v${cfg.package.version}】\n【#帮助】查看指令说明\n【#状态】查看运行状态\n【#日志】查看运行日志\n【#重启】重新启动\n【#更新】拉取 Git 更新\n【#全部更新】更新全部插件\n【#更新日志】查看更新日志\n【#设置主人】设置主人账号\n【#安装插件】查看可安装插件`)
22
  }
Yunzai/lib/events/online.js CHANGED
@@ -5,14 +5,14 @@ import cfg from '../config/config.js'
5
  * 监听上线事件
6
  */
7
  export default class onlineEvent extends EventListener {
8
- constructor () {
9
  super({
10
  event: 'online',
11
  once: true
12
  })
13
  }
14
 
15
- async execute () {
16
  logger.mark('----^_^----')
17
  }
18
  }
 
5
  * 监听上线事件
6
  */
7
  export default class onlineEvent extends EventListener {
8
+ constructor() {
9
  super({
10
  event: 'online',
11
  once: true
12
  })
13
  }
14
 
15
+ async execute() {
16
  logger.mark('----^_^----')
17
  }
18
  }
Yunzai/lib/modules/oicq/index.js CHANGED
@@ -2,20 +2,12 @@ import fs from "node:fs"
2
  import path from "node:path"
3
 
4
  function toSegment(type, data) {
5
- for (const i in data) {
6
- switch (typeof data[i]) {
7
- case "string":
8
- if ((i == "file" || data[i].match(/^file:\/\//)) && fs.existsSync(data[i].replace(/^file:\/\//, ""))) {
9
- if (i == "file" && !data.name)
10
- data.name = path.basename(data[i])
11
- data[i] = `base64://${fs.readFileSync(data[i].replace(/^file:\/\//, "")).toString("base64")}`
12
- }
13
- break
14
- case "object":
15
- if (Buffer.isBuffer(data[i]))
16
- data[i] = `base64://${data[i].toString("base64")}`
17
  }
18
- }
19
  return { type, ...data }
20
  }
21
 
@@ -23,6 +15,15 @@ const segment = new class segment {
23
  custom(type, data) {
24
  return toSegment(type, data)
25
  }
 
 
 
 
 
 
 
 
 
26
  image(file, name) {
27
  return toSegment("image", { file, name })
28
  }
 
2
  import path from "node:path"
3
 
4
  function toSegment(type, data) {
5
+ for (const i in data)
6
+ if (typeof data[i] == "string" && (i == "file" || data[i].match(/^file:\/\//)) && fs.existsSync(data[i].replace(/^file:\/\//, ""))) {
7
+ if (i == "file" && !data.name)
8
+ data.name = `${Date.now()}-${path.basename(data[i])}`
9
+ data[i] = fs.readFileSync(data[i].replace(/^file:\/\//, ""))
 
 
 
 
 
 
 
10
  }
 
11
  return { type, ...data }
12
  }
13
 
 
15
  custom(type, data) {
16
  return toSegment(type, data)
17
  }
18
+ raw(data) {
19
+ return toSegment("raw", { data })
20
+ }
21
+ button(...data) {
22
+ return toSegment("button", { data })
23
+ }
24
+ markdown(data) {
25
+ return toSegment("markdown", { data })
26
+ }
27
  image(file, name) {
28
  return toSegment("image", { file, name })
29
  }
Yunzai/lib/plugins/loader.js CHANGED
@@ -134,7 +134,7 @@ class PluginsLoader {
134
  for (let val of files) {
135
  let filepath = "../../plugins/" + val.name
136
  let tmp = {
137
- name: val.name,
138
  }
139
  if (val.isFile()) {
140
  if (!val.name.endsWith(".js")) continue
@@ -229,8 +229,16 @@ class PluginsLoader {
229
 
230
  // 判断是否是星铁命令,若是星铁命令则标准化处理
231
  // e.isSr = true,且命令标准化为 #星铁 开头
 
 
 
 
 
 
 
 
232
  if (this.srReg.test(e.msg)) {
233
- e.isSr = true
234
  e.msg = e.msg.replace(this.srReg, "#星铁")
235
  }
236
 
@@ -260,7 +268,7 @@ class PluginsLoader {
260
  e.logFnc = `[${plugin.name}][${v.fnc}]`
261
 
262
  if (v.log !== false) {
263
- logger.mark(`${e.logFnc}${e.logText} ${lodash.truncate(e.msg, { length: 80 })}`)
264
  }
265
 
266
  /** 判断权限 */
@@ -521,9 +529,7 @@ class PluginsLoader {
521
  try {
522
  res = await e.replyNew(msg)
523
  } catch (err) {
524
- if (typeof msg != "string")
525
- msg = lodash.truncate(JSON.stringify(msg), { length: 300 })
526
- logger.error(`发送消息错误:${msg}`)
527
  logger.error(err)
528
  }
529
 
@@ -631,25 +637,19 @@ class PluginsLoader {
631
  if (e.isGroup && e?.group?.mute_left > 0) return false
632
  if (!e.message || e.isPrivate) return true
633
 
634
- let config = cfg.getGroup(e.self_id, e.group_id)
635
 
636
- if (config.groupCD && this.groupCD[e.group_id]) {
637
  return false
638
- }
639
- if (config.singleCD && this.singleCD[`${e.group_id}.${e.user_id}`]) {
640
  return false
641
- }
642
 
643
- let { msgThrottle } = this
 
644
 
645
- let msgId = e.user_id + ':' + e.raw_message
646
- if (msgThrottle[msgId]) {
647
- return false
648
- }
649
- msgThrottle[msgId] = true
650
- setTimeout(() => {
651
- delete msgThrottle[msgId]
652
- }, 200)
653
 
654
  return true
655
  }
 
134
  for (let val of files) {
135
  let filepath = "../../plugins/" + val.name
136
  let tmp = {
137
+ name: val.name
138
  }
139
  if (val.isFile()) {
140
  if (!val.name.endsWith(".js")) continue
 
229
 
230
  // 判断是否是星铁命令,若是星铁命令则标准化处理
231
  // e.isSr = true,且命令标准化为 #星铁 开头
232
+ Object.defineProperty(e, "isSr", {
233
+ get: () => e.game === "sr",
234
+ set: (v) => e.game = v ? "sr" : "gs"
235
+ })
236
+ Object.defineProperty(e, "isGs", {
237
+ get: () => e.game === "gs",
238
+ set: (v) => e.game = v ? "gs" : "sr"
239
+ })
240
  if (this.srReg.test(e.msg)) {
241
+ e.game = "sr"
242
  e.msg = e.msg.replace(this.srReg, "#星铁")
243
  }
244
 
 
268
  e.logFnc = `[${plugin.name}][${v.fnc}]`
269
 
270
  if (v.log !== false) {
271
+ logger.info(`${e.logFnc}${e.logText} ${lodash.truncate(e.msg, { length: 80 })}`)
272
  }
273
 
274
  /** 判断权限 */
 
529
  try {
530
  res = await e.replyNew(msg)
531
  } catch (err) {
532
+ Bot.makeLog("error", `发送消息错误:${Bot.String(msg)}`, e.self_id)
 
 
533
  logger.error(err)
534
  }
535
 
 
637
  if (e.isGroup && e?.group?.mute_left > 0) return false
638
  if (!e.message || e.isPrivate) return true
639
 
640
+ const config = cfg.getGroup(e.self_id, e.group_id)
641
 
642
+ if (config.groupCD && this.groupCD[e.group_id])
643
  return false
644
+
645
+ if (config.singleCD && this.singleCD[`${e.group_id}.${e.user_id}`])
646
  return false
 
647
 
648
+ const msgId = `${e.self_id}:${e.user_id}:${e.raw_message}`
649
+ if (this.msgThrottle[msgId]) return false
650
 
651
+ this.msgThrottle[msgId] = true
652
+ setTimeout(() => delete this.msgThrottle[msgId], 1000)
 
 
 
 
 
 
653
 
654
  return true
655
  }
Yunzai/lib/plugins/plugin.js CHANGED
@@ -1,3 +1,5 @@
 
 
1
  let stateArr = {}
2
 
3
  export default class plugin {
@@ -116,4 +118,11 @@ export default class plugin {
116
  delete stateArr[this.conKey(isGroup)][type]
117
  }
118
  }
 
 
 
 
 
 
 
119
  }
 
1
+ import { Common } from '#miao'
2
+
3
  let stateArr = {}
4
 
5
  export default class plugin {
 
118
  delete stateArr[this.conKey(isGroup)][type]
119
  }
120
  }
121
+
122
+ async renderImg (plugin, tpl, data, cfg) {
123
+ return Common.render(plugin, tpl, data, {
124
+ ...cfg,
125
+ e: this.e
126
+ })
127
+ }
128
  }
Yunzai/lib/plugins/runtime.js CHANGED
@@ -77,7 +77,6 @@ export default class Runtime {
77
  await MysInfo.initCache()
78
  let runtime = new Runtime(e)
79
  e.runtime = runtime
80
- e.game = e.isSr ? 'sr' : 'gs'
81
  await runtime.initUser()
82
  return runtime
83
  }
@@ -88,7 +87,7 @@ export default class Runtime {
88
  if (user) {
89
  e.user = new Proxy(user, {
90
  get (self, key, receiver) {
91
- let game = e.isSr ? 'sr' : 'gs'
92
  let fnMap = {
93
  uid: 'getUid',
94
  uidList: 'getUidList',
@@ -238,7 +237,11 @@ export default class Runtime {
238
  }
239
  let ret = true
240
  if (base64) {
241
- ret = await this.e.reply(base64)
 
 
 
 
242
  }
243
  return cfg.retType === 'msgId' ? ret : true
244
  }
 
77
  await MysInfo.initCache()
78
  let runtime = new Runtime(e)
79
  e.runtime = runtime
 
80
  await runtime.initUser()
81
  return runtime
82
  }
 
87
  if (user) {
88
  e.user = new Proxy(user, {
89
  get (self, key, receiver) {
90
+ let game = e.game
91
  let fnMap = {
92
  uid: 'getUid',
93
  uidList: 'getUidList',
 
237
  }
238
  let ret = true
239
  if (base64) {
240
+ if (cfg.recallMsg) {
241
+ ret = await this.e.reply(base64, false, {})
242
+ } else {
243
+ ret = await this.e.reply(base64)
244
+ }
245
  }
246
  return cfg.retType === 'msgId' ? ret : true
247
  }
Yunzai/lib/plugins/stdin.js CHANGED
@@ -1,8 +1,6 @@
1
- import fetch from "node-fetch"
2
  import fs from "node:fs"
3
  import path from "node:path"
4
  import common from "../common/common.js"
5
- import { fileTypeFromBuffer } from "file-type"
6
 
7
  Bot.adapter.push(new class stdinAdapter {
8
  constructor() {
@@ -12,59 +10,36 @@ Bot.adapter.push(new class stdinAdapter {
12
  common.mkdirs(this.path)
13
  }
14
 
15
- async makeBuffer(file) {
16
- if (file.match(/^base64:\/\//))
17
- return Buffer.from(file.replace(/^base64:\/\//, ""), "base64")
18
- else if (file.match(/^https?:\/\//))
19
- return Buffer.from(await (await fetch(file)).arrayBuffer())
20
- else if (fs.existsSync(file))
21
- return Buffer.from(fs.readFileSync(file))
22
- return file
23
- }
24
-
25
- async fileType(data) {
26
- const file = {}
27
- try {
28
- file.url = data.replace(/^base64:\/\/.*/, "base64://...")
29
- file.buffer = await this.makeBuffer(data)
30
- file.type = await fileTypeFromBuffer(file.buffer)
31
- file.path = `${this.path}${Date.now()}.${file.type.ext}`
32
- } catch (err) {
33
- logger.error(`文件类型检测错误:${logger.red(err)}`)
34
- }
35
- return file
36
- }
37
-
38
  async sendMsg(msg) {
39
  if (!Array.isArray(msg))
40
  msg = [msg]
41
  for (let i of msg) {
42
  if (typeof i != "object")
43
- i = { type: "text", data: { text: i }}
44
- else if (!i.data)
45
- i = { type: i.type, data: { ...i, type: undefined }}
46
 
47
  let file
48
- if (i.data.file)
49
- file = await this.fileType(i.data.file)
 
 
 
 
 
50
 
51
  switch (i.type) {
52
  case "text":
53
- if (i.data.text.match("\n"))
54
- i.data.text = `\n${i.data.text}`
55
- logger.info(`${logger.blue(`[${this.id}]`)} 发送文本:${i.data.text}`)
56
  break
57
  case "image":
58
  logger.info(`${logger.blue(`[${this.id}]`)} 发送图片:${file.url}\n文件已保存到:${logger.cyan(file.path)}`)
59
- fs.writeFileSync(file.path, file.buffer)
60
  break
61
  case "record":
62
  logger.info(`${logger.blue(`[${this.id}]`)} 发送音频:${file.url}\n文件已保存到:${logger.cyan(file.path)}`)
63
- fs.writeFileSync(file.path, file.buffer)
64
  break
65
  case "video":
66
  logger.info(`${logger.blue(`[${this.id}]`)} 发送视频:${file.url}\n文件已保存到:${logger.cyan(file.path)}`)
67
- fs.writeFileSync(file.path, file.buffer)
68
  break
69
  case "reply":
70
  break
@@ -88,7 +63,7 @@ Bot.adapter.push(new class stdinAdapter {
88
  }
89
 
90
  async sendFile(file, name = path.basename(file)) {
91
- const buffer = await this.makeBuffer(file)
92
  if (!Buffer.isBuffer(buffer)) {
93
  logger.error(`${logger.blue(`[${this.id}]`)} 发送文件错误:找不到文件 ${logger.red(file)}`)
94
  return false
 
 
1
  import fs from "node:fs"
2
  import path from "node:path"
3
  import common from "../common/common.js"
 
4
 
5
  Bot.adapter.push(new class stdinAdapter {
6
  constructor() {
 
10
  common.mkdirs(this.path)
11
  }
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  async sendMsg(msg) {
14
  if (!Array.isArray(msg))
15
  msg = [msg]
16
  for (let i of msg) {
17
  if (typeof i != "object")
18
+ i = { type: "text", text: i }
 
 
19
 
20
  let file
21
+ if (i.file) {
22
+ file = await Bot.fileType(i.file, i.name)
23
+ if (Buffer.isBuffer(file.buffer)) {
24
+ file.path = `${this.path}${file.name || Date.now()}`
25
+ fs.writeFileSync(file.path, file.buffer)
26
+ }
27
+ }
28
 
29
  switch (i.type) {
30
  case "text":
31
+ if (i.text.match("\n"))
32
+ i.text = `\n${i.text}`
33
+ logger.info(`${logger.blue(`[${this.id}]`)} 发送文本:${i.text}`)
34
  break
35
  case "image":
36
  logger.info(`${logger.blue(`[${this.id}]`)} 发送图片:${file.url}\n文件已保存到:${logger.cyan(file.path)}`)
 
37
  break
38
  case "record":
39
  logger.info(`${logger.blue(`[${this.id}]`)} 发送音频:${file.url}\n文件已保存到:${logger.cyan(file.path)}`)
 
40
  break
41
  case "video":
42
  logger.info(`${logger.blue(`[${this.id}]`)} 发送视频:${file.url}\n文件已保存到:${logger.cyan(file.path)}`)
 
43
  break
44
  case "reply":
45
  break
 
63
  }
64
 
65
  async sendFile(file, name = path.basename(file)) {
66
+ const buffer = await Bot.Buffer(file)
67
  if (!Buffer.isBuffer(buffer)) {
68
  logger.error(`${logger.blue(`[${this.id}]`)} 发送文件错误:找不到文件 ${logger.red(file)}`)
69
  return false
Yunzai/lib/tools/log.js ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fs from "node:fs"
2
+ import childProcess from "child_process"
3
+
4
+ const _path = process.cwd()
5
+
6
+ fs.readFile(`${_path}/config/pm2/pm2.json`, `utf8`, (err, data) => {
7
+ if (err) {
8
+ console.log('pm2.json文件读取错误:', err)
9
+ return
10
+ }
11
+
12
+ try {
13
+ const config = JSON.parse(data)
14
+ if (config.apps && config.apps.length > 0 && config.apps[0].name) {
15
+ const appName = config.apps[0].name
16
+ runPm2Logs(appName)
17
+ } else {
18
+ console.log('读取失败:无法在pm2.json中找到name数组')
19
+ }
20
+ } catch (parseError) {
21
+ console.log('读取失败:json文件解析发生了错误', parseError)
22
+ }
23
+ })
24
+
25
+ function runPm2Logs(appName) {
26
+ const command = process.platform === 'win32' ? 'pm2.cmd' : 'pm2'
27
+ const args = ['logs', '--lines', '400', appName]
28
+ const pm2LogsProcess = childProcess.spawn(command, args, { stdio: 'inherit' })
29
+ pm2LogsProcess.on('exit', (code) => {
30
+ if (code !== 0) {
31
+ console.error(`pm2 logs process exited with code ${code}`)
32
+ }
33
+ })
34
+ }
Yunzai/package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
Yunzai/package.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "name": "trss-yunzai",
3
- "version": "3.1.0",
4
  "author": "TimeRainStarSky, Yoimiya-Kokomi, Le-niao",
5
  "description": "Bot",
6
  "main": "app.js",
@@ -13,37 +13,36 @@
13
  "start": "pm2 start ./config/pm2/pm2.json",
14
  "stop": "pm2 stop ./config/pm2/pm2.json",
15
  "restart": "pm2 restart ./config/pm2/pm2.json",
16
- "log": "node ./lib/tools/name.js"
17
  },
18
  "dependencies": {
19
  "art-template": "^4.13.2",
20
  "chalk": "^5.3.0",
21
  "chokidar": "^3.5.3",
22
  "express": "^4.18.2",
23
- "file-type": "^18.5.0",
24
  "https-proxy-agent": "7.0.2",
25
  "image-size": "^1.0.2",
26
  "lodash": "^4.17.21",
27
  "log4js": "^6.9.1",
28
  "md5": "^2.3.0",
29
- "moment": "^2.29.4",
30
  "node-fetch": "^3.3.2",
31
  "node-schedule": "^2.1.1",
32
- "node-xlsx": "^0.23.0",
33
  "oicq": "link:lib/modules/oicq",
34
  "pm2": "^5.3.0",
35
- "puppeteer": "^21.3.8",
36
- "redis": "^4.6.10",
37
- "sequelize": "^6.33.0",
38
  "sqlite3": "^5.1.6",
39
- "ws": "^8.14.2",
40
- "yaml": "^2.3.3"
41
  },
42
  "devDependencies": {
43
- "eslint": "^8.51.0",
44
  "eslint-config-standard": "^17.1.0",
45
- "eslint-plugin-import": "^2.28.1",
46
- "eslint-plugin-n": "^16.2.0",
47
  "eslint-plugin-promise": "^6.1.1"
48
  },
49
  "imports": {
 
1
  {
2
  "name": "trss-yunzai",
3
+ "version": "3.1.3",
4
  "author": "TimeRainStarSky, Yoimiya-Kokomi, Le-niao",
5
  "description": "Bot",
6
  "main": "app.js",
 
13
  "start": "pm2 start ./config/pm2/pm2.json",
14
  "stop": "pm2 stop ./config/pm2/pm2.json",
15
  "restart": "pm2 restart ./config/pm2/pm2.json",
16
+ "log": "node ./lib/tools/log.js"
17
  },
18
  "dependencies": {
19
  "art-template": "^4.13.2",
20
  "chalk": "^5.3.0",
21
  "chokidar": "^3.5.3",
22
  "express": "^4.18.2",
23
+ "file-type": "^18.7.0",
24
  "https-proxy-agent": "7.0.2",
25
  "image-size": "^1.0.2",
26
  "lodash": "^4.17.21",
27
  "log4js": "^6.9.1",
28
  "md5": "^2.3.0",
29
+ "moment": "^2.30.1",
30
  "node-fetch": "^3.3.2",
31
  "node-schedule": "^2.1.1",
 
32
  "oicq": "link:lib/modules/oicq",
33
  "pm2": "^5.3.0",
34
+ "puppeteer": "^21.6.1",
35
+ "redis": "^4.6.12",
36
+ "sequelize": "^6.35.2",
37
  "sqlite3": "^5.1.6",
38
+ "ws": "^8.16.0",
39
+ "yaml": "^2.3.4"
40
  },
41
  "devDependencies": {
42
+ "eslint": "^8.56.0",
43
  "eslint-config-standard": "^17.1.0",
44
+ "eslint-plugin-import": "^2.29.1",
45
+ "eslint-plugin-n": "^16.5.0",
46
  "eslint-plugin-promise": "^6.1.1"
47
  },
48
  "imports": {
Yunzai/pnpm-lock.yaml ADDED
The diff for this file is too large to render. See raw diff