CikeyQi erzaozi commited on
Commit
af5a34b
·
1 Parent(s): e99a853

Upload 154 files (#1)

Browse files

- Upload 154 files (b850bf7019e9b48f58c628ac8b13e98c4f87ca25)


Co-authored-by: Erzaozi <[email protected]>

This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +4 -0
  2. Yunzai/.eslintrc.cjs +22 -0
  3. Yunzai/.gitignore +149 -0
  4. Yunzai/.npmrc +3 -0
  5. Yunzai/.puppeteerrc.cjs +40 -0
  6. Yunzai/CHANGELOG.md +32 -0
  7. Yunzai/LICENSE +674 -0
  8. Yunzai/README.md +170 -0
  9. Yunzai/app.js +3 -0
  10. Yunzai/config/config/.gitignore +2 -0
  11. Yunzai/config/default_config/bot.yaml +23 -0
  12. Yunzai/config/default_config/group.yaml +37 -0
  13. Yunzai/config/default_config/other.yaml +27 -0
  14. Yunzai/config/default_config/redis.yaml +10 -0
  15. Yunzai/config/default_config/renderer.yaml +2 -0
  16. Yunzai/config/pm2/pm2.json +10 -0
  17. Yunzai/config/test/default.yaml +10 -0
  18. Yunzai/lib/bot.js +231 -0
  19. Yunzai/lib/common/common.js +75 -0
  20. Yunzai/lib/config/check.js +32 -0
  21. Yunzai/lib/config/config.js +174 -0
  22. Yunzai/lib/config/init.js +42 -0
  23. Yunzai/lib/config/log.js +98 -0
  24. Yunzai/lib/config/redis.js +76 -0
  25. Yunzai/lib/events/connect.js +23 -0
  26. Yunzai/lib/events/message.js +14 -0
  27. Yunzai/lib/events/notice.js +14 -0
  28. Yunzai/lib/events/online.js +18 -0
  29. Yunzai/lib/events/request.js +14 -0
  30. Yunzai/lib/listener/listener.js +16 -0
  31. Yunzai/lib/listener/loader.js +57 -0
  32. Yunzai/lib/modules/oicq/index.js +67 -0
  33. Yunzai/lib/modules/oicq/package.json +5 -0
  34. Yunzai/lib/plugins/handler.js +73 -0
  35. Yunzai/lib/plugins/loader.js +872 -0
  36. Yunzai/lib/plugins/plugin.js +119 -0
  37. Yunzai/lib/plugins/runtime.js +245 -0
  38. Yunzai/lib/plugins/stdin.js +159 -0
  39. Yunzai/lib/puppeteer/puppeteer.js +23 -0
  40. Yunzai/lib/renderer/Renderer.js +82 -0
  41. Yunzai/lib/renderer/loader.js +56 -0
  42. Yunzai/lib/tools/command.js +118 -0
  43. Yunzai/lib/tools/name.js +35 -0
  44. Yunzai/lib/tools/test.js +11 -0
  45. Yunzai/lib/tools/web.js +74 -0
  46. Yunzai/package.json +53 -0
  47. Yunzai/plugins/.gitignore +11 -0
  48. Yunzai/plugins/adapter/ComWeChat.js +501 -0
  49. Yunzai/plugins/adapter/GSUIDCore.js +249 -0
  50. Yunzai/plugins/adapter/go-cqhttp.js +842 -0
.gitattributes CHANGED
@@ -33,3 +33,7 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ Yunzai/plugins/ws-plugin/resources/common/font/HYWH-65W.ttf filter=lfs diff=lfs merge=lfs -text
37
+ Yunzai/plugins/ws-plugin/resources/common/font/HYWH-65W.woff filter=lfs diff=lfs merge=lfs -text
38
+ Yunzai/plugins/ws-plugin/resources/common/font/NZBZ.ttf filter=lfs diff=lfs merge=lfs -text
39
+ Yunzai/plugins/ws-plugin/resources/help/icon.png filter=lfs diff=lfs merge=lfs -text
Yunzai/.eslintrc.cjs ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module.exports = {
2
+ env: {
3
+ es2021: true,
4
+ node: true
5
+ },
6
+ extends: ['standard'],
7
+ parserOptions: {
8
+ ecmaVersion: 'latest',
9
+ sourceType: 'module'
10
+ },
11
+ globals: {
12
+ Bot: true,
13
+ redis: true,
14
+ logger: true,
15
+ plugin: true
16
+ },
17
+ rules: {
18
+ eqeqeq: ['off'],
19
+ 'prefer-const': ['off'],
20
+ 'arrow-body-style': 'off'
21
+ }
22
+ }
Yunzai/.gitignore ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ resources/
141
+
142
+ # Docker file
143
+ redis
144
+ yunzai
145
+ /.idea/
146
+ /data/
147
+ /temp/
148
+ /pnpm-lock.yaml
149
+ /entrypoint.sh
Yunzai/.npmrc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
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
Yunzai/.puppeteerrc.cjs ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const os = require("os")
2
+ const { existsSync } = require("fs")
3
+ const { execSync } = require("child_process")
4
+ const arch = os.arch()
5
+
6
+ let skipDownload = false
7
+ let executablePath
8
+
9
+ if (process.platform == "linux" || process.platform == "android")
10
+ for (const item of [
11
+ "chromium",
12
+ "chromium-browser",
13
+ "chrome",
14
+ ]) try {
15
+ const chromiumPath = execSync(`command -v ${item}`).toString().trim()
16
+ if (chromiumPath && existsSync(chromiumPath)) {
17
+ executablePath = chromiumPath
18
+ break
19
+ }
20
+ } catch (err) {}
21
+
22
+ if (!executablePath) for (const item of [
23
+ "/usr/bin/chromium",
24
+ "/usr/bin/chromium-browser",
25
+ "/usr/bin/chrome",
26
+ "C:/Program Files/Google/Chrome/Application/chrome.exe",
27
+ "C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe",
28
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
29
+ "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
30
+ ]) if (existsSync(item)) {
31
+ executablePath = item
32
+ break
33
+ }
34
+
35
+ if (executablePath || arch == "arm64" || arch == "aarch64") {
36
+ (typeof logger == "object" ? logger : console).info(`[Chromium] ${executablePath}`)
37
+ skipDownload = true
38
+ }
39
+
40
+ module.exports = { skipDownload, executablePath }
Yunzai/CHANGELOG.md ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 3.1.1
2
+
3
+ * 支持协议端:米游社大别野Bot
4
+ * 初步适配原神4.0版本,增加对应资源及信息展示,感谢**Ca(HCO₃)₂**、**@touchscale**、**@teriri7**
5
+ * 升级`#探索`内容,支持更多内容展示 **@bangbanbab**
6
+ * 增加 `#全部抽卡记录` **@story-x**
7
+
8
+ # 3.1.0
9
+
10
+ * 支持协议端:GSUIDCore、微信
11
+ * 重构CK与UID管理逻辑
12
+ * 支持多UID绑定,可绑定多个UID并进行切换
13
+ * 支持原神与星铁UID共存,可针对查询命令分配对应UID
14
+ * 新增`#删除uid1`命令,可对`#uid`列表内的绑定UID进行删除
15
+ * 使用sqlite进行ck与uid存储
16
+ * 底层对星铁查询进行支持 **@cvs**
17
+
18
+ # 3.0.2
19
+
20
+ * 支持协议端:ComWeChat、ICQQ、QQ频道、KOOK、Telegram、Discord
21
+ * 3.6卡池以及图像武器别名等数据更新 **@cvs**
22
+ * 将渲染逻辑独立,支持扩展渲染器 **@ikuaki**
23
+
24
+ # 3.0.1
25
+
26
+ * 支持多账号,支持协议端:go-cqhttp
27
+
28
+ # 3.0.0
29
+
30
+ * 从 Miao-Yunzai 分支
31
+
32
+ # 3.0.0
Yunzai/LICENSE ADDED
@@ -0,0 +1,674 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ GNU GENERAL PUBLIC LICENSE
2
+ Version 3, 29 June 2007
3
+
4
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
5
+ Everyone is permitted to copy and distribute verbatim copies
6
+ of this license document, but changing it is not allowed.
7
+
8
+ Preamble
9
+
10
+ The GNU General Public License is a free, copyleft license for
11
+ software and other kinds of works.
12
+
13
+ The licenses for most software and other practical works are designed
14
+ to take away your freedom to share and change the works. By contrast,
15
+ the GNU General Public License is intended to guarantee your freedom to
16
+ share and change all versions of a program--to make sure it remains free
17
+ software for all its users. We, the Free Software Foundation, use the
18
+ GNU General Public License for most of our software; it applies also to
19
+ any other work released this way by its authors. You can apply it to
20
+ your programs, too.
21
+
22
+ When we speak of free software, we are referring to freedom, not
23
+ price. Our General Public Licenses are designed to make sure that you
24
+ have the freedom to distribute copies of free software (and charge for
25
+ them if you wish), that you receive source code or can get it if you
26
+ want it, that you can change the software or use pieces of it in new
27
+ free programs, and that you know you can do these things.
28
+
29
+ To protect your rights, we need to prevent others from denying you
30
+ these rights or asking you to surrender the rights. Therefore, you have
31
+ certain responsibilities if you distribute copies of the software, or if
32
+ you modify it: responsibilities to respect the freedom of others.
33
+
34
+ For example, if you distribute copies of such a program, whether
35
+ gratis or for a fee, you must pass on to the recipients the same
36
+ freedoms that you received. You must make sure that they, too, receive
37
+ or can get the source code. And you must show them these terms so they
38
+ know their rights.
39
+
40
+ Developers that use the GNU GPL protect your rights with two steps:
41
+ (1) assert copyright on the software, and (2) offer you this License
42
+ giving you legal permission to copy, distribute and/or modify it.
43
+
44
+ For the developers' and authors' protection, the GPL clearly explains
45
+ that there is no warranty for this free software. For both users' and
46
+ authors' sake, the GPL requires that modified versions be marked as
47
+ changed, so that their problems will not be attributed erroneously to
48
+ authors of previous versions.
49
+
50
+ Some devices are designed to deny users access to install or run
51
+ modified versions of the software inside them, although the manufacturer
52
+ can do so. This is fundamentally incompatible with the aim of
53
+ protecting users' freedom to change the software. The systematic
54
+ pattern of such abuse occurs in the area of products for individuals to
55
+ use, which is precisely where it is most unacceptable. Therefore, we
56
+ have designed this version of the GPL to prohibit the practice for those
57
+ products. If such problems arise substantially in other domains, we
58
+ stand ready to extend this provision to those domains in future versions
59
+ of the GPL, as needed to protect the freedom of users.
60
+
61
+ Finally, every program is threatened constantly by software patents.
62
+ States should not allow patents to restrict development and use of
63
+ software on general-purpose computers, but in those that do, we wish to
64
+ avoid the special danger that patents applied to a free program could
65
+ make it effectively proprietary. To prevent this, the GPL assures that
66
+ patents cannot be used to render the program non-free.
67
+
68
+ The precise terms and conditions for copying, distribution and
69
+ modification follow.
70
+
71
+ TERMS AND CONDITIONS
72
+
73
+ 0. Definitions.
74
+
75
+ "This License" refers to version 3 of the GNU General Public License.
76
+
77
+ "Copyright" also means copyright-like laws that apply to other kinds of
78
+ works, such as semiconductor masks.
79
+
80
+ "The Program" refers to any copyrightable work licensed under this
81
+ License. Each licensee is addressed as "you". "Licensees" and
82
+ "recipients" may be individuals or organizations.
83
+
84
+ To "modify" a work means to copy from or adapt all or part of the work
85
+ in a fashion requiring copyright permission, other than the making of an
86
+ exact copy. The resulting work is called a "modified version" of the
87
+ earlier work or a work "based on" the earlier work.
88
+
89
+ A "covered work" means either the unmodified Program or a work based
90
+ on the Program.
91
+
92
+ To "propagate" a work means to do anything with it that, without
93
+ permission, would make you directly or secondarily liable for
94
+ infringement under applicable copyright law, except executing it on a
95
+ computer or modifying a private copy. Propagation includes copying,
96
+ distribution (with or without modification), making available to the
97
+ public, and in some countries other activities as well.
98
+
99
+ To "convey" a work means any kind of propagation that enables other
100
+ parties to make or receive copies. Mere interaction with a user through
101
+ a computer network, with no transfer of a copy, is not conveying.
102
+
103
+ An interactive user interface displays "Appropriate Legal Notices"
104
+ to the extent that it includes a convenient and prominently visible
105
+ feature that (1) displays an appropriate copyright notice, and (2)
106
+ tells the user that there is no warranty for the work (except to the
107
+ extent that warranties are provided), that licensees may convey the
108
+ work under this License, and how to view a copy of this License. If
109
+ the interface presents a list of user commands or options, such as a
110
+ menu, a prominent item in the list meets this criterion.
111
+
112
+ 1. Source Code.
113
+
114
+ The "source code" for a work means the preferred form of the work
115
+ for making modifications to it. "Object code" means any non-source
116
+ form of a work.
117
+
118
+ A "Standard Interface" means an interface that either is an official
119
+ standard defined by a recognized standards body, or, in the case of
120
+ interfaces specified for a particular programming language, one that
121
+ is widely used among developers working in that language.
122
+
123
+ The "System Libraries" of an executable work include anything, other
124
+ than the work as a whole, that (a) is included in the normal form of
125
+ packaging a Major Component, but which is not part of that Major
126
+ Component, and (b) serves only to enable use of the work with that
127
+ Major Component, or to implement a Standard Interface for which an
128
+ implementation is available to the public in source code form. A
129
+ "Major Component", in this context, means a major essential component
130
+ (kernel, window system, and so on) of the specific operating system
131
+ (if any) on which the executable work runs, or a compiler used to
132
+ produce the work, or an object code interpreter used to run it.
133
+
134
+ The "Corresponding Source" for a work in object code form means all
135
+ the source code needed to generate, install, and (for an executable
136
+ work) run the object code and to modify the work, including scripts to
137
+ control those activities. However, it does not include the work's
138
+ System Libraries, or general-purpose tools or generally available free
139
+ programs which are used unmodified in performing those activities but
140
+ which are not part of the work. For example, Corresponding Source
141
+ includes interface definition files associated with source files for
142
+ the work, and the source code for shared libraries and dynamically
143
+ linked subprograms that the work is specifically designed to require,
144
+ such as by intimate data communication or control flow between those
145
+ subprograms and other parts of the work.
146
+
147
+ The Corresponding Source need not include anything that users
148
+ can regenerate automatically from other parts of the Corresponding
149
+ Source.
150
+
151
+ The Corresponding Source for a work in source code form is that
152
+ same work.
153
+
154
+ 2. Basic Permissions.
155
+
156
+ All rights granted under this License are granted for the term of
157
+ copyright on the Program, and are irrevocable provided the stated
158
+ conditions are met. This License explicitly affirms your unlimited
159
+ permission to run the unmodified Program. The output from running a
160
+ covered work is covered by this License only if the output, given its
161
+ content, constitutes a covered work. This License acknowledges your
162
+ rights of fair use or other equivalent, as provided by copyright law.
163
+
164
+ You may make, run and propagate covered works that you do not
165
+ convey, without conditions so long as your license otherwise remains
166
+ in force. You may convey covered works to others for the sole purpose
167
+ of having them make modifications exclusively for you, or provide you
168
+ with facilities for running those works, provided that you comply with
169
+ the terms of this License in conveying all material for which you do
170
+ not control copyright. Those thus making or running the covered works
171
+ for you must do so exclusively on your behalf, under your direction
172
+ and control, on terms that prohibit them from making any copies of
173
+ your copyrighted material outside their relationship with you.
174
+
175
+ Conveying under any other circumstances is permitted solely under
176
+ the conditions stated below. Sublicensing is not allowed; section 10
177
+ makes it unnecessary.
178
+
179
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180
+
181
+ No covered work shall be deemed part of an effective technological
182
+ measure under any applicable law fulfilling obligations under article
183
+ 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184
+ similar laws prohibiting or restricting circumvention of such
185
+ measures.
186
+
187
+ When you convey a covered work, you waive any legal power to forbid
188
+ circumvention of technological measures to the extent such circumvention
189
+ is effected by exercising rights under this License with respect to
190
+ the covered work, and you disclaim any intention to limit operation or
191
+ modification of the work as a means of enforcing, against the work's
192
+ users, your or third parties' legal rights to forbid circumvention of
193
+ technological measures.
194
+
195
+ 4. Conveying Verbatim Copies.
196
+
197
+ You may convey verbatim copies of the Program's source code as you
198
+ receive it, in any medium, provided that you conspicuously and
199
+ appropriately publish on each copy an appropriate copyright notice;
200
+ keep intact all notices stating that this License and any
201
+ non-permissive terms added in accord with section 7 apply to the code;
202
+ keep intact all notices of the absence of any warranty; and give all
203
+ recipients a copy of this License along with the Program.
204
+
205
+ You may charge any price or no price for each copy that you convey,
206
+ and you may offer support or warranty protection for a fee.
207
+
208
+ 5. Conveying Modified Source Versions.
209
+
210
+ You may convey a work based on the Program, or the modifications to
211
+ produce it from the Program, in the form of source code under the
212
+ terms of section 4, provided that you also meet all of these conditions:
213
+
214
+ a) The work must carry prominent notices stating that you modified
215
+ it, and giving a relevant date.
216
+
217
+ b) The work must carry prominent notices stating that it is
218
+ released under this License and any conditions added under section
219
+ 7. This requirement modifies the requirement in section 4 to
220
+ "keep intact all notices".
221
+
222
+ c) You must license the entire work, as a whole, under this
223
+ License to anyone who comes into possession of a copy. This
224
+ License will therefore apply, along with any applicable section 7
225
+ additional terms, to the whole of the work, and all its parts,
226
+ regardless of how they are packaged. This License gives no
227
+ permission to license the work in any other way, but it does not
228
+ invalidate such permission if you have separately received it.
229
+
230
+ d) If the work has interactive user interfaces, each must display
231
+ Appropriate Legal Notices; however, if the Program has interactive
232
+ interfaces that do not display Appropriate Legal Notices, your
233
+ work need not make them do so.
234
+
235
+ A compilation of a covered work with other separate and independent
236
+ works, which are not by their nature extensions of the covered work,
237
+ and which are not combined with it such as to form a larger program,
238
+ in or on a volume of a storage or distribution medium, is called an
239
+ "aggregate" if the compilation and its resulting copyright are not
240
+ used to limit the access or legal rights of the compilation's users
241
+ beyond what the individual works permit. Inclusion of a covered work
242
+ in an aggregate does not cause this License to apply to the other
243
+ parts of the aggregate.
244
+
245
+ 6. Conveying Non-Source Forms.
246
+
247
+ You may convey a covered work in object code form under the terms
248
+ of sections 4 and 5, provided that you also convey the
249
+ machine-readable Corresponding Source under the terms of this License,
250
+ in one of these ways:
251
+
252
+ a) Convey the object code in, or embodied in, a physical product
253
+ (including a physical distribution medium), accompanied by the
254
+ Corresponding Source fixed on a durable physical medium
255
+ customarily used for software interchange.
256
+
257
+ b) Convey the object code in, or embodied in, a physical product
258
+ (including a physical distribution medium), accompanied by a
259
+ written offer, valid for at least three years and valid for as
260
+ long as you offer spare parts or customer support for that product
261
+ model, to give anyone who possesses the object code either (1) a
262
+ copy of the Corresponding Source for all the software in the
263
+ product that is covered by this License, on a durable physical
264
+ medium customarily used for software interchange, for a price no
265
+ more than your reasonable cost of physically performing this
266
+ conveying of source, or (2) access to copy the
267
+ Corresponding Source from a network server at no charge.
268
+
269
+ c) Convey individual copies of the object code with a copy of the
270
+ written offer to provide the Corresponding Source. This
271
+ alternative is allowed only occasionally and noncommercially, and
272
+ only if you received the object code with such an offer, in accord
273
+ with subsection 6b.
274
+
275
+ d) Convey the object code by offering access from a designated
276
+ place (gratis or for a charge), and offer equivalent access to the
277
+ Corresponding Source in the same way through the same place at no
278
+ further charge. You need not require recipients to copy the
279
+ Corresponding Source along with the object code. If the place to
280
+ copy the object code is a network server, the Corresponding Source
281
+ may be on a different server (operated by you or a third party)
282
+ that supports equivalent copying facilities, provided you maintain
283
+ clear directions next to the object code saying where to find the
284
+ Corresponding Source. Regardless of what server hosts the
285
+ Corresponding Source, you remain obligated to ensure that it is
286
+ available for as long as needed to satisfy these requirements.
287
+
288
+ e) Convey the object code using peer-to-peer transmission, provided
289
+ you inform other peers where the object code and Corresponding
290
+ Source of the work are being offered to the general public at no
291
+ charge under subsection 6d.
292
+
293
+ A separable portion of the object code, whose source code is excluded
294
+ from the Corresponding Source as a System Library, need not be
295
+ included in conveying the object code work.
296
+
297
+ A "User Product" is either (1) a "consumer product", which means any
298
+ tangible personal property which is normally used for personal, family,
299
+ or household purposes, or (2) anything designed or sold for incorporation
300
+ into a dwelling. In determining whether a product is a consumer product,
301
+ doubtful cases shall be resolved in favor of coverage. For a particular
302
+ product received by a particular user, "normally used" refers to a
303
+ typical or common use of that class of product, regardless of the status
304
+ of the particular user or of the way in which the particular user
305
+ actually uses, or expects or is expected to use, the product. A product
306
+ is a consumer product regardless of whether the product has substantial
307
+ commercial, industrial or non-consumer uses, unless such uses represent
308
+ the only significant mode of use of the product.
309
+
310
+ "Installation Information" for a User Product means any methods,
311
+ procedures, authorization keys, or other information required to install
312
+ and execute modified versions of a covered work in that User Product from
313
+ a modified version of its Corresponding Source. The information must
314
+ suffice to ensure that the continued functioning of the modified object
315
+ code is in no case prevented or interfered with solely because
316
+ modification has been made.
317
+
318
+ If you convey an object code work under this section in, or with, or
319
+ specifically for use in, a User Product, and the conveying occurs as
320
+ part of a transaction in which the right of possession and use of the
321
+ User Product is transferred to the recipient in perpetuity or for a
322
+ fixed term (regardless of how the transaction is characterized), the
323
+ Corresponding Source conveyed under this section must be accompanied
324
+ by the Installation Information. But this requirement does not apply
325
+ if neither you nor any third party retains the ability to install
326
+ modified object code on the User Product (for example, the work has
327
+ been installed in ROM).
328
+
329
+ The requirement to provide Installation Information does not include a
330
+ requirement to continue to provide support service, warranty, or updates
331
+ for a work that has been modified or installed by the recipient, or for
332
+ the User Product in which it has been modified or installed. Access to a
333
+ network may be denied when the modification itself materially and
334
+ adversely affects the operation of the network or violates the rules and
335
+ protocols for communication across the network.
336
+
337
+ Corresponding Source conveyed, and Installation Information provided,
338
+ in accord with this section must be in a format that is publicly
339
+ documented (and with an implementation available to the public in
340
+ source code form), and must require no special password or key for
341
+ unpacking, reading or copying.
342
+
343
+ 7. Additional Terms.
344
+
345
+ "Additional permissions" are terms that supplement the terms of this
346
+ License by making exceptions from one or more of its conditions.
347
+ Additional permissions that are applicable to the entire Program shall
348
+ be treated as though they were included in this License, to the extent
349
+ that they are valid under applicable law. If additional permissions
350
+ apply only to part of the Program, that part may be used separately
351
+ under those permissions, but the entire Program remains governed by
352
+ this License without regard to the additional permissions.
353
+
354
+ When you convey a copy of a covered work, you may at your option
355
+ remove any additional permissions from that copy, or from any part of
356
+ it. (Additional permissions may be written to require their own
357
+ removal in certain cases when you modify the work.) You may place
358
+ additional permissions on material, added by you to a covered work,
359
+ for which you have or can give appropriate copyright permission.
360
+
361
+ Notwithstanding any other provision of this License, for material you
362
+ add to a covered work, you may (if authorized by the copyright holders of
363
+ that material) supplement the terms of this License with terms:
364
+
365
+ a) Disclaiming warranty or limiting liability differently from the
366
+ terms of sections 15 and 16 of this License; or
367
+
368
+ b) Requiring preservation of specified reasonable legal notices or
369
+ author attributions in that material or in the Appropriate Legal
370
+ Notices displayed by works containing it; or
371
+
372
+ c) Prohibiting misrepresentation of the origin of that material, or
373
+ requiring that modified versions of such material be marked in
374
+ reasonable ways as different from the original version; or
375
+
376
+ d) Limiting the use for publicity purposes of names of licensors or
377
+ authors of the material; or
378
+
379
+ e) Declining to grant rights under trademark law for use of some
380
+ trade names, trademarks, or service marks; or
381
+
382
+ f) Requiring indemnification of licensors and authors of that
383
+ material by anyone who conveys the material (or modified versions of
384
+ it) with contractual assumptions of liability to the recipient, for
385
+ any liability that these contractual assumptions directly impose on
386
+ those licensors and authors.
387
+
388
+ All other non-permissive additional terms are considered "further
389
+ restrictions" within the meaning of section 10. If the Program as you
390
+ received it, or any part of it, contains a notice stating that it is
391
+ governed by this License along with a term that is a further
392
+ restriction, you may remove that term. If a license document contains
393
+ a further restriction but permits relicensing or conveying under this
394
+ License, you may add to a covered work material governed by the terms
395
+ of that license document, provided that the further restriction does
396
+ not survive such relicensing or conveying.
397
+
398
+ If you add terms to a covered work in accord with this section, you
399
+ must place, in the relevant source files, a statement of the
400
+ additional terms that apply to those files, or a notice indicating
401
+ where to find the applicable terms.
402
+
403
+ Additional terms, permissive or non-permissive, may be stated in the
404
+ form of a separately written license, or stated as exceptions;
405
+ the above requirements apply either way.
406
+
407
+ 8. Termination.
408
+
409
+ You may not propagate or modify a covered work except as expressly
410
+ provided under this License. Any attempt otherwise to propagate or
411
+ modify it is void, and will automatically terminate your rights under
412
+ this License (including any patent licenses granted under the third
413
+ paragraph of section 11).
414
+
415
+ However, if you cease all violation of this License, then your
416
+ license from a particular copyright holder is reinstated (a)
417
+ provisionally, unless and until the copyright holder explicitly and
418
+ finally terminates your license, and (b) permanently, if the copyright
419
+ holder fails to notify you of the violation by some reasonable means
420
+ prior to 60 days after the cessation.
421
+
422
+ Moreover, your license from a particular copyright holder is
423
+ reinstated permanently if the copyright holder notifies you of the
424
+ violation by some reasonable means, this is the first time you have
425
+ received notice of violation of this License (for any work) from that
426
+ copyright holder, and you cure the violation prior to 30 days after
427
+ your receipt of the notice.
428
+
429
+ Termination of your rights under this section does not terminate the
430
+ licenses of parties who have received copies or rights from you under
431
+ this License. If your rights have been terminated and not permanently
432
+ reinstated, you do not qualify to receive new licenses for the same
433
+ material under section 10.
434
+
435
+ 9. Acceptance Not Required for Having Copies.
436
+
437
+ You are not required to accept this License in order to receive or
438
+ run a copy of the Program. Ancillary propagation of a covered work
439
+ occurring solely as a consequence of using peer-to-peer transmission
440
+ to receive a copy likewise does not require acceptance. However,
441
+ nothing other than this License grants you permission to propagate or
442
+ modify any covered work. These actions infringe copyright if you do
443
+ not accept this License. Therefore, by modifying or propagating a
444
+ covered work, you indicate your acceptance of this License to do so.
445
+
446
+ 10. Automatic Licensing of Downstream Recipients.
447
+
448
+ Each time you convey a covered work, the recipient automatically
449
+ receives a license from the original licensors, to run, modify and
450
+ propagate that work, subject to this License. You are not responsible
451
+ for enforcing compliance by third parties with this License.
452
+
453
+ An "entity transaction" is a transaction transferring control of an
454
+ organization, or substantially all assets of one, or subdividing an
455
+ organization, or merging organizations. If propagation of a covered
456
+ work results from an entity transaction, each party to that
457
+ transaction who receives a copy of the work also receives whatever
458
+ licenses to the work the party's predecessor in interest had or could
459
+ give under the previous paragraph, plus a right to possession of the
460
+ Corresponding Source of the work from the predecessor in interest, if
461
+ the predecessor has it or can get it with reasonable efforts.
462
+
463
+ You may not impose any further restrictions on the exercise of the
464
+ rights granted or affirmed under this License. For example, you may
465
+ not impose a license fee, royalty, or other charge for exercise of
466
+ rights granted under this License, and you may not initiate litigation
467
+ (including a cross-claim or counterclaim in a lawsuit) alleging that
468
+ any patent claim is infringed by making, using, selling, offering for
469
+ sale, or importing the Program or any portion of it.
470
+
471
+ 11. Patents.
472
+
473
+ A "contributor" is a copyright holder who authorizes use under this
474
+ License of the Program or a work on which the Program is based. The
475
+ work thus licensed is called the contributor's "contributor version".
476
+
477
+ A contributor's "essential patent claims" are all patent claims
478
+ owned or controlled by the contributor, whether already acquired or
479
+ hereafter acquired, that would be infringed by some manner, permitted
480
+ by this License, of making, using, or selling its contributor version,
481
+ but do not include claims that would be infringed only as a
482
+ consequence of further modification of the contributor version. For
483
+ purposes of this definition, "control" includes the right to grant
484
+ patent sublicenses in a manner consistent with the requirements of
485
+ this License.
486
+
487
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
488
+ patent license under the contributor's essential patent claims, to
489
+ make, use, sell, offer for sale, import and otherwise run, modify and
490
+ propagate the contents of its contributor version.
491
+
492
+ In the following three paragraphs, a "patent license" is any express
493
+ agreement or commitment, however denominated, not to enforce a patent
494
+ (such as an express permission to practice a patent or covenant not to
495
+ sue for patent infringement). To "grant" such a patent license to a
496
+ party means to make such an agreement or commitment not to enforce a
497
+ patent against the party.
498
+
499
+ If you convey a covered work, knowingly relying on a patent license,
500
+ and the Corresponding Source of the work is not available for anyone
501
+ to copy, free of charge and under the terms of this License, through a
502
+ publicly available network server or other readily accessible means,
503
+ then you must either (1) cause the Corresponding Source to be so
504
+ available, or (2) arrange to deprive yourself of the benefit of the
505
+ patent license for this particular work, or (3) arrange, in a manner
506
+ consistent with the requirements of this License, to extend the patent
507
+ license to downstream recipients. "Knowingly relying" means you have
508
+ actual knowledge that, but for the patent license, your conveying the
509
+ covered work in a country, or your recipient's use of the covered work
510
+ in a country, would infringe one or more identifiable patents in that
511
+ country that you have reason to believe are valid.
512
+
513
+ If, pursuant to or in connection with a single transaction or
514
+ arrangement, you convey, or propagate by procuring conveyance of, a
515
+ covered work, and grant a patent license to some of the parties
516
+ receiving the covered work authorizing them to use, propagate, modify
517
+ or convey a specific copy of the covered work, then the patent license
518
+ you grant is automatically extended to all recipients of the covered
519
+ work and works based on it.
520
+
521
+ A patent license is "discriminatory" if it does not include within
522
+ the scope of its coverage, prohibits the exercise of, or is
523
+ conditioned on the non-exercise of one or more of the rights that are
524
+ specifically granted under this License. You may not convey a covered
525
+ work if you are a party to an arrangement with a third party that is
526
+ in the business of distributing software, under which you make payment
527
+ to the third party based on the extent of your activity of conveying
528
+ the work, and under which the third party grants, to any of the
529
+ parties who would receive the covered work from you, a discriminatory
530
+ patent license (a) in connection with copies of the covered work
531
+ conveyed by you (or copies made from those copies), or (b) primarily
532
+ for and in connection with specific products or compilations that
533
+ contain the covered work, unless you entered into that arrangement,
534
+ or that patent license was granted, prior to 28 March 2007.
535
+
536
+ Nothing in this License shall be construed as excluding or limiting
537
+ any implied license or other defenses to infringement that may
538
+ otherwise be available to you under applicable patent law.
539
+
540
+ 12. No Surrender of Others' Freedom.
541
+
542
+ If conditions are imposed on you (whether by court order, agreement or
543
+ otherwise) that contradict the conditions of this License, they do not
544
+ excuse you from the conditions of this License. If you cannot convey a
545
+ covered work so as to satisfy simultaneously your obligations under this
546
+ License and any other pertinent obligations, then as a consequence you may
547
+ not convey it at all. For example, if you agree to terms that obligate you
548
+ to collect a royalty for further conveying from those to whom you convey
549
+ the Program, the only way you could satisfy both those terms and this
550
+ License would be to refrain entirely from conveying the Program.
551
+
552
+ 13. Use with the GNU Affero General Public License.
553
+
554
+ Notwithstanding any other provision of this License, you have
555
+ permission to link or combine any covered work with a work licensed
556
+ under version 3 of the GNU Affero General Public License into a single
557
+ combined work, and to convey the resulting work. The terms of this
558
+ License will continue to apply to the part which is the covered work,
559
+ but the special requirements of the GNU Affero General Public License,
560
+ section 13, concerning interaction through a network will apply to the
561
+ combination as such.
562
+
563
+ 14. Revised Versions of this License.
564
+
565
+ The Free Software Foundation may publish revised and/or new versions of
566
+ the GNU General Public License from time to time. Such new versions will
567
+ be similar in spirit to the present version, but may differ in detail to
568
+ address new problems or concerns.
569
+
570
+ Each version is given a distinguishing version number. If the
571
+ Program specifies that a certain numbered version of the GNU General
572
+ Public License "or any later version" applies to it, you have the
573
+ option of following the terms and conditions either of that numbered
574
+ version or of any later version published by the Free Software
575
+ Foundation. If the Program does not specify a version number of the
576
+ GNU General Public License, you may choose any version ever published
577
+ by the Free Software Foundation.
578
+
579
+ If the Program specifies that a proxy can decide which future
580
+ versions of the GNU General Public License can be used, that proxy's
581
+ public statement of acceptance of a version permanently authorizes you
582
+ to choose that version for the Program.
583
+
584
+ Later license versions may give you additional or different
585
+ permissions. However, no additional obligations are imposed on any
586
+ author or copyright holder as a result of your choosing to follow a
587
+ later version.
588
+
589
+ 15. Disclaimer of Warranty.
590
+
591
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592
+ APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593
+ HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594
+ OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596
+ PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597
+ IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598
+ ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599
+
600
+ 16. Limitation of Liability.
601
+
602
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604
+ THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605
+ GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606
+ USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607
+ DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608
+ PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609
+ EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610
+ SUCH DAMAGES.
611
+
612
+ 17. Interpretation of Sections 15 and 16.
613
+
614
+ If the disclaimer of warranty and limitation of liability provided
615
+ above cannot be given local legal effect according to their terms,
616
+ reviewing courts shall apply local law that most closely approximates
617
+ an absolute waiver of all civil liability in connection with the
618
+ Program, unless a warranty or assumption of liability accompanies a
619
+ copy of the Program in return for a fee.
620
+
621
+ END OF TERMS AND CONDITIONS
622
+
623
+ How to Apply These Terms to Your New Programs
624
+
625
+ If you develop a new program, and you want it to be of the greatest
626
+ possible use to the public, the best way to achieve this is to make it
627
+ free software which everyone can redistribute and change under these terms.
628
+
629
+ To do so, attach the following notices to the program. It is safest
630
+ to attach them to the start of each source file to most effectively
631
+ state the exclusion of warranty; and each file should have at least
632
+ the "copyright" line and a pointer to where the full notice is found.
633
+
634
+ <one line to give the program's name and a brief idea of what it does.>
635
+ Copyright (C) <year> <name of author>
636
+
637
+ This program is free software: you can redistribute it and/or modify
638
+ it under the terms of the GNU General Public License as published by
639
+ the Free Software Foundation, either version 3 of the License, or
640
+ (at your option) any later version.
641
+
642
+ This program is distributed in the hope that it will be useful,
643
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
644
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645
+ GNU General Public License for more details.
646
+
647
+ You should have received a copy of the GNU General Public License
648
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
649
+
650
+ Also add information on how to contact you by electronic and paper mail.
651
+
652
+ If the program does terminal interaction, make it output a short
653
+ notice like this when it starts in an interactive mode:
654
+
655
+ <program> Copyright (C) <year> <name of author>
656
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657
+ This is free software, and you are welcome to redistribute it
658
+ under certain conditions; type `show c' for details.
659
+
660
+ The hypothetical commands `show w' and `show c' should show the appropriate
661
+ parts of the General Public License. Of course, your program's commands
662
+ might be different; for a GUI interface, you would use an "about box".
663
+
664
+ You should also get your employer (if you work as a programmer) or school,
665
+ if any, to sign a "copyright disclaimer" for the program, if necessary.
666
+ For more information on this, and how to apply and follow the GNU GPL, see
667
+ <https://www.gnu.org/licenses/>.
668
+
669
+ The GNU General Public License does not permit incorporating your program
670
+ into proprietary programs. If your program is a subroutine library, you
671
+ may consider it more useful to permit linking proprietary applications with
672
+ the library. If this is what you want to do, use the GNU Lesser General
673
+ Public License instead of this License. But first, please read
674
+ <https://www.gnu.org/licenses/why-not-lgpl.html>.
Yunzai/README.md ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div align="center">
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)
9
+ [![Downloads](https://img.shields.io/github/downloads/TimeRainStarSky/Yunzai/total?color=blue&label=下载)](../../archive/main.tar.gz)
10
+ [![Releases](https://img.shields.io/github/v/release/TimeRainStarSky/Yunzai?color=green&label=发行版)](../../releases/latest)
11
+
12
+ [![访问量](https://profile-counter.glitch.me/TimeRainStarSky-Yunzai/count.svg)](https://github.com/TimeRainStarSky/Yunzai)
13
+
14
+ </div>
15
+
16
+ - 基于 [Miao-Yunzai](../../../../yoimiya-kokomi/Miao-Yunzai) 改造,需要同时安装 [miao-plugin](../../../../yoimiya-kokomi/miao-plugin)
17
+ - 开发文档:[docs 分支](../../tree/docs)
18
+
19
+ ## TRSS-Yunzai 后续计划
20
+
21
+ 先刨坑,但也许会咕咕咕
22
+
23
+ - 完善现有协议端
24
+ - 支持更多协议端
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 or Linux,Node.js( [版本至少 v18 以上](http://nodejs.cn/download) ), [Redis](https://redis.io/docs/getting-started/installation)
40
+
41
+ 1.克隆项目并安装 genshin miao-plugin TRSS-Plugin(可选)
42
+
43
+ 请根据网络情况选择使用 GitHub 或 Gitee 安装
44
+
45
+ ```
46
+ git clone --depth 1 https://github.com/TimeRainStarSky/Yunzai
47
+ cd Yunzai
48
+ git clone --depth 1 https://github.com/TimeRainStarSky/Yunzai-genshin plugins/genshin
49
+ git clone --depth 1 https://github.com/yoimiya-kokomi/miao-plugin plugins/miao-plugin
50
+ git clone --depth 1 https://github.com/TimeRainStarSky/TRSS-Plugin plugins/TRSS-Plugin
51
+ ```
52
+
53
+ ```
54
+ git clone --depth 1 https://gitee.com/TimeRainStarSky/Yunzai
55
+ cd Yunzai
56
+ git clone --depth 1 https://gitee.com/TimeRainStarSky/Yunzai-genshin plugins/genshin
57
+ git clone --depth 1 https://gitee.com/yoimiya-kokomi/miao-plugin plugins/miao-plugin
58
+ git clone --depth 1 https://Yunzai.TRSS.me plugins/TRSS-Plugin
59
+ ```
60
+
61
+ 2.安装 [pnpm](https://pnpm.io/zh/installation)
62
+
63
+ ```
64
+ npm install -g pnpm
65
+ ```
66
+
67
+ 3.安装依赖
68
+
69
+ ```
70
+ pnpm i
71
+ ```
72
+
73
+ 4.运行
74
+
75
+ ```
76
+ node app
77
+ ```
78
+
79
+ 5.启动协议端:
80
+
81
+ <details><summary>go-cqhttp</summary>
82
+
83
+ 下载运行 [go-cqhttp](https://docs.go-cqhttp.org),选择反向 WebSocket,修改 `config.yml`,以下为必改项:
84
+
85
+ ```
86
+ uin: 账号
87
+ password: '密码'
88
+ post-format: array
89
+ universal: ws://localhost:2536/go-cqhttp
90
+ ```
91
+
92
+ </details>
93
+
94
+ <details><summary>ComWeChat</summary>
95
+
96
+ 下载运行 [ComWeChat](https://justundertaker.github.io/ComWeChatBotClient),修改 `.env`,以下为必改项:
97
+
98
+ ```
99
+ websocekt_type = "Backward"
100
+ websocket_url = ["ws://localhost:2536/ComWeChat"]
101
+ ```
102
+
103
+ </details>
104
+
105
+ <details><summary>GSUIDCore</summary>
106
+
107
+ 下载运行 [GenshinUID 插件](http://docs.gsuid.gbots.work/#/AdapterList),GSUIDCore 连接地址 修改为:
108
+
109
+ ```
110
+ ws://localhost:2536/GSUIDCore
111
+ ```
112
+
113
+ </details>
114
+
115
+ <details><summary>ICQQ</summary>
116
+
117
+ [TRSS-Yunzai ICQQ Plugin](../../../Yunzai-ICQQ-Plugin)
118
+
119
+ </details>
120
+
121
+ <details><summary>QQ频道</summary>
122
+
123
+ [TRSS-Yunzai QQGuild Plugin](../../../Yunzai-QQGuild-Plugin)
124
+
125
+ </details>
126
+
127
+ <details><summary>微信</summary>
128
+
129
+ [TRSS-Yunzai WeChat Plugin](../../../Yunzai-WeChat-Plugin)
130
+
131
+ </details>
132
+
133
+ <details><summary>米游社大别野</summary>
134
+
135
+ [TRSS-Yunzai mysVilla Plugin](../../../Yunzai-mysVilla-Plugin)
136
+
137
+ </details>
138
+
139
+ <details><summary>KOOK</summary>
140
+
141
+ [TRSS-Yunzai KOOK Plugin](../../../Yunzai-KOOK-Plugin)
142
+
143
+ </details>
144
+
145
+ <details><summary>Telegram</summary>
146
+
147
+ [TRSS-Yunzai Telegram Plugin](../../../Yunzai-Telegram-Plugin)
148
+
149
+ </details>
150
+
151
+ <details><summary>Discord</summary>
152
+
153
+ [TRSS-Yunzai Discord Plugin](../../../Yunzai-Discord-Plugin)
154
+
155
+ </details>
156
+
157
+ <details><summary>代理</summary>
158
+
159
+ [TRSS-Yunzai Proxy Plugin](../../../Yunzai-Proxy-Plugin)
160
+
161
+ </details>
162
+
163
+ 6.设置主人:发送 `#设置主人`,后台日志获取验证码并发送
164
+
165
+ ## 致谢
166
+
167
+ | Nickname | Contribution |
168
+ | :-----------------------------------------------------------: | -------------------- |
169
+ | [Yunzai-Bot](../../../../Le-niao/Yunzai-Bot) | 乐神的 Yunzai-Bot |
170
+ | [Miao-Yunzai](../../../../yoimiya-kokomi/Miao-Yunzai) | 喵喵的 Miao-Yunzai |
Yunzai/app.js ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ import Yunzai from "./lib/bot.js"
2
+ global.Bot = new Yunzai
3
+ Bot.run()
Yunzai/config/config/.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ *
2
+ !.gitignore
Yunzai/config/default_config/bot.yaml ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 日志等级:trace,debug,info,warn,fatal,mark,error,off
2
+ # mark时只显示执行命令,不显示聊天记录
3
+ log_level: info
4
+ # 服务器端口
5
+ port: 2536
6
+
7
+ # chromium其他路径
8
+ chromium_path:
9
+ # puppeteer接口地址
10
+ puppeteer_ws:
11
+ # puppeteer截图超时时间
12
+ puppeteer_timeout:
13
+
14
+ # 米游社接口代理地址,国际服用
15
+ proxyAddress:
16
+
17
+ # 上线时给主人推送帮助
18
+ online_msg: true
19
+ # 上线推送通知的冷却时间
20
+ online_msg_exp: 86400
21
+
22
+ # 单条日志长度
23
+ logLength: 1000
Yunzai/config/default_config/group.yaml ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 默认设置
2
+ default:
3
+ groupCD: 500 # 群聊中所有指令操作冷却时间,单位毫秒,0则无限制
4
+ singleCD: 2000 # 群聊中个人操作冷却时间,单位毫秒
5
+
6
+ onlyReplyAt: 0 # 是否只仅关注主动提及Bot的消息 0-否 1-是
7
+ botAlias: # 开启后则只回复提及Bot的消息及特定前缀的消息
8
+ - 云崽
9
+ - 云宝
10
+
11
+ addLimit: 0 # 是否限制添加消息 0-所有群员 1-群管理员 2-主人
12
+ addPrivate: 1 # 是否允许私聊添加
13
+ addReply: 1 # 是否回复触发消息
14
+ addAt: 0 # 是否提及触发用户
15
+ addRecall: 60 # 是否撤回回复消息
16
+
17
+ enable: # 只启用功能,配置后只有该功能才响应
18
+ disable: # 禁用功能,功能名称,例如:十连、角色查询、体力查询、用户绑定、抽卡记录、添加表情、欢迎新人、退群通知
19
+ - 禁用示例
20
+ - 支持多个
21
+
22
+ # Bot单独设置
23
+ 114514:default:
24
+ onlyReplyAt: 1
25
+ botAlias:
26
+ - 臭崽
27
+ - 臭宝
28
+
29
+ # 群单独设置
30
+ 123456:
31
+ groupCD: 500
32
+ singleCD: 2000
33
+
34
+ # [Bot:群]单独设置
35
+ 114514:123456:
36
+ enable:
37
+ disable:
Yunzai/config/default_config/other.yaml ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 是否自动同意加好友 1-同意 0-不处理
2
+ autoFriend: 1
3
+ # 是否自动退群人数,当被好友拉进群时,群人数小于配置值自动退出, 默认50,0则不处理
4
+ autoQuit: 50
5
+ # 主人帐号
6
+ masterQQ:
7
+ - "stdin"
8
+ # Bot账号:主人帐号
9
+ master:
10
+ - "stdin:stdin"
11
+
12
+ # 禁用私聊功能 true:私聊只接受ck以及抽卡链接(Bot主人不受限制),false:私聊可以触发全部指令,默认false
13
+ disablePrivate: false
14
+ # 禁用私聊Bot提示内容
15
+ disableMsg: "私聊功能已禁用,仅支持发送cookie,抽卡记录链接,记录日志文件"
16
+ # 私聊通行字符串
17
+ disableAdopt:
18
+ - stoken
19
+ #白名单群,配置后只在该群生效
20
+ whiteGroup:
21
+
22
+ #黑名单群
23
+ blackGroup:
24
+ - 213938015
25
+ #黑名单账号
26
+ blackQQ:
27
+ - 528952540
Yunzai/config/default_config/redis.yaml ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
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
Yunzai/config/default_config/renderer.yaml ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # 渲染后端, 默认为 puppeteer
2
+ name:
Yunzai/config/pm2/pm2.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "apps": [
3
+ {
4
+ "name": "TRSS-Yunzai",
5
+ "script": "./app.js",
6
+ "max_memory_restart": "512M",
7
+ "restart_delay": 60000
8
+ }
9
+ ]
10
+ }
Yunzai/config/test/default.yaml ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ # 默认测试配置,其他配置自行复制
2
+ post_type: message
3
+ message_type: group
4
+ sub_type: normal
5
+ group_id: 213938015
6
+ group_name: '2333'
7
+ user_id: 805475874
8
+ # 测试命令
9
+ text: 十连
10
+ card: 测试(104070461)
Yunzai/lib/bot.js ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import "./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 "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() {
55
+ await import("./plugins/stdin.js")
56
+ await PluginsLoader.load()
57
+ await ListenerLoader.load()
58
+ this.serverLoad()
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)
80
+ for (const [id, i] of this[bot_id].fl || [])
81
+ array.push({ ...i, bot_id })
82
+ return array
83
+ }
84
+
85
+ getFriendList() {
86
+ const array = []
87
+ for (const bot_id of this.uin)
88
+ for (const [id, i] of this[bot_id].fl || [])
89
+ array.push(id)
90
+ return array
91
+ }
92
+
93
+ getFriendMap() {
94
+ const map = new Map
95
+ for (const bot_id of this.uin)
96
+ for (const [id, i] of this[bot_id].fl || [])
97
+ map.set(id, { ...i, bot_id })
98
+ return map
99
+ }
100
+ get fl() { return this.getFriendMap() }
101
+
102
+ getGroupArray() {
103
+ const array = []
104
+ for (const bot_id of this.uin)
105
+ for (const [id, i] of this[bot_id].gl || [])
106
+ array.push({ ...i, bot_id })
107
+ return array
108
+ }
109
+
110
+ getGroupList() {
111
+ const array = []
112
+ for (const bot_id of this.uin)
113
+ for (const [id, i] of this[bot_id].gl || [])
114
+ array.push(id)
115
+ return array
116
+ }
117
+
118
+ getGroupMap() {
119
+ const map = new Map
120
+ for (const bot_id of this.uin)
121
+ for (const [id, i] of this[bot_id].gl || [])
122
+ map.set(id, { ...i, bot_id })
123
+ return map
124
+ }
125
+ get gl() { return this.getGroupMap() }
126
+ get gml() {
127
+ const map = new Map
128
+ for (const bot_id of this.uin)
129
+ for (const [id, i] of this[bot_id].gml || [])
130
+ map.set(id, i)
131
+ return map
132
+ }
133
+
134
+ pickFriend(user_id) {
135
+ user_id = Number(user_id) || String(user_id)
136
+ const user = this.fl.get(user_id)
137
+ if (user) return this[user.bot_id].pickFriend(user_id)
138
+ logger.error(`获取用户对象失败:找不到用户 ${logger.red(user_id)}`)
139
+ }
140
+ get pickUser() { return this.pickFriend }
141
+
142
+ pickGroup(group_id) {
143
+ group_id = Number(group_id) || String(group_id)
144
+ const group = this.gl.get(group_id)
145
+ if (group) return this[group.bot_id].pickGroup(group_id)
146
+ logger.error(`获取群对象失败:找不到群 ${logger.red(group_id)}`)
147
+ }
148
+
149
+ pickMember(group_id, user_id) {
150
+ const group = this.pickGroup(group_id)
151
+ if (group) return group.pickMember(user_id)
152
+ }
153
+
154
+ sendFriendMsg(bot_id, user_id, msg) {
155
+ try {
156
+ if (!bot_id)
157
+ return this.pickFriend(user_id).sendMsg(msg)
158
+
159
+ if (this[bot_id])
160
+ return this[bot_id].pickFriend(user_id).sendMsg(msg)
161
+
162
+ return new Promise(resolve =>
163
+ this.once(`connect.${bot_id}`, data =>
164
+ resolve(data.bot.pickFriend(user_id).sendMsg(msg))))
165
+ } catch (err) {
166
+ logger.error(`${logger.blue(`[${bot_id}]`)} 发送好友消息失败:[$${user_id}] ${err}`)
167
+ }
168
+ return false
169
+ }
170
+
171
+ sendGroupMsg(bot_id, group_id, msg) {
172
+ try {
173
+ if (!bot_id)
174
+ return this.pickGroup(group_id).sendMsg(msg)
175
+
176
+ if (this[bot_id])
177
+ return this[bot_id].pickGroup(group_id).sendMsg(msg)
178
+
179
+ return new Promise(resolve =>
180
+ this.once(`connect.${bot_id}`, data =>
181
+ resolve(data.bot.pickGroup(group_id).sendMsg(msg))))
182
+ } catch (err) {
183
+ logger.error(`${logger.blue(`[${bot_id}]`)} 发送群消息失败:[$${group_id}] ${err}`)
184
+ }
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
192
+ }
193
+
194
+ while (true) {
195
+ const msg = await new Promise(resolve => {
196
+ this.once("message", data => {
197
+ if (data.message && fnc(data)) {
198
+ let msg = ""
199
+ for (const i of data.message)
200
+ if (i.type = "text")
201
+ msg += i.text.trim()
202
+ resolve(msg)
203
+ } else {
204
+ resolve(false)
205
+ }
206
+ })
207
+ })
208
+ if (msg) return msg
209
+ }
210
+ }
211
+
212
+ getMasterMsg() {
213
+ return this.getFriendMsg(data =>
214
+ cfg.master[data.self_id]?.includes(String(data.user_id)))
215
+ }
216
+
217
+ sendMasterMsg(msg) {
218
+ for (const bot_id in cfg.master)
219
+ for (const user_id of cfg.master[bot_id])
220
+ this.sendFriendMsg(bot_id, user_id, msg)
221
+ }
222
+
223
+ makeForwardMsg(msg) { return { type: "node", data: msg } }
224
+
225
+ async sendForwardMsg(send, msg) {
226
+ const messages = []
227
+ for (const { message } of msg)
228
+ messages.push(await send(message))
229
+ return messages
230
+ }
231
+ }
Yunzai/lib/common/common.js ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ /**
17
+ * 休眠函数
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
40
+ }
41
+ }
42
+
43
+ function mkdirs(dirname) {
44
+ if (fs.existsSync(dirname)) {
45
+ return true
46
+ } else {
47
+ if (mkdirs(path.dirname(dirname))) {
48
+ fs.mkdirSync(dirname)
49
+ return true
50
+ }
51
+ }
52
+ }
53
+
54
+ /**
55
+ * 制作转发消息
56
+ * @param e 消息事件
57
+ * @param msg 消息数组
58
+ * @param dec 转发描述
59
+ */
60
+ function makeForwardMsg(e, msg = [], dec) {
61
+ const forwardMsg = []
62
+ if (dec)
63
+ forwardMsg.push({ message: dec })
64
+ for (const message of msg)
65
+ forwardMsg.push({ message })
66
+
67
+ if (e?.group?.makeForwardMsg)
68
+ return e.group.makeForwardMsg(forwardMsg)
69
+ else if (e?.friend?.makeForwardMsg)
70
+ return e.friend.makeForwardMsg(forwardMsg)
71
+ else
72
+ return Bot.makeForwardMsg(forwardMsg)
73
+ }
74
+
75
+ export default { relpyPrivate, sleep, downFile, mkdirs, makeForwardMsg }
Yunzai/lib/config/check.js ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fs from 'fs'
2
+ import { createRequire } from 'module'
3
+ const require = createRequire(import.meta.url)
4
+ const { exec } = require('child_process')
5
+
6
+ export async function checkRun () {
7
+ if (process.argv[1].includes('pm2')) return
8
+ if (process.argv[1].includes('test')) return
9
+
10
+ let cfg = pm2Cfg()
11
+ let status = await execSync(`pm2 show ${cfg.apps[0].name}`)
12
+
13
+ if (status.stdout.includes('online')) {
14
+ logger.mark('检测到后台正在运行')
15
+ logger.mark('已停止后台进程,防止重复运行')
16
+ execSync(`pm2 stop ${cfg.apps[0].name}`)
17
+ }
18
+ }
19
+
20
+ async function execSync (cmd) {
21
+ return new Promise((resolve, reject) => {
22
+ exec(cmd, (error, stdout, stderr) => {
23
+ resolve({ error, stdout, stderr })
24
+ })
25
+ })
26
+ }
27
+
28
+ function pm2Cfg () {
29
+ let cfg = fs.readFileSync('config/pm2/pm2.json')
30
+ cfg = JSON.parse(cfg)
31
+ return cfg
32
+ }
Yunzai/lib/config/config.js ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import YAML from "yaml"
2
+ import fs from "node:fs"
3
+ import chokidar from "chokidar"
4
+
5
+ /** 配置文件 */
6
+ class Cfg {
7
+ constructor () {
8
+ this.config = {}
9
+
10
+ /** 监听文件 */
11
+ this.watcher = { config: {}, defSet: {} }
12
+
13
+ this.initCfg()
14
+ }
15
+
16
+ /** 初始化配置 */
17
+ initCfg () {
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配置 */
29
+ get bot () {
30
+ let bot = this.getConfig("bot")
31
+ let defbot = this.getdefSet("bot")
32
+ bot = { ...defbot, ...bot }
33
+
34
+ return bot
35
+ }
36
+
37
+ get other () {
38
+ return this.getConfig("other")
39
+ }
40
+
41
+ get redis () {
42
+ return this.getConfig("redis")
43
+ }
44
+
45
+ get renderer() {
46
+ return this.getConfig("renderer");
47
+ }
48
+
49
+ /** 主人账号 */
50
+ get masterQQ () {
51
+ let masterQQ = this.getConfig("other").masterQQ || []
52
+
53
+ if (!Array.isArray(masterQQ))
54
+ masterQQ = [masterQQ]
55
+
56
+ const masters = []
57
+ for (const i of masterQQ)
58
+ masters.push(Number(i) || String(i))
59
+ return masters
60
+ }
61
+
62
+ /** Bot账号:[主人帐号] */
63
+ get master () {
64
+ let master = this.getConfig("other").master || []
65
+
66
+ if (!Array.isArray(master))
67
+ master = [master]
68
+
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
+ }
79
+
80
+ /** 机器人账号 */
81
+ get uin () {
82
+ return Object.keys(this.master)
83
+ }
84
+ get qq () {
85
+ return this.uin
86
+ }
87
+
88
+ /** package.json */
89
+ get package () {
90
+ if (this._package) return this._package
91
+
92
+ this._package = JSON.parse(fs.readFileSync("package.json", "utf8"))
93
+ return this._package
94
+ }
95
+
96
+ /** 群配置 */
97
+ getGroup (bot_id = "", group_id = "") {
98
+ const config = this.getConfig("group")
99
+ const defCfg = this.getdefSet("group")
100
+ return {
101
+ ...defCfg.default,
102
+ ...config.default,
103
+ ...config[`${bot_id}:default`],
104
+ ...config[group_id],
105
+ ...config[`${bot_id}:${group_id}`],
106
+ }
107
+ }
108
+
109
+ /** other配置 */
110
+ getOther () {
111
+ let def = this.getdefSet("other")
112
+ let config = this.getConfig("other")
113
+ return { ...def, ...config }
114
+ }
115
+
116
+ /**
117
+ * @param app 功能
118
+ * @param name 配置文件名称
119
+ */
120
+ getdefSet (name) {
121
+ return this.getYaml("default_config", name)
122
+ }
123
+
124
+ /** 用户配置 */
125
+ getConfig (name) {
126
+ return this.getYaml("config", name)
127
+ }
128
+
129
+ /**
130
+ * 获取配置yaml
131
+ * @param type 默认跑配置-defSet,用户配置-config
132
+ * @param name 名称
133
+ */
134
+ getYaml (type, name) {
135
+ let file = `config/${type}/${name}.yaml`
136
+ let key = `${type}.${name}`
137
+ if (this.config[key]) return this.config[key]
138
+
139
+ this.config[key] = YAML.parse(
140
+ fs.readFileSync(file, "utf8")
141
+ )
142
+
143
+ this.watch(file, name, type)
144
+
145
+ return this.config[key]
146
+ }
147
+
148
+ /** 监听配置文件 */
149
+ watch (file, name, type = "default_config") {
150
+ let key = `${type}.${name}`
151
+
152
+ if (this.watcher[key]) return
153
+
154
+ const watcher = chokidar.watch(file)
155
+ watcher.on("change", path => {
156
+ delete this.config[key]
157
+ if (typeof Bot == "undefined") return
158
+ logger.mark(`[修改配置文件][${type}][${name}]`)
159
+ if (this[`change_${name}`]) {
160
+ this[`change_${name}`]()
161
+ }
162
+ })
163
+
164
+ this.watcher[key] = watcher
165
+ }
166
+
167
+ async change_bot () {
168
+ /** 修改日志等级 */
169
+ let log = await import("./log.js")
170
+ log.default()
171
+ }
172
+ }
173
+
174
+ export default new Cfg()
Yunzai/lib/config/init.js ADDED
@@ -0,0 +1,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'
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()
29
+
30
+ /** 初始化事件 */
31
+ 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
+ }
Yunzai/lib/config/log.js ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import log4js from 'log4js'
2
+ import chalk from 'chalk'
3
+ import cfg from './config.js'
4
+ import fs from 'node:fs'
5
+
6
+ /**
7
+ * 设置日志样式
8
+ */
9
+ export default function setLog () {
10
+ let file = './logs'
11
+ if (!fs.existsSync(file)) {
12
+ fs.mkdirSync(file)
13
+ }
14
+
15
+ /** 调整error日志等级 */
16
+ // log4js.levels.levels[5].level = Number.MAX_VALUE
17
+ // log4js.levels.levels.sort((a, b) => a.level - b.level)
18
+
19
+ log4js.configure({
20
+ appenders: {
21
+ console: {
22
+ type: 'console',
23
+ layout: {
24
+ type: 'pattern',
25
+ pattern: '%[[TRSSYz][%d{hh:mm:ss.SSS}][%4.4p]%] %m'
26
+ }
27
+ },
28
+ command: {
29
+ type: 'dateFile', // 可以是console,dateFile,file,Logstash等
30
+ filename: 'logs/command', // 将会按照filename和pattern拼接文件名
31
+ pattern: 'yyyy-MM-dd.log',
32
+ numBackups: 15,
33
+ alwaysIncludePattern: true,
34
+ layout: {
35
+ type: 'pattern',
36
+ pattern: '[%d{hh:mm:ss.SSS}][%4.4p] %m'
37
+ }
38
+ },
39
+ error: {
40
+ type: 'file',
41
+ filename: 'logs/error.log',
42
+ alwaysIncludePattern: true,
43
+ layout: {
44
+ type: 'pattern',
45
+ pattern: '[%d{hh:mm:ss.SSS}][%4.4p] %m'
46
+ }
47
+ }
48
+ },
49
+ categories: {
50
+ default: { appenders: ['console'], level: cfg.bot.log_level },
51
+ command: { appenders: ['console', 'command'], level: 'warn' },
52
+ error: { appenders: ['console', 'command', 'error'], level: 'error' }
53
+ }
54
+ })
55
+
56
+ const defaultLogger = log4js.getLogger('message')
57
+ const commandLogger = log4js.getLogger('command')
58
+ const errorLogger = log4js.getLogger('error')
59
+
60
+ /* eslint-disable no-useless-call */
61
+ /** 全局变量 logger */
62
+ global.logger = {
63
+ trace () {
64
+ defaultLogger.trace.call(defaultLogger, ...arguments)
65
+ },
66
+ debug () {
67
+ defaultLogger.debug.call(defaultLogger, ...arguments)
68
+ },
69
+ info () {
70
+ defaultLogger.info.call(defaultLogger, ...arguments)
71
+ },
72
+ // warn及以上的日志采用error策略
73
+ warn () {
74
+ commandLogger.warn.call(defaultLogger, ...arguments)
75
+ },
76
+ error () {
77
+ errorLogger.error.call(errorLogger, ...arguments)
78
+ },
79
+ fatal () {
80
+ errorLogger.fatal.call(errorLogger, ...arguments)
81
+ },
82
+ mark () {
83
+ errorLogger.mark.call(commandLogger, ...arguments)
84
+ }
85
+ }
86
+
87
+ logColor()
88
+ }
89
+
90
+ function logColor () {
91
+ logger.chalk = chalk
92
+ logger.red = chalk.red
93
+ logger.green = chalk.green
94
+ logger.yellow = chalk.yellow
95
+ logger.blue = chalk.blue
96
+ logger.magenta = chalk.magenta
97
+ logger.cyan = chalk.cyan
98
+ }
Yunzai/lib/config/redis.js ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cfg from "./config.js"
2
+ import common from "../common/common.js"
3
+ import { createClient } from "redis"
4
+ import { exec } from "node:child_process"
5
+
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
+ let client = createClient({ url: redisUrl })
17
+
18
+ try {
19
+ logger.info(`正在连接 ${logger.blue(redisUrl)}`)
20
+ await client.connect()
21
+ } catch (err) {
22
+ logger.error(`Redis 错误:${logger.red(err)}`)
23
+
24
+ const cmd = "redis-server --save 900 1 --save 300 10 --daemonize yes" + await aarch64()
25
+ logger.info("正在启动 Redis...")
26
+ await execSync(cmd)
27
+ await common.sleep(1000)
28
+
29
+ try {
30
+ client = createClient({ url: redisUrl })
31
+ await client.connect()
32
+ } catch (err) {
33
+ logger.error(`Redis 错误:${logger.red(err)}`)
34
+ logger.error(`请先启动 Redis:${logger.blue(cmd)}`)
35
+ process.exit()
36
+ }
37
+ }
38
+
39
+ client.on("error", async err => {
40
+ logger.error(`Redis 错误:${logger.red(err)}`)
41
+ const cmd = "redis-server --save 900 1 --save 300 10 --daemonize yes" + await aarch64()
42
+ logger.error(`请先启动 Redis:${cmd}`)
43
+ process.exit()
44
+ })
45
+
46
+ /** 全局变量 redis */
47
+ global.redis = client
48
+ logger.info("Redis 连接成功")
49
+ return client
50
+ }
51
+
52
+ async function aarch64() {
53
+ if (process.platform == "win32")
54
+ return ""
55
+ /** 判断arch */
56
+ const arch = await execSync("uname -m")
57
+ if (arch.stdout && arch.stdout.includes("aarch64")) {
58
+ /** 判断redis版本 */
59
+ let v = await execSync("redis-server -v")
60
+ if (v.stdout) {
61
+ v = v.stdout.match(/v=(\d)./)
62
+ /** 忽略arm警告 */
63
+ if (v && v[1] >= 6)
64
+ return " --ignore-warnings ARM64-COW-BUG"
65
+ }
66
+ }
67
+ return ""
68
+ }
69
+
70
+ function execSync (cmd) {
71
+ return new Promise((resolve, reject) => {
72
+ exec(cmd, (error, stdout, stderr) => {
73
+ resolve({ error, stdout, stderr })
74
+ })
75
+ })
76
+ }
Yunzai/lib/events/connect.js ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import EventListener from "../listener/listener.js"
2
+ import cfg from "../config/config.js"
3
+
4
+ /**
5
+ * 监听连接事件
6
+ */
7
+ export default class connectEvent extends EventListener {
8
+ constructor() {
9
+ super({ event: "connect" })
10
+ }
11
+
12
+ async execute(e) {
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
+ }
23
+ }
Yunzai/lib/events/message.js ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import EventListener from '../listener/listener.js'
2
+
3
+ /**
4
+ * 监听群聊消息
5
+ */
6
+ export default class messageEvent extends EventListener {
7
+ constructor () {
8
+ super({ event: 'message' })
9
+ }
10
+
11
+ async execute (e) {
12
+ this.plugins.deal(e)
13
+ }
14
+ }
Yunzai/lib/events/notice.js ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import EventListener from '../listener/listener.js'
2
+
3
+ /**
4
+ * 监听群聊消息
5
+ */
6
+ export default class noticeEvent extends EventListener {
7
+ constructor () {
8
+ super({ event: 'notice' })
9
+ }
10
+
11
+ async execute (e) {
12
+ this.plugins.deal(e)
13
+ }
14
+ }
Yunzai/lib/events/online.js ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import EventListener from '../listener/listener.js'
2
+ import cfg from '../config/config.js'
3
+
4
+ /**
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/events/request.js ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import EventListener from '../listener/listener.js'
2
+
3
+ /**
4
+ * 监听群聊消息
5
+ */
6
+ export default class requestEvent extends EventListener {
7
+ constructor () {
8
+ super({ event: 'request' })
9
+ }
10
+
11
+ async execute (e) {
12
+ this.plugins.deal(e)
13
+ }
14
+ }
Yunzai/lib/listener/listener.js ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import PluginsLoader from '../plugins/loader.js'
2
+
3
+ export default class EventListener {
4
+ /**
5
+ * 事件监听
6
+ * @param data.prefix 事件名称前缀
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
14
+ this.plugins = PluginsLoader
15
+ }
16
+ }
Yunzai/lib/listener/loader.js ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fs from 'node:fs'
2
+ import lodash from 'lodash'
3
+
4
+ /**
5
+ * 加载监听事件
6
+ */
7
+ class ListenerLoader {
8
+ /**
9
+ * 监听事件加载
10
+ */
11
+ async load () {
12
+ logger.info("-----------")
13
+ logger.info("加载监听事件中...")
14
+ let eventCount = 0
15
+ for (const file of fs.readdirSync('./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
20
+ listener = new listener.default()
21
+ const on = listener.once ? 'once' : 'on'
22
+
23
+ if (lodash.isArray(listener.event)) {
24
+ listener.event.forEach((type) => {
25
+ const e = listener[type] ? type : 'execute'
26
+ Bot[on](listener.prefix + type, event => listener[e](event))
27
+ })
28
+ } else {
29
+ const e = listener[listener.event] ? listener.event : 'execute'
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
+
57
+ export default new ListenerLoader()
Yunzai/lib/modules/oicq/index.js ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 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
+
22
+ 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
+ }
29
+ at(qq, name) {
30
+ return toSegment("at", { qq, name })
31
+ }
32
+ record(file, name) {
33
+ return toSegment("record", { file, name })
34
+ }
35
+ video(file, name) {
36
+ return toSegment("video", { file, name })
37
+ }
38
+ file(file, name) {
39
+ return toSegment("file", { file, name })
40
+ }
41
+ reply(id, text, qq, time, seq) {
42
+ return toSegment("reply", { id, text, qq, time, seq })
43
+ }
44
+ face(id) {
45
+ return toSegment("face", { id })
46
+ }
47
+ share(url, title, content, image) {
48
+ return toSegment("share", { url, title, content, image })
49
+ }
50
+ music(type, id, url, audio, title) {
51
+ return toSegment("music", { type, id, url, audio, title })
52
+ }
53
+ poke(qq) {
54
+ return toSegment("poke", { qq })
55
+ }
56
+ gift(qq, id) {
57
+ return toSegment("gift", { qq, id })
58
+ }
59
+ cardimage(file, name, minwidth, minheight, maxwidth, maxheight, source, icon) {
60
+ return toSegment("cardimage", { file, name, minwidth, minheight, maxwidth, maxheight, source, icon })
61
+ }
62
+ tts(text) {
63
+ return toSegment("tts", { text })
64
+ }
65
+ }
66
+
67
+ export { segment }
Yunzai/lib/modules/oicq/package.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "name": "oicq",
3
+ "type": "module",
4
+ "main": "index.js"
5
+ }
Yunzai/lib/plugins/handler.js ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import util from 'node:util'
2
+ import lodash from 'lodash'
3
+
4
+ let events = {}
5
+ let Handler = {
6
+ add (cfg) {
7
+ let { ns, fn, self, property = 50 } = cfg
8
+ let key = cfg.key || cfg.event
9
+ if (!key || !fn) {
10
+ return
11
+ }
12
+ Handler.del(ns, key)
13
+ logger.mark(`[Handler][Reg]: [${ns}][${key}]`)
14
+ events[key] = events[key] || []
15
+ events[key].push({
16
+ property,
17
+ fn,
18
+ ns,
19
+ self,
20
+ key
21
+ })
22
+ events[key] = lodash.orderBy(events[key], ['priority'], ['asc'])
23
+ },
24
+ del (ns, key = '') {
25
+ if (!key) {
26
+ for (let key in events) {
27
+ Handler.del(ns, key)
28
+ }
29
+ return
30
+ }
31
+ if (!events[key]) {
32
+ return
33
+ }
34
+ for (let idx = 0; idx < events[key].length; idx++) {
35
+ let handler = events[key][idx]
36
+ if (handler.ns === ns) {
37
+ events[key].splice(idx, 1)
38
+ events[key] = lodash.orderBy(events[key], ['priority'], ['asc'])
39
+ }
40
+ }
41
+ },
42
+ async callAll (key, e, args) {
43
+ // 暂时屏蔽调用
44
+ // return Handler.call(key, e, args, true)
45
+ },
46
+ async call (key, e, args, allHandler = false) {
47
+ let ret
48
+ for (let obj of events[key]) {
49
+ let fn = obj.fn
50
+ let done = true
51
+ let reject = (msg = '') => {
52
+ if (msg) {
53
+ logger.mark(`[Handler][Reject]: [${obj.ns}][${key}] ${msg}`)
54
+ }
55
+ done = false
56
+ }
57
+ ret = fn.call(obj.self, e, args, reject)
58
+ if (util.types.isPromise(ret)) {
59
+ ret = await ret
60
+ }
61
+ if (done && !allHandler) {
62
+ logger.mark(`[Handler][Done]: [${obj.ns}][${key}]`)
63
+ return ret
64
+ }
65
+ }
66
+ return ret
67
+ },
68
+ has (key) {
69
+ return !!events[key]
70
+ }
71
+ }
72
+ export default Handler
73
+
Yunzai/lib/plugins/loader.js ADDED
@@ -0,0 +1,872 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import util from "node:util"
2
+ import fs from "node:fs"
3
+ import lodash from "lodash"
4
+ import cfg from "../config/config.js"
5
+ import plugin from "./plugin.js"
6
+ import schedule from "node-schedule"
7
+ import { segment } from "oicq"
8
+ import chokidar from "chokidar"
9
+ import moment from "moment"
10
+ import path from "node:path"
11
+ import common from "../common/common.js"
12
+ import Runtime from "./runtime.js"
13
+ import Handler from './handler.js'
14
+
15
+ /** 全局变量 plugin */
16
+ global.plugin = plugin
17
+ global.segment = segment
18
+
19
+ /**
20
+ * 加载插件
21
+ */
22
+ class PluginsLoader {
23
+ constructor() {
24
+ this.priority = []
25
+ this.handler = {}
26
+ this.task = []
27
+ this.dir = "./plugins"
28
+
29
+ /** 命令冷却cd */
30
+ this.groupCD = {}
31
+ this.singleCD = {}
32
+
33
+ /** 插件监听 */
34
+ this.watcher = {}
35
+
36
+ this.msgThrottle = {}
37
+
38
+ /** 星铁命令前缀 */
39
+ this.srReg = /^#?(\*|星铁|星轨|穹轨|星穹|崩铁|星穹铁道|崩坏星穹铁道|铁道)+/
40
+ }
41
+
42
+ /**
43
+ * 监听事件加载
44
+ * @param isRefresh 是否刷新
45
+ */
46
+ async load(isRefresh = false) {
47
+ this.delCount()
48
+ if (!lodash.isEmpty(this.priority) && !isRefresh) return
49
+
50
+ const files = this.getPlugins()
51
+
52
+ logger.info("-----------")
53
+ logger.info("加载插件中...")
54
+
55
+ let pluCount = 0
56
+
57
+ let packageErr = []
58
+ for (let File of files) {
59
+ try {
60
+ let tmp = await import(File.path)
61
+ let apps = tmp
62
+ if (tmp.apps) {
63
+ apps = { ...tmp.apps }
64
+ }
65
+ lodash.forEach(apps, (p, i) => {
66
+ if (!p.prototype) return
67
+ pluCount++
68
+ /* eslint-disable new-cap */
69
+ let plugin = new p()
70
+ logger.debug(`载入插件 [${File.name}][${plugin.name}]`)
71
+ /** 执行初始化 */
72
+ this.runInit(plugin)
73
+ /** 初始化定时任务 */
74
+ this.collectTask(plugin.task)
75
+ this.priority.push({
76
+ class: p,
77
+ key: File.name,
78
+ name: plugin.name,
79
+ priority: plugin.priority
80
+ })
81
+ if (plugin.handler) {
82
+ lodash.forEach(plugin.handler, ({ fn, key, priority }) => {
83
+ Handler.add({
84
+ ns: plugin.namespace || File.name,
85
+ key: key,
86
+ self: plugin,
87
+ property: priority || plugin.priority || 500,
88
+ fn: plugin[fn]
89
+ })
90
+ })
91
+ }
92
+ })
93
+ } catch (error) {
94
+ if (error.stack.includes("Cannot find package")) {
95
+ packageErr.push({ error, File })
96
+ } else {
97
+ logger.error(`载入插件错误:${logger.red(File.name)}`)
98
+ logger.error(decodeURI(error.stack))
99
+ }
100
+ }
101
+ }
102
+
103
+ this.packageTips(packageErr)
104
+ this.creatTask()
105
+
106
+ logger.info(`加载定时任务[${this.task.length}个]`)
107
+ logger.info(`加载插件[${pluCount}个]`)
108
+
109
+ /** 优先级排序 */
110
+ this.priority = lodash.orderBy(this.priority, ["priority"], ["asc"])
111
+ }
112
+
113
+ async runInit(plugin) {
114
+ plugin.init && plugin.init()
115
+ }
116
+
117
+ packageTips(packageErr) {
118
+ if (!packageErr || packageErr.length <= 0) return
119
+ logger.mark("--------插件载入错误--------")
120
+ packageErr.forEach(v => {
121
+ let pack = v.error.stack.match(/'(.+?)'/g)[0].replace(/'/g, "")
122
+ logger.mark(`${v.File.name} 缺少依赖:${logger.red(pack)}`)
123
+ logger.mark(`新增插件后请执行安装命令:${logger.red("pnpm i")} 安装依赖`)
124
+ logger.mark("如安装后仍未解决可联系插件作者解决")
125
+ })
126
+ // logger.error("或者使用其他包管理工具安装依赖")
127
+ logger.mark("---------------------")
128
+ }
129
+
130
+ getPlugins() {
131
+ let ignore = ["index.js"]
132
+ let files = fs.readdirSync(this.dir, { withFileTypes: true })
133
+ let ret = []
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
141
+ if (ignore.includes(val.name)) continue
142
+ tmp.path = filepath
143
+ ret.push(tmp)
144
+ continue
145
+ }
146
+
147
+ if (fs.existsSync(`${this.dir}/${val.name}/index.js`)) {
148
+ tmp.path = filepath + "/index.js"
149
+ ret.push(tmp)
150
+ continue
151
+ }
152
+
153
+ let apps = fs.readdirSync(`${this.dir}/${val.name}`, { withFileTypes: true })
154
+ for (let app of apps) {
155
+ if (!app.name.endsWith(".js")) continue
156
+ if (ignore.includes(app.name)) continue
157
+
158
+ ret.push({
159
+ name: `${val.name}/${app.name}`,
160
+ path: `../../plugins/${val.name}/${app.name}`
161
+ })
162
+
163
+ /** 监听热更新 */
164
+ this.watch(val.name, app.name)
165
+ }
166
+ }
167
+
168
+ return ret
169
+ }
170
+
171
+ /**
172
+ * 处理事件
173
+ *
174
+ * 参数文档 https://github.com/TimeRainStarSky/Yunzai/tree/docs
175
+ * @param e 事件
176
+ */
177
+ async deal(e) {
178
+ /** 检查黑白名单 */
179
+ if (!this.checkBlack(e)) return
180
+ /** 冷却 */
181
+ if (!this.checkLimit(e)) return
182
+ /** 处理事件 */
183
+ this.dealEvent(e)
184
+ /** 处理消息 */
185
+ this.dealMsg(e)
186
+ /** 处理回复 */
187
+ this.reply(e)
188
+ /** 过滤事件 */
189
+ let priority = []
190
+ /** 注册runtime */
191
+ await Runtime.init(e)
192
+
193
+ this.priority.forEach(v => {
194
+ let p = new v.class(e)
195
+ p.e = e
196
+ /** 判断是否启用功能 */
197
+ if (!this.checkDisable(e, p)) return
198
+ /** 过滤事件 */
199
+ if (!this.filtEvent(e, p)) return
200
+ priority.push(p)
201
+ })
202
+
203
+ for (let plugin of priority) {
204
+ /** 上下文hook */
205
+ if (plugin.getContext) {
206
+ let context = plugin.getContext()
207
+ if (!lodash.isEmpty(context)) {
208
+ for (let fnc in context) {
209
+ plugin[fnc](context[fnc])
210
+ }
211
+ return
212
+ }
213
+ }
214
+
215
+ /** 群上下文hook */
216
+ if (plugin.getContextGroup) {
217
+ let context = plugin.getContextGroup()
218
+ if (!lodash.isEmpty(context)) {
219
+ for (let fnc in context) {
220
+ plugin[fnc](context[fnc])
221
+ }
222
+ return
223
+ }
224
+ }
225
+ }
226
+
227
+ /** 是否只关注主动at */
228
+ if (!this.onlyReplyAt(e)) return
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
+
237
+ /** accept */
238
+ for (let plugin of priority) {
239
+ /** accept hook */
240
+ if (plugin.accept) {
241
+ let res = plugin.accept(e)
242
+
243
+ if (util.types.isPromise(res)) res = await res
244
+
245
+ if (res === "return") return
246
+
247
+ if (res) break
248
+ }
249
+ }
250
+
251
+ /* eslint-disable no-labels */
252
+ a: for (let plugin of priority) {
253
+ /** 正则匹配 */
254
+ if (plugin.rule) {
255
+ for (let v of plugin.rule) {
256
+ /** 判断事件 */
257
+ if (v.event && !this.filtEvent(e, v)) continue
258
+
259
+ if (new RegExp(v.reg).test(e.msg)) {
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
+ /** 判断权限 */
267
+ if (!this.filtPermission(e, v)) break a
268
+
269
+ try {
270
+ let res = plugin[v.fnc] && plugin[v.fnc](e)
271
+
272
+ let start = Date.now()
273
+
274
+ if (util.types.isPromise(res)) res = await res
275
+
276
+ if (res !== false) {
277
+ /** 设置冷却cd */
278
+ this.setLimit(e)
279
+ if (v.log !== false) {
280
+ logger.mark(`${e.logFnc} ${lodash.truncate(e.msg, { length: 80 })} 处理完成 ${Date.now() - start}ms`)
281
+ }
282
+ break a
283
+ }
284
+ } catch (error) {
285
+ logger.error(`${e.logFnc}`)
286
+ logger.error(error.stack)
287
+ break a
288
+ }
289
+ }
290
+ }
291
+ }
292
+ }
293
+ }
294
+
295
+ /** 过滤事件 */
296
+ filtEvent(e, v) {
297
+ if (!v.event) return false
298
+ let event = v.event.split(".")
299
+ let eventMap = {
300
+ message: ["post_type", "message_type", "sub_type"],
301
+ notice: ["post_type", "notice_type", "sub_type"],
302
+ request: ["post_type", "request_type", "sub_type"]
303
+ }
304
+ let newEvent = []
305
+ event.forEach((val, index) => {
306
+ if (val === "*") {
307
+ newEvent.push(val)
308
+ } else if (eventMap[e.post_type]) {
309
+ newEvent.push(e[eventMap[e.post_type][index]])
310
+ }
311
+ })
312
+ newEvent = newEvent.join(".")
313
+
314
+ return v.event === newEvent
315
+ }
316
+
317
+ /** 判断权限 */
318
+ filtPermission(e, v) {
319
+ if (v.permission == "all" || !v.permission) return true
320
+
321
+ if (v.permission == "master") {
322
+ if (e.isMaster) {
323
+ return true
324
+ } else {
325
+ e.reply("暂无权限,只有主人才能操作")
326
+ return false
327
+ }
328
+ }
329
+
330
+ if (e.isGroup) {
331
+ if (!e.member?._info) {
332
+ e.reply("数据加载中,请稍后再试")
333
+ return false
334
+ }
335
+ if (v.permission == "owner") {
336
+ if (!e.member.is_owner) {
337
+ e.reply("暂无权限,只有群主才能操作")
338
+ return false
339
+ }
340
+ }
341
+ if (v.permission == "admin") {
342
+ if (!e.member.is_admin) {
343
+ e.reply("暂无权限,只有管理员才能操作")
344
+ return false
345
+ }
346
+ }
347
+ }
348
+
349
+ return true
350
+ }
351
+
352
+ dealEvent(e) {
353
+ if (!e.friend && e.user_id) e.friend = e.bot.pickFriend(e.user_id)
354
+ if (!e.group && e.group_id) e.group = e.bot.pickGroup(e.group_id)
355
+ if (!e.member && e.group && e.user_id) e.member = e.group.pickMember(e.user_id)
356
+ for (const i of [e.friend, e.group, e.member]) {
357
+ if (typeof i != "object") continue
358
+ if (!i.makeForwardMsg) i.makeForwardMsg = Bot.makeForwardMsg
359
+ if (!i.sendForwardMsg) i.sendForwardMsg = msg => Bot.sendForwardMsg(msg => i.sendMsg(msg), msg)
360
+ if (!i.getInfo) i.getInfo = () => i
361
+ }
362
+ }
363
+
364
+ /**
365
+ * 处理消息,加入自定义���段
366
+ * @param e.msg 文本消息,多行会自动拼接
367
+ * @param e.img 图片消息数组
368
+ * @param e.atBot 是否at机器人
369
+ * @param e.at 是否at,多个at 以最后的为准
370
+ * @param e.file 接受到的文件
371
+ * @param e.isPrivate 是否私聊
372
+ * @param e.isGroup 是否群聊
373
+ * @param e.isMaster 是否管理员
374
+ * @param e.logText 日志用户字符串
375
+ * @param e.logFnc 日志方法字符串
376
+ */
377
+ dealMsg(e) {
378
+ if (e.message) for (const i of e.message) {
379
+ switch (i.type) {
380
+ case "text":
381
+ if (!e.msg) e.msg = ""
382
+ if (i.text) e.msg += i.text.replace(/^\s*[##井]+\s*/, "#").replace(/^\s*[\\**※]+\s*/, "*").trim()
383
+ break
384
+ case "image":
385
+ if (Array.isArray(e.img))
386
+ e.img.push(i.url)
387
+ else
388
+ e.img = [i.url]
389
+ break
390
+ case "at":
391
+ if (i.qq == e.self_id)
392
+ e.atBot = true
393
+ else
394
+ e.at = i.qq
395
+ break
396
+ case "reply":
397
+ e.reply_id = i.id
398
+ if (e.group?.getMsg)
399
+ e.getReply = () => e.group.getMsg(e.reply_id)
400
+ else if (e.friend?.getMsg)
401
+ e.getReply = () => e.friend.getMsg(e.reply_id)
402
+ break
403
+ case "file":
404
+ e.file = i
405
+ break
406
+ }
407
+ }
408
+
409
+ e.logText = ""
410
+
411
+ if (e.message_type == "private" || e.notice_type == "friend") {
412
+ e.isPrivate = true
413
+
414
+ if (e.sender) {
415
+ e.sender.card = e.sender.nickname
416
+ } else {
417
+ e.sender = {
418
+ card: e.friend?.nickname,
419
+ nickname: e.friend?.nickname
420
+ }
421
+ }
422
+
423
+ e.logText = `[${e.sender?.nickname ? `${e.sender.nickname}(${e.user_id})` : e.user_id}]`
424
+ }
425
+
426
+ if (e.message_type == "group" || e.notice_type == "group") {
427
+ e.isGroup = true
428
+ if (e.sender) {
429
+ e.sender.card = e.sender.card || e.sender.nickname
430
+ } else if (e.member) {
431
+ e.sender = {
432
+ card: e.member.card || e.member.nickname
433
+ }
434
+ } else if (e.nickname) {
435
+ e.sender = {
436
+ card: e.nickname,
437
+ nickname: e.nickname
438
+ }
439
+ } else {
440
+ e.sender = {
441
+ card: "",
442
+ nickname: ""
443
+ }
444
+ }
445
+
446
+ if (!e.group_name) e.group_name = e.group?.name
447
+
448
+ e.logText = `[${e.group_name ? `${e.group_name}(${e.group_id})` : e.group_id}, ${e.sender?.nickname ? `${e.sender.nickname}(${e.user_id})` : e.user_id}]`
449
+ }
450
+
451
+ if (e.user_id && cfg.master[e.self_id]?.includes(String(e.user_id))) {
452
+ e.isMaster = true
453
+ }
454
+
455
+ /** 只关注主动at msg处理 */
456
+ if (e.msg && e.isGroup) {
457
+ let groupCfg = cfg.getGroup(e.self_id, e.group_id)
458
+ let alias = groupCfg.botAlias
459
+ if (!Array.isArray(alias)) {
460
+ alias = [alias]
461
+ }
462
+ for (let name of alias) {
463
+ if (e.msg.startsWith(name)) {
464
+ e.msg = lodash.trimStart(e.msg, name).trim()
465
+ e.hasAlias = true
466
+ break
467
+ }
468
+ }
469
+ }
470
+ }
471
+
472
+ /** 处理回复,捕获发送失败异常 */
473
+ reply(e) {
474
+ if (e.reply)
475
+ e.replyNew = e.reply
476
+ else
477
+ e.replyNew = msg => {
478
+ if (e.isGroup) {
479
+ if (e.group?.sendMsg) {
480
+ return e.group.sendMsg(msg)
481
+ } else {
482
+ return e.bot.pickGroup(e.group_id).sendMsg(msg)
483
+ }
484
+ } else {
485
+ if (e.friend?.sendMsg) {
486
+ return e.friend.sendMsg(msg)
487
+ } else {
488
+ return e.bot.pickFriend(e.user_id).sendMsg(msg)
489
+ }
490
+ }
491
+ }
492
+
493
+ /**
494
+ * @param msg 发送的消息
495
+ * @param quote 是否引用回复
496
+ * @param data.recallMsg 是否撤回消息,0-120秒,0不撤回
497
+ * @param data.at 是否提及用户
498
+ */
499
+ e.reply = async (msg = "", quote = false, data = {}) => {
500
+ if (!msg) return false
501
+
502
+ let { recallMsg = 0, at = "" } = data
503
+
504
+ if (at) {
505
+ if (at === true)
506
+ at = e.user_id
507
+ if (Array.isArray(msg))
508
+ msg.unshift(segment.at(at))
509
+ else
510
+ msg = [segment.at(at), msg]
511
+ }
512
+
513
+ if (quote && e.message_id) {
514
+ if (Array.isArray(msg))
515
+ msg.unshift(segment.reply(e.message_id))
516
+ else
517
+ msg = [segment.reply(e.message_id), msg]
518
+ }
519
+
520
+ let res
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
+
530
+ if (recallMsg > 0 && res?.message_id) {
531
+ if (e.group?.recallMsg)
532
+ setTimeout(() => {
533
+ e.group.recallMsg(res.message_id)
534
+ if (e.message_id)
535
+ e.group.recallMsg(e.message_id)
536
+ }, recallMsg * 1000)
537
+ else if (e.friend?.recallMsg)
538
+ setTimeout(() => {
539
+ e.friend.recallMsg(res.message_id)
540
+ if (e.message_id)
541
+ e.friend.recallMsg(e.message_id)
542
+ }, recallMsg * 1000)
543
+ }
544
+
545
+ this.count(e, msg)
546
+ return res
547
+ }
548
+ }
549
+
550
+ count(e, msg) {
551
+ let screenshot = false
552
+ if (msg && msg?.file)
553
+ screenshot = true
554
+
555
+ this.saveCount("sendMsg")
556
+ if (screenshot)
557
+ this.saveCount("screenshot")
558
+
559
+ if (e.group_id) {
560
+ this.saveCount("sendMsg", e.group_id)
561
+ if (screenshot)
562
+ this.saveCount("screenshot", e.group_id)
563
+ }
564
+ }
565
+
566
+ saveCount(type, groupId = "") {
567
+ let key = "Yz:count:"
568
+
569
+ if (groupId) {
570
+ key += `group:${groupId}:`
571
+ }
572
+
573
+ let dayKey = `${key}${type}:day:${moment().format("MMDD")}`
574
+ let monthKey = `${key}${type}:month:${Number(moment().month()) + 1}`
575
+ let totalKey = `${key}${type}:total`
576
+
577
+ redis.incr(dayKey)
578
+ redis.incr(monthKey)
579
+ if (!groupId) redis.incr(totalKey)
580
+ redis.expire(dayKey, 3600 * 24 * 30)
581
+ redis.expire(monthKey, 3600 * 24 * 30)
582
+ }
583
+
584
+ delCount() {
585
+ let key = "Yz:count:"
586
+ redis.set(`${key}sendMsg:total`, "0")
587
+ redis.set(`${key}screenshot:total`, "0")
588
+ }
589
+
590
+ /** 收集定时任务 */
591
+ collectTask(task) {
592
+ if (Array.isArray(task)) {
593
+ task.forEach((val) => {
594
+ if (!val.cron) return
595
+ if (!val.name) throw new Error("插件任务名称错误")
596
+ this.task.push(val)
597
+ })
598
+ } else {
599
+ if (task.fnc && task.cron) {
600
+ if (!task.name) throw new Error("插件任务名称错误")
601
+ this.task.push(task)
602
+ }
603
+ }
604
+ }
605
+
606
+ /** 创建定时任务 */
607
+ creatTask() {
608
+ if (process.argv[1].includes("test")) return
609
+ this.task.forEach((val) => {
610
+ val.job = schedule.scheduleJob(val.cron, async () => {
611
+ try {
612
+ if (val.log === true) {
613
+ logger.mark(`开始定时任务:${val.name}`)
614
+ }
615
+ let res = val.fnc()
616
+ if (util.types.isPromise(res)) res = await res
617
+ if (val.log === true) {
618
+ logger.mark(`定时任务完成:${val.name}`)
619
+ }
620
+ } catch (error) {
621
+ logger.error(`定时任务报错:${val.name}`)
622
+ logger.error(error)
623
+ }
624
+ })
625
+ })
626
+ }
627
+
628
+ /** 检查命令冷却cd */
629
+ checkLimit(e) {
630
+ /** 禁言中 */
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
+ }
656
+
657
+ /** 设置冷却cd */
658
+ setLimit(e) {
659
+ if (!e.message || e.isPrivate) return
660
+ let config = cfg.getGroup(e.self_id, e.group_id)
661
+
662
+ if (config.groupCD) {
663
+ this.groupCD[e.group_id] = true
664
+ setTimeout(() => {
665
+ delete this.groupCD[e.group_id]
666
+ }, config.groupCD)
667
+ }
668
+ if (config.singleCD) {
669
+ let key = `${e.group_id}.${e.user_id}`
670
+ this.singleCD[key] = true
671
+ setTimeout(() => {
672
+ delete this.singleCD[key]
673
+ }, config.singleCD)
674
+ }
675
+ }
676
+
677
+ /** 是否只关注主动at */
678
+ onlyReplyAt(e) {
679
+ if (!e.message || e.isPrivate) return true
680
+
681
+ let groupCfg = cfg.getGroup(e.self_id, e.group_id)
682
+
683
+ if (groupCfg.onlyReplyAt != 1 || !groupCfg.botAlias) return true
684
+
685
+ /** at机器人 */
686
+ if (e.atBot) return true
687
+
688
+ /** 消息带前缀 */
689
+ if (e.hasAlias) return true
690
+
691
+ return false
692
+ }
693
+
694
+ /** 判断黑白名单 */
695
+ checkBlack(e) {
696
+ let other = cfg.getOther()
697
+
698
+ if (e.test) return true
699
+
700
+ /** 黑名单qq */
701
+ if (other.blackQQ?.length && other.blackQQ.includes(Number(e.user_id) || String(e.user_id))) {
702
+ return false
703
+ }
704
+
705
+ if (e.group_id) {
706
+ /** 白名单群 */
707
+ if (other.whiteGroup?.length) {
708
+ if (other.whiteGroup.includes(Number(e.group_id) || String(e.group_id))) return true
709
+ return false
710
+ }
711
+ /** 黑名单群 */
712
+ if (other.blackGroup?.length && other.blackGroup.includes(Number(e.group_id) || String(e.group_id))) {
713
+ return false
714
+ }
715
+ }
716
+
717
+ return true
718
+ }
719
+
720
+ /** 判断是否启用功能 */
721
+ checkDisable(e, p) {
722
+ let groupCfg = cfg.getGroup(e.self_id, e.group_id)
723
+ if (!lodash.isEmpty(groupCfg.enable)) {
724
+ if (groupCfg.enable.includes(p.name)) {
725
+ return true
726
+ }
727
+ // logger.debug(`${e.logText}[${p.name}]功能已禁用`)
728
+ return false
729
+ }
730
+
731
+ if (!lodash.isEmpty(groupCfg.disable)) {
732
+ if (groupCfg.disable.includes(p.name)) {
733
+ // logger.debug(`${e.logText}[${p.name}]功能已禁用`)
734
+ return false
735
+ }
736
+
737
+ return true
738
+ }
739
+ return true
740
+ }
741
+
742
+ /** 监听热更新 */
743
+ watch(dirName, appName) {
744
+ this.watchDir(dirName)
745
+ if (this.watcher[`${dirName}.${appName}`]) return
746
+
747
+ let file = `./plugins/${dirName}/${appName}`
748
+ const watcher = chokidar.watch(file)
749
+ let key = `${dirName}/${appName}`
750
+
751
+ /** 监听修改 */
752
+ watcher.on("change", async path => {
753
+ logger.mark(`[修改插件][${dirName}][${appName}]`)
754
+
755
+ let tmp = {}
756
+ try {
757
+ tmp = await import(`../../plugins/${dirName}/${appName}?${moment().format("x")}`)
758
+ } catch (error) {
759
+ logger.error(`载入插件错误:${logger.red(dirName + "/" + appName)}`)
760
+ logger.error(decodeURI(error.stack))
761
+ return
762
+ }
763
+
764
+ if (tmp.apps) tmp = { ...tmp.apps }
765
+ lodash.forEach(tmp, (p) => {
766
+ /* eslint-disable new-cap */
767
+ let plugin = new p()
768
+ for (let i in this.priority) {
769
+ if (this.priority[i].key == key) {
770
+ this.priority[i].class = p
771
+ this.priority[i].priority = plugin.priority
772
+ }
773
+ }
774
+
775
+ if (plugin.handler) {
776
+ lodash.forEach(plugin.handler, ({ fn, key, priority }) => {
777
+ Handler.add({
778
+ ns: plugin.namespace || File.name,
779
+ key: key,
780
+ self: plugin,
781
+ property: priority || plugin.priority || 500,
782
+ fn: plugin[fn]
783
+ })
784
+ })
785
+ }
786
+ })
787
+
788
+ this.priority = lodash.orderBy(this.priority, ["priority"], ["asc"])
789
+ })
790
+
791
+ /** 监听删除 */
792
+ watcher.on("unlink", async path => {
793
+ logger.mark(`[卸载插件][${dirName}][${appName}]`)
794
+ for (let i in this.priority) {
795
+ if (this.priority[i].key == key) {
796
+ this.priority.splice(i, 1)
797
+ /** 停止更新监听 */
798
+ this.watcher[`${dirName}.${appName}`].removeAllListeners("change")
799
+ break
800
+ }
801
+ }
802
+ })
803
+
804
+ this.watcher[`${dirName}.${appName}`] = watcher
805
+ }
806
+
807
+ /** 监听文件夹更新 */
808
+ watchDir(dirName) {
809
+ if (this.watcher[dirName]) return
810
+
811
+ let file = `./plugins/${dirName}/`
812
+ const watcher = chokidar.watch(file)
813
+
814
+ /** 热更新 */
815
+ setTimeout(() => {
816
+ /** 新增文件 */
817
+ watcher.on("add", async PluPath => {
818
+ let appName = path.basename(PluPath)
819
+ if (!appName.endsWith(".js")) return
820
+ if (!fs.existsSync(`${this.dir}/${dirName}/${appName}`)) return
821
+
822
+ let key = `${dirName}/${appName}`
823
+
824
+ this.watch(dirName, appName)
825
+
826
+ /** 太快了延迟下 */
827
+ await common.sleep(500)
828
+
829
+ logger.mark(`[新增插件][${dirName}][${appName}]`)
830
+ let tmp = {}
831
+ try {
832
+ tmp = await import(`../../plugins/${dirName}/${appName}?${moment().format("X")}`)
833
+ } catch (error) {
834
+ logger.error(`载入插件错误:${logger.red(dirName + "/" + appName)}`)
835
+ logger.error(decodeURI(error.stack))
836
+ return
837
+ }
838
+
839
+ if (tmp.apps) tmp = { ...tmp.apps }
840
+
841
+ lodash.forEach(tmp, (p) => {
842
+ if (!p.prototype) {
843
+ logger.error(`[载入失败][${dirName}][${appName}] 格式错误已跳过`)
844
+ return
845
+ }
846
+ /* eslint-disable new-cap */
847
+ let plugin = new p()
848
+
849
+ for (let i in this.priority) {
850
+ if (this.priority[i].key == key) {
851
+ return
852
+ }
853
+ }
854
+
855
+ this.priority.push({
856
+ class: p,
857
+ key,
858
+ name: plugin.name,
859
+ priority: plugin.priority
860
+ })
861
+ })
862
+
863
+ /** 优先级排序 */
864
+ this.priority = lodash.orderBy(this.priority, ["priority"], ["asc"])
865
+ })
866
+ }, 500)
867
+
868
+ this.watcher[dirName] = watcher
869
+ }
870
+ }
871
+
872
+ export default new PluginsLoader()
Yunzai/lib/plugins/plugin.js ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ let stateArr = {}
2
+
3
+ export default class plugin {
4
+ /**
5
+ * @param name 插件名称
6
+ * @param dsc 插件描述
7
+ * @param handler handler配置
8
+ * @param handler.key handler支持的事件key
9
+ * @param handler.fn handler的处理func
10
+ * @param namespace namespace,设置handler时建议设置
11
+ * @param event 执行事件,默认message
12
+ * @param priority 优先级,数字越小优先级越高
13
+ * @param rule
14
+ * @param rule.reg 命令正则
15
+ * @param rule.fnc 命令执行方法
16
+ * @param rule.event 执行事件,默认message
17
+ * @param rule.log false时不显示执行日志
18
+ * @param rule.permission 权限 master,owner,admin,all
19
+ * @param task
20
+ * @param task.name 定时任务名称
21
+ * @param task.cron 定时任务cron表达式
22
+ * @param task.fnc 定时任务方法名
23
+ * @param task.log false时不显示执行日志
24
+ */
25
+ constructor ({
26
+ name = 'your-plugin',
27
+ dsc = '无',
28
+ handler,
29
+ namespace,
30
+ event = 'message',
31
+ priority = 5000,
32
+ task = { fnc: '', cron: '' },
33
+ rule = []
34
+ }) {
35
+ /** 插件名称 */
36
+ this.name = name
37
+ /** 插件描述 */
38
+ this.dsc = dsc
39
+ /** 监听事件,默认message https://oicqjs.github.io/oicq/#events */
40
+ this.event = event
41
+ /** 优先级 */
42
+ this.priority = priority
43
+ /** 定时任务,可以是数组 */
44
+ this.task = {
45
+ /** 任务名 */
46
+ name: '',
47
+ /** 任务方法名 */
48
+ fnc: task.fnc || '',
49
+ /** 任务cron表达式 */
50
+ cron: task.cron || ''
51
+ }
52
+ /** 命令规则 */
53
+ this.rule = rule
54
+
55
+ if (handler) {
56
+ this.handler = handler
57
+ this.namespace = namespace || ''
58
+ }
59
+ }
60
+
61
+ /**
62
+ * @param msg 发送的消息
63
+ * @param quote 是否引用回复
64
+ * @param data.recallMsg 群聊是否撤回消息,0-120秒,0不撤回
65
+ * @param data.at 是否at用户
66
+ */
67
+ reply (msg = '', quote = false, data = {}) {
68
+ if (!this.e.reply || !msg) return false
69
+ return this.e.reply(msg, quote, data)
70
+ }
71
+
72
+ conKey (isGroup = false) {
73
+ if (isGroup) {
74
+ return `${this.name}.${this.e.group_id}`
75
+ } else {
76
+ return `${this.name}.${this.userId || this.e.user_id}`
77
+ }
78
+ }
79
+
80
+ /**
81
+ * @param type 执行方法
82
+ * @param isGroup 是否群聊
83
+ * @param time 操作时间,默认120秒
84
+ */
85
+ setContext (type, isGroup = false, time = 120) {
86
+ let key = this.conKey(isGroup)
87
+ if (!stateArr[key]) stateArr[key] = {}
88
+ stateArr[key][type] = this.e
89
+ if (time) {
90
+ /** 操作时间 */
91
+ setTimeout(() => {
92
+ if (stateArr[key][type]) {
93
+ delete stateArr[key][type]
94
+ this.e.reply('操作超时已取消', true)
95
+ }
96
+ }, time * 1000)
97
+ }
98
+ }
99
+
100
+ getContext () {
101
+ let key = this.conKey()
102
+ return stateArr[key]
103
+ }
104
+
105
+ getContextGroup () {
106
+ let key = this.conKey(true)
107
+ return stateArr[key]
108
+ }
109
+
110
+ /**
111
+ * @param type 执行方法
112
+ * @param isGroup 是否群聊
113
+ */
114
+ finish (type, isGroup = false) {
115
+ if (stateArr[this.conKey(isGroup)] && stateArr[this.conKey(isGroup)][type]) {
116
+ delete stateArr[this.conKey(isGroup)][type]
117
+ }
118
+ }
119
+ }
Yunzai/lib/plugins/runtime.js ADDED
@@ -0,0 +1,245 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * plugin的runtime,可通过e.runtime访问
3
+ *
4
+ * 提供一些常用的运行时变量、方法及model获取
5
+ * 降低对目录结构的依赖
6
+ */
7
+ import lodash from 'lodash'
8
+ import fs from 'node:fs'
9
+ import gsCfg from '../../plugins/genshin/model/gsCfg.js'
10
+ import common from '../common/common.js'
11
+ import cfg from '../config/config.js'
12
+ import MysApi from '../../plugins/genshin/model/mys/mysApi.js'
13
+ import MysInfo from '../../plugins/genshin/model/mys/mysInfo.js'
14
+ import puppeteer from '../puppeteer/puppeteer.js'
15
+ import { Version } from '#miao'
16
+ import NoteUser from '../../plugins/genshin/model/mys/NoteUser.js'
17
+ import MysUser from '../../plugins/genshin/model/mys/MysUser.js'
18
+ import Handler from './handler.js'
19
+
20
+ /**
21
+ * 常用的处理方法
22
+ */
23
+
24
+ export default class Runtime {
25
+ constructor (e) {
26
+ this.e = e
27
+ this._mysInfo = {}
28
+
29
+ this.handler = {
30
+ has: Handler.has,
31
+ call: Handler.call,
32
+ callAll: Handler.callAll
33
+ }
34
+ }
35
+
36
+ get uid () {
37
+ return this.user?.uid
38
+ }
39
+
40
+ get hasCk () {
41
+ return this.user?.hasCk
42
+ }
43
+
44
+ get user () {
45
+ return this.e.user
46
+ }
47
+
48
+ get cfg () {
49
+ return cfg
50
+ }
51
+
52
+ get gsCfg () {
53
+ return gsCfg
54
+ }
55
+
56
+ get common () {
57
+ return common
58
+ }
59
+
60
+ get puppeteer () {
61
+ return puppeteer
62
+ }
63
+
64
+ get MysInfo () {
65
+ return MysInfo
66
+ }
67
+
68
+ get NoteUser () {
69
+ return NoteUser
70
+ }
71
+
72
+ get MysUser () {
73
+ return MysUser
74
+ }
75
+
76
+ static async init (e) {
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
+ }
84
+
85
+ async initUser () {
86
+ let e = this.e
87
+ let user = await NoteUser.create(e)
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',
95
+ mysUser: 'getMysUser',
96
+ ckUidList: 'getCkUidList'
97
+ }
98
+ if (fnMap[key]) {
99
+ return self[fnMap[key]](game)
100
+ }
101
+ if (key === 'uidData') {
102
+ return self.getUidData('', game)
103
+ }
104
+ if (['getUid', 'getUidList', 'getMysUser', 'getCkUidList', 'getUidMapList', 'getGameDs'].includes(key)) {
105
+ return (_game, arg2) => {
106
+ return self[key](_game || game, arg2)
107
+ }
108
+ }
109
+ if (['getUidData', 'hasUid', 'addRegUid', 'delRegUid', 'setMainUid'].includes(key)) {
110
+ return (uid, _game = '') => {
111
+ return self[key](uid, _game || game)
112
+ }
113
+ }
114
+ return self[key]
115
+ }
116
+ })
117
+ }
118
+ }
119
+
120
+ /**
121
+ * 获取MysInfo实例
122
+ *
123
+ * @param targetType all: 所有用户均可, cookie:查询用户必须具备Cookie
124
+ * @returns {Promise<boolean|MysInfo>}
125
+ */
126
+ async getMysInfo (targetType = 'all') {
127
+ if (!this._mysInfo[targetType]) {
128
+ this._mysInfo[targetType] = await MysInfo.init(this.e, targetType === 'cookie' ? 'detail' : 'roleIndex')
129
+ }
130
+ return this._mysInfo[targetType]
131
+ }
132
+
133
+ async getUid () {
134
+ return await MysInfo.getUid(this.e)
135
+ }
136
+
137
+ /**
138
+ * 获取MysApi实例
139
+ *
140
+ * @param targetType all: 所有用户均可, cookie:查询用户必须具备Cookie
141
+ * @param option MysApi option
142
+ * @returns {Promise<boolean|MysApi>}
143
+ */
144
+ async getMysApi (targetType = 'all', option = {}) {
145
+ let mys = await this.getMysInfo(targetType)
146
+ if (mys.uid && mys?.ckInfo?.ck) {
147
+ return new MysApi(mys.uid, mys.ckInfo.ck, option)
148
+ }
149
+ return false
150
+ }
151
+
152
+ /**
153
+ * 生成MysApi实例
154
+ * @param uid
155
+ * @param ck
156
+ * @param option
157
+ * @returns {Promise<MysApi>}
158
+ */
159
+ async createMysApi (uid, ck, option) {
160
+ return new MysApi(uid, ck, option)
161
+ }
162
+
163
+ /**
164
+ *
165
+ * @param plugin plugin key
166
+ * @param path html文件路径,相对于plugin resources目录
167
+ * @param data 渲染数据
168
+ * @param cfg 渲染配置
169
+ * @param cfg.retType 返回值类型
170
+ * * default/空:自动发送图片,返回true
171
+ * * msgId:自动发送图片,返回msg id
172
+ * * base64: 不自动发送图像,返回图像base64数据
173
+ * @param cfg.beforeRender({data}) 可改写渲染的data数据
174
+ * @returns {Promise<boolean>}
175
+ */
176
+ async render (plugin, path, data = {}, cfg = {}) {
177
+ // 处理传入的path
178
+ path = path.replace(/.html$/, '')
179
+ let paths = lodash.filter(path.split('/'), (p) => !!p)
180
+ path = paths.join('/')
181
+ // 创建目录
182
+ const mkdir = (check) => {
183
+ let currDir = `${process.cwd()}/temp`
184
+ for (let p of check.split('/')) {
185
+ currDir = `${currDir}/${p}`
186
+ if (!fs.existsSync(currDir)) {
187
+ fs.mkdirSync(currDir)
188
+ }
189
+ }
190
+ return currDir
191
+ }
192
+ mkdir(`html/${plugin}/${path}`)
193
+ // 自动计算pluResPath
194
+ let pluResPath = `../../../${lodash.repeat('../', paths.length)}plugins/${plugin}/resources/`
195
+ let miaoResPath = `../../../${lodash.repeat('../', paths.length)}plugins/miao-plugin/resources/`
196
+ const layoutPath = process.cwd() + '/plugins/miao-plugin/resources/common/layout/'
197
+ // 渲染data
198
+ data = {
199
+ sys: {
200
+ scale: 1
201
+ },
202
+ /** miao 相关参数 **/
203
+ copyright: `Created By TRSS-Yunzai<span class="version">${Version.yunzai}</span> `,
204
+ _res_path: pluResPath,
205
+ _miao_path: miaoResPath,
206
+ _tpl_path: process.cwd() + '/plugins/miao-plugin/resources/common/tpl/',
207
+ defaultLayout: layoutPath + 'default.html',
208
+ elemLayout: layoutPath + 'elem.html',
209
+
210
+ ...data,
211
+
212
+ /** 默认参数 **/
213
+ _plugin: plugin,
214
+ _htmlPath: path,
215
+ pluResPath,
216
+ tplFile: `./plugins/${plugin}/resources/${path}.html`,
217
+ saveId: data.saveId || data.save_id || paths[paths.length - 1],
218
+ pageGotoParams: {
219
+ waitUntil: 'networkidle2'
220
+ }
221
+ }
222
+ // 处理beforeRender
223
+ if (cfg.beforeRender) {
224
+ data = cfg.beforeRender({ data }) || data
225
+ }
226
+ // 保存模板数据
227
+ if (process.argv.includes('dev')) {
228
+ // debug下保存当前页面的渲染数据,方便模板编写与调试
229
+ // 由于只用于调试,开发者只关注自己当时开发的文件即可,暂不考虑app及plugin的命名冲突
230
+ let saveDir = mkdir(`ViewData/${plugin}`)
231
+ let file = `${saveDir}/${data._htmlPath.split('/').join('_')}.json`
232
+ fs.writeFileSync(file, JSON.stringify(data))
233
+ }
234
+ // 截图
235
+ let base64 = await puppeteer.screenshot(`${plugin}/${path}`, data)
236
+ if (cfg.retType === 'base64') {
237
+ return base64
238
+ }
239
+ let ret = true
240
+ if (base64) {
241
+ ret = await this.e.reply(base64)
242
+ }
243
+ return cfg.retType === 'msgId' ? ret : true
244
+ }
245
+ }
Yunzai/lib/plugins/stdin.js ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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() {
9
+ this.id = "stdin"
10
+ this.name = "标准输入"
11
+ this.path = "data/stdin/"
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
71
+ case "at":
72
+ break
73
+ case "node":
74
+ Bot.sendForwardMsg(msg => this.sendMsg(msg), i.data)
75
+ break
76
+ default:
77
+ i = JSON.stringify(i)
78
+ if (i.match("\n"))
79
+ i = `\n${i}`
80
+ logger.info(`${logger.blue(`[${this.id}]`)} 发送消息:${i}`)
81
+ }
82
+ }
83
+ return { message_id: Date.now() }
84
+ }
85
+
86
+ recallMsg(message_id) {
87
+ logger.info(`${logger.blue(`[${this.id}]`)} 撤回消息:${message_id}`)
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
95
+ }
96
+
97
+ const files = `${this.path}${Date.now()}-${name}`
98
+ logger.info(`${logger.blue(`[${this.id}]`)} 发送文件:${file}\n文件已保存到:${logger.cyan(files)}`)
99
+ return fs.writeFileSync(files, buffer)
100
+ }
101
+
102
+ pickFriend() {
103
+ return {
104
+ user_id: this.id,
105
+ nickname: this.name,
106
+ group_id: this.id,
107
+ group_name: this.name,
108
+ sendMsg: msg => this.sendMsg(msg),
109
+ recallMsg: message_id => this.recallMsg(message_id),
110
+ sendFile: (file, name) => this.sendFile(file, name),
111
+ }
112
+ }
113
+
114
+ message(msg) {
115
+ const data = {
116
+ bot: Bot[this.id],
117
+ self_id: this.id,
118
+ user_id: this.id,
119
+ post_type: "message",
120
+ message_type: "private",
121
+ sender: { user_id: this.id, nickname: this.name },
122
+ message: [{ type: "text", text: msg }],
123
+ raw_message: msg,
124
+ friend: this.pickFriend(),
125
+ }
126
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 系统消息:[${data.sender.nickname}(${data.user_id})] ${data.raw_message}`)
127
+
128
+ Bot.em(`${data.post_type}.${data.message_type}`, data)
129
+ }
130
+
131
+ load() {
132
+ Bot[this.id] = {
133
+ adapter: this,
134
+ uin: this.id,
135
+ nickname: this.name,
136
+ stat: { start_time: Date.now()/1000 },
137
+ version: { id: this.id, name: this.name },
138
+ pickFriend: () => this.pickFriend(),
139
+ get pickUser() { return this.pickFriend },
140
+ get pickMember() { return this.pickFriend },
141
+ get pickGroup() { return this.pickFriend },
142
+
143
+ fl: new Map().set(this.id, {
144
+ user_id: this.id,
145
+ nickname: this.name,
146
+ group_id: this.id,
147
+ group_name: this.name,
148
+ }),
149
+ get gl() { return this.fl },
150
+ gml: new Map,
151
+ }
152
+ Bot[this.id].gml.set(this.id, Bot[this.id].fl)
153
+
154
+ process[this.id].on("data", data => this.message(data.toString()))
155
+
156
+ logger.mark(`${logger.blue(`[${this.id}]`)} ${this.name}(${this.id}) 已连接`)
157
+ Bot.em(`connect.${this.id}`, { self_id: this.id })
158
+ }
159
+ })
Yunzai/lib/puppeteer/puppeteer.js ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
Yunzai/lib/renderer/Renderer.js ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import template from 'art-template'
2
+ import chokidar from 'chokidar'
3
+ import path from 'node:path'
4
+ import fs from 'node:fs'
5
+
6
+ export default class Renderer {
7
+ /**
8
+ * 渲染器
9
+ * @param data.id 渲染器ID
10
+ * @param data.type 渲染器类型
11
+ * @param data.render 渲染器入口
12
+ */
13
+ constructor(data) {
14
+ /** 渲染器ID */
15
+ this.id = data.id || 'renderer'
16
+ /** 渲染器类型 */
17
+ this.type = data.type || 'image'
18
+ /** 渲染器入口 */
19
+ this.render = this[data.render || 'render']
20
+ this.dir = './temp/html'
21
+ this.html = {}
22
+ this.watcher = {}
23
+ this.createDir(this.dir)
24
+ }
25
+
26
+ /** 创建文件夹 */
27
+ createDir(dirname) {
28
+ if (fs.existsSync(dirname)) {
29
+ return true
30
+ } else {
31
+ if (this.createDir(path.dirname(dirname))) {
32
+ fs.mkdirSync(dirname)
33
+ return true
34
+ }
35
+ }
36
+ }
37
+
38
+ /** 模板 */
39
+ dealTpl(name, data) {
40
+ let { tplFile, saveId = name } = data
41
+ let savePath = `./temp/html/${name}/${saveId}.html`
42
+
43
+ /** 读取html模板 */
44
+ if (!this.html[tplFile]) {
45
+ this.createDir(`./temp/html/${name}`)
46
+
47
+ try {
48
+ this.html[tplFile] = fs.readFileSync(tplFile, 'utf8')
49
+ } catch (error) {
50
+ logger.error(`加载html错误:${tplFile}`)
51
+ return false
52
+ }
53
+
54
+ this.watch(tplFile)
55
+ }
56
+
57
+ data.resPath = `./resources/`
58
+
59
+ /** 替换模板 */
60
+ let tmpHtml = template.render(this.html[tplFile], data)
61
+
62
+ /** 保存模板 */
63
+ fs.writeFileSync(savePath, tmpHtml)
64
+
65
+ logger.debug(`[图片生成][使用模板] ${savePath}`)
66
+
67
+ return savePath
68
+ }
69
+
70
+ /** 监听配置文件 */
71
+ watch(tplFile) {
72
+ if (this.watcher[tplFile]) return
73
+
74
+ const watcher = chokidar.watch(tplFile)
75
+ watcher.on('change', path => {
76
+ delete this.html[tplFile]
77
+ logger.mark(`[修改html模板] ${tplFile}`)
78
+ })
79
+
80
+ this.watcher[tplFile] = watcher
81
+ }
82
+ }
Yunzai/lib/renderer/loader.js ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fs from 'node:fs'
2
+ import yaml from 'yaml'
3
+ import lodash from 'lodash'
4
+ import cfg from '../config/config.js'
5
+ import { Data } from '#miao'
6
+ import Renderer from './Renderer.js'
7
+
8
+ /** 全局变量 Renderer */
9
+ global.Renderer = Renderer
10
+
11
+ /**
12
+ * 加载渲染器
13
+ */
14
+ class RendererLoader {
15
+ constructor() {
16
+ this.renderers = new Map()
17
+ this.dir = './renderers'
18
+ // TODO 渲染器热加载
19
+ this.watcher = {}
20
+ }
21
+
22
+ static async init() {
23
+ const render = new RendererLoader()
24
+ await render.load()
25
+ return render
26
+ }
27
+
28
+ async load() {
29
+ const subFolders = fs.readdirSync(this.dir, { withFileTypes: true }).filter((dirent) => dirent.isDirectory())
30
+ for (let subFolder of subFolders) {
31
+ let name = subFolder.name
32
+ try {
33
+ const rendererFn = await Data.importDefault(`${this.dir}/${name}/index.js`)
34
+ let configFile = `${this.dir}/${name}/config.yaml`
35
+ let rendererCfg = fs.existsSync(configFile) ? yaml.parse(fs.readFileSync(configFile, 'utf8')) : {}
36
+ let renderer = rendererFn(rendererCfg)
37
+ if (!renderer.id || !renderer.type || !renderer.render || !lodash.isFunction(renderer.render)) {
38
+ logger.warn('渲染后端 ' + (renderer.id || subFolder.name) + ' 不可用')
39
+ }
40
+ this.renderers.set(renderer.id, renderer)
41
+ logger.info(`加载渲染后端 ${renderer.id}`)
42
+ } catch (err) {
43
+ logger.error(`渲染后端 ${name} 加载失败`)
44
+ logger.error(err)
45
+ }
46
+ }
47
+ }
48
+
49
+ getRenderer(name = cfg.renderer?.name || 'puppeteer') {
50
+ // TODO 渲染器降级
51
+ return this.renderers.get(name)
52
+ }
53
+ }
54
+
55
+
56
+ export default await RendererLoader.init()
Yunzai/lib/tools/command.js ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import '../config/init.js'
3
+ import log4js from 'log4js'
4
+ import PluginsLoader from '../plugins/loader.js'
5
+ import cfg from '../config/config.js'
6
+
7
+ class Command {
8
+ constructor () {
9
+ this.command = ''
10
+ // this.setLog()
11
+ /** 全局Bot */
12
+ global.Bot = {}
13
+ }
14
+
15
+ /**
16
+ * @param type 命令配置类型,默认default
17
+ */
18
+ async run (type = 'default') {
19
+ /** 加载icqq事件监听 */
20
+ await PluginsLoader.load()
21
+ /** 获取命令行参数 */
22
+ this.getCommand()
23
+ /** 伪造消息 */
24
+ let e = this.fakeE(type)
25
+
26
+ /** 插件处理消息 */
27
+ await PluginsLoader.deal(e)
28
+ }
29
+
30
+ /** 设置命令 */
31
+ getCommand () {
32
+ if (process.argv[2]) {
33
+ this.command = '#' + process.argv[2].replace(/#|#|井/g, '#').trim()
34
+ }
35
+ }
36
+
37
+ fakeE (id = 'default') {
38
+ /** 获取配置 */
39
+ let data = cfg.getYaml('test', id)
40
+ let text = this.command || data.text || ''
41
+ logger.info(`测试命令 [${text}]`)
42
+ let e = {
43
+ test: true,
44
+ self_id: 10000,
45
+ time: new Date().getTime(),
46
+ post_type: data.post_type || 'message',
47
+ message_type: data.message_type || 'group',
48
+ sub_type: data.sub_type || 'normal',
49
+ group_id: data.group_id || 826198224,
50
+ group_name: data.group_name || '测试群',
51
+ user_id: data.user_id,
52
+ anonymous: null,
53
+ message: [{ type: 'text', text }],
54
+ raw_message: text,
55
+ font: '微软雅黑',
56
+ sender: {
57
+ user_id: data.user_id,
58
+ nickname: '测试',
59
+ card: data.card,
60
+ sex: 'male',
61
+ age: 0,
62
+ area: 'unknown',
63
+ level: 2,
64
+ role: 'owner',
65
+ title: ''
66
+ },
67
+ group: {
68
+ mute_left: 0,
69
+ sendMsg: (msg) => {
70
+ logger.info(`回复内容 ${msg}`)
71
+ }
72
+ },
73
+ friend: {
74
+ getFileUrl: (fid) => {
75
+ return data.message[0].url
76
+ }
77
+ },
78
+ message_id: 'JzHU0DACliIAAAD3RzTh1WBOIC48',
79
+ reply: async (msg) => {
80
+ logger.info(`回复内容 ${msg}`)
81
+ },
82
+ toString: () => {
83
+ return text
84
+ }
85
+ }
86
+
87
+ if (data.message) {
88
+ e.message = data.message
89
+ }
90
+
91
+ return e
92
+ }
93
+
94
+ /** 日志 */
95
+ setLog () {
96
+ log4js.configure({
97
+ appenders: {
98
+ // 设置控制台输出 (默认日志级别是关闭的(即不会输出日志))
99
+ out: {
100
+ type: 'console',
101
+ layout: {
102
+ type: 'pattern',
103
+ pattern: '[%d{hh:mm:ss.SSS}][%[%5.5p%]] - %m'
104
+ }
105
+ }
106
+ },
107
+ // 不同等级的日志追加到不同的输出位置:appenders: ['out', 'allLog'] categories 作为getLogger方法的键名对应
108
+ categories: {
109
+ // appenders:采用的appender,取上面appenders项,level:设置级别
110
+ default: { appenders: ['out'], level: 'debug' }
111
+ }
112
+ })
113
+ global.logger = log4js.getLogger('[test]')
114
+ logger.level = 'debug'
115
+ }
116
+ }
117
+
118
+ export default new Command()
Yunzai/lib/tools/name.js ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ console.log(config.apps[0].name)
17
+ runPm2Logs(appName)
18
+ } else {
19
+ console.log('读取失败:无法在pm2.json中找到name数组')
20
+ }
21
+ } catch (parseError) {
22
+ console.log('读取失败:json文件解析发生了错误', parseError)
23
+ }
24
+ })
25
+
26
+ function runPm2Logs(appName) {
27
+ const command = process.platform === 'win32' ? 'pm2.cmd' : 'pm2'
28
+ const args = ['logs', '--lines', '400', appName]
29
+ const pm2LogsProcess = childProcess.spawn(command, args, { stdio: 'inherit' })
30
+ pm2LogsProcess.on('exit', (code) => {
31
+ if (code !== 0) {
32
+ console.error(`pm2 logs process exited with code ${code}`)
33
+ }
34
+ })
35
+ }
Yunzai/lib/tools/test.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import command from './command.js'
2
+
3
+ /**
4
+ * npm test 十连
5
+ * 配置数据config/test/defult.yaml
6
+ */
7
+ await command.run()
8
+ // await command.run('bingCk')
9
+ // await command.run('gachaLog')
10
+ // await command.run('xlsx')
11
+ process.exit()
Yunzai/lib/tools/web.js ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import express from 'express'
2
+ import template from 'express-art-template'
3
+ import fs from 'fs'
4
+ import lodash from 'lodash'
5
+
6
+ /*
7
+ * npm run app web-debug开启Bot后
8
+ * 可另外通过 npm run web 开启浏览器调试
9
+ * 访问 http://localhost:8000/ 即可看到对应页面
10
+ * 页面内的资源需使用 {{_res_path}}来作为resources目录的根目录
11
+ * 可编辑模板与页面查看效果
12
+ * todo: 预览页面的热更
13
+ *
14
+ * */
15
+
16
+ let app = express()
17
+
18
+ let _path = process.cwd()
19
+
20
+ app.engine('html', template)
21
+ app.set('views', _path + '/resources/')
22
+ app.set('view engine', 'art')
23
+ app.use(express.static(_path + '/resources'))
24
+ app.use('/plugins', express.static('plugins'))
25
+
26
+ app.get('/', function (req, res) {
27
+ let pluginList = fs.readdirSync(_path + '/temp/ViewData/') || []
28
+ let html = [
29
+ '在npm run web-dev模式下触发截图消息后,可在下方选择页面进行调试',
30
+ '如果页面内资源路径不正确请使用{{_res_path}}作为根路径,对应之前的../../../../',
31
+ '可直接修改模板html或css刷新查看效果'
32
+ ]
33
+ let li = {}
34
+ for (let pIdx in pluginList) {
35
+ const plugin = pluginList[pIdx]
36
+ let fileList = fs.readdirSync(_path + `/temp/ViewData/${plugin}/`) || []
37
+ for (let idx in fileList) {
38
+ let ret = /(.+)\.json$/.exec(fileList[idx])
39
+ if (ret && ret[1]) {
40
+ let text = [plugin, ...ret[1].split('_')]
41
+ li[text.join('')] = (`<li style="font-size:18px; line-height:30px;"><a href="/${plugin}_${ret[1]}">${text.join(' / ')}</a></li>`)
42
+ }
43
+ }
44
+ }
45
+ res.send(html.join('</br>') + '<ul>' + lodash.values(li).join('') + '</ul>')
46
+ })
47
+
48
+ app.get('/:page', function (req, res) {
49
+ let [plugin, app, ...page] = req.params.page.split('_')
50
+ page = page.join('_')
51
+ if (plugin == 'favicon.ico') {
52
+ return res.send('')
53
+ }
54
+ let data = JSON.parse(fs.readFileSync(_path + `/temp/ViewData/${plugin}/${app}_${page}.json`, 'utf8'))
55
+ data = data || {}
56
+ data._res_path = ''
57
+ data._sys_res_path = data._res_path
58
+
59
+ if (data._plugin) {
60
+ data._res_path = `/plugins/${data._plugin}/resources/`
61
+ data.pluResPath = data._res_path
62
+ }
63
+ let htmlPath = ''
64
+ let tplPath = `${app}/${htmlPath}${page}/${page}.html`
65
+ if (data._plugin) {
66
+ tplPath = `../plugins/${data._plugin}/resources/${htmlPath}/${app}/${page.split('_').join('/')}.html`
67
+ } else if (data._no_type_path) {
68
+ tplPath = `${app}/${page}.html`
69
+ }
70
+ res.render(tplPath, data)
71
+ })
72
+
73
+ app.listen(8000)
74
+ console.log('页面服务已启动,触发消息图片后访问 http://localhost:8000/ 调试页面')
Yunzai/package.json ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "trss-yunzai",
3
+ "version": "3.1.0",
4
+ "author": "TimeRainStarSky, Yoimiya-Kokomi, Le-niao",
5
+ "description": "Bot",
6
+ "main": "app.js",
7
+ "type": "module",
8
+ "scripts": {
9
+ "app": "node .",
10
+ "dev": "node . dev",
11
+ "web": "node ./lib/tools/web.js",
12
+ "test": "node ./lib/tools/test.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/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": {
50
+ "#miao": "./plugins/miao-plugin/components/index.js",
51
+ "#miao.models": "./plugins/miao-plugin/models/index.js"
52
+ }
53
+ }
Yunzai/plugins/.gitignore ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *
2
+ !.gitignore
3
+ !adapter
4
+ !adapter/**
5
+ !system
6
+ !system/**
7
+ !other
8
+ !other/**
9
+ !example/一言.js
10
+ !example/主动复读.js
11
+ !example/进群退群通知.js
Yunzai/plugins/adapter/ComWeChat.js ADDED
@@ -0,0 +1,501 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { randomUUID } from "crypto"
2
+ import path from "node:path"
3
+ import fs from "node:fs"
4
+ import { fileTypeFromBuffer } from "file-type"
5
+
6
+ Bot.adapter.push(new class ComWeChatAdapter {
7
+ constructor() {
8
+ this.id = "WeChat"
9
+ this.name = "ComWeChat"
10
+ this.path = this.name
11
+ }
12
+
13
+ toStr(data) {
14
+ switch (typeof data) {
15
+ case "string":
16
+ return data
17
+ case "number":
18
+ return String(data)
19
+ case "object":
20
+ if (Buffer.isBuffer(data))
21
+ return Buffer.from(data, "utf8").toString()
22
+ else
23
+ return JSON.stringify(data)
24
+ }
25
+ return data
26
+ }
27
+
28
+ makeLog(msg) {
29
+ return this.toStr(msg).replace(/(base64:\/\/|"type":"data","data":").*?"/g, '$1..."')
30
+ }
31
+
32
+ sendApi(ws, action, params = {}) {
33
+ const echo = randomUUID()
34
+ const msg = { action, params, echo }
35
+ ws.sendMsg(msg)
36
+ return new Promise(resolve =>
37
+ Bot.once(echo, data =>
38
+ resolve({ ...data, ...data.data })))
39
+ }
40
+
41
+ async fileName(file) {
42
+ try {
43
+ if (file.match(/^base64:\/\//)) {
44
+ const buffer = Buffer.from(file.replace(/^base64:\/\//, ""), "base64")
45
+ const type = await fileTypeFromBuffer(buffer)
46
+ return `${Date.now()}.${type.ext}`
47
+ } else {
48
+ return path.basename(file)
49
+ }
50
+ } catch (err) {
51
+ logger.error(`文件类型检测错误:${logger.red(err)}`)
52
+ }
53
+ return false
54
+ }
55
+
56
+ async uploadFile(data, file, name) {
57
+ const opts = { name: name || await this.fileName(file) || randomUUID() }
58
+
59
+ if (file.match(/^https?:\/\//)) {
60
+ opts.type = "url"
61
+ opts.url = file
62
+ } else if (file.match(/^base64:\/\//)) {
63
+ opts.type = "data"
64
+ opts.data = file.replace(/^base64:\/\//, "")
65
+ } else if (fs.existsSync(file)) {
66
+ opts.type = "data"
67
+ opts.data = fs.readFileSync(file).toString("base64")
68
+ } else {
69
+ opts.type = "path"
70
+ opts.path = file
71
+ }
72
+
73
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 上传文件:${this.makeLog(opts)}`)
74
+ return data.bot.sendApi("upload_file", opts)
75
+ }
76
+
77
+ async makeMsg(data, msg) {
78
+ if (!Array.isArray(msg))
79
+ msg = [msg]
80
+ const msgs = []
81
+ for (let i of msg) {
82
+ if (typeof i != "object")
83
+ i = { type: "text", data: { text: i }}
84
+ else if (!i.data)
85
+ i = { type: i.type, data: { ...i, type: undefined }}
86
+ if (i.data.file)
87
+ i.data = { file_id: (await this.uploadFile(data, i.data.file, i.data.name)).file_id }
88
+
89
+ switch (i.type) {
90
+ case "text":
91
+ case "image":
92
+ case "file":
93
+ case "wx.emoji":
94
+ case "wx.link":
95
+ break
96
+ case "record":
97
+ case "video":
98
+ i.type = "file"
99
+ break
100
+ case "at":
101
+ if (i.data.qq == "all")
102
+ i = { type: "mention_all", data: {}}
103
+ else
104
+ i = { type: "mention", data: { user_id: i.data.qq }}
105
+ break
106
+ case "reply":
107
+ continue
108
+ default:
109
+ i = { type: "text", data: { text: JSON.stringify(i) }}
110
+ }
111
+ msgs.push(i)
112
+ }
113
+ return msgs
114
+ }
115
+
116
+ async sendFriendMsg(data, msg) {
117
+ if (msg?.type == "node")
118
+ return Bot.sendForwardMsg(msg => this.sendFriendMsg(data, msg), msg.data)
119
+
120
+ const message = await this.makeMsg(data, msg)
121
+ logger.info(`${logger.blue(`[${data.self_id} => ${data.user_id}]`)} 发送好友消息:${this.makeLog(message)}`)
122
+ return data.bot.sendApi("send_message", {
123
+ detail_type: "private",
124
+ user_id: data.user_id,
125
+ message,
126
+ })
127
+ }
128
+
129
+ async sendGroupMsg(data, msg) {
130
+ if (msg?.type == "node")
131
+ return Bot.sendForwardMsg(msg => this.sendGroupMsg(data, msg), msg.data)
132
+
133
+ const message = await this.makeMsg(data, msg)
134
+ logger.info(`${logger.blue(`[${data.self_id} => ${data.group_id}]`)} 发送群消息:${this.makeLog(message)}`)
135
+ return data.bot.sendApi("send_message", {
136
+ detail_type: "group",
137
+ group_id: data.group_id,
138
+ message,
139
+ })
140
+ }
141
+
142
+ async getFriendArray(data) {
143
+ const array = []
144
+ for (const i of (await data.bot.sendApi("get_friend_list")).data)
145
+ array.push({
146
+ ...i,
147
+ nickname: i.user_remark == "null" ? i.user_displayname || i.user_name : i.user_remark,
148
+ })
149
+ return array
150
+ }
151
+
152
+ async getFriendList(data) {
153
+ const array = []
154
+ for (const { user_id } of (await this.getFriendArray(data)))
155
+ array.push(user_id)
156
+ return array
157
+ }
158
+
159
+ async getFriendMap(data) {
160
+ for (const i of (await this.getFriendArray(data)))
161
+ data.bot.fl.set(i.user_id, i)
162
+ return data.bot.fl
163
+ }
164
+
165
+ getFriendInfo(data) {
166
+ return data.bot.sendApi("get_user_info", {
167
+ user_id: data.user_id,
168
+ })
169
+ }
170
+
171
+ async getGroupArray(data) {
172
+ return (await data.bot.sendApi("get_group_list")).data
173
+ }
174
+
175
+ async getGroupList(data) {
176
+ const array = []
177
+ for (const { group_id } of (await this.getGroupArray(data)))
178
+ array.push(group_id)
179
+ return array
180
+ }
181
+
182
+ async getGroupMap(data) {
183
+ for (const i of (await this.getGroupArray(data)))
184
+ data.bot.gl.set(i.group_id, i)
185
+ return data.bot.gl
186
+ }
187
+
188
+ getGroupInfo(data) {
189
+ return data.bot.sendApi("get_group_info", {
190
+ group_id: data.group_id,
191
+ })
192
+ }
193
+
194
+ async getMemberArray(data) {
195
+ return (await data.bot.sendApi("get_group_member_list", {
196
+ group_id: data.group_id,
197
+ })).data
198
+ }
199
+
200
+ async getMemberList(data) {
201
+ const array = []
202
+ for (const { user_id } of (await this.getMemberArray(data)))
203
+ array.push(user_id)
204
+ return array
205
+ }
206
+
207
+ async getMemberMap(data) {
208
+ const map = new Map
209
+ for (const i of (await this.getMemberArray(data)))
210
+ map.set(i.user_id, i)
211
+ return map
212
+ }
213
+
214
+ getMemberInfo(data) {
215
+ return data.bot.sendApi("get_group_member_info", {
216
+ group_id: data.group_id,
217
+ user_id: data.user_id,
218
+ })
219
+ }
220
+
221
+ pickFriend(data, user_id) {
222
+ const i = {
223
+ ...data.bot.fl.get(user_id),
224
+ ...data,
225
+ user_id,
226
+ }
227
+ return {
228
+ ...i,
229
+ sendMsg: msg => this.sendFriendMsg(i, msg),
230
+ sendFile: (file, name) => this.sendFriendMsg(i, segment.file(file, name)),
231
+ getInfo: () => this.getFriendInfo(i),
232
+ getAvatarUrl: async () => (await this.getFriendInfo(i))["wx.avatar"],
233
+ }
234
+ }
235
+
236
+ pickMember(data, group_id, user_id) {
237
+ const i = {
238
+ ...data.bot.fl.get(user_id),
239
+ ...data,
240
+ group_id,
241
+ user_id,
242
+ }
243
+ return {
244
+ ...this.pickFriend(i, user_id),
245
+ ...i,
246
+ getInfo: () => this.getMemberInfo(i),
247
+ getAvatarUrl: async () => (await this.getMemberInfo(i))["wx.avatar"],
248
+ }
249
+ }
250
+
251
+ pickGroup(data, group_id) {
252
+ const i = {
253
+ ...data.bot.gl.get(group_id),
254
+ ...data,
255
+ group_id,
256
+ }
257
+ return {
258
+ ...i,
259
+ sendMsg: msg => this.sendGroupMsg(i, msg),
260
+ sendFile: (file, name) => this.sendGroupMsg(i, segment.file(file, name)),
261
+ getInfo: () => this.getGroupInfo(i),
262
+ getAvatarUrl: async () => (await this.getGroupInfo(i))["wx.avatar"],
263
+ getMemberArray: () => this.getMemberArray(i),
264
+ getMemberList: () => this.getMemberList(i),
265
+ getMemberMap: () => this.getMemberMap(i),
266
+ pickMember: user_id => this.pickMember(i, i.group_id, user_id),
267
+ }
268
+ }
269
+
270
+ async connect(data, ws) {
271
+ for (const bot of data.status.bots)
272
+ data.self_id = bot.self.user_id
273
+
274
+ Bot[data.self_id] = {
275
+ adapter: this,
276
+ ws: ws,
277
+ sendApi: (action, params) => this.sendApi(ws, action, params),
278
+ stat: { ...data.status, start_time: data.time },
279
+
280
+ info: {},
281
+ get uin() { return this.info.user_id },
282
+ get nickname() { return this.info.user_name },
283
+ get avatar() { return this.info["wx.avatar"] },
284
+
285
+ pickFriend: user_id => this.pickFriend(data, user_id),
286
+ get pickUser() { return this.pickFriend },
287
+ getFriendArray: () => this.getFriendArray(data),
288
+ getFriendList: () => this.getFriendList(data),
289
+ getFriendMap: () => this.getFriendMap(data),
290
+ fl: new Map,
291
+
292
+ pickMember: (group_id, user_id) => this.pickMember(data, group_id, user_id),
293
+ pickGroup: group_id => this.pickGroup(data, group_id),
294
+ getGroupArray: () => this.getGroupArray(data),
295
+ getGroupList: () => this.getGroupList(data),
296
+ getGroupMap: () => this.getGroupMap(data),
297
+ gl: new Map,
298
+ gml: new Map,
299
+ }
300
+ data.bot = Bot[data.self_id]
301
+
302
+ if (!Bot.uin.includes(data.self_id))
303
+ Bot.uin.push(data.self_id)
304
+
305
+ data.bot.info = (await data.bot.sendApi("get_self_info")).data
306
+ data.bot.version = {
307
+ ...(await data.bot.sendApi("get_version")).data,
308
+ id: this.id,
309
+ name: this.name,
310
+ }
311
+
312
+ data.bot.getFriendMap()
313
+ data.bot.getGroupMap()
314
+
315
+ logger.mark(`${logger.blue(`[${data.self_id}]`)} ${this.name}(${this.id}) ${data.bot.version.impl}-${data.bot.version.version} 已连接`)
316
+ Bot.em(`connect.${data.self_id}`, data)
317
+ }
318
+
319
+ makeMessage(data) {
320
+ data.post_type = data.type
321
+ data.message_type = data.detail_type
322
+ data.raw_message = data.alt_message
323
+
324
+ data.sender = {
325
+ ...data.bot.fl.get(data.user_id),
326
+ user_id: data.user_id,
327
+ }
328
+
329
+ const message = []
330
+ for (const i of data.message)
331
+ switch (i.type) {
332
+ case "mention":
333
+ message.push({ type: "at", qq: i.data.user_id })
334
+ break
335
+ case "mention_all":
336
+ message.push({ type: "at", qq: "all" })
337
+ break
338
+ case "voice":
339
+ message.push({ type: "record", ...i.data })
340
+ break
341
+ case "reply":
342
+ message.push({ type: "reply", id: i.data.message_id, user_id: i.data.user_id })
343
+ break
344
+ default:
345
+ message.push({ type: i.type, ...i.data })
346
+ }
347
+ data.message = message
348
+
349
+ switch (data.message_type) {
350
+ case "private":
351
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 好友消息:[${data.user_id}] ${data.raw_message}`)
352
+ break
353
+ case "group":
354
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 群消息:[${data.group_id}, ${data.user_id}] ${data.raw_message}`)
355
+ break
356
+ default:
357
+ logger.warn(`${logger.blue(`[${data.self_id}]`)} 未知消息:${logger.magenta(JSON.stringify(data))}`)
358
+ }
359
+
360
+ Bot.em(`${data.post_type}.${data.message_type}`, data)
361
+ }
362
+
363
+ makeNotice(data) {
364
+ data.post_type = data.type
365
+ if (data.group_id)
366
+ data.notice_type = "group"
367
+ else
368
+ data.notice_type = "friend"
369
+
370
+ switch (data.detail_type) {
371
+ case "private_message_delete":
372
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 好友消息撤回:[${data.user_id}] ${data.message_id}`)
373
+ data.sub_type = "recall"
374
+ break
375
+ case "group_message_delete":
376
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 群消息撤回:[${data.group_id}, ${data.operator_id}=>${data.user_id}] ${data.message_id}`)
377
+ data.sub_type = "recall"
378
+ break
379
+ case "wx.get_private_file":
380
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 私聊文件:[${data.user_id}] ${data.file_name} ${data.file_length} ${data.md5}`)
381
+ break
382
+ case "wx.get_group_file":
383
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 群文件:[${data.group_id}, ${data.user_id}] ${data.file_name} ${data.file_length} ${data.md5}`)
384
+ break
385
+ case "wx.get_private_redbag":
386
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 好友红包:[${data.user_id}]`)
387
+ break
388
+ case "wx.get_group_redbag":
389
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 群红包:[${data.group_id}, ${data.user_id}]`)
390
+ break
391
+ case "wx.get_private_poke":
392
+ data.operator_id = data.from_user_id
393
+ data.target_id = data.user_id
394
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 好友拍一拍:[${data.operator_id}=>${data.target_id}]`)
395
+ break
396
+ case "wx.get_group_poke":
397
+ data.operator_id = data.from_user_id
398
+ data.target_id = data.user_id
399
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 群拍一拍:[${data.group_id}, ${data.operator_id}=>${data.target_id}]`)
400
+ break
401
+ case "wx.get_private_card":
402
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 好友用户名片:[${data.user_id}] ${data.v3} ${data.v4} ${data.nickname} ${data.head_url} ${data.province} ${data.city} ${data.sex}`)
403
+ break
404
+ case "wx.get_group_card":
405
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 群用户名片:[${data.group_id}, ${data.user_id}] ${data.v3} ${data.v4} ${data.nickname} ${data.head_url} ${data.province} ${data.city} ${data.sex}`)
406
+ break
407
+ default:
408
+ logger.warn(`${logger.blue(`[${data.self_id}]`)} 未知通知:${logger.magenta(JSON.stringify(data))}`)
409
+ }
410
+ if (!data.sub_type)
411
+ data.sub_type = data.detail_type.split("_").pop()
412
+
413
+ Bot.em(`${data.post_type}.${data.notice_type}.${data.sub_type}`, data)
414
+ }
415
+
416
+ makeRequest(data) {
417
+ data.post_type = data.type
418
+ if (data.group_id)
419
+ data.notice_type = "group"
420
+ else
421
+ data.notice_type = "friend"
422
+
423
+ switch (data.detail_type) {
424
+ case "wx.friend_request":
425
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 加好友请求:[${data.user_id}] ${data.v3} ${data.v4} ${data.nickname} ${data.content} ${data.province} ${data.city}`)
426
+ data.sub_type = "add"
427
+ break
428
+ default:
429
+ logger.warn(`${logger.blue(`[${data.self_id}]`)} 未知请求:${logger.magenta(JSON.stringify(data))}`)
430
+ }
431
+ if (!data.sub_type)
432
+ data.sub_type = data.detail_type.split("_").pop()
433
+
434
+ Bot.em(`${data.post_type}.${data.request_type}.${data.sub_type}`, data)
435
+ }
436
+
437
+ makeMeta(data, ws) {
438
+ switch (data.detail_type) {
439
+ case "heartbeat":
440
+ break
441
+ case "connect":
442
+ break
443
+ case "status_update":
444
+ this.connect(data, ws)
445
+ break
446
+ default:
447
+ logger.warn(`${logger.blue(`[${data.self_id}]`)} 未知消息:${logger.magenta(JSON.stringify(data))}`)
448
+ }
449
+ }
450
+
451
+ message(data, ws) {
452
+ try {
453
+ data = JSON.parse(data)
454
+ } catch (err) {
455
+ return logger.error(`解码数据失败:${logger.red(err)}`)
456
+ }
457
+
458
+ if (data.self?.user_id) {
459
+ data.self_id = data.self.user_id
460
+ } else {
461
+ data.self_id = data.id
462
+ }
463
+
464
+ if (data.type) {
465
+ if (data.type != "meta" && !Bot.uin.includes(data.self_id)) {
466
+ logger.warn(`${logger.blue(`[${data.self_id}]`)} 找不到对应Bot,忽略消息:${logger.magenta(JSON.stringify(data))}`)
467
+ return false
468
+ }
469
+ data.bot = Bot[data.self_id]
470
+
471
+ switch (data.type) {
472
+ case "meta":
473
+ this.makeMeta(data, ws)
474
+ break
475
+ case "message":
476
+ this.makeMessage(data)
477
+ break
478
+ case "notice":
479
+ this.makeNotice(data)
480
+ break
481
+ case "request":
482
+ this.makeRequest(data)
483
+ break
484
+ default:
485
+ logger.warn(`${logger.blue(`[${data.self_id}]`)} 未知消息:${logger.magenta(JSON.stringify(data))}`)
486
+ }
487
+ } else if (data.echo) {
488
+ Bot.emit(data.echo, data)
489
+ } else {
490
+ logger.warn(`${logger.blue(`[${data.self_id}]`)} 未知消息:${logger.magenta(JSON.stringify(data))}`)
491
+ }
492
+ }
493
+
494
+ load() {
495
+ if (!Array.isArray(Bot.wsf[this.path]))
496
+ Bot.wsf[this.path] = []
497
+ Bot.wsf[this.path].push((ws, ...args) =>
498
+ ws.on("message", data => this.message(data, ws, ...args))
499
+ )
500
+ }
501
+ })
Yunzai/plugins/adapter/GSUIDCore.js ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { randomUUID } from "crypto"
2
+ import path from "node:path"
3
+ import fs from "node:fs"
4
+
5
+ Bot.adapter.push(new class GSUIDCoreAdapter {
6
+ constructor() {
7
+ this.id = "GSUIDCore"
8
+ this.name = "早柚核心"
9
+ this.path = this.id
10
+ }
11
+
12
+ toStr(data) {
13
+ switch (typeof data) {
14
+ case "string":
15
+ return data
16
+ case "number":
17
+ return String(data)
18
+ case "object":
19
+ if (Buffer.isBuffer(data))
20
+ return Buffer.from(data, "utf8").toString()
21
+ else
22
+ return JSON.stringify(data)
23
+ }
24
+ return data
25
+ }
26
+
27
+ makeLog(msg) {
28
+ return this.toStr(msg).replace(/base64:\/\/.*?"/g, "base64://...\"")
29
+ }
30
+
31
+ makeMsg(msg) {
32
+ if (!Array.isArray(msg))
33
+ msg = [msg]
34
+ const msgs = []
35
+ for (let i of msg) {
36
+ if (typeof i != "object")
37
+ i = { type: "text", text: i }
38
+
39
+ switch (i.type) {
40
+ case "text":
41
+ i = { type: "text", data: i.text }
42
+ break
43
+ case "image":
44
+ i = { type: "image", data: i.file }
45
+ break
46
+ case "record":
47
+ i = { type: "file", data: i.file }
48
+ break
49
+ case "video":
50
+ i = { type: "file", data: i.file }
51
+ break
52
+ case "file":
53
+ i = { type: "file", data: i.file }
54
+ break
55
+ case "at":
56
+ i = { type: "at", data: i.qq }
57
+ break
58
+ case "reply":
59
+ i = { type: "reply", data: i.id }
60
+ break
61
+ case "node": {
62
+ const array = []
63
+ for (const { message } of i.data)
64
+ array.push(...this.makeMsg(message))
65
+ i.data = array
66
+ break
67
+ } default:
68
+ i = { type: "text", data: JSON.stringify(i) }
69
+ }
70
+ msgs.push(i)
71
+ }
72
+ return msgs
73
+ }
74
+
75
+ sendFriendMsg(data, msg) {
76
+ const content = this.makeMsg(msg)
77
+ logger.info(`${logger.blue(`[${data.self_id} => ${data.user_id}]`)} 发送好友消息:${this.makeLog(content)}`)
78
+ data.bot.sendApi({
79
+ bot_id: data.bot.bot_id,
80
+ bot_self_id: data.bot.bot_self_id,
81
+ target_type: "direct",
82
+ target_id: data.user_id,
83
+ content,
84
+ })
85
+ return { message_id: Date.now() }
86
+ }
87
+
88
+ sendGroupMsg(data, msg) {
89
+ const target = data.group_id.split("-")
90
+ const content = this.makeMsg(msg)
91
+ logger.info(`${logger.blue(`[${data.self_id} => ${data.group_id}]`)} 发送群消息:${this.makeLog(content)}`)
92
+ data.bot.sendApi({
93
+ bot_id: data.bot.bot_id,
94
+ bot_self_id: data.bot.bot_self_id,
95
+ target_type: target[0],
96
+ target_id: target[1],
97
+ content,
98
+ })
99
+ return { message_id: Date.now() }
100
+ }
101
+
102
+ pickFriend(id, user_id) {
103
+ const i = {
104
+ ...Bot[id].fl.get(user_id),
105
+ self_id: id,
106
+ bot: Bot[id],
107
+ user_id: user_id,
108
+ }
109
+ return {
110
+ ...i,
111
+ sendMsg: msg => this.sendFriendMsg(i, msg),
112
+ }
113
+ }
114
+
115
+ pickMember(id, group_id, user_id) {
116
+ const i = {
117
+ ...Bot[id].fl.get(user_id),
118
+ self_id: id,
119
+ bot: Bot[id],
120
+ group_id: group_id,
121
+ user_id: user_id,
122
+ }
123
+ return {
124
+ ...this.pickFriend(id, user_id),
125
+ ...i,
126
+ }
127
+ }
128
+
129
+ pickGroup(id, group_id) {
130
+ const i = {
131
+ ...Bot[id].gl.get(group_id),
132
+ self_id: id,
133
+ bot: Bot[id],
134
+ group_id: group_id,
135
+ }
136
+ return {
137
+ ...i,
138
+ sendMsg: msg => this.sendGroupMsg(i, msg),
139
+ pickMember: user_id => this.pickMember(id, group_id, user_id),
140
+ }
141
+ }
142
+
143
+ makeBot(data, ws) {
144
+ Bot[data.self_id] = {
145
+ adapter: this,
146
+ ws: ws,
147
+ get sendApi() { return this.ws.sendMsg },
148
+ uin: data.self_id,
149
+ bot_id: data.bot_id,
150
+ bot_self_id: data.bot_self_id,
151
+ stat: { start_time: Date.now()/1000 },
152
+ version: {
153
+ id: this.id,
154
+ name: this.name,
155
+ },
156
+ pickFriend: user_id => this.pickFriend(data.self_id, user_id),
157
+ get pickUser() { return this.pickFriend },
158
+ pickMember: (group_id, user_id) => this.pickMember(data.self_id, group_id, user_id),
159
+ pickGroup: group_id => this.pickGroup(data.self_id, group_id),
160
+ fl: new Map,
161
+ gl: new Map,
162
+ gml: new Map,
163
+ }
164
+
165
+ logger.mark(`${logger.blue(`[${data.self_id}]`)} ${this.name}(${this.id}) 已连接`)
166
+ Bot.em(`connect.${data.self_id}`, data)
167
+ }
168
+
169
+ message(data, ws) {
170
+ try {
171
+ data = JSON.parse(data)
172
+ } catch (err) {
173
+ return logger.error(`解码数据失败:${logger.red(err)}`)
174
+ }
175
+
176
+ data.self_id = data.bot_self_id
177
+ if (Bot[data.self_id]) {
178
+ data.bot = Bot[data.self_id]
179
+ data.bot.ws = ws
180
+ } else {
181
+ this.makeBot(data, ws)
182
+ }
183
+
184
+ data.post_type = "message"
185
+ data.message_id = data.msg_id
186
+ data.user_id = data.user_id
187
+ data.sender = {
188
+ user_id: data.user_id,
189
+ user_pm: data.user_pm,
190
+ }
191
+ if (!data.bot.fl.has(data.user_id))
192
+ data.bot.fl.set(data.user_id, data.sender)
193
+
194
+ data.message = []
195
+ data.raw_message = ""
196
+ for (const i of data.content) {
197
+ switch (i.type) {
198
+ case "text":
199
+ data.message.push({ type: "text", text: i.data })
200
+ data.raw_message += i.data
201
+ break
202
+ case "image":
203
+ data.message.push({ type: "image", url: i.data })
204
+ data.raw_message += `[图片:${i.data}]`
205
+ break
206
+ case "file":
207
+ data.message.push({ type: "file", url: i.data })
208
+ data.raw_message += `[文件:${i.data}]`
209
+ break
210
+ case "at":
211
+ data.message.push({ type: "at", qq: i.data })
212
+ data.raw_message += `[提及:${i.data}]`
213
+ break
214
+ case "reply":
215
+ data.message.push({ type: "reply", id: i.data })
216
+ data.raw_message += `[回复:${i.data}]`
217
+ break
218
+ case "node":
219
+ data.message.push({ type: "node", data: i.data })
220
+ data.raw_message += `[合并转发:${JSON.stringify(i.data)}]`
221
+ break
222
+ default:
223
+ data.message.push(i)
224
+ data.raw_message += JSON.stringify(i)
225
+ }
226
+ }
227
+
228
+ if (data.user_type == "direct") {
229
+ data.message_type = "private"
230
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 好友消息:[${data.user_id}] ${data.raw_message}`)
231
+ } else {
232
+ data.message_type = "group"
233
+ data.group_id = `${data.user_type}-${data.group_id}`
234
+ if (!data.bot.gl.has(data.group_id))
235
+ data.bot.gl.set(data.group_id, { group_id: data.group_id })
236
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 群消息:[${data.group_id}, ${data.user_id}] ${data.raw_message}`)
237
+ }
238
+
239
+ Bot.em(`${data.post_type}.${data.message_type}`, data)
240
+ }
241
+
242
+ load() {
243
+ if (!Array.isArray(Bot.wsf[this.path]))
244
+ Bot.wsf[this.path] = []
245
+ Bot.wsf[this.path].push((ws, ...args) =>
246
+ ws.on("message", data => this.message(data, ws, ...args))
247
+ )
248
+ }
249
+ })
Yunzai/plugins/adapter/go-cqhttp.js ADDED
@@ -0,0 +1,842 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { randomUUID } from "crypto"
2
+ import path from "node:path"
3
+ import fs from "node:fs"
4
+
5
+ Bot.adapter.push(new class gocqhttpAdapter {
6
+ constructor() {
7
+ this.id = "QQ"
8
+ this.name = "go-cqhttp"
9
+ this.path = this.name
10
+ }
11
+
12
+ toStr(data) {
13
+ switch (typeof data) {
14
+ case "string":
15
+ return data
16
+ case "number":
17
+ return String(data)
18
+ case "object":
19
+ if (Buffer.isBuffer(data))
20
+ return Buffer.from(data, "utf8").toString()
21
+ else
22
+ return JSON.stringify(data)
23
+ }
24
+ return data
25
+ }
26
+
27
+ makeLog(msg) {
28
+ return this.toStr(msg).replace(/base64:\/\/.*?(,|]|")/g, "base64://...$1")
29
+ }
30
+
31
+ sendApi(ws, action, params) {
32
+ const echo = randomUUID()
33
+ const msg = { action, params, echo }
34
+ ws.sendMsg(msg)
35
+ return new Promise(resolve =>
36
+ Bot.once(echo, data =>
37
+ resolve({ ...data, ...data.data })))
38
+ }
39
+
40
+ setProfile(data, profile) {
41
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 设置资料:${JSON.stringify(profile)}`)
42
+ return data.bot.sendApi("set_qq_profile", profile)
43
+ }
44
+
45
+ makeMsg(msg) {
46
+ if (!Array.isArray(msg))
47
+ msg = [msg]
48
+ const msgs = []
49
+ for (const i of msg)
50
+ if (typeof i == "object") {
51
+ if (i.data)
52
+ msgs.push(i)
53
+ else
54
+ msgs.push({ type: i.type, data: { ...i, type: undefined }})
55
+ } else {
56
+ msgs.push({ type: "text", data: { text: i }})
57
+ }
58
+ return msgs
59
+ }
60
+
61
+ sendFriendMsg(data, msg) {
62
+ if (msg?.type == "node")
63
+ return this.sendFriendForwardMsg(data, msg.data)
64
+
65
+ logger.info(`${logger.blue(`[${data.self_id} => ${data.user_id}]`)} 发送好友消息:${this.makeLog(msg)}`)
66
+ return data.bot.sendApi("send_msg", {
67
+ user_id: data.user_id,
68
+ message: this.makeMsg(msg),
69
+ })
70
+ }
71
+
72
+ sendGroupMsg(data, msg) {
73
+ if (msg?.type == "node")
74
+ return this.sendGroupForwardMsg(data, msg.data)
75
+
76
+ logger.info(`${logger.blue(`[${data.self_id} => ${data.group_id}]`)} 发送群消息:${this.makeLog(msg)}`)
77
+ return data.bot.sendApi("send_msg", {
78
+ group_id: data.group_id,
79
+ message: this.makeMsg(msg),
80
+ })
81
+ }
82
+
83
+ sendGuildMsg(data, msg) {
84
+ if (msg?.type == "node")
85
+ return Bot.sendForwardMsg(msg => this.sendGuildMsg(data, msg), msg)
86
+
87
+ logger.info(`${logger.blue(`[${data.self_id}] => ${data.guild_id}-${data.channel_id}`)} 发送频道消息:${this.makeLog(msg)}`)
88
+ return data.bot.sendApi("send_guild_channel_msg", {
89
+ guild_id: data.guild_id,
90
+ channel_id: data.channel_id,
91
+ message: this.makeMsg(msg),
92
+ })
93
+ }
94
+
95
+ async getMsg(data, message_id) {
96
+ const msg = (await data.bot.sendApi("get_msg", { message_id })).data
97
+
98
+ if (msg?.message) {
99
+ const message = []
100
+ for (const i of msg.message)
101
+ message.push({ ...i.data, type: i.type })
102
+ msg.message = message
103
+ }
104
+
105
+ return msg
106
+ }
107
+
108
+ recallMsg(data, message_id) {
109
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 撤回消息:${message_id}`)
110
+ return data.bot.sendApi("delete_msg", { message_id })
111
+ }
112
+
113
+ getForwardMsg(data, message_id) {
114
+ return data.bot.sendApi("get_forward_msg", { message_id })
115
+ }
116
+
117
+ makeForwardMsg(msg) {
118
+ const messages = []
119
+ for (const i of msg)
120
+ messages.push({
121
+ type: "node",
122
+ data: {
123
+ name: i.nickname || "匿名消息",
124
+ uin: Number(i.user_id) || 80000000,
125
+ content: this.makeMsg(i.message),
126
+ time: i.time,
127
+ },
128
+ })
129
+ return messages
130
+ }
131
+
132
+ async sendFriendForwardMsg(data, msg) {
133
+ logger.info(`${logger.blue(`[${data.self_id} => ${data.user_id}]`)} 发送好友转发消息:${this.makeLog(msg)}`)
134
+ msg = await data.bot.sendApi("send_private_forward_msg", {
135
+ user_id: data.user_id,
136
+ messages: this.makeForwardMsg(msg),
137
+ })
138
+ return msg
139
+ }
140
+
141
+ async sendGroupForwardMsg(data, msg) {
142
+ logger.info(`${logger.blue(`[${data.self_id} => ${data.group_id}]`)} 发送群转发消息:${this.makeLog(msg)}`)
143
+ msg = await data.bot.sendApi("send_group_forward_msg", {
144
+ group_id: data.group_id,
145
+ messages: this.makeForwardMsg(msg),
146
+ })
147
+ return msg
148
+ }
149
+
150
+ async getFriendArray(data) {
151
+ return (await data.bot.sendApi("get_friend_list")).data
152
+ }
153
+
154
+ async getFriendList(data) {
155
+ const array = []
156
+ for (const { user_id } of (await this.getFriendArray(data)))
157
+ array.push(user_id)
158
+ return array
159
+ }
160
+
161
+ async getFriendMap(data) {
162
+ for (const i of (await this.getFriendArray(data)))
163
+ data.bot.fl.set(i.user_id, i)
164
+ return data.bot.fl
165
+ }
166
+
167
+ getFriendInfo(data) {
168
+ return data.bot.sendApi("get_stranger_info", {
169
+ user_id: data.user_id,
170
+ })
171
+ }
172
+
173
+ async getGroupArray(data) {
174
+ const array = (await data.bot.sendApi("get_group_list")).data
175
+ for (const guild of (await this.getGuildArray(data)))
176
+ for (const channel of (await this.getGuildChannelArray({
177
+ ...data,
178
+ guild_id: guild.guild_id,
179
+ })))
180
+ array.push({
181
+ guild,
182
+ channel,
183
+ group_id: `${guild.guild_id}-${channel.channel_id}`,
184
+ group_name: `${guild.guild_name}-${channel.channel_name}`,
185
+ })
186
+ return array
187
+ }
188
+
189
+ async getGroupList(data) {
190
+ const array = []
191
+ for (const { group_id } of (await this.getGroupArray(data)))
192
+ array.push(group_id)
193
+ return array
194
+ }
195
+
196
+ async getGroupMap(data) {
197
+ for (const i of (await this.getGroupArray(data)))
198
+ data.bot.gl.set(i.group_id, i)
199
+ return data.bot.gl
200
+ }
201
+
202
+ getGroupInfo(data) {
203
+ return data.bot.sendApi("get_group_info", {
204
+ group_id: data.group_id,
205
+ })
206
+ }
207
+
208
+ async getMemberArray(data) {
209
+ return (await data.bot.sendApi("get_group_member_list", {
210
+ group_id: data.group_id,
211
+ })).data
212
+ }
213
+
214
+ async getMemberList(data) {
215
+ const array = []
216
+ for (const { user_id } of (await this.getMemberArray(data)))
217
+ array.push(user_id)
218
+ return array
219
+ }
220
+
221
+ async getMemberMap(data) {
222
+ const map = new Map
223
+ for (const i of (await this.getMemberArray(data)))
224
+ map.set(i.user_id, i)
225
+ return map
226
+ }
227
+
228
+ getMemberInfo(data) {
229
+ return data.bot.sendApi("get_group_member_info", {
230
+ group_id: data.group_id,
231
+ user_id: data.user_id,
232
+ })
233
+ }
234
+
235
+ async getGuildArray(data) {
236
+ return (await data.bot.sendApi("get_guild_list")).data
237
+ }
238
+
239
+ getGuildInfo(data) {
240
+ return data.bot.sendApi("get_guild_meta_by_guest", {
241
+ guild_id: data.guild_id,
242
+ })
243
+ }
244
+
245
+ async getGuildChannelArray(data) {
246
+ return (await data.bot.sendApi("get_guild_channel_list", {
247
+ guild_id: data.guild_id,
248
+ })).data
249
+ }
250
+
251
+ async getGuildChannelMap(data) {
252
+ const map = new Map
253
+ for (const i of (await this.getGuildChannelArray(data)))
254
+ map.set(i.channel_id, i)
255
+ return map
256
+ }
257
+
258
+ async getGuildMemberArray(data) {
259
+ const array = []
260
+ let next_token = ""
261
+ while (true) {
262
+ const list = (await data.bot.sendApi("get_guild_member_list", {
263
+ guild_id: data.guild_id,
264
+ next_token,
265
+ })).data
266
+
267
+ for (const i of list.members)
268
+ array.push({
269
+ ...i,
270
+ user_id: i.tiny_id,
271
+ })
272
+ if (list.finished) break
273
+ next_token = list.next_token
274
+ }
275
+ return array
276
+ }
277
+
278
+ async getGuildMemberList(data) {
279
+ const array = []
280
+ for (const { user_id } of (await this.getGuildMemberArray(data)))
281
+ array.push(user_id)
282
+ return array.push
283
+ }
284
+
285
+ async getGuildMemberMap(data) {
286
+ const map = new Map
287
+ for (const i of (await this.getGuildMemberArray(data)))
288
+ map.set(i.user_id, i)
289
+ return map
290
+ }
291
+
292
+ getGuildMemberInfo(data) {
293
+ return data.bot.sendApi("get_guild_member_profile", {
294
+ guild_id: data.guild_id,
295
+ user_id: data.user_id,
296
+ })
297
+ }
298
+
299
+ setGroupName(data, group_name) {
300
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 设置群名:[${data.group_id}] ${group_name}`)
301
+ return data.bot.sendApi("set_group_name", {
302
+ group_id: data.group_id,
303
+ group_name,
304
+ })
305
+ }
306
+
307
+ setGroupAvatar(data, file) {
308
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 设置群头像:[${data.group_id}] ${file}`)
309
+ return data.bot.sendApi("set_group_portrait", {
310
+ group_id: data.group_id,
311
+ file: segment.image(file).file,
312
+ })
313
+ }
314
+
315
+ setGroupAdmin(data, user_id, enable) {
316
+ logger.info(`${logger.blue(`[${data.self_id}]`)} ${enable ? "设置" : "取消"}群管理员:[${data.group_id}] ${user_id}`)
317
+ return data.bot.sendApi("set_group_admin", {
318
+ group_id: data.group_id,
319
+ user_id,
320
+ enable,
321
+ })
322
+ }
323
+
324
+ setGroupCard(data, user_id, card) {
325
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 设置群名片:[${data.group_id}] ${user_id} ${card}`)
326
+ return data.bot.sendApi("set_group_card", {
327
+ group_id: data.group_id,
328
+ user_id,
329
+ card,
330
+ })
331
+ }
332
+
333
+ setGroupTitle(data, user_id, special_title, duration) {
334
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 设置群头衔:[${data.group_id}] ${user_id} ${special_title} ${duration}`)
335
+ return data.bot.sendApi("set_group_special_title", {
336
+ group_id: data.group_id,
337
+ user_id,
338
+ special_title,
339
+ duration,
340
+ })
341
+ }
342
+
343
+ downloadFile(data, url, thread_count, headers) {
344
+ return data.bot.sendApi("download_file", {
345
+ url,
346
+ thread_count,
347
+ headers,
348
+ })
349
+ }
350
+
351
+ async makeFile(data, file, name = path.basename(file)) {
352
+ if (file.match(/^https?:\/\//))
353
+ file = (await this.downloadFile(data, file)).file
354
+ else if (fs.existsSync(file))
355
+ file = path.resolve(file)
356
+ return { file, name }
357
+ }
358
+
359
+ async sendFriendFile(data, file, name) {
360
+ logger.info(`${logger.blue(`[${data.self_id} => ${data.user_id}]`)} 发送好友文件:${name}(${file})`)
361
+ return data.bot.sendApi("upload_private_file", {
362
+ user_id: data.user_id,
363
+ ...await this.makeFile(data, file, name),
364
+ })
365
+ }
366
+
367
+ async sendGroupFile(data, file, folder, name) {
368
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 发送群文件:[${data.group_id}] ${folder||""}/${name}(${file})`)
369
+ return data.bot.sendApi("upload_group_file", {
370
+ group_id: data.group_id,
371
+ folder,
372
+ ...await this.makeFile(data, file, name),
373
+ })
374
+ }
375
+
376
+ deleteGroupFile(data, file_id, busid) {
377
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 删除群文件:[${data.group_id}] ${file_id}(${busid})`)
378
+ return data.bot.sendApi("delete_group_file", {
379
+ group_id: data.group_id,
380
+ file_id,
381
+ busid,
382
+ })
383
+ }
384
+
385
+ createGroupFileFolder(data, name) {
386
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 创建群文件夹:[${data.group_id}] ${name}`)
387
+ return data.bot.sendApi("create_group_file_folder", {
388
+ group_id: data.group_id,
389
+ name,
390
+ })
391
+ }
392
+
393
+ getGroupFileSystemInfo(data) {
394
+ return data.bot.sendApi("get_group_file_system_info", {
395
+ group_id: data.group_id,
396
+ })
397
+ }
398
+
399
+ getGroupFiles(data, folder_id) {
400
+ if (folder_id)
401
+ return data.bot.sendApi("get_group_files_by_folder", {
402
+ group_id: data.group_id,
403
+ folder_id,
404
+ })
405
+ return data.bot.sendApi("get_group_root_files", {
406
+ group_id: data.group_id,
407
+ })
408
+ }
409
+
410
+ getGroupFileUrl(data, file_id, busid) {
411
+ return data.bot.sendApi("get_group_file_url", {
412
+ group_id: data.group_id,
413
+ file_id,
414
+ busid,
415
+ })
416
+ }
417
+
418
+ getGroupFs(data) {
419
+ return {
420
+ upload: (file, folder, name) => this.sendGroupFile(data, file, folder, name),
421
+ rm: (file_id, busid) => this.deleteGroupFile(data, file_id, busid),
422
+ mkdir: name => this.createGroupFileFolder(data, name),
423
+ df: () => this.getGroupFileSystemInfo(data),
424
+ ls: folder_id => this.getGroupFiles(data, folder_id),
425
+ download: (file_id, busid) => this.getGroupFileUrl(data, file_id, busid),
426
+ }
427
+ }
428
+
429
+ setFriendAddRequest(data, flag, approve, remark) {
430
+ return data.bot.sendApi("set_friend_add_request", {
431
+ flag,
432
+ approve,
433
+ remark,
434
+ })
435
+ }
436
+
437
+ setGroupAddRequest(data, flag, sub_type, approve, reason) {
438
+ return data.bot.sendApi("set_group_add_request", {
439
+ flag,
440
+ sub_type,
441
+ approve,
442
+ reason,
443
+ })
444
+ }
445
+
446
+ pickFriend(data, user_id) {
447
+ const i = {
448
+ ...data.bot.fl.get(user_id),
449
+ ...data,
450
+ user_id,
451
+ }
452
+ return {
453
+ ...i,
454
+ sendMsg: msg => this.sendFriendMsg(i, msg),
455
+ getMsg: message_id => this.getMsg(i, message_id),
456
+ recallMsg: message_id => this.recallMsg(i, message_id),
457
+ getForwardMsg: message_id => this.getForwardMsg(i, message_id),
458
+ sendForwardMsg: msg => this.sendFriendForwardMsg(i, msg),
459
+ sendFile: (file, name) => this.sendFriendFile(i, file, name),
460
+ getInfo: () => this.getFriendInfo(i),
461
+ getAvatarUrl: () => `https://q1.qlogo.cn/g?b=qq&s=0&nk=${user_id}`,
462
+ }
463
+ }
464
+
465
+ pickMember(data, group_id, user_id) {
466
+ if (typeof group_id == "string" && group_id.match("-")) {
467
+ const guild_id = group_id.split("-")
468
+ const i = {
469
+ ...data,
470
+ guild_id: guild_id[0],
471
+ channel_id: guild_id[1],
472
+ user_id,
473
+ }
474
+ return {
475
+ ...this.pickGroup(i, group_id),
476
+ ...i,
477
+ getInfo: () => this.getGuildMemberInfo(i),
478
+ getAvatarUrl: async () => (await this.getGuildMemberInfo(i)).avatar_url,
479
+ }
480
+ }
481
+
482
+ const i = {
483
+ ...data.bot.fl.get(user_id),
484
+ ...data,
485
+ group_id,
486
+ user_id,
487
+ }
488
+ return {
489
+ ...this.pickFriend(i, user_id),
490
+ ...i,
491
+ getInfo: () => this.getMemberInfo(i),
492
+ poke: () => this.sendGroupMsg(i, segment.poke(user_id)),
493
+ }
494
+ }
495
+
496
+ pickGroup(data, group_id) {
497
+ if (typeof group_id == "string" && group_id.match("-")) {
498
+ const guild_id = group_id.split("-")
499
+ const i = {
500
+ ...data.bot.gl.get(group_id),
501
+ ...data,
502
+ guild_id: guild_id[0],
503
+ channel_id: guild_id[1],
504
+ }
505
+ return {
506
+ ...i,
507
+ sendMsg: msg => this.sendGuildMsg(i, msg),
508
+ getMsg: message_id => this.getMsg(i, message_id),
509
+ recallMsg: message_id => this.recallMsg(i, message_id),
510
+ getForwardMsg: message_id => this.getForwardMsg(i, message_id),
511
+ getInfo: () => this.getGuildInfo(i),
512
+ getChannelArray: () => this.getGuildChannelArray(i),
513
+ getChannelList: () => this.getGuildChannelList(i),
514
+ getChannelMap: () => this.getGuildChannelMap(i),
515
+ getMemberArray: () => this.getGuildMemberArray(i),
516
+ getMemberList: () => this.getGuildMemberList(i),
517
+ getMemberMap: () => this.getGuildMemberMap(i),
518
+ pickMember: user_id => this.pickMember(i, group_id, user_id),
519
+ }
520
+ }
521
+
522
+ const i = {
523
+ ...data.bot.gl.get(group_id),
524
+ ...data,
525
+ group_id,
526
+ }
527
+ return {
528
+ ...i,
529
+ sendMsg: msg => this.sendGroupMsg(i, msg),
530
+ getMsg: message_id => this.getMsg(i, message_id),
531
+ recallMsg: message_id => this.recallMsg(i, message_id),
532
+ getForwardMsg: message_id => this.getForwardMsg(i, message_id),
533
+ sendForwardMsg: msg => this.sendGroupForwardMsg(i, msg),
534
+ sendFile: (file, name) => this.sendGroupFile(i, file, undefined, name),
535
+ getInfo: () => this.getGroupInfo(i),
536
+ getAvatarUrl: () => `https://p.qlogo.cn/gh/${group_id}/${group_id}/0`,
537
+ getMemberArray: () => this.getMemberArray(i),
538
+ getMemberList: () => this.getMemberList(i),
539
+ getMemberMap: () => this.getMemberMap(i),
540
+ pickMember: user_id => this.pickMember(i, group_id, user_id),
541
+ pokeMember: user_id => this.sendGroupMsg(i, segment.poke(user_id)),
542
+ setName: group_name => this.setGroupName(i, group_name),
543
+ setAvatar: file => this.setGroupAvatar(i, file),
544
+ setAdmin: (user_id, enable) => this.setGroupAdmin(i, user_id, enable),
545
+ setCard: (user_id, card) => this.setGroupCard(i, user_id, card),
546
+ setTitle: (user_id, special_title, duration) => this.setGroupTitle(i, user_id, special_title, duration),
547
+ fs: this.getGroupFs(i),
548
+ }
549
+ }
550
+
551
+ async connect(data, ws) {
552
+ Bot[data.self_id] = {
553
+ adapter: this,
554
+ ws: ws,
555
+ sendApi: (action, params) => this.sendApi(ws, action, params),
556
+ stat: { start_time: data.time },
557
+ model: "TRSS Yunzai ",
558
+
559
+ info: {},
560
+ get uin() { return this.info.user_id },
561
+ get nickname() { return this.info.nickname },
562
+ get avatar() { return `https://q1.qlogo.cn/g?b=qq&s=0&nk=${this.uin}` },
563
+
564
+ setProfile: profile => this.setProfile(data, profile),
565
+ setNickname: nickname => this.setProfile(data, { nickname }),
566
+
567
+ pickFriend: user_id => this.pickFriend(data, user_id),
568
+ get pickUser() { return this.pickFriend },
569
+ getFriendArray: () => this.getFriendArray(data),
570
+ getFriendList: () => this.getFriendList(data),
571
+ getFriendMap: () => this.getFriendMap(data),
572
+ fl: new Map,
573
+
574
+ pickMember: (group_id, user_id) => this.pickMember(data, group_id, user_id),
575
+ pickGroup: group_id => this.pickGroup(data, group_id),
576
+ getGroupArray: () => this.getGroupArray(data),
577
+ getGroupList: () => this.getGroupList(data),
578
+ getGroupMap: () => this.getGroupMap(data),
579
+ gl: new Map,
580
+ gml: new Map,
581
+
582
+ request_list: [],
583
+ getSystemMsg: () => data.bot.request_list,
584
+ setFriendAddRequest: (flag, approve, remark) => this.setFriendAddRequest(data, flag, approve, remark),
585
+ setGroupAddRequest: (flag, sub_type, approve, reason) => this.setGroupAddRequest(data, flag, sub_type, approve, reason),
586
+ }
587
+ data.bot = Bot[data.self_id]
588
+
589
+ if (!Bot.uin.includes(data.self_id))
590
+ Bot.uin.push(data.self_id)
591
+
592
+ data.bot.sendApi("_set_model_show", {
593
+ model: data.bot.model,
594
+ model_show: data.bot.model,
595
+ })
596
+
597
+ data.bot.info = (await data.bot.sendApi("get_login_info")).data
598
+ data.bot.guild_info = (await data.bot.sendApi("get_guild_service_profile")).data
599
+ data.bot.clients = (await data.bot.sendApi("get_online_clients")).clients
600
+ data.bot.version = {
601
+ ...(await data.bot.sendApi("get_version_info")).data,
602
+ id: this.id,
603
+ name: this.name,
604
+ }
605
+
606
+ data.bot.getFriendMap()
607
+ data.bot.getGroupMap()
608
+
609
+ logger.mark(`${logger.blue(`[${data.self_id}]`)} ${this.name}(${this.id}) ${data.bot.version.app_full_name} 已连接`)
610
+ Bot.em(`connect.${data.self_id}`, data)
611
+ }
612
+
613
+ makeMessage(data) {
614
+ const message = []
615
+ for (const i of data.message)
616
+ message.push({ ...i.data, type: i.type })
617
+ data.message = message
618
+
619
+ switch (data.message_type) {
620
+ case "private":
621
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 好友消息:[${data.sender.nickname}(${data.user_id})] ${data.raw_message}`)
622
+ break
623
+ case "group":
624
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 群消息:[${data.group_id}, ${data.sender.card||data.sender.nickname}(${data.user_id})] ${data.raw_message}`)
625
+ break
626
+ case "guild":
627
+ data.message_type = "group"
628
+ data.group_id = `${data.guild_id}-${data.channel_id}`
629
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 频道消息:[${data.group_id}, ${data.sender.nickname}(${data.user_id})] ${JSON.stringify(data.message)}`)
630
+ Object.defineProperty(data, "friend", { get() { return this.member || {}}})
631
+ break
632
+ default:
633
+ logger.warn(`${logger.blue(`[${data.self_id}]`)} 未知消息:${logger.magenta(JSON.stringify(data))}`)
634
+ }
635
+
636
+ Bot.em(`${data.post_type}.${data.message_type}.${data.sub_type}`, data)
637
+ }
638
+
639
+ async makeNotice(data) {
640
+ switch (data.notice_type) {
641
+ case "friend_recall":
642
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 好友消息撤回:[${data.user_id}] ${data.message_id}`)
643
+ break
644
+ case "group_recall":
645
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 群消息撤回:[${data.group_id}, ${data.operator_id}=>${data.user_id}] ${data.message_id}`)
646
+ break
647
+ case "group_increase":
648
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 群成员增加:[${data.group_id}, ${data.operator_id}=>${data.user_id}] ${data.sub_type}`)
649
+ if (data.user_id == data.self_id)
650
+ data.bot.getGroupMap()
651
+ break
652
+ case "group_decrease":
653
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 群成员减少:[${data.group_id}, ${data.operator_id}=>${data.user_id}] ${data.sub_type}`)
654
+ if (data.user_id == data.self_id)
655
+ data.bot.getGroupMap()
656
+ break
657
+ case "group_admin":
658
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 群管理员变动:[${data.group_id}, ${data.user_id}] ${data.sub_type}`)
659
+ data.set = data.sub_type == "set"
660
+ break
661
+ case "group_upload":
662
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 群文件上传:[${data.group_id}, ${data.user_id}] ${JSON.stringify(data.file)}`)
663
+ break
664
+ case "group_ban":
665
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 群禁言:[${data.group_id}, ${data.operator_id}=>${data.user_id}] ${data.sub_type} ${data.duration}秒`)
666
+ break
667
+ case "friend_add":
668
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 好友添加:[${data.user_id}]`)
669
+ data.bot.getFriendMap()
670
+ break
671
+ case "notify":
672
+ if (data.group_id)
673
+ data.notice_type = "group"
674
+ else
675
+ data.notice_type = "friend"
676
+ switch (data.sub_type) {
677
+ case "poke":
678
+ data.operator_id = data.user_id
679
+ if (data.group_id)
680
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 群戳一戳:[${data.group_id}, ${data.operator_id}=>${data.target_id}]`)
681
+ else
682
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 好友戳一戳:[${data.operator_id}=>${data.target_id}]`)
683
+ break
684
+ case "honor":
685
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 群荣誉:[${data.group_id}, ${data.user_id}] ${data.honor_type}`)
686
+ break
687
+ case "title":
688
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 群头衔:[${data.group_id}, ${data.user_id}] ${data.title}`)
689
+ break
690
+ default:
691
+ logger.warn(`${logger.blue(`[${data.self_id}]`)} 未知通知:${logger.magenta(JSON.stringify(data))}`)
692
+ }
693
+ break
694
+ case "group_card":
695
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 群名片更新:[${data.group_id}, ${data.user_id}] ${data.card_old}=>${data.card_new}`)
696
+ break
697
+ case "offline_file":
698
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 离线文件:[${data.user_id}] ${JSON.stringify(data.file)}`)
699
+ break
700
+ case "client_status":
701
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 客户端${data.online ? "上线" : "下线"}:${JSON.stringify(data.client)}`)
702
+ data.clients = (await data.bot.sendApi("get_online_clients")).clients
703
+ data.bot.clients = data.clients
704
+ break
705
+ case "essence":
706
+ data.notice_type = "group_essence"
707
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 群精华消息:[${data.group_id}, ${data.operator_id}=>${data.sender_id}] ${data.sub_type} ${data.message_id}`)
708
+ break
709
+ case "guild_channel_recall":
710
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 频道消息撤回:[${data.guild_id}-${data.channel_id}, ${data.operator_id}=>${data.user_id}] ${data.message_id}`)
711
+ break
712
+ case "message_reactions_updated":
713
+ data.notice_type = "guild_message_reactions_updated"
714
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 频道消息表情贴:[${data.guild_id}-${data.channel_id}, ${data.user_id}] ${data.message_id} ${JSON.stringify(data.current_reactions)}`)
715
+ break
716
+ case "channel_updated":
717
+ data.notice_type = "guild_channel_updated"
718
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 子频道更新:[${data.guild_id}-${data.channel_id}, ${data.user_id}] ${JSON.stringify(data.old_info)}=>${JSON.stringify(data.new_info)}`)
719
+ break
720
+ case "channel_created":
721
+ data.notice_type = "guild_channel_created"
722
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 子频道创建:[${data.guild_id}-${data.channel_id}, ${data.user_id}] ${JSON.stringify(data.channel_info)}`)
723
+ data.bot.getGroupMap()
724
+ break
725
+ case "channel_destroyed":
726
+ data.notice_type = "guild_channel_destroyed"
727
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 子频道删除:[${data.guild_id}-${data.channel_id}, ${data.user_id}] ${JSON.stringify(data.channel_info)}`)
728
+ data.bot.getGroupMap()
729
+ break
730
+ default:
731
+ logger.warn(`${logger.blue(`[${data.self_id}]`)} 未知通知:${logger.magenta(JSON.stringify(data))}`)
732
+ }
733
+
734
+ let notice = data.notice_type.split("_")
735
+ data.notice_type = notice.shift()
736
+ notice = notice.join("_")
737
+ if (notice)
738
+ data.sub_type = notice
739
+
740
+ if (data.guild_id && data.channel_id) {
741
+ data.group_id = `${data.guild_id}-${data.channel_id}`
742
+ Object.defineProperty(data, "friend", { get() { return this.member || {}}})
743
+ }
744
+
745
+ Bot.em(`${data.post_type}.${data.notice_type}.${data.sub_type}`, data)
746
+ }
747
+
748
+ makeRequest(data) {
749
+ switch (data.request_type) {
750
+ case "friend":
751
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 加好友请求:[${data.user_id}] ${data.comment}(${data.flag})`)
752
+ data.sub_type = "add"
753
+ data.approve = approve => data.bot.setFriendAddRequest(data.flag, approve)
754
+ break
755
+ case "group":
756
+ logger.info(`${logger.blue(`[${data.self_id}]`)} 加群请求:[${data.group_id}, ${data.user_id}] ${data.sub_type} ${data.comment}(${data.flag})`)
757
+ data.approve = approve => data.bot.setGroupAddRequest(data.flag, data.sub_type, approve)
758
+ break
759
+ default:
760
+ logger.warn(`${logger.blue(`[${data.self_id}]`)} 未知请求:${logger.magenta(JSON.stringify(data))}`)
761
+ }
762
+
763
+ data.bot.request_list.push(data)
764
+ Bot.em(`${data.post_type}.${data.request_type}.${data.sub_type}`, data)
765
+ }
766
+
767
+ heartbeat(data) {
768
+ if (data.status?.stat)
769
+ data.bot.stat = {
770
+ ...data.status,
771
+ lost_pkt_cnt: data.status.stat.packet_lost,
772
+ lost_times: data.status.stat.lost_times,
773
+ recv_msg_cnt: data.status.stat.message_received,
774
+ recv_pkt_cnt: data.status.stat.packet_received,
775
+ sent_msg_cnt: data.status.stat.message_sent,
776
+ sent_pkt_cnt: data.status.stat.packet_sent,
777
+ start_time: data.bot.stat.start_time,
778
+ }
779
+ }
780
+
781
+ makeMeta(data, ws) {
782
+ switch (data.meta_event_type) {
783
+ case "heartbeat":
784
+ this.heartbeat(data)
785
+ break
786
+ case "lifecycle":
787
+ this.connect(data, ws)
788
+ break
789
+ default:
790
+ logger.warn(`${logger.blue(`[${data.self_id}]`)} 未知消息:${logger.magenta(JSON.stringify(data))}`)
791
+ }
792
+ }
793
+
794
+ message(data, ws) {
795
+ try {
796
+ data = JSON.parse(data)
797
+ } catch (err) {
798
+ return logger.error(`解码数据失败:${logger.red(err)}`)
799
+ }
800
+
801
+ if (data.post_type) {
802
+ if (data.meta_event_type != "lifecycle" && !Bot.uin.includes(data.self_id)) {
803
+ logger.warn(`${logger.blue(`[${data.self_id}]`)} 找不到对应Bot,忽略消息:${logger.magenta(JSON.stringify(data))}`)
804
+ return false
805
+ }
806
+ data.bot = Bot[data.self_id]
807
+
808
+ switch (data.post_type) {
809
+ case "meta_event":
810
+ this.makeMeta(data, ws)
811
+ break
812
+ case "message":
813
+ this.makeMessage(data)
814
+ break
815
+ case "notice":
816
+ this.makeNotice(data)
817
+ break
818
+ case "request":
819
+ this.makeRequest(data)
820
+ break
821
+ case "message_sent":
822
+ data.post_type = "message"
823
+ this.makeMessage(data)
824
+ break
825
+ default:
826
+ logger.warn(`${logger.blue(`[${data.self_id}]`)} 未知消息:${logger.magenta(JSON.stringify(data))}`)
827
+ }
828
+ } else if (data.echo) {
829
+ Bot.emit(data.echo, data)
830
+ } else {
831
+ logger.warn(`${logger.blue(`[${data.self_id}]`)} 未知消息:${logger.magenta(JSON.stringify(data))}`)
832
+ }
833
+ }
834
+
835
+ load() {
836
+ if (!Array.isArray(Bot.wsf[this.path]))
837
+ Bot.wsf[this.path] = []
838
+ Bot.wsf[this.path].push((ws, ...args) =>
839
+ ws.on("message", data => this.message(data, ws, ...args))
840
+ )
841
+ }
842
+ })