├── .eslintrc.json ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ └── sbom.yml ├── .gitignore ├── .npmrc ├── LICENSE ├── README.md ├── default-config.json ├── docs ├── configuration-guide.md └── images │ ├── RTFM.png │ ├── cliGui.png │ ├── grep.png │ └── webhooks.png ├── package.json ├── pnpm-lock.yaml ├── proxy.js ├── scripts ├── debugFetch.js ├── findCommitBySubstringInFileHash.sh ├── processArchives.js └── updateNgrokBinary.js ├── test ├── test-config.json └── test.js └── util ├── chatty.js ├── config.js ├── downloader.js ├── logger.js ├── mineflayer.js ├── ngrok.js ├── notifier.js ├── queue.js └── schemas.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "commonjs": true, 4 | "es2021": true, 5 | "node": true, 6 | "mocha": true 7 | }, 8 | "plugins": [ 9 | "jsdoc" 10 | ], 11 | "extends": [ "eslint:recommended", "plugin:jsdoc/recommended-error" ], 12 | "overrides": [ 13 | ], 14 | "parserOptions": { 15 | "ecmaVersion": "latest" 16 | }, 17 | "rules": { 18 | "indent": [ 19 | "error", 20 | "tab", 21 | { "SwitchCase": 1 } 22 | ], 23 | "quotes": [ 24 | "error", 25 | "double" 26 | ], 27 | "semi": [ 28 | "error", 29 | "always" 30 | ], 31 | "no-trailing-spaces": [ 32 | "error" 33 | ], 34 | "jsdoc/newline-after-description": [ 35 | "error", 36 | "never" 37 | ], 38 | "no-unneeded-ternary": [ 39 | "error" 40 | ], 41 | "multiline-ternary": [ 42 | "error", 43 | "always-multiline" 44 | ], 45 | "no-nested-ternary": [ 46 | "error" 47 | ] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalize line endings 2 | * text=auto 3 | # Force Bash files to use LF line endings 4 | *.sh text eol=lf -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | ignore: 8 | - dependency-name: "node-fetch" 9 | versions: "3.x" 10 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | env: 8 | CI: true 9 | 10 | jobs: 11 | cache-and-install: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | 18 | - name: Install Node.js 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: 18 22 | 23 | - uses: pnpm/action-setup@v2 24 | name: Install pnpm 25 | id: pnpm-install 26 | with: 27 | version: 7 28 | run_install: false 29 | 30 | - name: Get pnpm store directory 31 | id: pnpm-cache 32 | shell: bash 33 | run: | 34 | echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT 35 | 36 | - uses: actions/cache@v3 37 | name: Setup pnpm cache 38 | with: 39 | path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} 40 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 41 | restore-keys: | 42 | ${{ runner.os }}-pnpm-store- 43 | 44 | - name: Install dependencies 45 | run: pnpm install 46 | 47 | - name: Run test 48 | run: pnpm run test 49 | -------------------------------------------------------------------------------- /.github/workflows/sbom.yml: -------------------------------------------------------------------------------- 1 | name: SBOM upload 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | 7 | jobs: 8 | SBOM-upload: 9 | 10 | runs-on: ubuntu-latest 11 | permissions: 12 | id-token: write 13 | contents: write 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Generate SBOM 18 | run: | 19 | curl -Lo $RUNNER_TEMP/sbom-tool https://github.com/microsoft/sbom-tool/releases/latest/download/sbom-tool-linux-x64 20 | chmod +x $RUNNER_TEMP/sbom-tool 21 | $RUNNER_TEMP/sbom-tool generate -b . -bc . -pn ${{ github.repository }} -pv 1.0.0 -ps OwnerName -nsb https://sbom.mycompany.com -V Verbose 22 | - uses: actions/upload-artifact@v3 23 | with: 24 | name: sbom 25 | path: _manifest/spdx_2.2 26 | - name: SBOM upload 27 | uses: jhutchings1/spdx-to-dependency-graph-action@v0.0.2 28 | with: 29 | filePath: "_manifest/spdx_2.2/" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Modules 2 | node_modules/ 3 | 4 | # Logs 5 | log/ 6 | *.log 7 | *.mca 8 | *.gz 9 | 10 | # Configs 11 | ngrok.yml 12 | config.json 13 | *.bak 14 | 15 | # VS 16 | .vs 17 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | auto-install-peers=false 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

2Based2Wait

3 |

Lightweight & extensible 2b2t proxy.

4 | 5 | Wiki 6 | · 7 | Report a Bug 8 | · 9 | Configuration Guide 10 | · 11 | Discussions 12 | 13 |

14 | Last Commit 15 | Code Size 16 | Current Version 17 | License 18 |
19 | 20 | ## Table of Contents 21 | 22 | - [Installation](#installation) 23 | - [Features](#features) 24 | - [Images](#images) 25 | - [For Developers](#for-developers) 26 | 27 | ## Installation 28 | 29 | ### Prerequisites 30 | 31 | - [Git](https://git-scm.com/) 32 | - [NodeJS](https://nodejs.org/) 33 | - [pnpm](https://pnpm.io/): `npm install -g pnpm` 34 | 35 | ### Quick Start 36 | 37 | 1. Clone the latest release: `git clone https://github.com/Enchoseon/2based2wait --branch v1.0.5 --depth 1 && cd 2based2wait` 38 | 2. Install dependencies: `pnpm install --prod` 39 | 3. Configure your proxy: 40 | - Duplicate the `default-config.json` template file and rename it to `config.json` 41 | - Use the [configuration guide](https://github.com/Enchoseon/2based2wait/blob/main/docs/configuration-guide.md) to configure your proxy 42 | - At the minimum you _must_ configure [`account.username`](https://github.com/Enchoseon/2based2wait/blob/main/docs/configuration-guide.md#user-content-account-username) and [`proxy.whitelist`](https://github.com/Enchoseon/2based2wait/blob/main/docs/configuration-guide.md#user-content-proxy-whitelist) 43 | 4. Start the proxy: `pnpm start` 44 | 45 | ### RTFM 46 | 47 | We'll never phone home or enable something you didn't. In other words, you are responsible for enabling and configuring features such as: 48 | - [Reconnecting to the server](https://github.com/Enchoseon/2based2wait/wiki/How-to-Auto-Reconnect-with-Supervisor) 49 | - [Ngrok tunnelling to share the proxy with others](https://github.com/Enchoseon/2based2wait/wiki/How-to-Share-Accounts-With-A-Reverse-Proxy) 50 | - [Coordinating multiple accounts at once](https://github.com/Enchoseon/2based2wait/wiki/How-to-Proxy-Multiple-Accounts) 51 | - [And much more](https://github.com/Enchoseon/2based2wait/wiki) 52 | 53 |
54 | Read the Fun Manual 55 |

"Read it and you'll understand everything", RMS

56 |
57 | 58 | 59 | ## Features 60 | 61 | - Extremely low RAM and CPU usage 62 | - Robust auto-reconnection 63 | - Battle-tested to be online 24/7/365 64 | - High configurability 65 | - Easily configure small-to-medium-sized networks 66 | - Convenient Discord webhooks for: 67 | - Livechat 68 | - Queue position 69 | - Tunnels & connections 70 | - Toast notifications 71 | - Auto `/queue main` 72 | - Mineflayer support and extensibility *(see: `./utils/mineflayer.js`)* 73 | - Already comes with: 74 | - Kill aura 75 | - Auto eat 76 | - Anti afk 77 | - Anti drowning 78 | - Auto totem 79 | - Extensive logging 80 | - Share proxies with plug-and-play Ngrok tunnels 81 | - Your machine's IP is never shared with players connecting to your proxy 82 | - Your players' IPs are never shared with your machine 83 | 84 | ## Images 85 | 86 |
87 | Cli Gui 88 |

No-Frills Cli Gui

89 | Webhooks 90 |

Convenient Discord Webhooks

91 | Grep 92 |

Extensive Logs

93 |
94 | 95 | ## For Developers 96 | 97 | If you want to contribute to the project's source you must install developer dependencies (`pnpm i`) and use an editor with ESLint support. 98 | 99 | You can run tests locally with `pnpm testLocal`. (`pnpm test` will test your actual `config.json` rather than `./test/test-config.json`) 100 | -------------------------------------------------------------------------------- /default-config.json: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a heavily trimmed-down configuration file that you can use as a starting point! 3 | * 4 | * It's HIGHLY recommended you read the wiki if you want to: 5 | * - Learn every single feature this proxy has (https://github.com/Enchoseon/2based2wait/wiki/Configuration-Guide) 6 | * - ^ A LOT of useful features aren't shown in this file and aren't enabled by default, such as ngrok tunneling 7 | * - ^ Change values away from their defaults (e.g. log cutoff) 8 | * - Enable robust autoreconnecting (https://github.com/Enchoseon/2based2wait/wiki/How-to-Auto-Reconnect-with-Supervisor) 9 | * - Coordinate multiple accounts (https://github.com/Enchoseon/2based2wait/wiki/How-to-Proxy-Multiple-Accounts) 10 | * - Learn the ideal setup for accounts with priority queue (https://github.com/Enchoseon/2based2wait/wiki/How-to-Configure-Accounts-With-Priority-Queue) 11 | */ 12 | { 13 | "account": { 14 | "username": "YOUR USERNAME" 15 | }, 16 | 17 | "proxy": { 18 | "whitelist": [ "YOUR WHITELISTED PLAYERNAMES" ], 19 | "port": 25565 20 | }, 21 | 22 | "discord": { 23 | "active": false, 24 | "webhook": { 25 | "spam": "YOUR DISCORD WEBHOOK", 26 | "livechat": "YOUR DISCORD WEBHOOK", 27 | "status": "YOUR DISCORD WEBHOOK" 28 | }, 29 | "id": "424701879151230977" 30 | }, 31 | 32 | "queueThreshold": 21, 33 | 34 | "server": { 35 | "host": "connect.2b2t.org", 36 | "port": 25565, 37 | "version": "1.12.2" 38 | }, 39 | 40 | "mineflayer": { 41 | "active": true, 42 | "autoQueueMainInterval": 690, 43 | "killAura": { 44 | "interval": 0.69, 45 | "blacklist": [ "zombie_pigman", "enderman" ] 46 | }, 47 | "autoEat": { 48 | "priority": "saturation", 49 | "startAt": 19, 50 | "bannedFood": [ "rotten_flesh", "pufferfish", "chorus_fruit", "poisonous_potato", "spider_eye" ] 51 | }, 52 | "antiAfk": { 53 | "actions": [ "rotate", "swingArm" ], 54 | "fishing": false, 55 | "chatting": false, 56 | "chatMessages": [ "!pt", "!queue" ], 57 | "chatInterval": 690420 58 | } 59 | }, 60 | 61 | "coordination": { 62 | "active": false, 63 | "path": "./../" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /docs/configuration-guide.md: -------------------------------------------------------------------------------- 1 | 2 | **[account](#user-content-account)** `{type: object}` 3 | - **[username](#user-content-account-username)** `{type: string}` : The in-game playername of the account 4 | - **[password](#user-content-account-password)** `{type: string}` `{default: ""}` : The password of the account (only required for Mojang accounts, leave it empty for Microsoft accounts. Microsoft accounts will just get instructions in the console to put a token into [microsoft.com/link](https://microsoft.com/link) 5 | - **[auth](#user-content-account-auth)** `{type: string}` `{default: "microsoft"}` : Authentication type (options: 'microsoft', 'mojang', 'offline') 6 | 7 | **[discord](#user-content-discord)** `{type: object}` 8 | - **[active](#user-content-discord-active)** `{type: boolean}` `{default: false}` : Whether to send Discord webhooks 9 | - **[webhook](#user-content-discord-webhook)** `{type: object}` 10 | - **[spam](#user-content-discord-webhook-spam)** `{type: string}` `{default: ""}` : Url of webhook to relay position in queue, new tunnels, connects/disconnects, and other spam 11 | - **[livechat](#user-content-discord-webhook-livechat)** `{type: string}` `{default: ""}` : Url of webhook to relay livechat 12 | - **[status](#user-content-discord-webhook-status)** `{type: string}` `{default: ""}` : Url of webhook to relay pertinent info for connecting and nothing else (e.g. joining server, low queue position) 13 | - **[color](#user-content-discord-color)** `{type: number}` `{default: 2123412}` : Color of Discord embeds sent to the webhooks in **decimal value** (you can use convertingcolors.com to find the decimal value of a color you want) 14 | - **[id](#user-content-discord-id)** `{type: string}` `{default: 0}` : ID of the Discord user or role to ping when below the queueThreshold 15 | 16 | **[queueThreshold](#user-content-queuethreshold)** `{type: number}` `{default: 21}` : Minimum queue position before toast notifications & Discord pings start getting sent 17 | 18 | **[reconnectInterval](#user-content-reconnectinterval)** `{type: number}` `{default: 69}` : Time (in seconds) between each reconnection attempt (see: [How to Auto-Reconnect with Supervisor](https://github.com/Enchoseon/2based2wait/wiki/How-to-Auto-Reconnect-with-Supervisor)) 19 | 20 | **[uncleanDisconnectInterval](#user-content-uncleandisconnectinterval)** `{type: number}` `{default: 196}` : Time (in seconds) proxy will go without getting a single packet from 2B2T before assuming it was uncleanly disconnected and initiating a reconnect attempt 21 | 22 | **[log](#user-content-log)** `{type: object}` 23 | - **[active](#user-content-log-active)** `{type: object}` : Settings for which logging categories should be enabled 24 | - **[error](#user-content-log-active-error)** `{type: boolean}` `{default: true}` : Whether to log errors 25 | - **[proxy](#user-content-log-active-proxy)** `{type: boolean}` `{default: true}` : Whether to log proxy status (e.g. connecting to server, starting Mineflayer, etc.) 26 | - **[chat](#user-content-log-active-chat)** `{type: boolean}` `{default: true}` : Whether to log chat 27 | - **[bridgeClientPackets](#user-content-log-active-bridgeclientpackets)** `{type: boolean}` `{default: true}` : Whether to log packets being sent from the controller to the proxy 28 | - **[serverPackets](#user-content-log-active-serverpackets)** `{type: boolean}` `{default: true}` : Whether to log packets being sent from 2b2t to the proxy 29 | - **[mineflayer](#user-content-log-active-mineflayer)** `{type: boolean}` `{default: false}` : Whether to log high-level Mineflayer events, if the Mineflayer bot is active (e.g. player join/leave, items, etc.) 30 | - **[cutoff](#user-content-log-cutoff)** `{type: number}` `{default: 69000}` : Maximum size a log file can be (in bytes) before it gets split up 31 | - **[packetFilters](#user-content-log-packetfilters)** `{type: object}` : Settings for which packets we shouldn't log 32 | - **[server](#user-content-log-packetfilters-server)** `{type: array}` `{default: ["map","map_chunk","player_info","entity_metadata","entity_velocity","entity_move_look","entity_look","update_time","world_particles","unload_chunk","teams","rel_entity_move","entity_head_rotation","entity_update_attributes","block_change"]}` : Packets being sent from 2b2t to not log 33 | - **[bridgeClient](#user-content-log-packetfilters-bridgeclient)** `{type: array}` `{default: ["position","look","position_look","arm_animation","keep_alive"]}` : Packets being sent from the controller to not log 34 | - **[compression](#user-content-log-compression)** `{type: object}` : Settings for log compression. Tweak with caution. The default options maximize memory usage for the fastest speed 35 | - **[active](#user-content-log-compression-active)** `{type: boolean}` `{default: false}` : **[Warning, Event Thread-Blocking!]** Whether to compress log files with Gzip. Leave this off unless you have a really good reason to enable it 36 | - **[level](#user-content-log-compression-level)** `{type: number}` `{default: 1}` : How much compression to apply between 1 and 9. Higher values result in better compression ratio at the expense of speed (**[Warning, Event Thread-Blocking!]**) 37 | - **[memLevel](#user-content-log-compression-memlevel)** `{type: number}` `{default: 9}` : How much memory to allocate to the internal compression state between 1 and 9. Higher values result in better compression ratio and speed at the expense of memory usage 38 | - **[windowBits](#user-content-log-compression-windowbits)** `{type: number}` `{default: 15}` : How much memory to allocate to the history buffer between 8 and 15. Higher values result in better compression ratio at the expense of memory usage 39 | - **[alwaysIncrement](#user-content-log-alwaysincrement)** `{type: boolean}` `{default: false}` : Whether to increment the log file every session (can lead to thousands of 1kb log files in production, but is pretty useful when rapidly testing during development) 40 | 41 | **[server](#user-content-server)** `{type: object}` : Settings for how the proxy connects to the server 42 | - **[host](#user-content-server-host)** `{type: string}` `{default: "connect.2b2t.org"}` : Address of the server to connect to 43 | - **[version](#user-content-server-version)** `{type: string}` `{default: "1.12.2"}` : Version of Minecraft the server is on 44 | - **[port](#user-content-server-port)** `{type: number}` `{default: 25565}` : Port of the server to connect to 45 | 46 | **[proxy](#user-content-proxy)** `{type: object}` : Settings for how you connect to the proxy 47 | - **[active](#user-content-proxy-active)** `{type: boolean}` `{default: true}` : Whether to allow players to control the account by connecting through a tunnel 48 | - **[whitelist](#user-content-proxy-whitelist)** `{type: array}` : Playernames of accounts that are allowed to connect to the proxy 49 | - **[onlineMode](#user-content-proxy-onlinemode)** `{type: boolean}` `{default: true}` : Whether to enable online-mode on the proxy. This probably should never be touched 50 | - **[loopbackAddress](#user-content-proxy-loopbackaddress)** `{type: string}` `{default: "127.0.0.1"}` : Loopback address to connect to the proxy. (options: '127.0.0.1', 'localhost', '0.0.0.0', '::1') 51 | - **[port](#user-content-proxy-port)** `{type: number}` `{default: 25565}` : Port on the machine to connect to the proxy 52 | 53 | **[ngrok](#user-content-ngrok)** `{type: object}` : Settings for ngrok tunneling 54 | - **[active](#user-content-ngrok-active)** `{type: boolean}` `{default: false}` : Whether to create an ngrok tunnel 55 | - **[authtoken](#user-content-ngrok-authtoken)** `{type: string}` `{default: ""}` : The auth token for your Ngrok.io account 56 | - **[region](#user-content-ngrok-region)** `{type: string}` `{default: "us"}` : Tunnel region (options: 'us', 'eu', 'au', 'ap', 'sa', 'jp', or 'in') 57 | 58 | **[mineflayer](#user-content-mineflayer)** `{type: object}` : Settings for the mineflayer bot 59 | - **[active](#user-content-mineflayer-active)** `{type: boolean}` `{default: true}` : Whether to enable Mineflayer 60 | - **[autoQueueMainInterval](#user-content-mineflayer-autoqueuemaininterval)** `{type: number}` `{default: 690}` : Time (in seconds) between every `/queue main` command 61 | - **[killAura](#user-content-mineflayer-killaura)** `{type: object}` : Settings for killaura 62 | - **[interval](#user-content-mineflayer-killaura-interval)** `{type: number}` `{default: 0.69}` : Time (in seconds) between every attack attempt 63 | - **[blacklist](#user-content-mineflayer-killaura-blacklist)** `{type: array}` `{default: ["zombie_pigman","enderman"]}` : Array of mobs that will not be attacked 64 | - **[autoEat](#user-content-mineflayer-autoeat)** `{type: object}` : Settings for autoeat 65 | - **[priority](#user-content-mineflayer-autoeat-priority)** `{type: string}` `{default: "saturation"}` : What type of food to prioritize eating (options: 'saturation', 'foodPoints', 'effectiveQuality') 66 | - **[startAt](#user-content-mineflayer-autoeat-startat)** `{type: number}` `{default: 19}` : Hunger level at which to start eating 67 | - **[eatingTimeout](#user-content-mineflayer-autoeat-eatingtimeout)** `{type: number}` `{default: 6969}` : Maximum time (in ms) the proxy will attempt to eat an item before giving up 68 | - **[bannedFood](#user-content-mineflayer-autoeat-bannedfood)** `{type: array}` `{default: ["rotten_flesh","pufferfish","chorus_fruit","poisonous_potato","spider_eye"]}` : Foods that will not be eaten 69 | - **[ignoreInventoryCheck](#user-content-mineflayer-autoeat-ignoreinventorycheck)** `{type: boolean}` `{default: false}` : Whether to disable inventory window click confirmation as a dirty hack to get around ViaBackwards' protocol noncompliance 70 | - **[checkOnItemPickup](#user-content-mineflayer-autoeat-checkonitempickup)** `{type: boolean}` `{default: true}` : Whether to attempt to eat food that's picked up when below the startAt threshold 71 | - **[offhand](#user-content-mineflayer-autoeat-offhand)** `{type: boolean}` `{default: false}` : Whether to use the offhand slot to eat food 72 | - **[equipOldItem](#user-content-mineflayer-autoeat-equipolditem)** `{type: boolean}` `{default: true}` : Whether to reequip the previously held item after eating 73 | - **[antiAfk](#user-content-mineflayer-antiafk)** `{type: object}` : Settings for antiafk 74 | - **[actions](#user-content-mineflayer-antiafk-actions)** `{type: array}` `{default: ["rotate"]}` : Actions the proxy can do (options: 'rotate', 'walk', 'jump', 'jumpWalk', 'swingArm', 'breakBlock') 75 | - **[fishing](#user-content-mineflayer-antiafk-fishing)** `{type: boolean}` `{default: false}` : Whether the proxy will fish. The account must be standing in water and have a fishing rod to autofish. 76 | - **[chatting](#user-content-mineflayer-antiafk-chatting)** `{type: boolean}` `{default: false}` : Whether the proxy will chat 77 | - **[chatMessages](#user-content-mineflayer-antiafk-chatmessages)** `{type: array}` `{default: ["!pt","!queue"]}` : Chat messages that the proxy will send if chatting is enabled 78 | - **[chatInterval](#user-content-mineflayer-antiafk-chatinterval)** `{type: number}` `{default: 690420}` : Time (in milliseconds) between each chat message 79 | - **[autoTotem](#user-content-mineflayer-autototem)** `{type: object}` : Settings for autototem 80 | - **[interval](#user-content-mineflayer-autototem-interval)** `{type: number}` `{default: 50}` : Time (in milliseconds) between each totem equip attempt 81 | 82 | **[experimental](#user-content-experimental)** `{type: object}` : Settings for experimental features that may be more unstable in resource usage and/or server and version parity 83 | - **[spoofPlayerInfo](#user-content-experimental-spoofplayerinfo)** `{type: object}` 84 | - **[active](#user-content-experimental-spoofplayerinfo-active)** `{type: boolean}` `{default: true}` : Whether to spoof the [Player Info packet](https://wiki.vg/Protocol#Player_Info) to set a custom skin 85 | - **[texture](#user-content-experimental-spoofplayerinfo-texture)** `{type: object}` 86 | - **[value](#user-content-experimental-spoofplayerinfo-texture-value)** `{type: string}` `{default: ""}` : Base64 string of skin from [https://sessionserver.mojang.com/session/minecraft/profile/?unsigned=false](https://wiki.vg/Mojang_API#UUID_to_Profile_and_Skin.2FCape) 87 | - **[signature](#user-content-experimental-spoofplayerinfo-texture-signature)** `{type: string}` `{default: ""}` : Base64 string of signed data using Yggdrasil's private key from [https://sessionserver.mojang.com/session/minecraft/profile/?unsigned=false](https://wiki.vg/Mojang_API#UUID_to_Profile_and_Skin.2FCape) 88 | - **[spoofPing](#user-content-experimental-spoofping)** `{type: object}` 89 | - **[active](#user-content-experimental-spoofping-active)** `{type: boolean}` `{default: false}` : Whether to spoof the [Status Response packet](https://wiki.vg/Server_List_Ping#Status_Response) when pinging the proxy server 90 | - **[noResponse](#user-content-experimental-spoofping-noresponse)** `{type: boolean}` `{default: false}` : Whether to cancel the response entirely. Otherwise, the packet described in fakeResponse will be sent. 91 | - **[fakeResponse](#user-content-experimental-spoofping-fakeresponse)** `{type: object}` 92 | - **[version](#user-content-experimental-spoofping-fakeresponse-version)** `{type: object}` 93 | - **[name](#user-content-experimental-spoofping-fakeresponse-version-name)** `{type: string}` `{default: "1.12.2"}` : Spoofed server version 94 | - **[protocol](#user-content-experimental-spoofping-fakeresponse-version-protocol)** `{type: number}` `{default: 340}` : Spoofed [protocol number](https://wiki.vg/Protocol_version_numbers) 95 | - **[players](#user-content-experimental-spoofping-fakeresponse-players)** `{type: object}` 96 | - **[max](#user-content-experimental-spoofping-fakeresponse-players-max)** `{type: number}` `{default: 20}` : Spoofed max players 97 | - **[online](#user-content-experimental-spoofping-fakeresponse-players-online)** `{type: number}` `{default: 0}` : Spoofed number of players online 98 | - **[sample](#user-content-experimental-spoofping-fakeresponse-players-sample)** `{type: array}` `{default: []}` 99 | - **[name](#user-content-experimental-spoofping-fakeresponse-players-sample-items-0-name)** `{type: string}` : Spoofed playername 100 | - **[id](#user-content-experimental-spoofping-fakeresponse-players-sample-items-0-id)** `{type: string}` : Spoofed player UUID 101 | - **[description](#user-content-experimental-spoofping-fakeresponse-description)** `{type: object}` 102 | - **[text](#user-content-experimental-spoofping-fakeresponse-description-text)** `{type: string}` `{default: "A Minecraft server"}` : Spoofed MOTD 103 | - **[favicon](#user-content-experimental-spoofping-fakeresponse-favicon)** `{type: string}` `{default: "undefined"}` : Spoofed Base64-encoded 64x64 png favicon 104 | - **[disconnectIfNoController](#user-content-experimental-disconnectifnocontroller)** `{type: object}` 105 | - **[active](#user-content-experimental-disconnectifnocontroller-active)** `{type: boolean}` `{default: false}` : Whether to disconnect if noone is controlling the proxy disconnectIfNoController.delay seconds after a controller disconnects from the proxy while it isn't in queue 106 | - **[delay](#user-content-experimental-disconnectifnocontroller-delay)** `{type: number}` `{default: 7}` : How long to wait (in seconds) after a controller disconnects from the proxy while it isn't in queue before disconnecting from the server 107 | - **[worldDownloader](#user-content-experimental-worlddownloader)** `{type: object}` 108 | - **[active](#user-content-experimental-worlddownloader-active)** `{type: boolean}` `{default: false}` : **[Warning, Event Thread-Blocking!]** Whether to use the experimental world downloader 109 | - **[compression](#user-content-experimental-worlddownloader-compression)** `{type: object}` : Settings for packet archive compression. Tweak with caution. The default options maximize memory usage for the fastest speed 110 | - **[level](#user-content-experimental-worlddownloader-compression-level)** `{type: number}` `{default: 1}` : How much compression to apply between 1 and 9. Higher values result in better compression ratio at the expense of speed (**[Warning, Event Thread-Blocking!]**) 111 | - **[memLevel](#user-content-experimental-worlddownloader-compression-memlevel)** `{type: number}` `{default: 9}` : How much memory to allocate to the internal compression state between 1 and 9. Higher values result in better compression ratio and speed at the expense of memory usage 112 | - **[windowBits](#user-content-experimental-worlddownloader-compression-windowbits)** `{type: number}` `{default: 15}` : How much memory to allocate to the history buffer between 8 and 15. Higher values result in better compression ratio at the expense of memory usage 113 | - **[maxThreadpool](#user-content-experimental-maxthreadpool)** `{type: object}` 114 | - **[active](#user-content-experimental-maxthreadpool-active)** `{type: boolean}` `{default: true}` : Whether to set UV_THREADPOOL_SIZE to use all possible CPU logic cores 115 | - **[syncGamestate](#user-content-experimental-syncgamestate)** `{type: object}` 116 | - **[active](#user-content-experimental-syncgamestate-active)** `{type: boolean}` `{default: true}` : Send fake packets to attempt to sync gamestate 117 | 118 | **[waitForControllerBeforeConnect](#user-content-waitforcontrollerbeforeconnect)** `{type: boolean}` `{default: false}` : Whether the proxy will wait for someone to take control before it connects to the server 119 | 120 | **[notify](#user-content-notify)** `{type: object}` : Settings for what the proxy will send notifications about 121 | - **[whenJoining](#user-content-notify-whenjoining)** `{type: boolean}` `{default: true}` : Whether to send a toast notification and status webhook message when the proxy joins the server from queue 122 | - **[whenBelowQueueThreshold](#user-content-notify-whenbelowqueuethreshold)** `{type: boolean}` `{default: true}` : Whether to send a toast notification and status webhook message when the proxy dips below position `queueThreshold` in queue 123 | - **[whenControlling](#user-content-notify-whencontrolling)** `{type: boolean}` `{default: false}` : Whether to send a status webhook message when a controller connects and disconnects from the proxy 124 | 125 | **[noCliGui](#user-content-nocligui)** `{type: boolean}` `{default: false}` : Whether to disable the cli gui 126 | 127 | **[coordination](#user-content-coordination)** `{type: object}` : Settings for coordinating multiple proxies 128 | - **[active](#user-content-coordination-active)** `{type: boolean}` `{default: false}` : Whether to use a [master config file and coordinator](https://github.com/Enchoseon/2based2wait/wiki/How-to-Proxy-Multiple-Accounts) 129 | - **[path](#user-content-coordination-path)** `{type: string}` `{default: "./../"}` : Path to the folder where the shared master-config.json and coordinator.flag files should go 130 | -------------------------------------------------------------------------------- /docs/images/RTFM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Enchoseon/2based2wait/84ae4c62b1c95ddc8ca2343ab84e957cf7df08e4/docs/images/RTFM.png -------------------------------------------------------------------------------- /docs/images/cliGui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Enchoseon/2based2wait/84ae4c62b1c95ddc8ca2343ab84e957cf7df08e4/docs/images/cliGui.png -------------------------------------------------------------------------------- /docs/images/grep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Enchoseon/2based2wait/84ae4c62b1c95ddc8ca2343ab84e957cf7df08e4/docs/images/grep.png -------------------------------------------------------------------------------- /docs/images/webhooks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Enchoseon/2based2wait/84ae4c62b1c95ddc8ca2343ab84e957cf7df08e4/docs/images/webhooks.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "2based2wait", 3 | "version": "1.0.5", 4 | "description": "Lightweight and extensible proxy for 2B2T.", 5 | "main": "proxy.js", 6 | "scripts": { 7 | "test": "mocha --exit --bail", 8 | "testLocal": "CI=true mocha --exit --bail", 9 | "start": "node proxy.js", 10 | "supervisor": "supervisor -n success proxy.js", 11 | "supervisorAllGasNoBrakes": "supervisor proxy.js", 12 | "generate-documentation": "node proxy.js --documentation", 13 | "process-archives": "node ./scripts/processArchives.js", 14 | "update-ngrok-binary": "node ./scripts/updateNgrokBinary.js", 15 | "debug-info": "node ./scripts/debugFetch.js", 16 | "lint": "eslint --ext .js" 17 | }, 18 | "author": "Enchoseon", 19 | "license": "GPL-2.0", 20 | "dependencies": { 21 | "@icetank/mcproxy": "^1.0.6", 22 | "deepmerge": "^4.3.1", 23 | "joi": "^17.12.2", 24 | "json5": "^2.2.3", 25 | "minecraft-data": "^3.62.0", 26 | "minecraft-protocol": "^1.47.0", 27 | "mineflayer": "^4.20.0", 28 | "mineflayer-antiafk": "github:Etiaro/mineflayer-antiafk", 29 | "mineflayer-auto-eat": "^3.3.6", 30 | "ngrok": "^4.3.3", 31 | "node-fetch": "^2.7.0", 32 | "node-notifier": "^10.0.1", 33 | "prismarine-chat": "^1.10.0", 34 | "prismarine-provider-anvil": "^2.7.0", 35 | "typescript": "^5.3.3" 36 | }, 37 | "devDependencies": { 38 | "eslint": "^8.57.0", 39 | "eslint-plugin-jsdoc": "^39.9.1", 40 | "mocha": "^10.3.0" 41 | }, 42 | "repository": { 43 | "type": "git", 44 | "url": "https://github.com/Enchoseon/2based2wait.git" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | settings: 4 | autoInstallPeers: false 5 | excludeLinksFromLockfile: false 6 | 7 | dependencies: 8 | '@icetank/mcproxy': 9 | specifier: ^1.0.6 10 | version: 1.0.6 11 | deepmerge: 12 | specifier: ^4.3.1 13 | version: 4.3.1 14 | joi: 15 | specifier: ^17.12.2 16 | version: 17.12.2 17 | json5: 18 | specifier: ^2.2.3 19 | version: 2.2.3 20 | minecraft-data: 21 | specifier: ^3.62.0 22 | version: 3.62.0 23 | minecraft-protocol: 24 | specifier: ^1.47.0 25 | version: 1.47.0 26 | mineflayer: 27 | specifier: ^4.20.0 28 | version: 4.20.0 29 | mineflayer-antiafk: 30 | specifier: github:Etiaro/mineflayer-antiafk 31 | version: github.com/Etiaro/mineflayer-antiafk/41a08c7e71adb052baee4b739510b6f78987291b 32 | mineflayer-auto-eat: 33 | specifier: ^3.3.6 34 | version: 3.3.6 35 | ngrok: 36 | specifier: ^4.3.3 37 | version: 4.3.3 38 | node-fetch: 39 | specifier: ^2.7.0 40 | version: 2.7.0 41 | node-notifier: 42 | specifier: ^10.0.1 43 | version: 10.0.1 44 | prismarine-chat: 45 | specifier: ^1.10.0 46 | version: 1.10.0 47 | prismarine-provider-anvil: 48 | specifier: ^2.7.0 49 | version: 2.7.0(minecraft-data@3.62.0) 50 | typescript: 51 | specifier: ^5.3.3 52 | version: 5.3.3 53 | 54 | devDependencies: 55 | eslint: 56 | specifier: ^8.57.0 57 | version: 8.57.0 58 | eslint-plugin-jsdoc: 59 | specifier: ^39.9.1 60 | version: 39.9.1(eslint@8.57.0) 61 | mocha: 62 | specifier: ^10.3.0 63 | version: 10.3.0 64 | 65 | packages: 66 | 67 | /@aashutoshrathi/word-wrap@1.2.6: 68 | resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} 69 | engines: {node: '>=0.10.0'} 70 | dev: true 71 | 72 | /@azure/msal-common@14.7.1: 73 | resolution: {integrity: sha512-v96btzjM7KrAu4NSEdOkhQSTGOuNUIIsUdB8wlyB9cdgl5KqEKnTonHUZ8+khvZ6Ap542FCErbnTyDWl8lZ2rA==} 74 | engines: {node: '>=0.8.0'} 75 | dev: false 76 | 77 | /@azure/msal-node@2.6.4: 78 | resolution: {integrity: sha512-nNvEPx009/80UATCToF+29NZYocn01uKrB91xtFr7bSqkqO1PuQGXRyYwryWRztUrYZ1YsSbw9A+LmwOhpVvcg==} 79 | engines: {node: '>=16'} 80 | dependencies: 81 | '@azure/msal-common': 14.7.1 82 | jsonwebtoken: 9.0.2 83 | uuid: 8.3.2 84 | dev: false 85 | 86 | /@es-joy/jsdoccomment@0.36.1: 87 | resolution: {integrity: sha512-922xqFsTpHs6D0BUiG4toiyPOMc8/jafnWKxz1KWgS4XzKPy2qXf1Pe6UFuNSCQqt6tOuhAWXBNuuyUhJmw9Vg==} 88 | engines: {node: ^14 || ^16 || ^17 || ^18 || ^19} 89 | dependencies: 90 | comment-parser: 1.3.1 91 | esquery: 1.5.0 92 | jsdoc-type-pratt-parser: 3.1.0 93 | dev: true 94 | 95 | /@eslint-community/eslint-utils@4.4.0(eslint@8.57.0): 96 | resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} 97 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 98 | peerDependencies: 99 | eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 100 | dependencies: 101 | eslint: 8.57.0 102 | eslint-visitor-keys: 3.4.3 103 | dev: true 104 | 105 | /@eslint-community/regexpp@4.10.0: 106 | resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} 107 | engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} 108 | dev: true 109 | 110 | /@eslint/eslintrc@2.1.4: 111 | resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} 112 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 113 | dependencies: 114 | ajv: 6.12.6 115 | debug: 4.3.4(supports-color@8.1.1) 116 | espree: 9.6.1 117 | globals: 13.24.0 118 | ignore: 5.3.1 119 | import-fresh: 3.3.0 120 | js-yaml: 4.1.0 121 | minimatch: 3.1.2 122 | strip-json-comments: 3.1.1 123 | transitivePeerDependencies: 124 | - supports-color 125 | dev: true 126 | 127 | /@eslint/js@8.57.0: 128 | resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} 129 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 130 | dev: true 131 | 132 | /@hapi/hoek@9.3.0: 133 | resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} 134 | dev: false 135 | 136 | /@hapi/topo@5.1.0: 137 | resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} 138 | dependencies: 139 | '@hapi/hoek': 9.3.0 140 | dev: false 141 | 142 | /@humanwhocodes/config-array@0.11.14: 143 | resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} 144 | engines: {node: '>=10.10.0'} 145 | dependencies: 146 | '@humanwhocodes/object-schema': 2.0.2 147 | debug: 4.3.4(supports-color@8.1.1) 148 | minimatch: 3.1.2 149 | transitivePeerDependencies: 150 | - supports-color 151 | dev: true 152 | 153 | /@humanwhocodes/module-importer@1.0.1: 154 | resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} 155 | engines: {node: '>=12.22'} 156 | dev: true 157 | 158 | /@humanwhocodes/object-schema@2.0.2: 159 | resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==} 160 | dev: true 161 | 162 | /@icetank/mcproxy@1.0.6: 163 | resolution: {integrity: sha512-me3r03xdJfYMrFMBKPzqlGmP6c3vrl3M5FsPglF83UBY5mYFdPEBfoBWh0Bn/f7i9WmFdu6RW0Prtk6Vw4T6Iw==} 164 | dependencies: 165 | deepcopy: 2.1.0 166 | mineflayer: 4.20.0 167 | smart-buffer: 4.2.0 168 | transitivePeerDependencies: 169 | - encoding 170 | - supports-color 171 | dev: false 172 | 173 | /@nodelib/fs.scandir@2.1.5: 174 | resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 175 | engines: {node: '>= 8'} 176 | dependencies: 177 | '@nodelib/fs.stat': 2.0.5 178 | run-parallel: 1.2.0 179 | dev: true 180 | 181 | /@nodelib/fs.stat@2.0.5: 182 | resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} 183 | engines: {node: '>= 8'} 184 | dev: true 185 | 186 | /@nodelib/fs.walk@1.2.8: 187 | resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 188 | engines: {node: '>= 8'} 189 | dependencies: 190 | '@nodelib/fs.scandir': 2.1.5 191 | fastq: 1.17.1 192 | dev: true 193 | 194 | /@sideway/address@4.1.5: 195 | resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} 196 | dependencies: 197 | '@hapi/hoek': 9.3.0 198 | dev: false 199 | 200 | /@sideway/formula@3.0.1: 201 | resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} 202 | dev: false 203 | 204 | /@sideway/pinpoint@2.0.0: 205 | resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} 206 | dev: false 207 | 208 | /@sindresorhus/is@4.6.0: 209 | resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} 210 | engines: {node: '>=10'} 211 | dev: false 212 | 213 | /@szmarczak/http-timer@4.0.6: 214 | resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} 215 | engines: {node: '>=10'} 216 | dependencies: 217 | defer-to-connect: 2.0.1 218 | dev: false 219 | 220 | /@types/cacheable-request@6.0.3: 221 | resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} 222 | dependencies: 223 | '@types/http-cache-semantics': 4.0.4 224 | '@types/keyv': 3.1.4 225 | '@types/node': 8.10.66 226 | '@types/responselike': 1.0.3 227 | dev: false 228 | 229 | /@types/http-cache-semantics@4.0.4: 230 | resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} 231 | dev: false 232 | 233 | /@types/keyv@3.1.4: 234 | resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} 235 | dependencies: 236 | '@types/node': 8.10.66 237 | dev: false 238 | 239 | /@types/node@20.11.24: 240 | resolution: {integrity: sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long==} 241 | dependencies: 242 | undici-types: 5.26.5 243 | dev: false 244 | 245 | /@types/node@8.10.66: 246 | resolution: {integrity: sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==} 247 | dev: false 248 | 249 | /@types/readable-stream@4.0.10: 250 | resolution: {integrity: sha512-AbUKBjcC8SHmImNi4yK2bbjogQlkFSg7shZCcicxPQapniOlajG8GCc39lvXzCWX4lLRRs7DM3VAeSlqmEVZUA==} 251 | dependencies: 252 | '@types/node': 20.11.24 253 | safe-buffer: 5.1.2 254 | dev: false 255 | 256 | /@types/responselike@1.0.3: 257 | resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} 258 | dependencies: 259 | '@types/node': 8.10.66 260 | dev: false 261 | 262 | /@types/yauzl@2.10.3: 263 | resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} 264 | requiresBuild: true 265 | dependencies: 266 | '@types/node': 8.10.66 267 | dev: false 268 | optional: true 269 | 270 | /@ungap/structured-clone@1.2.0: 271 | resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} 272 | dev: true 273 | 274 | /@xboxreplay/errors@0.1.0: 275 | resolution: {integrity: sha512-Tgz1d/OIPDWPeyOvuL5+aai5VCcqObhPnlI3skQuf80GVF3k1I0lPCnGC+8Cm5PV9aLBT5m8qPcJoIUQ2U4y9g==} 276 | dev: false 277 | 278 | /@xboxreplay/xboxlive-auth@3.3.3(debug@4.3.4): 279 | resolution: {integrity: sha512-j0AU8pW10LM8O68CTZ5QHnvOjSsnPICy0oQcP7zyM7eWkDQ/InkiQiirQKsPn1XRYDl4ccNu0WM582s3UKwcBg==} 280 | dependencies: 281 | '@xboxreplay/errors': 0.1.0 282 | axios: 0.21.4(debug@4.3.4) 283 | transitivePeerDependencies: 284 | - debug 285 | dev: false 286 | 287 | /abort-controller@3.0.0: 288 | resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} 289 | engines: {node: '>=6.5'} 290 | dependencies: 291 | event-target-shim: 5.0.1 292 | dev: false 293 | 294 | /acorn-jsx@5.3.2(acorn@8.11.3): 295 | resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} 296 | peerDependencies: 297 | acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 298 | dependencies: 299 | acorn: 8.11.3 300 | dev: true 301 | 302 | /acorn@8.11.3: 303 | resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} 304 | engines: {node: '>=0.4.0'} 305 | hasBin: true 306 | dev: true 307 | 308 | /aes-js@3.1.2: 309 | resolution: {integrity: sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ==} 310 | dev: false 311 | 312 | /ajv@6.12.6: 313 | resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} 314 | dependencies: 315 | fast-deep-equal: 3.1.3 316 | fast-json-stable-stringify: 2.1.0 317 | json-schema-traverse: 0.4.1 318 | uri-js: 4.4.1 319 | 320 | /ansi-colors@4.1.1: 321 | resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==} 322 | engines: {node: '>=6'} 323 | dev: true 324 | 325 | /ansi-regex@5.0.1: 326 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 327 | engines: {node: '>=8'} 328 | dev: true 329 | 330 | /ansi-styles@4.3.0: 331 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 332 | engines: {node: '>=8'} 333 | dependencies: 334 | color-convert: 2.0.1 335 | dev: true 336 | 337 | /anymatch@3.1.3: 338 | resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} 339 | engines: {node: '>= 8'} 340 | dependencies: 341 | normalize-path: 3.0.0 342 | picomatch: 2.3.1 343 | dev: true 344 | 345 | /argparse@2.0.1: 346 | resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} 347 | dev: true 348 | 349 | /asn1@0.2.3: 350 | resolution: {integrity: sha512-6i37w/+EhlWlGUJff3T/Q8u1RGmP5wgbiwYnOnbOqvtrPxT63/sYFyP9RcpxtxGymtfA075IvmOnL7ycNOWl3w==} 351 | dev: false 352 | 353 | /axios@0.21.4(debug@4.3.4): 354 | resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} 355 | dependencies: 356 | follow-redirects: 1.15.5(debug@4.3.4) 357 | transitivePeerDependencies: 358 | - debug 359 | dev: false 360 | 361 | /balanced-match@1.0.2: 362 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 363 | dev: true 364 | 365 | /base64-js@1.5.1: 366 | resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} 367 | dev: false 368 | 369 | /binary-extensions@2.2.0: 370 | resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} 371 | engines: {node: '>=8'} 372 | dev: true 373 | 374 | /brace-expansion@1.1.11: 375 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 376 | dependencies: 377 | balanced-match: 1.0.2 378 | concat-map: 0.0.1 379 | dev: true 380 | 381 | /brace-expansion@2.0.1: 382 | resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} 383 | dependencies: 384 | balanced-match: 1.0.2 385 | dev: true 386 | 387 | /braces@3.0.2: 388 | resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} 389 | engines: {node: '>=8'} 390 | dependencies: 391 | fill-range: 7.0.1 392 | dev: true 393 | 394 | /browser-stdout@1.3.1: 395 | resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} 396 | dev: true 397 | 398 | /buffer-crc32@0.2.13: 399 | resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} 400 | dev: false 401 | 402 | /buffer-equal-constant-time@1.0.1: 403 | resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} 404 | dev: false 405 | 406 | /buffer-equal@1.0.1: 407 | resolution: {integrity: sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg==} 408 | engines: {node: '>=0.4'} 409 | dev: false 410 | 411 | /buffer@6.0.3: 412 | resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} 413 | dependencies: 414 | base64-js: 1.5.1 415 | ieee754: 1.2.1 416 | dev: false 417 | 418 | /cacheable-lookup@5.0.4: 419 | resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} 420 | engines: {node: '>=10.6.0'} 421 | dev: false 422 | 423 | /cacheable-request@7.0.4: 424 | resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==} 425 | engines: {node: '>=8'} 426 | dependencies: 427 | clone-response: 1.0.3 428 | get-stream: 5.2.0 429 | http-cache-semantics: 4.1.1 430 | keyv: 4.5.4 431 | lowercase-keys: 2.0.0 432 | normalize-url: 6.1.0 433 | responselike: 2.0.1 434 | dev: false 435 | 436 | /callsites@3.1.0: 437 | resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} 438 | engines: {node: '>=6'} 439 | dev: true 440 | 441 | /camelcase@6.3.0: 442 | resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} 443 | engines: {node: '>=10'} 444 | dev: true 445 | 446 | /chalk@4.1.2: 447 | resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 448 | engines: {node: '>=10'} 449 | dependencies: 450 | ansi-styles: 4.3.0 451 | supports-color: 7.2.0 452 | dev: true 453 | 454 | /chokidar@3.5.3: 455 | resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} 456 | engines: {node: '>= 8.10.0'} 457 | dependencies: 458 | anymatch: 3.1.3 459 | braces: 3.0.2 460 | glob-parent: 5.1.2 461 | is-binary-path: 2.1.0 462 | is-glob: 4.0.3 463 | normalize-path: 3.0.0 464 | readdirp: 3.6.0 465 | optionalDependencies: 466 | fsevents: 2.3.3 467 | dev: true 468 | 469 | /cliui@7.0.4: 470 | resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} 471 | dependencies: 472 | string-width: 4.2.3 473 | strip-ansi: 6.0.1 474 | wrap-ansi: 7.0.0 475 | dev: true 476 | 477 | /clone-response@1.0.3: 478 | resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} 479 | dependencies: 480 | mimic-response: 1.0.1 481 | dev: false 482 | 483 | /color-convert@2.0.1: 484 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 485 | engines: {node: '>=7.0.0'} 486 | dependencies: 487 | color-name: 1.1.4 488 | dev: true 489 | 490 | /color-name@1.1.4: 491 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 492 | dev: true 493 | 494 | /commander@2.20.3: 495 | resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} 496 | dev: false 497 | 498 | /comment-parser@1.3.1: 499 | resolution: {integrity: sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA==} 500 | engines: {node: '>= 12.0.0'} 501 | dev: true 502 | 503 | /concat-map@0.0.1: 504 | resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 505 | dev: true 506 | 507 | /cross-spawn@7.0.3: 508 | resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} 509 | engines: {node: '>= 8'} 510 | dependencies: 511 | path-key: 3.1.1 512 | shebang-command: 2.0.0 513 | which: 2.0.2 514 | dev: true 515 | 516 | /debug@4.3.4(supports-color@8.1.1): 517 | resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} 518 | engines: {node: '>=6.0'} 519 | peerDependencies: 520 | supports-color: '*' 521 | peerDependenciesMeta: 522 | supports-color: 523 | optional: true 524 | dependencies: 525 | ms: 2.1.2 526 | supports-color: 8.1.1 527 | 528 | /decamelize@4.0.0: 529 | resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} 530 | engines: {node: '>=10'} 531 | dev: true 532 | 533 | /decompress-response@6.0.0: 534 | resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} 535 | engines: {node: '>=10'} 536 | dependencies: 537 | mimic-response: 3.1.0 538 | dev: false 539 | 540 | /deep-is@0.1.4: 541 | resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} 542 | dev: true 543 | 544 | /deepcopy@2.1.0: 545 | resolution: {integrity: sha512-8cZeTb1ZKC3bdSCP6XOM1IsTczIO73fdqtwa2B0N15eAz7gmyhQo+mc5gnFuulsgN3vIQYmTgbmQVKalH1dKvQ==} 546 | dependencies: 547 | type-detect: 4.0.8 548 | dev: false 549 | 550 | /deepmerge@4.3.1: 551 | resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} 552 | engines: {node: '>=0.10.0'} 553 | dev: false 554 | 555 | /defer-to-connect@2.0.1: 556 | resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} 557 | engines: {node: '>=10'} 558 | dev: false 559 | 560 | /diff@5.0.0: 561 | resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==} 562 | engines: {node: '>=0.3.1'} 563 | dev: true 564 | 565 | /discontinuous-range@1.0.0: 566 | resolution: {integrity: sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==} 567 | dev: false 568 | 569 | /doctrine@3.0.0: 570 | resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} 571 | engines: {node: '>=6.0.0'} 572 | dependencies: 573 | esutils: 2.0.3 574 | dev: true 575 | 576 | /ecdsa-sig-formatter@1.0.11: 577 | resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} 578 | dependencies: 579 | safe-buffer: 5.2.1 580 | dev: false 581 | 582 | /emoji-regex@8.0.0: 583 | resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 584 | dev: true 585 | 586 | /end-of-stream@1.4.4: 587 | resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} 588 | dependencies: 589 | once: 1.4.0 590 | dev: false 591 | 592 | /endian-toggle@0.0.0: 593 | resolution: {integrity: sha512-ShfqhXeHRE4TmggSlHXG8CMGIcsOsqDw/GcoPcosToE59Rm9e4aXaMhEQf2kPBsBRrKem1bbOAv5gOKnkliMFQ==} 594 | dev: false 595 | 596 | /escalade@3.1.2: 597 | resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} 598 | engines: {node: '>=6'} 599 | dev: true 600 | 601 | /escape-string-regexp@4.0.0: 602 | resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} 603 | engines: {node: '>=10'} 604 | dev: true 605 | 606 | /eslint-plugin-jsdoc@39.9.1(eslint@8.57.0): 607 | resolution: {integrity: sha512-Rq2QY6BZP2meNIs48aZ3GlIlJgBqFCmR55+UBvaDkA3ZNQ0SvQXOs2QKkubakEijV8UbIVbVZKsOVN8G3MuqZw==} 608 | engines: {node: ^14 || ^16 || ^17 || ^18 || ^19} 609 | peerDependencies: 610 | eslint: ^7.0.0 || ^8.0.0 611 | dependencies: 612 | '@es-joy/jsdoccomment': 0.36.1 613 | comment-parser: 1.3.1 614 | debug: 4.3.4(supports-color@8.1.1) 615 | escape-string-regexp: 4.0.0 616 | eslint: 8.57.0 617 | esquery: 1.5.0 618 | semver: 7.6.0 619 | spdx-expression-parse: 3.0.1 620 | transitivePeerDependencies: 621 | - supports-color 622 | dev: true 623 | 624 | /eslint-scope@7.2.2: 625 | resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} 626 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 627 | dependencies: 628 | esrecurse: 4.3.0 629 | estraverse: 5.3.0 630 | dev: true 631 | 632 | /eslint-visitor-keys@3.4.3: 633 | resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} 634 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 635 | dev: true 636 | 637 | /eslint@8.57.0: 638 | resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} 639 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 640 | hasBin: true 641 | dependencies: 642 | '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) 643 | '@eslint-community/regexpp': 4.10.0 644 | '@eslint/eslintrc': 2.1.4 645 | '@eslint/js': 8.57.0 646 | '@humanwhocodes/config-array': 0.11.14 647 | '@humanwhocodes/module-importer': 1.0.1 648 | '@nodelib/fs.walk': 1.2.8 649 | '@ungap/structured-clone': 1.2.0 650 | ajv: 6.12.6 651 | chalk: 4.1.2 652 | cross-spawn: 7.0.3 653 | debug: 4.3.4(supports-color@8.1.1) 654 | doctrine: 3.0.0 655 | escape-string-regexp: 4.0.0 656 | eslint-scope: 7.2.2 657 | eslint-visitor-keys: 3.4.3 658 | espree: 9.6.1 659 | esquery: 1.5.0 660 | esutils: 2.0.3 661 | fast-deep-equal: 3.1.3 662 | file-entry-cache: 6.0.1 663 | find-up: 5.0.0 664 | glob-parent: 6.0.2 665 | globals: 13.24.0 666 | graphemer: 1.4.0 667 | ignore: 5.3.1 668 | imurmurhash: 0.1.4 669 | is-glob: 4.0.3 670 | is-path-inside: 3.0.3 671 | js-yaml: 4.1.0 672 | json-stable-stringify-without-jsonify: 1.0.1 673 | levn: 0.4.1 674 | lodash.merge: 4.6.2 675 | minimatch: 3.1.2 676 | natural-compare: 1.4.0 677 | optionator: 0.9.3 678 | strip-ansi: 6.0.1 679 | text-table: 0.2.0 680 | transitivePeerDependencies: 681 | - supports-color 682 | dev: true 683 | 684 | /espree@9.6.1: 685 | resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} 686 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 687 | dependencies: 688 | acorn: 8.11.3 689 | acorn-jsx: 5.3.2(acorn@8.11.3) 690 | eslint-visitor-keys: 3.4.3 691 | dev: true 692 | 693 | /esquery@1.5.0: 694 | resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} 695 | engines: {node: '>=0.10'} 696 | dependencies: 697 | estraverse: 5.3.0 698 | dev: true 699 | 700 | /esrecurse@4.3.0: 701 | resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} 702 | engines: {node: '>=4.0'} 703 | dependencies: 704 | estraverse: 5.3.0 705 | dev: true 706 | 707 | /estraverse@5.3.0: 708 | resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} 709 | engines: {node: '>=4.0'} 710 | dev: true 711 | 712 | /esutils@2.0.3: 713 | resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} 714 | engines: {node: '>=0.10.0'} 715 | dev: true 716 | 717 | /event-target-shim@5.0.1: 718 | resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} 719 | engines: {node: '>=6'} 720 | dev: false 721 | 722 | /events@3.3.0: 723 | resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} 724 | engines: {node: '>=0.8.x'} 725 | dev: false 726 | 727 | /extract-zip@2.0.1: 728 | resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} 729 | engines: {node: '>= 10.17.0'} 730 | hasBin: true 731 | dependencies: 732 | debug: 4.3.4(supports-color@8.1.1) 733 | get-stream: 5.2.0 734 | yauzl: 2.10.0 735 | optionalDependencies: 736 | '@types/yauzl': 2.10.3 737 | transitivePeerDependencies: 738 | - supports-color 739 | dev: false 740 | 741 | /fast-deep-equal@3.1.3: 742 | resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} 743 | 744 | /fast-json-stable-stringify@2.1.0: 745 | resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} 746 | 747 | /fast-levenshtein@2.0.6: 748 | resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} 749 | dev: true 750 | 751 | /fastq@1.17.1: 752 | resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} 753 | dependencies: 754 | reusify: 1.0.4 755 | dev: true 756 | 757 | /fd-slicer@1.1.0: 758 | resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} 759 | dependencies: 760 | pend: 1.2.0 761 | dev: false 762 | 763 | /file-entry-cache@6.0.1: 764 | resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} 765 | engines: {node: ^10.12.0 || >=12.0.0} 766 | dependencies: 767 | flat-cache: 3.2.0 768 | dev: true 769 | 770 | /fill-range@7.0.1: 771 | resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} 772 | engines: {node: '>=8'} 773 | dependencies: 774 | to-regex-range: 5.0.1 775 | dev: true 776 | 777 | /find-up@5.0.0: 778 | resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} 779 | engines: {node: '>=10'} 780 | dependencies: 781 | locate-path: 6.0.0 782 | path-exists: 4.0.0 783 | dev: true 784 | 785 | /flat-cache@3.2.0: 786 | resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} 787 | engines: {node: ^10.12.0 || >=12.0.0} 788 | dependencies: 789 | flatted: 3.3.1 790 | keyv: 4.5.4 791 | rimraf: 3.0.2 792 | dev: true 793 | 794 | /flat@5.0.2: 795 | resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} 796 | hasBin: true 797 | dev: true 798 | 799 | /flatted@3.3.1: 800 | resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} 801 | dev: true 802 | 803 | /follow-redirects@1.15.5(debug@4.3.4): 804 | resolution: {integrity: sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==} 805 | engines: {node: '>=4.0'} 806 | peerDependencies: 807 | debug: '*' 808 | peerDependenciesMeta: 809 | debug: 810 | optional: true 811 | dependencies: 812 | debug: 4.3.4(supports-color@8.1.1) 813 | dev: false 814 | 815 | /fs.realpath@1.0.0: 816 | resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} 817 | dev: true 818 | 819 | /fsevents@2.3.3: 820 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 821 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 822 | os: [darwin] 823 | requiresBuild: true 824 | dev: true 825 | optional: true 826 | 827 | /get-caller-file@2.0.5: 828 | resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} 829 | engines: {node: 6.* || 8.* || >= 10.*} 830 | dev: true 831 | 832 | /get-stream@5.2.0: 833 | resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} 834 | engines: {node: '>=8'} 835 | dependencies: 836 | pump: 3.0.0 837 | dev: false 838 | 839 | /glob-parent@5.1.2: 840 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 841 | engines: {node: '>= 6'} 842 | dependencies: 843 | is-glob: 4.0.3 844 | dev: true 845 | 846 | /glob-parent@6.0.2: 847 | resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} 848 | engines: {node: '>=10.13.0'} 849 | dependencies: 850 | is-glob: 4.0.3 851 | dev: true 852 | 853 | /glob@7.2.3: 854 | resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} 855 | dependencies: 856 | fs.realpath: 1.0.0 857 | inflight: 1.0.6 858 | inherits: 2.0.4 859 | minimatch: 3.1.2 860 | once: 1.4.0 861 | path-is-absolute: 1.0.1 862 | dev: true 863 | 864 | /glob@8.1.0: 865 | resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} 866 | engines: {node: '>=12'} 867 | dependencies: 868 | fs.realpath: 1.0.0 869 | inflight: 1.0.6 870 | inherits: 2.0.4 871 | minimatch: 5.0.1 872 | once: 1.4.0 873 | dev: true 874 | 875 | /globals@13.24.0: 876 | resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} 877 | engines: {node: '>=8'} 878 | dependencies: 879 | type-fest: 0.20.2 880 | dev: true 881 | 882 | /got@11.8.6: 883 | resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==} 884 | engines: {node: '>=10.19.0'} 885 | dependencies: 886 | '@sindresorhus/is': 4.6.0 887 | '@szmarczak/http-timer': 4.0.6 888 | '@types/cacheable-request': 6.0.3 889 | '@types/responselike': 1.0.3 890 | cacheable-lookup: 5.0.4 891 | cacheable-request: 7.0.4 892 | decompress-response: 6.0.0 893 | http2-wrapper: 1.0.3 894 | lowercase-keys: 2.0.0 895 | p-cancelable: 2.1.1 896 | responselike: 2.0.1 897 | dev: false 898 | 899 | /graphemer@1.4.0: 900 | resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} 901 | dev: true 902 | 903 | /growly@1.3.0: 904 | resolution: {integrity: sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==} 905 | dev: false 906 | 907 | /has-flag@4.0.0: 908 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 909 | engines: {node: '>=8'} 910 | 911 | /he@1.2.0: 912 | resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} 913 | hasBin: true 914 | dev: true 915 | 916 | /hpagent@0.1.2: 917 | resolution: {integrity: sha512-ePqFXHtSQWAFXYmj+JtOTHr84iNrII4/QRlAAPPE+zqnKy4xJo7Ie1Y4kC7AdB+LxLxSTTzBMASsEcy0q8YyvQ==} 918 | requiresBuild: true 919 | dev: false 920 | optional: true 921 | 922 | /http-cache-semantics@4.1.1: 923 | resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} 924 | dev: false 925 | 926 | /http2-wrapper@1.0.3: 927 | resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} 928 | engines: {node: '>=10.19.0'} 929 | dependencies: 930 | quick-lru: 5.1.1 931 | resolve-alpn: 1.2.1 932 | dev: false 933 | 934 | /ieee754@1.2.1: 935 | resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} 936 | dev: false 937 | 938 | /ignore@5.3.1: 939 | resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} 940 | engines: {node: '>= 4'} 941 | dev: true 942 | 943 | /import-fresh@3.3.0: 944 | resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} 945 | engines: {node: '>=6'} 946 | dependencies: 947 | parent-module: 1.0.1 948 | resolve-from: 4.0.0 949 | dev: true 950 | 951 | /imurmurhash@0.1.4: 952 | resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} 953 | engines: {node: '>=0.8.19'} 954 | dev: true 955 | 956 | /inflight@1.0.6: 957 | resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} 958 | dependencies: 959 | once: 1.4.0 960 | wrappy: 1.0.2 961 | dev: true 962 | 963 | /inherits@2.0.4: 964 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 965 | 966 | /is-binary-path@2.1.0: 967 | resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} 968 | engines: {node: '>=8'} 969 | dependencies: 970 | binary-extensions: 2.2.0 971 | dev: true 972 | 973 | /is-docker@2.2.1: 974 | resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} 975 | engines: {node: '>=8'} 976 | hasBin: true 977 | dev: false 978 | 979 | /is-extglob@2.1.1: 980 | resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 981 | engines: {node: '>=0.10.0'} 982 | dev: true 983 | 984 | /is-fullwidth-code-point@3.0.0: 985 | resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} 986 | engines: {node: '>=8'} 987 | dev: true 988 | 989 | /is-glob@4.0.3: 990 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 991 | engines: {node: '>=0.10.0'} 992 | dependencies: 993 | is-extglob: 2.1.1 994 | dev: true 995 | 996 | /is-number@7.0.0: 997 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 998 | engines: {node: '>=0.12.0'} 999 | dev: true 1000 | 1001 | /is-path-inside@3.0.3: 1002 | resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} 1003 | engines: {node: '>=8'} 1004 | dev: true 1005 | 1006 | /is-plain-obj@2.1.0: 1007 | resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} 1008 | engines: {node: '>=8'} 1009 | dev: true 1010 | 1011 | /is-unicode-supported@0.1.0: 1012 | resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} 1013 | engines: {node: '>=10'} 1014 | dev: true 1015 | 1016 | /is-wsl@2.2.0: 1017 | resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} 1018 | engines: {node: '>=8'} 1019 | dependencies: 1020 | is-docker: 2.2.1 1021 | dev: false 1022 | 1023 | /isexe@2.0.0: 1024 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 1025 | 1026 | /joi@17.12.2: 1027 | resolution: {integrity: sha512-RonXAIzCiHLc8ss3Ibuz45u28GOsWE1UpfDXLbN/9NKbL4tCJf8TWYVKsoYuuh+sAUt7fsSNpA+r2+TBA6Wjmw==} 1028 | dependencies: 1029 | '@hapi/hoek': 9.3.0 1030 | '@hapi/topo': 5.1.0 1031 | '@sideway/address': 4.1.5 1032 | '@sideway/formula': 3.0.1 1033 | '@sideway/pinpoint': 2.0.0 1034 | dev: false 1035 | 1036 | /jose@4.15.4: 1037 | resolution: {integrity: sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==} 1038 | dev: false 1039 | 1040 | /js-yaml@4.1.0: 1041 | resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} 1042 | hasBin: true 1043 | dependencies: 1044 | argparse: 2.0.1 1045 | dev: true 1046 | 1047 | /jsdoc-type-pratt-parser@3.1.0: 1048 | resolution: {integrity: sha512-MgtD0ZiCDk9B+eI73BextfRrVQl0oyzRG8B2BjORts6jbunj4ScKPcyXGTbB6eXL4y9TzxCm6hyeLq/2ASzNdw==} 1049 | engines: {node: '>=12.0.0'} 1050 | dev: true 1051 | 1052 | /json-buffer@3.0.1: 1053 | resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} 1054 | 1055 | /json-schema-traverse@0.4.1: 1056 | resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} 1057 | 1058 | /json-stable-stringify-without-jsonify@1.0.1: 1059 | resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} 1060 | dev: true 1061 | 1062 | /json5@2.2.3: 1063 | resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} 1064 | engines: {node: '>=6'} 1065 | hasBin: true 1066 | dev: false 1067 | 1068 | /jsonwebtoken@9.0.2: 1069 | resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} 1070 | engines: {node: '>=12', npm: '>=6'} 1071 | dependencies: 1072 | jws: 3.2.2 1073 | lodash.includes: 4.3.0 1074 | lodash.isboolean: 3.0.3 1075 | lodash.isinteger: 4.0.4 1076 | lodash.isnumber: 3.0.3 1077 | lodash.isplainobject: 4.0.6 1078 | lodash.isstring: 4.0.1 1079 | lodash.once: 4.1.1 1080 | ms: 2.1.3 1081 | semver: 7.6.0 1082 | dev: false 1083 | 1084 | /jwa@1.4.1: 1085 | resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} 1086 | dependencies: 1087 | buffer-equal-constant-time: 1.0.1 1088 | ecdsa-sig-formatter: 1.0.11 1089 | safe-buffer: 5.2.1 1090 | dev: false 1091 | 1092 | /jws@3.2.2: 1093 | resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} 1094 | dependencies: 1095 | jwa: 1.4.1 1096 | safe-buffer: 5.2.1 1097 | dev: false 1098 | 1099 | /keyv@4.5.4: 1100 | resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} 1101 | dependencies: 1102 | json-buffer: 3.0.1 1103 | 1104 | /levn@0.4.1: 1105 | resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} 1106 | engines: {node: '>= 0.8.0'} 1107 | dependencies: 1108 | prelude-ls: 1.2.1 1109 | type-check: 0.4.0 1110 | dev: true 1111 | 1112 | /locate-path@6.0.0: 1113 | resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} 1114 | engines: {node: '>=10'} 1115 | dependencies: 1116 | p-locate: 5.0.0 1117 | dev: true 1118 | 1119 | /lodash.clonedeep@4.5.0: 1120 | resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} 1121 | dev: false 1122 | 1123 | /lodash.get@4.4.2: 1124 | resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} 1125 | dev: false 1126 | 1127 | /lodash.includes@4.3.0: 1128 | resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} 1129 | dev: false 1130 | 1131 | /lodash.isboolean@3.0.3: 1132 | resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} 1133 | dev: false 1134 | 1135 | /lodash.isinteger@4.0.4: 1136 | resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} 1137 | dev: false 1138 | 1139 | /lodash.isnumber@3.0.3: 1140 | resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} 1141 | dev: false 1142 | 1143 | /lodash.isplainobject@4.0.6: 1144 | resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} 1145 | dev: false 1146 | 1147 | /lodash.isstring@4.0.1: 1148 | resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} 1149 | dev: false 1150 | 1151 | /lodash.merge@4.6.2: 1152 | resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} 1153 | 1154 | /lodash.once@4.1.1: 1155 | resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} 1156 | dev: false 1157 | 1158 | /lodash.reduce@4.6.0: 1159 | resolution: {integrity: sha512-6raRe2vxCYBhpBu+B+TtNGUzah+hQjVdu3E17wfusjyrXBka2nBS8OH/gjVZ5PvHOhWmIZTYri09Z6n/QfnNMw==} 1160 | dev: false 1161 | 1162 | /log-symbols@4.1.0: 1163 | resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} 1164 | engines: {node: '>=10'} 1165 | dependencies: 1166 | chalk: 4.1.2 1167 | is-unicode-supported: 0.1.0 1168 | dev: true 1169 | 1170 | /lowercase-keys@2.0.0: 1171 | resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} 1172 | engines: {node: '>=8'} 1173 | dev: false 1174 | 1175 | /lru-cache@6.0.0: 1176 | resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} 1177 | engines: {node: '>=10'} 1178 | dependencies: 1179 | yallist: 4.0.0 1180 | 1181 | /macaddress@0.5.3: 1182 | resolution: {integrity: sha512-vGBKTA+jwM4KgjGZ+S/8/Mkj9rWzePyGY6jManXPGhiWu63RYwW8dKPyk5koP+8qNVhPhHgFa1y/MJ4wrjsNrg==} 1183 | dev: false 1184 | 1185 | /mimic-response@1.0.1: 1186 | resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} 1187 | engines: {node: '>=4'} 1188 | dev: false 1189 | 1190 | /mimic-response@3.1.0: 1191 | resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} 1192 | engines: {node: '>=10'} 1193 | dev: false 1194 | 1195 | /minecraft-data@3.62.0: 1196 | resolution: {integrity: sha512-jJXZ/WgyX79tIHlqvfyqG+sJDUekHiA3e9NRUCMGUno4NDrZMcpRI065DnkrI720RHTMb8iadf0NmnBP4Rla5A==} 1197 | dev: false 1198 | 1199 | /minecraft-folder-path@1.2.0: 1200 | resolution: {integrity: sha512-qaUSbKWoOsH9brn0JQuBhxNAzTDMwrOXorwuRxdJKKKDYvZhtml+6GVCUrY5HRiEsieBEjCUnhVpDuQiKsiFaw==} 1201 | dev: false 1202 | 1203 | /minecraft-protocol@1.47.0: 1204 | resolution: {integrity: sha512-IHL8faXLLIWv1O+2v2NgyKlooilu/OiSL9orI8Kqed/rZvVOrFPzs2PwMAYjpQX9gxLPhiSU19KqZ8CjfNuqhg==} 1205 | engines: {node: '>=14'} 1206 | dependencies: 1207 | '@types/readable-stream': 4.0.10 1208 | aes-js: 3.1.2 1209 | buffer-equal: 1.0.1 1210 | debug: 4.3.4(supports-color@8.1.1) 1211 | endian-toggle: 0.0.0 1212 | lodash.get: 4.4.2 1213 | lodash.merge: 4.6.2 1214 | minecraft-data: 3.62.0 1215 | minecraft-folder-path: 1.2.0 1216 | node-fetch: 2.7.0 1217 | node-rsa: 0.4.2 1218 | prismarine-auth: 2.4.1 1219 | prismarine-chat: 1.10.0 1220 | prismarine-nbt: 2.5.0 1221 | prismarine-realms: 1.3.2 1222 | protodef: 1.15.0 1223 | readable-stream: 4.5.2 1224 | uuid-1345: 1.0.2 1225 | yggdrasil: 1.7.0 1226 | transitivePeerDependencies: 1227 | - encoding 1228 | - supports-color 1229 | dev: false 1230 | 1231 | /mineflayer-auto-eat@3.3.6: 1232 | resolution: {integrity: sha512-CgtIboWu5xB7bWmPTtU66TgfPoKdyOmtgFBEQZ1RoEwednX/cVBTZmMTMpG8PLOPPbfb4wBi1Qd7A0qmkd0SFA==} 1233 | dev: false 1234 | 1235 | /mineflayer@4.20.0: 1236 | resolution: {integrity: sha512-X2cRjbPnAXFlz+byXs3yIFiGTCkB1v4Ei+BhMurfmVy16Wvz9wC1Wd/e+QAex6KOJ8BIPu7JIoq+47VyFmcrDA==} 1237 | engines: {node: '>=18'} 1238 | dependencies: 1239 | minecraft-data: 3.62.0 1240 | minecraft-protocol: 1.47.0 1241 | prismarine-biome: 1.3.0(minecraft-data@3.62.0)(prismarine-registry@1.7.0) 1242 | prismarine-block: 1.17.1 1243 | prismarine-chat: 1.10.0 1244 | prismarine-chunk: 1.35.0(minecraft-data@3.62.0) 1245 | prismarine-entity: 2.4.0 1246 | prismarine-item: 1.14.0 1247 | prismarine-nbt: 2.5.0 1248 | prismarine-physics: 1.8.0 1249 | prismarine-recipe: 1.3.1(prismarine-registry@1.7.0) 1250 | prismarine-registry: 1.7.0 1251 | prismarine-windows: 2.9.0 1252 | prismarine-world: 3.6.3 1253 | protodef: 1.15.0 1254 | typed-emitter: 1.4.0 1255 | vec3: 0.1.10 1256 | transitivePeerDependencies: 1257 | - encoding 1258 | - supports-color 1259 | dev: false 1260 | 1261 | /minimatch@3.1.2: 1262 | resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 1263 | dependencies: 1264 | brace-expansion: 1.1.11 1265 | dev: true 1266 | 1267 | /minimatch@5.0.1: 1268 | resolution: {integrity: sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==} 1269 | engines: {node: '>=10'} 1270 | dependencies: 1271 | brace-expansion: 2.0.1 1272 | dev: true 1273 | 1274 | /mocha@10.3.0: 1275 | resolution: {integrity: sha512-uF2XJs+7xSLsrmIvn37i/wnc91nw7XjOQB8ccyx5aEgdnohr7n+rEiZP23WkCYHjilR6+EboEnbq/ZQDz4LSbg==} 1276 | engines: {node: '>= 14.0.0'} 1277 | hasBin: true 1278 | dependencies: 1279 | ansi-colors: 4.1.1 1280 | browser-stdout: 1.3.1 1281 | chokidar: 3.5.3 1282 | debug: 4.3.4(supports-color@8.1.1) 1283 | diff: 5.0.0 1284 | escape-string-regexp: 4.0.0 1285 | find-up: 5.0.0 1286 | glob: 8.1.0 1287 | he: 1.2.0 1288 | js-yaml: 4.1.0 1289 | log-symbols: 4.1.0 1290 | minimatch: 5.0.1 1291 | ms: 2.1.3 1292 | serialize-javascript: 6.0.0 1293 | strip-json-comments: 3.1.1 1294 | supports-color: 8.1.1 1295 | workerpool: 6.2.1 1296 | yargs: 16.2.0 1297 | yargs-parser: 20.2.4 1298 | yargs-unparser: 2.0.0 1299 | dev: true 1300 | 1301 | /mojangson@2.0.4: 1302 | resolution: {integrity: sha512-HYmhgDjr1gzF7trGgvcC/huIg2L8FsVbi/KacRe6r1AswbboGVZDS47SOZlomPuMWvZLas8m9vuHHucdZMwTmQ==} 1303 | dependencies: 1304 | nearley: 2.20.1 1305 | dev: false 1306 | 1307 | /moo@0.5.2: 1308 | resolution: {integrity: sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==} 1309 | dev: false 1310 | 1311 | /ms@2.1.2: 1312 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} 1313 | 1314 | /ms@2.1.3: 1315 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 1316 | 1317 | /natural-compare@1.4.0: 1318 | resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} 1319 | dev: true 1320 | 1321 | /nearley@2.20.1: 1322 | resolution: {integrity: sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==} 1323 | hasBin: true 1324 | dependencies: 1325 | commander: 2.20.3 1326 | moo: 0.5.2 1327 | railroad-diagrams: 1.0.0 1328 | randexp: 0.4.6 1329 | dev: false 1330 | 1331 | /ngrok@4.3.3: 1332 | resolution: {integrity: sha512-a2KApnkiG5urRxBPdDf76nNBQTnNNWXU0nXw0SsqsPI+Kmt2lGf9TdVYpYrHMnC+T9KhcNSWjCpWqBgC6QcFvw==} 1333 | engines: {node: '>=10.19.0 <14 || >=14.2'} 1334 | hasBin: true 1335 | requiresBuild: true 1336 | dependencies: 1337 | '@types/node': 8.10.66 1338 | extract-zip: 2.0.1 1339 | got: 11.8.6 1340 | lodash.clonedeep: 4.5.0 1341 | uuid: 8.3.2 1342 | yaml: 1.10.2 1343 | optionalDependencies: 1344 | hpagent: 0.1.2 1345 | transitivePeerDependencies: 1346 | - supports-color 1347 | dev: false 1348 | 1349 | /node-fetch@2.7.0: 1350 | resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} 1351 | engines: {node: 4.x || >=6.0.0} 1352 | peerDependencies: 1353 | encoding: ^0.1.0 1354 | peerDependenciesMeta: 1355 | encoding: 1356 | optional: true 1357 | dependencies: 1358 | whatwg-url: 5.0.0 1359 | dev: false 1360 | 1361 | /node-notifier@10.0.1: 1362 | resolution: {integrity: sha512-YX7TSyDukOZ0g+gmzjB6abKu+hTGvO8+8+gIFDsRCU2t8fLV/P2unmt+LGFaIa4y64aX98Qksa97rgz4vMNeLQ==} 1363 | dependencies: 1364 | growly: 1.3.0 1365 | is-wsl: 2.2.0 1366 | semver: 7.6.0 1367 | shellwords: 0.1.1 1368 | uuid: 8.3.2 1369 | which: 2.0.2 1370 | dev: false 1371 | 1372 | /node-rsa@0.4.2: 1373 | resolution: {integrity: sha512-Bvso6Zi9LY4otIZefYrscsUpo2mUpiAVIEmSZV2q41sP8tHZoert3Yu6zv4f/RXJqMNZQKCtnhDugIuCma23YA==} 1374 | dependencies: 1375 | asn1: 0.2.3 1376 | dev: false 1377 | 1378 | /normalize-path@3.0.0: 1379 | resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} 1380 | engines: {node: '>=0.10.0'} 1381 | dev: true 1382 | 1383 | /normalize-url@6.1.0: 1384 | resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} 1385 | engines: {node: '>=10'} 1386 | dev: false 1387 | 1388 | /once@1.4.0: 1389 | resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 1390 | dependencies: 1391 | wrappy: 1.0.2 1392 | 1393 | /optionator@0.9.3: 1394 | resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} 1395 | engines: {node: '>= 0.8.0'} 1396 | dependencies: 1397 | '@aashutoshrathi/word-wrap': 1.2.6 1398 | deep-is: 0.1.4 1399 | fast-levenshtein: 2.0.6 1400 | levn: 0.4.1 1401 | prelude-ls: 1.2.1 1402 | type-check: 0.4.0 1403 | dev: true 1404 | 1405 | /p-cancelable@2.1.1: 1406 | resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} 1407 | engines: {node: '>=8'} 1408 | dev: false 1409 | 1410 | /p-limit@3.1.0: 1411 | resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} 1412 | engines: {node: '>=10'} 1413 | dependencies: 1414 | yocto-queue: 0.1.0 1415 | dev: true 1416 | 1417 | /p-locate@5.0.0: 1418 | resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} 1419 | engines: {node: '>=10'} 1420 | dependencies: 1421 | p-limit: 3.1.0 1422 | dev: true 1423 | 1424 | /parent-module@1.0.1: 1425 | resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} 1426 | engines: {node: '>=6'} 1427 | dependencies: 1428 | callsites: 3.1.0 1429 | dev: true 1430 | 1431 | /path-exists@4.0.0: 1432 | resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} 1433 | engines: {node: '>=8'} 1434 | dev: true 1435 | 1436 | /path-is-absolute@1.0.1: 1437 | resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} 1438 | engines: {node: '>=0.10.0'} 1439 | dev: true 1440 | 1441 | /path-key@3.1.1: 1442 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 1443 | engines: {node: '>=8'} 1444 | dev: true 1445 | 1446 | /pend@1.2.0: 1447 | resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} 1448 | dev: false 1449 | 1450 | /picomatch@2.3.1: 1451 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 1452 | engines: {node: '>=8.6'} 1453 | dev: true 1454 | 1455 | /prelude-ls@1.2.1: 1456 | resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} 1457 | engines: {node: '>= 0.8.0'} 1458 | dev: true 1459 | 1460 | /prismarine-auth@2.4.1: 1461 | resolution: {integrity: sha512-DwDI3Ucxf/eThJJo5QVzlywFrJulL1fK1z6F8bybvddim8YgudRksQc3w4cE2m0hPPHfE1BRd5lh1NpedrixMQ==} 1462 | dependencies: 1463 | '@azure/msal-node': 2.6.4 1464 | '@xboxreplay/xboxlive-auth': 3.3.3(debug@4.3.4) 1465 | debug: 4.3.4(supports-color@8.1.1) 1466 | jose: 4.15.4 1467 | node-fetch: 2.7.0 1468 | smart-buffer: 4.2.0 1469 | uuid-1345: 1.0.2 1470 | transitivePeerDependencies: 1471 | - encoding 1472 | - supports-color 1473 | dev: false 1474 | 1475 | /prismarine-biome@1.3.0(minecraft-data@3.62.0)(prismarine-registry@1.7.0): 1476 | resolution: {integrity: sha512-GY6nZxq93mTErT7jD7jt8YS1aPrOakbJHh39seYsJFXvueIOdHAmW16kYQVrTVMW5MlWLQVxV/EquRwOgr4MnQ==} 1477 | peerDependencies: 1478 | minecraft-data: ^3.0.0 1479 | prismarine-registry: ^1.1.0 1480 | dependencies: 1481 | minecraft-data: 3.62.0 1482 | prismarine-registry: 1.7.0 1483 | dev: false 1484 | 1485 | /prismarine-block@1.17.1: 1486 | resolution: {integrity: sha512-r1TIn/b5v77BX4a+qd+Yv+4/vZpsC/Jp5ElYxd6++2wpCnqiuxVG7BlS2Eo14vez1M2gt3qoNEl54Hr8qox/rQ==} 1487 | dependencies: 1488 | minecraft-data: 3.62.0 1489 | prismarine-biome: 1.3.0(minecraft-data@3.62.0)(prismarine-registry@1.7.0) 1490 | prismarine-chat: 1.10.0 1491 | prismarine-item: 1.14.0 1492 | prismarine-nbt: 2.5.0 1493 | prismarine-registry: 1.7.0 1494 | dev: false 1495 | 1496 | /prismarine-chat@1.10.0: 1497 | resolution: {integrity: sha512-f9ESzi2Kkf4GJadtgBl+SmvtAlmOXaso6Fxt4M/vUXDKgQNSk66APmIHnBkKWyjzG9X2VXsbDGeIIPf/d3guxA==} 1498 | dependencies: 1499 | mojangson: 2.0.4 1500 | prismarine-nbt: 2.5.0 1501 | prismarine-registry: 1.7.0 1502 | dev: false 1503 | 1504 | /prismarine-chunk@1.35.0(minecraft-data@3.62.0): 1505 | resolution: {integrity: sha512-Q1lElMUle7wWxWdQjbZo3j2/dLNG325j90IcbbMmBTnHdQSWIjWFe792XOz3RVBlvrhRJEiZk38S6/eQTQ9esw==} 1506 | engines: {node: '>=14'} 1507 | dependencies: 1508 | prismarine-biome: 1.3.0(minecraft-data@3.62.0)(prismarine-registry@1.7.0) 1509 | prismarine-block: 1.17.1 1510 | prismarine-nbt: 2.5.0 1511 | prismarine-registry: 1.7.0 1512 | smart-buffer: 4.2.0 1513 | uint4: 0.1.2 1514 | vec3: 0.1.10 1515 | xxhash-wasm: 0.4.2 1516 | transitivePeerDependencies: 1517 | - minecraft-data 1518 | dev: false 1519 | 1520 | /prismarine-entity@2.4.0: 1521 | resolution: {integrity: sha512-DBwjmoCX1IYAhN99KwYkk2rMArn65JHTzuuGXchr4GLWQs7UN4Pf9tELqBwNOu4r57x3RaW0+9+0sI3FvJQWzQ==} 1522 | dependencies: 1523 | prismarine-chat: 1.10.0 1524 | prismarine-item: 1.14.0 1525 | prismarine-registry: 1.7.0 1526 | vec3: 0.1.10 1527 | dev: false 1528 | 1529 | /prismarine-item@1.14.0: 1530 | resolution: {integrity: sha512-udQHYGJ05klFe8Kkc0TOmwoXj5Xl1ZPgHVoMbGUAFB9exN4TFxEa1A39vkSYhxP5Et9PNufQQvFBFVom0nXikA==} 1531 | dependencies: 1532 | prismarine-nbt: 2.5.0 1533 | prismarine-registry: 1.7.0 1534 | dev: false 1535 | 1536 | /prismarine-nbt@2.5.0: 1537 | resolution: {integrity: sha512-F0/8UAa9SDDnAGrBYqZc4nG8h2zj5cE2eAJU5xlDR/IsQQ3moVxkOjE3h3nMv6SbvZrvAcgX7waA/nd9LLHYdA==} 1538 | dependencies: 1539 | protodef: 1.15.0 1540 | dev: false 1541 | 1542 | /prismarine-physics@1.8.0: 1543 | resolution: {integrity: sha512-gbM+S+bmVtOKVv+Z0WGaHMeEeBHISIDsRDRlv8sr0dex3ZJRhuq8djA02CBreguXtI18ZKh6q3TSj2qDr45NHA==} 1544 | dependencies: 1545 | minecraft-data: 3.62.0 1546 | prismarine-nbt: 2.5.0 1547 | vec3: 0.1.10 1548 | dev: false 1549 | 1550 | /prismarine-provider-anvil@2.7.0(minecraft-data@3.62.0): 1551 | resolution: {integrity: sha512-a4NfihRfy+PeKpiWp2CJxTUPMhSROXb7JzA44nfRXqHPDnK4ilC2+MBNeZyi5Wj5OuYRbSd8ZS44YkQMoFR9aQ==} 1552 | dependencies: 1553 | prismarine-chunk: 1.35.0(minecraft-data@3.62.0) 1554 | prismarine-nbt: 2.5.0 1555 | uint4: 0.1.2 1556 | vec3: 0.1.10 1557 | transitivePeerDependencies: 1558 | - minecraft-data 1559 | dev: false 1560 | 1561 | /prismarine-realms@1.3.2: 1562 | resolution: {integrity: sha512-5apl9Ru8veTj5q2OozRc4GZOuSIcs3yY4UEtALiLKHstBe8bRw8vNlaz4Zla3jsQ8yP/ul1b1IJINTRbocuA6g==} 1563 | dependencies: 1564 | debug: 4.3.4(supports-color@8.1.1) 1565 | node-fetch: 2.7.0 1566 | transitivePeerDependencies: 1567 | - encoding 1568 | - supports-color 1569 | dev: false 1570 | 1571 | /prismarine-recipe@1.3.1(prismarine-registry@1.7.0): 1572 | resolution: {integrity: sha512-xfa9E9ACoaDi+YzNQ+nk8kWSIqt5vSZOOCHIT+dTXscf/dng2HaJ/59uwe1D/jvOkAd2OvM6RRJM6fFe0q/LDA==} 1573 | peerDependencies: 1574 | prismarine-registry: ^1.4.0 1575 | dependencies: 1576 | prismarine-registry: 1.7.0 1577 | dev: false 1578 | 1579 | /prismarine-registry@1.7.0: 1580 | resolution: {integrity: sha512-yyva0FpWI078nNeMhx8ekVza5uUTYhEv+C+ADu3wUQXiG8qhXkvrf0uzsnhTgZL8BLdsi2axgCEiKw9qSKIuxQ==} 1581 | dependencies: 1582 | minecraft-data: 3.62.0 1583 | prismarine-nbt: 2.5.0 1584 | dev: false 1585 | 1586 | /prismarine-windows@2.9.0: 1587 | resolution: {integrity: sha512-fm4kOLjGFPov7TEJRmXHoiPabxIQrG36r2mDjlNxfkcLfMHFb3/1ML6mp4iRQa7wL0GK4DIAyiBqCWoeWDxARg==} 1588 | dependencies: 1589 | prismarine-item: 1.14.0 1590 | prismarine-registry: 1.7.0 1591 | typed-emitter: 2.1.0 1592 | dev: false 1593 | 1594 | /prismarine-world@3.6.3: 1595 | resolution: {integrity: sha512-zqdqPEYCDHzqi6hglJldEO63bOROXpbZeIdxBmoQq7o04Lf81t016LU6stFHo3E+bmp5+xU74eDFdOvzYNABkA==} 1596 | engines: {node: '>=8.0.0'} 1597 | dependencies: 1598 | vec3: 0.1.10 1599 | dev: false 1600 | 1601 | /process@0.11.10: 1602 | resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} 1603 | engines: {node: '>= 0.6.0'} 1604 | dev: false 1605 | 1606 | /protodef-validator@1.3.1: 1607 | resolution: {integrity: sha512-lZ5FWKZYR9xOjpMw1+EfZRfCjzNRQWPq+Dk+jki47Sikl2EeWEPnTfnJERwnU/EwFq6us+0zqHHzSsmLeYX+Lg==} 1608 | hasBin: true 1609 | dependencies: 1610 | ajv: 6.12.6 1611 | dev: false 1612 | 1613 | /protodef@1.15.0: 1614 | resolution: {integrity: sha512-bZ2Omw8dT+DACjJHLrBWZlqN4MlT9g9oSpJDdkUAJOStUzgJp+Zn42FJfPUdwutUxjaxA0PftN0PDlNa2XbneA==} 1615 | engines: {node: '>=14'} 1616 | dependencies: 1617 | lodash.get: 4.4.2 1618 | lodash.reduce: 4.6.0 1619 | protodef-validator: 1.3.1 1620 | readable-stream: 3.6.2 1621 | dev: false 1622 | 1623 | /pump@3.0.0: 1624 | resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} 1625 | dependencies: 1626 | end-of-stream: 1.4.4 1627 | once: 1.4.0 1628 | dev: false 1629 | 1630 | /punycode@2.3.1: 1631 | resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} 1632 | engines: {node: '>=6'} 1633 | 1634 | /queue-microtask@1.2.3: 1635 | resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} 1636 | dev: true 1637 | 1638 | /quick-lru@5.1.1: 1639 | resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} 1640 | engines: {node: '>=10'} 1641 | dev: false 1642 | 1643 | /railroad-diagrams@1.0.0: 1644 | resolution: {integrity: sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==} 1645 | dev: false 1646 | 1647 | /randexp@0.4.6: 1648 | resolution: {integrity: sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==} 1649 | engines: {node: '>=0.12'} 1650 | dependencies: 1651 | discontinuous-range: 1.0.0 1652 | ret: 0.1.15 1653 | dev: false 1654 | 1655 | /randombytes@2.1.0: 1656 | resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} 1657 | dependencies: 1658 | safe-buffer: 5.2.1 1659 | dev: true 1660 | 1661 | /readable-stream@3.6.2: 1662 | resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} 1663 | engines: {node: '>= 6'} 1664 | dependencies: 1665 | inherits: 2.0.4 1666 | string_decoder: 1.3.0 1667 | util-deprecate: 1.0.2 1668 | dev: false 1669 | 1670 | /readable-stream@4.5.2: 1671 | resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==} 1672 | engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 1673 | dependencies: 1674 | abort-controller: 3.0.0 1675 | buffer: 6.0.3 1676 | events: 3.3.0 1677 | process: 0.11.10 1678 | string_decoder: 1.3.0 1679 | dev: false 1680 | 1681 | /readdirp@3.6.0: 1682 | resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} 1683 | engines: {node: '>=8.10.0'} 1684 | dependencies: 1685 | picomatch: 2.3.1 1686 | dev: true 1687 | 1688 | /require-directory@2.1.1: 1689 | resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} 1690 | engines: {node: '>=0.10.0'} 1691 | dev: true 1692 | 1693 | /resolve-alpn@1.2.1: 1694 | resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} 1695 | dev: false 1696 | 1697 | /resolve-from@4.0.0: 1698 | resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} 1699 | engines: {node: '>=4'} 1700 | dev: true 1701 | 1702 | /responselike@2.0.1: 1703 | resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} 1704 | dependencies: 1705 | lowercase-keys: 2.0.0 1706 | dev: false 1707 | 1708 | /ret@0.1.15: 1709 | resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==} 1710 | engines: {node: '>=0.12'} 1711 | dev: false 1712 | 1713 | /reusify@1.0.4: 1714 | resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} 1715 | engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 1716 | dev: true 1717 | 1718 | /rimraf@3.0.2: 1719 | resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} 1720 | hasBin: true 1721 | dependencies: 1722 | glob: 7.2.3 1723 | dev: true 1724 | 1725 | /run-parallel@1.2.0: 1726 | resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 1727 | dependencies: 1728 | queue-microtask: 1.2.3 1729 | dev: true 1730 | 1731 | /rxjs@7.8.1: 1732 | resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} 1733 | requiresBuild: true 1734 | dependencies: 1735 | tslib: 2.6.2 1736 | dev: false 1737 | optional: true 1738 | 1739 | /safe-buffer@5.1.2: 1740 | resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} 1741 | dev: false 1742 | 1743 | /safe-buffer@5.2.1: 1744 | resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} 1745 | 1746 | /semver@7.6.0: 1747 | resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} 1748 | engines: {node: '>=10'} 1749 | hasBin: true 1750 | dependencies: 1751 | lru-cache: 6.0.0 1752 | 1753 | /serialize-javascript@6.0.0: 1754 | resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==} 1755 | dependencies: 1756 | randombytes: 2.1.0 1757 | dev: true 1758 | 1759 | /shebang-command@2.0.0: 1760 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 1761 | engines: {node: '>=8'} 1762 | dependencies: 1763 | shebang-regex: 3.0.0 1764 | dev: true 1765 | 1766 | /shebang-regex@3.0.0: 1767 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 1768 | engines: {node: '>=8'} 1769 | dev: true 1770 | 1771 | /shellwords@0.1.1: 1772 | resolution: {integrity: sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==} 1773 | dev: false 1774 | 1775 | /smart-buffer@4.2.0: 1776 | resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} 1777 | engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} 1778 | dev: false 1779 | 1780 | /spdx-exceptions@2.5.0: 1781 | resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} 1782 | dev: true 1783 | 1784 | /spdx-expression-parse@3.0.1: 1785 | resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} 1786 | dependencies: 1787 | spdx-exceptions: 2.5.0 1788 | spdx-license-ids: 3.0.17 1789 | dev: true 1790 | 1791 | /spdx-license-ids@3.0.17: 1792 | resolution: {integrity: sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==} 1793 | dev: true 1794 | 1795 | /string-width@4.2.3: 1796 | resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} 1797 | engines: {node: '>=8'} 1798 | dependencies: 1799 | emoji-regex: 8.0.0 1800 | is-fullwidth-code-point: 3.0.0 1801 | strip-ansi: 6.0.1 1802 | dev: true 1803 | 1804 | /string_decoder@1.3.0: 1805 | resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} 1806 | dependencies: 1807 | safe-buffer: 5.2.1 1808 | dev: false 1809 | 1810 | /strip-ansi@6.0.1: 1811 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 1812 | engines: {node: '>=8'} 1813 | dependencies: 1814 | ansi-regex: 5.0.1 1815 | dev: true 1816 | 1817 | /strip-json-comments@3.1.1: 1818 | resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} 1819 | engines: {node: '>=8'} 1820 | dev: true 1821 | 1822 | /supports-color@7.2.0: 1823 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 1824 | engines: {node: '>=8'} 1825 | dependencies: 1826 | has-flag: 4.0.0 1827 | dev: true 1828 | 1829 | /supports-color@8.1.1: 1830 | resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} 1831 | engines: {node: '>=10'} 1832 | dependencies: 1833 | has-flag: 4.0.0 1834 | 1835 | /text-table@0.2.0: 1836 | resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} 1837 | dev: true 1838 | 1839 | /to-regex-range@5.0.1: 1840 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 1841 | engines: {node: '>=8.0'} 1842 | dependencies: 1843 | is-number: 7.0.0 1844 | dev: true 1845 | 1846 | /tr46@0.0.3: 1847 | resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} 1848 | dev: false 1849 | 1850 | /tslib@2.6.2: 1851 | resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} 1852 | dev: false 1853 | optional: true 1854 | 1855 | /type-check@0.4.0: 1856 | resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} 1857 | engines: {node: '>= 0.8.0'} 1858 | dependencies: 1859 | prelude-ls: 1.2.1 1860 | dev: true 1861 | 1862 | /type-detect@4.0.8: 1863 | resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} 1864 | engines: {node: '>=4'} 1865 | dev: false 1866 | 1867 | /type-fest@0.20.2: 1868 | resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} 1869 | engines: {node: '>=10'} 1870 | dev: true 1871 | 1872 | /typed-emitter@1.4.0: 1873 | resolution: {integrity: sha512-weBmoo3HhpKGgLBOYwe8EB31CzDFuaK7CCL+axXhUYhn4jo6DSkHnbefboCF5i4DQ2aMFe0C/FdTWcPdObgHyg==} 1874 | dev: false 1875 | 1876 | /typed-emitter@2.1.0: 1877 | resolution: {integrity: sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==} 1878 | optionalDependencies: 1879 | rxjs: 7.8.1 1880 | dev: false 1881 | 1882 | /typescript@5.3.3: 1883 | resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} 1884 | engines: {node: '>=14.17'} 1885 | hasBin: true 1886 | dev: false 1887 | 1888 | /uint4@0.1.2: 1889 | resolution: {integrity: sha512-lhEx78gdTwFWG+mt6cWAZD/R6qrIj0TTBeH5xwyuDJyswLNlGe+KVlUPQ6+mx5Ld332pS0AMUTo9hIly7YsWxQ==} 1890 | dev: false 1891 | 1892 | /undici-types@5.26.5: 1893 | resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} 1894 | dev: false 1895 | 1896 | /uri-js@4.4.1: 1897 | resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} 1898 | dependencies: 1899 | punycode: 2.3.1 1900 | 1901 | /util-deprecate@1.0.2: 1902 | resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} 1903 | dev: false 1904 | 1905 | /uuid-1345@1.0.2: 1906 | resolution: {integrity: sha512-bA5zYZui+3nwAc0s3VdGQGBfbVsJLVX7Np7ch2aqcEWFi5lsAEcmO3+lx3djM1npgpZI8KY2FITZ2uYTnYUYyw==} 1907 | dependencies: 1908 | macaddress: 0.5.3 1909 | dev: false 1910 | 1911 | /uuid@8.3.2: 1912 | resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} 1913 | hasBin: true 1914 | dev: false 1915 | 1916 | /vec3@0.1.10: 1917 | resolution: {integrity: sha512-Sr1U3mYtMqCOonGd3LAN9iqy0qF6C+Gjil92awyK/i2OwiUo9bm7PnLgFpafymun50mOjnDcg4ToTgRssrlTcw==} 1918 | dev: false 1919 | 1920 | /webidl-conversions@3.0.1: 1921 | resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} 1922 | dev: false 1923 | 1924 | /whatwg-url@5.0.0: 1925 | resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} 1926 | dependencies: 1927 | tr46: 0.0.3 1928 | webidl-conversions: 3.0.1 1929 | dev: false 1930 | 1931 | /which@2.0.2: 1932 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 1933 | engines: {node: '>= 8'} 1934 | hasBin: true 1935 | dependencies: 1936 | isexe: 2.0.0 1937 | 1938 | /workerpool@6.2.1: 1939 | resolution: {integrity: sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==} 1940 | dev: true 1941 | 1942 | /wrap-ansi@7.0.0: 1943 | resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} 1944 | engines: {node: '>=10'} 1945 | dependencies: 1946 | ansi-styles: 4.3.0 1947 | string-width: 4.2.3 1948 | strip-ansi: 6.0.1 1949 | dev: true 1950 | 1951 | /wrappy@1.0.2: 1952 | resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 1953 | 1954 | /xxhash-wasm@0.4.2: 1955 | resolution: {integrity: sha512-/eyHVRJQCirEkSZ1agRSCwriMhwlyUcFkXD5TPVSLP+IPzjsqMVzZwdoczLp1SoQU0R3dxz1RpIK+4YNQbCVOA==} 1956 | dev: false 1957 | 1958 | /y18n@5.0.8: 1959 | resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} 1960 | engines: {node: '>=10'} 1961 | dev: true 1962 | 1963 | /yallist@4.0.0: 1964 | resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} 1965 | 1966 | /yaml@1.10.2: 1967 | resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} 1968 | engines: {node: '>= 6'} 1969 | dev: false 1970 | 1971 | /yargs-parser@20.2.4: 1972 | resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==} 1973 | engines: {node: '>=10'} 1974 | dev: true 1975 | 1976 | /yargs-unparser@2.0.0: 1977 | resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} 1978 | engines: {node: '>=10'} 1979 | dependencies: 1980 | camelcase: 6.3.0 1981 | decamelize: 4.0.0 1982 | flat: 5.0.2 1983 | is-plain-obj: 2.1.0 1984 | dev: true 1985 | 1986 | /yargs@16.2.0: 1987 | resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} 1988 | engines: {node: '>=10'} 1989 | dependencies: 1990 | cliui: 7.0.4 1991 | escalade: 3.1.2 1992 | get-caller-file: 2.0.5 1993 | require-directory: 2.1.1 1994 | string-width: 4.2.3 1995 | y18n: 5.0.8 1996 | yargs-parser: 20.2.4 1997 | dev: true 1998 | 1999 | /yauzl@2.10.0: 2000 | resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} 2001 | dependencies: 2002 | buffer-crc32: 0.2.13 2003 | fd-slicer: 1.1.0 2004 | dev: false 2005 | 2006 | /yggdrasil@1.7.0: 2007 | resolution: {integrity: sha512-QBIo5fiNd7688G3FqXXYGr36uyrYzczlNuzpWFy2zL3+R+3KT2lF+wFxm51synfA3l3z6IBiGOc1/EVXWCYY1Q==} 2008 | dependencies: 2009 | node-fetch: 2.7.0 2010 | uuid: 8.3.2 2011 | transitivePeerDependencies: 2012 | - encoding 2013 | dev: false 2014 | 2015 | /yocto-queue@0.1.0: 2016 | resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} 2017 | engines: {node: '>=10'} 2018 | dev: true 2019 | 2020 | github.com/Etiaro/mineflayer-antiafk/41a08c7e71adb052baee4b739510b6f78987291b: 2021 | resolution: {tarball: https://codeload.github.com/Etiaro/mineflayer-antiafk/tar.gz/41a08c7e71adb052baee4b739510b6f78987291b} 2022 | name: mineflayer-antiafk 2023 | version: 1.1.1 2024 | dependencies: 2025 | mineflayer-auto-eat: 3.3.6 2026 | vec3: 0.1.10 2027 | dev: false 2028 | -------------------------------------------------------------------------------- /proxy.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // ======= 4 | // Imports 5 | // ======= 6 | 7 | const mcproxy = require("@icetank/mcproxy"); 8 | const mc = require("minecraft-protocol"); 9 | 10 | const { config, status, updateStatus, updateCoordinatorStatus } = require("./util/config.js"); 11 | const logger = require("./util/logger.js"); 12 | const notifier = require("./util/notifier.js"); 13 | const chatty = require("./util/chatty.js"); 14 | const ngrok = require("./util/ngrok.js"); 15 | const mineflayer = require("./util/mineflayer.js"); 16 | const queue = require("./util/queue.js"); 17 | const downloader = require("./util/downloader.js"); 18 | 19 | // =========== 20 | // Global Vars 21 | // =========== 22 | 23 | let conn; 24 | let client; 25 | let server; 26 | 27 | // ============== 28 | // Initialization 29 | // ============== 30 | 31 | // Max out the threadpool (if enabled) 32 | if (config.experimental.maxThreadpool.active) { 33 | process.env.UV_THREADPOOL_SIZE = require("os").cpus().length; 34 | } 35 | 36 | // Start proxy 37 | start(); 38 | 39 | // ================= 40 | // Packet Handler(s) 41 | // ================= 42 | 43 | /** 44 | * Handle incoming packets 45 | * @param {object} packetData Packet object data 46 | * @param {object} packetMeta Packet metadata 47 | */ 48 | function packetHandler(packetData, packetMeta) { 49 | // Log packets 50 | logger.packetHandler(packetData, packetMeta, "server"); 51 | 52 | // Assorted packet handlers 53 | chatty.chatPacketHandler(packetMeta.name, packetData); 54 | queue.difficultyPacketHandler(packetMeta.name, packetData, conn); 55 | queue.playerlistHeaderPacketHandler(packetMeta.name, packetData, server); 56 | if (!config.experimental.worldDownloader.active) 57 | downloader.mapChunkPacketHandler(packetMeta.name, packetData); 58 | 59 | // Reset uncleanDisconnectMonitor timer 60 | refreshMonitor(); 61 | } 62 | 63 | // ================= 64 | // Start Proxy Stack 65 | // ================= 66 | 67 | /** Start proxy stack */ 68 | function start() { 69 | logger.log("proxy", "Starting proxy stack.", "proxy"); 70 | 71 | // Delete any leftover coordinator.flag files 72 | if (config.coordination.active) { 73 | updateCoordinatorStatus(); 74 | } 75 | 76 | // Create local server for proxy connections 77 | if (config.proxy.active) { 78 | createLocalServer(); 79 | } 80 | 81 | // Create client (connects to server) 82 | if (!config.waitForControllerBeforeConnect) { // ... but if waitForControllerBeforeConnect is true, delay the creation of the client until someone connects to the local server 83 | createClient(); 84 | } else { 85 | console.log("Waiting for a controller..."); 86 | if (!config.proxy.active) { 87 | console.log("WARNING: config.waitForControllerBeforeConnect is true but config.proxy.active is false, meaning there will never be a controller!"); 88 | } 89 | if (config.ngrok.active) { // Create ngrok tunnel 90 | ngrok.createTunnel(); // Note: May overwrite MSA instructions in console(?) 91 | } 92 | } 93 | } 94 | 95 | // ========================== 96 | // Client (Connect to Server) 97 | // ========================== 98 | 99 | /** 100 | * Create the client, initialize Mineflayer, and connect to the server 101 | */ 102 | function createClient() { 103 | console.log("Creating client (connecting to server)"); 104 | logger.log("proxy", "Creating client.", "proxy"); 105 | // Connect to server 106 | conn = new mcproxy.Conn({ 107 | "host": config.server.host, 108 | "version": config.server.version, 109 | "port": config.server.port, 110 | "username": config.account.username, 111 | "password": config.account.password, 112 | "auth": config.account.auth 113 | }); 114 | client = conn.bot._client; 115 | mineflayer.initialize(conn.bot); 116 | 117 | // Log connect and start Mineflayer 118 | client.on("connect", function () { 119 | logger.log("connected", "Client connected", "proxy"); 120 | startMineflayer(); 121 | // Create ngrok tunnel (unless waitForControllerBeforeConnect is true, in which case the tunnel already exists) 122 | if (config.ngrok.active && !config.waitForControllerBeforeConnect) { 123 | ngrok.createTunnel(); 124 | } 125 | }); 126 | 127 | // Log disconnect 128 | client.on("disconnect", function (packet) { 129 | logger.log("disconnected", packet.reason, "proxy"); 130 | notifier.sendWebhook({ 131 | title: "Disconnected from Server: " + packet.reason, 132 | category: "spam" 133 | }); 134 | if (JSON.parse(packet.reason).text === "You are already connected to this proxy!") { // Send notifications when the proxy is unable to log on because the account is already in use 135 | notifier.sendWebhook({ 136 | title: "Someone is already connected to the server using this proxy's account.", 137 | category: "spam" 138 | }); 139 | if (typeof server !== "undefined" && typeof server.clients[0] !== "undefined") { // Make sure client exists 140 | server.clients[0].end("Someone is already connected to the server using this proxy's account."); // Disconnect client from the proxy with a helpful message 141 | } 142 | } 143 | reconnect(); 144 | }); 145 | 146 | // Log kick 147 | client.on("kick_disconnect", function (packet) { 148 | logger.log("kick/disconnect", packet.reason, "proxy"); 149 | notifier.sendWebhook({ 150 | title: "Kicked from Server: " + packet.reason, 151 | category: "spam" 152 | }); 153 | reconnect(); 154 | }); 155 | 156 | // Packet handlers 157 | client.on("packet", (packetData, packetMeta) => { 158 | packetHandler(packetData, packetMeta); 159 | }); 160 | } 161 | 162 | // ============ 163 | // Local Server 164 | // ============ 165 | 166 | /** 167 | * Create the local server. 168 | * Handles players connecting & bridges packets between from players to the bridgeClient 169 | */ 170 | function createLocalServer() { 171 | console.log("Creating local server"); 172 | logger.log("proxy", "Creating local server.", "proxy"); 173 | // Create server 174 | server = mc.createServer({ 175 | "online-mode": config.proxy.onlineMode, 176 | "encryption": true, 177 | "host": config.proxy.loopbackAddress, 178 | "port": config.proxy.port, 179 | "version": config.server.version, 180 | "max-players": 1, 181 | "beforePing": (function (response, client, answerToPing) { 182 | if (!config.experimental.spoofPing.active) { // Don't proceed if noPing isn't enabled in config.json 183 | return; 184 | } 185 | if (config.experimental.spoofPing.noResponse) { // Don't return a response 186 | answerToPing(false); 187 | } else { // Or return a fake response 188 | answerToPing(null, config.experimental.spoofPing.fakeResponse); 189 | } 190 | }) 191 | }); 192 | // Handle logins 193 | server.on("login", (bridgeClient) => { 194 | // Block attempt if... 195 | if (config.proxy.whitelist.findIndex(needle => bridgeClient.username.toLowerCase() === needle.toLowerCase()) === -1) { // ... player isn't in whitelist 196 | bridgeClient.end("Your account (" + bridgeClient.username + ") is not whitelisted.\n\nIf you're getting this error in error the Microsoft account token may have expired."); 197 | logSpam(bridgeClient.username + " (" + bridgeClient.uuid + ")" + " was denied connection to the proxy for not being whitelisted."); 198 | return; 199 | } else if (server.playerCount > 1) { // ... and another player isn't already connected 200 | bridgeClient.end("This proxy is at max capacity.\n\nCurrent Controller: " + status.controller); 201 | logSpam(bridgeClient.username + " (" + bridgeClient.uuid + ")" + " was denied connection to the proxy despite being whitelisted because " + status.controller + " was already in control."); 202 | return; 203 | } 204 | 205 | // Finally, kick the player if the proxy is restarting 206 | if (status.restart === ("Reconnecting in " + config.reconnectInterval + " seconds...")) { 207 | if (config.ngrok.active) { 208 | bridgeClient.end("This proxy is currently restarting.\n\nPlease wait at least " + config.reconnectInterval + " seconds and try again using the new tunnel."); 209 | } else { 210 | bridgeClient.end("This proxy is currently restarting.\n\nPlease wait at least " + config.reconnectInterval + " seconds and try again."); 211 | } 212 | logSpam(bridgeClient.username + " (" + bridgeClient.uuid + ")" + " was denied connection to the proxy despite being whitelisted because the proxy was restarting."); 213 | return; 214 | } 215 | 216 | // Log successful connection attempt 217 | logSpam(bridgeClient.username + " (" + bridgeClient.uuid + ")" + " has connected to the proxy."); 218 | updateStatus("controller", bridgeClient.username); 219 | if (config.notify.whenControlling) { // optional: send message to status webhook 220 | notifier.sendWebhook({ 221 | title: bridgeClient.username + " is using the proxy.", 222 | category: "status", 223 | deleteOnRestart: true 224 | }); 225 | } 226 | 227 | // Create client if it hasn't been created yet (waitForControllerBeforeConnect) 228 | if (config.waitForControllerBeforeConnect && typeof conn === "undefined") { 229 | createClient(); 230 | client.on("packet", (packetData, packetMeta) => { 231 | if (packetMeta.name === "success") { 232 | createBridge(); 233 | } 234 | }); 235 | } else { 236 | createBridge(); 237 | } 238 | 239 | /** Create bridge between client and local server */ 240 | function createBridge() { 241 | console.log("Creating packet bridge"); 242 | logger.log("proxy", "Creating packet bridge.", "proxy"); 243 | // Start Mineflayer when disconnected 244 | bridgeClient.on("end", () => { 245 | // Log disconnect 246 | logSpam(bridgeClient.username + " (" + bridgeClient.uuid + ")" + " has disconnected from the local server."); 247 | updateStatus("controller", "None"); 248 | if (config.notify.whenControlling) { // optional: send message to status webhook 249 | notifier.sendWebhook({ 250 | title: bridgeClient.username + " is no longer using the proxy.", 251 | category: "status", 252 | deleteOnRestart: true 253 | }); 254 | } 255 | // Disconnect if no controller after config.experimental.disconnectIfNoController.delay seconds 256 | if (config.experimental.disconnectIfNoController.active && status.inQueue === "false") { 257 | setTimeout(function () { 258 | if (status.controller === "None") { 259 | logger.log("proxy", "Restarting proxy because noone was in control " + config.experimental.disconnectIfNoController.delay + " seconds after someone DCed from proxy while it was on the server.", "proxy"); 260 | reconnect(); 261 | } 262 | }, config.experimental.disconnectIfNoController.delay * 1000); 263 | } 264 | // Start Mineflayer 265 | startMineflayer(); 266 | }); 267 | 268 | // Stop Mineflayer 269 | stopMineflayer(); 270 | 271 | // Send fake packets 272 | setTimeout(function(){ 273 | // Spoof player_info (skin fix) 274 | if (config.experimental.spoofPlayerInfo.active) { 275 | bridgeClient.write("player_info", { // Add spoofed player to tablist 276 | action: 0, 277 | data: [{ 278 | UUID: bridgeClient.uuid, 279 | name: conn.bot.player.username, 280 | properties: [{ 281 | "name": "textures", // Remember to get skin info from https://sessionserver.mojang.com/session/minecraft/profile/?unsigned=false! 282 | "value": config.experimental.spoofPlayerInfo.texture.value, 283 | "signature": config.experimental.spoofPlayerInfo.texture.signature 284 | }] 285 | }] 286 | }); 287 | bridgeClient.write("player_info", { // Remove bot player from tablist 288 | action: 4, 289 | data: [{ 290 | UUID: conn.bot.player.uuid, 291 | name: conn.bot.player.username, 292 | properties: [] 293 | }] 294 | }); 295 | } 296 | // Attempt to fix game state desyncs by sending fake packets to the client 297 | if (config.experimental.syncGamestate.active) { 298 | if (typeof conn.bot.experience !== "undefined") { 299 | bridgeClient.write("experience", { 300 | "experienceBar": conn.bot.experience.progress, 301 | "totalExperience": conn.bot.experience.points, 302 | "level": conn.bot.experience.level 303 | }); 304 | } 305 | if (typeof conn.bot.vehicle !== "undefined") { 306 | conn.bot.dismount(); 307 | } 308 | for (const entityIndex in conn.bot.entities) { 309 | const ENTITY = conn.bot.entities[entityIndex]; 310 | if (ENTITY.kind !== "Vehicles") 311 | continue; 312 | bridgeClient.write("spawn_entity", { 313 | "entityId": ENTITY.id, 314 | "objectUUID": ENTITY.uuid, 315 | "type": ENTITY.entityType, 316 | "x": ENTITY.position.x, 317 | "y": ENTITY.position.y, 318 | "z": ENTITY.position.z, 319 | "pitch": 0, 320 | "yaw": ENTITY.yaw, 321 | "headPitch": ENTITY.pitch, 322 | "objectData": ENTITY.metadata, 323 | "velocityX": ENTITY.velocity.x, 324 | "velocityY": ENTITY.velocity.y, 325 | "velocityZ": ENTITY.velocity.z, 326 | }); 327 | } 328 | } 329 | }, 1000); 330 | 331 | // Log packets 332 | bridgeClient.on("packet", (packetData, packetMeta) => { 333 | logger.packetHandler(packetData, packetMeta, "bridgeClient"); 334 | }); 335 | 336 | // Bridge packets 337 | bridgeClient.on("packet", (data, meta, rawData) => { 338 | bridge(rawData, meta, client); 339 | }); 340 | conn.sendPackets(bridgeClient); 341 | conn.link(bridgeClient); 342 | } 343 | 344 | /** 345 | * Send message to logger and spam webhook 346 | * @param {string} logMsg Message to log 347 | */ 348 | function logSpam(logMsg) { 349 | logger.log("bridgeclient", logMsg, "proxy"); 350 | notifier.sendWebhook({ 351 | title: logMsg, 352 | category: "spam" 353 | }); 354 | } 355 | }); 356 | } 357 | 358 | // ========================== 359 | // Unclean Disconnect Monitor 360 | // ========================== 361 | 362 | let uncleanDisconnectMonitor; 363 | /** 364 | * If no packets are received for config.uncleanDisconnectInterval seconds, assume we were disconnected uncleanly and reconnect. 365 | */ 366 | function refreshMonitor() { 367 | if (!uncleanDisconnectMonitor) { // Create timer on the first packet 368 | uncleanDisconnectMonitor = setTimeout(function () { 369 | logger.log("proxy", "No packets were received for " + config.uncleanDisconnectInterval + " seconds. Assuming unclean disconnect.", "proxy"); 370 | reconnect(); 371 | }, config.uncleanDisconnectInterval * 1000); 372 | return; 373 | } 374 | uncleanDisconnectMonitor.refresh(); // Restart the timer 375 | } 376 | 377 | // ========= 378 | // Functions 379 | // ========= 380 | 381 | /** Reconnect (Remember to read https://github.com/Enchoseon/2based2wait/wiki/How-to-Auto-Reconnect-with-Supervisor or this will just cause the script to shut down!) */ 382 | function reconnect() { 383 | console.log("Reconnecting..."); 384 | logger.log("proxy", "Reconnecting...", "proxy"); 385 | if (typeof conn !== "undefined") { // Make sure connection exists 386 | conn.disconnect(); // Disconnect proxy from the server 387 | } 388 | if (typeof server !== "undefined" && typeof server.clients[0] !== "undefined") { // Make sure client exists 389 | server.clients[0].end("Proxy restarting..."); // Disconnect client from the proxy 390 | } 391 | notifier.sendWebhook({ 392 | title: "Reconnecting...", 393 | category: "spam" 394 | }); 395 | updateStatus("restart", "Reconnecting in " + config.reconnectInterval + " seconds..."); 396 | updateStatus("livechatRelay", "false"); 397 | notifier.deleteMarkedMessages(); 398 | setTimeout(function() { 399 | updateStatus("restart", "Reconnecting now!"); 400 | notifier.sendToast("Reconnecting now!"); 401 | process.exit(1); 402 | }, config.reconnectInterval * 1000); 403 | } 404 | 405 | /** Start Mineflayer */ 406 | function startMineflayer() { 407 | logger.log("mineflayer", "Starting Mineflayer.", "proxy"); 408 | updateStatus("mineflayer", true); 409 | if (config.mineflayer.active) { 410 | conn.bot.autoEat.enable(); 411 | conn.bot.afk.start(); 412 | } 413 | } 414 | 415 | /** Stop Mineflayer */ 416 | function stopMineflayer() { 417 | logger.log("mineflayer", "Stopping Mineflayer.", "proxy"); 418 | updateStatus("mineflayer", false); 419 | if (config.mineflayer.active) { 420 | conn.bot.autoEat.disable(); 421 | conn.bot.afk.stop(); 422 | } 423 | } 424 | 425 | /** 426 | * Send packets from Point A to Point B 427 | * @param {object} packetData Packet object data 428 | * @param {object} packetMeta Packet metadata 429 | * @param {object} dest McProxy client to write data to 430 | */ 431 | function bridge(packetData, packetMeta, dest) { 432 | if (packetMeta.name !== "keep_alive" && packetMeta.name !== "update_time") { // Keep-alive packets are filtered bc the client already handles them. Sending double would kick us. 433 | dest.writeRaw(packetData); 434 | } 435 | } 436 | -------------------------------------------------------------------------------- /scripts/debugFetch.js: -------------------------------------------------------------------------------- 1 | // ======= 2 | // Imports 3 | // ======= 4 | 5 | const fs = require("fs"); 6 | const path = require("path"); 7 | const os = require("os"); 8 | const crypto = require("crypto"); 9 | const { execSync } = require("child_process"); 10 | const JSON5 = require("json5"); 11 | 12 | // =============== 13 | // Get Information 14 | // =============== 15 | 16 | /** System Info */ 17 | const operatingSystem = `${os.type()}_${os.release()}_${os.arch()}`; // Operating system + CPU architecture 18 | const memory = os.totalmem(); // Total memory 19 | const nodeVersion = process.version; // Current node version 20 | 21 | /** 2Based2Wait Information */ 22 | const currentCommitHash = getCurrentCommitHash(); // Current git commit hash 23 | const packageJsonVersion = process.env.npm_package_version; // Current version in package.json 24 | const filesToHash = [ // Paths of files to hash 25 | "./proxy.js", 26 | "./package.json", 27 | "./package-lock.json", 28 | "./util/chatty.js", 29 | "./util/config.js", 30 | "./util/downloader.js", 31 | "./util/logger.js", 32 | "./util/mineflayer.js", 33 | "./util/ngrok.js", 34 | "./util/notifier.js", 35 | "./util/queue.js", 36 | "./util/schemas.js", 37 | "./scripts/debugFetch.js", 38 | "./scripts/processArchives.js", 39 | "./scripts/updateNgrokBinary.js", 40 | "./test/test-config.json", 41 | "./test/test.js" 42 | ]; 43 | const lastModified = getLastModifiedFile(filesToHash); 44 | 45 | /** Logs */ 46 | const logDirs = fs.existsSync("./log") ? getDirectories("./log") : []; // Array of all directories in ./log 47 | 48 | /** Tests*/ 49 | const isConfigValid = isValidJson5("./config.json"); // Whether ./config.json can be parsed 50 | const passesMocha = passesMochaTests(); // Whether Mocha tests pass on this machine 51 | const mochaInstalled = isMochaInstalled(); // Whether the optional Mocha dependency is even installed 52 | 53 | /** Errors */ 54 | const errors = !isConfigValid || (!passesMocha && mochaInstalled); 55 | 56 | // ================== 57 | // Output Information 58 | // ================== 59 | 60 | /** System Info */ 61 | console.log("\x1b[36m%s\x1b[33m", "=== System Info ==="); 62 | console.log(`OS: ${operatingSystem}`); 63 | console.log(`Memory: ${(memory / Math.pow(1024,3)).toString().slice(0,4)} GB (${memory} Bytes)`); 64 | console.log(`Node Version: ${nodeVersion}`); 65 | 66 | /** 2Based2Wait Information */ 67 | console.log("\x1b[36m%s\x1b[33m", "=== 2Based2Wait Info ==="); 68 | console.log(`Current Commit Hash: ${currentCommitHash}`|| "Couldn't find .git"); 69 | console.log(`Package.json Version: ${packageJsonVersion}`); 70 | console.log("File Hashes:"); 71 | filesToHash.forEach((path) => { 72 | console.log(`-${path}:${getFileHash(path)}` || "File wasn't found"); 73 | }); 74 | console.log(`Last Modified File: ${lastModified}`); 75 | 76 | /** Logs */ 77 | console.log("\x1b[36m%s\x1b[33m", "=== Log Folders ==="); 78 | logDirs.forEach((path) => { 79 | console.log(`- ${path.replace("log/", "")}(${fs.readdirSync(path).length} files/folders)`); 80 | }); 81 | 82 | /** Tests */ 83 | console.log("\x1b[36m%s\x1b[33m", "=== Tests ==="); 84 | console.log(`Config.json is Valid JSON5: ${isConfigValid.toString()}`); 85 | console.log(`Passes Mocha Tests: ${passesMocha.toString()}`); 86 | console.log(`Is Mocha Installed: ${mochaInstalled.toString()}`); 87 | if (!mochaInstalled) { 88 | console.log("\x1b[32m", " ^ You can install mocha by running `pnpm i`!"); 89 | } 90 | 91 | /** User-Friendly Debugging */ 92 | console.log(errors ? "\x1b[36m%s\x1b[32m\n=== Summary: Errors Found ===" : ""); 93 | console.log(!isConfigValid ? "- Your config.json file is not valid Json5, probably due to syntax errors. Use a text editor or IDE with syntax highlighting to fix these mistakes faster." : ""); 94 | console.log((!passesMocha && mochaInstalled) ? "- The Mocha tests failed on your machine. If you didn't do anything to cause this (e.g. delete the test files, set up a super-strict firewall/container, etc.), then it probably means that there is a fatal bug that warrants investigation." : ""); 95 | 96 | 97 | // ========= 98 | // Functions 99 | // ========= 100 | 101 | /** 102 | * Returns the current git commit hash (https://stackoverflow.com/a/34518749) 103 | * @returns {string} Current git commit hash. Returns false if ./git/ can't be found 104 | */ 105 | function getCurrentCommitHash() { 106 | if (!fs.existsSync(".git/")) return false; 107 | 108 | let hash = fs.readFileSync(".git/HEAD").toString().trim(); 109 | return hash.indexOf(":") === -1 ? hash.slice(0, 7) : fs.readFileSync(".git/" + hash.substring(5)).toString().trim().slice(0, 7); 110 | } 111 | 112 | /** 113 | * Returns the git hash of the file at the path, returns false if the file wasn't found 114 | * @param {string} path Path to a file 115 | * @returns {string} Short 6-character hash 116 | */ 117 | function getFileHash(path) { 118 | if (!fs.existsSync(path)) return false; // Return false if file wasn't found 119 | const hashSum = crypto.createHash("sha1"); 120 | hashSum.update(`blob ${fs.statSync(path).size + "\0" + fs.readFileSync(path)}`); 121 | return hashSum.digest("hex").slice(0, 7); 122 | } 123 | 124 | /** 125 | * Returns whether the file at path is valid Json5 126 | * @param {string} path Path to a file 127 | * @returns {boolean} Whether or not the file is valid Json5 128 | */ 129 | function isValidJson5(path) { 130 | try { 131 | JSON5.parse(fs.readFileSync(path)); 132 | } catch (error) { // JSON5 Parsing Error 133 | return false; 134 | } 135 | return true; 136 | } 137 | 138 | /** 139 | * Returns whether Mocha test is successful 140 | * @returns {boolean} Whether `pnpm run test` is successful 141 | */ 142 | function passesMochaTests() { 143 | try { 144 | execSync("CI=true pnpm run test"); 145 | } catch (error) { // The test fails 146 | return false; 147 | } 148 | return true; 149 | } 150 | 151 | /** 152 | * Returns whether optional Mocha dependency is installed 153 | * @returns {boolean} Whether Mocha is installed 154 | */ 155 | function isMochaInstalled() { 156 | try { 157 | require("mocha"); 158 | } catch (error) { // The test fails 159 | return false; 160 | } 161 | return true; 162 | } 163 | 164 | /** 165 | * Returns (path of) most-recently modified file 166 | * @param {Array} paths Array of filepaths 167 | * @returns {string} Path of the last modified file 168 | */ 169 | function getLastModifiedFile(paths) { 170 | let newest = { 171 | "path": paths[0], 172 | "time": 0 173 | }; 174 | paths.forEach((path) => { 175 | if (fs.existsSync(path)) { 176 | const time = fs.statSync(path).mtimeMs; 177 | if (time > newest.time) { 178 | newest.path = path; 179 | newest.time = time; 180 | } 181 | } 182 | }); 183 | return newest.path; 184 | } 185 | 186 | /** 187 | * Returns array of directories in a given srcpath 188 | * @param {string} srcpath Path to a directory 189 | * @returns {Array} Array of directories 190 | * {@link https://stackoverflow.com/a/40896897} 191 | */ 192 | function getDirectories(srcpath) { 193 | return fs.readdirSync(srcpath) 194 | .map(file => path.join(srcpath, file)) 195 | .filter(path => fs.statSync(path).isDirectory()); 196 | } -------------------------------------------------------------------------------- /scripts/findCommitBySubstringInFileHash.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Description: Prints all instances of a file with a git hash containing the given argument. Results are formatted as "commit (subject): hash (name)". 3 | # Why: Using the short hashes outputted by `pnpm run debug-info`, this utility can be used to figure out what commit someone is using if they don't have a .git folder based on their last modified file (which is also outputted by `pnpm run debug-info`) 4 | find="$1" 5 | echo "Searching for file with git hash containing \"$find\":" 6 | shift 7 | git log "$@" --pretty=format:'%T %h %s' \ 8 | | while read tree commit subject ; do 9 | git ls-tree -r "$commit" | while read _ _ sha name; do \ 10 | if [[ "$sha" == *"$find"* ]]; then 11 | echo "$commit ($subject): $sha ($name)" 12 | fi 13 | done 14 | done 15 | -------------------------------------------------------------------------------- /scripts/processArchives.js: -------------------------------------------------------------------------------- 1 | // ======= 2 | // Imports 3 | // ======= 4 | 5 | const fs = require("fs"); 6 | const zlib = require("zlib"); 7 | const { Transform } = require("stream"); 8 | 9 | const nbt = require("prismarine-nbt"); 10 | 11 | const { config } = require("./../util/config.js"); 12 | const downloader = require("./../util/downloader.js"); 13 | 14 | const Anvil = new (require("prismarine-provider-anvil").Anvil(config.server.version))(downloader.createOutputDir("default")); 15 | const Chunk = require("prismarine-chunk")(config.server.version); 16 | 17 | // ======= 18 | // Classes 19 | // ======= 20 | 21 | /** Stream from a file in chunks (https://stackoverflow.com/a/44931499) */ 22 | class Delimited extends Transform { 23 | constructor(delimiter) { 24 | super({ objectMode: true }); 25 | this._delimiter = delimiter instanceof RegExp ? delimiter : new RegExp(delimiter, "g"); 26 | this._encoding = "utf8"; 27 | this._buffer = ""; 28 | this._first = true; 29 | } 30 | _transform(chunk, encoding, callback) { 31 | if (encoding === "buffer") { 32 | this._buffer += chunk.toString(this._encoding); 33 | } else if (encoding === this._encoding) { 34 | this._buffer += chunk; 35 | } else { 36 | this._buffer += Buffer.from(chunk, encoding).toString(this._encoding); 37 | } 38 | if (this._delimiter.test(this._buffer)) { 39 | let sections = this._buffer.split(this._delimiter); 40 | this._buffer = sections.pop(); 41 | sections.forEach(this.push, this); 42 | } 43 | callback(); 44 | } 45 | } 46 | 47 | // ========= 48 | // Functions 49 | // ========= 50 | 51 | /** 52 | * Convert a packet.gz archive into .mca files 53 | * @param {string} inputDirectory Path to directory to search for *.packet.gz archives 54 | * @param {number} stoppingDate Timestamp to stop converting packets (will process all *packet.gz archives otherwise) 55 | */ 56 | async function packetsToAnvil(inputDirectory, stoppingDate) { 57 | // Get all files in the directory to convert 58 | let files = fs.readdirSync(inputDirectory, { 59 | "withFileTypes": true 60 | }).filter(f => f.name.endsWith(".packets.gz")); // (Check the extension to make sure) 61 | // Process each file in sequential order 62 | while (files.length > 0) { 63 | console.log(`\tNow Reading: ${files[0].name}`); 64 | await streamFile(`${inputDirectory}/${files[0].name}`); 65 | files.shift(); 66 | } 67 | /** 68 | * Stream an archive file 69 | * @param {string} inputFile Path to *packet.gz archive to stream 70 | */ 71 | async function streamFile(inputFile) { 72 | const fileStream = fs.createReadStream(inputFile); 73 | const transform = new Delimited(/\u0D9E/gu); // https://youtu.be/UNhphyF74sA 74 | transform.on("data", (chunk) => handleChunk(chunk)); 75 | transform.on("end", () => { 76 | console.log("Complete."); 77 | process.kill(0); 78 | }); 79 | fileStream.pipe(zlib.createGunzip()).pipe(transform); 80 | } 81 | /** 82 | * Handle a chunk of data from streamFile 83 | * @param {string} line Stringified object containing chunk info (timestamp, x, z, groundUp, bitMap, chunkData, and blockEntities) 84 | * @returns {null} When chunk is finished processing 85 | */ 86 | async function handleChunk(line) { 87 | const parsed = JSON.parse(line); 88 | const packetData = { 89 | "timestamp": new Date(parsed[0] * 1000), 90 | "x": parsed[1], 91 | "z": parsed[2], 92 | "groundUp": Boolean(parsed[3]), 93 | "bitMap": parsed[4], 94 | "chunkData": Buffer.from(parsed[5]), 95 | "blockEntities": parsed[6] 96 | }; 97 | // Check if we've passed the stopping date 98 | if (stoppingDate > 0 && stoppingDate < packetData.timestamp.getTime()) { 99 | console.log(`\tReached Stopping Date On Packet Dated: ${packetData.timestamp.toUTCString()}`); 100 | console.log("Complete."); 101 | process.kill(0); 102 | return; 103 | } 104 | // Convert to prismarine-chunk format 105 | console.log(`\t\tConverting Chunk: ${packetData.x}${packetData.z}\n\t\t\tRecorded At: ${packetData.timestamp.toUTCString()}`); 106 | const chunk = new Chunk(); 107 | chunk.load(packetData.chunkData, packetData.bitMap, { // Load chunks 108 | "minecraft:end": 1, 109 | "minecraft:overworld": 0, 110 | "minecraft:nether": -1 111 | }, packetData.groundUp); 112 | if (packetData.blockEntities.length > 0) { // Load block entities 113 | const simplified = packetData.blockEntities.map((entity) => nbt.simplify(entity)); 114 | await chunk.loadBlockEntities(simplified); 115 | } 116 | // Save to anvil 117 | await Anvil.save(packetData.x, packetData.z, chunk); 118 | } 119 | } 120 | 121 | /** Lazy Package.json hook */ 122 | if (process.argv.length >= 3) { 123 | // Get arguments 124 | const inputDirectory = process.argv[2]; 125 | const stoppingDate = Date.parse(process.argv[3]); 126 | console.log(`Processing Archive: ${inputDirectory} Stopping Date: ${stoppingDate}` || "None"); 127 | if (fs.existsSync(inputDirectory)) { 128 | packetsToAnvil(inputDirectory, stoppingDate); 129 | } else { 130 | throw new Error(`${inputDirectory} could not be found. Are you sure you have the correct path?`); 131 | } 132 | } 133 | 134 | // ======= 135 | // Exports 136 | // ======= 137 | 138 | module.exports = { 139 | packetsToAnvil 140 | }; 141 | -------------------------------------------------------------------------------- /scripts/updateNgrokBinary.js: -------------------------------------------------------------------------------- 1 | // ======= 2 | // Imports 3 | // ======= 4 | 5 | const os = require("os"); 6 | const downloadNgrok = require("ngrok/download"); 7 | 8 | // ============= 9 | // Update Binary 10 | // ============= 11 | 12 | const arch = os.platform() + os.arch(); 13 | console.log(`Downloading latest Ngrok binary for your architecture ("${arch}")...`); 14 | downloadNgrok(callback, { ignoreCache: true }); 15 | 16 | // ========= 17 | // Functions 18 | // ========= 19 | 20 | /** 21 | * Prints error message to console, or notifies the user that they successfully updated their Ngrok binary/ 22 | * @param {string} msg Error message from downloadNgrok. Empty if there were no errors; 23 | */ 24 | function callback(msg) { 25 | msg ? console.error(msg) : console.log("Successfully updated Ngrok binary!"); 26 | } -------------------------------------------------------------------------------- /test/test-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "account": { 3 | "username": "UnitTester", 4 | "auth": "offline" 5 | }, 6 | "proxy": { 7 | "whitelist": [ "UnitTester" ], 8 | "port": 25566 9 | }, 10 | "server": { 11 | "host": "127.0.0.1", 12 | "port": 25565 13 | }, 14 | "noCliGui": true, 15 | "coordination": { 16 | "active": false 17 | } 18 | } -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // ======= 4 | // Imports 5 | // ======= 6 | 7 | const assert = require("assert"); 8 | const mc = require("minecraft-protocol"); 9 | 10 | // =========== 11 | // Global Vars 12 | // =========== 13 | 14 | let server; 15 | 16 | // ===== 17 | // Mocha 18 | // ===== 19 | 20 | describe("2Bored2Wait Testing", () => { 21 | before(() => { 22 | console.log("Creating testing server."); 23 | server = mc.createServer({ 24 | "online-mode": false, 25 | encryption: true, 26 | version: "1.12.2" 27 | }); 28 | }); 29 | describe("config.js", () => { 30 | it("Can generate a valid config.json from bare-minimum information", () => { 31 | const { configSchema } = require("./../util/config.js"); 32 | const generated = configSchema.validate({ 33 | account: { 34 | username: "UnitTester" 35 | } 36 | }); 37 | console.log("Generated Config:"); 38 | console.dir(generated, { depth: null }); 39 | assert.equal(typeof generated.error, "undefined"); // Check that schema validation returned no errors 40 | }); 41 | }); 42 | describe("proxy.js", () => { 43 | it("Can perform login sequence", async function () { 44 | this.timeout(13000); 45 | require("./../proxy.js"); // Start proxy 46 | const username = await waitForLogin(); 47 | console.log(`Received Login: ${username}`); 48 | assert.equal(username, "UnitTester"); 49 | }); 50 | }); 51 | }); 52 | 53 | // ============== 54 | // Packet Testing 55 | // ============== 56 | 57 | /** 58 | * Send packets that'll test a couple aspects of the proxy. This is not version agnostic is based on packets I got connecting through 1.19.4 59 | * @returns {Promise} Promise object representing success of server login code 60 | */ 61 | function waitForLogin() { 62 | return new Promise((resolve) => { 63 | server.on("login", function (client) { // This is not an accurate login sequence. 64 | // =================== 65 | // 2B2T Login Sequence 66 | // =================== 67 | client.write("chat", { 68 | "message": JSON.stringify({ 69 | "color": "gold", 70 | "text": "Queued for server main." 71 | }), 72 | "position": 0 73 | }); 74 | client.write("login", { 75 | "entityId": client.id, 76 | "gameMode": 3, 77 | "dimension": 1, 78 | "difficulty": 0, 79 | "maxPlayers": 48, 80 | "levelType": "default", 81 | "reducedDebugInfo": false 82 | }); 83 | // client.write("custom_payload", { 84 | // "channel": "minecraft:brand", 85 | // "data": { 86 | // "type": "Buffer", 87 | // "data": [15,50,98,50,116,32,40,86,101,108,111,99,105,116,121,41] 88 | // } 89 | // }); 90 | client.write("difficulty", { 91 | "difficulty": 1, 92 | "difficultyLocked": false 93 | }); 94 | // ============ 95 | // Chat Message 96 | // ============ 97 | client.write("chat", { 98 | message: JSON.stringify({ 99 | "text": " Chat message." 100 | }), 101 | position: 0 102 | }); 103 | // =================== 104 | // 2B2T Server Restart 105 | // =================== 106 | client.write("chat", { 107 | message: JSON.stringify({ 108 | "text": "[SERVER] Server restarting in 7 seconds..." 109 | }), 110 | position: 0 111 | }); 112 | resolve(client.username); 113 | }); 114 | }); 115 | } 116 | -------------------------------------------------------------------------------- /util/chatty.js: -------------------------------------------------------------------------------- 1 | // ======= 2 | // Imports 3 | // ======= 4 | const fs = require("fs"); 5 | 6 | const { config, status, updateStatus } = require("./config.js"); 7 | const logger = require("./logger.js"); 8 | const notifier = require("./notifier.js"); 9 | 10 | const ChatMessage = require("prismarine-chat")(config.server.version); 11 | 12 | // ========= 13 | // Functions 14 | // ========= 15 | 16 | /** 17 | * Handle incoming chat packets 18 | * @param {string} packetName Packet type 19 | * @param {object} packetData Packet data object 20 | */ 21 | function chatPacketHandler(packetName, packetData) { 22 | if (packetName != "chat") 23 | return; 24 | // Parse chat messages 25 | const msgObj = JSON.parse(packetData.message); 26 | const msg = ChatMessage.fromNotch(msgObj).toString(); 27 | 28 | // Notify about server restarts (haven't been tested in a long time due to restarts being less common) 29 | if (msg && msg.startsWith("[SERVER] Server restarting in ")) { 30 | let restart = msg.replace("[SERVER] Server restarting in ", "").replace(" ...", ""); 31 | if (updateStatus("restart", restart)) { 32 | notifier.sendToast(`Server Restart In: ${status.restart}`); 33 | notifier.sendWebhook({ 34 | title: `Server Restart In: ${status.restart}`, 35 | ping: true, 36 | category: "spam" 37 | }); 38 | } 39 | } 40 | 41 | // Livechat webhook relay, if not in queue. 42 | if (status.inQueue === "false") { 43 | // If coordination is active... 44 | if (config.coordination.active) { 45 | // If no proxy in the pool is the designated livechat relayer, make this one the one 46 | const flagPath = config.coordination.path + "coordinator.flag"; 47 | if (status.livechatRelay === "false" && !fs.existsSync(flagPath)) { 48 | updateStatus("livechatRelay", "true"); 49 | } 50 | // Relay livechat if this proxy is the designated livechat relayer 51 | if (status.livechatRelay === "true") { 52 | updateLivechatWebhook(msg); 53 | } 54 | } else { 55 | updateLivechatWebhook(msg); 56 | } 57 | } else { 58 | updateStatus("livechatRelay", "false"); 59 | } 60 | 61 | // Log message 62 | logger.log("chat", msg, "chat"); 63 | } 64 | 65 | /** 66 | * Update livechat webhook 67 | * @param {string} msg Chat message to relay to webhook 68 | */ 69 | function updateLivechatWebhook(msg) { 70 | if (msg.trim().length > 0) { 71 | notifier.sendWebhook({ 72 | "description": escapeMarkdown(msg), 73 | "category": "livechat", 74 | "disableAttribution": true 75 | }); 76 | } 77 | } 78 | 79 | /** 80 | * Escape Discord markdown (and emojis) 81 | * @param {string} text Unescaped string 82 | * @returns {string} Escaped string 83 | * {@link https://stackoverflow.com/a/39543625} 84 | */ 85 | function escapeMarkdown(text) { 86 | const unescaped = text.replace(/\\(\*|_|:|`|~|\\)/g, "$1"); // Unescape backslashed characters 87 | const escaped = unescaped.replace(/(\*|_|:|`|~|\\)/g, "\\$1"); // Escape *, _, :, `, ~, \ 88 | return escaped; 89 | } 90 | 91 | // ======= 92 | // Exports 93 | // ======= 94 | 95 | module.exports = { 96 | chatPacketHandler 97 | }; 98 | -------------------------------------------------------------------------------- /util/config.js: -------------------------------------------------------------------------------- 1 | // ======= 2 | // Imports 3 | // ======= 4 | 5 | const fs = require("fs"); 6 | 7 | const merge = require("deepmerge"); 8 | const JSON5 = require("json5"); 9 | 10 | const { configSchema } = require("./schemas.js"); 11 | 12 | // =========== 13 | // Global Vars 14 | // =========== 15 | 16 | const config = processConfig(); // Stores parsed and validated user configuration 17 | let status = { // Stores pertinent information (to-do: set up setters and getters) 18 | "position": "CHECKING...", 19 | "eta": "CHECKING...", 20 | "restart": "None", 21 | "mineflayer": "CHECKING...", 22 | "inQueue": "true", 23 | "ngrokUrl": "None", 24 | "livechatRelay": "false", 25 | "controller": "None" 26 | }; 27 | 28 | // ====================== 29 | // Generate Documentation 30 | // ====================== 31 | 32 | if (process.argv.indexOf("--documentation") !== -1) { 33 | console.clear(); 34 | const doc = joiToMarkdown(configSchema, true); // Generate documentation with anchor links 35 | const dir = "./docs/"; 36 | if (!fs.existsSync(dir)) { // Create directory if it doesn't exist 37 | fs.mkdirSync(dir, { 38 | "recursive": true 39 | }); 40 | } 41 | fs.writeFileSync(dir + "configuration-guide.md", doc); // Write documentation to markdown file 42 | // Output documentation without any anchor links for the GitHub Wiki, which annoyingly doesn't support anchor links 43 | console.log("### See [this page](https://github.com/Enchoseon/2based2wait/blob/main/docs/configuration-guide.md) for a better version of this guide with anchor links\n---\n" + joiToMarkdown(configSchema, false)); 44 | process.exit(); 45 | } 46 | 47 | 48 | // ========= 49 | // Functions 50 | // ========= 51 | 52 | /** 53 | * Process and validate the config.json 54 | * @returns {config} Validated config object 55 | */ 56 | function processConfig() { 57 | let config; 58 | try { // Read config.json 59 | config = JSON5.parse(fs.readFileSync(`${process.env.CI ? "./test/test-" : "" }config.json`)); 60 | } catch (error) { // JSON5 Parsing Error 61 | console.error(error); 62 | throw new Error(`Couldn't read config.json, likely due to user error near or at line ${error.lineNumber} column ${error.columnNumber}`); // Kill the process here 63 | } 64 | // If coordination is active... 65 | if (config.coordination.active) { 66 | // ... create coordination path folder(s) if it doesn't exist 67 | const dir = config.coordination.path; 68 | if (!fs.existsSync(dir)) { 69 | fs.mkdirSync(dir, { 70 | recursive: true 71 | }); 72 | } 73 | // ... and apply master-config.json overrides if provided 74 | const masterConfigPath = config.coordination.path + "master-config.json"; 75 | if (fs.existsSync(masterConfigPath)) { 76 | const masterConfig = JSON5.parse(fs.readFileSync(masterConfigPath)); 77 | config = merge(masterConfig, config); 78 | } 79 | } 80 | // Validate the config with Joi 81 | const validationResult = configSchema.validate(config, { // Validate schema 82 | "abortEarly": false, // (find all errors) 83 | "allowUnknown": true // (allow undefined values (we'll set defaults where we can)) 84 | }); 85 | const validationErrors = validationResult.error; 86 | if (validationErrors) { // If error found, print error to console and kill process... 87 | if (validationErrors.details.length == 1) { 88 | console.log("\x1b[36m", "Stopped proxy, encountered an error in config.json (you must fix it): \n"); 89 | } else { 90 | console.log("\x1b[36m", `Stopped proxy, encountered ${validationErrors.details.length} errors in config.json (you must fix them):\n`); 91 | } 92 | for (let i = 0; i < validationErrors.details.length; i++) { // Print helpful color-coded errors to console 93 | const error = validationErrors.details[i]; 94 | console.log("\x1b[33m", `ERROR #${i}: ${error.message}`); 95 | console.log("\x1b[32m", `- Invalid Value: ${error.context.value}`); 96 | console.log("\x1b[32m", `- Should Be Type: ${error.type}`); 97 | if (i !== validationErrors.details.length) { 98 | console.log("\x1b[36m", ""); 99 | } 100 | } 101 | throw new Error("Couldn't validate config.json"); // Kill the process here 102 | } 103 | // ... If no errors were found, return the validated config 104 | return validationResult.value; 105 | } 106 | 107 | /** 108 | * Update status object. Returns whether the input being received is different from what's already stored in the object. 109 | * @param {string} type The name of the key being updated 110 | * @param {string} input The new value of status[type] 111 | * @returns {boolean} Whether the value was updated (in other words, returns false if the value is the same as the input value) 112 | */ 113 | function updateStatus(type, input) { 114 | if (status[type] !== input.toString()) { 115 | status[type] = input.toString(); 116 | if (config.coordination.active && type === "livechatRelay") { // Update coordinator status if livechatRelay changes 117 | updateCoordinatorStatus(); 118 | } 119 | if (!config.noCliGui) updateGui(); 120 | return true; 121 | } 122 | return false; 123 | } 124 | 125 | /** 126 | * Update proxy coordinator status 127 | */ 128 | function updateCoordinatorStatus() { 129 | // Add or remove the flag 130 | const flagPath = config.coordination.path + "coordinator.flag"; 131 | if (status.livechatRelay === "true") { 132 | fs.writeFile(flagPath, config.account.username, (error) => { 133 | if (error) { 134 | // logger.log("updateCoordinatorStatus", error, "error"); 135 | } 136 | }); 137 | } else { 138 | // Check if the flag is assigned to this proxy 139 | if (fs.existsSync(flagPath) && fs.readFileSync(flagPath).toString() === config.account.username) { 140 | fs.unlinkSync(flagPath); 141 | } 142 | } 143 | } 144 | 145 | /** 146 | * Display a basic CLI GUI 147 | */ 148 | function updateGui() { 149 | // Cli GUI 150 | console.clear(); 151 | console.log("\x1b[36m", ` 152 | 88888 88888 153 | 8 88888 88888 88888 8888 88888 8 e e e 88888 8 88888 154 | 8 8 8 8 8 8 8 88 8 8 8 8 8 8 8 8 8 155 | 88888 888888 88888 88888 8888 8 8 88888 8 8 8 88888 8 8 156 | 8 8 8 8 8 8 8 88 8 8 8 8 8 8 8 8 8 157 | 88888 888888 8 8 88888 8888 88888 88888 8888888 8 8 8 8 158 | `); 159 | console.log("\n"); 160 | console.log("\x1b[37m", `Last Update: [${getTimestamp()}]`); 161 | console.log("\x1b[37m", `Account: ${config.account.username}`); 162 | console.log("\x1b[37m", `Current Controller: ${status.controller}`); 163 | console.log("\x1b[33m", `Current Queue Position: ${status.position}`); 164 | console.log("\x1b[33m", `ETA: ${status.eta}`); 165 | console.log("\x1b[33m", `Restart: ${status.restart}`); 166 | console.log("\x1b[33m", `In Queue Server: ${status.inQueue.toUpperCase()}`); 167 | console.log(config.mineflayer.active ? `\x1b[35mMineflayer Running: ${status.mineflayer.toUpperCase()}` : ""); 168 | console.log(config.coordination.active ? `\x1b[32mLivechat Relay: ${status.livechatRelay.toUpperCase()}` : ""); 169 | console.log(config.ngrok.active ? `\x1b[32mNgrok URL: ${status.ngrokUrl}` : ""); 170 | } 171 | 172 | /** 173 | * Get current timestamp 174 | * @returns {string} Human-readable timestamp 175 | */ 176 | function getTimestamp() { 177 | let timestamp = new Date(); 178 | timestamp = timestamp.toLocaleString(); 179 | return timestamp.replace(/\//g, "-") // Replace forward-slash with hyphen 180 | .replace(",", ""); // Remove comma 181 | } 182 | 183 | /* TODO: Move this into a separate .js file, hopefully also make a GitHub 184 | workflow with it to update docs on a new push */ 185 | 186 | // =============== 187 | // Joi To Markdown 188 | // =============== 189 | 190 | /** 191 | * Generate markdown documentation from Joi schema. 192 | * @param {object} schema JOI schema to generate the documentation from 193 | * @param {boolean} includeAnchors Whether to include page anchors (page anchors don't work in GitHub Wikis) 194 | * @returns {string} Documentation from the schema in markdown 195 | */ 196 | function joiToMarkdown(schema, includeAnchors) { 197 | let output = ""; 198 | // Convert to JSON 199 | schema = schema.describe(); 200 | // Get value from path (https://stackoverflow.com/a/70356013) 201 | const get = (record, path) => path.reduce((record, item) => record[item], record); 202 | // Traverse configSchema 203 | for (let [key, path] of traverse(schema.keys)) { 204 | const level = path.length; 205 | const flags = get(schema.keys, path).flags; 206 | if (flags && key !== "empty" && key !== "0") { // Don't proceed if the object doesn't have any flags or is empty 207 | const info = { // Important information about the entry 208 | "type": get(schema.keys, path).type, 209 | "default": flags.default, 210 | "description": flags.description 211 | }; 212 | output += level !== 1 ? " ".repeat(level - 1) + "- " : "\n"; // Add a newlines in-between top-level entries to stop GitHub's markdown interpreter from merging everything into one giant list 213 | if (includeAnchors) { 214 | const anchor = path.join("-").replace(/-keys-/g, "-").toLowerCase(); // Create a unique and URL-friendly anchor for the entry 215 | output += ``; // Add the anchor to an invisible pair of tags 216 | output += `**[${key}](#user-content-${anchor})**`; // Output the entry's name 217 | } else { 218 | output += `**${key}**`; // Output the entry's name 219 | } 220 | output += ` \`{type: ${info.type}}\``; // Output the entry's type 221 | if (typeof info.default !== "undefined" && info.default.special !== "deep") { // If provided, output the entry's default value(s) 222 | output += ` \`{default: ${JSON.stringify(info.default)}}\``; 223 | } 224 | if (typeof info.description !== "undefined") { // If provided, output the entry's description 225 | output += ` : ${info.description}`; 226 | } 227 | output += "\n"; 228 | } 229 | } 230 | return output; 231 | /** 232 | * Traverse through an object 233 | * @param {object} o Object to traverse through 234 | * @param {Array} path Path to current position in traversal 235 | * @yields {Array} key, value, path, and parent 236 | * {@link https://stackoverflow.com/a/45628445} 237 | */ 238 | function* traverse(o, path = []) { 239 | for (let i in o) { 240 | const itemPath = path.concat(i); 241 | yield [i, itemPath]; 242 | if (o[i] !== null && typeof (o[i]) == "object") { 243 | yield* traverse(o[i], itemPath); 244 | } 245 | } 246 | } 247 | } 248 | 249 | // ======= 250 | // Exports 251 | // ======= 252 | 253 | module.exports = { 254 | config, 255 | status, 256 | updateStatus, 257 | updateCoordinatorStatus, 258 | configSchema 259 | }; -------------------------------------------------------------------------------- /util/downloader.js: -------------------------------------------------------------------------------- 1 | // ======= 2 | // Imports 3 | // ======= 4 | 5 | const fs = require("fs"); 6 | const zlib = require("zlib"); 7 | 8 | const { config } = require("./config.js"); 9 | const logger = require("./logger.js"); 10 | 11 | // ========= 12 | // Functions 13 | // ========= 14 | 15 | /** 16 | * Extremely unstable world downloader 17 | * @param {string} packetName Packet type 18 | * @param {object} packetData Packetdata from map_chunk 19 | */ 20 | function mapChunkPacketHandler(packetName, packetData) { 21 | if (packetName != "map_chunk") 22 | return; 23 | const serialized = JSON.stringify([ // Serialize the data we want to save 24 | Math.floor(Date.now() / 1000), 25 | packetData.x, 26 | packetData.z, 27 | packetData.groundUp ? 1 : 0, 28 | packetData.bitMap, 29 | packetData.chunkData, 30 | packetData.blockEntities, 31 | ]); 32 | const packetFile = createOutputDir("default") + logger.getTimestamp(true) + ".packets.gz"; // Save to log 33 | let stream = fs.createWriteStream(packetFile, { flags: "a" }); 34 | stream.write(zlib.gzipSync(serialized + "\u{0D9E}", { // Gzip 35 | "level": config.experimental.worldDownloader.compression.level, 36 | "memLevel": config.experimental.worldDownloader.compression.memLevel, 37 | "windowBits": config.experimental.worldDownloader.compression.windowBits, 38 | "strategy": 1 39 | })); 40 | // stream.write(serialized + "\u{0D9E}"); 41 | stream.end(); 42 | } 43 | 44 | /** 45 | * Create output folder if it doesn't exist 46 | * @param {string} worldName Name of the world, used for naming the directory 47 | * @returns {string} Path to the created folder 48 | */ 49 | function createOutputDir(worldName) { 50 | const outputDir = `./log/worldDownloader/${config.server.host}/${worldName.replace(/:/g, "_")}/`; 51 | if (!fs.existsSync(outputDir)) { 52 | fs.mkdirSync(outputDir, { 53 | "recursive": true 54 | }); 55 | } 56 | return outputDir; 57 | } 58 | 59 | // ======= 60 | // Exports 61 | // ======= 62 | 63 | module.exports = { 64 | mapChunkPacketHandler, 65 | createOutputDir 66 | }; 67 | -------------------------------------------------------------------------------- /util/logger.js: -------------------------------------------------------------------------------- 1 | // ======= 2 | // Imports 3 | // ======= 4 | 5 | const fs = require("fs"); 6 | const zlib = require("zlib"); 7 | 8 | const { config } = require("./config.js"); 9 | 10 | // =========== 11 | // Global Vars 12 | // =========== 13 | 14 | let logFiles = {}; 15 | 16 | // =================== 17 | // Initialize Logfiles 18 | // =================== 19 | 20 | // Create the directories and plan which logfiles to write to 21 | for (const category in config.log.active) { 22 | const dir = createDirectory(category); // Create directory for category if it doesn't exist 23 | const files = fs.readdirSync(dir, { // Get all files in the directory with the correct extension (either .log.gz or .log) 24 | "withFileTypes": true 25 | }).filter(f => { 26 | return f.name.endsWith(`.log${config.log.compression.active ? ".gz" : ""}`); 27 | }); 28 | let file = findExisting(dir, category, files); // Pick a filename (either a valid existing one or a new one) 29 | if (!file || config.log.alwaysIncrement) file = createFilename(category, files.length + 1); 30 | logFiles[category] = dir + file; // Push file path to logFiles 31 | } 32 | 33 | // ========= 34 | // Functions 35 | // ========= 36 | 37 | /** 38 | * Filter & log incoming packets from the server or bridgeClient 39 | * @param {object} packetData Packet data 40 | * @param {object} packetMeta Packet metadeta 41 | * @param {string} category Log category 42 | */ 43 | function packetHandler(packetData, packetMeta, category) { 44 | if (config.log.packetFilters[category].indexOf(packetMeta.name) === -1) { // Don't log filtered packets 45 | log(packetMeta.name, packetData, category + "Packets"); 46 | } 47 | } 48 | 49 | /** 50 | * Write to a log file. 51 | * @param {string} name The name of the entry 52 | * @param {object} data The data to log 53 | * @param {string} category The category to log this under 54 | */ 55 | function log(name, data, category) { 56 | // Don't proceed if logging category is disabled in config.json 57 | if (!config.log.active[category]) return; 58 | // Create file name 59 | createDirectory(category); 60 | let logFile = logFiles[category]; 61 | // Create log message 62 | const logMessage = `[${getTimestamp()}] [${name}] ${JSON.stringify(data)}\n`; 63 | // Write to log (either gzipped or raw) 64 | let stream = fs.createWriteStream(logFile, { 65 | flags: "a" 66 | }); 67 | if (config.log.compression.active) { 68 | stream.write(zlib.gzipSync(logMessage, { // Save Gzipped 69 | "level": config.log.compression.level, 70 | "memLevel": config.log.compression.memLevel, 71 | "windowBits": config.log.compression.windowBits, 72 | "strategy": 1, 73 | })); 74 | } else { 75 | stream.write(logMessage); // Save raw 76 | } 77 | stream.end(); 78 | } 79 | 80 | /** 81 | * Create a directory if it doesn't exist (in "./log/${category}/"). Also makes sure that logs from mocha tests don't contaminate normal logs. 82 | * @param {string} category The category to write logs to 83 | * @returns {string} Path to the created directory 84 | */ 85 | function createDirectory(category) { 86 | // Choose the directory 87 | const dir = `./log/${process.env.CI ? "test/" : ""}${category}/`; 88 | // Create directory if it doesn't exist 89 | if (!fs.existsSync(dir)) { 90 | fs.mkdirSync(dir, { 91 | "recursive": true 92 | }); 93 | } 94 | return dir; 95 | } 96 | 97 | /** 98 | * Return a filename for a log category. 99 | * @param {string} category The category of the log file 100 | * @param {number} index The index of the log file 101 | * @returns {string} The created filename 102 | */ 103 | function createFilename(category, index) { 104 | let filename = `${category}_${index}.log`; 105 | if (config.log.compression.active) filename += ".gz"; 106 | return filename; 107 | } 108 | 109 | /** 110 | * Returns a filename of the most recent and valid log to continue using. 111 | * @param {string} dir Directory of log folder 112 | * @param {string} category Category of the log to look for 113 | * @param {Array} files All valid files in the directory 114 | * @returns {string} Filename of the most recent and valid log to continue writing to (returns false otherwise) 115 | */ 116 | function findExisting(dir, category, files) { 117 | for (let i = files.length - 1; i >= 0; i--) { 118 | const file = files[i].name; 119 | // Check if there's an existing log file to reuse and whether it has enough space to write to 120 | if (file === createFilename(category, files.length) && fs.existsSync(dir) && fs.statSync(dir + file).size < config.log.cutoff * 1000) { 121 | return file; 122 | } 123 | } 124 | return false; 125 | } 126 | 127 | /** 128 | * Get current timestamp 129 | * @param {boolean} includeTime Whether to include the Date String 130 | * @returns {string} Human-readable timestamp 131 | */ 132 | function getTimestamp(includeTime) { 133 | let timestamp = new Date(); 134 | if (includeTime) { 135 | timestamp = timestamp.toLocaleDateString(); 136 | } else { 137 | timestamp = timestamp.toLocaleString(); 138 | } 139 | return timestamp.replace(/\//g, "-") // Replace forward-slash with hyphen 140 | .replace(",", ""); // Remove comma 141 | } 142 | 143 | // ======= 144 | // Exports 145 | // ======= 146 | 147 | module.exports = { 148 | packetHandler, 149 | log, 150 | getTimestamp 151 | }; 152 | -------------------------------------------------------------------------------- /util/mineflayer.js: -------------------------------------------------------------------------------- 1 | // ======= 2 | // Imports 3 | // ======= 4 | 5 | const autoeat = require("mineflayer-auto-eat").plugin; 6 | const antiafk = require("mineflayer-antiafk"); 7 | 8 | const { config, status } = require("./config.js"); 9 | const logger = require("./logger.js"); 10 | 11 | // === 12 | // Bot 13 | // === 14 | 15 | /** 16 | * Create the Mineflayer bot 17 | * @param {object} bot Mineflayer bot 18 | */ 19 | function initialize(bot) { 20 | // Don't proceed if Mineflayer isn't active 21 | if (!config.mineflayer.active) return; 22 | // Load plugins 23 | bot.loadPlugin(autoeat); 24 | bot.loadPlugin(antiafk); 25 | // Create bot 26 | bot.once("login", () => { 27 | // =============== 28 | // Auto-Queue Main 29 | // =============== 30 | if (config.server.host === "connect.2b2t.org") { // (only on 2b2t) 31 | const autoQueueMain = setInterval(function () { 32 | status.inQueue ? bot.chat("/queue main") : clearInterval(autoQueueMain); 33 | }, config.mineflayer.autoQueueMainInterval * 1000); 34 | } 35 | }); 36 | bot.once("spawn", () => { 37 | // ======= 38 | // Antiafk 39 | // ======= 40 | // Set plugin options 41 | bot.afk.setOptions(config.mineflayer.antiAfk); 42 | bot.afk.setOptions({ 43 | "killauraEnabled": false, 44 | "autoEatEnabled": false 45 | }); 46 | // ======= 47 | // Autoeat 48 | // ======= 49 | bot.autoEat.options = config.mineflayer.autoEat; // Load autoeat options 50 | // ========= 51 | // Kill Aura 52 | // ========= 53 | setInterval(() => { 54 | if (status.mineflayer === "true" && status.inQueue === "false") { 55 | // Target hostile mobs within 3.5 blocks 56 | const mobFilter = e => (e.kind === "Hostile mobs") && (e.position.distanceTo(bot.entity.position) < 3.5); 57 | const victim = bot.nearestEntity(mobFilter); 58 | if (victim && config.mineflayer.killAura.blacklist.indexOf(victim.name) === -1) { // Don't target mobs in config.mineflayer.killAura.blacklist 59 | bot.lookAt(victim.position); // For some reason using the promise doesn't work 60 | bot.attack(victim); 61 | } 62 | } 63 | }, config.mineflayer.killAura.interval * 1000); 64 | // ========== 65 | // Auto Totem 66 | // ========== 67 | setInterval(() => { 68 | if (status.mineflayer === "true" && status.inQueue === "false") { 69 | const totem = bot.inventory.findInventoryItem("totem_of_undying", null); 70 | if (totem) { 71 | bot.equip(totem, "off-hand"); 72 | } 73 | } 74 | }, config.mineflayer.autoTotem.interval * 1000); 75 | // ===== 76 | // Jesus 77 | // ===== 78 | bot.on("breath", () => { 79 | if (status.mineflayer === "true" && status.inQueue === "false") { 80 | bot.setControlState("jump", bot.oxygenLevel < 20); 81 | } 82 | }); 83 | // ====== 84 | // Logger 85 | // ====== 86 | // Events to listen to 87 | if (config.log.active.mineflayer) { 88 | bot.on("entitySpawn", (entity) => entityLogger(entity, "entered render distance")); 89 | bot.on("entityGone", (entity) => entityLogger(entity, "left render distance")); 90 | bot.on("playerJoined", (player) => playerLogger(player, "joined server")); 91 | bot.on("playerLeft", (player) => playerLogger(player, "left server")); 92 | } 93 | // Log all players that were already here in our render distance when we joined 94 | Object.values(bot.entities).filter(e => e.type === 'player' && e.username !== bot.username).forEach((entity) => { 95 | entityLogger(entity, "was already in render distance when we joined") 96 | }); 97 | /** 98 | * Log entities 99 | * @param {object} entity Mineflayer entity objec 100 | * @param {object} action One-word description of the event being logged 101 | */ 102 | function entityLogger(entity, action) { 103 | // Only log player entities 104 | if (entity.type !== "player") return; 105 | logger.log(action, `${entity.username} (${entity.uuid})`, "mineflayer"); 106 | } 107 | /** 108 | * Log player events 109 | * @param {object} player Mineflayer Player object 110 | * @param {object} action One-word description of the event being logged 111 | */ 112 | function playerLogger(player, action) { 113 | logger.log(action, `${player.username} (${player.uuid})`, "mineflayer"); 114 | } 115 | }); 116 | } 117 | 118 | // ======= 119 | // Exports 120 | // ======= 121 | 122 | module.exports = { 123 | initialize 124 | }; 125 | -------------------------------------------------------------------------------- /util/ngrok.js: -------------------------------------------------------------------------------- 1 | // ======= 2 | // Imports 3 | // ======= 4 | 5 | const fs = require("fs"); 6 | 7 | const ngrokWrapper = require("ngrok"); 8 | 9 | const { config, updateStatus } = require("./config.js"); 10 | const logger = require("./logger.js"); 11 | const notifier = require("./notifier.js"); 12 | 13 | // ========= 14 | // Functions 15 | // ========= 16 | 17 | /** 18 | * Create ngrok tunnel 19 | */ 20 | function createTunnel() { 21 | // Create ngrok.yml for the authtoken (using the wrapper causes issues when running multiple tunnels, as it isn't an officially supported thing) 22 | const data = `authtoken: ${config.ngrok.authtoken}`; 23 | fs.writeFile("./ngrok.yml", data, (error) => { 24 | if (error) { 25 | logger.log("createTunnel", error, "error"); 26 | return; 27 | } 28 | // Create tunnel 29 | ngrokWrapper.connect({ 30 | proto: "tcp", 31 | addr: config.proxy.port, 32 | region: config.ngrok.region, 33 | configPath: "./ngrok.yml" 34 | }).then(url => { 35 | url = url.split("tcp://")[1]; 36 | updateStatus("ngrokUrl", url); // Update cli gui and webhook 37 | notifier.sendWebhook({ 38 | title: "New Tunnel:", 39 | description: `Current IP: \`${url}\``, 40 | category: "spam" 41 | }); 42 | if (config.waitForControllerBeforeConnect) { // Since the client isn't connected we'll need to send the tunnel IP to the status webhook (normally the tunnel IP would be sent to the status webhook after going under the queueThreshold and joining the server) 43 | notifier.sendWebhook({ 44 | title: "Current Tunnel:", 45 | description: `Current IP: \`${url}\``, 46 | category: "status", 47 | deleteOnRestart: true 48 | }); 49 | } 50 | }).catch(error => { 51 | logger.log("createTunnel", error, "error"); 52 | }); 53 | }); 54 | } 55 | 56 | // ======= 57 | // Exports 58 | // ======= 59 | 60 | module.exports = { 61 | createTunnel 62 | }; 63 | -------------------------------------------------------------------------------- /util/notifier.js: -------------------------------------------------------------------------------- 1 | // ======= 2 | // Imports 3 | // ======= 4 | 5 | const toast = require("node-notifier"); 6 | const fetch = require("node-fetch"); 7 | 8 | const { config, status } = require("./config.js"); 9 | 10 | // =========== 11 | // Global Vars 12 | // =========== 13 | 14 | let deleteOnRestart = []; // Urls of messages to be deleted when restarting the proxy 15 | 16 | // ========= 17 | // Functions 18 | // ========= 19 | 20 | /** 21 | * Send a toast 22 | * @param {string} titleText The title of the toast notification 23 | */ 24 | function sendToast(titleText) { 25 | toast.notify({ 26 | "title": titleText, 27 | "message": " ", 28 | "subtitle": "2Based2Wait", 29 | "icon": "null", 30 | "contentImage": "null", 31 | "sound": "ding.mp3", 32 | "wait": false 33 | }); 34 | } 35 | 36 | /** 37 | * Send Discord webhook 38 | * @param {object} options Options object 39 | * @param {string} options.title The title of the embed 40 | * @param {string} options.description The description of the embed 41 | * @param {boolean} options.disableAttribution Whether or not to include explicit attribution to the proxy that sent the message 42 | * @param {string} options.category The webhook category to send the embed to 43 | * @param {string} options.imageUrl The imageUrl of the embed 44 | * @param {boolean} options.deleteOnRestart Whether to delete the message when restarting the proxy (technically only used on status webhook messages) 45 | */ 46 | function sendWebhook(options) { 47 | // Don't proceed if Discord webhooks are disabled in config.json 48 | if (!config.discord.active) return; 49 | 50 | // Create embed 51 | let params = { 52 | embeds: [ 53 | { 54 | "color": config.discord.color, 55 | "title": options.title, 56 | "description": options.description || "", 57 | "timestamp": new Date(), 58 | "image": { 59 | "url": null 60 | } 61 | } 62 | ] 63 | }; 64 | // If someone is controlling the bot add that to the embed 65 | if (status.controller !== "None") { 66 | params.embeds[0].footer = { 67 | "text": `Controller: ${status.controller}` 68 | }; 69 | } 70 | // Set author fields so that we know where each embed originated. If disabled, the only way to tell the source of a message (without checking logs) would be through embed color. 71 | if (!options.disableAttribution) { 72 | params.embeds[0].author = { 73 | "name": `Account: ${config.account.username}`, 74 | "icon_url": "https://minotar.net/helm/${config.account.username}/69.png" // nice - 2023-02-08 unclamped 75 | }; 76 | } 77 | 78 | // Add Discord ping to message content 79 | if (options.ping) params.content = `<@${config.discord.id}>`; 80 | 81 | // Add image to embed 82 | if (options.imageUrl) { 83 | params.embeds[0].image = { 84 | url: options.imageUrl, 85 | }; 86 | } 87 | 88 | // Send embed (if no destination is provided, defaults to config.discord.webhook.spam) 89 | const webhookUrl = (config.discord.webhook[options.category] || config.discord.webhook.spam); 90 | fetch(webhookUrl + "?wait=true", { 91 | method: "POST", 92 | headers: { 93 | "Content-type": "application/json" 94 | }, 95 | body: JSON.stringify(params) 96 | }).then(response => { 97 | if (options.deleteOnRestart) { 98 | response.text().then(json => { 99 | deleteOnRestart.push(`${webhookUrl}/messages/${JSON.parse(json).id}`); // URL to send DELETE request to when restarting the proxy 100 | }); 101 | } 102 | }); 103 | } 104 | 105 | /** Delete webhook messages marked for deletion */ 106 | function deleteMarkedMessages() { 107 | deleteOnRestart.forEach(url => { 108 | fetch(url, { 109 | method: "DELETE", 110 | headers: { 111 | "Content-type": "application/json" 112 | } 113 | }); 114 | }); 115 | } 116 | 117 | // ======= 118 | // Exports 119 | // ======= 120 | 121 | module.exports = { 122 | sendToast, 123 | sendWebhook, 124 | deleteMarkedMessages 125 | }; 126 | -------------------------------------------------------------------------------- /util/queue.js: -------------------------------------------------------------------------------- 1 | // ======= 2 | // Imports 3 | // ======= 4 | 5 | const { config, status, updateStatus } = require("./config.js"); 6 | const notifier = require("./notifier.js"); 7 | 8 | // =========== 9 | // Global Vars 10 | // =========== 11 | 12 | let sentNotification = false; 13 | 14 | // ========= 15 | // Functions 16 | // ========= 17 | 18 | /** 19 | * Difficulty packet handler, checks whether or not we're in queue 20 | * (explanation: when rerouted by Velocity, the difficulty packet is always sent *after* the MC|Brand packet.) 21 | * @param {string} packetName Packet type 22 | * @param {object} packetData `difficulty` packet data object 23 | * @param {object} conn McProxy conn object 24 | */ 25 | function difficultyPacketHandler(packetName, packetData, conn) { 26 | if (packetName != "difficulty") 27 | return; 28 | const inQueue = (conn.bot.game.serverBrand === "2b2t (Velocity)") && (conn.bot.game.dimension === "the_end") && (packetData.difficulty === 1); 29 | if (updateStatus("inQueue", inQueue) && inQueue === false && config.notify.whenJoining) { // Send notification when joining server 30 | notifier.sendToast("In Server!"); 31 | notifier.sendWebhook({ 32 | title: "In Server!", 33 | description: `Current IP: \`${status.ngrokUrl}\``, 34 | ping: true, 35 | category: "status", 36 | deleteOnRestart: true 37 | }); 38 | } 39 | } 40 | 41 | /** 42 | * Playerlist packet handler, checks position in queue 43 | * @param {string} packetName Packet type 44 | * @param {object} packetData `playerlist_header` packet data object 45 | * @param {object} server Mineflayer server object 46 | */ 47 | function playerlistHeaderPacketHandler(packetName, packetData, server) { 48 | if (packetName != "playerlist_header") 49 | return; 50 | // If no longer in queue, stop here 51 | if (status.inQueue === "false") { 52 | updateStatus("position", "In Server!"); 53 | updateStatus("eta", "Now!"); 54 | return; 55 | } 56 | // Parse header packets 57 | const header = JSON.parse(packetData.header).extra; 58 | if (header && header.length === 4) { 59 | const position = header[2].extra[0].text.replace(/\n/, ""); 60 | const eta = header[3].extra[0].text.replace(/\n/, ""); 61 | // Update position 62 | if (updateStatus("position", position)) { 63 | // Update local server motd 64 | server.motd = `Position: ${status.position} - ETA: ${status.eta}`; 65 | if (status.position <= config.queueThreshold) { // Position notifications on Discord (status webhook) 66 | notifier.sendToast(`2B2T Queue Position: ${status.position}`); 67 | notifier.sendWebhook({ 68 | title: `2B2T Queue Position: ${status.position}`, 69 | description: `ETA: ${status.eta}`, 70 | category: "spam" 71 | }); 72 | if (!sentNotification && config.notify.whenBelowQueueThreshold) { 73 | notifier.sendWebhook({ 74 | title: `Position ${status.position} in queue`, 75 | description: `Current IP: \`${status.ngrokUrl}\``, 76 | ping: true, 77 | category: "status", 78 | deleteOnRestart: true 79 | }); 80 | } 81 | sentNotification = true; 82 | } else { // Position notifications on Discord (spam webhook) 83 | notifier.sendWebhook({ 84 | title: `2B2T Queue Position: ${status.position}`, 85 | description: `ETA: ${status.eta}`, 86 | category: "spam" 87 | }); 88 | } 89 | } 90 | // Update ETA 91 | updateStatus("eta", eta); 92 | } 93 | } 94 | 95 | // ======= 96 | // Exports 97 | // ======= 98 | 99 | module.exports = { 100 | difficultyPacketHandler, 101 | playerlistHeaderPacketHandler 102 | }; 103 | -------------------------------------------------------------------------------- /util/schemas.js: -------------------------------------------------------------------------------- 1 | // ======= 2 | // Imports 3 | // ======= 4 | 5 | /* eslint-disable no-useless-escape */ 6 | const joi = require("joi"); 7 | const testedVersions = require("mineflayer").testedVersions; 8 | 9 | // =========== 10 | // Sub-Schemas 11 | // =========== 12 | 13 | // Schema used to validate Minecraft usernames (between 3 and 16 characters, containing only a-z, A-Z, 0-9, and _) 14 | const usernameSchema = joi.string().min(3).max(16).token(); 15 | 16 | // Schema used to validate packet names (lowercase, consisting only of a-z and underscores) 17 | const packetSchema = joi.string().pattern(/^[a-z_]*$/).lowercase(); 18 | 19 | // Schema used to validate Discord webhooks (to-do: update this to only recognize webhooks) 20 | const webhookSchema = joi.string().empty("").default(""); 21 | 22 | // Schema used to validate Minecraft versions tested by Mineflayer (see: https://github.com/PrismarineJS/mineflayer/blob/master/lib/version.js) 23 | const versionSchema = joi.string().valid(...testedVersions).default("1.12.2"); 24 | 25 | // Schema used to validate a handful of the most important Zlib options, based off of information available on https://zlib.net/manual.html 26 | const zlibOptionsSchema = joi.object({ 27 | "level": joi.number().integer().min(1).max(9).default(1) 28 | .description("How much compression to apply between 1 and 9. Higher values result in better compression ratio at the expense of speed (**\[Warning, Event Thread-Blocking!\]**)"), 29 | "memLevel": joi.number().integer().min(1).max(9).default(9) 30 | .description("How much memory to allocate to the internal compression state between 1 and 9. Higher values result in better compression ratio and speed at the expense of memory usage"), 31 | "windowBits": joi.number().integer().min(8).max(15).default(15) 32 | .description("How much memory to allocate to the history buffer between 8 and 15. Higher values result in better compression ratio at the expense of memory usage"), 33 | }).default(); 34 | 35 | // ============= 36 | // Config Schema 37 | // ============= 38 | 39 | // Schema used to validate config.json 40 | const configSchema = joi.object({ 41 | "account": joi.object({ 42 | "username": usernameSchema.required() 43 | .description("The in-game playername of the account"), 44 | "password": joi.string().empty("").default("") 45 | .description("The password of the account (only required for Mojang accounts, leave it empty for Microsoft accounts. Microsoft accounts will just get instructions in the console to put a token into [microsoft.com/link](https://microsoft.com/link)"), // to-do: add a mojang password regex 46 | "auth": joi.string().valid("microsoft", "mojang", "offline").default("microsoft") 47 | .description("Authentication type (options: 'microsoft', 'mojang', 'offline')") 48 | }).default(), 49 | "discord": joi.object({ 50 | "active": joi.boolean().default(false) 51 | .description("Whether to send Discord webhooks"), 52 | "webhook": joi.object({ 53 | "spam": webhookSchema 54 | .description("Url of webhook to relay position in queue, new tunnels, connects/disconnects, and other spam"), 55 | "livechat": webhookSchema 56 | .description("Url of webhook to relay livechat"), 57 | "status": webhookSchema 58 | .description("Url of webhook to relay pertinent info for connecting and nothing else (e.g. joining server, low queue position)") 59 | }).default(), 60 | "color": joi.number().integer().min(0).max(16777215).default(2123412) 61 | .description("Color of Discord embeds sent to the webhooks in **decimal value** (you can use convertingcolors.com to find the decimal value of a color you want)"), 62 | "id": joi.string().default(0) // although this can be an number for users, it can be a string for roles! 63 | .description("ID of the Discord user or role to ping when below the queueThreshold") 64 | }).default(), 65 | "queueThreshold": joi.number().integer().min(0).default(21) 66 | .description("Minimum queue position before toast notifications & Discord pings start getting sent"), 67 | "reconnectInterval": joi.number().positive().default(69) 68 | .description("Time (in seconds) between each reconnection attempt (see: [How to Auto-Reconnect with Supervisor](https://github.com/Enchoseon/2based2wait/wiki/How-to-Auto-Reconnect-with-Supervisor))"), 69 | "uncleanDisconnectInterval": joi.number().positive().default(196) 70 | .description("Time (in seconds) proxy will go without getting a single packet from 2B2T before assuming it was uncleanly disconnected and initiating a reconnect attempt"), 71 | "log": joi.object({ 72 | "active": joi.object({ 73 | "error": joi.boolean().default(true) 74 | .description("Whether to log errors"), 75 | "proxy": joi.boolean().default(true) 76 | .description("Whether to log proxy status (e.g. connecting to server, starting Mineflayer, etc.)"), 77 | "chat": joi.boolean().default(true) 78 | .description("Whether to log chat"), 79 | "bridgeClientPackets": joi.boolean().default(true) 80 | .description("Whether to log packets being sent from the controller to the proxy"), 81 | "serverPackets": joi.boolean().default(true) 82 | .description("Whether to log packets being sent from 2b2t to the proxy"), 83 | "mineflayer": joi.boolean().default(false) 84 | .description("Whether to log high-level Mineflayer events, if the Mineflayer bot is active (e.g. player join/leave, items, etc.)") 85 | }).default() 86 | .description("Settings for which logging categories should be enabled"), 87 | "cutoff": joi.number().integer().positive().default(69000) // Not setting a minimum for this seems dangerous... 88 | .description("Maximum size a log file can be (in bytes) before it gets split up"), 89 | "packetFilters": joi.object({ 90 | "server": joi.array().items(packetSchema).default(["map", "map_chunk", "player_info", "entity_metadata", "entity_velocity", "entity_move_look", "entity_look", "update_time", "world_particles", "unload_chunk", "teams", "rel_entity_move", "entity_head_rotation", "entity_update_attributes", "block_change"]) 91 | .description("Packets being sent from 2b2t to not log"), 92 | "bridgeClient": joi.array().items(packetSchema).default(["position", "look", "position_look", "arm_animation", "keep_alive"]) 93 | .description("Packets being sent from the controller to not log") 94 | }).default() 95 | .description("Settings for which packets we shouldn't log"), 96 | "compression": joi.object({ 97 | "active": joi.boolean().default(false) 98 | .description("**\[Warning, Event Thread-Blocking!\]** Whether to compress log files with Gzip. Leave this off unless you have a really good reason to enable it"), 99 | }).concat(zlibOptionsSchema).default() 100 | .description("Settings for log compression. Tweak with caution. The default options maximize memory usage for the fastest speed"), 101 | "alwaysIncrement": joi.boolean().default(false) 102 | .description("Whether to increment the log file every session (can lead to thousands of 1kb log files in production, but is pretty useful when rapidly testing during development)"), 103 | }).default(), 104 | "server": joi.object({ 105 | "host": joi.string().hostname().default("connect.2b2t.org") 106 | .description("Address of the server to connect to"), 107 | "version": versionSchema 108 | .description("Version of Minecraft the server is on "), 109 | "port": joi.number().port().default(25565) 110 | .description("Port of the server to connect to") 111 | }).default() 112 | .description("Settings for how the proxy connects to the server"), 113 | "proxy": joi.object({ 114 | "active": joi.boolean().default(true) 115 | .description("Whether to allow players to control the account by connecting through a tunnel"), 116 | "whitelist": joi.array().items(usernameSchema) 117 | .description("Playernames of accounts that are allowed to connect to the proxy"), 118 | "onlineMode": joi.boolean().default(true) 119 | .description("Whether to enable online-mode on the proxy. This probably should never be touched"), 120 | "loopbackAddress": joi.string().valid("127.0.0.1", "localhost", "0.0.0.0", "::1").default("127.0.0.1") 121 | .description("Loopback address to connect to the proxy. (options: '127.0.0.1', 'localhost', '0.0.0.0', '::1')"), 122 | "port": joi.number().port().default(25565) 123 | .description("Port on the machine to connect to the proxy") 124 | }).default() 125 | .description("Settings for how you connect to the proxy"), 126 | "ngrok": joi.object({ 127 | "active": joi.boolean().default(false) 128 | .description("Whether to create an ngrok tunnel"), 129 | "authtoken": joi.string().empty("").pattern(/[A-Za-z0-9\-\._~\+\/]+=*/).default("") // (Bearer Token Regex) From: https://www.regextester.com/95017 130 | .description("The auth token for your Ngrok.io account"), 131 | "region": joi.string().valid("us", "eu", "au", "ap", "sa", "jp", "in").default("us") // From: https://ngrok.com/docs/ngrok-agent/ (under "--region string") 132 | .description("Tunnel region (options: 'us', 'eu', 'au', 'ap', 'sa', 'jp', or 'in')") 133 | }).default() 134 | .description("Settings for ngrok tunneling"), 135 | "mineflayer": joi.object({ 136 | "active": joi.boolean().default(true) 137 | .description("Whether to enable Mineflayer"), 138 | "autoQueueMainInterval": joi.number().positive().default(690) // Not setting a minimum for this seems dangerous... 139 | .description("Time (in seconds) between every `/queue main` command"), 140 | "killAura": joi.object({ 141 | "interval": joi.number().positive().default(0.69) 142 | .description("Time (in seconds) between every attack attempt"), 143 | "blacklist": joi.array().items(packetSchema).default(["zombie_pigman", "enderman"]) 144 | .description("Array of mobs that will not be attacked") 145 | }).default() 146 | .description("Settings for killaura"), 147 | "autoEat": joi.object({ 148 | "priority": joi.string().valid("saturation", "foodPoints", "effectiveQuality").default("saturation") // From: https://github.com/link-discord/mineflayer-auto-eat#botautoeatoptionspriority 149 | .description("What type of food to prioritize eating (options: 'saturation', 'foodPoints', 'effectiveQuality')"), 150 | "startAt": joi.number().integer().min(0).default(19) 151 | .description("Hunger level at which to start eating"), 152 | "eatingTimeout": joi.number().integer().min(-1).default(6969) 153 | .description("Maximum time (in ms) the proxy will attempt to eat an item before giving up"), 154 | "bannedFood": joi.array().items(packetSchema).default(["rotten_flesh", "pufferfish", "chorus_fruit", "poisonous_potato", "spider_eye"]) 155 | .description("Foods that will not be eaten"), 156 | "ignoreInventoryCheck": joi.boolean().default(false) 157 | .description("Whether to disable inventory window click confirmation as a dirty hack to get around ViaBackwards' protocol noncompliance"), 158 | "checkOnItemPickup": joi.boolean().default(true) 159 | .description("Whether to attempt to eat food that's picked up when below the startAt threshold"), 160 | "offhand": joi.boolean().default(false) 161 | .description("Whether to use the offhand slot to eat food"), 162 | "equipOldItem": joi.boolean().default(true) 163 | .description("Whether to reequip the previously held item after eating") 164 | }).default() 165 | .description("Settings for autoeat"), 166 | "antiAfk": joi.object({ 167 | "actions": joi.array().items(joi.string().valid("rotate", "walk", "jump", "jumpWalk", "swingArm", "breakBlock")).default(["rotate"]) 168 | .description("Actions the proxy can do (options: 'rotate', 'walk', 'jump', 'jumpWalk', 'swingArm', 'breakBlock')"), 169 | "fishing": joi.boolean().default(false) 170 | .description("Whether the proxy will fish. The account must be standing in water and have a fishing rod to autofish."), 171 | "chatting": joi.boolean().default(false) 172 | .description("Whether the proxy will chat"), 173 | "chatMessages": joi.array().items(joi.string().min(1).max(256)).default(["!pt", "!queue"]) // to-do: find out any other chat limits 174 | .description("Chat messages that the proxy will send if chatting is enabled"), 175 | "chatInterval": joi.number().integer().positive().default(690420) // Not setting a minimum for this seems dangerous... 176 | .description("Time (in milliseconds) between each chat message") 177 | }).default() 178 | .description("Settings for antiafk"), 179 | "autoTotem": joi.object({ 180 | "interval": joi.number().integer().positive().min(1).default(50) 181 | .description("Time (in milliseconds) between each totem equip attempt") 182 | }).default() 183 | .description("Settings for autototem"), 184 | }).default() 185 | .description("Settings for the mineflayer bot"), 186 | "experimental": joi.object({ 187 | "spoofPlayerInfo": joi.object({ 188 | "active": joi.boolean().default(true) 189 | .description("Whether to spoof the [Player Info packet](https://wiki.vg/Protocol#Player_Info) to set a custom skin"), 190 | "texture": joi.object({ // From: https://wiki.vg/Mojang_API#UUID_to_Profile_and_Skin.2FCape 191 | "value": joi.string().empty("").base64({ urlSafe: true, paddingRequired: true }).default("") 192 | .description("Base64 string of skin from [https://sessionserver.mojang.com/session/minecraft/profile/?unsigned=false](https://wiki.vg/Mojang_API#UUID_to_Profile_and_Skin.2FCape)"), 193 | "signature": joi.string().empty("").base64({ paddingRequired: true }).default("") 194 | .description("Base64 string of signed data using Yggdrasil's private key from [https://sessionserver.mojang.com/session/minecraft/profile/?unsigned=false](https://wiki.vg/Mojang_API#UUID_to_Profile_and_Skin.2FCape)"), 195 | }).default() 196 | }).default(), 197 | "spoofPing": joi.object({ 198 | "active": joi.boolean().default(false) 199 | .description("Whether to spoof the [Status Response packet](https://wiki.vg/Server_List_Ping#Status_Response) when pinging the proxy server"), 200 | "noResponse": joi.boolean().default(false) 201 | .description("Whether to cancel the response entirely. Otherwise, the packet described in fakeResponse will be sent."), 202 | "fakeResponse": joi.object({ // From: https://wiki.vg/Server_List_Ping#Status_Response (default values simulate a normal server) 203 | "version": joi.object({ 204 | "name": versionSchema 205 | .description("Spoofed server version"), 206 | "protocol": joi.number().integer().default(340) // From: https://wiki.vg/Protocol_version_numbers 207 | .description("Spoofed [protocol number](https://wiki.vg/Protocol_version_numbers)") 208 | }).default(), 209 | "players": joi.object({ 210 | "max": joi.number().integer().default(20) // From: https://minecraft.fandom.com/wiki/Server.properties#Java_Edition_2 (under "max-players") 211 | .description("Spoofed max players"), 212 | "online": joi.number().integer().default(0) 213 | .description("Spoofed number of players online"), 214 | "sample": joi.array().items(joi.object({ 215 | "name": usernameSchema 216 | .description("Spoofed playername"), 217 | "id": joi.string().uuid({ version: "uuidv4", separator: "-" }) 218 | .description("Spoofed player UUID") 219 | })).default([]) 220 | }).default(), 221 | "description": joi.object({ 222 | "text": joi.string().default("A Minecraft server") 223 | .description("Spoofed MOTD") 224 | }).default(), 225 | "favicon": joi.string().default("undefined") 226 | .description("Spoofed Base64-encoded 64x64 png favicon") 227 | }).default() 228 | }).default(), 229 | "disconnectIfNoController": joi.object({ 230 | "active": joi.boolean().default(false) 231 | .description("Whether to disconnect if noone is controlling the proxy disconnectIfNoController.delay seconds after a controller disconnects from the proxy while it isn't in queue"), 232 | "delay": joi.number().min(0).default(7) // Not setting a minimum for this seems dangerous... 233 | .description("How long to wait (in seconds) after a controller disconnects from the proxy while it isn't in queue before disconnecting from the server") 234 | }).default(), 235 | "worldDownloader": joi.object({ 236 | "active": joi.boolean().default(false) 237 | .description("**\[Warning, Event Thread-Blocking!\]** Whether to use the experimental world downloader"), 238 | "compression": zlibOptionsSchema.default() 239 | .description("Settings for packet archive compression. Tweak with caution. The default options maximize memory usage for the fastest speed") 240 | }).default(), 241 | "maxThreadpool": joi.object({ 242 | "active": joi.boolean().default(true) 243 | .description("Whether to set UV_THREADPOOL_SIZE to use all possible CPU logic cores") 244 | }).default(), 245 | "syncGamestate": joi.object({ 246 | "active": joi.boolean().default(true) 247 | .description("Send fake packets to attempt to sync gamestate") 248 | }).default(), 249 | }).default() 250 | .description("Settings for experimental features that may be more unstable in resource usage and/or server and version parity"), 251 | "waitForControllerBeforeConnect": joi.boolean().default(false) 252 | .description("Whether the proxy will wait for someone to take control before it connects to the server"), 253 | "notify": joi.object({ 254 | "whenJoining": joi.boolean().default(true) 255 | .description("Whether to send a toast notification and status webhook message when the proxy joins the server from queue"), 256 | "whenBelowQueueThreshold": joi.boolean().default(true) 257 | .description("Whether to send a toast notification and status webhook message when the proxy dips below position `queueThreshold` in queue"), 258 | "whenControlling": joi.boolean().default(false) 259 | .description("Whether to send a status webhook message when a controller connects and disconnects from the proxy") 260 | }).default() 261 | .description("Settings for what the proxy will send notifications about"), 262 | "noCliGui": joi.boolean().default(false) 263 | .description("Whether to disable the cli gui"), 264 | "coordination": joi.object({ 265 | "active": joi.boolean().default(false) 266 | .description("Whether to use a [master config file and coordinator](https://github.com/Enchoseon/2based2wait/wiki/How-to-Proxy-Multiple-Accounts)"), 267 | "path": joi.string().default("./../") 268 | .description("Path to the folder where the shared master-config.json and coordinator.flag files should go") 269 | }).default() 270 | .description("Settings for coordinating multiple proxies") 271 | }); 272 | 273 | // ======= 274 | // Exports 275 | // ======= 276 | 277 | module.exports = { 278 | configSchema 279 | }; 280 | --------------------------------------------------------------------------------