├── .github
└── workflows
│ └── release.yml
├── .gitignore
├── LICENSE
├── Qadapter
└── index.js
├── README.md
├── handles
├── chat_pal.html
├── fakeapi.js
├── file.js
├── logger.js
├── msgbuilder.js
├── neoWebEditable.html
├── packbuilder.js
├── parserCQString.js
├── plugin_create.js
├── reconnect.js
├── regex_edi.html
└── ws_con.html
├── index.js
├── llapi.js
├── logo.txt
├── manifest.json
├── package-lock.json
├── package.json
├── plugins
├── JandLandCmsg
│ ├── index.js
│ └── spark.json
├── bridgebase
│ ├── index.js
│ └── spark.json
├── economy
│ ├── index.js
│ └── spark.json
├── list.json
├── llapi
│ ├── index.js
│ ├── lib.js
│ ├── lib
│ │ └── EventAPI.js
│ └── spark.json
├── mc
│ ├── index.js
│ └── spark.json
├── regex
│ ├── index.js
│ └── spark.json
└── telemetry
│ ├── README.md
│ ├── index.js
│ ├── spark.json
│ ├── static
│ ├── index-BJJF37Ya.js
│ └── index-CzIKTSi7.css
│ ├── web
│ └── index.html
│ └── webConfig.js
├── sandbox.bat
├── spark
├── README.md
└── index.js
└── update.bat
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Build and Release
2 |
3 | on:
4 | push:
5 | branches:
6 | - main # 或者你使用的其他分支
7 |
8 | jobs:
9 | build-and-release:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Checkout code
14 | uses: actions/checkout@v4
15 |
16 | - name: Set Git user identity
17 | run: |
18 | git config --global user.email "dreamgqf@163.com"
19 | git config --global user.name "Lition802"
20 |
21 | - name: Update version number
22 | id: update_version
23 | run: |
24 | # 获取当前版本号
25 | version=$(node -p "require('./package.json').version")
26 | # 自动递增版本号
27 | IFS='.' read -r -a version_parts <<< "$version"
28 | version_parts[2]=$((version_parts[2] + 1))
29 | new_version="${version_parts[0]}.${version_parts[1]}.${version_parts[2]}"
30 | echo "New version: $new_version"
31 | echo "VERSION=$new_version" >> $GITHUB_ENV
32 | # 更新 package.json 中的版本号
33 | npm version $new_version --no-git-tag-version
34 | git add package.json
35 | git commit -m "chore: update version to $new_version"
36 | git push
37 |
38 | - name: Package files
39 | run: |
40 | # 打包所有文件到一个压缩包中
41 | zip -r release-${{ env.VERSION }}.zip .
42 |
43 | - name: Create GitHub Release
44 | id: create_release
45 | uses: softprops/action-gh-release@v1
46 | env:
47 | GITHUB_TOKEN: ${{ secrets.ACTION_TOKEN }}
48 | with:
49 | tag_name: v${{ env.VERSION }}
50 | release_name: Release v${{ env.VERSION }}
51 | body: |
52 | Release notes for v${{ env.VERSION }}
53 | draft: false
54 | prerelease: false
55 | files: release-${{ env.VERSION }}.zip
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # sandbox
2 | testdata/
3 | serverdata/
4 | tmp/
5 | plugins/examples/
6 | ./sparkbridge2.zip
7 |
8 | # idea
9 | .vscode/
10 |
11 | # llapi
12 | llapi.js
13 |
14 | # updata
15 | update.bat
16 |
17 | # Logs
18 | logs
19 | *.log
20 | npm-debug.log*
21 | yarn-debug.log*
22 | yarn-error.log*
23 | lerna-debug.log*
24 | .pnpm-debug.log*
25 |
26 | # Diagnostic reports (https://nodejs.org/api/report.html)
27 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
28 |
29 | # Runtime data
30 | pids
31 | *.pid
32 | *.seed
33 | *.pid.lock
34 |
35 | # Directory for instrumented libs generated by jscoverage/JSCover
36 | lib-cov
37 |
38 | # Coverage directory used by tools like istanbul
39 | coverage
40 | *.lcov
41 |
42 | # nyc test coverage
43 | .nyc_output
44 |
45 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
46 | .grunt
47 |
48 | # Bower dependency directory (https://bower.io/)
49 | bower_components
50 |
51 | # node-waf configuration
52 | .lock-wscript
53 |
54 | # Compiled binary addons (https://nodejs.org/api/addons.html)
55 | build/Release
56 |
57 | # Dependency directories
58 | node_modules/
59 | jspm_packages/
60 |
61 | # Snowpack dependency directory (https://snowpack.dev/)
62 | web_modules/
63 |
64 | # TypeScript cache
65 | *.tsbuildinfo
66 |
67 | # Optional npm cache directory
68 | .npm
69 |
70 | # Optional eslint cache
71 | .eslintcache
72 |
73 | # Optional stylelint cache
74 | .stylelintcache
75 |
76 | # Microbundle cache
77 | .rpt2_cache/
78 | .rts2_cache_cjs/
79 | .rts2_cache_es/
80 | .rts2_cache_umd/
81 |
82 | # Optional REPL history
83 | .node_repl_history
84 |
85 | # Output of 'npm pack'
86 | *.tgz
87 |
88 | # Yarn Integrity file
89 | .yarn-integrity
90 |
91 | # dotenv environment variable files
92 | .env
93 | .env.development.local
94 | .env.test.local
95 | .env.production.local
96 | .env.local
97 |
98 | # parcel-bundler cache (https://parceljs.org/)
99 | .cache
100 | .parcel-cache
101 |
102 | # Next.js build output
103 | .next
104 | out
105 |
106 | # Nuxt.js build / generate output
107 | .nuxt
108 | dist
109 |
110 | # Gatsby files
111 | .cache/
112 | # Comment in the public line in if your project uses Gatsby and not Next.js
113 | # https://nextjs.org/blog/next-9-1#public-directory-support
114 | # public
115 |
116 | # vuepress build output
117 | .vuepress/dist
118 |
119 | # vuepress v2.x temp and cache directory
120 | .temp
121 | .cache
122 |
123 | # Docusaurus cache and generated files
124 | .docusaurus
125 |
126 | # Serverless directories
127 | .serverless/
128 |
129 | # FuseBox cache
130 | .fusebox/
131 |
132 | # DynamoDB Local files
133 | .dynamodb/
134 |
135 | # TernJS port file
136 | .tern-port
137 |
138 | # Stores VSCode versions used for testing VSCode extensions
139 | .vscode-test
140 |
141 | # yarn v2
142 | .yarn/cache
143 | .yarn/unplugged
144 | .yarn/build-state.yml
145 | .yarn/install-state.gz
146 | .pnp.*
147 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/Qadapter/index.js:
--------------------------------------------------------------------------------
1 | const { WebSocket } = require('ws');
2 | const EventEmitter = require("events");
3 | const logger = require('../handles/logger');
4 | const { text } = require('../handles/msgbuilder');
5 | const { boom } = require('../handles/reconnect');
6 |
7 | class CustomTrigger extends EventEmitter {
8 | constructor() {
9 | super();
10 | this.interceptors = {}; // 使用对象存储拦截器,键为事件名称
11 | }
12 |
13 | // 添加拦截器,并指定事件名称
14 | addInterceptor(event, interceptor) {
15 | if (!this.interceptors[event]) {
16 | this.interceptors[event] = [];
17 | }
18 | this.interceptors[event].push(interceptor);
19 | }
20 |
21 | // 触发事件
22 | trigger(event, ...args) {
23 | // 检查是否有拦截器拦截当前事件
24 | if (this.interceptors[event]) {
25 | for (const interceptor of this.interceptors[event]) {
26 | const result = interceptor(...args);
27 | if (result === false) {
28 | if(spark.debug) console.log(`Event '${event}' was intercepted.`);
29 | return; // 如果拦截器返回 false,则中断事件触发
30 | }
31 | }
32 | }
33 |
34 | // 如果没有被拦截,则正常触发事件
35 | this.emit(event, ...args);
36 | }
37 | }
38 |
39 |
40 | class Qadapter {
41 | client;
42 | target;
43 | qid;
44 | pwd;
45 | ws_type;
46 | port;
47 | eventEmitter = new CustomTrigger();
48 | //eventKeyMap = new Map();
49 | logger = logger.getLogger('Qadapter')
50 | constructor(ws_type,target,port, qid, pwd, customs) {
51 | this.target = target;
52 | this.qid = qid;
53 | this.pwd = pwd;
54 | this.ws_type = ws_type;
55 | this.port = port;
56 | this.customs = customs;
57 | }
58 | login() {
59 | //console.log(this.pwd);
60 | if(this.ws_type == 0){
61 | this.client = new WebSocket(this.target, { headers: { Authorization: 'Bearer ' + this.pwd } });//
62 | this.client.on('open', () => {
63 | this.logger.info('登录成功,开始处理事件');
64 | this.eventEmitter.emit('bot.online');
65 | });
66 | this.client.on('error', (e) => {
67 | this.logger.error('websocket 故障!!');
68 | this.logger.error('请检查连接到go-cqhttp的密钥是否填写正确');
69 | console.log(e);
70 | });
71 | this.client.on('close', (e) => {
72 | let waitTime = boom();
73 | this.logger.warn(`websocket 已经断开,将在 ${(new Date(Date.now() + waitTime)).toLocaleString()} 尝试重连`);
74 | setTimeout(() => {
75 | this.login()
76 | }, waitTime);
77 | });
78 | this.client.on('message', (_data, _islib) => {
79 | let raw = _data;
80 | if (_islib) {
81 | raw = _data.toString()
82 | }
83 | let msg_obj;
84 | try {
85 | msg_obj = JSON.parse(raw);
86 | } catch (err) {
87 | this.logger.error('解析消息出现错误!');
88 | console.log(err);
89 | }
90 | this.eventEmitter.emit('gocq.pack', msg_obj);
91 | })
92 | }
93 | else if(this.ws_type == 1){
94 | this.client = new WebSocket.Server({ port:this.port});
95 |
96 |
97 | this.logger.info('Websocket 服务器 于 ws://localhost:'+this.port+" 开启");
98 |
99 | // 监听连接事件
100 | this.client.on('connection', (ws,req) => {
101 | this.logger.info(`${req.headers.host}:${req.headers.port} 发起连接`);
102 |
103 | // 打印客户端的请求头信息
104 | if(spark.debug) console.log('Client request headers:', req.headers);
105 |
106 | if(req.headers.authorization != `Bearer ${this.pwd}` && spark.debug == false){
107 | this.logger.info('Client 未提供正确的授权头');
108 | ws.send('Client 未提供正确的授权头');
109 | ws.close();
110 | return;
111 | }
112 |
113 | if (req.headers['x-self-id'] == this.qid){
114 | this.logger.info('WebSocket 服务器 已接收到来自 '+"["+this.qid+"]"+' 的连接');
115 | this.eventEmitter.emit('bot.online');
116 | }
117 |
118 | // 监听客户端消息事件
119 | ws.on('message', (message) => {
120 | // console.log(`Received message: ${message}`);
121 | // 向客户端发送响应
122 | // ws.send(`Server received: ${message}`);
123 | let msg_obj;
124 | try {
125 | msg_obj = JSON.parse(message);
126 | } catch (err) {
127 | this.logger.error('解析消息出现错误!');
128 | console.log(err);
129 | }
130 | this.eventEmitter.emit('gocq.pack', msg_obj);
131 | });
132 |
133 | // 监听错误事件
134 | ws.on('error', (error) => {
135 | this.logger.error('WebSocket error:', error);
136 | });
137 |
138 | // 监听关闭事件
139 | ws.on('close', () => {
140 | this.logger.info('Client disconnected');
141 | });
142 |
143 | });
144 |
145 | // 监听服务器错误事件
146 | this.client.on('error', (error) => {
147 | this.logger.error('websocket 故障!!');
148 | this.logger.error('请检查连接到go-cqhttp的密钥是否填写正确');
149 | console.error('Server error:', error);
150 | });
151 |
152 | // 监听服务器关闭事件
153 | this.client.on('close', () => {
154 | console.log('WebSocket server closed');
155 | });
156 | }
157 |
158 | }
159 | on(evk, func) {
160 | if (spark.debug) console.log('触发on', evk);
161 | this.eventEmitter.on(evk, func);
162 |
163 | // 初始化spark.plugins_list
164 | //spark.plugins_list = spark.plugins_list || [];
165 |
166 | // 设置最大监听器数量
167 | // PS:Nodejs中,监听器数量默认限制为10,否则会警告。设置为0才是无限个
168 |
169 | this.eventEmitter.setMaxListeners(0);
170 |
171 | }
172 |
173 | emit(evk, ...arg) {
174 | if (spark.debug) console.log('触发emit', evk);
175 | /* if(this.eventKeyMap.has(evk)){
176 | this.eventKeyMap.get(evk).forEach(element => {
177 | element(...arg);
178 | });
179 | }*/
180 | this.eventEmitter.trigger(evk, ...arg);
181 | }
182 |
183 | addInterceptor(evt,interceptor){
184 | if (spark.debug) console.log('触发addInterceptor');
185 | this.eventEmitter.addInterceptor(evt,interceptor);
186 | }
187 |
188 | setOwnProperty(k, v) {
189 | if (spark.debug) console.log('挂载 ——> ' + k);
190 | this[k] = v;
191 | }
192 | sendWSPack(pack) {
193 | if (typeof pack !== 'string') {
194 | pack = JSON.stringify(pack);
195 | }
196 | if (spark.debug) console.log("send-->", pack);
197 | if(this.ws_type == 0){
198 | this.client.send(pack);
199 | }else if(this.ws_type == 1){
200 | this.client.clients.forEach((client) => {
201 | if (client.readyState === WebSocket.OPEN) {
202 | client.send(pack);
203 | }
204 | });
205 | }
206 |
207 | }
208 | }
209 |
210 |
211 | module.exports = Qadapter;
212 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SparkBridge2
2 |
3 | > ✨向BDS推出的使用Onebot标准的机器人框架✨
4 |
5 | - Nodejs提供强力支持
6 | - Onebot框架,兼容广泛
7 | - 同时支持String,array工作方式
8 | - NilBridge原班人马为您带来全新的互通机器人方案。
9 | - 支持内核分离方案,轻松二次开发。
10 |
11 |
12 | [开始查阅](https://sparkbridge.cn)
13 |
14 | [Minebbs链接](https://www.minebbs.com/resources/sparkbridge-bot-qq.5480/)
--------------------------------------------------------------------------------
/handles/chat_pal.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | SpakrBridge WebSocket调试器
8 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 | 发送
203 |
204 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
×
242 |
这是一条消息
243 |
244 |
245 |
246 |
443 |
444 |
445 |
--------------------------------------------------------------------------------
/handles/fakeapi.js:
--------------------------------------------------------------------------------
1 | global.mc = {};
2 | global.ll = {};
3 |
4 | ll.exports = (fnc,name) =>{
5 | console.log('共享:'+name +'=>'+ fnc.name);
6 | }
7 |
8 | mc.listen = (evk, func) => {
9 | console.log('监听:' + evk);
10 | }
11 |
12 | mc.runcmd = (cmd) => {
13 | console.log('执行:' + cmd);
14 | }
15 |
16 | mc.runcmdEx = (cmd) => {
17 | console.log('执行:' + cmd);
18 | return { output: '执行成功:fakeapi completed', success: true }
19 | }
20 |
21 |
22 | mc.broadcast = (msg) => {
23 | console.log('广播:' + msg);
24 | }
--------------------------------------------------------------------------------
/handles/file.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | var PLUGIN_DATA_DIR;
3 | if (typeof mc !== 'undefined') {
4 | if (!exists('./plugins/LeviLamina')) {
5 | PLUGIN_DATA_DIR = './plugins/nodejs/sparkbridge2/serverdata'
6 | }
7 | else {
8 | PLUGIN_DATA_DIR = './plugins/sparkbridge2/serverdata'
9 | }
10 | } else {
11 | PLUGIN_DATA_DIR = './testdata'
12 | }
13 |
14 |
15 | const JSON5 = require('json5');
16 | class FileObj {
17 | pname;
18 | constructor(plugin_name) {
19 | this.pname = plugin_name;
20 | if (exists(PLUGIN_DATA_DIR + '/' + this.pname) == false) {
21 | mkdir(PLUGIN_DATA_DIR + '/' + this.pname);
22 | }
23 | }
24 | initFile(fname, init_obj, autoUpdate = true) {
25 |
26 | let filePath = PLUGIN_DATA_DIR + '/' + this.pname + '/' + fname;
27 |
28 | // 检查文件是否存在
29 | if (!exists(filePath)) {
30 | // 文件不存在,直接创建并写入初始对象
31 | writeTo(filePath, JSON.stringify(init_obj, null, 4));
32 | } else {
33 | if (!autoUpdate) {
34 | return
35 | }
36 | // 文件存在,读取内容
37 | const existingData = JSON.parse(read(filePath));
38 |
39 | // 遍历初始对象,检查现存文件中的项是否缺失
40 | let updated = false;
41 | for (const key in init_obj) {
42 | if (!(key in existingData)) {
43 | existingData[key] = init_obj[key]; // 补全缺失项
44 | updated = true;
45 | }
46 | }
47 |
48 | // 如果有更新,重新写入文件
49 | if (updated) {
50 | writeTo(filePath, JSON.stringify(existingData, null, 4));
51 | }
52 | }
53 | }
54 |
55 | getFile(fname) {
56 | if (exists(PLUGIN_DATA_DIR + '/' + this.pname) == false) {
57 | return null;
58 | } else {
59 | return read(PLUGIN_DATA_DIR + '/' + this.pname + '/' + fname)
60 | }
61 | }
62 | getBuffer(fname) {
63 | if (exists(PLUGIN_DATA_DIR + '/' + this.pname) == false) {
64 | return null;
65 | } else {
66 | return fs.readFileSync(PLUGIN_DATA_DIR + '/' + this.pname + '/' + fname)
67 | }
68 | }
69 | updateFile(fname, data_obj, json = JSON) {
70 | writeTo(PLUGIN_DATA_DIR + '/' + this.pname + '/' + fname, json.stringify(data_obj, null, 4));
71 | }
72 | }
73 |
74 |
75 |
76 | function exists(pt) {
77 | try {
78 | return fs.existsSync(pt);
79 | } catch (e) { console.log(e) }
80 | }
81 |
82 | function writeTo(pt, raw) {
83 | try {
84 | fs.writeFileSync(pt, raw, { encoding: 'utf-8' });
85 | } catch (e) { console.log(e) }
86 | }
87 |
88 | function mkdir(pt) {
89 | try {
90 | fs.mkdirSync(pt);
91 | } catch (e) { console.log(e) }
92 | }
93 |
94 | function copy(pt, npt) {
95 | try {
96 | fs.copyFileSync(pt, npt);
97 | } catch (e) { console.log(e) }
98 | }
99 |
100 | function read(pt) {
101 | try {
102 | return fs.readFileSync(pt, { encoding: 'utf-8' });
103 | } catch (e) { console.log(e) }
104 | }
105 |
106 | function listdir(pt) {
107 | try {
108 | return fs.readdirSync(pt);
109 | } catch (e) { console.log(e) }
110 | }
111 |
112 | module.exports = {
113 | exists, writeTo, mkdir, copy, read, listdir, FileObj
114 | }
--------------------------------------------------------------------------------
/handles/logger.js:
--------------------------------------------------------------------------------
1 | const { createLogger, format, transports } = require('winston');
2 | const { combine, timestamp, label, printf } = format;
3 | const dayjs = require('dayjs');
4 | const today = dayjs();
5 |
6 | const myFormat = printf(({ level, message, label}) => {
7 | return `${dayjs().format("YYYY-MM-DD HH:mm:ss")} [${label}] [${ level}] ${message}`;
8 | });
9 |
10 | function SparkLogger(plugin_name){
11 | return createLogger({
12 | format: combine(
13 | label({ label: plugin_name}),
14 | myFormat
15 | ),
16 | transports: [
17 | new transports.Console()
18 | //new winston.transports.File({ filename: `./plugins/sparkbridge2/logs/${today.format("YYYY-MM-DD")}-${}.log` })
19 | ]
20 | });
21 | }
22 | module.exports ={ getLogger:SparkLogger};
--------------------------------------------------------------------------------
/handles/msgbuilder.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const fhelper = require('./file');
3 |
4 | class ForwardMsgBuilder {
5 | msg = [];
6 | addMsgById(id) {
7 | this.msg.push({
8 | type: 'node',
9 | data: {
10 | id
11 | }
12 | });
13 | }
14 | addCustomsMsg(name, uin, content) {
15 | this.msg.push({
16 | type: 'node',
17 | data: {
18 | name,
19 | uin,
20 | content
21 | }
22 | });
23 | }
24 | getMsg() {
25 | return this.msg;
26 | }
27 | }
28 |
29 | class Builder {
30 | static img(file) {
31 | if (typeof file === 'string' && fhelper.exists(file)) file = fs.readFileSync(file);
32 | if (file instanceof Buffer) file = `base64://${file.toString('base64')}`;
33 | return { type: 'image', data: { file: file, subType: 0 } };
34 | }
35 | static at(qid) {
36 | qid = qid.toString();
37 | return { type: "at", data: { "qq": qid } };
38 | }
39 | static face(id) {
40 | id = id.toString();
41 | return { type: 'face', data: { id } };
42 | }
43 | static text(raw) {
44 | return { type: 'text', data: { text: raw } };
45 | }
46 | static poke(id) {
47 | id = id.toString();
48 | return { type: 'poke', data: { qq: id } };
49 | }
50 | static video(file) {
51 | file = file.toString();
52 | return { type: 'video', data: { file: file } };
53 | }
54 | static record(file) {
55 | file = file.toString();
56 | return { type: "record", data: { file: file } };
57 | }
58 | static reply(id) {
59 | id = id.toString();
60 | return { type: 'reply', data: { id } };
61 | }
62 | static format(msg) {
63 | if (Array.isArray(msg) == false) {
64 | msg = [msg];
65 | }
66 | for (let index in msg) {
67 | var imsg = msg[index];
68 | if (typeof imsg == 'string') {
69 | msg[index] = Builder.text(imsg);
70 | }
71 | }
72 | if (spark.debug) console.log('build msg -->' + JSON.stringify(msg));
73 | return msg;
74 | }
75 | static ForwardMsgBuilder() {
76 | return new ForwardMsgBuilder();
77 | }
78 | }
79 |
80 |
81 | module.exports = Builder;
82 |
--------------------------------------------------------------------------------
/handles/neoWebEditable.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 正则表达式 数据可视化 编辑器
8 |
260 |
261 |
262 |
263 | 正则表达式 数据可视化 编辑器
264 |
265 |
266 | 配置部分1
267 |
268 |
269 |
270 | 配置名称
271 | 描述
272 | 可编辑项
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 | 配置部分2
281 |
282 |
283 |
284 | 配置名称
285 | 描述
286 | 可编辑项
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
×
300 |
编辑数组
301 |
302 |
303 | 添加
304 | 保存
305 |
306 |
307 |
308 |
309 |
501 |
502 |
503 |
--------------------------------------------------------------------------------
/handles/packbuilder.js:
--------------------------------------------------------------------------------
1 |
2 | class Builder {
3 | static MessagePack(id, type, msg, pid) {
4 | let sendjson = {
5 | action: 'send_msg',
6 | echo: pid,
7 | params: {
8 | user_id: id,
9 | message: msg,
10 | message_type: type
11 | }
12 | }
13 | if (type == 'group') {
14 | sendjson.params.group_id = id
15 | }
16 | return sendjson
17 | }
18 | static PrivateMessagePack(fid, msg, id) {
19 | return this.MessagePack(fid, 'private', msg, id);
20 | }
21 |
22 | static GroupMessagePack(gid, msg, id) {
23 | return this.MessagePack(gid, 'group', msg, id);
24 | }
25 | static SendGroupMessagePack(gid, msg, escape = false) {
26 | return {
27 | action: 'send_group_msg',
28 | params: {
29 | group_id: gid,
30 | message: msg,
31 | auto_escape: escape
32 | }
33 | }
34 | }
35 | static SandGroupMessagePack(gid, msg, escape = false) {
36 | return SendGroupMessagePack(gid, msg, escape)
37 | //由铭记制造的历史遗留问题
38 | }
39 | static LikePack(fid, times) {
40 | return {
41 | action: 'send_like',
42 | params: {
43 | user_id: fid,
44 | times
45 | }
46 | }
47 | }
48 |
49 | static DeleteMsgPack(id) {
50 | return {
51 | action: 'delete_msg',
52 | params: {
53 | message_id: id
54 | }
55 | }
56 | }
57 | static GetMsgPack(mid, id) {
58 | return {
59 | action: 'get_msg',
60 | echo: id,
61 | params: {
62 | message_id: mid,
63 | }
64 | }
65 | }
66 | static GroupBanPack(gid, mid, duration) {
67 | return {
68 | action: 'set_group_ban',
69 | params: {
70 | group_id: gid,
71 | user_id: mid,
72 | duration
73 | }
74 | }
75 | }
76 | static GroupRequestPack(flag, sub_type, approve,reason) {
77 | return {
78 | action: 'set_group_add_request',
79 | params: {
80 | flag,
81 | sub_type,
82 | approve,
83 | reason
84 | }
85 | }
86 | }
87 | static FriendRequestPack(flag, approve) {
88 | return {
89 | action: 'set_friend_add_request',
90 | params: {
91 | flag,
92 | approve
93 | }
94 | }
95 | }
96 | static GroupMemberListPack(gid, id) {
97 | return {
98 | action: 'get_group_member_list',
99 | echo: id,
100 | params: {
101 | group_id: gid
102 | }
103 | }
104 | }
105 | static GroupMemberInfoPack(gid, mid, id) {
106 | return {
107 | action: 'get_group_member_info',
108 | echo: id,
109 | params: {
110 | group_id: gid,
111 | user_id: mid
112 | }
113 | }
114 | }
115 | static GroupForwardMessagePack(gid, msg, id) {
116 | return {
117 | action: 'send_group_forward_msg',
118 | echo: id,
119 | params: {
120 | group_id: gid,
121 | messages: msg
122 | }
123 | }
124 | }
125 | static GroupCardSet(gid, mid, card) {
126 | return {
127 | action: 'set_group_card',
128 | params: {
129 | group_id: gid,
130 | user_id: mid,
131 | card: card
132 | }
133 | }
134 | }
135 | static GroupKickPack(gid, mid, rej) {
136 | return {
137 | action: 'set_group_kick',
138 | params: {
139 | group_id: gid,
140 | user_id: mid,
141 | reject_add_request: rej
142 | }
143 | }
144 | }
145 | static GroupLeavePack(gid, dismiss) {
146 | return {
147 | action: 'set_group_leave',
148 | params: {
149 | group_id: gid,
150 | is_dismiss: dismiss
151 | }
152 | }
153 | }
154 | static GroupWholeBanPack(gid, enable) {
155 | return {
156 | action: 'set_group_whole_ban',
157 | params: {
158 | group_id: gid,
159 | enable
160 | }
161 | }
162 | }
163 | static GroupNamePack(gid, name) {
164 | return {
165 | action: 'set_group_name',
166 | params: {
167 | group_id: gid,
168 | group_name: name
169 | }
170 | }
171 | }
172 | static StrangerInfoPack(sid, no_cache, id) {
173 | return {
174 | action: 'get_stranger_info',
175 | echo: id,
176 | params: {
177 | user_id: sid,
178 | no_cache
179 | }
180 | }
181 | }
182 | static FriendInfoPack(fid, no_cache, id) {
183 | return {
184 | action: 'get_friend_info',
185 | echo: id,
186 | params: {
187 | user_id: fid,
188 | no_cache
189 | }
190 | }
191 | }
192 | static GroupInfoPack(gid, no_cache, id) {
193 | return {
194 | action: 'get_group_info',
195 | echo: id,
196 | params: {
197 | group_id: gid,
198 | no_cache
199 | }
200 | }
201 | }
202 | static FriendListPack(id) {
203 | return {
204 | action: 'get_friend_list',
205 | echo: id
206 | }
207 | }
208 | static GroupListPack(id) {
209 | return {
210 | action: 'get_group_list',
211 | echo: id
212 | }
213 | }
214 | static GroupHonorInfoPack(gid, type, id) {
215 | return {
216 | action: 'get_group_honor_info',
217 | echo: id,
218 | params: {
219 | user_id: gid,
220 | type
221 | }
222 | }
223 | }
224 | static StatusPack(id) {
225 | return {
226 | action: 'get_status',
227 | echo: id
228 | }
229 | }
230 | static GroupRootFilesPack(gid, id) {
231 | return {
232 | action: "get_group_root_files",
233 | echo: id,
234 | params: {
235 | group_id: gid, // 群号
236 | }
237 | }
238 | }
239 | static UploadGroupFilePack(gid, FileName, AsName, FolderID) {
240 | return {
241 | action: "upload_group_file",
242 | params: {
243 | group_id: gid, // 群号
244 | file: FileName, // 本地文件路径
245 | name: AsName, // 储存名称
246 | folder: FolderID // 不提供父目录ID,默认上传到根目录
247 | }
248 | };
249 | }
250 | }
251 |
252 |
253 | module.exports = Builder;
254 |
--------------------------------------------------------------------------------
/handles/parserCQString.js:
--------------------------------------------------------------------------------
1 | //var CQstr = '[CQ:reply,text=Hello World,qq=10086,time=3376656000,seq=5123]';
2 |
3 | function parseCode(codeStr) {
4 | let data = { type: "", data: {} }
5 | let code = codeStr.slice(0).slice(1, -1).split(',');
6 | let index = 1;
7 | code.forEach(element => {
8 | if (index === 1) {
9 | data.type = element.split(':')[1];
10 | } else {
11 | data.data[element.split('=')[0]] = element.split('=')[1];
12 | }
13 | index++;
14 | });
15 | return data;
16 | }
17 |
18 | function parseCQString(codeStr) {
19 | let inCode = false;
20 |
21 | let currentCode = '';
22 | let currentText = '';
23 |
24 | let totalCode = [];
25 | for (let i in codeStr) {
26 | let currentChar = codeStr[i];
27 | if (currentChar === '[') {
28 | inCode = true;
29 | if (currentText !== '') {
30 | totalCode.push({ type: "text", data: { text: currentText } });
31 | currentText = '';
32 | }
33 | }
34 | if (inCode) {
35 | currentCode += currentChar;
36 | if (currentChar === ']' && inCode) {
37 | inCode = false;
38 | totalCode.push(parseCode(currentCode));
39 | currentCode = '';
40 | }
41 | } else {
42 | currentText += currentChar;
43 | }
44 | }
45 |
46 | if (currentText !== '') {
47 | totalCode.push({ type: "text", data: { text: currentText } });
48 | }
49 | if (spark.debug) console.log('parsing -->', codeStr, 'result -->', totalCode);
50 | return totalCode;
51 | }
52 |
53 |
54 | module.exports = {
55 | parseCode,
56 | parseCQString
57 | }
--------------------------------------------------------------------------------
/handles/plugin_create.js:
--------------------------------------------------------------------------------
1 | // ./handles/createPlugin.js
2 | const fs = require('fs');
3 | const path = require('path');
4 |
5 | // 获取用户输入的第一个参数(插件名称)
6 | const pluginName = process.argv[2];
7 |
8 | if (!pluginName) {
9 | console.error('请提供插件名称作为参数!');
10 | process.exit(1);
11 | }
12 |
13 | // 目标路径:./plugins/[用户输入的参数]
14 | const pluginDir = path.join(__dirname, '../plugins', pluginName);
15 | const sparkJsonPath = path.join(pluginDir, 'spark.json');
16 |
17 | // 创建插件目录(如果不存在)
18 | if (!fs.existsSync(pluginDir)) {
19 | fs.mkdirSync(pluginDir, { recursive: true });
20 | console.log(`目录已创建:${pluginDir}`);
21 | }
22 |
23 | // 写入 spark.json 文件
24 | const sparkJsonContent = {
25 | author:"noman",
26 | name: pluginName,
27 | version: [0,0,1],
28 | description: "由生成器生成的模板描述",
29 | loadmode: "hybrid",
30 | permission: "key",
31 | priority: "post",
32 | load: true
33 | };
34 |
35 | fs.writeFileSync(
36 | sparkJsonPath,
37 | JSON.stringify(sparkJsonContent, null, 2) // 格式化缩进
38 | );
39 |
40 | console.log(`spark.json 已生成:${sparkJsonPath}`);
--------------------------------------------------------------------------------
/handles/reconnect.js:
--------------------------------------------------------------------------------
1 | var count = 1;
2 | var sleep_t = 5;
3 |
4 | // 前面10次延迟1秒,然后十次延迟10秒,30秒,1分钟,5分钟,10,分钟
5 |
6 | function boom() {
7 | if (count < 11)
8 | count++;
9 | else
10 | count = 1;
11 | awa();
12 | return sleep_t * 1e3;
13 | }
14 |
15 | function awa() {
16 | if (sleep_t < 300) {
17 | sleep_t = sleep_t * 2;
18 | if (sleep_t > 300) {
19 | sleep_t = 300;
20 | }
21 | }
22 | }
23 | module.exports = {
24 | boom,
25 | awa
26 | }
--------------------------------------------------------------------------------
/handles/regex_edi.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 正则表达式 数据可视化 编辑器
8 |
184 |
185 |
186 |
187 | 正则表达式 数据可视化 编辑器
188 |
189 | 导入 JSON 文件
190 | 导出 JSON 文件
191 |
192 |
193 |
194 |
195 |
196 |
197 | 正则表达式
198 | 命令
199 | 管理员权限
200 | 操作
201 |
202 |
203 |
204 |
205 |
206 |
207 | 添加新规则
208 |
209 | 正则表达式
210 |
211 |
212 |
213 | 命令
214 |
215 |
216 |
217 | 管理员权限
218 |
219 |
220 |
221 |
222 |
223 | 添加规则
224 |
225 |
228 |
229 |
230 |
231 |
232 |
364 |
365 |
366 |
--------------------------------------------------------------------------------
/handles/ws_con.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | SpakrBridge WebSocket 调试台
8 |
171 |
172 |
173 |
174 |
175 |
SpakrBridge WebSocket 调试台
176 |
177 | WebSocket URL
178 |
179 |
180 |
181 | 连接
182 |
183 |
184 | 消息模板
185 |
186 |
187 |
188 |
189 |
190 |
191 | 发送
192 |
193 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
206 |
×
207 |
208 |
209 |
210 |
367 |
368 |
369 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const ME = require('./package.json');
2 | const JSON5 = require('json5');
3 | const fs = require('fs');
4 | const path = require('path');
5 | const fhelper = require('./handles/file');
6 | const lg = require('./handles/logger');
7 | const Spark = require("./spark");
8 | const PLUGIN_ROOT_DIR = './plugins/nodejs/sparkbridge2';
9 | const logger = lg.getLogger('sparkbridge2');
10 | const vm = require("node:vm");
11 | process.on('unhandledRejection', (reason, promise) => {
12 |
13 | });
14 | var PLUGIN_DATA_DIR;
15 |
16 | if (typeof mc !== 'undefined') {
17 | if (!fhelper.exists('./plugins/LeviLamina')) {
18 | PLUGIN_DATA_DIR = './plugins/nodejs/sparkbridge2/serverdata'
19 |
20 | }
21 | else {
22 | PLUGIN_DATA_DIR = './plugins/sparkbridge2/serverdata'
23 | }
24 | } else {
25 | PLUGIN_DATA_DIR = './testdata'
26 | }
27 |
28 |
29 |
30 | if (fhelper.exists(PLUGIN_DATA_DIR) == false) fhelper.mkdir(PLUGIN_DATA_DIR);
31 | console.log(fhelper.read(path.join(__dirname, 'logo.txt')));
32 |
33 | let ROOT_FILE_HELPER = new fhelper.FileObj('base');
34 | ROOT_FILE_HELPER.initFile('config.json', {
35 | target: "ws://127.0.0.1:3001",
36 | qid: 114514, pwd: '',
37 | onebot_mode_v11: true,
38 | ws_type: 0,
39 | server_port: 3001,
40 | debug: false
41 | }
42 | );
43 | let RAW_CONFIG = ROOT_FILE_HELPER.getFile('config.json');
44 | const CONFIG = JSON5.parse(RAW_CONFIG);
45 |
46 | global.spark = new Spark(CONFIG.ws_type, CONFIG.target, CONFIG.server_port, CONFIG.qid, CONFIG.pwd);
47 |
48 | spark.on("event.telemetry.ready", () => {
49 | const WebConfigBuilder = spark.telemetry.WebConfigBuilder;
50 | let wbc = new WebConfigBuilder("base");
51 | wbc.addText("target", CONFIG.target, "连接地址");
52 | wbc.addNumber("qid", CONFIG.qid, 'QQ号码');
53 | wbc.addChoosing("ws_type", ["正向WS", "反向WS"], CONFIG.ws_type, "websocket类型");
54 | wbc.addNumber("server_port", CONFIG.server_port, 'Websocket Server端口');
55 | wbc.addText("pwd", CONFIG.pwd, "连接密码(Access Token)");
56 | wbc.addSwitch('onebot_mode_v11', CONFIG.onebot_mode_v11, "是否使用onebot适配器");
57 | wbc.addSwitch("debug", CONFIG.debug, "开发者模式");
58 | //wbc.addChoosing("pl_priority_top", ["完全隔离", "可访问关键部件", '可访问内核'], CONFIG.pl_priority_top, "插件最高权限");
59 | spark.emit("event.telemetry.pushconfig", wbc);
60 | });
61 |
62 | spark.on("event.telemetry.updateconfig_base", (plname, K, newV) => {
63 | logger.info(`收到配置文件[${K}]更改请求,此项无法热重载, 请重启服务器`);
64 | CONFIG[K] = newV;
65 | ROOT_FILE_HELPER.updateFile('config.json', CONFIG);
66 | if (K == "debug") {
67 | spark.debug = newV;
68 | logger.info(`开发者模式已` + (newV == true ? "开启" : "关闭"));
69 | } else {
70 |
71 | }
72 | });
73 |
74 | const PermissionMap = {
75 | nor: 0,
76 | key: 1,
77 | core: 2
78 | }
79 |
80 | function ModuleLoaderInit(plugins_list) {
81 | const WebConfigBuilder = spark.telemetry.WebConfigBuilder;
82 | let wbc = new WebConfigBuilder("ModuleLoader");
83 | plugins_list.forEach(v => {
84 | let pl_info = require('./plugins/' + v + "/spark.json");
85 | wbc.addChoosing(`${v}_permission`, ["完全隔离", "可访问关键部件", '可访问内核'], PermissionMap[pl_info.permission], '插件最高权限');
86 | })
87 | spark.emit("event.telemetry.pushconfig", wbc);
88 |
89 | spark.on("event.telemetry.updateconfig_ModuleLoader", (plname, K, newV) => {
90 | let tgtname = K.split("_")[0];
91 | if (Object.keys(spark.plugins_list).includes(tgtname)) {
92 | let pkpath = path.join(__dirname, 'plugins', spark.plugins_list[tgtname].folder, 'spark.json');
93 | try {
94 | // 确保文件路径是绝对路径
95 | const absolutePath = path.resolve(pkpath);
96 | // 读取文件内容
97 | const data = fs.readFileSync(absolutePath, 'utf8');
98 | // 解析 JSON 数据
99 | let jsonData = JSON.parse(data);
100 | let pm_arr = ['nor', 'key', 'core'];
101 | jsonData.permission = pm_arr[newV];
102 | // 将修改后的 JSON 数据写回到文件
103 | fs.writeFileSync(absolutePath, JSON.stringify(jsonData, null, 2));
104 | // console.log(`文件已成功修改并保存:${absolutePath}`);
105 | } catch (error) {
106 | console.error(`处理文件时出错:${error}`);
107 | }
108 | }
109 | });
110 | }
111 |
112 | function writeFilePermission(plugin_dir, permission) {
113 | try {
114 | // 确保文件路径是绝对路径
115 | let kpath = path.join(__dirname, 'plugins', plugin_dir, 'spark.json');
116 | // 修改权限字段
117 | let jsonData = JSON.parse(fs.readFileSync(kpath, 'utf8'));
118 | jsonData.permission = permission;
119 |
120 | // 将修改后的 JSON 数据写回到文件
121 | fs.writeFileSync(kpath, JSON.stringify(jsonData, null, 2));
122 | console.log(`文件已成功修改并保存:${path}`);
123 | } catch (error) {
124 | console.error(`处理文件时出错:${error}`);
125 | }
126 | }
127 |
128 | function writeFilePriority(plugin_dir, priority) {
129 | try {
130 | // 确保文件路径是绝对路径
131 | let kpath = path.join(__dirname, 'plugins', plugin_dir, 'spark.json');
132 | // 修改权限字段
133 | let jsonData = JSON.parse(fs.readFileSync(kpath, 'utf8'));
134 | jsonData.priority = priority;
135 |
136 | // 将修改后的 JSON 数据写回到文件
137 | fs.writeFileSync(kpath, JSON.stringify(jsonData, null, 2));
138 | console.log(`文件已成功修改并保存:${path}`);
139 | } catch (error) {
140 | console.error(`处理文件时出错:${error}`);
141 | }
142 | }
143 |
144 |
145 | logger.info('SparkBridge载入中...VERSION:' + ME.version);
146 | spark.VERSION = ME.VERSION;
147 |
148 | if (typeof mc !== 'undefined') {
149 | spark.onBDS = true;
150 | } else {
151 | spark.onBDS = false;
152 | }
153 |
154 | const PLUGINS_PATH = path.join(__dirname, 'plugins');
155 | const plugins_list = fhelper.listdir(PLUGINS_PATH);
156 |
157 | function VMrunCodeFromDir(perm, dirPath) {
158 | try {
159 | // 尝试读取 package.json 文件
160 | const packageJsonPath = path.join(__dirname, dirPath, 'package.json');
161 | // console.log(`正在尝试从目录 ${dirPath} 运行代码...`);
162 | let mainFile = 'index.js'; // 默认入口文件
163 | if (fs.existsSync(packageJsonPath)) {
164 | const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
165 | mainFile = packageJson.main || 'index.js'; // 如果有 package.json,则使用其指定的入口文件
166 | }
167 |
168 | // 获取入口文件的完整路径
169 | const filePath = path.join(__dirname, dirPath, mainFile);
170 |
171 | // 读取入口文件内容
172 | const code = fs.readFileSync(filePath, 'utf8');
173 |
174 | let context = {};
175 |
176 | if (perm == 'key') {
177 | // key permission
178 | context = {
179 | console,
180 | setInterval,
181 | setTimeout,
182 | clearInterval,
183 | clearTimeout,
184 | spark,
185 | ll,
186 | mc,
187 | require: (moduleName) => {
188 | // 解析模块路径
189 | const resolvedPath = require.resolve(moduleName, { paths: [path.join(__dirname, dirPath)] });
190 | return require(resolvedPath);
191 | },
192 | };
193 | context.ll.import = ll.imports;
194 | context.ll.exports = ll.exports;
195 | } else {
196 | // normal permission
197 | context = {
198 | console,
199 | setInterval,
200 | setTimeout,
201 | clearInterval,
202 | clearTimeout,
203 | spark
204 | };
205 | }
206 |
207 | // 创建一个 Script 对象
208 | let script = new vm.Script(code);
209 |
210 | // 在上下文中运行代码
211 | script.runInNewContext(context);
212 |
213 | logger.info(`插件 ${path.basename(dirPath)} 已成功从虚拟环境加载,入口文件:${mainFile}`);
214 |
215 | } catch (error) {
216 | console.error(`读取或执行插件 ${dirPath} 时出错:${error}`);
217 | }
218 | }
219 |
220 | // 优先级映射表
221 | const priorityMap = {
222 | post: 0, // 普通
223 | main: 1, // 关键
224 | init: 2, // 核心
225 | base: 3
226 | };
227 |
228 | function loadPlugin(_name) {
229 | try {
230 | // 读取 spark.json 文件
231 | let pl_info = require('./plugins/' + _name + "/spark.json");
232 |
233 | if (pl_info.loadmode) {
234 | if (pl_info.loadmode !== 'hybrid') {
235 | if (pl_info.loadmode == 'offline' && spark.onBDS) {
236 | logger.info(`忽略 ${pl_info.name},因在BDS模式下无法使用`);
237 | return;
238 | }
239 | if (pl_info.loadmode == 'bds' && !spark.onBDS) {
240 | logger.info(`忽略 ${pl_info.name},因插件强制在BDS模式下使用`);
241 | return;
242 | }
243 | }
244 | }
245 |
246 | if (pl_info.load) {
247 | if (pl_info.permission == 'core') {
248 | let pl_obj = require('./plugins/' + _name);
249 | } else {
250 | VMrunCodeFromDir(pl_info.permission, './plugins/' + _name)
251 | }
252 | // logger.info(`加载 ${pl_info.name}`);
253 | logger.info(`${pl_info.name} 加载完成,作者:${pl_info.author}`);
254 | } else {
255 | logger.info('跳过加载插件:' + _name);
256 | }
257 | } catch (err) {
258 | console.log(err);
259 | logger.error(`插件 ${_name} 加载失败`);
260 | }
261 | }
262 |
263 | function readPluginDir() {
264 | // 遍历plugins文件夹,找到list.json,按照list.json的顺序加载插件
265 | // 记录当前插件列表,如果在旧的中没有就新增
266 |
267 | // 这里获取旧插件list
268 | const plugins_load_list = JSON.parse(fhelper.read(path.join(__dirname, 'plugins', 'list.json')));
269 | // 这里遍历 plugins文件夹,读取spark.json
270 | const current_list = {};
271 | const plugins = fs.readdirSync(path.join(__dirname, 'plugins'));
272 | plugins.forEach(epl => {
273 | const sata = fs.statSync(path.join(__dirname, 'plugins', epl));
274 | if (!sata.isDirectory()) return;
275 | logger.info('读取 ' + epl);
276 | let i_info = JSON.parse(fhelper.read(path.join(__dirname, 'plugins', epl, 'spark.json')));
277 | if (!i_info.priority) {
278 | writeFilePriority(epl, 'post');
279 | }
280 | if (!i_info.permission) {
281 | writeFilePermission(epl, 'core');
282 | }
283 | const priorityValue = priorityMap[i_info.priority] || 0;
284 | current_list[i_info.name] = {
285 | name: i_info.name,
286 | folder: epl,
287 | priority: priorityValue
288 | };
289 | });
290 |
291 | // 对比当前插件列表和旧列表,有新增的就加到旧插件列表
292 | for (let i in current_list) {
293 | if (!plugins_load_list.includes(current_list[i].folder)) {
294 | // 新增插件
295 | logger.info('新增插件' + i);
296 | plugins_load_list.push(current_list[i].folder);
297 | }
298 | }
299 |
300 | // 移除不存在的插件
301 | for (let i = plugins_load_list.length - 1; i >= 0; i--) {
302 | const pluginFolder = plugins_load_list[i];
303 | if (!Object.values(current_list).some(plugin => plugin.folder === pluginFolder)) {
304 | logger.info('移除不存在的插件' + pluginFolder);
305 | plugins_load_list.splice(i, 1);
306 | }
307 | }
308 |
309 | logger.info('检测到了' + plugins_load_list.length + '个插件');
310 | bootUpPlugins(plugins_load_list, current_list);
311 | }
312 |
313 |
314 | function bootUpPlugins(plugins_load_list, current_list) {
315 | logger.info('开始加载插件');
316 | try {
317 | if (spark.debug) console.log(plugins_load_list);
318 | if (spark.debug) console.log(current_list);
319 |
320 | // 按照 priority 排序
321 | // pluginInfoList.sort((a, b) => b.priority - a.priority); // 降序排序,优先级高的先加载
322 |
323 | const pluginInfoArray = Object.values(current_list);
324 |
325 | // 按照 priority 降序排序
326 | pluginInfoArray.sort((a, b) => b.priority - a.priority);
327 |
328 | // 将排序后的数组重新转换为对象
329 | const sortedPluginInfoObj = {};
330 | pluginInfoArray.forEach(item => {
331 | sortedPluginInfoObj[item.name] = item;
332 | });
333 |
334 | for (let pl_info in sortedPluginInfoObj) {
335 | const { name, folder, priority } = sortedPluginInfoObj[pl_info];
336 |
337 | // 将优先级数值转换回字符串类型,用于日志输出
338 | const priorityStr = Object.keys(priorityMap).find(key => priorityMap[key] === priority);
339 | logger.info(`加载插件 ${name},优先级为 ${priorityStr}`);
340 | loadPlugin(folder);
341 | }
342 | // 更新 list.json 文件
343 | fhelper.writeTo(path.join(__dirname, 'plugins', 'list.json'), JSON.stringify(plugins_load_list));
344 | if (CONFIG.debug) {
345 | ModuleLoaderInit(plugins_load_list);
346 | }
347 | spark.plugins_list = sortedPluginInfoObj;
348 | } catch (e) {
349 | console.log(e);
350 | }
351 | }
352 |
353 | spark.debug = CONFIG.debug;
354 |
355 | if (spark.onBDS) {
356 |
357 | // 针对 https://github.com/LiteLDev/LegacyScriptEngine/issues/270 的临时解决方案
358 | global.ll.import = ll.imports;
359 | global.ll.exports = ll.exports;
360 | ll.registerPlugin(
361 | /* name */ "sparkbridge2",
362 | /* introduction */ "a qq bot system",
363 | /* version */[2, 6, 0]
364 | );
365 | readPluginDir();
366 | } else {
367 | // spark.debug = true;
368 | console.log('\n');
369 | logger.warn('====本地调试器====');
370 | logger.warn("您现在处于调试模式!!!");
371 | logger.warn("MC类将被覆盖");
372 | logger.warn("数据存储已转移到testdata文件夹");
373 | logger.warn('====本地调试器====\n');
374 | require('./handles/fakeapi');
375 | readPluginDir();
376 | }
377 |
378 | function moduleExists(moduleName) {
379 | try {
380 | // 尝试解析模块路径
381 | require.resolve(moduleName);
382 | return true; // 模块存在
383 | } catch (error) {
384 | return false; // 模块不存在
385 | }
386 | }
387 |
388 | // // 示例用法
389 | // const moduleName = 'vm';
390 | // if (moduleExists(moduleName)) {
391 | // console.log(`模块 ${moduleName} 存在`);
392 | // } else {
393 | // console.log(`模块 ${moduleName} 不存在`);
394 | // }
--------------------------------------------------------------------------------
/llapi.js:
--------------------------------------------------------------------------------
1 | // LiteLoader-AIDS automatic generated
2 | ///
3 |
4 |
5 | // 单独开发请在这里引入你们的 API 地址
6 |
--------------------------------------------------------------------------------
/logo.txt:
--------------------------------------------------------------------------------
1 |
2 | _____ __ ____ _ __ ___
3 | / ___/____ ____ ______/ /__/ __ )_____(_)___/ /___ ____ |__ \
4 | \__ \/ __ \/ __ `/ ___/ //_/ __ / ___/ / __ / __ `/ _ \__/ /
5 | ___/ / /_/ / /_/ / / / ,< / /_/ / / / / /_/ / /_/ / __/ __/
6 | /____/ .___/\__,_/_/ /_/|_/_____/_/ /_/\__,_/\__, /\___/____/
7 | /_/ /____/
8 |
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "entry": "index.js",
3 | "name": "sparkbridge2",
4 | "type": "lse-nodejs",
5 | "version": "2.6.0",
6 | "author": "Sbaoor",
7 | "dependencies": [
8 | {
9 | "name": "legacy-script-engine-nodejs"
10 | }
11 | ],
12 | "optionalDependencies":[
13 | {
14 | "name":"GMLIB"
15 | },
16 | {
17 | "name":"GMLIB-LegacyRemoteCallApi"
18 | }
19 | ]
20 | }
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sparkbridge2",
3 | "version": "2.5.11",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "sparkbridge2",
9 | "version": "2.5.11",
10 | "license": "AGPL-3.0",
11 | "dependencies": {
12 | "axios": "^1.4.0",
13 | "dayjs": "^1.11.8",
14 | "json5": "^2.2.3",
15 | "node-schedule": "^2.1.1",
16 | "pinyin-pro": "^3.26.0",
17 | "vm": "^0.1.0",
18 | "winston": "^3.9.0",
19 | "ws": "^8.13.0"
20 | }
21 | },
22 | "node_modules/@colors/colors": {
23 | "version": "1.5.0",
24 | "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
25 | "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==",
26 | "engines": {
27 | "node": ">=0.1.90"
28 | }
29 | },
30 | "node_modules/@dabh/diagnostics": {
31 | "version": "2.0.3",
32 | "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz",
33 | "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==",
34 | "dependencies": {
35 | "colorspace": "1.1.x",
36 | "enabled": "2.0.x",
37 | "kuler": "^2.0.0"
38 | }
39 | },
40 | "node_modules/@types/triple-beam": {
41 | "version": "1.3.2",
42 | "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.2.tgz",
43 | "integrity": "sha512-txGIh+0eDFzKGC25zORnswy+br1Ha7hj5cMVwKIU7+s0U2AxxJru/jZSMU6OC9MJWP6+pc/hc6ZjyZShpsyY2g=="
44 | },
45 | "node_modules/async": {
46 | "version": "3.2.4",
47 | "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
48 | "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ=="
49 | },
50 | "node_modules/asynckit": {
51 | "version": "0.4.0",
52 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
53 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
54 | },
55 | "node_modules/axios": {
56 | "version": "1.7.9",
57 | "resolved": "https://mirrors.cloud.tencent.com/npm/axios/-/axios-1.7.9.tgz",
58 | "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
59 | "dependencies": {
60 | "follow-redirects": "^1.15.6",
61 | "form-data": "^4.0.0",
62 | "proxy-from-env": "^1.1.0"
63 | }
64 | },
65 | "node_modules/color": {
66 | "version": "3.2.1",
67 | "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
68 | "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==",
69 | "dependencies": {
70 | "color-convert": "^1.9.3",
71 | "color-string": "^1.6.0"
72 | }
73 | },
74 | "node_modules/color-convert": {
75 | "version": "1.9.3",
76 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
77 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
78 | "dependencies": {
79 | "color-name": "1.1.3"
80 | }
81 | },
82 | "node_modules/color-name": {
83 | "version": "1.1.3",
84 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
85 | "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
86 | },
87 | "node_modules/color-string": {
88 | "version": "1.9.1",
89 | "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
90 | "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
91 | "dependencies": {
92 | "color-name": "^1.0.0",
93 | "simple-swizzle": "^0.2.2"
94 | }
95 | },
96 | "node_modules/colorspace": {
97 | "version": "1.1.4",
98 | "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz",
99 | "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==",
100 | "dependencies": {
101 | "color": "^3.1.3",
102 | "text-hex": "1.0.x"
103 | }
104 | },
105 | "node_modules/combined-stream": {
106 | "version": "1.0.8",
107 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
108 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
109 | "dependencies": {
110 | "delayed-stream": "~1.0.0"
111 | },
112 | "engines": {
113 | "node": ">= 0.8"
114 | }
115 | },
116 | "node_modules/cron-parser": {
117 | "version": "4.8.1",
118 | "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.8.1.tgz",
119 | "integrity": "sha512-jbokKWGcyU4gl6jAfX97E1gDpY12DJ1cLJZmoDzaAln/shZ+S3KBFBuA2Q6WeUN4gJf/8klnV1EfvhA2lK5IRQ==",
120 | "dependencies": {
121 | "luxon": "^3.2.1"
122 | },
123 | "engines": {
124 | "node": ">=12.0.0"
125 | }
126 | },
127 | "node_modules/dayjs": {
128 | "version": "1.11.8",
129 | "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.8.tgz",
130 | "integrity": "sha512-LcgxzFoWMEPO7ggRv1Y2N31hUf2R0Vj7fuy/m+Bg1K8rr+KAs1AEy4y9jd5DXe8pbHgX+srkHNS7TH6Q6ZhYeQ=="
131 | },
132 | "node_modules/delayed-stream": {
133 | "version": "1.0.0",
134 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
135 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
136 | "engines": {
137 | "node": ">=0.4.0"
138 | }
139 | },
140 | "node_modules/enabled": {
141 | "version": "2.0.0",
142 | "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
143 | "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="
144 | },
145 | "node_modules/fecha": {
146 | "version": "4.2.3",
147 | "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
148 | "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw=="
149 | },
150 | "node_modules/fn.name": {
151 | "version": "1.1.0",
152 | "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
153 | "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="
154 | },
155 | "node_modules/follow-redirects": {
156 | "version": "1.15.9",
157 | "resolved": "https://mirrors.cloud.tencent.com/npm/follow-redirects/-/follow-redirects-1.15.9.tgz",
158 | "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
159 | "funding": [
160 | {
161 | "type": "individual",
162 | "url": "https://github.com/sponsors/RubenVerborgh"
163 | }
164 | ],
165 | "engines": {
166 | "node": ">=4.0"
167 | },
168 | "peerDependenciesMeta": {
169 | "debug": {
170 | "optional": true
171 | }
172 | }
173 | },
174 | "node_modules/form-data": {
175 | "version": "4.0.0",
176 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
177 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
178 | "dependencies": {
179 | "asynckit": "^0.4.0",
180 | "combined-stream": "^1.0.8",
181 | "mime-types": "^2.1.12"
182 | },
183 | "engines": {
184 | "node": ">= 6"
185 | }
186 | },
187 | "node_modules/inherits": {
188 | "version": "2.0.4",
189 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
190 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
191 | },
192 | "node_modules/is-arrayish": {
193 | "version": "0.3.2",
194 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
195 | "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
196 | },
197 | "node_modules/is-stream": {
198 | "version": "2.0.1",
199 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
200 | "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
201 | "engines": {
202 | "node": ">=8"
203 | },
204 | "funding": {
205 | "url": "https://github.com/sponsors/sindresorhus"
206 | }
207 | },
208 | "node_modules/json5": {
209 | "version": "2.2.3",
210 | "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
211 | "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
212 | "bin": {
213 | "json5": "lib/cli.js"
214 | },
215 | "engines": {
216 | "node": ">=6"
217 | }
218 | },
219 | "node_modules/kuler": {
220 | "version": "2.0.0",
221 | "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
222 | "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A=="
223 | },
224 | "node_modules/logform": {
225 | "version": "2.5.1",
226 | "resolved": "https://registry.npmjs.org/logform/-/logform-2.5.1.tgz",
227 | "integrity": "sha512-9FyqAm9o9NKKfiAKfZoYo9bGXXuwMkxQiQttkT4YjjVtQVIQtK6LmVtlxmCaFswo6N4AfEkHqZTV0taDtPotNg==",
228 | "dependencies": {
229 | "@colors/colors": "1.5.0",
230 | "@types/triple-beam": "^1.3.2",
231 | "fecha": "^4.2.0",
232 | "ms": "^2.1.1",
233 | "safe-stable-stringify": "^2.3.1",
234 | "triple-beam": "^1.3.0"
235 | }
236 | },
237 | "node_modules/long-timeout": {
238 | "version": "0.1.1",
239 | "resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz",
240 | "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w=="
241 | },
242 | "node_modules/luxon": {
243 | "version": "3.3.0",
244 | "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.3.0.tgz",
245 | "integrity": "sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==",
246 | "engines": {
247 | "node": ">=12"
248 | }
249 | },
250 | "node_modules/mime-db": {
251 | "version": "1.52.0",
252 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
253 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
254 | "engines": {
255 | "node": ">= 0.6"
256 | }
257 | },
258 | "node_modules/mime-types": {
259 | "version": "2.1.35",
260 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
261 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
262 | "dependencies": {
263 | "mime-db": "1.52.0"
264 | },
265 | "engines": {
266 | "node": ">= 0.6"
267 | }
268 | },
269 | "node_modules/ms": {
270 | "version": "2.1.3",
271 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
272 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
273 | },
274 | "node_modules/node-schedule": {
275 | "version": "2.1.1",
276 | "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz",
277 | "integrity": "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==",
278 | "dependencies": {
279 | "cron-parser": "^4.2.0",
280 | "long-timeout": "0.1.1",
281 | "sorted-array-functions": "^1.3.0"
282 | },
283 | "engines": {
284 | "node": ">=6"
285 | }
286 | },
287 | "node_modules/one-time": {
288 | "version": "1.0.0",
289 | "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz",
290 | "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==",
291 | "dependencies": {
292 | "fn.name": "1.x.x"
293 | }
294 | },
295 | "node_modules/pinyin-pro": {
296 | "version": "3.26.0",
297 | "resolved": "https://mirrors.cloud.tencent.com/npm/pinyin-pro/-/pinyin-pro-3.26.0.tgz",
298 | "integrity": "sha512-HcBZZb0pvm0/JkPhZHWA5Hqp2cWHXrrW/WrV+OtaYYM+kf35ffvZppIUuGmyuQ7gDr1JDJKMkbEE+GN0wfMoGg=="
299 | },
300 | "node_modules/proxy-from-env": {
301 | "version": "1.1.0",
302 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
303 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
304 | },
305 | "node_modules/readable-stream": {
306 | "version": "3.6.2",
307 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
308 | "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
309 | "dependencies": {
310 | "inherits": "^2.0.3",
311 | "string_decoder": "^1.1.1",
312 | "util-deprecate": "^1.0.1"
313 | },
314 | "engines": {
315 | "node": ">= 6"
316 | }
317 | },
318 | "node_modules/safe-buffer": {
319 | "version": "5.2.1",
320 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
321 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
322 | "funding": [
323 | {
324 | "type": "github",
325 | "url": "https://github.com/sponsors/feross"
326 | },
327 | {
328 | "type": "patreon",
329 | "url": "https://www.patreon.com/feross"
330 | },
331 | {
332 | "type": "consulting",
333 | "url": "https://feross.org/support"
334 | }
335 | ]
336 | },
337 | "node_modules/safe-stable-stringify": {
338 | "version": "2.4.3",
339 | "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz",
340 | "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==",
341 | "engines": {
342 | "node": ">=10"
343 | }
344 | },
345 | "node_modules/simple-swizzle": {
346 | "version": "0.2.2",
347 | "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
348 | "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
349 | "dependencies": {
350 | "is-arrayish": "^0.3.1"
351 | }
352 | },
353 | "node_modules/sorted-array-functions": {
354 | "version": "1.3.0",
355 | "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz",
356 | "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA=="
357 | },
358 | "node_modules/stack-trace": {
359 | "version": "0.0.10",
360 | "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
361 | "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==",
362 | "engines": {
363 | "node": "*"
364 | }
365 | },
366 | "node_modules/string_decoder": {
367 | "version": "1.3.0",
368 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
369 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
370 | "dependencies": {
371 | "safe-buffer": "~5.2.0"
372 | }
373 | },
374 | "node_modules/text-hex": {
375 | "version": "1.0.0",
376 | "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
377 | "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg=="
378 | },
379 | "node_modules/triple-beam": {
380 | "version": "1.3.0",
381 | "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz",
382 | "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw=="
383 | },
384 | "node_modules/util-deprecate": {
385 | "version": "1.0.2",
386 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
387 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
388 | },
389 | "node_modules/vm": {
390 | "version": "0.1.0",
391 | "resolved": "http://mirrors.cloud.tencent.com/npm/vm/-/vm-0.1.0.tgz",
392 | "integrity": "sha512-1aKVjgohVDnVhGrJLhl2k8zIrapH+7HsdnIjGvBp3XX2OCj6XGzsIbDp9rZ3r7t6qgDfXEE1EoEAEOLJm9LKnw=="
393 | },
394 | "node_modules/winston": {
395 | "version": "3.9.0",
396 | "resolved": "https://registry.npmjs.org/winston/-/winston-3.9.0.tgz",
397 | "integrity": "sha512-jW51iW/X95BCW6MMtZWr2jKQBP4hV5bIDq9QrIjfDk6Q9QuxvTKEAlpUNAzP+HYHFFCeENhph16s0zEunu4uuQ==",
398 | "dependencies": {
399 | "@colors/colors": "1.5.0",
400 | "@dabh/diagnostics": "^2.0.2",
401 | "async": "^3.2.3",
402 | "is-stream": "^2.0.0",
403 | "logform": "^2.4.0",
404 | "one-time": "^1.0.0",
405 | "readable-stream": "^3.4.0",
406 | "safe-stable-stringify": "^2.3.1",
407 | "stack-trace": "0.0.x",
408 | "triple-beam": "^1.3.0",
409 | "winston-transport": "^4.5.0"
410 | },
411 | "engines": {
412 | "node": ">= 12.0.0"
413 | }
414 | },
415 | "node_modules/winston-transport": {
416 | "version": "4.5.0",
417 | "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz",
418 | "integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==",
419 | "dependencies": {
420 | "logform": "^2.3.2",
421 | "readable-stream": "^3.6.0",
422 | "triple-beam": "^1.3.0"
423 | },
424 | "engines": {
425 | "node": ">= 6.4.0"
426 | }
427 | },
428 | "node_modules/ws": {
429 | "version": "8.18.0",
430 | "resolved": "https://mirrors.cloud.tencent.com/npm/ws/-/ws-8.18.0.tgz",
431 | "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
432 | "engines": {
433 | "node": ">=10.0.0"
434 | },
435 | "peerDependencies": {
436 | "bufferutil": "^4.0.1",
437 | "utf-8-validate": ">=5.0.2"
438 | },
439 | "peerDependenciesMeta": {
440 | "bufferutil": {
441 | "optional": true
442 | },
443 | "utf-8-validate": {
444 | "optional": true
445 | }
446 | }
447 | }
448 | }
449 | }
450 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sparkbridge2",
3 | "version": "2.6.9",
4 | "description": "all new adapter",
5 | "main": "index.js",
6 | "scripts": {
7 | "dev": "node index",
8 | "create": "node handles/plugin_create.js"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/sparkbridge/sparkbridge2.git"
13 | },
14 | "author": "Sbaoor",
15 | "license": "AGPL-3.0",
16 | "bugs": {
17 | "url": "https://github.com/sparkbridge/sparkbridge2/issues"
18 | },
19 | "homepage": "https://github.com/sparkbridge/sparkbridge2#readme",
20 | "dependencies": {
21 | "axios": "^1.4.0",
22 | "dayjs": "^1.11.8",
23 | "json5": "^2.2.3",
24 | "node-schedule": "^2.1.1",
25 | "pinyin-pro": "^3.26.0",
26 | "vm": "^0.1.0",
27 | "winston": "^3.9.0",
28 | "ws": "^8.13.0"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/plugins/JandLandCmsg/index.js:
--------------------------------------------------------------------------------
1 | const _config = spark.getFileHelper('JandLandCmsg');
2 | const JSON5 = require('json5');
3 |
4 | function formatMsg(msg) {
5 | const formattedMessages =msg.map((t) => {
6 | switch (t.type) {
7 | case 'at':
8 | if (spark.mc.getXbox(t.data.qq) == undefined) {
9 | return '@' + t.data.qq;
10 | }else{
11 | return '@' + spark.mc.getXbox(t.data.qq);
12 | }
13 | case 'text':
14 | return t.data.text;
15 | case 'image':
16 | return '[图片]';
17 | case 'face':
18 | return '[表情]';
19 | }
20 | });
21 | return formattedMessages.join('');
22 | }
23 |
24 |
25 | _config.initFile('config.json', {
26 | switch: {
27 | join: true,
28 | left: true,
29 | chat: {
30 | group: true,
31 | server: false //发送到服务器的默认为关闭,默认的正则表达式附带了chat xxxx的正则
32 | }
33 | },
34 | chatMaxLength: 20,
35 | // chatShield: []
36 | });
37 |
38 | const WebConfigBuilder = spark.telemetry.WebConfigBuilder;
39 |
40 |
41 | _config.initFile('lang.json', {
42 | join: '%PLAYER_NAME% 进入了服务器',
43 | left: '%PLAYER_NAME% 离开了服务器',
44 | chat: {
45 | group: '%PLAYER_NAME% >> %PLAYER_MSG%',
46 | server: '[群聊]%USER_XBOXID% >> %PLAYER_MSG%'
47 | }
48 | });
49 |
50 | const config = JSON5.parse(_config.getFile('config.json'));
51 | const lang = JSON5.parse(_config.getFile('lang.json'));
52 |
53 | let wbc_2 = new WebConfigBuilder("JandLandCmsg");
54 | wbc_2.addSwitch("join",config.switch.join,"是否开启加入提示");
55 | wbc_2.addSwitch("left",config.switch.left,"是否开启离开提示");
56 | wbc_2.addSwitch("chat_group",config.switch.chat.group,"是否转发消息到群聊");
57 | wbc_2.addSwitch("chat_server",config.switch.chat.server,"是否转发消息到服务器(默认为关闭,默认的正则表达式附带了chat xxxx的正则)");
58 | wbc_2.addNumber("chatMaxLength",config.chatMaxLength,"聊天最大字数长度");
59 | // wbc_2.addEditArray("chatShield",config.chatShield,"聊天屏蔽词语");
60 | spark.emit("event.telemetry.pushconfig", wbc_2);
61 |
62 | spark.on("event.telemetry.updateconfig_JandLandCmsg",(p,k,v)=>{
63 | switch(k){
64 | case "join":
65 | config.switch.join = v;
66 | break;
67 | case 'left':
68 | config.switch.left = v;
69 | break;
70 | case 'chat_group':
71 | config.switch.chat.group = v;
72 | break;
73 | case 'chat_server':
74 | config.switch.chat.server = v;
75 | break;
76 | case 'chatMaxLength':
77 | config.chatMaxLength = v;
78 | break;
79 | case 'chatShield':
80 | config.chatShield = v;
81 | break;
82 | }
83 | _config.updateFile("config.json",config);
84 | })
85 |
86 |
87 |
88 | let wbc_1 = new WebConfigBuilder("JandLandCmsg_lang");
89 | wbc_1.addText("join",lang.join,"加入服务器向群聊中发送的消息");
90 | wbc_1.addText("left",lang.left,'离开服务器向群聊中发送的消息');
91 | wbc_1.addText("chat_group",lang.chat.group,"发送到群聊的聊天信息格式");
92 | wbc_1.addText('chat_server',lang.chat.server,"发送到服务器的聊天信息格式");
93 | spark.emit("event.telemetry.pushconfig", wbc_1);
94 | spark.on("event.telemetry.updateconfig_JandLandCmsg_lang",(p,k,v)=>{
95 | switch(k){
96 | case 'join':
97 | lang.join = v;
98 | break;
99 | case 'left':
100 | lang.left = v;
101 | break;
102 | case 'chat_group':
103 | lang.chat.group = v;
104 | break;
105 | case 'chat_server':
106 | lang.chat.server = v;
107 | break;
108 | }
109 | _config.updateFile("lang.json",lang);
110 | })
111 |
112 |
113 |
114 | spark.Cmd.regPlaceHolder('PLAYER_NAME', e => {
115 | return e.realName;
116 | });
117 |
118 | spark.Cmd.regPlaceHolder('USER_NAME', e => {
119 | return e.sender.nickname;
120 | });
121 |
122 | spark.Cmd.regPlaceHolder('USER_CARD', e => {
123 | return e.sender.card;
124 | });
125 |
126 | spark.Cmd.regPlaceHolder('USER_QID', e => {
127 | return e.sender.user_id;
128 | });
129 |
130 | spark.Cmd.regPlaceHolder('USER_XBOXID', e => {
131 | // console.log(e);
132 | const qid = e.user_id;
133 |
134 | if (spark.mc.getXbox(qid) == undefined) {
135 |
136 | return e.sender.nickname; //获取card有时候是空的,用nickname代替
137 | } else {
138 | return spark.mc.getXbox(qid);
139 | }
140 | });
141 |
142 | spark.Cmd.regPlaceHolder('PLAYER_XUID', e => {
143 | return e.xuid;
144 | });
145 |
146 | spark.Cmd.regPlaceHolder('PLAYER_IP', e => {
147 | return e.getDevice().ip.split(':')[0];
148 | });
149 |
150 | spark.Cmd.regPlaceHolder('PLAYER_MSG', (player, msg) => {
151 | return msg;
152 | });
153 |
154 |
155 | spark.Cmd.regPlaceHolder('USER_MSG', e => {
156 | return formatMsg(e.message);
157 | });
158 | // //这个代码不知道为什么没有办法运行,直接用上面的Player_msg就行了
159 | // spark.Cmd.regPlaceHolder('USER_MSG', (player, msg) => {
160 | // return msg;
161 | // });
162 |
163 |
164 | const GROUP_ID = spark.mc.config.group;
165 |
166 | if (config.switch.join) {
167 | spark.mc.on('onJoin', (player) => {
168 | if(player.isSimulatedPlayer()){
169 | return;
170 | }
171 | spark.QClient.sendGroupMsg(GROUP_ID, spark.Cmd.buildString(lang.join, [], player));
172 | });
173 | }
174 | if (config.switch.left) {
175 | spark.mc.on('onLeft', (player) => {
176 | spark.QClient.sendGroupMsg(GROUP_ID, spark.Cmd.buildString(lang.left, [], player));
177 | });
178 | }
179 |
180 | if (config.switch.chat.group) {
181 | spark.mc.on('onChat', (player, msg) => {
182 | if(msg.length > config.chatMaxLength){
183 | player.tell('聊天长度过长,将不会转发');
184 | return;
185 | }
186 |
187 | spark.QClient.sendGroupMsg(GROUP_ID, spark.Cmd.buildString(lang.chat.group, [], player, msg));
188 | });
189 | }
190 |
191 | if (config.switch.chat.server) {
192 |
193 | spark.on('message.group.normal', async (e) => {
194 | if (e.group_id !== GROUP_ID) return;
195 | let msg = formatMsg(e.message);
196 | // console.log(spark.Cmd.buildString(lang.chat.server, [], e.sender, msg));
197 | mc.broadcast(spark.Cmd.buildString(lang.chat.server, [], e.sender, msg));
198 | });
199 | }
200 |
201 |
--------------------------------------------------------------------------------
/plugins/JandLandCmsg/spark.json:
--------------------------------------------------------------------------------
1 | {
2 | "name":"JandLandCmsg",
3 | "author":"lition",
4 | "version":[0,0,1],
5 | "desc":"进出服务器和聊天适配器",
6 | "loadmode": "hybrid",
7 | "permission": "key",
8 | "priority": "post",
9 | "load":true
10 | }
--------------------------------------------------------------------------------
/plugins/bridgebase/index.js:
--------------------------------------------------------------------------------
1 | const msgbuilder = require('../../handles/msgbuilder')
2 | const packbuilder = require('../../handles/packbuilder');
3 | const { parseCQString } = require('../../handles/parserCQString');
4 | const lg = require('../../handles/logger')
5 | const logger = lg.getLogger('QClient');
6 | const JSON5 = require('json5');
7 |
8 | function text(str) {
9 | if (typeof str == 'string') return msgbuilder.text(str);
10 | else return str;
11 | }
12 |
13 | const build_reply = (id, type, mid) => {
14 | return (msg, quote = false) => {
15 | msg = msgbuilder.format(msg);
16 | if (quote) {
17 | msg.unshift({
18 | type: 'reply',
19 | data: {
20 | id: mid.toString()
21 | }
22 | });
23 | }
24 | if (type == 'group') {
25 | return sendGroupMsg(id, msg);
26 | } else {
27 | return sendPrivateMsg(id, msg);
28 | }
29 | }
30 | }
31 |
32 | const _config = spark.getFileHelper('base');
33 | const _raw_file = _config.getFile("config.json");
34 | const _p_raw = JSON5.parse(_raw_file);
35 | const _isArray = _p_raw.onebot_mode_v11;
36 | //console.log(_isArray)
37 | spark.on('gocq.pack', (pack) => {
38 | if (spark.debug) console.log(pack);
39 | //let evt_name = `${pack.post_type}${pack.message_type == undefined ? '' :'.'+ pack.message_type}`;
40 |
41 | if (pack.echo != undefined) {
42 | // if (spark.debug) console.log(pack);
43 | spark.QClient.eventEmitter.emit("packid_" + pack.echo, pack.data);
44 | // return // <-- 要不要return呢,不return也没什么,但是怕出啥问题。。。
45 | }
46 | const POST_TYPE = pack.post_type;
47 | switch (POST_TYPE) {
48 | case 'meta_event':
49 | spark.emit(`${POST_TYPE}.${pack.meta_event_type}`, pack);
50 | break;
51 | case 'message':
52 | //console.log("in");
53 | if (!_isArray) {
54 | //console.log("in",typeof pack.message);
55 | let _pmessage = parseCQString(pack.message.toString());
56 | pack.message = _pmessage;
57 | //console.log(_pmessage);
58 | }
59 | if (pack.raw_message.includes('[') || pack.raw_message.includes(']') || pack.raw_message.includes(',') || pack.raw_message.includes('&')) {
60 | pack.raw_message = pack.raw_message.replaceAll('[', '[')
61 | .replaceAll(']', ']')
62 | .replaceAll(',', ',')
63 | .replaceAll('&', '&');
64 | // 采用最烂的替换方式,希望能有高效率的方法,欢迎PR
65 | }
66 |
67 | // if (pack.raw_message.includes('[') || pack.raw_message.includes(']') || pack.raw_message.includes(',') || pack.raw_message.includes('&')) {
68 | // pack.raw_message = decodeURIComponent(pack.raw_message.replace(/(\d+);/g, function (match, p1) {
69 | // return String.fromCharCode(p1);
70 | // }));
71 | // }
72 |
73 |
74 | spark.emit(`${POST_TYPE}.${pack.message_type}.${pack.sub_type}`, pack, build_reply(pack.group_id == undefined ? pack.user_id : pack.group_id, pack.message_type, pack.message_id));
75 | break;
76 | case 'notice':
77 | spark.emit(`${POST_TYPE}.${pack.notice_type}`, pack)
78 | break;
79 | case 'request':
80 | spark.emit(`${POST_TYPE}.${pack.request_type}`, pack);
81 | break;
82 | }
83 | });
84 |
85 | function uuid() {
86 | let s = []
87 | let hexDigits = '0123456789abcdef'
88 | for (let i = 0; i < 36; i++) {
89 | s[i] = hexDigits.substring(Math.floor(Math.random() * 0x10), 1)
90 | }
91 | s[14] = '4' // bits 12-15 of the time_hi_and_version field to 0010
92 | s[19] = hexDigits.substring((s[19] & 0x3) | 0x8, 1) // bits 6-7 of the clock_seq_hi_and_reserved to 01
93 | s[8] = s[13] = s[18] = s[23] = '-'
94 |
95 | let uuid = s.join('')
96 | return uuid
97 | }
98 | function defaultErrorHandler(error) {
99 | if (error.reason === 'timeout') {
100 | logger.warn("请求超时,此信息可能发送失败");
101 | // 这里可以做一些超时后的默认处理,比如重试等
102 | } else {
103 | logger.error("请求发送时发生错误:", error);
104 | }
105 | }
106 |
107 |
108 | function sendGroupMsg(gid, msg) {
109 | let tmp_id = uuid();
110 | msg = msgbuilder.format(msg);
111 | spark.QClient.sendWSPack(packbuilder.GroupMessagePack(gid, msg, tmp_id));
112 | return new Promise((res, rej) => {
113 | spark.QClient.eventEmitter.once('packid_' + tmp_id, (data) => {
114 | res(data);
115 | });
116 | setTimeout(() => {
117 | rej({ reason: 'timeout' });
118 | }, 10e3);
119 | }).catch(defaultErrorHandler);
120 | }
121 | spark.QClient.setOwnProperty('sendGroupMsg', sendGroupMsg);
122 |
123 | function sendPrivateMsg(fid, msg) {
124 | msg = msgbuilder.format(msg);
125 | let tmp_id = uuid();
126 | spark.QClient.sendWSPack(packbuilder.PrivateMessagePack(fid, msg, tmp_id));
127 | return new Promise((res, rej) => {
128 | spark.QClient.eventEmitter.once('packid_' + tmp_id, (data) => {
129 | res(data);
130 | });
131 | setTimeout(() => {
132 | rej({ reason: 'timeout' });
133 | }, 10e3);
134 | }).catch(defaultErrorHandler);
135 | }
136 | spark.QClient.setOwnProperty('sendPrivateMsg', sendPrivateMsg);
137 |
138 | function sendGroupForwardMsg(gid, msg) {
139 | let tmp_id = uuid();
140 | spark.QClient.sendWSPack(packbuilder.GroupForwardMessagePack(gid, msg, tmp_id));
141 | return new Promise((res, rej) => {
142 | spark.QClient.eventEmitter.once('packid_' + tmp_id, (data) => {
143 | res(data);
144 | });
145 | setTimeout(() => {
146 | rej({ reason: 'timeout' });
147 | }, 10e3);
148 | }).catch(defaultErrorHandler);
149 | }
150 | spark.QClient.setOwnProperty('sendGroupForwardMsg', sendGroupForwardMsg);
151 |
152 | function sendGroupBan(gid, mid, d) {
153 | spark.QClient.sendWSPack(packbuilder.GroupBanPack(gid, mid, d));
154 | }
155 | spark.QClient.setOwnProperty('sendGroupBan', sendGroupBan);
156 |
157 | function deleteMsg(id) {
158 | spark.QClient.sendWSPack(packbuilder.DeleteMsgPack(id));
159 | }
160 | spark.QClient.setOwnProperty('deleteMsg', deleteMsg);
161 |
162 | function getGroupMemberList(gid) {
163 | let tmp_id = uuid();
164 | spark.QClient.sendWSPack(packbuilder.GroupMemberListPack(gid, tmp_id));
165 | return new Promise((res, rej) => {
166 | spark.QClient.eventEmitter.once('packid_' + tmp_id, (data) => {
167 | res(data);
168 | });
169 | setTimeout(() => {
170 | rej({ reason: 'timeout' });
171 | }, 10e3);
172 | }).catch(defaultErrorHandler)
173 | }
174 | spark.QClient.setOwnProperty('getGroupMemberList', getGroupMemberList)
175 |
176 | function getGroupMemberInfo(gid, mid) {
177 | let tmp_id = uuid();
178 | spark.QClient.sendWSPack(packbuilder.GroupMemberInfoPack(gid, mid, tmp_id));
179 | return new Promise((res, rej) => {
180 | spark.QClient.eventEmitter.once('packid_' + tmp_id, (data) => {
181 | res(data);
182 | });
183 | setTimeout(() => {
184 | rej({ reason: 'timeout' });
185 | }, 10e3);
186 | }).catch(defaultErrorHandler)
187 | }
188 | spark.QClient.setOwnProperty('getGroupMemberInfo', getGroupMemberInfo);
189 |
190 | function setGroupAddRequest(flag, sub_type, approve) {
191 | spark.QClient.sendWSPack(packbuilder.GroupRequestPack(flag, sub_type, approve));
192 | }
193 | spark.QClient.setOwnProperty('setGroupAddRequest', setGroupAddRequest);
194 |
195 | function setFriendAddRequest(flag, approve) {
196 | spark.QClient.sendWSPack(packbuilder.FriendRequestPack(flag, approve));
197 | }
198 | spark.QClient.setOwnProperty('setFriendAddRequest', setFriendAddRequest);
199 |
200 | function sendLike(fid, times) {
201 | spark.QClient.sendWSPack(packbuilder.LikePack(fid, times));
202 | }
203 | spark.QClient.setOwnProperty('sendLike', sendLike);
204 |
205 | function getMsg(id) {
206 | let tmp_id = uuid();
207 | spark.QClient.sendWSPack(packbuilder.GetMsgPack(id, tmp_id));
208 | return new Promise((res, rej) => {
209 | spark.QClient.eventEmitter.once('packid_' + tmp_id, (data) => {
210 | res(data);
211 | });
212 | setTimeout(() => {
213 | rej({ reason: 'timeout' });
214 | }, 10e3);
215 | }).catch(defaultErrorHandler)
216 | }
217 | spark.QClient.setOwnProperty('getMsg', getMsg)
218 |
219 | function sendGroupForwardMessage(gid, msg) {
220 | let tmp_id = uuid();
221 | spark.QClient.sendWSPack(packbuilder.GroupForwardMessagePack(gid, msg, tmp_id));
222 | return new Promise((res, rej) => {
223 | spark.QClient.eventEmitter.once('packid_' + tmp_id, (data) => {
224 | res(data);
225 | });
226 | setTimeout(() => {
227 | rej({ reason: 'timeout' });
228 | }, 10e3);
229 | }).catch(defaultErrorHandler)
230 | }
231 | spark.QClient.setOwnProperty('sendGroupForwardMessage', sendGroupForwardMessage)
232 |
233 | function getGroupRootFiles(gid) {
234 | try {
235 | let tmp_id = uuid();
236 | spark.QClient.sendWSPack(packbuilder.GroupRootFilesPack(gid, tmp_id));
237 | return new Promise((res, rej) => {
238 | spark.QClient.eventEmitter.once('packid_' + tmp_id, (data) => {
239 | res(data);
240 | });
241 | setTimeout(() => {
242 | rej({ reason: 'timeout' });
243 | }, 10e3);
244 | });
245 | } catch (error) {
246 | return defaultErrorHandler(error);
247 | }
248 | }
249 |
250 | spark.QClient.setOwnProperty('getGroupRootFiles', getGroupRootFiles);
251 |
252 | function uploadGroupFile(gid, FileName, AsName, FolderID) {
253 | spark.QClient.sendWSPack(packbuilder.UploadGroupFilePack(gid, FileName, AsName, FolderID))
254 | }
255 | spark.QClient.setOwnProperty('uploadGroupFile', uploadGroupFile);
256 |
257 | function sendGroupWholeBan(gid, enable) {
258 | spark.QClient.sendWSPack(packbuilder.GroupWholeBanPack(gid, enable));
259 | }
260 | spark.QClient.setOwnProperty('sendGroupWholeBan', sendGroupWholeBan)
261 |
262 | function setGroupKick(gid, mid, rej) {
263 | spark.QClient.sendWSPack(packbuilder.GroupKickPack(gid, mid, rej));
264 | }
265 | spark.QClient.setOwnProperty('setGroupKick', setGroupKick);
266 |
267 | function setGroupLeave(gid, dismiss) {
268 | spark.QClient.sendWSPack(packbuilder.GroupLeavePack(gid, dismiss));
269 | }
270 | spark.QClient.setOwnProperty('setGroupLeave', setGroupLeave);
271 |
272 | function setGroupName(gid, name) {
273 | spark.QClient.sendWSPack(packbuilder.GroupNamePack(gid, name));
274 | }
275 | spark.QClient.setOwnProperty('setGroupName', setGroupName);
276 |
277 | function getStrangerInfo(sid, no_cache) {
278 | let tmp_id = uuid();
279 | spark.QClient.sendWSPack(packbuilder.StrangerInfoPack(sid, no_cache, tmp_id));
280 | return new Promise((res, rej) => {
281 | spark.QClient.eventEmitter.once('packid_' + tmp_id, (data) => {
282 | res(data);
283 | });
284 | setTimeout(() => {
285 | rej({ reason: 'timeout' });
286 | }, 10e3);
287 | }).catch(defaultErrorHandler)
288 | }
289 | spark.QClient.setOwnProperty('getStrangerInfo', getStrangerInfo);
290 |
291 | function getFriendInfo(fid, no_cache) {
292 | let tmp_id = uuid();
293 | spark.QClient.sendWSPack(packbuilder.FriendInfoPack(fid, no_cache, tmp_id));
294 | return new Promise((res, rej) => {
295 | spark.QClient.eventEmitter.once('packid_' + tmp_id, (data) => {
296 | res(data);
297 | });
298 | setTimeout(() => {
299 | rej({ reason: 'timeout' });
300 | }, 10e3);
301 | }).catch(defaultErrorHandler)
302 | }
303 | spark.QClient.setOwnProperty('getFriendInfo', getFriendInfo);
304 |
305 | function getGroupInfo(gid, no_cache) {
306 | let tmp_id = uuid();
307 | spark.QClient.sendWSPack(packbuilder.GroupInfoPack(gid, no_cache, tmp_id));
308 | return new Promise((res, rej) => {
309 | spark.QClient.eventEmitter.once('packid_' + tmp_id, (data) => {
310 | res(data);
311 | });
312 | setTimeout(() => {
313 | rej({ reason: 'timeout' });
314 | }, 10e3);
315 | }).catch(defaultErrorHandler)
316 | }
317 | spark.QClient.setOwnProperty('getGroupInfo', getGroupInfo);
318 |
319 | function getFriendList() {
320 | let tmp_id = uuid();
321 | spark.QClient.sendWSPack(packbuilder.FriendInfoPack(tmp_id));
322 | return new Promise((res, rej) => {
323 | spark.QClient.eventEmitter.once('packid_' + tmp_id, (data) => {
324 | res(data);
325 | });
326 | setTimeout(() => {
327 | rej({ reason: 'timeout' });
328 | }, 10e3);
329 | }).catch(defaultErrorHandler)
330 | }
331 | spark.QClient.setOwnProperty('getFriendList', getFriendList);
332 |
333 | function getGroupList() {
334 | let tmp_id = uuid();
335 | spark.QClient.sendWSPack(packbuilder.GroupInfoPack(tmp_id));
336 | return new Promise((res, rej) => {
337 | spark.QClient.eventEmitter.once('packid_' + tmp_id, (data) => {
338 | res(data);
339 | });
340 | setTimeout(() => {
341 | rej({ reason: 'timeout' });
342 | }, 10e3);
343 | }).catch(defaultErrorHandler)
344 | }
345 | spark.QClient.setOwnProperty('getGroupList', getGroupList);
346 |
347 | function getGroupHonorInfo(gid, type) {
348 | let tmp_id = uuid();
349 | spark.QClient.sendWSPack(packbuilder.GroupInfoPack(gid, type, tmp_id));
350 | return new Promise((res, rej) => {
351 | spark.QClient.eventEmitter.once('packid_' + tmp_id, (data) => {
352 | res(data);
353 | });
354 | setTimeout(() => {
355 | rej({ reason: 'timeout' });
356 | }, 10e3);
357 | }).catch(defaultErrorHandler)
358 | }
359 | spark.QClient.setOwnProperty('getGroupHonorInfo', getGroupHonorInfo);
360 |
361 | function getStatus() {
362 | let tmp_id = uuid();
363 | spark.QClient.sendWSPack(packbuilder.StatusPack(tmp_id));
364 | return new Promise((res, rej) => {
365 | spark.QClient.eventEmitter.once('packid_' + tmp_id, (data) => {
366 | res(data);
367 | });
368 | setTimeout(() => {
369 | rej({ reason: 'timeout' });
370 | }, 10e3);
371 | }).catch(defaultErrorHandler)
372 | }
373 | spark.QClient.setOwnProperty('getStatus', getStatus);
--------------------------------------------------------------------------------
/plugins/bridgebase/spark.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bridgebase",
3 | "author": "lition",
4 | "version": [
5 | 0,
6 | 0,
7 | 1
8 | ],
9 | "desc": "bridge适配器",
10 | "loadmode": "hybrid",
11 | "permission":"core",
12 | "priority": "base",
13 | "load":true
14 | }
--------------------------------------------------------------------------------
/plugins/economy/index.js:
--------------------------------------------------------------------------------
1 | const _config = spark.getFileHelper('economy');
2 | _config.initFile('config.json', {
3 | moneyType: 0 // 0 LLMoney 1 local
4 | });
5 | _config.initFile('data.json', {});
6 |
7 | const config = JSON.parse(_config.getFile('config.json'));
8 | const dat = JSON.parse(_config.getFile("data.json"));
9 |
10 | function HasXbox(qid) {
11 | if (spark.mc.getXbox(qid) == undefined) {
12 | return false;
13 | } else {
14 | return true;
15 | }
16 | }
17 |
18 | function c_save() {
19 | _config.updateFile('data.json', dat);
20 | }
21 |
22 | function c_add(qid, c) {
23 | dta[qid] += c;
24 | c_save()
25 | }
26 |
27 | function c_rem(qid, c) {
28 | dta[qid] -= c;
29 | c_save()
30 | }
31 |
32 | function c_get(qid) {
33 | if (dat[qid] == undefined) dat[qid] = 0;
34 | return dat[qid];
35 | }
36 |
37 | function getMoney(qid) {
38 | if (config.moneyType == 0) {
39 | if (HasXbox(qid)) {
40 | let xuid = data.name2xuid(spark.mc.getXbox(qid));
41 | return { success: true, result: money.get(xuid) };
42 | } else {
43 | return { success: false };
44 | }
45 | } else {
46 | return { success: true, result: c_get(qid) };
47 | }
48 | }
49 |
50 | function remMoney(qid, count) {
51 | if (config.moneyType == 0) {
52 | if (HasXbox(qid)) {
53 | let xuid = data.name2xuid(spark.mc.getXbox(qid));
54 | return { success: money.reduce(xuid,count)};
55 | } else {
56 | return { success: false };
57 | }
58 | } else {
59 | let has = c_get(qid);
60 | if(has >= count){
61 | c_rem(qid,count);
62 | return {success:true};
63 | }else{
64 | return {success :false};
65 | }
66 | }
67 | }
68 |
69 | function addMoney(qid, count) {
70 | if (config.moneyType == 0) {
71 | if(HasXbox(qid)){
72 | let xuid = data.name2xuid(spark.mc.getXbox(qid));
73 | return { success: money.add(xuid,count)};
74 | }
75 | } else {
76 | c_add(qid,count);
77 | return {success:true};
78 | }
79 | }
80 |
81 | spark.economy = {
82 | addMoney,remMoney,getMoney
83 | }
--------------------------------------------------------------------------------
/plugins/economy/spark.json:
--------------------------------------------------------------------------------
1 | {
2 | "name":"economy",
3 | "author":"lition",
4 | "version":[0,0,1],
5 | "desc":"经济系统",
6 | "loadmode": "hybrid",
7 | "permission": "nor",
8 | "priority":"post",
9 | "load":true
10 | }
--------------------------------------------------------------------------------
/plugins/list.json:
--------------------------------------------------------------------------------
1 | ["bridgebase","economy","JandLandCmsg","llapi","mc","regex","telemetry"]
--------------------------------------------------------------------------------
/plugins/llapi/index.js:
--------------------------------------------------------------------------------
1 | ll.exports((msg) => spark.QClient.sendGroupMsg(spark.mc.config.group, msg), "SparkAPI", "sendGroupMessage");
2 | ll.exports(() => spark.mc.config.group, "SparkAPI", "GetGroupId");
3 | // -----------------------------------------------------------------
4 |
5 | const msgbuilder = require('../../handles/msgbuilder');
6 | const packbuilder = require('../../handles/packbuilder');
7 | function callCustomEvent(event, eventId) {
8 | spark.on(event, (e) => {
9 | if (ll.hasExported(event, eventId)) ll.imports(event, eventId)(e);
10 | });
11 | }
12 |
13 | let EventId = 0;
14 | ll.exports(callCustomEvent, "SparkAPI", "callCustomEvent");
15 | ll.exports(() => `SparkBridge_Event_${++EventId}`, "SparkAPI", "GetEventID");
16 | ll.exports((type) => {
17 | switch (type) {
18 | case 'spark.mc.config':
19 | return spark.mc.config;
20 | }
21 | }, "SparkAPI", "GetInfo")
22 | ll.exports((data) => spark.QClient.sendWSPack(data), "SparkAPI", "sendWSPack");//直接spark.QClient.sendWSPack会报错
23 | ll.exports(spark.QClient.sendGroupMsg, "SparkAPI", "sendGroupMsg");
24 |
25 | function getStaticMethods(klass) {
26 | return Object.getOwnPropertyNames(klass)
27 | .filter(prop => typeof klass[prop] === 'function'
28 | && prop !== 'length'
29 | && prop !== 'name'
30 | && prop !== 'prototype');
31 | }
32 | function exportClass(klass, name) {
33 | let list = getStaticMethods(klass);
34 | ll.exports(() => list, "SparkAPI", name);
35 | list.forEach(method => {
36 | ll.exports(klass[method], "SparkAPI", `${name}.${method}`);
37 | });
38 | }
39 | exportClass(msgbuilder, 'msgbuilder');
40 | exportClass(packbuilder, 'packbuilder');
--------------------------------------------------------------------------------
/plugins/llapi/lib.js:
--------------------------------------------------------------------------------
1 | const CallEvent = ll.imports("SparkAPI", "callCustomEvent");
2 | const GetEventID = ll.imports("SparkAPI", "GetEventID");
3 | function importClass(name) {
4 | let res = {}, list = ll.imports("SparkAPI", name)();
5 | list.forEach(funcname => res[funcname] = ll.imports("SparkAPI", `${name}.${funcname}`));
6 | return res;
7 | }
8 | const msgbuilder = importClass('msgbuilder'), packbuilder = importClass('packbuilder');
9 | module.exports = {
10 | spark: {
11 | mc: {
12 | config: ll.imports("SparkAPI", "GetInfo")('spark.mc.config')
13 | },
14 | on: (event, callback) => {
15 | let eventId = GetEventID();
16 | ll.exports(callback, event, eventId);
17 | CallEvent(event, eventId);
18 | return eventId;
19 | },
20 | QClient: {
21 | sendGroupMsg: ll.imports("SparkAPI", "sendGroupMsg"),
22 | sendWSPack: ll.imports("SparkAPI", "sendWSPack"),
23 | },
24 | },
25 | msgbuilder: msgbuilder,
26 | packbuilder: packbuilder
27 | }
--------------------------------------------------------------------------------
/plugins/llapi/lib/EventAPI.js:
--------------------------------------------------------------------------------
1 | const CallEvent=ll.imports("SparkAPI","callCustomEvent");
2 | const GetEventID=ll.imports("SparkAPI","GetEventID");
3 | class Spark{
4 | constructor() {
5 | throw new Error("Static class cannot be instantiated");
6 | }
7 | static on(event, callback) {
8 | let eventId=GetEventID();
9 | ll.exports(callback, event, eventId);
10 | CallEvent(event, eventId);
11 | }
12 | }
13 | module.exports = {
14 | Spark
15 | };
--------------------------------------------------------------------------------
/plugins/llapi/spark.json:
--------------------------------------------------------------------------------
1 | {
2 | "name":"llapi",
3 | "author":"lition",
4 | "version":[0,0,1],
5 | "desc":"liteloader适配器",
6 | "loadmode": "hybrid",
7 | "permission": "key",
8 | "priority": "main",
9 | "load":true
10 | }
--------------------------------------------------------------------------------
/plugins/mc/index.js:
--------------------------------------------------------------------------------
1 | const EventEmitter = require("events");
2 | const JSON5 = require('json5');
3 | const _config = spark.getFileHelper('mc');
4 |
5 | _config.initFile('config.json', {
6 | group: 114514,
7 | admins: []
8 | });
9 |
10 | _config.initFile('xbox.json', {});
11 |
12 | function convertArrayToNumbersSafe(arr) {
13 | return arr.map(item => {
14 | const number = Number(item);
15 | return isNaN(number) ? 0 : number;
16 | });
17 | }
18 | var config = JSON5.parse(_config.getFile('config.json'));
19 | var xboxs = JSON.parse(_config.getFile('xbox.json'));
20 |
21 | const WebConfigBuilder = spark.telemetry.WebConfigBuilder;
22 | let wbc = new WebConfigBuilder("mc");
23 | wbc.addNumber("group",config.group,"监听的群聊");
24 | wbc.addEditArray("admins",config.admins,"管理员列表(请仅填入数字)");
25 | spark.emit("event.telemetry.pushconfig", wbc);
26 |
27 | spark.on("event.telemetry.updateconfig_mc",(plname,K,newV)=>{
28 | let v = newV;
29 | if(K == 'admins'){
30 | v = convertArrayToNumbersSafe(newV);
31 | }
32 | config[K] = v;
33 | _config.updateFile("config.json",config);
34 | })
35 |
36 | spark.setOwnProperty('mc', {});
37 | spark.mc.config = config;
38 | const eventmitter = new EventEmitter();
39 | //spark.mc['eventmitter'] = eventmitter;
40 |
41 | spark.mc.on = eventmitter.on;
42 | spark.mc.emit = eventmitter.emit;
43 |
44 | mc.listen('onChat', (p, m) => {
45 | spark.mc.emit('onChat', p, m);
46 | });
47 |
48 | mc.listen('onLeft', (p) => {
49 | spark.mc.emit('onLeft', p);
50 | });
51 |
52 | mc.listen('onJoin', (p) => {
53 | spark.mc.emit('onJoin', p);
54 | });
55 |
56 | function getXbox(qid){
57 | return xboxs[qid];
58 | }
59 |
60 | function addXbox(qid, xbox) {
61 | xboxs[qid] = xbox;
62 | _config.updateFile('xbox.json', xboxs);
63 | }
64 |
65 | function hasXbox(xboxid){
66 | return Object.values(xboxs).includes(xboxid);
67 | }
68 |
69 | function remXboxByName(xbox) {
70 | let t = Object.values(xboxs);
71 | if (t.includes(xbox)) {
72 | let num = t.indexOf(xbox);
73 | delete xboxs[Object.keys(xboxs)[num]];
74 | _config.updateFile('xbox.json', xboxs);
75 | }
76 | }
77 |
78 | function remXboxByQid(qid) {
79 | if (xboxs[qid] == undefined) return;
80 | delete xboxs[qid];
81 | _config.updateFile('xbox.json', xboxs);
82 | }
83 | function getQQByXbox(xbox) {
84 | return Object.keys(xboxs).find(key => xboxs[key] === xbox);
85 | }
86 |
87 |
88 | spark.mc.remXboxByName = remXboxByName;
89 | spark.mc.addXbox = addXbox;
90 | spark.mc.remXboxByQid = remXboxByQid;
91 | spark.mc.getXbox = getXbox;
92 | spark.mc.hasXbox = hasXbox;
93 | spark.mc.getQQByXbox = getQQByXbox;
--------------------------------------------------------------------------------
/plugins/mc/spark.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mc",
3 | "author": "lition",
4 | "version": [
5 | 0,
6 | 0,
7 | 1
8 | ],
9 | "desc": "LL适配器",
10 | "loadmode": "hybrid",
11 | "permission": "core",
12 | "priority": "init",
13 | "load":true
14 | }
--------------------------------------------------------------------------------
/plugins/regex/index.js:
--------------------------------------------------------------------------------
1 | const logger = spark.getLogger('regex');
2 | const JSON5 = require('json5');
3 | spark.setOwnProperty('Cmd', {});
4 |
5 | let cfg = spark.getFileHelper('JandLandCmsg');
6 | cfg.initFile('config.json', {})
7 |
8 | let JandQuitConfig = JSON.parse(cfg.getFile('config.json'))
9 |
10 |
11 | const WebConfigBuilder = spark.telemetry.WebConfigBuilder;
12 | let wbc = new WebConfigBuilder("Regex");
13 | wbc.addButtom("regex_penal", () => {
14 | return {
15 | status: 303, // 跳转状态码为303
16 | url: "http://reg.sparkbridge.cn"
17 | }
18 | }, "跳转到正则表达式编辑面板");
19 | spark.emit("event.telemetry.pushconfig", wbc);
20 |
21 | /**
22 | *
23 | * @param {String} str
24 | * @param {RegExpMatchArray} reg
25 | * @returns
26 | */
27 | function buildString(str, reg, ...arg) {
28 | if (spark.debug) console.log(reg);
29 | let i = 0;
30 | reg.forEach(s => {
31 | str = str.replace(`\$${i}`, s);
32 | i++
33 | });
34 | if (str.includes("%")) {
35 | let str_arr = buildPlaceHolder(str);
36 | return str_arr.map((t) => {
37 | if (t.type == 'holder') {
38 | return getPlaceHolder(t.raw, ...arg);
39 | } else {
40 | return t.raw;
41 | }
42 | }).join("");
43 | } else {
44 | return str;
45 | }
46 | }
47 |
48 | spark.Cmd.buildString = buildString;
49 |
50 | function getPlaceHolder(key, ...arg) {
51 | if (PlaceHolders.has(key)) {
52 | return PlaceHolders.get(key)(...arg);
53 | } else {
54 | return 'null';
55 | }
56 | }
57 |
58 | // function buildPlaceHolder(raw) {
59 | // let out_raw = [];
60 | // // 是否正在匹配
61 | // let matching = false;
62 | // // 正在匹配的字符串
63 | // let matching_now = '';
64 | // // 是否跳过当前转义
65 | // let skip_next = false;
66 | // for (let i in raw) {
67 | // let now_i = raw[i];
68 | // //console.log('匹配:'+now_i);
69 | // if (skip_next == false) { // 需要进行变量判断
70 | // if (now_i == '\\') { // 需要直接写入下一位
71 | // skip_next = true;
72 | // //console.log('跳过判断下一位');
73 | // } else if (now_i == '%') {
74 | // // 开始或者结束匹配变量
75 | // if (matching) {
76 | // matching = false;
77 | // out_raw.push({ type: 'holder', raw: matching_now });
78 | // matching_now = '';
79 | // } else {
80 | // matching = true;
81 | // }
82 | // } else {
83 | // if (matching) {
84 | // matching_now += now_i;
85 | // } else {
86 | // out_raw.push({ type: 'plan', raw: now_i })
87 | // }
88 | // }
89 | // } else { //需要直接写入当前字符串
90 | // out_raw.push({ type: 'plan', raw: now_i })
91 | // skip_next = false;
92 | // }
93 | // }
94 | // return out_raw;
95 | // }
96 |
97 | function buildPlaceHolder(raw) {
98 | const tokens = [];
99 | let currentPosition = 0;
100 | let buffer = '';
101 | let isEscaped = false;
102 | let isInsidePlaceholder = false;
103 |
104 | while (currentPosition < raw.length) {
105 | const char = raw[currentPosition];
106 |
107 | if (isEscaped) {
108 | // 处理转义字符,直接添加到缓冲区
109 | buffer += char;
110 | isEscaped = false;
111 | currentPosition++;
112 | continue;
113 | }
114 |
115 | if (char === '\\') {
116 | // 遇到转义字符,标记下一个字符直接处理
117 | isEscaped = true;
118 | currentPosition++;
119 | continue;
120 | }
121 |
122 | if (char === '%') {
123 | // 处理缓冲区内容
124 | if (buffer.length > 0) {
125 | tokens.push({
126 | type: isInsidePlaceholder ? 'holder' : 'plain',
127 | raw: buffer
128 | });
129 | buffer = '';
130 | }
131 |
132 | // 切换占位符状态
133 | isInsidePlaceholder = !isInsidePlaceholder;
134 | currentPosition++;
135 | continue;
136 | }
137 |
138 | // 普通字符添加到缓冲区
139 | buffer += char;
140 | currentPosition++;
141 | }
142 |
143 | // 处理最后剩余的缓冲区内容
144 | if (buffer.length > 0) {
145 | tokens.push({
146 | type: isInsidePlaceholder ? 'holder' : 'plain',
147 | raw: buffer
148 | });
149 | }
150 |
151 | return tokens;
152 | }
153 |
154 | spark.Cmd.buildPlaceHolder = buildPlaceHolder;
155 |
156 |
157 |
158 | const Cmds = new Map();
159 |
160 | function regCmd(head, cb) {
161 | Cmds.set(head, cb);
162 | }
163 |
164 | spark.Cmd.regCmd = regCmd;
165 |
166 | function runCmd(_first, _args, reg, e, reply) {
167 | if (Cmds.has(_first)) {
168 | try {
169 | Cmds.get(_first)(_args, reg, e, reply);
170 | } catch (err) { console.log(err) }
171 | }
172 | }
173 |
174 | regCmd('reply', (_arg, reg, e, reply) => {
175 | let txt1 = buildString(_arg, reg, e);
176 | reply(txt1.replace(/§[a-zA-Z0-9]/g, ''), true);
177 | });
178 |
179 | regCmd('f', (_arg, reg, e, reply) => {
180 | let t_and_a = _arg.split(':');
181 | if (t_and_a.length == 0) {
182 | logger.warn(`执行正则表达式遇到错误:参数不足,请指定私聊联系人`);
183 | }
184 | let target = t_and_a[0];
185 | let arg = t_and_a[1];
186 | spark.QClient.sendPrivateMsg(Number(target), buildString(arg, reg, e))
187 | });
188 |
189 | regCmd('g', (_arg, reg, e, reply) => {
190 | let t_and_a = _arg.split(':');
191 | if (t_and_a.length == 0) {
192 | logger.warn(`执行正则表达式遇到错误:参数不足,请指定群号`);
193 | }
194 | let target = t_and_a[0];
195 | let arg = t_and_a[1];
196 | spark.QClient.sendGroupMsg(Number(target), buildString(arg, reg, e))
197 | })
198 |
199 | regCmd('t', (arg, reg, e, reply) => {
200 | let t_and_m = arg.split(':');
201 | let tp = t_and_m[0];
202 | let ms = t_and_m[1];
203 |
204 | if (tp == 'all') {
205 | let tellallMsg = buildString(ms, reg, e).trim()
206 | // console.log(tellallMsg)
207 | if (tellallMsg.length > JandQuitConfig.chatMaxLength + ms.replace(/\$1/g, '').length) {
208 | tellallMsg = '[群聊]聊天长度过长,将不会转发'
209 | }
210 |
211 | mc.broadcast(tellallMsg);
212 | } else {
213 | let top = mc.getPlayer(tp);
214 | if (top) {
215 | top.tell(buildString(ms, reg, e));
216 | }
217 | }
218 | })
219 | regCmd('run', (arg, reg, e, reply) => {
220 | let command = arg;
221 | let run_str = buildString(command, reg, e).trim();
222 | let r = mc.runcmdEx(run_str);
223 | if (!r.success) {
224 | reply(run_str + '执行失败');
225 | }
226 | else {
227 | // 没有
228 | reply(r.output.replace(/§[a-zA-Z0-9]/g, ''), true);
229 | }
230 | })
231 |
232 | regCmd('addwl', (arg, reg, e, reply) => {
233 | let command = arg.split(":");
234 | let xboxid = buildString(command[0], reg, e).trim();
235 | if (!spark.mc.hasXbox(xboxid) && spark.mc.getXbox(e.user_id) == undefined) {
236 | spark.mc.addXbox(e.user_id, xboxid);
237 | reply(xboxid + '绑定成功', true);
238 | if (command[1] == 'true') {
239 | mc.runcmd('allowlist add "' + xboxid + '"');
240 | }
241 | }
242 | else {
243 | reply('绑定失败,请检查是否已经绑定', true);
244 | }
245 | })
246 |
247 | regCmd('remwl', (arg, reg, e, reply) => {
248 | //console.log(e);
249 | //console.log(spark.mc.getXbox(e.sender.user_id));
250 | if (spark.mc.getXbox(e.sender.user_id) != undefined) {
251 | let xb = spark.mc.getXbox(e.user_id);
252 | spark.mc.remXboxByQid(e.sender.user_id);
253 | reply('解绑成功', true);
254 | mc.runcmd('allowlist remove "' + xb + '"');
255 | }
256 | });
257 |
258 | /**
259 | *
260 | * @param {String} cmd
261 | */
262 | function commandParse(cmd, reg, e, reply) {
263 | let items = cmd.split("|");
264 | if (items.length == 1) {
265 | //logger.warn(`执行正则表达式:${cmd} 遇到错误:参数不足,请写入参数`);
266 | }
267 | let _first = items[0];
268 | let _arg = items[1];
269 | if (spark.debug)
270 | logger.info('执行正则表达式命令:' + _first + ',参数:' + _arg);
271 | runCmd(_first, _arg, reg, e, reply);
272 | }
273 |
274 | const PlaceHolders = new Map();
275 |
276 | function regPlaceHolder(key, recall) {
277 | PlaceHolders.set(key, recall);
278 | }
279 |
280 | spark.Cmd.regPlaceHolder = regPlaceHolder;
281 |
282 | spark.Cmd.addRegex = (key, item) => {
283 | if (regexs[key] != undefined) return false;
284 | regexs[key] = item;
285 | return true;
286 | }
287 |
288 | const GROUP = spark.mc.config.group;
289 | const ADMINS = spark.mc.config.admins;
290 |
291 | const _config = spark.getFileHelper('regex');
292 | const PRE_CONFIG = {
293 | "^我是(.+)": {
294 | cmd: 'reply|你是$1',
295 | adm: false
296 | },
297 | "^bot测试": {
298 | cmd: 'reply|已上线',
299 | adm: true
300 | },
301 | "^绑定(.+)": {
302 | cmd: 'addwl|$1:true',
303 | adm: false
304 | },
305 | "^解绑": {
306 | cmd: 'remwl|$1',
307 | adm: false
308 | },
309 | "^查服": {
310 | cmd: 'run|list',
311 | adm: false
312 | },
313 | "^chat (.+)": {
314 | cmd: 't|all:[群聊]%USER_XBOXID% >> $1',
315 | adm: false
316 | },
317 | "^执行(.+)": {
318 | cmd: 'run|$1',
319 | adm: true
320 | }
321 | }
322 |
323 | _config.initFile('data.json', PRE_CONFIG, false);
324 | const regexs = JSON5.parse(_config.getFile('data.json'));
325 |
326 | function formatMsg(msg) {
327 | const formattedMessages = msg.map((t) => {
328 | switch (t.type) {
329 | case 'at':
330 | try {
331 | if (spark.mc.getXbox(t.data.qq) == undefined) {
332 | // const data = await spark.QClient.getGroupMemberInfo(spark.mc.config.group, t.data.qq);
333 | // if (data) {
334 | // let name
335 | // if (data.card == "") {
336 | // name = data.nickname;
337 | // }
338 | // else {
339 | // name = data.card;
340 | // }
341 | // return '@' + name;
342 | // } else {
343 | // return '@' + t.data.qq;
344 | // }
345 | return '@' + t.data.qq;
346 | } else {
347 | return '@' + spark.mc.getXbox(t.data.qq);
348 | }
349 | } catch (error) {
350 | console.error(error);
351 | return '@' + t.data.qq;
352 | }
353 | case 'text':
354 | return t.data.text;
355 | case 'image':
356 | return '[图片]';
357 | case 'face':
358 | return '[表情]';
359 | }
360 | });
361 |
362 | return formattedMessages.join('');
363 | }
364 |
365 | //spark.debug = true;
366 | spark.on('message.group.normal', (e, reply) => {
367 | //reply("?")
368 | const { raw_message, group_id, user_id } = e;
369 | //console.log(raw_message, group_id, user_id ,GROUP);
370 | const raw = formatMsg(e.message);
371 |
372 | //console.log(raw);
373 | if (group_id !== GROUP) return;
374 | for (let reg_it in regexs) {
375 | //console.log(reg_it);
376 |
377 | let tmp = raw.match(reg_it);
378 | if (tmp == null) continue;
379 | if (spark.debug) console.log('regex working...', reg_it);
380 | if (regexs[reg_it].adm == true && !ADMINS.includes(user_id)) {
381 | reply("执行失败,权限不足");
382 | return;
383 | }
384 | try {
385 | regexs[reg_it].cmd.split(';').forEach(regtmp => {
386 | commandParse(regtmp, tmp, e, reply);
387 |
388 | })
389 | } catch (err) {
390 | console.log(err);
391 | }
392 | }
393 | });
394 |
395 |
396 | spark.on('notice.group_decrease', (e) => {
397 |
398 | const { self_id, user_id, group_id } = e;
399 | if (group_id != spark.mc.config.group || user_id == self_id) return
400 | if (spark.mc.getXbox(user_id) != undefined) {
401 | let xb = spark.mc.getXbox(user_id);
402 | spark.mc.remXboxByQid(user_id);
403 | spark.QClient.sendGroupMsg(group_id, `用户${xb}退出群聊,已从白名单移除`)
404 | mc.runcmd('allowlist remove "' + xb + '"');
405 | }
406 | })
--------------------------------------------------------------------------------
/plugins/regex/spark.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "regex",
3 | "author": "lition",
4 | "version": [
5 | 0,
6 | 0,
7 | 1
8 | ],
9 | "desc": "正则表达式适配器",
10 | "loadmode": "hybrid",
11 | "permission": "core",
12 | "priority": "main",
13 | "load":true
14 | }
--------------------------------------------------------------------------------
/plugins/telemetry/README.md:
--------------------------------------------------------------------------------
1 | # telemetry
2 |
3 | 作为SparkBridge的网页可视化配置编辑器
4 |
5 | ## 接入方法
6 |
7 |
8 | ### 推送配置
9 |
10 | 注:以下示例注册了一个名为`example`的插件
11 | ``` js
12 | const WebConfigBuilder = spark.telemetry.WebConfigBuilder;
13 |
14 | let wbc = new WebConfigBuilder("example");
15 | wbc.addEditArray('admins',config.admins,'管理员列表');
16 | wbc.addNumber("group",config.group,'监听的群聊');
17 | spark.emit("event.telemetry.pushconfig", wbc);
18 |
19 | ```
20 |
21 | ### 监听更改
22 | ``` js
23 | spark.on("event.telemetry.updateconfig_example",(plname,K,newV)=>{
24 | // plname 插件名称
25 | // K 修改的配置项
26 | // newV 配置项更新的值
27 | })
28 | ```
29 |
30 | ## API
31 |
32 | ``` js
33 | const WebConfigBuilder = spark.telemetry.WebConfigBuilder;
34 |
35 | let wbc = new WebConfigBuilder("example"); //插件名称
36 |
37 | wbc.addEditArray("配置项名称","配置值","配置描述"); // 添加一个可编辑数组
38 |
39 | wbc.addChoosing("配置项名称","配置可选列表","配置值(注意此项为number)","配置描述"); // 添加一个可选择数组
40 |
41 | wbc.addSwitch("配置项名称","配置值(注意此项为boolen值)","配置描述"); //添加一个开关,此项返回true/false
42 |
43 | wbc.addText("配置项名称","配置值","配置描述"); //添加一个可编辑文本
44 |
45 | wbc.addNumber("配置项名称","配置值","配置描述"); // 添加一个可编辑数值
46 | ```
--------------------------------------------------------------------------------
/plugins/telemetry/index.js:
--------------------------------------------------------------------------------
1 | //spark.debug = true;
2 | const lg = require('../../handles/logger');
3 | const logger = lg.getLogger('telemetry');
4 | const { WebConfigBuilder, WebConfigTye } = require('./webConfig');
5 | const _config = spark.getFileHelper('telemetry');
6 | _config.initFile('config.json', {
7 | webPort: 3002,
8 | lock_panel: false,
9 | allow_global: true,
10 | pwd_timeout: 5,
11 | reply_after_auth: true
12 | });
13 | var config = JSON.parse(_config.getFile('config.json'));
14 |
15 | var GConfig = {};
16 |
17 | spark.setOwnProperty("telemetry", { WebConfigBuilder, WebConfigTye });
18 |
19 | spark.on("event.telemetry.pushconfig", (cObj) => {
20 | if (GConfig[cObj.plname]) {
21 | return;
22 | }
23 | GConfig[cObj.plname] = cObj.configObj;
24 | });
25 | spark.emit("event.telemetry.ready");
26 |
27 | spark.on("event.telemetry.updateconfig_telemetry", (id, changeK, value) => {
28 | // console.log("触发回调",id,changeK,value);
29 | config[changeK] = value;
30 | _config.updateFile('config.json', config);
31 | });
32 |
33 | function generateRandomPassword(length) {
34 | const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
35 | let password = '';
36 | for (let i = 0; i < length; i++) {
37 | const randomIndex = Math.floor(Math.random() * characters.length);
38 | password += characters[randomIndex];
39 | }
40 | return password;
41 | }
42 |
43 | var currentPwd = {};
44 | var ip_whitelist = {};
45 |
46 | function isIpAllowed(req) {
47 | const clientIp = req.socket.remoteAddress;
48 | const inclue_id = Object.values(ip_whitelist).includes(clientIp);
49 | if (config.lock_panel) {
50 | // 锁面板,判断IP
51 | // console.log(inclue_id);
52 | return inclue_id;
53 | } else {
54 | return true;
55 | }
56 | }
57 |
58 | //const ADMINS = ;
59 | function findQid(pwd) {
60 | for (let id in currentPwd) {
61 | if (currentPwd[id] == pwd) {
62 | return id;
63 | }
64 | }
65 | return null;
66 | }
67 |
68 | spark.on("message.private.friend", (obj, reply) => {
69 | if (obj.raw_message != "获取密码") return;
70 | if (spark.mc.config.admins.includes(obj.sender.user_id)) {
71 | if (currentPwd[obj.sender.user_id]) {
72 | reply('您的临时密码为:' + currentPwd[obj.sender.user_id], true);
73 | return;
74 | }
75 | const pwd = generateRandomPassword(12);
76 | currentPwd[obj.sender.user_id] = pwd;
77 | reply('您的临时密码为:' + pwd, true);
78 | setTimeout(() => {
79 | delete currentPwd[obj.sender.user_id];
80 | delete ip_whitelist[obj.sender.user_id];
81 | }, config.pwd_timeout * 60000); // 过期时间
82 | }
83 | });
84 |
85 |
86 |
87 | const wbc = new WebConfigBuilder("telemetry");
88 | wbc.addNumber("webPort", config.webPort, "网页端口");
89 | wbc.addSwitch("allow_global", config.allow_global, "是否允许外网访问");
90 | wbc.addSwitch("lock_panel", config.lock_panel, "是否锁定面板,锁定后只能提供私聊机器人获取临时密码");
91 | wbc.addSwitch("reply_after_auth", config.reply_after_auth, "登入面板后是否提醒");
92 | wbc.addNumber("pwd_timeout", config.pwd_timeout, "密码过期时间(单位分钟)");
93 |
94 |
95 | // 以下为http服务器部分
96 |
97 | const http = require('http');
98 | const { parse } = require('url');
99 | const fs = require('fs');
100 |
101 | // 创建HTTP服务器
102 | const server = http.createServer((req, res) => {
103 | const { pathname, query } = parse(req.url);
104 | // console.log(req.socket.remoteAddress);
105 | // const isLocal = req.headers.host.startsWith('localhost') || req.headers.host.startsWith('127.0.0.1');
106 | const isLocal = req.socket.remoteAddress === '::1' || req.socket.remoteAddress === '::ffff:127.0.0.1';
107 | if (!isLocal && config.allow_global == false) {
108 | logger.info(`收到外部网络${req.socket.remoteAddress}的访问,已拒绝`);
109 | return;
110 | }
111 | // 定义一个中间件来处理请求数据
112 | function handleRequest(req, res, next) {
113 | if (req.method === 'POST') {
114 | let body = [];
115 | req.on('data', chunk => body.push(chunk));
116 | req.on('end', () => {
117 | body = Buffer.concat(body).toString();
118 | next(body);
119 | });
120 | } else {
121 | next(null);
122 | }
123 | }
124 | // 检查请求方法
125 | if (req.method === 'GET') {
126 | if (pathname === '/') {
127 | // 重定向到/page/index.html
128 | res.writeHead(302, { 'Location': '/page/index' });
129 | res.end();
130 | } else if (pathname.startsWith('/page/')) {
131 | // 用户访问/page/xxx时,读取本地的xxx.html文件
132 | try {
133 | var pageName = req.url.substring('/page/'.length);
134 | if (pageName.includes(".html")) {
135 | pageName = pageName.replace(".html", "");
136 | }
137 | const filePath = `${__dirname}/web/${pageName}.html`;
138 | const content = fs.readFileSync(filePath, 'utf-8');
139 | res.writeHead(200, { 'Content-Type': 'text/html' });
140 | res.end(content);
141 | } catch (e) {
142 | console.log('get err', e);
143 | }
144 | } else if (pathname.startsWith('/api/')) {
145 | // 处理GET类型的API请求
146 | try {
147 | handleApiRequest(req, pathname.substring('/api/'.length), null, req.method, res);
148 | } catch (err) { console.log(err); }
149 | } else if (pathname.startsWith('/static/')) {
150 | try {
151 | var fileName = req.url.substring('/static/'.length);
152 | const filePath = `${__dirname}/static/${fileName}`;
153 | let data = fs.readFileSync(filePath, { encoding: 'utf-8' });
154 | if (fileName.endsWith(".js"))
155 | res.setHeader("Content-Type", "text/javascript");
156 | res.end(data);
157 | } catch (err) {
158 | console.log(err);
159 | }
160 | }
161 |
162 | else {
163 | // 其他GET请求,返回404页面
164 | // 这里需要实现404页面的发送逻辑
165 | // 其他请求,返回404页面,并重定向到/page/index.html
166 | res.writeHead(404, { 'Content-Type': 'text/html' });
167 | //res.end(fs.readFileSync('./pages/404.html', 'utf-8'));
168 | //res.writeHead(302, { 'Location': '/page/index.html' });
169 | res.end();
170 | }
171 | } else if (req.method === 'POST') {
172 | // 处理POST请求
173 | handleRequest(req, res, body => {
174 | if (pathname.startsWith('/api/')) {
175 | // 处理POST类型的API请求
176 | try {
177 | handleApiRequest(req, pathname.substring('/api/'.length), body, req.method, res);
178 | } catch (err) { console.log(err); }
179 | } else {
180 | // POST请求到非API路径,返回405 Method Not Allowed
181 | res.writeHead(405, { 'Content-Type': 'text/plain' });
182 | res.end('Method Not Allowed');
183 | }
184 | });
185 | }
186 | else {
187 | // 其他HTTP方法,返回405 Method Not Allowed
188 | res.writeHead(405, { 'Content-Type': 'text/plain' });
189 | res.end('Method Not Allowed');
190 | }
191 | });
192 |
193 | function handleApiRequest(req, apiName, requestBody, method, res) {
194 | if (spark.debug) logger.info(method + " >> " + apiName);
195 |
196 | // 检查是否是登录接口
197 | const isLoginRequest = apiName === "login";
198 |
199 | // 如果不是登录请求,检查IP是否在白名单中
200 | if (!isLoginRequest && !isIpAllowed(req)) {
201 | logger.warn(`IP [${req.socket.remoteAddress}] 不在白名单中,拒绝访问API: ${apiName}`);
202 | res.writeHead(403, { 'Content-Type': 'text/plain' });
203 | res.end('Access denied: Your IP is not allowed to access this API.');
204 | return;
205 | }
206 |
207 | // 根据不同的请求方法处理API请求
208 | if (method === 'GET') {
209 | // 处理GET请求
210 | var responseContent = {};
211 | switch (apiName) {
212 | case "globa_config":
213 | responseContent = {
214 | status: 'success',
215 | message: `GET request for ${apiName} received.`,
216 | data: GConfig
217 | };
218 | break;
219 | }
220 | res.writeHead(200, { 'Content-Type': 'application/json' });
221 | res.end(JSON.stringify(responseContent));
222 | } else if (method === 'POST') {
223 | // 处理POST请求
224 | try {
225 | const parsedBody = requestBody ? JSON.parse(requestBody) : {};
226 | var responseContent = {};
227 | switch (apiName) {
228 | case "update_global_config":
229 | let cgK = parsedBody.value;
230 | if (GConfig[parsedBody.plugin_id][parsedBody.changeK].type == 5) {
231 | cgK = Number(parsedBody.value);
232 | }
233 | GConfig[parsedBody.plugin_id][parsedBody.changeK].value = cgK;
234 | spark.emit("event.telemetry.updateconfig_" + parsedBody.plugin_id, parsedBody.plugin_id, parsedBody.changeK, cgK);
235 | responseContent.code = 0;
236 | break;
237 | case "login":
238 | // 登录逻辑
239 | if (config.lock_panel) {
240 | let pwd = parsedBody.password;
241 | let qid = findQid(pwd);
242 | logger.info(`IP:[${req.socket.remoteAddress}] 尝试连接面板`);
243 | if (qid) {
244 | responseContent = {
245 | expires_day: 0,
246 | message: `您使用管理账户:${qid} 登入`,
247 | status: 200
248 | };
249 | if (config.reply_after_auth) spark.QClient.sendPrivateMsg(qid, `登入提示:\n您使用密码登入SpakrBridge面板\nIP:[${req.socket.remoteAddress}]\n若非本人操作请前往控制台查看`);
250 | logger.info(`管理员:${qid} 登入面板`);
251 | ip_whitelist[qid] = req.socket.remoteAddress;
252 | } else {
253 | responseContent = {
254 | expires_day: 0,
255 | message: "无法找到您的账户,请重新登入",
256 | status: 0
257 | };
258 | }
259 | } else {
260 | responseContent = {
261 | expires_day: 0,
262 | message: "未开启面板锁定,已放行",
263 | status: 200
264 | };
265 | }
266 | break;
267 | case "button_click_event":
268 | // console.log(parsedBody);
269 | responseContent = GConfig[parsedBody.plugin_name][parsedBody.event_name].value();
270 | break
271 | }
272 | res.writeHead(200, { 'Content-Type': 'application/json' });
273 | res.end(JSON.stringify(responseContent));
274 | } catch (error) {
275 | // 如果请求体解析出错,返回400 Bad Request
276 | res.writeHead(400, { 'Content-Type': 'text/plain' });
277 | res.end('Invalid JSON payload');
278 | }
279 | }
280 | }
281 |
282 | server.on('error', (err) => {
283 | if (err.code === 'EADDRINUSE') {
284 | logger.warn(`Port ${config.webPort} is already in use, trying next port...`);
285 | config.webPort++;
286 | server.close(() => { // 关闭当前服务器
287 | startServer(); // 尝试下一个端口
288 | });
289 | } else {
290 | console.error(err);
291 | }
292 | });
293 |
294 | function startServer() {
295 | logger.info(`Server starting on ${config.webPort}...`);
296 | try {
297 |
298 | server.listen(config.webPort, () => {
299 | logger.info('服务器运行在 http://localhost:' + config.webPort + '/');
300 | });
301 | } catch (e) { console.log(e); }
302 | }
303 |
304 |
305 | setTimeout(() => {
306 | startServer();
307 | }, 500);
308 |
--------------------------------------------------------------------------------
/plugins/telemetry/spark.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "telemetry",
3 | "author": "lition",
4 | "version": [
5 | 0,
6 | 0,
7 | 2
8 | ],
9 | "desc": "回传数据定义器",
10 | "loadmode": "hybrid",
11 | "permission": "core",
12 | "priority": "base",
13 | "load":true
14 | }
--------------------------------------------------------------------------------
/plugins/telemetry/static/index-CzIKTSi7.css:
--------------------------------------------------------------------------------
1 | *{margin:0;padding:0;box-sizing:border-box}html[theme=light]{--head_end_back_color: rgb(234, 247, 247);--head_end_font_color: #1a1a1c;--body_back_color: rgb(220, 227, 232);--button_color: rgb(92, 158, 191)}html[theme=dark]{--head_end_back_color: #1a1a1c;--head_end_font_color: rgb(226, 238, 238);--body_back_color: rgb(39, 39, 39);--table_back_color: #1a1a1c;--table_font_color: rgb(177, 188, 188);--table_border_color: rgb(70, 69, 69);--switch_open_back_color: rgb(226, 238, 238);--switch_close_color: rgb(62, 199, 55);--switch_open_color: rgb(71, 118, 200);--button_color: rgba(44, 56, 134, .71);--font_color: aliceblue}html,body,#app{width:100%;height:100%}input,button,select,dialog{outline:none;border:none;background-color:initial;cursor:pointer}input:focus,button:focus,dialog:focus{outline:none}.notification[data-v-fe5324ef]{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:400px;padding:20px;height:300px;border-radius:10px;background:var(--head_end_back_color);box-shadow:0 2px 10px #0000001a;color:var(--font_color);z-index:1000}.notification-title[data-v-fe5324ef]{font-size:20px;font-weight:700;margin-bottom:10px;text-align:center;color:var(--head_end_font_color)}.notification-content[data-v-fe5324ef]{font-size:16px;overflow:auto;max-height:200px;padding:10px}.notification-close[data-v-fe5324ef]{position:absolute;bottom:10px;right:10px;color:var(--font_color);background-color:var(--head_end_back_color);border:none;padding:5px 10px;border-radius:5px;cursor:pointer;transition:all .3s ease;font-size:16px;font-weight:700}.body[data-v-fcc17ae6]{display:flex;width:100%;height:100%}.body-open[data-v-fcc17ae6]{display:flex;margin:auto;font-weight:700}.button[data-v-fcc17ae6]{width:40%;max-height:25px;height:100%;border-radius:5px;background-color:var(--button_color);text-align:center;cursor:pointer;font-size:16px;color:var(--font_color)}.button[data-v-fcc17ae6]:hover{background-color:var(--body_back_color)}.dialog[data-v-fcc17ae6]{position:relative;display:none;width:100%;height:100%}.dialog-body[data-v-fcc17ae6]{margin:auto;cursor:auto;width:60%;min-width:495px;min-height:600px;height:60%;border-radius:15px;background-color:var(--head_end_back_color);color:var(--table_font_color)}.dialog-body-content[data-v-fcc17ae6]{display:flex;flex-direction:column;width:100%;height:calc(100% - 30px);background-color:var(--head_end_back_color);border-bottom-left-radius:inherit;border-bottom-right-radius:inherit}.dialog-body-content-btn[data-v-fcc17ae6]{width:100%;margin-top:auto;height:30px;border-radius:inherit;display:flex}.dialog-body-content-btn>div[data-v-fcc17ae6]{display:flex;width:33.3333333333%;justify-content:center;align-items:center;border-radius:inherit}.dialog-body-content-btn>div[data-v-fcc17ae6]:hover{background-color:var(--head_end_font_color);color:var(--head_end_back_color);cursor:pointer}.dialog-body-content-array[data-v-fcc17ae6]{display:flex;flex-direction:column;width:100%;height:calc(100% - 60px);overflow-y:auto}.dialog-body-content-array-input[data-v-fcc17ae6]{display:flex;width:100%;min-height:40px;height:40px;margin-top:35px;margin-left:auto}.dialog-body-content-array-input>div[data-v-fcc17ae6]{display:flex;width:10%;border-radius:10px;align-items:center;font-size:medium;font-weight:700;justify-content:center}.dialog-body-content-array-input>div[data-v-fcc17ae6]:hover{box-shadow:inset 0 0 5px #00000080}.dialog-body-content-array-input>input[data-v-fcc17ae6]{width:80%;min-height:40px;height:40px;font-size:large;text-align:center;margin-left:auto;border-radius:10px;background-color:var(--body_back_color);box-shadow:inset 0 0 5px #00000080}.dialog-body-content-array-input>input[data-v-fcc17ae6]:focus{background-color:var(--head_end_font_color);color:var(--head_end_back_color)}.dialog-body-head[data-v-fcc17ae6]{display:flex;cursor:copy;width:100%;height:30px;font-size:larger;font-weight:bolder;border-top-left-radius:inherit;border-top-right-radius:inherit;color:#faebd7}.dialog-body-head-title[data-v-fcc17ae6]{margin:auto}.dialog[data-v-fcc17ae6]::backdrop{width:100%;height:100%;border-radius:10px}.select[data-v-fcc17ae6]{width:90%;border-radius:5px;background-color:var(--table_back_color);color:var(--table_font_color)}.select>option[data-v-fcc17ae6]{outline:10px solid #a41818;border-radius:none;background-color:var(--table_back_color)}.input[data-v-fcc17ae6]{width:100%;color:var(--table_font_color);height:100%;background-color:initial;font-weight:600;font-size:.9rem}@keyframes switch-off-fcc17ae6{0%{border-radius:inherit;border-top-right-radius:5px;border-bottom-right-radius:5px;background-color:var(--switch_open_color);transform:translate(0)}to{border-radius:10px;background-color:var(--switch_close_color);transform:translate(30px)}}@keyframes switch-on-fcc17ae6{to{border-radius:10px;transform:translate(0);background-color:var(--switch_open_color)}0%{border-radius:inherit;border-top-left-radius:5px;border-bottom-left-radius:5px;background-color:var(--switch_close_color);transform:translate(30px)}}.switch[data-v-fcc17ae6]{position:relative;width:50px;max-height:25px;height:100%;border-radius:10px;background-color:var(--switch_open_back_color)}.switch[data-v-fcc17ae6]:before{position:absolute;content:"";width:20px;height:100%;animation:switch-on-fcc17ae6 .85s forwards}.switch-on[data-v-fcc17ae6]:before{animation:switch-off-fcc17ae6 1s forwards}table[data-v-dcd60114]{display:table;width:96%;max-width:1240px;border-collapse:collapse;margin:20px auto auto;color:var(--table_font_color)}table>tr>th[data-v-dcd60114],table>tr>td[data-v-dcd60114]{width:auto;height:30px;border:1px solid var(--table_border_color);padding:8px;text-align:left}table>h2[data-v-dcd60114]{margin-bottom:20px}table>tr>#val[data-v-dcd60114]{cursor:pointer}table>tr>th[data-v-dcd60114]{background-color:var(--table_back_color)}.login[data-v-6c953b80]{position:absolute;display:flex;flex-direction:column;justify-content:center;align-items:center;overflow:hidden;overflow-y:hidden;font-size:20px;color:#f0f8ff;width:100vw;height:100vh;background-color:var(--head_end_back_color)}.login-body[data-v-6c953b80]{display:inherit;flex-direction:column;margin:auto;border-radius:15px;background-color:var(--body_back_color);width:50%;min-width:500px;height:20%;min-height:300px}.login-body>div>span[data-v-6c953b80]{margin:auto;font-size:large;font-weight:700}.login-body-title[data-v-6c953b80]{display:inherit;width:50%;height:40px;border-radius:20px;margin:10px auto auto;background-color:var(--head_end_back_color)}.login-body-content[data-v-6c953b80]{display:inherit}.login-body-content-input[data-v-6c953b80]{display:flex;width:70%;height:40px;background-color:var(--head_end_font_color);border-radius:10px;margin:30px auto auto;text-align:center}.login-body-content-input>input[data-v-6c953b80]{width:80%;height:100%;font-size:large;margin:auto}.login-body-button[data-v-6c953b80]{display:flex;width:40%;height:40px;margin:auto auto 10px;background-color:var(--head_end_back_color);border-radius:25px;cursor:pointer}.login-footer[data-v-6c953b80]{display:flex;margin:auto auto 10px}@keyframes menu_btn_hover-a90f8584{0%{width:95%;height:95%;background-color:var(--head_end_back_color);box-shadow:inset 0 8px 5px var(--head_end_back_color)}to{color:var(--head_end_back_color);box-shadow:inset 0 1px 20px var(--head_end_back_color);background-color:var(--head_end_font_color)}}.telemetry-data[data-v-a90f8584]{display:flex;width:100%;height:100%}.header[data-v-a90f8584]{position:absolute;display:flex;width:100%;height:60px;background-color:var(--head_end_back_color);font-weight:700;color:var(--head_end_font_color)}.header-logo[data-v-a90f8584]{width:40%;height:100%;display:flex;margin:auto}.header-logo>span[data-v-a90f8584]{margin:auto auto auto 60px}.header-logo>img[data-v-a90f8584]{height:70%;margin:auto 0;border-radius:5px}.header-menu[data-v-a90f8584]{display:flex;width:50%;margin-left:auto}.header-menu-btn[data-v-a90f8584]{display:flex;width:25%;height:100%;align-items:center;justify-content:center}.header-menu-btn:hover>span[data-v-a90f8584]{animation:forwards .4s menu_btn_hover-a90f8584 .1s}.header-menu-btn>span[data-v-a90f8584]{display:flex;align-items:center;justify-content:center;width:100%;height:100%;font-size:17px}.body[data-v-a90f8584]{position:absolute;margin-top:60px;overflow-y:scroll;height:calc(100% - 100px);scrollbar-width:none;width:100%;background-color:var(--body_back_color);color:var(--head_end_font_color);box-shadow:0 0 10px #5c5c5f}.end[data-v-a90f8584]{color:var(--head_end_font_color);display:flex;width:100%;position:absolute;bottom:0;height:40px;background-color:var(--head_end_back_color);box-shadow:0 0 10px #5c5c5f;align-items:center;justify-content:center;font-weight:bolder}
2 |
--------------------------------------------------------------------------------
/plugins/telemetry/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Sparkbridge2
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/plugins/telemetry/webConfig.js:
--------------------------------------------------------------------------------
1 | class WebConfigTye{
2 | static EditArray = 1;
3 | static switch = 2;
4 | static Choosing = 3;
5 | static text = 4;
6 | static number = 5;
7 | static buttom = 6;
8 | }
9 |
10 |
11 | class WebConfigBuilder{
12 | plname;
13 | configObj = {};
14 | constructor(plname){
15 | this.plname = plname;
16 | }
17 | addEditArray(k,v,desc = "描述"){
18 | this.#setKV(k,v,WebConfigTye.EditArray,desc);
19 | }
20 | addChoosing(k,opt,v,desc = "描述"){
21 | if(this.configObj[k]){
22 | throw new Error(`plugin ${this.plname} add a already key (${k}) when build web config`);
23 | }
24 | this.configObj[k] = {
25 | type: WebConfigTye.Choosing,
26 | value: v,
27 | options: opt,
28 | desc
29 | }
30 | }
31 | addSwitch(k,v,desc = "描述"){
32 | this.#setKV(k,v,WebConfigTye.switch,desc);
33 | }
34 | addText(k,v,desc = "描述"){
35 | this.#setKV(k,v,WebConfigTye.text,desc);
36 | }
37 | addNumber(k,v,desc = "描述"){
38 | this.#setKV(k,v,WebConfigTye.number,desc);
39 | }
40 | addButtom(k,v,desc = "描述"){
41 | this.#setKV(k, v, WebConfigTye.buttom, desc);
42 | }
43 | #setKV(k,v,t,desc){
44 | if(this.configObj[k]){
45 | throw new Error(`plugin ${this.plname} add a already key (${k}) when build web config`);
46 | }
47 | this.configObj[k] = {
48 | type: t,
49 | value: v,
50 | desc
51 | }
52 | }
53 | }
54 |
55 | module.exports = {WebConfigTye,WebConfigBuilder};
--------------------------------------------------------------------------------
/sandbox.bat:
--------------------------------------------------------------------------------
1 | chcp 65001
2 | @echo off
3 | title 调试模式
4 | REM 检测Node.js和node_modules文件夹的批处理脚本
5 |
6 | REM 检测Node.js环境
7 | where node >nul 2>&1
8 | if %errorlevel% neq 0 (
9 | echo Node.js 未安装或未添加到系统环境变量。
10 | goto end
11 | )else (
12 | echo Node.js 已安装。
13 | node -v
14 | )
15 | REM 检测node_modules文件夹是否存在
16 | if not exist node_modules (
17 | echo node_modules 文件夹不存在,正在尝试安装...
18 | REM 执行npm install
19 | npm install
20 | if %errorlevel% neq 0 (
21 | echo npm install 失败,请检查网络连接或npm配置。
22 | goto end
23 | ) else (
24 | echo npm install 完成。
25 | )
26 | ) else (
27 | echo node_modules 文件夹已存在。
28 | )
29 |
30 | echo 如果你是开服用户,你不需要打开这个文件,这是开发模拟用的。
31 | npm run dev
32 |
33 | :end
34 | pause
--------------------------------------------------------------------------------
/spark/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparkbridge/sparkbridge2/06c76d137352112fdee71bf71b045437c831e17b/spark/README.md
--------------------------------------------------------------------------------
/spark/index.js:
--------------------------------------------------------------------------------
1 | const adapter = require('../Qadapter');
2 | const fhelper = require('../handles/file');
3 | const logger = require('../handles/logger');
4 | const PLUGIN_ROOT_DIR = './plugins/nodejs/sparkbridge2';
5 | const PLUGIN_DATA_DIR = './plugins/sparkbridge2';
6 | class Spark {
7 | QClient;
8 | debug = false;
9 | constructor(ws_type,target,port,qid, pwd,customs) {
10 | this.QClient = new adapter(ws_type,target,port,qid, pwd,customs);
11 | this.QClient.login();
12 | }
13 | getLogger(header){
14 | return logger.getLogger(header);
15 | }
16 | on(evt, func) {
17 | this.QClient.on(evt, func);
18 | }
19 | emit(evt,...arg){
20 | this.QClient.emit(evt,...arg);
21 | }
22 | addInterceptor(evt,interceptor) {
23 | this.QClient.addInterceptor(evt,interceptor);
24 | }
25 | /**
26 | *
27 | * @param {*} plugin_name
28 | * @returns {fhelper.FileObj}
29 | */
30 | getFileHelper(plugin_name) {
31 | return new fhelper.FileObj(plugin_name);
32 | }
33 | setOwnProperty(k, v) {
34 | this[k] = v;
35 | }
36 | }
37 |
38 | module.exports = Spark;
--------------------------------------------------------------------------------
/update.bat:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sparkbridge/sparkbridge2/06c76d137352112fdee71bf71b045437c831e17b/update.bat
--------------------------------------------------------------------------------