├── .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 |
205 | 209 |
210 | 211 | 212 |
213 |
214 | 215 | 216 |
217 |
218 | 219 | 220 |
221 | 229 |
230 | 231 | 232 |
233 | 234 |
235 |
236 | 237 | 238 | 239 | 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 | 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 | 190 | 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 | 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 | 178 | 179 |
180 |
181 | 182 |
183 |
184 | 185 | 188 |
189 |
190 |
191 | 192 |
193 |
194 |

消息将在此显示...

195 |
196 |
197 | 198 | 199 |
200 |
201 |
202 |
203 |
204 |

205 |
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 --------------------------------------------------------------------------------