Reewrite project #430
							
								
								
									
										35
									
								
								.github/workflows/testProject.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										35
									
								
								.github/workflows/testProject.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,35 +0,0 @@
 | 
				
			|||||||
name: Test Bds Core
 | 
					 | 
				
			||||||
on:
 | 
					 | 
				
			||||||
  pull_request:
 | 
					 | 
				
			||||||
  push:
 | 
					 | 
				
			||||||
    branches:
 | 
					 | 
				
			||||||
      - main
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
jobs:
 | 
					 | 
				
			||||||
  test:
 | 
					 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					 | 
				
			||||||
    name: "Test (Node version: ${{ matrix.node_version }})"
 | 
					 | 
				
			||||||
    strategy:
 | 
					 | 
				
			||||||
      matrix:
 | 
					 | 
				
			||||||
        node_version:
 | 
					 | 
				
			||||||
          - "18"
 | 
					 | 
				
			||||||
          - "17"
 | 
					 | 
				
			||||||
          - "16"
 | 
					 | 
				
			||||||
    steps:
 | 
					 | 
				
			||||||
      - uses: actions/checkout@v2.4.0
 | 
					 | 
				
			||||||
      - name: Setup Node.js (Github Packages)
 | 
					 | 
				
			||||||
        uses: actions/setup-node@v3.4.1
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          node-version: "${{ matrix.node_version }}.x"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Install latest java
 | 
					 | 
				
			||||||
        uses: actions/setup-java@v3
 | 
					 | 
				
			||||||
        with:
 | 
					 | 
				
			||||||
          distribution: temurin
 | 
					 | 
				
			||||||
          java-version: "17"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Install node depencies
 | 
					 | 
				
			||||||
        run: npm ci
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Test
 | 
					 | 
				
			||||||
        run: npm run test -- --show-log
 | 
					 | 
				
			||||||
							
								
								
									
										25
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										25
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							@@ -1,16 +1,21 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "version": "0.2.0",
 | 
						"version": "0.2.0",
 | 
				
			||||||
  "configurations": [
 | 
						"configurations": [
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      "name": "Bds Test",
 | 
					 | 
				
			||||||
      "type": "node",
 | 
					      "type": "node",
 | 
				
			||||||
      "request": "launch",
 | 
					      "name": "Mocha Tests",
 | 
				
			||||||
      "runtimeExecutable": "node",
 | 
					      "program": "${workspaceFolder}/node_modules/mocha/bin/mocha.js",
 | 
				
			||||||
      "runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"],
 | 
					 | 
				
			||||||
      "args": ["testProject.ts"],
 | 
					 | 
				
			||||||
      "cwd": "${workspaceRoot}",
 | 
					 | 
				
			||||||
      "internalConsoleOptions": "openOnSessionStart",
 | 
					      "internalConsoleOptions": "openOnSessionStart",
 | 
				
			||||||
      "skipFiles": ["<node_internals>/**", "node_modules/**"]
 | 
					      "request": "launch",
 | 
				
			||||||
 | 
					      "skipFiles": [
 | 
				
			||||||
 | 
					        "<node_internals>/**"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "args": [
 | 
				
			||||||
 | 
					        "-r", "ts-node/register",
 | 
				
			||||||
 | 
					        "--colors",
 | 
				
			||||||
 | 
					        "${workspaceFolder}/tests/**/*.ts"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  ]
 | 
					
 | 
				
			||||||
 | 
						]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,128 +0,0 @@
 | 
				
			|||||||
# Contributor Covenant Code of Conduct
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Our Pledge
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
We as members, contributors, and leaders pledge to make participation in our
 | 
					 | 
				
			||||||
community a harassment-free experience for everyone, regardless of age, body
 | 
					 | 
				
			||||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
 | 
					 | 
				
			||||||
identity and expression, level of experience, education, socio-economic status,
 | 
					 | 
				
			||||||
nationality, personal appearance, race, religion, or sexual identity
 | 
					 | 
				
			||||||
and orientation.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
We pledge to act and interact in ways that contribute to an open, welcoming,
 | 
					 | 
				
			||||||
diverse, inclusive, and healthy community.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Our Standards
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Examples of behavior that contributes to a positive environment for our
 | 
					 | 
				
			||||||
community include:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* Demonstrating empathy and kindness toward other people
 | 
					 | 
				
			||||||
* Being respectful of differing opinions, viewpoints, and experiences
 | 
					 | 
				
			||||||
* Giving and gracefully accepting constructive feedback
 | 
					 | 
				
			||||||
* Accepting responsibility and apologizing to those affected by our mistakes,
 | 
					 | 
				
			||||||
  and learning from the experience
 | 
					 | 
				
			||||||
* Focusing on what is best not just for us as individuals, but for the
 | 
					 | 
				
			||||||
  overall community
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Examples of unacceptable behavior include:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* The use of sexualized language or imagery, and sexual attention or
 | 
					 | 
				
			||||||
  advances of any kind
 | 
					 | 
				
			||||||
* Trolling, insulting or derogatory comments, and personal or political attacks
 | 
					 | 
				
			||||||
* Public or private harassment
 | 
					 | 
				
			||||||
* Publishing others' private information, such as a physical or email
 | 
					 | 
				
			||||||
  address, without their explicit permission
 | 
					 | 
				
			||||||
* Other conduct which could reasonably be considered inappropriate in a
 | 
					 | 
				
			||||||
  professional setting
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Enforcement Responsibilities
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Community leaders are responsible for clarifying and enforcing our standards of
 | 
					 | 
				
			||||||
acceptable behavior and will take appropriate and fair corrective action in
 | 
					 | 
				
			||||||
response to any behavior that they deem inappropriate, threatening, offensive,
 | 
					 | 
				
			||||||
or harmful.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Community leaders have the right and responsibility to remove, edit, or reject
 | 
					 | 
				
			||||||
comments, commits, code, wiki edits, issues, and other contributions that are
 | 
					 | 
				
			||||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
 | 
					 | 
				
			||||||
decisions when appropriate.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Scope
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
This Code of Conduct applies within all community spaces, and also applies when
 | 
					 | 
				
			||||||
an individual is officially representing the community in public spaces.
 | 
					 | 
				
			||||||
Examples of representing our community include using an official e-mail address,
 | 
					 | 
				
			||||||
posting via an official social media account, or acting as an appointed
 | 
					 | 
				
			||||||
representative at an online or offline event.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Enforcement
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
 | 
					 | 
				
			||||||
reported to the community leaders responsible for enforcement at
 | 
					 | 
				
			||||||
.
 | 
					 | 
				
			||||||
All complaints will be reviewed and investigated promptly and fairly.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
All community leaders are obligated to respect the privacy and security of the
 | 
					 | 
				
			||||||
reporter of any incident.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Enforcement Guidelines
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Community leaders will follow these Community Impact Guidelines in determining
 | 
					 | 
				
			||||||
the consequences for any action they deem in violation of this Code of Conduct:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 1. Correction
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Community Impact**: Use of inappropriate language or other behavior deemed
 | 
					 | 
				
			||||||
unprofessional or unwelcome in the community.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Consequence**: A private, written warning from community leaders, providing
 | 
					 | 
				
			||||||
clarity around the nature of the violation and an explanation of why the
 | 
					 | 
				
			||||||
behavior was inappropriate. A public apology may be requested.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 2. Warning
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Community Impact**: A violation through a single incident or series
 | 
					 | 
				
			||||||
of actions.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Consequence**: A warning with consequences for continued behavior. No
 | 
					 | 
				
			||||||
interaction with the people involved, including unsolicited interaction with
 | 
					 | 
				
			||||||
those enforcing the Code of Conduct, for a specified period of time. This
 | 
					 | 
				
			||||||
includes avoiding interactions in community spaces as well as external channels
 | 
					 | 
				
			||||||
like social media. Violating these terms may lead to a temporary or
 | 
					 | 
				
			||||||
permanent ban.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 3. Temporary Ban
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Community Impact**: A serious violation of community standards, including
 | 
					 | 
				
			||||||
sustained inappropriate behavior.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Consequence**: A temporary ban from any sort of interaction or public
 | 
					 | 
				
			||||||
communication with the community for a specified period of time. No public or
 | 
					 | 
				
			||||||
private interaction with the people involved, including unsolicited interaction
 | 
					 | 
				
			||||||
with those enforcing the Code of Conduct, is allowed during this period.
 | 
					 | 
				
			||||||
Violating these terms may lead to a permanent ban.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 4. Permanent Ban
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Community Impact**: Demonstrating a pattern of violation of community
 | 
					 | 
				
			||||||
standards, including sustained inappropriate behavior,  harassment of an
 | 
					 | 
				
			||||||
individual, or aggression toward or disparagement of classes of individuals.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Consequence**: A permanent ban from any sort of public interaction within
 | 
					 | 
				
			||||||
the community.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Attribution
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
 | 
					 | 
				
			||||||
version 2.0, available at
 | 
					 | 
				
			||||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
 | 
					 | 
				
			||||||
enforcement ladder](https://github.com/mozilla/diversity).
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[homepage]: https://www.contributor-covenant.org
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
For answers to common questions about this code of conduct, see the FAQ at
 | 
					 | 
				
			||||||
https://www.contributor-covenant.org/faq. Translations are available at
 | 
					 | 
				
			||||||
https://www.contributor-covenant.org/translations.
 | 
					 | 
				
			||||||
@@ -1,14 +0,0 @@
 | 
				
			|||||||
# Contributing to Core Core for server management and events
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Read Code of Conduct fist!
 | 
					 | 
				
			||||||
[Bds Manager Core code of conduct](CODE_OF_CONDUCT.md)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## TL;DR
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
[Add Platform](https://github.com/The-Bds-Maneger/Bds-Maneger-Core/wiki/Add-new-Platform-to-Core)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Bds Manager Core accepts contributions provided that:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* Be a fork of the repository.
 | 
					 | 
				
			||||||
* have the latest repository modifications applied to the repository in the `main` branch.
 | 
					 | 
				
			||||||
* to add a platform to the project, maintaining scripts.
 | 
					 | 
				
			||||||
							
								
								
									
										146
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										146
									
								
								LICENSE
									
									
									
									
									
								
							@@ -1,5 +1,5 @@
 | 
				
			|||||||
                    GNU AFFERO GENERAL PUBLIC LICENSE
 | 
					                    GNU GENERAL PUBLIC LICENSE
 | 
				
			||||||
                       Version 3, 19 November 2007
 | 
					                       Version 3, 29 June 2007
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
 | 
					 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
 | 
				
			||||||
 Everyone is permitted to copy and distribute verbatim copies
 | 
					 Everyone is permitted to copy and distribute verbatim copies
 | 
				
			||||||
@@ -7,15 +7,17 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                            Preamble
 | 
					                            Preamble
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  The GNU Affero General Public License is a free, copyleft license for
 | 
					  The GNU General Public License is a free, copyleft license for
 | 
				
			||||||
software and other kinds of works, specifically designed to ensure
 | 
					software and other kinds of works.
 | 
				
			||||||
cooperation with the community in the case of network server software.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  The licenses for most software and other practical works are designed
 | 
					  The licenses for most software and other practical works are designed
 | 
				
			||||||
to take away your freedom to share and change the works.  By contrast,
 | 
					to take away your freedom to share and change the works.  By contrast,
 | 
				
			||||||
our General Public Licenses are intended to guarantee your freedom to
 | 
					the GNU General Public License is intended to guarantee your freedom to
 | 
				
			||||||
share and change all versions of a program--to make sure it remains free
 | 
					share and change all versions of a program--to make sure it remains free
 | 
				
			||||||
software for all its users.
 | 
					software for all its users.  We, the Free Software Foundation, use the
 | 
				
			||||||
 | 
					GNU General Public License for most of our software; it applies also to
 | 
				
			||||||
 | 
					any other work released this way by its authors.  You can apply it to
 | 
				
			||||||
 | 
					your programs, too.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  When we speak of free software, we are referring to freedom, not
 | 
					  When we speak of free software, we are referring to freedom, not
 | 
				
			||||||
price.  Our General Public Licenses are designed to make sure that you
 | 
					price.  Our General Public Licenses are designed to make sure that you
 | 
				
			||||||
@@ -24,34 +26,44 @@ them if you wish), that you receive source code or can get it if you
 | 
				
			|||||||
want it, that you can change the software or use pieces of it in new
 | 
					want it, that you can change the software or use pieces of it in new
 | 
				
			||||||
free programs, and that you know you can do these things.
 | 
					free programs, and that you know you can do these things.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Developers that use our General Public Licenses protect your rights
 | 
					  To protect your rights, we need to prevent others from denying you
 | 
				
			||||||
with two steps: (1) assert copyright on the software, and (2) offer
 | 
					these rights or asking you to surrender the rights.  Therefore, you have
 | 
				
			||||||
you this License which gives you legal permission to copy, distribute
 | 
					certain responsibilities if you distribute copies of the software, or if
 | 
				
			||||||
and/or modify the software.
 | 
					you modify it: responsibilities to respect the freedom of others.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  A secondary benefit of defending all users' freedom is that
 | 
					  For example, if you distribute copies of such a program, whether
 | 
				
			||||||
improvements made in alternate versions of the program, if they
 | 
					gratis or for a fee, you must pass on to the recipients the same
 | 
				
			||||||
receive widespread use, become available for other developers to
 | 
					freedoms that you received.  You must make sure that they, too, receive
 | 
				
			||||||
incorporate.  Many developers of free software are heartened and
 | 
					or can get the source code.  And you must show them these terms so they
 | 
				
			||||||
encouraged by the resulting cooperation.  However, in the case of
 | 
					know their rights.
 | 
				
			||||||
software used on network servers, this result may fail to come about.
 | 
					 | 
				
			||||||
The GNU General Public License permits making a modified version and
 | 
					 | 
				
			||||||
letting the public access it on a server without ever releasing its
 | 
					 | 
				
			||||||
source code to the public.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  The GNU Affero General Public License is designed specifically to
 | 
					  Developers that use the GNU GPL protect your rights with two steps:
 | 
				
			||||||
ensure that, in such cases, the modified source code becomes available
 | 
					(1) assert copyright on the software, and (2) offer you this License
 | 
				
			||||||
to the community.  It requires the operator of a network server to
 | 
					giving you legal permission to copy, distribute and/or modify it.
 | 
				
			||||||
provide the source code of the modified version running there to the
 | 
					 | 
				
			||||||
users of that server.  Therefore, public use of a modified version, on
 | 
					 | 
				
			||||||
a publicly accessible server, gives the public access to the source
 | 
					 | 
				
			||||||
code of the modified version.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  An older license, called the Affero General Public License and
 | 
					  For the developers' and authors' protection, the GPL clearly explains
 | 
				
			||||||
published by Affero, was designed to accomplish similar goals.  This is
 | 
					that there is no warranty for this free software.  For both users' and
 | 
				
			||||||
a different license, not a version of the Affero GPL, but Affero has
 | 
					authors' sake, the GPL requires that modified versions be marked as
 | 
				
			||||||
released a new version of the Affero GPL which permits relicensing under
 | 
					changed, so that their problems will not be attributed erroneously to
 | 
				
			||||||
this license.
 | 
					authors of previous versions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Some devices are designed to deny users access to install or run
 | 
				
			||||||
 | 
					modified versions of the software inside them, although the manufacturer
 | 
				
			||||||
 | 
					can do so.  This is fundamentally incompatible with the aim of
 | 
				
			||||||
 | 
					protecting users' freedom to change the software.  The systematic
 | 
				
			||||||
 | 
					pattern of such abuse occurs in the area of products for individuals to
 | 
				
			||||||
 | 
					use, which is precisely where it is most unacceptable.  Therefore, we
 | 
				
			||||||
 | 
					have designed this version of the GPL to prohibit the practice for those
 | 
				
			||||||
 | 
					products.  If such problems arise substantially in other domains, we
 | 
				
			||||||
 | 
					stand ready to extend this provision to those domains in future versions
 | 
				
			||||||
 | 
					of the GPL, as needed to protect the freedom of users.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Finally, every program is threatened constantly by software patents.
 | 
				
			||||||
 | 
					States should not allow patents to restrict development and use of
 | 
				
			||||||
 | 
					software on general-purpose computers, but in those that do, we wish to
 | 
				
			||||||
 | 
					avoid the special danger that patents applied to a free program could
 | 
				
			||||||
 | 
					make it effectively proprietary.  To prevent this, the GPL assures that
 | 
				
			||||||
 | 
					patents cannot be used to render the program non-free.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  The precise terms and conditions for copying, distribution and
 | 
					  The precise terms and conditions for copying, distribution and
 | 
				
			||||||
modification follow.
 | 
					modification follow.
 | 
				
			||||||
@@ -60,7 +72,7 @@ modification follow.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  0. Definitions.
 | 
					  0. Definitions.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  "This License" refers to version 3 of the GNU Affero General Public License.
 | 
					  "This License" refers to version 3 of the GNU General Public License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  "Copyright" also means copyright-like laws that apply to other kinds of
 | 
					  "Copyright" also means copyright-like laws that apply to other kinds of
 | 
				
			||||||
works, such as semiconductor masks.
 | 
					works, such as semiconductor masks.
 | 
				
			||||||
@@ -537,45 +549,35 @@ to collect a royalty for further conveying from those to whom you convey
 | 
				
			|||||||
the Program, the only way you could satisfy both those terms and this
 | 
					the Program, the only way you could satisfy both those terms and this
 | 
				
			||||||
License would be to refrain entirely from conveying the Program.
 | 
					License would be to refrain entirely from conveying the Program.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  13. Remote Network Interaction; Use with the GNU General Public License.
 | 
					  13. Use with the GNU Affero General Public License.
 | 
				
			||||||
 | 
					 | 
				
			||||||
  Notwithstanding any other provision of this License, if you modify the
 | 
					 | 
				
			||||||
Program, your modified version must prominently offer all users
 | 
					 | 
				
			||||||
interacting with it remotely through a computer network (if your version
 | 
					 | 
				
			||||||
supports such interaction) an opportunity to receive the Corresponding
 | 
					 | 
				
			||||||
Source of your version by providing access to the Corresponding Source
 | 
					 | 
				
			||||||
from a network server at no charge, through some standard or customary
 | 
					 | 
				
			||||||
means of facilitating copying of software.  This Corresponding Source
 | 
					 | 
				
			||||||
shall include the Corresponding Source for any work covered by version 3
 | 
					 | 
				
			||||||
of the GNU General Public License that is incorporated pursuant to the
 | 
					 | 
				
			||||||
following paragraph.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Notwithstanding any other provision of this License, you have
 | 
					  Notwithstanding any other provision of this License, you have
 | 
				
			||||||
permission to link or combine any covered work with a work licensed
 | 
					permission to link or combine any covered work with a work licensed
 | 
				
			||||||
under version 3 of the GNU General Public License into a single
 | 
					under version 3 of the GNU Affero General Public License into a single
 | 
				
			||||||
combined work, and to convey the resulting work.  The terms of this
 | 
					combined work, and to convey the resulting work.  The terms of this
 | 
				
			||||||
License will continue to apply to the part which is the covered work,
 | 
					License will continue to apply to the part which is the covered work,
 | 
				
			||||||
but the work with which it is combined will remain governed by version
 | 
					but the special requirements of the GNU Affero General Public License,
 | 
				
			||||||
3 of the GNU General Public License.
 | 
					section 13, concerning interaction through a network will apply to the
 | 
				
			||||||
 | 
					combination as such.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  14. Revised Versions of this License.
 | 
					  14. Revised Versions of this License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  The Free Software Foundation may publish revised and/or new versions of
 | 
					  The Free Software Foundation may publish revised and/or new versions of
 | 
				
			||||||
the GNU Affero General Public License from time to time.  Such new versions
 | 
					the GNU General Public License from time to time.  Such new versions will
 | 
				
			||||||
will be similar in spirit to the present version, but may differ in detail to
 | 
					be similar in spirit to the present version, but may differ in detail to
 | 
				
			||||||
address new problems or concerns.
 | 
					address new problems or concerns.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Each version is given a distinguishing version number.  If the
 | 
					  Each version is given a distinguishing version number.  If the
 | 
				
			||||||
Program specifies that a certain numbered version of the GNU Affero General
 | 
					Program specifies that a certain numbered version of the GNU General
 | 
				
			||||||
Public License "or any later version" applies to it, you have the
 | 
					Public License "or any later version" applies to it, you have the
 | 
				
			||||||
option of following the terms and conditions either of that numbered
 | 
					option of following the terms and conditions either of that numbered
 | 
				
			||||||
version or of any later version published by the Free Software
 | 
					version or of any later version published by the Free Software
 | 
				
			||||||
Foundation.  If the Program does not specify a version number of the
 | 
					Foundation.  If the Program does not specify a version number of the
 | 
				
			||||||
GNU Affero General Public License, you may choose any version ever published
 | 
					GNU General Public License, you may choose any version ever published
 | 
				
			||||||
by the Free Software Foundation.
 | 
					by the Free Software Foundation.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  If the Program specifies that a proxy can decide which future
 | 
					  If the Program specifies that a proxy can decide which future
 | 
				
			||||||
versions of the GNU Affero General Public License can be used, that proxy's
 | 
					versions of the GNU General Public License can be used, that proxy's
 | 
				
			||||||
public statement of acceptance of a version permanently authorizes you
 | 
					public statement of acceptance of a version permanently authorizes you
 | 
				
			||||||
to choose that version for the Program.
 | 
					to choose that version for the Program.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -630,32 +632,44 @@ state the exclusion of warranty; and each file should have at least
 | 
				
			|||||||
the "copyright" line and a pointer to where the full notice is found.
 | 
					the "copyright" line and a pointer to where the full notice is found.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <one line to give the program's name and a brief idea of what it does.>
 | 
					    <one line to give the program's name and a brief idea of what it does.>
 | 
				
			||||||
    Copyright (C) 2021  <name of author>
 | 
					    Copyright (C) <year>  <name of author>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    This program is free software: you can redistribute it and/or modify
 | 
					    This program is free software: you can redistribute it and/or modify
 | 
				
			||||||
    it under the terms of the GNU Affero General Public License as published
 | 
					    it under the terms of the GNU General Public License as published by
 | 
				
			||||||
    by the Free Software Foundation, either version 3 of the License, or
 | 
					    the Free Software Foundation, either version 3 of the License, or
 | 
				
			||||||
    (at your option) any later version.
 | 
					    (at your option) any later version.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    This program is distributed in the hope that it will be useful,
 | 
					    This program is distributed in the hope that it will be useful,
 | 
				
			||||||
    but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					    but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
				
			||||||
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
				
			||||||
    GNU Affero General Public License for more details.
 | 
					    GNU General Public License for more details.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    You should have received a copy of the GNU Affero General Public License
 | 
					    You should have received a copy of the GNU General Public License
 | 
				
			||||||
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
					    along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Also add information on how to contact you by electronic and paper mail.
 | 
					Also add information on how to contact you by electronic and paper mail.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  If your software can interact with users remotely through a computer
 | 
					  If the program does terminal interaction, make it output a short
 | 
				
			||||||
network, you should also make sure that it provides a way for users to
 | 
					notice like this when it starts in an interactive mode:
 | 
				
			||||||
get its source.  For example, if your program is a web application, its
 | 
					
 | 
				
			||||||
interface could display a "Source" link that leads users to an archive
 | 
					    <program>  Copyright (C) <year>  <name of author>
 | 
				
			||||||
of the code.  There are many ways you could offer source, and different
 | 
					    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
 | 
				
			||||||
solutions will be better for different programs; see section 13 for the
 | 
					    This is free software, and you are welcome to redistribute it
 | 
				
			||||||
specific requirements.
 | 
					    under certain conditions; type `show c' for details.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The hypothetical commands `show w' and `show c' should show the appropriate
 | 
				
			||||||
 | 
					parts of the General Public License.  Of course, your program's commands
 | 
				
			||||||
 | 
					might be different; for a GUI interface, you would use an "about box".
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  You should also get your employer (if you work as a programmer) or school,
 | 
					  You should also get your employer (if you work as a programmer) or school,
 | 
				
			||||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
 | 
					if any, to sign a "copyright disclaimer" for the program, if necessary.
 | 
				
			||||||
For more information on this, and how to apply and follow the GNU AGPL, see
 | 
					For more information on this, and how to apply and follow the GNU GPL, see
 | 
				
			||||||
<https://www.gnu.org/licenses/>.
 | 
					<https://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  The GNU General Public License does not permit incorporating your program
 | 
				
			||||||
 | 
					into proprietary programs.  If your program is a subroutine library, you
 | 
				
			||||||
 | 
					may consider it more useful to permit linking proprietary applications with
 | 
				
			||||||
 | 
					the library.  If this is what you want to do, use the GNU Lesser General
 | 
				
			||||||
 | 
					Public License instead of this License.  But first, please read
 | 
				
			||||||
 | 
					<https://www.gnu.org/licenses/why-not-lgpl.html>.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										10455
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										10455
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										138
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										138
									
								
								package.json
									
									
									
									
									
								
							@@ -1,92 +1,50 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "@the-bds-maneger/core",
 | 
						"name": "@the-bds-maneger/core",
 | 
				
			||||||
  "version": "3.1.0",
 | 
						"version": "4.0.0",
 | 
				
			||||||
  "description": "A very simple way to manage Minecraft servers",
 | 
						"description": "A very simple way to manage Minecraft servers",
 | 
				
			||||||
  "author": "Sirherobrine23",
 | 
						"author": "Sirherobrine23",
 | 
				
			||||||
  "license": "AGPL-3.0-or-later",
 | 
						"license": "GPL-3.0",
 | 
				
			||||||
  "homepage": "https://sirherobrine23.org/Bds_Maneger_Project",
 | 
						"homepage": "https://sirherobrine23.org/BdsProject",
 | 
				
			||||||
  "private": false,
 | 
						"type": "commonjs",
 | 
				
			||||||
  "publishConfig": {
 | 
						"types": "./dist/index.d.ts",
 | 
				
			||||||
    "access": "public"
 | 
						"main": "./dist/index.js",
 | 
				
			||||||
  },
 | 
						"private": false,
 | 
				
			||||||
  "maintainers": [
 | 
						"publishConfig": {
 | 
				
			||||||
    {
 | 
							"access": "public"
 | 
				
			||||||
      "name": "Matheus Sampaio Queiroga",
 | 
						},
 | 
				
			||||||
      "email": "srherobrine20@gmail.com",
 | 
						"scripts": {
 | 
				
			||||||
      "url": "https://sirherobrine23.org"
 | 
							"dev": "nodemon",
 | 
				
			||||||
    }
 | 
							"build": "tsc",
 | 
				
			||||||
  ],
 | 
							"test": "mocha -r ts-node/register 'tests/**/*.ts'"
 | 
				
			||||||
  "types": "./dist/dts/index.d.ts",
 | 
						},
 | 
				
			||||||
  "main": "./dist/cjs/index.js",
 | 
						"repository": {
 | 
				
			||||||
  "module": "./dist/esm/index.mjs",
 | 
							"type": "git",
 | 
				
			||||||
  "exports": {
 | 
							"url": "git+https://github.com/The-Bds-Maneger/Bds-Maneger-Core.git"
 | 
				
			||||||
    "require": "./dist/cjs/index.js",
 | 
						},
 | 
				
			||||||
    "import": "./dist/esm/index.mjs"
 | 
						"keywords": [],
 | 
				
			||||||
  },
 | 
						"bugs": {
 | 
				
			||||||
  "scripts": {
 | 
							"url": "https://github.com/The-Bds-Maneger/Bds-Maneger-Core/issues/new",
 | 
				
			||||||
    "dev": "nodemon",
 | 
							"email": "support_bds@sirherobrine23.org"
 | 
				
			||||||
    "build": "run-s build:*",
 | 
						},
 | 
				
			||||||
    "test": "ts-node testProject.ts",
 | 
						"engines": {
 | 
				
			||||||
    "build:cjs": "tsc --outDir dist/cjs --module commonjs",
 | 
							"node": ">=16.0.0"
 | 
				
			||||||
    "build:esm": "tsc --outDir dist/esm --module es6 && node -e 'const fs = require(\"fs\");const path = require(\"path\");const read = (pathRe) => {for (const fileFolde of fs.readdirSync(pathRe)) {const filePath = path.join(pathRe, fileFolde);if (fs.statSync(filePath).isDirectory()) read(filePath);else {console.log(filePath, \"-->\", filePath.replace(/\\.js$/, \".mjs\"));fs.renameSync(filePath, filePath.replace(/\\.js$/, \".mjs\"));}}};read(\"dist/esm\");'"
 | 
						},
 | 
				
			||||||
  },
 | 
						"dependencies": {
 | 
				
			||||||
  "repository": {
 | 
							"@the-bds-maneger/server_versions": "^3.0.1",
 | 
				
			||||||
    "type": "git",
 | 
							"adm-zip": "^0.5.9",
 | 
				
			||||||
    "url": "git+https://github.com/The-Bds-Maneger/Bds-Maneger-Core.git"
 | 
							"axios": "^0.27.2",
 | 
				
			||||||
  },
 | 
							"cron": "^2.1.0",
 | 
				
			||||||
  "keywords": [
 | 
							"prismarine-nbt": "^2.2.1",
 | 
				
			||||||
    "typescript",
 | 
							"tar": "^6.1.11"
 | 
				
			||||||
    "bds",
 | 
						},
 | 
				
			||||||
    "bds_maneger",
 | 
						"devDependencies": {
 | 
				
			||||||
    "bds-maneger",
 | 
							"@types/adm-zip": "^0.5.0",
 | 
				
			||||||
    "bds_project",
 | 
							"@types/cron": "^2.0.0",
 | 
				
			||||||
    "minecraft",
 | 
							"@types/mocha": "^9.1.1",
 | 
				
			||||||
    "bedrock",
 | 
							"@types/node": "^18.7.14",
 | 
				
			||||||
    "java",
 | 
							"@types/tar": "^6.1.2",
 | 
				
			||||||
    "pocketmine",
 | 
							"mocha": "^10.0.0",
 | 
				
			||||||
    "spigot"
 | 
							"ts-node": "^10.9.1",
 | 
				
			||||||
  ],
 | 
							"typescript": "^4.8.2"
 | 
				
			||||||
  "bugs": {
 | 
						}
 | 
				
			||||||
    "url": "https://github.com/The-Bds-Maneger/Bds-Maneger-Core/issues/new",
 | 
					 | 
				
			||||||
    "email": "support_bds@sirherobrine23.org"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "engines": {
 | 
					 | 
				
			||||||
    "node": ">=16.0.0"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "os": [
 | 
					 | 
				
			||||||
    "linux",
 | 
					 | 
				
			||||||
    "darwin",
 | 
					 | 
				
			||||||
    "win32",
 | 
					 | 
				
			||||||
    "android"
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  "dependencies": {
 | 
					 | 
				
			||||||
    "@the-bds-maneger/server_versions": "^2.3.1",
 | 
					 | 
				
			||||||
    "adm-zip": "^0.5.9",
 | 
					 | 
				
			||||||
    "axios": "^0.27.2",
 | 
					 | 
				
			||||||
    "cron": "^2.1.0",
 | 
					 | 
				
			||||||
    "fs-extra": "^10.1.0",
 | 
					 | 
				
			||||||
    "prismarine-nbt": "^2.2.1",
 | 
					 | 
				
			||||||
    "tar": "^6.1.11"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "devDependencies": {
 | 
					 | 
				
			||||||
    "@types/adm-zip": "^0.5.0",
 | 
					 | 
				
			||||||
    "@types/cron": "^2.0.0",
 | 
					 | 
				
			||||||
    "@types/fs-extra": "^9.0.13",
 | 
					 | 
				
			||||||
    "@types/node": "^18.7.8",
 | 
					 | 
				
			||||||
    "@types/tar": "^6.1.2",
 | 
					 | 
				
			||||||
    "nodemon": "^2.0.19",
 | 
					 | 
				
			||||||
    "npm-run-all": "^4.1.5",
 | 
					 | 
				
			||||||
    "ts-node": "^10.9.1",
 | 
					 | 
				
			||||||
    "typescript": "^4.7.4"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "nodemonConfig": {
 | 
					 | 
				
			||||||
    "delay": 2500,
 | 
					 | 
				
			||||||
    "exec": "npm run test",
 | 
					 | 
				
			||||||
    "ext": "json,ts",
 | 
					 | 
				
			||||||
    "watch": [
 | 
					 | 
				
			||||||
      "src/**/*",
 | 
					 | 
				
			||||||
      "package.json",
 | 
					 | 
				
			||||||
      "package-lock.json"
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,42 +0,0 @@
 | 
				
			|||||||
import * as fs from "node:fs/promises";
 | 
					 | 
				
			||||||
import * as path from "node:path";
 | 
					 | 
				
			||||||
import Git from "./git";
 | 
					 | 
				
			||||||
import { worldStorageRoot, serverRoot } from "./pathControl";
 | 
					 | 
				
			||||||
import type { Platform } from "./globalType";
 | 
					 | 
				
			||||||
export const worldGit = new Git(worldStorageRoot, {remoteUrl: process.env.BDS_GIT_WORLDBACKUP});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
setInterval(async () => {
 | 
					 | 
				
			||||||
  if ((await worldGit.status()).length === 0) return;
 | 
					 | 
				
			||||||
  console.log("Committing world backup");
 | 
					 | 
				
			||||||
  await worldGit.addSync(".");
 | 
					 | 
				
			||||||
  await worldGit.commitSync("Automatic backup");
 | 
					 | 
				
			||||||
  await worldGit.pushSync().catch(err => console.error(err));
 | 
					 | 
				
			||||||
  return;
 | 
					 | 
				
			||||||
}, 1000*60*60*2);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function copyWorld(serverPlatform: Platform, worldName: string, worldPath: string) {
 | 
					 | 
				
			||||||
  const copyPath = path.join(serverRoot, serverPlatform, worldPath);
 | 
					 | 
				
			||||||
  const worldPathFolder = path.join(worldGit.repoRoot, serverPlatform);
 | 
					 | 
				
			||||||
  if (await fs.lstat(worldPathFolder).then(stats => stats.isDirectory()).catch(() => false)) await fs.mkdir(path.join(worldGit.repoRoot, serverPlatform));
 | 
					 | 
				
			||||||
  if (await fs.lstat(path.join(worldPathFolder, worldName)).then(stats => stats.isDirectory() && !stats.isSymbolicLink()).catch(() => false)) {
 | 
					 | 
				
			||||||
    await fs.rmdir(path.join(worldPathFolder, worldName), {recursive: true});
 | 
					 | 
				
			||||||
    await fs.cp(copyPath, path.join(worldPathFolder, worldName), {recursive: true, force: true});
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  await worldGit.addSync(path.join(worldPathFolder, worldName));
 | 
					 | 
				
			||||||
  const currentDate = new Date();
 | 
					 | 
				
			||||||
  await worldGit.commitSync(`${worldName} backup - ${currentDate.getDate()}.${currentDate.getMonth()}.${currentDate.getFullYear()}`, [`${worldName} backup - ${currentDate.toLocaleDateString()}`]);
 | 
					 | 
				
			||||||
  await worldGit.pushSync().catch(err => console.error(err));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function restoreWorld(serverPlatform: Platform, worldName: string, worldPath: string) {
 | 
					 | 
				
			||||||
  // check if world exists in repo
 | 
					 | 
				
			||||||
  const worldPathFolder = path.join(worldGit.repoRoot, serverPlatform);
 | 
					 | 
				
			||||||
  if (!await fs.lstat(path.join(worldPathFolder, worldName)).then(stats => stats.isDirectory() && !stats.isSymbolicLink()).catch(() => false)) throw new Error("World folder does not exist");
 | 
					 | 
				
			||||||
  // check if world is not link to worlds
 | 
					 | 
				
			||||||
  if (await fs.lstat(path.join(worldPathFolder, worldName)).then(stats => stats.isSymbolicLink()).catch(() => false)) throw new Error("World folder is a link, do not necessary restore");
 | 
					 | 
				
			||||||
  // rename world folder
 | 
					 | 
				
			||||||
  if (await fs.lstat(worldPath+"_backup").then(stats => stats.isDirectory()).catch(() => false)) await fs.rmdir(worldPath, {recursive: true});
 | 
					 | 
				
			||||||
  await fs.rename(worldPath, worldPath+"_backup");
 | 
					 | 
				
			||||||
  // copy world to world path
 | 
					 | 
				
			||||||
  await fs.cp(path.join(worldPathFolder, worldName), worldPath, {recursive: true, force: true});
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										98
									
								
								src/bedrock.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								src/bedrock.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,98 @@
 | 
				
			|||||||
 | 
					import * as path from "node:path";
 | 
				
			||||||
 | 
					import * as fsOld from "node:fs";
 | 
				
			||||||
 | 
					import * as fs from "node:fs/promises";
 | 
				
			||||||
 | 
					import { promisify } from "node:util";
 | 
				
			||||||
 | 
					import { getBedrockZip } from "@the-bds-maneger/server_versions";
 | 
				
			||||||
 | 
					import admZip from "adm-zip";
 | 
				
			||||||
 | 
					import { exec, execAsync } from "./childPromisses";
 | 
				
			||||||
 | 
					import { serverRoot } from "./pathControl";
 | 
				
			||||||
 | 
					import { actions, actionConfig } from "./globalPlatfroms";
 | 
				
			||||||
 | 
					export const serverPath = path.join(serverRoot, "Bedrock");
 | 
				
			||||||
 | 
					export { bedrockServerWorld, bedrockWorld, linkBedrock } from "./linkWorlds/bedrock_pocketmine";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RegExp
 | 
				
			||||||
 | 
					export const saveFf = /^(worlds|server\.properties|config|((permissions|allowlist|valid_known_packs)\.json)|(development_.*_packs))$/;
 | 
				
			||||||
 | 
					export const portListen = /\[.*\]\s+(IPv[46])\s+supported,\s+port:\s+([0-9]+)/;
 | 
				
			||||||
 | 
					export const started = /\[.*\]\s+Server\s+started\./;
 | 
				
			||||||
 | 
					// [2022-08-30 20:50:53:821 INFO] Player connected: Sirherobrine, xuid: 111111111111111
 | 
				
			||||||
 | 
					// [2022-08-30 20:56:55:231 INFO] Player disconnected: Sirherobrine, xuid: 111111111111111
 | 
				
			||||||
 | 
					export const player = /\[.*\]\s+Player\s+((dis|)connected):\s+(.*),\s+xuid:\s+([0-9]+)/;
 | 
				
			||||||
 | 
					// [2022-08-30 20:56:55:601 INFO] Running AutoCompaction...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function installServer(version: string|boolean) {
 | 
				
			||||||
 | 
					  let arch = process.arch;
 | 
				
			||||||
 | 
					  if (process.platform === "linux" && process.arch !== "x64") {
 | 
				
			||||||
 | 
					    if (await execAsync("command -v qemu-x86_64-static").then(() => true).catch(() => false)||await execAsync("command -v box64").then(() => true).catch(() => false)) arch = "x64";
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  const zip = new admZip(await getBedrockZip(version, arch));
 | 
				
			||||||
 | 
					  if (!fsOld.existsSync(serverPath)) await fs.mkdir(serverPath, {recursive: true});
 | 
				
			||||||
 | 
					  // Remover files
 | 
				
			||||||
 | 
					  for (const file of await fs.readdir(serverPath).then(files => files.filter(file => !saveFf.test(file)))) await fs.rm(path.join(serverPath, file), {recursive: true, force: true});
 | 
				
			||||||
 | 
					  const serverConfig = (await fs.readFile(path.join(serverPath, "server.properties"), "utf8").catch(() => "")).trim();
 | 
				
			||||||
 | 
					  await promisify(zip.extractAllToAsync)(serverPath, true, true);
 | 
				
			||||||
 | 
					  if (serverConfig) await fs.writeFile(path.join(serverPath, "server.properties"), serverConfig);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const serverConfig: actionConfig[] = [
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    name: "portListening",
 | 
				
			||||||
 | 
					    callback(data, done) {
 | 
				
			||||||
 | 
					      const match = data.match(portListen);
 | 
				
			||||||
 | 
					      if (!match) return;
 | 
				
			||||||
 | 
					      const [, protocol, port] = match;
 | 
				
			||||||
 | 
					      done({port: parseInt(port), type: "UDP", host: "127.0.0.1", protocol: protocol?.trim() === "IPv4" ? "IPv4" : protocol?.trim() === "IPv6" ? "IPv6" : "Unknown"});
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    name: "serverStarted",
 | 
				
			||||||
 | 
					    callback(data, done) {
 | 
				
			||||||
 | 
					      const resulter = data.match(started);
 | 
				
			||||||
 | 
					      if (resulter) done(new Date());
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    name: "playerConnect",
 | 
				
			||||||
 | 
					    callback(data, done) {
 | 
				
			||||||
 | 
					      const match = data.match(player);
 | 
				
			||||||
 | 
					      if (!match) return;
 | 
				
			||||||
 | 
					      const [, action,, playerName, xuid] = match;
 | 
				
			||||||
 | 
					      if (action === "connect") done({connectTime: new Date(), playerName: playerName, xuid});
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    name: "playerDisconnect",
 | 
				
			||||||
 | 
					    callback(data, done) {
 | 
				
			||||||
 | 
					      const match = data.match(player);
 | 
				
			||||||
 | 
					      if (!match) return;
 | 
				
			||||||
 | 
					      const [, action,, playerName, xuid] = match;
 | 
				
			||||||
 | 
					      if (action === "disconnect") done({connectTime: new Date(), playerName: playerName, xuid});
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    name: "playerUnknown",
 | 
				
			||||||
 | 
					    callback(data, done) {
 | 
				
			||||||
 | 
					      const match = data.match(player);
 | 
				
			||||||
 | 
					      if (!match) return;
 | 
				
			||||||
 | 
					      const [, action,, playerName, xuid] = match;
 | 
				
			||||||
 | 
					      if (!(action === "disconnect" || action === "connect")) done({connectTime: new Date(), playerName: playerName, xuid});
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    name: "serverStop",
 | 
				
			||||||
 | 
					    run: (child) => child.writeStdin("stop")
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function startServer() {
 | 
				
			||||||
 | 
					  if (!fsOld.existsSync(serverPath)) throw new Error("Install server fist");
 | 
				
			||||||
 | 
					  const args: string[] = [];
 | 
				
			||||||
 | 
					  let command = path.join(serverPath, "bedrock_server");
 | 
				
			||||||
 | 
					  if (process.platform === "linux" && process.arch !== "x64") {
 | 
				
			||||||
 | 
					    args.push(command);
 | 
				
			||||||
 | 
					    if (await execAsync("command -v qemu-x86_64-static").then(() => true).catch(() => false)) command = "qemu-x86_64-static";
 | 
				
			||||||
 | 
					    else if (await execAsync("command -v box64").then(() => true).catch(() => false)) command = "box64";
 | 
				
			||||||
 | 
					    else throw new Error("Cannot emulate x64 architecture. Check the documentents in \"https://github.com/The-Bds-Maneger/Bds-Maneger-Core/wiki/Server-Platforms#minecraft-bedrock-server-alpha\"");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return new actions(exec(command, args, {cwd: serverPath, maxBuffer: Infinity, env: {LD_LIBRARY_PATH: process.platform === "win32"?undefined:serverPath}}), serverConfig);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,586 +0,0 @@
 | 
				
			|||||||
/**
 | 
					 | 
				
			||||||
 * Original file url: https://github.com/chegele/BDSAddonInstaller/blob/6e9cf7334022941f8007c28470eb1e047dfe0e90/index.js
 | 
					 | 
				
			||||||
 * License: No license provided.
 | 
					 | 
				
			||||||
 * Github Repo: https://github.com/chegele/BDSAddonInstaller
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * Patch by Sirherorine23 (Matheus Sampaio Queirora) <srherobrine20@gmail.com>
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
import path from "node:path";
 | 
					 | 
				
			||||||
import admZip from "adm-zip";
 | 
					 | 
				
			||||||
import fs from "node:fs";
 | 
					 | 
				
			||||||
import { serverRoot } from "../pathControl";
 | 
					 | 
				
			||||||
// import stripJsonComments from "strip-json-comments";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const stripJsonComments = (data: string) => data.replace(/\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g, (m, g) => g ? "" : m);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function ensureFileSync(pathFile: string){
 | 
					 | 
				
			||||||
  if (!fs.existsSync(pathFile)){
 | 
					 | 
				
			||||||
  if (!fs.existsSync(path.parse(pathFile).dir)) fs.mkdirSync(path.parse(pathFile).dir, {recursive: true});
 | 
					 | 
				
			||||||
    fs.writeFileSync(pathFile, "");
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Below variables are updated by the constructor.
 | 
					 | 
				
			||||||
// All paths will be converted to full paths by including serverPath at the beginning.
 | 
					 | 
				
			||||||
let serverPath = null;
 | 
					 | 
				
			||||||
let worldName = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const providedServerPath = path.join(serverRoot, "bedrock");
 | 
					 | 
				
			||||||
const addonPath = path.resolve(providedServerPath, "../BDS-Addons/");
 | 
					 | 
				
			||||||
if (!(fs.existsSync(addonPath))) fs.mkdirSync(addonPath, {recursive: true});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
let serverPacksJsonPath = "valid_known_packs.json";
 | 
					 | 
				
			||||||
let serverPacksJSON = null;
 | 
					 | 
				
			||||||
let serverResourcesDir = "resource_packs/";
 | 
					 | 
				
			||||||
let serverBehaviorsDir = "behavior_packs/";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
let worldResourcesJsonPath = "worlds/<worldname>/world_resource_packs.json";
 | 
					 | 
				
			||||||
let worldResourcesJSON = null;
 | 
					 | 
				
			||||||
let worldBehaviorsJsonPath = "worlds/<worldname>/world_behavior_packs.json";
 | 
					 | 
				
			||||||
let worldBehaviorsJSON = null;
 | 
					 | 
				
			||||||
let worldResourcesDir = "worlds/<worldname>/resource_packs/";
 | 
					 | 
				
			||||||
let worldBehaviorsDir = "worlds/<worldname>/behavior_packs/";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Below variables updated by mapInstalledPacks function.
 | 
					 | 
				
			||||||
// Updated to contain installed pack info {name, uuid, version, location}
 | 
					 | 
				
			||||||
let installedServerResources = new Map();
 | 
					 | 
				
			||||||
let installedServerBehaviors = new Map();
 | 
					 | 
				
			||||||
let installedWorldResources = new Map();
 | 
					 | 
				
			||||||
let installedWorldBehaviors = new Map();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Prepares to install addons for the provided Bedrock Dedicated Server.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export function addonInstaller() {
 | 
					 | 
				
			||||||
  // Update all module paths from relative to full paths.
 | 
					 | 
				
			||||||
  serverPath = providedServerPath;
 | 
					 | 
				
			||||||
  // addonPath = path.join(providedServerPath, addonPath);
 | 
					 | 
				
			||||||
  worldName = readWorldName();
 | 
					 | 
				
			||||||
  worldResourcesJsonPath = path.join(serverPath, worldResourcesJsonPath.replace("<worldname>", worldName));
 | 
					 | 
				
			||||||
  worldBehaviorsJsonPath = path.join(serverPath, worldBehaviorsJsonPath.replace("<worldname>", worldName));
 | 
					 | 
				
			||||||
  worldResourcesDir = path.join(serverPath, worldResourcesDir.replace("<worldname>", worldName));
 | 
					 | 
				
			||||||
  worldBehaviorsDir = path.join(serverPath, worldBehaviorsDir.replace("<worldname>", worldName));
 | 
					 | 
				
			||||||
  serverPacksJsonPath = path.join(serverPath, serverPacksJsonPath);
 | 
					 | 
				
			||||||
  serverResourcesDir = path.join(serverPath, serverResourcesDir);
 | 
					 | 
				
			||||||
  serverBehaviorsDir = path.join(serverPath, serverBehaviorsDir);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Create JSON files if they do not exists
 | 
					 | 
				
			||||||
  ensureFileSync(serverPacksJsonPath);
 | 
					 | 
				
			||||||
  ensureFileSync(worldResourcesJsonPath);
 | 
					 | 
				
			||||||
  ensureFileSync(worldBehaviorsJsonPath);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Read installed packs from JSON files & attempt to parse content.
 | 
					 | 
				
			||||||
  let serverPackContents = fs.readFileSync(serverPacksJsonPath, "utf8");
 | 
					 | 
				
			||||||
  let worldResourceContents = fs.readFileSync(worldResourcesJsonPath, "utf8");
 | 
					 | 
				
			||||||
  let worldBehaviorContents = fs.readFileSync(worldBehaviorsJsonPath, "utf8");
 | 
					 | 
				
			||||||
  // If there is an error parsing JSON assume no packs installed and use empty array.
 | 
					 | 
				
			||||||
  try { serverPacksJSON = JSON.parse(serverPackContents) } catch(err) { serverPacksJSON = [] };
 | 
					 | 
				
			||||||
  try { worldResourcesJSON = JSON.parse(worldResourceContents) } catch(err) { worldResourcesJSON = [] };
 | 
					 | 
				
			||||||
  try { worldBehaviorsJSON = JSON.parse(worldBehaviorContents) } catch(err) { worldBehaviorsJSON = [] };
 | 
					 | 
				
			||||||
  // If unexpected results from parsing JSON assume no packs installed and use empty array.
 | 
					 | 
				
			||||||
  if (!Array.isArray(serverPacksJSON)) serverPacksJSON = [];
 | 
					 | 
				
			||||||
  if (!Array.isArray(worldResourcesJSON)) worldResourcesJSON = [];
 | 
					 | 
				
			||||||
  if (!Array.isArray(worldBehaviorsJSON)) worldBehaviorsJSON = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Map installed packs from install directories
 | 
					 | 
				
			||||||
  installedServerResources = mapInstalledPacks(serverResourcesDir);
 | 
					 | 
				
			||||||
  installedServerBehaviors = mapInstalledPacks(serverBehaviorsDir);
 | 
					 | 
				
			||||||
  installedWorldResources = mapInstalledPacks(worldResourcesDir);
 | 
					 | 
				
			||||||
  installedWorldBehaviors = mapInstalledPacks(worldBehaviorsDir);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * Installs the provide addon/pack to the BDS server and the active world.
 | 
					 | 
				
			||||||
   * @param {String} packPath - The full path to the mcpack or mcaddon file.
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
  async function installAddon(packPath: string) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Validate provided pack (pack exists & is the correct file type)
 | 
					 | 
				
			||||||
    if (!fs.existsSync(packPath)) throw new Error("Unable to install pack. The provided path does not exist. " + packPath);
 | 
					 | 
				
			||||||
    if (!packPath.endsWith(".mcpack") && !packPath.endsWith(".mcaddon")) throw new Error("Unable to install pack. The provided file is not an addon or pack. " + packPath);
 | 
					 | 
				
			||||||
    if (packPath.endsWith(".mcaddon")) {
 | 
					 | 
				
			||||||
      // If the provided pack is an addon extract packs and execute this function again for each one.
 | 
					 | 
				
			||||||
      let packs = await extractAddonPacks(packPath);
 | 
					 | 
				
			||||||
      for (const pack of packs) await this.installAddon(pack);
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Gather pack details from the manifest.json file
 | 
					 | 
				
			||||||
    let manifest = await extractPackManifest(packPath);
 | 
					 | 
				
			||||||
    // let name = manifest.header.name.replace(/\W/g, "");
 | 
					 | 
				
			||||||
    let uuid = manifest.header.uuid;
 | 
					 | 
				
			||||||
    let version = manifest.header.version;
 | 
					 | 
				
			||||||
    if (!version) version = manifest.header.modules[0].version;
 | 
					 | 
				
			||||||
    let type: string;
 | 
					 | 
				
			||||||
    if (manifest.modules) {
 | 
					 | 
				
			||||||
      type = manifest.modules[0].type.toLowerCase();
 | 
					 | 
				
			||||||
    } else if (manifest.header.modules) {
 | 
					 | 
				
			||||||
      type = manifest.header.modules[0].type.toLowerCase();
 | 
					 | 
				
			||||||
    }else {
 | 
					 | 
				
			||||||
      throw new Error("Unable to install pack. Unknown pack manifest format.\n" + packPath);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // console.log("BDSAddonInstaller - Installing " + name + "...");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Check if already installed
 | 
					 | 
				
			||||||
    let installedWorldPack: any;
 | 
					 | 
				
			||||||
    let installedServerPack: any = null;
 | 
					 | 
				
			||||||
    if (type == "resources") {
 | 
					 | 
				
			||||||
      installedWorldPack = installedWorldResources.get(uuid);
 | 
					 | 
				
			||||||
      installedServerPack = installedServerResources.get(uuid);
 | 
					 | 
				
			||||||
    }else if (type == "data") {
 | 
					 | 
				
			||||||
      installedWorldPack = installedWorldBehaviors.get(uuid);
 | 
					 | 
				
			||||||
      installedServerPack = installedServerBehaviors.get(uuid)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Check if current installed packs are up to date
 | 
					 | 
				
			||||||
    if (installedWorldPack || installedServerPack) {
 | 
					 | 
				
			||||||
      let upToDate = true;
 | 
					 | 
				
			||||||
      if (installedWorldPack && installedWorldPack.version.toString() != version.toString()) upToDate = false;
 | 
					 | 
				
			||||||
      if (installedServerPack && installedServerPack.version.toString() != version.toString()) upToDate = false;
 | 
					 | 
				
			||||||
      if (upToDate) {
 | 
					 | 
				
			||||||
        // console.log(`BDSAddonInstaller - The ${name} pack is already installed and up to date.`);
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
      }else{
 | 
					 | 
				
			||||||
        // uninstall pack if not up to date
 | 
					 | 
				
			||||||
        // console.log("BDSAddonInstaller - Uninstalling old version of pack");
 | 
					 | 
				
			||||||
        if (installedServerPack) await uninstallServerPack(uuid, installedServerPack.location);
 | 
					 | 
				
			||||||
        if (installedWorldPack && type == "resources") await uninstallWorldResource(uuid, installedWorldPack.location);
 | 
					 | 
				
			||||||
        if (installedWorldPack && type == "data") await uninstallWorldBehavior(uuid, installedWorldPack.location);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await installPack(packPath, manifest);
 | 
					 | 
				
			||||||
    // console.log("BDSAddonInstaller - Successfully installed the " + name + " pack.");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * Installs all of the addons & packs found within the BDS-Addons directory.
 | 
					 | 
				
			||||||
   * NOTE: Running this function with remove packs is only recommended if facing issues.
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
  async function installAllAddons(removeOldPacks: boolean) {
 | 
					 | 
				
			||||||
    // If chosen, uninstall all world packs.
 | 
					 | 
				
			||||||
    if (removeOldPacks) await uninstallAllWorldPacks();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Read all packs & addons from BDS-Addon directory.
 | 
					 | 
				
			||||||
    let packs = fs.readdirSync(addonPath);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Get the full path of each addon/pack and install it.
 | 
					 | 
				
			||||||
    for (let pack of packs) {
 | 
					 | 
				
			||||||
      try {
 | 
					 | 
				
			||||||
        let location = path.join(addonPath, pack);
 | 
					 | 
				
			||||||
        await this.installAddon(location);
 | 
					 | 
				
			||||||
      }catch(err) {
 | 
					 | 
				
			||||||
        // console.error("BDSAddonInstaller - " + err);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    installAddon,
 | 
					 | 
				
			||||||
    installAllAddons
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
////////////////////////////////////////////////////////////////
 | 
					 | 
				
			||||||
// BDSAddonInstaller - Install & Uninstall functions
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Installs the provided pack to the world and Bedrock Dedicated Server.
 | 
					 | 
				
			||||||
 * @param packPath - The path to the pack to be installed.
 | 
					 | 
				
			||||||
 * @param manifest - The pre-parsed manifest information for the pack.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
async function installPack(packPath: string, manifest: {[d: string]: any}) {
 | 
					 | 
				
			||||||
  // Extract manifest information
 | 
					 | 
				
			||||||
  let name = manifest.header.name.replace(/\W/g, "");
 | 
					 | 
				
			||||||
  let uuid = manifest.header.uuid;
 | 
					 | 
				
			||||||
  let version = manifest.header.version;
 | 
					 | 
				
			||||||
  if (!version) version = manifest.header.modules[0].version;
 | 
					 | 
				
			||||||
  let type: string;
 | 
					 | 
				
			||||||
  if (manifest.modules) {
 | 
					 | 
				
			||||||
    type = manifest.modules[0].type.toLowerCase();
 | 
					 | 
				
			||||||
  } else if (manifest.header.modules) {
 | 
					 | 
				
			||||||
    type = manifest.header.modules[0].type.toLowerCase();
 | 
					 | 
				
			||||||
  }else {
 | 
					 | 
				
			||||||
    throw new Error("Unable to install pack. Unknown pack manifest format.\n" + packPath);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Create placeholder variables for pack installation paths.
 | 
					 | 
				
			||||||
  let installServerPath: string
 | 
					 | 
				
			||||||
  let installWorldPath: string
 | 
					 | 
				
			||||||
  let WorldPacksJSON: any
 | 
					 | 
				
			||||||
  let WorldPacksPath: string
 | 
					 | 
				
			||||||
  let rawPath: string|null = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Update variables based on the pack type.
 | 
					 | 
				
			||||||
  if (type == "data") {
 | 
					 | 
				
			||||||
    installServerPath = path.join(serverBehaviorsDir, name);
 | 
					 | 
				
			||||||
    installWorldPath = path.join(worldBehaviorsDir, name);
 | 
					 | 
				
			||||||
    WorldPacksJSON = worldBehaviorsJSON;
 | 
					 | 
				
			||||||
    WorldPacksPath = worldBehaviorsJsonPath;
 | 
					 | 
				
			||||||
    rawPath = "behavior_packs/" + name;
 | 
					 | 
				
			||||||
  }else if (type == "resources") {
 | 
					 | 
				
			||||||
    installServerPath = path.join(serverResourcesDir, name);
 | 
					 | 
				
			||||||
    installWorldPath = path.join(worldResourcesDir, name);
 | 
					 | 
				
			||||||
    WorldPacksJSON = worldResourcesJSON;
 | 
					 | 
				
			||||||
    WorldPacksPath = worldResourcesJsonPath;
 | 
					 | 
				
			||||||
    rawPath = "resource_packs/" + name;
 | 
					 | 
				
			||||||
  }else {
 | 
					 | 
				
			||||||
    throw new Error("Unknown pack type, " + type);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Install pack to the world.
 | 
					 | 
				
			||||||
  let worldPackInfo = {"pack_id": uuid, "version": version}
 | 
					 | 
				
			||||||
  WorldPacksJSON.unshift(worldPackInfo);
 | 
					 | 
				
			||||||
  await promiseExtract(packPath, installWorldPath);
 | 
					 | 
				
			||||||
  fs.writeFileSync(WorldPacksPath, JSON.stringify(WorldPacksJSON, undefined, 2));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Install pack to the server.
 | 
					 | 
				
			||||||
  version = `${version[0]}.${version[1]}.${version[2]}`;
 | 
					 | 
				
			||||||
  let serverPackInfo = {"file_system": "RawPath", "node:path": rawPath, "uuid": uuid, "version": version};
 | 
					 | 
				
			||||||
  serverPacksJSON.splice(1, 0, serverPackInfo);
 | 
					 | 
				
			||||||
  await promiseExtract(packPath, installServerPath);
 | 
					 | 
				
			||||||
  fs.writeFileSync(serverPacksJsonPath, JSON.stringify(serverPacksJSON, undefined, 2));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Uninstall all resource and behavior packs from the Minecraft world.
 | 
					 | 
				
			||||||
 * If the server also has the pick it will also be uninstalled.
 | 
					 | 
				
			||||||
 * NOTE: Vanilla packs can"t be safely removed from the server packs & there is no way to differentiate vanilla and added packs.
 | 
					 | 
				
			||||||
 * NOTE: This is why only packs found installed to the world will be removed from the server.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
async function uninstallAllWorldPacks() {
 | 
					 | 
				
			||||||
  // console.log("BDSAddonInstaller - Uninstalling all packs found saved to world.");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Uninstall all cached world resource packs.
 | 
					 | 
				
			||||||
  for (let pack of installedWorldResources.values()) {
 | 
					 | 
				
			||||||
    await uninstallWorldResource(pack.uuid, pack.location);
 | 
					 | 
				
			||||||
    let serverPack = installedServerResources.get(pack.uuid);
 | 
					 | 
				
			||||||
    if (serverPack) await uninstallServerPack(pack.uuid, serverPack.location);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Uninstall all cached world behavior packs.
 | 
					 | 
				
			||||||
  for (let pack of installedWorldBehaviors.values()) {
 | 
					 | 
				
			||||||
    await uninstallWorldBehavior(pack.uuid, pack.location);
 | 
					 | 
				
			||||||
    let serverPack = installedServerBehaviors.get(pack.uuid);
 | 
					 | 
				
			||||||
    if (serverPack) await uninstallServerPack(pack.uuid, serverPack.location);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // All packs are cached by the constructor.
 | 
					 | 
				
			||||||
  // Reload world packs after uninstall.
 | 
					 | 
				
			||||||
  installedServerResources = mapInstalledPacks(serverResourcesDir);
 | 
					 | 
				
			||||||
  installedServerBehaviors = mapInstalledPacks(serverBehaviorsDir);
 | 
					 | 
				
			||||||
  installedWorldResources = mapInstalledPacks(worldResourcesDir);
 | 
					 | 
				
			||||||
  installedWorldBehaviors = mapInstalledPacks(worldBehaviorsDir);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// TODO: uninstallWorldResource, uninstallWorldBehavior, and uninstallServerPack share the same logic.
 | 
					 | 
				
			||||||
// These functions can be merged into one function using an additional argument for pack type.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Uninstalls the pack from the world_resource_packs.json by uuid & deletes the provided pack path.
 | 
					 | 
				
			||||||
 * @param uuid - The id of the pack to remove from the world_resource_packs.json file.
 | 
					 | 
				
			||||||
 * @param location - The path to the root directory of the installed pack to be deleted.
 | 
					 | 
				
			||||||
 * WARNING: No validation is done to confirm that the provided path is a pack.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
async function uninstallWorldResource(uuid: string, location: string) {
 | 
					 | 
				
			||||||
  // Locate the pack in the manifest data.
 | 
					 | 
				
			||||||
  let packIndex = findIndexOf(worldResourcesJSON, "pack_id", uuid);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Remove the pack data and update the json file.
 | 
					 | 
				
			||||||
  if (packIndex != -1) {
 | 
					 | 
				
			||||||
    worldResourcesJSON.splice(packIndex, 1);
 | 
					 | 
				
			||||||
    fs.writeFileSync(worldResourcesJsonPath, JSON.stringify(worldResourcesJSON, undefined, 2));
 | 
					 | 
				
			||||||
    // console.log(`BDSAddonInstaller - Removed ${uuid} from world resource packs JSON.`);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Delete the provided pack path.
 | 
					 | 
				
			||||||
  if (fs.existsSync(location)) {
 | 
					 | 
				
			||||||
    await fs.promises.rm(location, {recursive: true});
 | 
					 | 
				
			||||||
    // console.log(`BDSAddonInstaller - Removed ${location}`);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Uninstalls the pack from the world_behavior_packs.json by uuid & deletes the provided pack path.
 | 
					 | 
				
			||||||
 * @param uuid - The id of the pack to remove from the world_behavior_packs.json file.
 | 
					 | 
				
			||||||
 * @param location - The path to the root directory of the installed pack to be deleted.
 | 
					 | 
				
			||||||
 * WARNING: No validation is done to confirm that the provided path is a pack.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
async function uninstallWorldBehavior(uuid: string, location: string) {
 | 
					 | 
				
			||||||
  // Locate the pack in the manifest data.
 | 
					 | 
				
			||||||
  let packIndex = findIndexOf(worldBehaviorsJSON, "pack_id", uuid);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Remove the pack data and update the json file.
 | 
					 | 
				
			||||||
  if (packIndex != -1) {
 | 
					 | 
				
			||||||
    worldBehaviorsJSON.splice(packIndex, 1);
 | 
					 | 
				
			||||||
    fs.writeFileSync(worldBehaviorsJsonPath, JSON.stringify(worldBehaviorsJSON, undefined, 2));
 | 
					 | 
				
			||||||
    // console.log(`BDSAddonInstaller - Removed ${uuid} from world behavior packs JSON.`);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Delete the provided pack path.
 | 
					 | 
				
			||||||
  if (fs.existsSync(location)) {
 | 
					 | 
				
			||||||
    fs.promises.rm(location);
 | 
					 | 
				
			||||||
    // console.log(`BDSAddonInstaller - Removed ${location}`);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Uninstalls the pack from the valid_known_packs.json by uuid & deletes the provided pack path.
 | 
					 | 
				
			||||||
 * @param uuid - The id of the pack to remove from the valid_known_packs.json file.
 | 
					 | 
				
			||||||
 * @param location - The path to the root directory of the installed pack to be deleted.
 | 
					 | 
				
			||||||
 * WARNING: No validation is done to confirm that the provided path is a pack.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
async function uninstallServerPack (uuid: string, location: string) {
 | 
					 | 
				
			||||||
  // Locate the pack in the manifest data.
 | 
					 | 
				
			||||||
  let packIndex = findIndexOf(serverPacksJSON, "uuid", uuid);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Remove the pack data and update the json file.
 | 
					 | 
				
			||||||
  if (packIndex != -1) {
 | 
					 | 
				
			||||||
    serverPacksJSON.splice(packIndex, 1);
 | 
					 | 
				
			||||||
    fs.writeFileSync(serverPacksJsonPath, JSON.stringify(serverPacksJSON, undefined, 2));
 | 
					 | 
				
			||||||
    // console.log(`BDSAddonInstaller - Removed ${uuid} from server packs JSON.`);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Delete the provided pack path.
 | 
					 | 
				
			||||||
  if (fs.existsSync(location)) {
 | 
					 | 
				
			||||||
    fs.promises.rm(location);
 | 
					 | 
				
			||||||
    // console.log(`BDSAddonInstaller - Removed ${location}`);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
///////////////////////////////////////////////////////////
 | 
					 | 
				
			||||||
// BDSAddonInstaller misc functions
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Extracts bundled packs from the provided addon file.
 | 
					 | 
				
			||||||
 * This will only need to be ran once on an addon as it will convert the addon to multiple .mcpack files.
 | 
					 | 
				
			||||||
 * @param addonPath - The path of the addon file to extract packs from.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
async function extractAddonPacks(addonPath: string) {
 | 
					 | 
				
			||||||
  // Validate the provided path is to an addon.
 | 
					 | 
				
			||||||
  if (!fs.existsSync(addonPath)) throw new Error("Unable to extract packs from addon. Invalid file path provided: " + addonPath);
 | 
					 | 
				
			||||||
  if (!addonPath.endsWith('.mcaddon')) throw new Error('Unable to extract packs from addon. The provided file is not an addon. ' + addonPath);
 | 
					 | 
				
			||||||
  // console.log("BDSAddonInstaller - Extracting packs from " + addonPath);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Extract file path and name info for saving the extracted packs.
 | 
					 | 
				
			||||||
  let addonName = path.basename(addonPath).replace(".mcaddon", "");
 | 
					 | 
				
			||||||
  let dirPath = path.dirname(addonPath);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Create a temp location and extract the addon contents to it.
 | 
					 | 
				
			||||||
  let tempLocation = path.join(dirPath, "tmp/", addonName + "/");
 | 
					 | 
				
			||||||
  await promiseExtract(addonPath, tempLocation);
 | 
					 | 
				
			||||||
  let packs = fs.readdirSync(tempLocation);
 | 
					 | 
				
			||||||
  let results = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Move addon packs from temporary location to BDS-Addon directory.
 | 
					 | 
				
			||||||
  for (let pack of packs) {
 | 
					 | 
				
			||||||
    // console.log(`BDSAddonInstaller - Extracting ${pack} from ${addonName}.`);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // If the mcpack is already packaged, move the file.
 | 
					 | 
				
			||||||
    if (pack.endsWith(".mcpack")) {
 | 
					 | 
				
			||||||
      let packName = addonName + "_" + pack;
 | 
					 | 
				
			||||||
      let packFile = path.join(tempLocation, pack);
 | 
					 | 
				
			||||||
      let packDestination = path.join(dirPath, packName);
 | 
					 | 
				
			||||||
      await fs.promises.rename(packFile, packDestination);
 | 
					 | 
				
			||||||
      results.push(packDestination);
 | 
					 | 
				
			||||||
      // console.log("BDSAddonInstaller - Extracted " + packDestination);
 | 
					 | 
				
			||||||
    }else {
 | 
					 | 
				
			||||||
      // The pack still needs to be zipped and then moved.
 | 
					 | 
				
			||||||
      let packName = addonName + "_" + pack + ".mcpack";
 | 
					 | 
				
			||||||
      let packFolder = path.join(tempLocation, pack);
 | 
					 | 
				
			||||||
      let packDestination = path.join(dirPath, packName);
 | 
					 | 
				
			||||||
      await promiseZip(packFolder, packDestination);
 | 
					 | 
				
			||||||
      results.push(packDestination);
 | 
					 | 
				
			||||||
      // console.log("BDSAddonInstaller - Extracted " + packDestination);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Remove temporary files and old addon.
 | 
					 | 
				
			||||||
  await fs.promises.rm(path.join(dirPath, "tmp/"), {recursive: true});
 | 
					 | 
				
			||||||
  await fs.promises.unlink(addonPath);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Return an array of paths to the extracted packs.
 | 
					 | 
				
			||||||
  return results;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Extracts the manifest data as an object from the provided .mcpack file.
 | 
					 | 
				
			||||||
 * @param packPath - The path to the pack to extract the manifest from.
 | 
					 | 
				
			||||||
 * @returns The parsed manifest.json file.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
function extractPackManifest(packPath: string): {[key: string]: any} {
 | 
					 | 
				
			||||||
  // Validate the provided pack (path exists and file is correct type)
 | 
					 | 
				
			||||||
  if (!fs.existsSync(packPath)) throw new Error("Unable to extract manifest file. Invalid file path provided: " + packPath);
 | 
					 | 
				
			||||||
  if (!packPath.endsWith(".mcpack")) throw new Error("Unable to extract manifest file. The provided file is not a pack. " + packPath);
 | 
					 | 
				
			||||||
  // console.log("BDSAddonInstaller - Reading manifest data from " + packPath);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Locate the manifest file in the zipped pack.
 | 
					 | 
				
			||||||
  let archive = new admZip(packPath);
 | 
					 | 
				
			||||||
  let manifest = archive.getEntries().filter(entry => entry.entryName.endsWith("manifest.json") || entry.entryName.endsWith("pack_manifest.json"));
 | 
					 | 
				
			||||||
  if (!manifest[0]) throw new Error("Unable to extract manifest file. It does not exist in this pack. " + packPath);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Read the manifest and return the parsed JSON.
 | 
					 | 
				
			||||||
  return JSON.parse(stripJsonComments(archive.readAsText(manifest[0].entryName)));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Reads the world name from a BDS server.properties file.
 | 
					 | 
				
			||||||
 * @returns The value found for level-name from server.properties.
 | 
					 | 
				
			||||||
 * NOTE: This function is Synchronous for use in the constructor without need for a callback.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
function readWorldName(): string {
 | 
					 | 
				
			||||||
  let propertyFile = path.join(serverPath, "server.properties");
 | 
					 | 
				
			||||||
  // console.log("BDSAddonInstaller - Reading world name from " + propertyFile);
 | 
					 | 
				
			||||||
  if (!fs.existsSync(propertyFile)) throw new Error("Unable to locate server properties @ " + propertyFile);
 | 
					 | 
				
			||||||
  let properties = fs.readFileSync(propertyFile);
 | 
					 | 
				
			||||||
  let levelName = properties.toString().match(/level-name=.*/);
 | 
					 | 
				
			||||||
  if (!levelName) throw new Error("Unable to retrieve level-name from server properties.");
 | 
					 | 
				
			||||||
  return levelName.toString().replace("level-name=", "");
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Collects manifest information from all installed packs in provided location.
 | 
					 | 
				
			||||||
 * @param directory - The path to the directory containing extracted/installed packs.
 | 
					 | 
				
			||||||
 * @returns A collection of manifest information with the uuid as the key.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * Bug Note:
 | 
					 | 
				
			||||||
 * Some of the vanilla packs are installed multiple times using the same uuid but different versions.
 | 
					 | 
				
			||||||
 * This causes the map to only capture the last read pack with that uuid.
 | 
					 | 
				
			||||||
 * This bug should not impact the installer, as there wont be a need to install / update vanilla packs.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * NOTE: This function is Synchronous for use in the constructor without need for a callback.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function mapInstalledPacks(directory: string): Map<{}, any> {
 | 
					 | 
				
			||||||
  // The provided directory may not exist if the world has no packs installed.
 | 
					 | 
				
			||||||
  // Create the results Map & return empty if the directory does not exist.
 | 
					 | 
				
			||||||
  let results = new Map();
 | 
					 | 
				
			||||||
  if (!fs.existsSync(directory)) return results;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Extract manifest & path information for each installed pack
 | 
					 | 
				
			||||||
  let subdirectories = fs.readdirSync(directory);
 | 
					 | 
				
			||||||
  subdirectories.forEach(subdirectory => {
 | 
					 | 
				
			||||||
    let location = path.join(directory, subdirectory);
 | 
					 | 
				
			||||||
    // console.log("BDSAddonInstaller - Reading manifest data from " + location);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Locate the directory containing the pack manifest.
 | 
					 | 
				
			||||||
    let manifestLocation = findFilesSync(["manifest.json", "pack_manifest.json"], location);
 | 
					 | 
				
			||||||
    if (!manifestLocation) {
 | 
					 | 
				
			||||||
      // console.error(manifestLocation);
 | 
					 | 
				
			||||||
      // console.warn("BDSAddonInstaller - Unable to locate manifest file of installed pack.");
 | 
					 | 
				
			||||||
      // console.warn("BDSAddonInstaller - Installed location: " + location);
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Check if pack is using a manifest.json or pack.manifest.json
 | 
					 | 
				
			||||||
    let filePath = path.join(manifestLocation, "manifest.json");
 | 
					 | 
				
			||||||
    if (!fs.existsSync(filePath)) filePath = path.join(manifestLocation, "pack_manifest.json");
 | 
					 | 
				
			||||||
    let file = fs.readFileSync(filePath, "utf8");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Some vanilla packs have comments in them, this is not valid JSON and needs to be removed.
 | 
					 | 
				
			||||||
    file = stripJsonComments(file.toString());
 | 
					 | 
				
			||||||
    let manifest = JSON.parse(file);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Collect and map the manifest information
 | 
					 | 
				
			||||||
    let uuid = manifest.header.uuid;
 | 
					 | 
				
			||||||
    let name = manifest.header.name;
 | 
					 | 
				
			||||||
    let version = manifest.header.version;
 | 
					 | 
				
			||||||
    if (!version) version = manifest.header.modules[0].version;
 | 
					 | 
				
			||||||
    results.set(uuid, {name, uuid, version, location});
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
  return results;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
////////////////////////////////////////////////////////////////////
 | 
					 | 
				
			||||||
// Misc helper functions
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Finds the first index of a key value pair from an array of objects.
 | 
					 | 
				
			||||||
 * @param objectArray - An array of objects to search.
 | 
					 | 
				
			||||||
 * @param key - The key to match the value against.
 | 
					 | 
				
			||||||
 * @param value - The value to find the index of.
 | 
					 | 
				
			||||||
 * @returns - The index of the key value pair or -1.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
function findIndexOf(objectArray: Array<{[d: string]: any}>, key: string, value: any): number {
 | 
					 | 
				
			||||||
  for (let index = 0; index < objectArray.length; index++) {
 | 
					 | 
				
			||||||
    if (objectArray[index][key] == value) return index;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return -1;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Extracts all of the contents from a provided .zip archive.
 | 
					 | 
				
			||||||
 * @param file - The file to extract the contents from.
 | 
					 | 
				
			||||||
 * @param destination - The directory to unzip the contents into.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
function promiseExtract(file: string, destination: string) {
 | 
					 | 
				
			||||||
  return new Promise(function(resolve, reject) {
 | 
					 | 
				
			||||||
    let archive = new admZip(file);
 | 
					 | 
				
			||||||
    archive.extractAllToAsync(destination, true, true, err => {
 | 
					 | 
				
			||||||
      if (err) return reject(err);
 | 
					 | 
				
			||||||
      resolve("");
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Compresses contents of the provided folder using ADM Zip.
 | 
					 | 
				
			||||||
 * @param folder - The folder containing folder containing the files to compress.
 | 
					 | 
				
			||||||
 * @param destinationFile - The file to save the archive as.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
function promiseZip(folder: string, destinationFile: string) {
 | 
					 | 
				
			||||||
  return new Promise(async function(resolve, reject) {
 | 
					 | 
				
			||||||
    let archive = new admZip();
 | 
					 | 
				
			||||||
    let contents = await fs.promises.readdir(folder);
 | 
					 | 
				
			||||||
    for (let file of contents) {
 | 
					 | 
				
			||||||
      let filePath = path.join(folder, file);
 | 
					 | 
				
			||||||
      let stat = await fs.promises.stat(filePath);
 | 
					 | 
				
			||||||
      stat.isFile() ? archive.addLocalFile(filePath) : archive.addLocalFolder(filePath, file);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    archive.writeZip(destinationFile, err => {
 | 
					 | 
				
			||||||
      if (err) return reject(err);
 | 
					 | 
				
			||||||
      resolve("");
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Attempt to locate the subdirectory containing one of the provided file names.
 | 
					 | 
				
			||||||
 * @param filenames - The name of files to search for.
 | 
					 | 
				
			||||||
 * @param directory - The directory to search in.
 | 
					 | 
				
			||||||
 * @returns The path to the first folder containing one of the files or null.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
function findFilesSync(filenames: Array<string>, directory: string): string {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Get the contents of the directory and see if it includes one of the files.
 | 
					 | 
				
			||||||
  const contents = fs.readdirSync(directory);
 | 
					 | 
				
			||||||
  for (let file of contents) {
 | 
					 | 
				
			||||||
    if (filenames.includes(file)) return directory;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // If unable to find one of the files, check subdirectories.
 | 
					 | 
				
			||||||
  for (let subDir of contents) {
 | 
					 | 
				
			||||||
    let dirPath = path.join(directory, subDir);
 | 
					 | 
				
			||||||
    let stat = fs.statSync(dirPath);
 | 
					 | 
				
			||||||
    if (stat.isDirectory()) {
 | 
					 | 
				
			||||||
      let subDirectoryResult = findFilesSync(filenames, dirPath);
 | 
					 | 
				
			||||||
      if (subDirectoryResult) return subDirectoryResult;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Unable to find the files.
 | 
					 | 
				
			||||||
  return null;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//TODO: Add type definitions for the manifest files.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * @typedef {Object} PackData - Information extracted from an installed pack.
 | 
					 | 
				
			||||||
 * @property {String} name - The name found in the packs manifest.json file.
 | 
					 | 
				
			||||||
 * @property {String} uuid - The uuid found in the packs manifest.json file.
 | 
					 | 
				
			||||||
 * @property {String} version - the version found in the packs manifest.json fle.
 | 
					 | 
				
			||||||
 * @property {String} location - The full path to the root directory of the installed pack.
 | 
					 | 
				
			||||||
 * Used by the mapInstalledPacks function
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
@@ -1,38 +0,0 @@
 | 
				
			|||||||
import {promises as fsPromise, existsSync as fsExists} from "node:fs";
 | 
					 | 
				
			||||||
import * as path from "node:path";
 | 
					 | 
				
			||||||
import admZip from "adm-zip";
 | 
					 | 
				
			||||||
import { serverRoot } from "../pathControl";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const bedrockPath = path.join(serverRoot, "bedrock");
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Create backup for Worlds and Settings
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export async function CreateBackup(): Promise<Buffer> {
 | 
					 | 
				
			||||||
  if (!(fsExists(bedrockPath))) throw new Error("Bedrock folder does not exist");
 | 
					 | 
				
			||||||
  const zip = new admZip();
 | 
					 | 
				
			||||||
  const FFs = (await fsPromise.readdir(bedrockPath)).filter(FF => (["allowlist.json", "permissions.json", "server.properties", "worlds"]).some(file => file === FF));
 | 
					 | 
				
			||||||
  for (const FF of FFs) {
 | 
					 | 
				
			||||||
    const FFpath = path.join(bedrockPath, FF);
 | 
					 | 
				
			||||||
    const stats = await fsPromise.stat(FFpath);
 | 
					 | 
				
			||||||
    if (stats.isSymbolicLink()) {
 | 
					 | 
				
			||||||
      const realPath = await fsPromise.realpath(FFpath);
 | 
					 | 
				
			||||||
      const realStats = await fsPromise.stat(realPath);
 | 
					 | 
				
			||||||
      if (realStats.isDirectory()) zip.addLocalFolder(realPath, FF);
 | 
					 | 
				
			||||||
      else zip.addLocalFile(realPath, FF);
 | 
					 | 
				
			||||||
    } else if (stats.isDirectory()) zip.addLocalFolder(FFpath);
 | 
					 | 
				
			||||||
    else zip.addLocalFile(FFpath);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  // Return Buffer
 | 
					 | 
				
			||||||
  return zip.toBufferPromise();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Restore backup for Worlds and Settings
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * WARNING: This will overwrite existing files and World folder files
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export async function RestoreBackup(zipBuffer: Buffer): Promise<void> {
 | 
					 | 
				
			||||||
  const zip = new admZip(zipBuffer);
 | 
					 | 
				
			||||||
  await new Promise((resolve, reject) => zip.extractAllToAsync(bedrockPath, true, true, (err) => !!err ? reject(err) : resolve("")));
 | 
					 | 
				
			||||||
  return;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,324 +0,0 @@
 | 
				
			|||||||
import os from "os";
 | 
					 | 
				
			||||||
import path from "node:path";
 | 
					 | 
				
			||||||
import fs, { promises as fsPromise } from "node:fs";
 | 
					 | 
				
			||||||
import AdmZip from "adm-zip";
 | 
					 | 
				
			||||||
import * as Proprieties from "../lib/Proprieties"
 | 
					 | 
				
			||||||
import { parse as nbtParse, NBT, Metadata as nbtData, NBTFormat } from "prismarine-nbt";
 | 
					 | 
				
			||||||
import { getBuffer } from "../lib/HttpRequests";
 | 
					 | 
				
			||||||
import { serverRoot } from "../pathControl";
 | 
					 | 
				
			||||||
const serverPath = path.join(serverRoot, "bedrock");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type bedrockConfig = {
 | 
					 | 
				
			||||||
  /** This is the server name shown in the in-game server list. */
 | 
					 | 
				
			||||||
  serverName: string,
 | 
					 | 
				
			||||||
  /** The maximum numbers of players that should be able to play on the server. `Higher values have performance impact.` */
 | 
					 | 
				
			||||||
  maxPlayers?: number,
 | 
					 | 
				
			||||||
  /** Default gamemode to server and new Players */
 | 
					 | 
				
			||||||
  gamemode: "survival"|"creative"|"adventure"|1|2|3,
 | 
					 | 
				
			||||||
  /** Default server difficulty */
 | 
					 | 
				
			||||||
  difficulty?: "peaceful"|1|"easy"|2|"normal"|3|"hard"|4,
 | 
					 | 
				
			||||||
  /** Which permission level new players will have when they join for the first time. */
 | 
					 | 
				
			||||||
  PlayerDefaultPermissionLevel?: "visitor"|"member"|"operator",
 | 
					 | 
				
			||||||
  /** World Name to show in list friends and pause menu */
 | 
					 | 
				
			||||||
  worldName: string,
 | 
					 | 
				
			||||||
  /** The seed to be used for randomizing the world (`If left empty a seed will be chosen at random`). */
 | 
					 | 
				
			||||||
  worldSeed?: string|number,
 | 
					 | 
				
			||||||
  /** For remote servers always use true as the server will be exposed. */
 | 
					 | 
				
			||||||
  requiredXboxLive?: true|false,
 | 
					 | 
				
			||||||
  /** if enabled, allow only player in permission.json */
 | 
					 | 
				
			||||||
  allowList?: true|false,
 | 
					 | 
				
			||||||
  /** if enabled server allow commands, Command block and in survival disable achievements */
 | 
					 | 
				
			||||||
  allowCheats?: true|false,
 | 
					 | 
				
			||||||
  /** Server Ports */
 | 
					 | 
				
			||||||
  port?: {
 | 
					 | 
				
			||||||
    /** IPv4 Port, different for v6 */
 | 
					 | 
				
			||||||
    v4?: number,
 | 
					 | 
				
			||||||
    /** IPv6 */
 | 
					 | 
				
			||||||
    v6?: number,
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  /** The maximum allowed view distance (`Higher values have performance impact`). */
 | 
					 | 
				
			||||||
  viewDistance?: number,
 | 
					 | 
				
			||||||
  /** The world will be ticked this many chunks away from any player (`Higher values have performance impact`). */
 | 
					 | 
				
			||||||
  tickDistance?: number,
 | 
					 | 
				
			||||||
  /** After a player has idled for this many minutes they will be kicked (`If set to 0 then players can idle indefinitely`). */
 | 
					 | 
				
			||||||
  playerIdleTimeout?: number,
 | 
					 | 
				
			||||||
  /** Maximum number of threads the server will try to use (`Bds Core auto detect Threads`). */
 | 
					 | 
				
			||||||
  maxCpuThreads?: number,
 | 
					 | 
				
			||||||
  /** If the world uses any specific texture packs then this setting will force the client to use it. */
 | 
					 | 
				
			||||||
  texturepackRequired?: true|false
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function CreateServerConfig(config: bedrockConfig): Promise<bedrockConfig> {
 | 
					 | 
				
			||||||
  if (!!config.difficulty) {
 | 
					 | 
				
			||||||
    if (typeof config.difficulty === "number") {
 | 
					 | 
				
			||||||
      if (config.difficulty === 1) config.difficulty = "peaceful";
 | 
					 | 
				
			||||||
      else if (config.difficulty === 2) config.difficulty = "easy";
 | 
					 | 
				
			||||||
      else if (config.difficulty === 3) config.difficulty = "normal";
 | 
					 | 
				
			||||||
      else if (config.difficulty === 4) config.difficulty = "hard";
 | 
					 | 
				
			||||||
      else {
 | 
					 | 
				
			||||||
        console.log("[Bds Core] Invalid difficulty value, defaulting to normal");
 | 
					 | 
				
			||||||
        config.difficulty = "normal";
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (!!config.gamemode) {
 | 
					 | 
				
			||||||
    if (typeof config.gamemode === "number") {
 | 
					 | 
				
			||||||
      if (config.gamemode === 1) config.gamemode = "survival";
 | 
					 | 
				
			||||||
      else if (config.gamemode === 2) config.gamemode = "creative";
 | 
					 | 
				
			||||||
      else if (config.gamemode === 3) config.gamemode = "adventure";
 | 
					 | 
				
			||||||
      else {
 | 
					 | 
				
			||||||
        console.log("[Bds Core] Invalid gamemode value, defaulting to survival");
 | 
					 | 
				
			||||||
        config.gamemode = "survival";
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (!!config.viewDistance) {
 | 
					 | 
				
			||||||
    if (typeof config.viewDistance === "number") {
 | 
					 | 
				
			||||||
      if (config.viewDistance < 4) {
 | 
					 | 
				
			||||||
        console.log("[Bds Core] Invalid view distance value, defaulting to 4");
 | 
					 | 
				
			||||||
        config.viewDistance = 4;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      console.log("[Bds Core] Invalid view distance value, defaulting to 4");
 | 
					 | 
				
			||||||
      config.viewDistance = 4;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (!!config.tickDistance) {
 | 
					 | 
				
			||||||
    if (typeof config.tickDistance === "number") {
 | 
					 | 
				
			||||||
      if (config.tickDistance < 4) {
 | 
					 | 
				
			||||||
        console.log("[Bds Core] Invalid tick distance value, defaulting to 4");
 | 
					 | 
				
			||||||
        config.tickDistance = 4;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      console.log("[Bds Core] Invalid tick distance value, defaulting to 4");
 | 
					 | 
				
			||||||
      config.tickDistance = 4;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (!!config.maxPlayers) {
 | 
					 | 
				
			||||||
    if (typeof config.maxPlayers === "number") {
 | 
					 | 
				
			||||||
      if (config.maxPlayers < 2) {
 | 
					 | 
				
			||||||
        console.log("[Bds Core] Invalid max players value, defaulting to 2");
 | 
					 | 
				
			||||||
        config.maxPlayers = 2;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      console.log("[Bds Core] Invalid max players value, defaulting to 2");
 | 
					 | 
				
			||||||
      config.maxPlayers = 2;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (!!config.playerIdleTimeout||config.playerIdleTimeout !== 0) {
 | 
					 | 
				
			||||||
    if (typeof config.playerIdleTimeout === "number") {
 | 
					 | 
				
			||||||
      if (config.playerIdleTimeout < 0) {
 | 
					 | 
				
			||||||
        console.log("[Bds Core] Invalid player idle timeout value, defaulting to 0");
 | 
					 | 
				
			||||||
        config.playerIdleTimeout = 0;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      console.log("[Bds Core] Invalid player idle timeout value, defaulting to 0");
 | 
					 | 
				
			||||||
      config.playerIdleTimeout = 0;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (!!config.port) {
 | 
					 | 
				
			||||||
    if (!!config.port.v4) {
 | 
					 | 
				
			||||||
      if (typeof config.port.v4 === "number") {
 | 
					 | 
				
			||||||
        if (config.port.v4 < 1) {
 | 
					 | 
				
			||||||
          console.log("[Bds Core] Invalid v4 port value, defaulting to 19132");
 | 
					 | 
				
			||||||
          config.port.v4 = 19132;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!!config.port.v6) {
 | 
					 | 
				
			||||||
      if (typeof config.port.v6 === "number") {
 | 
					 | 
				
			||||||
        if (config.port.v6 < 1) {
 | 
					 | 
				
			||||||
          console.log("[Bds Core] Invalid v6 port value, defaulting to 19133");
 | 
					 | 
				
			||||||
          config.port.v6 = 19133;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  const serverName = config.serverName || "Bedrock Server";
 | 
					 | 
				
			||||||
  const maxPlayers = config.maxPlayers || 20;
 | 
					 | 
				
			||||||
  const gamemode = config.gamemode || "survival";
 | 
					 | 
				
			||||||
  const difficulty = config.difficulty || "peaceful";
 | 
					 | 
				
			||||||
  const PlayerDefaultPermissionLevel =  config.PlayerDefaultPermissionLevel || "member";
 | 
					 | 
				
			||||||
  const worldName = config.worldName || "Bedrock level";
 | 
					 | 
				
			||||||
  const worldSeed = config.worldSeed || "";
 | 
					 | 
				
			||||||
  const requiredXboxLive = config.requiredXboxLive || true;
 | 
					 | 
				
			||||||
  const allowList = config.allowList || false;
 | 
					 | 
				
			||||||
  const allowCheats = config.allowCheats || false;
 | 
					 | 
				
			||||||
  const port = {v4: (config.port||{}).v4 || 19132, v6: (config.port||{}).v6 || 19133};
 | 
					 | 
				
			||||||
  const viewDistance = config.viewDistance || 32;
 | 
					 | 
				
			||||||
  const tickDistance = config.tickDistance || 4;
 | 
					 | 
				
			||||||
  const playerIdleTimeout = config.playerIdleTimeout || 0;
 | 
					 | 
				
			||||||
  const maxCpuThreads = config.maxCpuThreads || os.cpus().length || 8;
 | 
					 | 
				
			||||||
  const texturepackRequired = config.texturepackRequired || false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Server config
 | 
					 | 
				
			||||||
  const configFileArray = [
 | 
					 | 
				
			||||||
    `server-name=${serverName}`,
 | 
					 | 
				
			||||||
    `gamemode=${gamemode}`,
 | 
					 | 
				
			||||||
    "force-gamemode=false",
 | 
					 | 
				
			||||||
    `difficulty=${difficulty}`,
 | 
					 | 
				
			||||||
    `allow-cheats=${allowCheats}`,
 | 
					 | 
				
			||||||
    `max-players=${maxPlayers}`,
 | 
					 | 
				
			||||||
    `online-mode=${requiredXboxLive}`,
 | 
					 | 
				
			||||||
    `allow-list=${allowList}`,
 | 
					 | 
				
			||||||
    `server-port=${port.v4}`,
 | 
					 | 
				
			||||||
    `server-portv6=${port.v6}`,
 | 
					 | 
				
			||||||
    `view-distance=${viewDistance}`,
 | 
					 | 
				
			||||||
    `tick-distance=${tickDistance}`,
 | 
					 | 
				
			||||||
    `player-idle-timeout=${playerIdleTimeout}`,
 | 
					 | 
				
			||||||
    `max-threads=${maxCpuThreads}`,
 | 
					 | 
				
			||||||
    `level-name=${worldName}`,
 | 
					 | 
				
			||||||
    `level-seed=${worldSeed}`,
 | 
					 | 
				
			||||||
    `default-player-permission-level=${PlayerDefaultPermissionLevel}`,
 | 
					 | 
				
			||||||
    `texturepack-required=${texturepackRequired}`,
 | 
					 | 
				
			||||||
    "emit-server-telemetry=true",
 | 
					 | 
				
			||||||
    "content-log-file-enabled=false",
 | 
					 | 
				
			||||||
    "compression-threshold=1",
 | 
					 | 
				
			||||||
    "server-authoritative-movement=server-auth",
 | 
					 | 
				
			||||||
    "player-movement-score-threshold=20",
 | 
					 | 
				
			||||||
    "player-movement-action-direction-threshold=0.85",
 | 
					 | 
				
			||||||
    "player-movement-distance-threshold=0.3",
 | 
					 | 
				
			||||||
    "player-movement-duration-threshold-in-ms=500",
 | 
					 | 
				
			||||||
    "correct-player-movement=false",
 | 
					 | 
				
			||||||
    "server-authoritative-block-breaking=false"
 | 
					 | 
				
			||||||
  ];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Write config file
 | 
					 | 
				
			||||||
  await fsPromise.writeFile(path.join(serverPath, "server.properties"), configFileArray.join("\n"), {encoding: "utf8"});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Return writed config
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    serverName,
 | 
					 | 
				
			||||||
    maxPlayers,
 | 
					 | 
				
			||||||
    gamemode,
 | 
					 | 
				
			||||||
    difficulty,
 | 
					 | 
				
			||||||
    PlayerDefaultPermissionLevel,
 | 
					 | 
				
			||||||
    worldName,
 | 
					 | 
				
			||||||
    worldSeed,
 | 
					 | 
				
			||||||
    requiredXboxLive,
 | 
					 | 
				
			||||||
    allowList,
 | 
					 | 
				
			||||||
    allowCheats,
 | 
					 | 
				
			||||||
    port,
 | 
					 | 
				
			||||||
    viewDistance,
 | 
					 | 
				
			||||||
    tickDistance,
 | 
					 | 
				
			||||||
    playerIdleTimeout,
 | 
					 | 
				
			||||||
    maxCpuThreads,
 | 
					 | 
				
			||||||
    texturepackRequired
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type bedrockParsedConfig = {
 | 
					 | 
				
			||||||
  /** This is the server name shown in the in-game server list. */
 | 
					 | 
				
			||||||
  serverName: string,
 | 
					 | 
				
			||||||
  /** World Name to show in list friends and pause menu */
 | 
					 | 
				
			||||||
  worldName: string,
 | 
					 | 
				
			||||||
  /** Default gamemode to server and new Players */
 | 
					 | 
				
			||||||
  gamemode: "survival"|"creative"|"adventure",
 | 
					 | 
				
			||||||
  /** The maximum numbers of players that should be able to play on the server. `Higher values have performance impact.` */
 | 
					 | 
				
			||||||
  maxPlayers: number,
 | 
					 | 
				
			||||||
  /** Default server difficulty */
 | 
					 | 
				
			||||||
  difficulty: "peaceful"|"easy"|"normal"|"hard",
 | 
					 | 
				
			||||||
  /** The seed to be used for randomizing the world (`If left empty a seed will be chosen at random`). */
 | 
					 | 
				
			||||||
  worldSeed: string|number,
 | 
					 | 
				
			||||||
  port: {
 | 
					 | 
				
			||||||
    v4: number,
 | 
					 | 
				
			||||||
    v6: number
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  /** World NBT */
 | 
					 | 
				
			||||||
  nbtParsed: {parsed: NBT, type: NBTFormat, metadata: nbtData}
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
export async function getConfig(): Promise<bedrockParsedConfig> {
 | 
					 | 
				
			||||||
  const config: bedrockParsedConfig = {
 | 
					 | 
				
			||||||
    serverName: "Bedrock Server",
 | 
					 | 
				
			||||||
    worldName: "Bedrock level",
 | 
					 | 
				
			||||||
    gamemode: "survival",
 | 
					 | 
				
			||||||
    difficulty: "normal",
 | 
					 | 
				
			||||||
    maxPlayers: 0,
 | 
					 | 
				
			||||||
    worldSeed: "",
 | 
					 | 
				
			||||||
    port: {
 | 
					 | 
				
			||||||
      v4: 19132,
 | 
					 | 
				
			||||||
      v6: 19133
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    nbtParsed: undefined
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
  if (fs.existsSync(path.join(serverPath, "server.properties"))) {
 | 
					 | 
				
			||||||
    const ProPri = Proprieties.parse(await fsPromise.readFile(path.join(serverPath, "server.properties"), {encoding: "utf8"}));
 | 
					 | 
				
			||||||
    if (ProPri["server-name"] !== undefined) config.serverName = String(ProPri["server-name"]);
 | 
					 | 
				
			||||||
    if (ProPri["level-name"] !== undefined) config.worldName = String(ProPri["level-name"]);
 | 
					 | 
				
			||||||
    if (ProPri["gamemode"] !== undefined) config.gamemode = String(ProPri["gamemode"]) as "survival"|"creative"|"adventure";
 | 
					 | 
				
			||||||
    if (ProPri["max-players"] !== undefined) config.maxPlayers = Number(ProPri["max-players"]);
 | 
					 | 
				
			||||||
    if (ProPri["difficulty"] !== undefined) config.difficulty = String(ProPri["difficulty"]) as "peaceful"|"easy"|"normal"|"hard";
 | 
					 | 
				
			||||||
    if (ProPri["server-port"] !== undefined) config.port.v4 = Number(ProPri["server-port"]);
 | 
					 | 
				
			||||||
    if (ProPri["server-portv6"] !== undefined) config.port.v6 = Number(ProPri["server-portv6"]);
 | 
					 | 
				
			||||||
    if (ProPri["level-seed"] !== undefined) config.worldSeed = String(ProPri["level-seed"]);
 | 
					 | 
				
			||||||
    // if (ProPri["allow-cheats"] !== undefined) config.allowCheats = Boolean(ProPri["allow-cheats"]);
 | 
					 | 
				
			||||||
    // if (ProPri["allow-list"] !== undefined) config.allowList = Boolean(ProPri["allow-list"]);
 | 
					 | 
				
			||||||
    // if (ProPri["texturepack-required"] !== undefined) config.texturepackRequired = Boolean(ProPri["texturepack-required"]);
 | 
					 | 
				
			||||||
    // if (ProPri["view-distance"] !== undefined) config.viewDistance = Number(ProPri["view-distance"]);
 | 
					 | 
				
			||||||
    // if (ProPri["tick-distance"] !== undefined) config.tickDistance = Number(ProPri["tick-distance"]);
 | 
					 | 
				
			||||||
    // if (ProPri["player-idle-timeout"] !== undefined) config.playerIdleTimeout = Number(ProPri["player-idle-timeout"]);
 | 
					 | 
				
			||||||
    // if (ProPri["max-threads"] !== undefined) config.maxCpuThreads = Number(ProPri["max-threads"]);
 | 
					 | 
				
			||||||
    // if (ProPri["default-player-permission-level"] !== undefined) config.PlayerDefaultPermissionLevel = String(ProPri["default-player-permission-level"]);
 | 
					 | 
				
			||||||
    // if (ProPri["emit-server-telemetry"] !== undefined) config.emitServerTelemetry = Boolean(ProPri["emit-server-telemetry"]);
 | 
					 | 
				
			||||||
    // if (ProPri["content-log-file-enabled"] !== undefined) config.contentLogFileEnabled = Boolean(ProPri["content-log-file-enabled"]);
 | 
					 | 
				
			||||||
    // if (ProPri["compression-threshold"] !== undefined) config.compressionThreshold = Number(ProPri["compression-threshold"]);
 | 
					 | 
				
			||||||
    // if (ProPri["server-authoritative-movement"] !== undefined) config.
 | 
					 | 
				
			||||||
    const worldDatePath = path.join(serverPath, "worlds", config.worldName, "level.dat");
 | 
					 | 
				
			||||||
    if (fs.existsSync(worldDatePath)) config.nbtParsed = await nbtParse(await fsPromise.readFile(worldDatePath));
 | 
					 | 
				
			||||||
    if (ProPri["level-seed"] !== undefined) config.worldSeed = String(ProPri["level-seed"]);
 | 
					 | 
				
			||||||
    else {
 | 
					 | 
				
			||||||
      if (config.nbtParsed !== undefined) {
 | 
					 | 
				
			||||||
        const seedValue = ((((((config||{}).nbtParsed||{}).parsed||{}).value||{}).RandomSeed||{}).value||"").toString()
 | 
					 | 
				
			||||||
        if (!!seedValue) config.worldSeed = seedValue;
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (config.worldSeed === "null") delete config.worldSeed;
 | 
					 | 
				
			||||||
  return config;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function Permission(): Promise<Array<{ignoresPlayerLimit: false|true, name: string, xuid?: string}>> {
 | 
					 | 
				
			||||||
  const permissionPath = path.join(serverPath, "allowlist.json");
 | 
					 | 
				
			||||||
  if (fs.existsSync(permissionPath)) {
 | 
					 | 
				
			||||||
    const permission = JSON.parse(await fsPromise.readFile(permissionPath, {encoding: "utf8"}));
 | 
					 | 
				
			||||||
    return permission;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return [];
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function resourcePack(WorldName: string) {
 | 
					 | 
				
			||||||
  const mapPath = path.join(serverPath, "worlds", WorldName);
 | 
					 | 
				
			||||||
  if (!(fs.existsSync(mapPath))) throw new Error("Map not found");
 | 
					 | 
				
			||||||
  const remotePack = async () => {
 | 
					 | 
				
			||||||
    const { tree } = await getBuffer("https://api.github.com/repos/The-Bds-Maneger/BedrockAddonTextureManeger/git/trees/main?recursive=true").then(res => JSON.parse(res.toString()) as {sha: string, url: string, truncated: true|false, tree: Array<{path: string, mode: string, type: "tree"|"blob", sha: string, size: number, url: string}>});
 | 
					 | 
				
			||||||
    const pack = tree.filter(item => item.path.includes(".mcpack") && item.type === "blob");
 | 
					 | 
				
			||||||
    return await Promise.all(pack.map(BlobFile => getBuffer(BlobFile.url).then(res => JSON.parse(res.toString())).then(res => {
 | 
					 | 
				
			||||||
      const fileBuffer = Buffer.from(res.content, "base64");
 | 
					 | 
				
			||||||
      const fileName = BlobFile.path.split("/").pop().replace(/\.mcpack.*/, "");
 | 
					 | 
				
			||||||
      const zip = new AdmZip(fileBuffer);
 | 
					 | 
				
			||||||
      const manifest = JSON.parse(zip.getEntry("manifest.json").getData().toString()) as {format_version: number, header: {name: string, description: string, uuid: string, version: Array<number>, min_engine_version?: Array<number>}, modules: Array<{type: string, uuid: string, version: Array<number>}>, metadata?: {authors?: Array<string>, url?: string}};
 | 
					 | 
				
			||||||
      return {fileName, fileBuffer, manifest};
 | 
					 | 
				
			||||||
    })));
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
  // const localPack = async () => {};
 | 
					 | 
				
			||||||
  const installPack = async (zipBuffer: Buffer) => {
 | 
					 | 
				
			||||||
    const worldResourcePacksPath = path.join(mapPath, "world_resource_packs.json");
 | 
					 | 
				
			||||||
    let worldResourcePacks: Array<{pack_id: string, version: Array<number>}> = [];
 | 
					 | 
				
			||||||
    if (fs.existsSync(worldResourcePacksPath)) worldResourcePacks = JSON.parse(await fsPromise.readFile(worldResourcePacksPath, {encoding: "utf8"}));
 | 
					 | 
				
			||||||
    const zip = new AdmZip(zipBuffer);
 | 
					 | 
				
			||||||
    const manifest = JSON.parse(zip.getEntry("manifest.json").getData().toString()) as {format_version: number, header: {name: string, description: string, uuid: string, version: Array<number>, min_engine_version?: Array<number>}, modules: Array<{type: string, uuid: string, version: Array<number>}>, metadata?: {authors?: Array<string>, url?: string}};
 | 
					 | 
				
			||||||
    const pack_id = manifest.header.uuid;
 | 
					 | 
				
			||||||
    if (worldResourcePacks.find(item => item.pack_id === pack_id)) throw new Error("Pack already installed");
 | 
					 | 
				
			||||||
    worldResourcePacks.push({pack_id, version: manifest.header.version});
 | 
					 | 
				
			||||||
    await fsPromise.writeFile(worldResourcePacksPath, JSON.stringify(worldResourcePacks, null, 2));
 | 
					 | 
				
			||||||
    return {pack_id, version: manifest.header.version.join(".")};
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
  const removePack = async () => {};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    remotePack,
 | 
					 | 
				
			||||||
    //localPack,
 | 
					 | 
				
			||||||
    installPack,
 | 
					 | 
				
			||||||
    removePack
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,41 +0,0 @@
 | 
				
			|||||||
import path from "node:path";
 | 
					 | 
				
			||||||
import fs from "node:fs";
 | 
					 | 
				
			||||||
import adm_zip from "adm-zip";
 | 
					 | 
				
			||||||
import * as versionManeger from "@the-bds-maneger/server_versions";
 | 
					 | 
				
			||||||
import * as httpRequests from "../lib/HttpRequests";
 | 
					 | 
				
			||||||
import { runCommandAsync } from "../lib/childProcess"
 | 
					 | 
				
			||||||
import { serverRoot } from "../pathControl";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function download(version: string|boolean) {
 | 
					 | 
				
			||||||
  const ServerPath = path.join(serverRoot, "bedrock");
 | 
					 | 
				
			||||||
  if (!(await fs.existsSync(ServerPath))) fs.mkdirSync(ServerPath, {recursive: true});
 | 
					 | 
				
			||||||
  let arch = process.arch;
 | 
					 | 
				
			||||||
  if (process.platform === "linux" && process.arch !== "x64") {
 | 
					 | 
				
			||||||
    const existQemu = await runCommandAsync("command -v qemu-x86_64-static").then(() => true).catch(() => false);
 | 
					 | 
				
			||||||
    if (existQemu) arch = "x64";
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  const bedrockInfo = await versionManeger.findUrlVersion("bedrock", version, arch);
 | 
					 | 
				
			||||||
  const BedrockZip = new adm_zip(await httpRequests.getBuffer(bedrockInfo.url));
 | 
					 | 
				
			||||||
  let realPathWorldBedrock = "";
 | 
					 | 
				
			||||||
  if (fs.existsSync(path.resolve(ServerPath, "worlds"))) {
 | 
					 | 
				
			||||||
    if (fs.lstatSync(path.resolve(ServerPath, "worlds")).isSymbolicLink()) {
 | 
					 | 
				
			||||||
      realPathWorldBedrock = await fs.promises.realpath(path.resolve(ServerPath, "worlds"));
 | 
					 | 
				
			||||||
      await fs.promises.unlink(path.resolve(ServerPath, "worlds"));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  let ServerProperties = "";
 | 
					 | 
				
			||||||
  if (fs.existsSync(path.resolve(ServerPath, "server.properties"))) {
 | 
					 | 
				
			||||||
    ServerProperties = await fs.promises.readFile(path.resolve(ServerPath, "server.properties"), "utf8");
 | 
					 | 
				
			||||||
    await fs.promises.rm(path.resolve(ServerPath, "server.properties"));
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  BedrockZip.extractAllTo(ServerPath, true);
 | 
					 | 
				
			||||||
  if (!!realPathWorldBedrock) await fs.promises.symlink(realPathWorldBedrock, path.resolve(ServerPath, "worlds"), "dir");
 | 
					 | 
				
			||||||
  if (!!ServerProperties) await fs.promises.writeFile(path.resolve(ServerPath, "server.properties"), ServerProperties, "utf8");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Return info
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    version: bedrockInfo.version,
 | 
					 | 
				
			||||||
    publishDate: bedrockInfo.datePublish,
 | 
					 | 
				
			||||||
    url: bedrockInfo.url,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,7 +0,0 @@
 | 
				
			|||||||
 | 
					 | 
				
			||||||
export {download as DownloadServer} from "./download";
 | 
					 | 
				
			||||||
export * as linkWorld from "./linkWorld";
 | 
					 | 
				
			||||||
export * as config from "./config";
 | 
					 | 
				
			||||||
export * as server from "./server";
 | 
					 | 
				
			||||||
export * as backup from "./backup";
 | 
					 | 
				
			||||||
export * as addon from "./addon";
 | 
					 | 
				
			||||||
@@ -1,21 +0,0 @@
 | 
				
			|||||||
import * as fs from "node:fs/promises";
 | 
					 | 
				
			||||||
import * as fsOld from "node:fs";
 | 
					 | 
				
			||||||
import * as path from "path";
 | 
					 | 
				
			||||||
import { serverRoot, worldStorageRoot } from "../pathControl";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function linkWorld(): Promise<void> {
 | 
					 | 
				
			||||||
  const worldFolder = path.join(worldStorageRoot, "bedrock");
 | 
					 | 
				
			||||||
  const bedrockFolder = path.join(serverRoot, "bedrock");
 | 
					 | 
				
			||||||
  if (!fsOld.existsSync(bedrockFolder)) throw new Error("Server not installed")
 | 
					 | 
				
			||||||
  if (!fsOld.existsSync(worldFolder)) await fs.mkdir(worldFolder, {recursive: true});
 | 
					 | 
				
			||||||
  const bedrockServerWorld = path.join(bedrockFolder, "worlds");
 | 
					 | 
				
			||||||
  if (fsOld.existsSync(bedrockServerWorld)) {
 | 
					 | 
				
			||||||
    if ((await fs.lstat(bedrockServerWorld)).isSymbolicLink()) return;
 | 
					 | 
				
			||||||
    for (const folder of await fs.readdir(bedrockServerWorld)) {
 | 
					 | 
				
			||||||
      await fs.rename(path.join(bedrockServerWorld, folder), path.join(worldFolder, folder))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    await fs.rmdir(bedrockServerWorld);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  await fs.symlink(worldFolder, bedrockServerWorld, "dir");
 | 
					 | 
				
			||||||
  return;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,196 +0,0 @@
 | 
				
			|||||||
import path from "node:path";
 | 
					 | 
				
			||||||
import fs from "node:fs";
 | 
					 | 
				
			||||||
import crypto from "crypto";
 | 
					 | 
				
			||||||
import node_cron from "cron";
 | 
					 | 
				
			||||||
import * as child_process from "../lib/childProcess";
 | 
					 | 
				
			||||||
import { backupRoot, serverRoot } from "../pathControl";
 | 
					 | 
				
			||||||
import { BdsSession, bdsSessionCommands, playerAction2 } from '../globalType';
 | 
					 | 
				
			||||||
import { getConfig } from "./config";
 | 
					 | 
				
			||||||
import { CreateBackup } from "./backup";
 | 
					 | 
				
			||||||
import events from "../lib/customEvents";
 | 
					 | 
				
			||||||
import portislisten from "../lib/portIsAllocated";
 | 
					 | 
				
			||||||
import { linkWorld } from "./linkWorld";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const bedrockSesions: {[key: string]: BdsSession} = {};
 | 
					 | 
				
			||||||
export function getSessions() {return bedrockSesions;}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const ServerPath = path.join(serverRoot, "bedrock");
 | 
					 | 
				
			||||||
export async function startServer(): Promise<BdsSession> {
 | 
					 | 
				
			||||||
  if (!(fs.existsSync(ServerPath))) throw new Error("server dont installed");
 | 
					 | 
				
			||||||
  if (process.env.AUTO_LINK_WORLD === "true" || process.env.AUTO_LINK_WORLDS === "1") await linkWorld();
 | 
					 | 
				
			||||||
  const SessionID = crypto.randomUUID();
 | 
					 | 
				
			||||||
  const serverConfig = await getConfig();
 | 
					 | 
				
			||||||
  if (await portislisten(serverConfig.port.v4)) throw new Error("Port is already in use");
 | 
					 | 
				
			||||||
  if (await portislisten(serverConfig.port.v6)) throw new Error("Port is already in use");
 | 
					 | 
				
			||||||
  const Process: {command: string; args: Array<string>; env: {[env: string]: string};} = {command: "", args: [], env: {...process.env}};
 | 
					 | 
				
			||||||
  if (process.platform === "darwin") throw new Error("Run Docker image");
 | 
					 | 
				
			||||||
    Process.command = path.resolve(ServerPath, "bedrock_server"+(process.platform === "win32"?".exe":""));
 | 
					 | 
				
			||||||
    if (process.platform !== "win32") {
 | 
					 | 
				
			||||||
      await child_process.runAsync("chmod", ["a+x", Process.command]);
 | 
					 | 
				
			||||||
      Process.env.LD_LIBRARY_PATH = path.resolve(ServerPath, "bedrock");
 | 
					 | 
				
			||||||
      if (process.platform === "linux" && process.arch !== "x64") {
 | 
					 | 
				
			||||||
      const existQemu = await child_process.runCommandAsync("command -v qemu-x86_64-static").then(() => true).catch(() => false);
 | 
					 | 
				
			||||||
      if (existQemu) {
 | 
					 | 
				
			||||||
        console.warn("Minecraft bedrock start with emulated x64 architecture");
 | 
					 | 
				
			||||||
        Process.args.push(Process.command);
 | 
					 | 
				
			||||||
        Process.command = "qemu-x86_64-static";
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Start Server
 | 
					 | 
				
			||||||
  const serverEvents = new events({captureRejections: false});
 | 
					 | 
				
			||||||
  serverEvents.setMaxListeners(0);
 | 
					 | 
				
			||||||
  const ServerProcess = await child_process.execServer({runOn: "host"}, Process.command, Process.args, {env: Process.env, cwd: ServerPath});
 | 
					 | 
				
			||||||
  // Log Server redirect to callbacks events and exit
 | 
					 | 
				
			||||||
  ServerProcess.on("out", data => serverEvents.emit("log_stdout", data));
 | 
					 | 
				
			||||||
  ServerProcess.on("err", data => serverEvents.emit("log_stderr", data));
 | 
					 | 
				
			||||||
  ServerProcess.on("all", data => serverEvents.emit("log", data));
 | 
					 | 
				
			||||||
  ServerProcess.Exec.on("exit", code => {
 | 
					 | 
				
			||||||
    serverEvents.emit("closed", code);
 | 
					 | 
				
			||||||
    if (code === null) serverEvents.emit("err", new Error("Server exited with code null"));
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // on start
 | 
					 | 
				
			||||||
  serverEvents.on("log", lineData => {
 | 
					 | 
				
			||||||
    // [2022-05-19 22:35:09:315 INFO] Server started.
 | 
					 | 
				
			||||||
    if (/\[.*\]\s+Server\s+started\./.test(lineData)) serverEvents.emit("started", new Date());
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Port
 | 
					 | 
				
			||||||
  serverEvents.on("log", data => {
 | 
					 | 
				
			||||||
    const portParse = data.match(/(IPv[46])\s+supported,\s+port:\s+(.*)/);
 | 
					 | 
				
			||||||
    if (!!portParse) serverEvents.emit("port_listen", {port: parseInt(portParse[2]), protocol: "UDP", version: portParse[1] as "IPv4"|"IPv6"});
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Player
 | 
					 | 
				
			||||||
  serverEvents.on("log", data => {
 | 
					 | 
				
			||||||
    if (/r\s+.*\:\s+.*\,\s+xuid\:\s+.*/gi.test(data)) {
 | 
					 | 
				
			||||||
      const actionDate = new Date();
 | 
					 | 
				
			||||||
      const [action, player, xuid] = (data.match(/r\s+(.*)\:\s+(.*)\,\s+xuid\:\s+(.*)/)||[]).slice(1, 4);
 | 
					 | 
				
			||||||
      const playerAction: playerAction2 = {player: player, xuid: xuid, action: "unknown", Date: actionDate};
 | 
					 | 
				
			||||||
      if (action === "connected") playerAction.action = "connect";
 | 
					 | 
				
			||||||
      else if (action === "disconnected") playerAction.action = "disconnect";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // Server player event
 | 
					 | 
				
			||||||
      serverEvents.emit("player", playerAction);
 | 
					 | 
				
			||||||
      delete playerAction.action;
 | 
					 | 
				
			||||||
      if (action === "connect") serverEvents.emit("player_connect", playerAction);
 | 
					 | 
				
			||||||
      else if (action === "disconnect") serverEvents.emit("player_disconnect", playerAction);
 | 
					 | 
				
			||||||
      else serverEvents.emit("player_unknown", playerAction);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Run Command
 | 
					 | 
				
			||||||
  const serverCommands: bdsSessionCommands = {
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Run any commands in server.
 | 
					 | 
				
			||||||
     * @param command - Run any commands in server without parse commands
 | 
					 | 
				
			||||||
     * @returns - Server commands
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    execCommand: (...command) => {
 | 
					 | 
				
			||||||
      ServerProcess.writelf(command.map(a => String(a)).join(" "));
 | 
					 | 
				
			||||||
      return serverCommands;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    tpPlayer: (player: string, x: number, y: number, z: number) => {
 | 
					 | 
				
			||||||
      serverCommands.execCommand("tp", player, x, y, z);
 | 
					 | 
				
			||||||
      return serverCommands;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    worldGamemode: (gamemode: "survival"|"creative"|"hardcore") => {
 | 
					 | 
				
			||||||
      serverCommands.execCommand("gamemode", gamemode);
 | 
					 | 
				
			||||||
      return serverCommands;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    userGamemode: (player: string, gamemode: "survival"|"creative"|"hardcore") => {
 | 
					 | 
				
			||||||
      serverCommands.execCommand("gamemode", gamemode, player);
 | 
					 | 
				
			||||||
      return serverCommands;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    stop: (): Promise<number|null> => {
 | 
					 | 
				
			||||||
      if (ServerProcess.Exec.exitCode !== null||ServerProcess.Exec.killed) return Promise.resolve(ServerProcess.Exec.exitCode);
 | 
					 | 
				
			||||||
      if (ServerProcess.Exec.killed) return Promise.resolve(ServerProcess.Exec.exitCode);
 | 
					 | 
				
			||||||
      ServerProcess.writelf("stop");
 | 
					 | 
				
			||||||
      return ServerProcess.onExit();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const backupCron = (crontime: string|Date, option?: {type: "zip", config?: {pathZip?: string}}): node_cron.CronJob => {
 | 
					 | 
				
			||||||
    // Validate Config
 | 
					 | 
				
			||||||
    if (option) {
 | 
					 | 
				
			||||||
      if (option.type === "zip") {}
 | 
					 | 
				
			||||||
      else option = {type: "zip"};
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    async function lockServerBackup() {
 | 
					 | 
				
			||||||
      serverCommands.execCommand("save hold");
 | 
					 | 
				
			||||||
      await new Promise(accept => setTimeout(accept, 1000));
 | 
					 | 
				
			||||||
      serverCommands.execCommand("save query");
 | 
					 | 
				
			||||||
      await new Promise(accept => setTimeout(accept, 1000));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    async function unLockServerBackup() {
 | 
					 | 
				
			||||||
      serverCommands.execCommand("save resume");
 | 
					 | 
				
			||||||
      await new Promise(accept => setTimeout(accept, 1000));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!option) option = {type: "zip"};
 | 
					 | 
				
			||||||
    const CrontimeBackup = new node_cron.CronJob(crontime, async () => {
 | 
					 | 
				
			||||||
      if (option.type === "zip") {
 | 
					 | 
				
			||||||
        await lockServerBackup();
 | 
					 | 
				
			||||||
        if (!!option?.config?.pathZip) await CreateBackup().then(res => fs.promises.writeFile(path.resolve(backupRoot, option?.config?.pathZip), res)).catch(() => undefined);
 | 
					 | 
				
			||||||
        // else await createZipBackup(true).catch(() => undefined);
 | 
					 | 
				
			||||||
        await unLockServerBackup();
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    CrontimeBackup.start();
 | 
					 | 
				
			||||||
    serverEvents.on("closed", () => CrontimeBackup.stop());
 | 
					 | 
				
			||||||
    return CrontimeBackup;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Session log
 | 
					 | 
				
			||||||
  const logFile = path.resolve(process.env.LOG_PATH||path.resolve(ServerPath, "../log"), `bedrock_${SessionID}.log`);
 | 
					 | 
				
			||||||
  if(!(fs.existsSync(path.parse(logFile).dir))) fs.mkdirSync(path.parse(logFile).dir, {recursive: true});
 | 
					 | 
				
			||||||
  const logStream = fs.createWriteStream(logFile, {flags: "w+"});
 | 
					 | 
				
			||||||
  logStream.write(`[${(new Date()).toString()}] Server started\n\n`);
 | 
					 | 
				
			||||||
  ServerProcess.Exec.stdout.pipe(logStream);
 | 
					 | 
				
			||||||
  ServerProcess.Exec.stderr.pipe(logStream);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Session Object
 | 
					 | 
				
			||||||
  const Seesion: BdsSession = {
 | 
					 | 
				
			||||||
    id: SessionID,
 | 
					 | 
				
			||||||
    logFile: logFile,
 | 
					 | 
				
			||||||
    creteBackup: backupCron,
 | 
					 | 
				
			||||||
    seed: serverConfig.worldSeed,
 | 
					 | 
				
			||||||
    ports: [],
 | 
					 | 
				
			||||||
    Player: {},
 | 
					 | 
				
			||||||
    commands: serverCommands,
 | 
					 | 
				
			||||||
    server: {
 | 
					 | 
				
			||||||
      on: (act, fn) => serverEvents.on(act, fn),
 | 
					 | 
				
			||||||
      once: (act, fn) => serverEvents.once(act, fn),
 | 
					 | 
				
			||||||
      started: false,
 | 
					 | 
				
			||||||
      startDate: new Date(),
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  serverEvents.on("port_listen", Seesion.ports.push);
 | 
					 | 
				
			||||||
  serverEvents.on("started", date => {Seesion.server.started = true; Seesion.server.startDate = date;});
 | 
					 | 
				
			||||||
  serverEvents.on("player", playerAction => {
 | 
					 | 
				
			||||||
    // Add to object
 | 
					 | 
				
			||||||
    const playerExist = !!Seesion.Player[playerAction.player];
 | 
					 | 
				
			||||||
    if (playerExist) {
 | 
					 | 
				
			||||||
      Seesion.Player[playerAction.player].action = playerAction.action;
 | 
					 | 
				
			||||||
      Seesion.Player[playerAction.player].date = playerAction.Date;
 | 
					 | 
				
			||||||
      Seesion.Player[playerAction.player].history.push({
 | 
					 | 
				
			||||||
        action: playerAction.action,
 | 
					 | 
				
			||||||
        date: playerAction.Date
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    } else Seesion.Player[playerAction.player] = {
 | 
					 | 
				
			||||||
      action: playerAction.action,
 | 
					 | 
				
			||||||
      date: playerAction.Date,
 | 
					 | 
				
			||||||
      history: [{
 | 
					 | 
				
			||||||
        action: playerAction.action,
 | 
					 | 
				
			||||||
        date: playerAction.Date
 | 
					 | 
				
			||||||
      }]
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Return Session
 | 
					 | 
				
			||||||
  bedrockSesions[SessionID] = Seesion;
 | 
					 | 
				
			||||||
  serverEvents.on("closed", () => delete bedrockSesions[SessionID]);
 | 
					 | 
				
			||||||
  return Seesion;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										106
									
								
								src/childPromisses.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								src/childPromisses.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,106 @@
 | 
				
			|||||||
 | 
					import type { ObjectEncodingOptions } from "node:fs";
 | 
				
			||||||
 | 
					import { execFile, exec as nodeExec, ExecFileOptions, ChildProcess } from "node:child_process";
 | 
				
			||||||
 | 
					import { EventEmitter } from "node:events";
 | 
				
			||||||
 | 
					import { promisify } from "node:util";
 | 
				
			||||||
 | 
					export {execFile};
 | 
				
			||||||
 | 
					export const execAsync = promisify(nodeExec);
 | 
				
			||||||
 | 
					// export const execFileAsync = promisify(execFile);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type execOptions = ObjectEncodingOptions & ExecFileOptions & {stdio?: "ignore"|"inherit"};
 | 
				
			||||||
 | 
					export function execFileAsync(command: string): Promise<{stdout: string, stderr: string}>;
 | 
				
			||||||
 | 
					export function execFileAsync(command: string, args: string[]): Promise<{stdout: string, stderr: string}>;
 | 
				
			||||||
 | 
					export function execFileAsync(command: string, options: execOptions): Promise<{stdout: string, stderr: string}>;
 | 
				
			||||||
 | 
					export function execFileAsync(command: string, args: string[], options: execOptions): Promise<{stdout: string, stderr: string}>;
 | 
				
			||||||
 | 
					export function execFileAsync(command: string, args?: execOptions|string[], options?: execOptions) {
 | 
				
			||||||
 | 
					  let childOptions: execOptions = {};
 | 
				
			||||||
 | 
					  let childArgs: string[] = [];
 | 
				
			||||||
 | 
					  if (args instanceof Array) childArgs = args; else if (args instanceof Object) childOptions = args as execOptions;
 | 
				
			||||||
 | 
					  if (options) childOptions = options;
 | 
				
			||||||
 | 
					  if (childOptions?.env) childOptions.env = {...process.env, ...childOptions.env};
 | 
				
			||||||
 | 
					  return new Promise<{stdout: string, stderr: string}>((resolve, rejectExec) => {
 | 
				
			||||||
 | 
					    const child = execFile(command, childArgs, childOptions, (err, out, err2) => {if (err) return rejectExec(err);resolve({stdout: out, stderr: err2});});
 | 
				
			||||||
 | 
					    if (options?.stdio === "inherit") {
 | 
				
			||||||
 | 
					      child.stdout.on("data", data => process.stdout.write(data));
 | 
				
			||||||
 | 
					      child.stderr.on("data", data => process.stderr.write(data));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class customChild {
 | 
				
			||||||
 | 
					  private eventMiter = new EventEmitter({captureRejections: false});
 | 
				
			||||||
 | 
					  public child?: ChildProcess;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public kill(signal?: number|NodeJS.Signals) {if(this.child?.killed) return this.child?.killed;return this.child?.kill(signal);}
 | 
				
			||||||
 | 
					  public writeStdin(command: string, args?: string[]) {
 | 
				
			||||||
 | 
					    let toWrite = command;
 | 
				
			||||||
 | 
					    if (args?.length > 0) toWrite += (" "+args.join(" "));
 | 
				
			||||||
 | 
					    toWrite+="\n";
 | 
				
			||||||
 | 
					    this.child.stdin.write(toWrite);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private emit(act: "error", data: Error): this;
 | 
				
			||||||
 | 
					  private emit(act: "close", data: {code: number, signal: NodeJS.Signals}): this;
 | 
				
			||||||
 | 
					  private emit(act: "stdoutRaw", data: string): this;
 | 
				
			||||||
 | 
					  private emit(act: "stderrRaw", data: string): this;
 | 
				
			||||||
 | 
					  private emit(act: "breakStdout", data: string): this;
 | 
				
			||||||
 | 
					  private emit(act: "breakStderr", data: string): this;
 | 
				
			||||||
 | 
					  private emit(act: string, ...args: any[]): this {this.eventMiter.emit(act, ...args); return this;}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public on(act: "error", fn: (err: Error) => void): this;
 | 
				
			||||||
 | 
					  public on(act: "close", fn: (data: {code: number, signal: NodeJS.Signals}) => void): this;
 | 
				
			||||||
 | 
					  public on(act: "stdoutRaw", fn: (data: string) => void): this;
 | 
				
			||||||
 | 
					  public on(act: "stderrRaw", fn: (data: string) => void): this;
 | 
				
			||||||
 | 
					  public on(act: "breakStdout", fn: (data: string) => void): this;
 | 
				
			||||||
 | 
					  public on(act: "breakStderr", fn: (data: string) => void): this;
 | 
				
			||||||
 | 
					  public on(act: string, fn: (...args: any[]) => void): this {this.eventMiter.on(act, fn); return this;}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public once(act: "stdoutRaw", fn: (data: string) => void): this;
 | 
				
			||||||
 | 
					  public once(act: "stderrRaw", fn: (data: string) => void): this;
 | 
				
			||||||
 | 
					  public once(act: "breakStdout", fn: (data: string) => void): this;
 | 
				
			||||||
 | 
					  public once(act: "breakStderr", fn: (data: string) => void): this;
 | 
				
			||||||
 | 
					  public once(act: string, fn: (...args: any[]) => void): this {this.eventMiter.once(act, fn);return this;}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private tempLog = {};
 | 
				
			||||||
 | 
					  constructor(child: ChildProcess) {
 | 
				
			||||||
 | 
					    this.child = child;
 | 
				
			||||||
 | 
					    child.on("close", (code, signal) => this.emit("close", {code, signal}));
 | 
				
			||||||
 | 
					    child.on("exit", (code, signal) => this.emit("close", {code, signal}));
 | 
				
			||||||
 | 
					    child.on("error", err => this.emit("error", err));
 | 
				
			||||||
 | 
					    child.stdout.on("data", data => this.eventMiter.emit("stdoutRaw", data instanceof Buffer ? data.toString("utf8"):data));
 | 
				
			||||||
 | 
					    child.stderr.on("data", data => this.eventMiter.emit("stderrRaw", data instanceof Buffer ? data.toString("utf8"):data));
 | 
				
			||||||
 | 
					    // Storage tmp lines
 | 
				
			||||||
 | 
					    const parseLog = (to: "breakStdout"|"breakStderr", data: string): any => {
 | 
				
			||||||
 | 
					      if (this.tempLog[to] === undefined) this.tempLog[to] = "";
 | 
				
			||||||
 | 
					      const lines = data.split(/\r?\n/);
 | 
				
			||||||
 | 
					      if (lines.length === 1) return this.tempLog[to] += lines[0];
 | 
				
			||||||
 | 
					      const a = lines.pop();
 | 
				
			||||||
 | 
					      if (a !== "") lines.push(a);
 | 
				
			||||||
 | 
					      for (const line of lines) {
 | 
				
			||||||
 | 
					        if (!this.tempLog[to]) {
 | 
				
			||||||
 | 
					          // console.log(this.tempLog, lines);
 | 
				
			||||||
 | 
					          this.eventMiter.emit(to, line);
 | 
				
			||||||
 | 
					          continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        console.log(this.tempLog, lines);
 | 
				
			||||||
 | 
					        this.tempLog[to]+=line;
 | 
				
			||||||
 | 
					        this.eventMiter.emit(to, this.tempLog[to]);
 | 
				
			||||||
 | 
					        this.tempLog[to] = "";
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    child.stdout.on("data", data => parseLog("breakStdout", data));
 | 
				
			||||||
 | 
					    child.stderr.on("data", data => parseLog("breakStderr", data));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function exec(command: string): customChild;
 | 
				
			||||||
 | 
					export function exec(command: string, args: string[]): customChild;
 | 
				
			||||||
 | 
					export function exec(command: string, options: ObjectEncodingOptions & ExecFileOptions): customChild;
 | 
				
			||||||
 | 
					export function exec(command: string, args: string[], options: ObjectEncodingOptions & ExecFileOptions): customChild;
 | 
				
			||||||
 | 
					export function exec(command: string, args?: ObjectEncodingOptions & ExecFileOptions|string[], options?: ObjectEncodingOptions & ExecFileOptions): customChild {
 | 
				
			||||||
 | 
					  let childOptions: ObjectEncodingOptions & ExecFileOptions = {};
 | 
				
			||||||
 | 
					  let childArgs: string[] = [];
 | 
				
			||||||
 | 
					  if (args instanceof Array) childArgs = args; else if (args instanceof Object) childOptions = args as execOptions;
 | 
				
			||||||
 | 
					  if (options) childOptions = options;
 | 
				
			||||||
 | 
					  if (childOptions?.env) childOptions.env = {...process.env, ...childOptions.env};
 | 
				
			||||||
 | 
					  return new customChild(execFile(command, childArgs, childOptions));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,5 +1,3 @@
 | 
				
			|||||||
export default parse;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Parse Proprieties files and return a map of properties.
 | 
					 * Parse Proprieties files and return a map of properties.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
							
								
								
									
										83
									
								
								src/config/bedrock.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								src/config/bedrock.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,83 @@
 | 
				
			|||||||
 | 
					import { readFile } from "node:fs/promises";
 | 
				
			||||||
 | 
					import {} from "prismarine-nbt";
 | 
				
			||||||
 | 
					import { serverPath } from "../bedrock";
 | 
				
			||||||
 | 
					import * as Proprieties from "./Proprieties";
 | 
				
			||||||
 | 
					import * as path from 'node:path';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type bedrockParseProprieties = {
 | 
				
			||||||
 | 
					  "server-name": string,
 | 
				
			||||||
 | 
					  gamemode: "survival"|"creative"|"adventure",
 | 
				
			||||||
 | 
					  "force-gamemode": boolean,
 | 
				
			||||||
 | 
					  difficulty: "peaceful"|"easy"|"normal"|"hard",
 | 
				
			||||||
 | 
					  "allow-cheats": boolean,
 | 
				
			||||||
 | 
					  "max-players": number,
 | 
				
			||||||
 | 
					  "online-mode": boolean,
 | 
				
			||||||
 | 
					  "allow-list": boolean,
 | 
				
			||||||
 | 
					  "server-port": number,
 | 
				
			||||||
 | 
					  "server-portv6": number,
 | 
				
			||||||
 | 
					  "view-distance": number,
 | 
				
			||||||
 | 
					  "tick-distance": 4|6|8|10|12,
 | 
				
			||||||
 | 
					  "player-idle-timeout": number,
 | 
				
			||||||
 | 
					  "max-threads": number,
 | 
				
			||||||
 | 
					  "level-name": string,
 | 
				
			||||||
 | 
					  "level-seed": string|number|bigint|null,
 | 
				
			||||||
 | 
					  "default-player-permission-level": "visitor"|"member"|"operator",
 | 
				
			||||||
 | 
					  "texturepack-required": boolean,
 | 
				
			||||||
 | 
					  "content-log-file-enabled": boolean,
 | 
				
			||||||
 | 
					  "compression-threshold": number,
 | 
				
			||||||
 | 
					  "server-authoritative-movement": "client-auth"|"server-auth"|"server-auth-with-rewind",
 | 
				
			||||||
 | 
					  "player-movement-score-threshold": number,
 | 
				
			||||||
 | 
					  "player-movement-action-direction-threshold": number,
 | 
				
			||||||
 | 
					  "player-movement-distance-threshold": number,
 | 
				
			||||||
 | 
					  "player-movement-duration-threshold-in-ms": number,
 | 
				
			||||||
 | 
					  "correct-player-movement": boolean,
 | 
				
			||||||
 | 
					  "server-authoritative-block-breaking": boolean,
 | 
				
			||||||
 | 
					  "chat-restriction": "None"|"Dropped"|"Disabled",
 | 
				
			||||||
 | 
					  "disable-player-interaction": boolean
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function getConfig(): Promise<bedrockParseProprieties> {
 | 
				
			||||||
 | 
					  return Proprieties.parse(await readFile(path.join(serverPath, "server.proprieties"), "utf8")) as bedrockParseProprieties;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const keys = RegExp("("+(["server-name", "gamemode", "force-gamemode", "difficulty", "allow-cheats", "max-players", "online-mode", "allow-list", "server-port", "server-portv6", "view-distance", "tick-distance", "player-idle-timeout", "max-threads", "level-name", "level-seed", "default-player-permission-level", "texturepack-required", "content-log-file-enabled", "compression-threshold", "server-authoritative-movement", "player-movement-score-threshold", "player-movement-action-direction-threshold", "player-movement-distance-threshold", "player-movement-duration-threshold-in-ms", "correct-player-movement", "server-authoritative-block-breaking", "chat-restriction", "disable-player-interaction"]).join("|")+")")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function createConfig(config: bedrockParseProprieties): string {
 | 
				
			||||||
 | 
					  let configString = "";
 | 
				
			||||||
 | 
					  for (const key of Object.keys(config).filter(a => keys.test(a))) configString += `${key}=${config[key]}\n`;
 | 
				
			||||||
 | 
					  return configString.trim();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					console.log(createConfig({
 | 
				
			||||||
 | 
					  "server-name": "string",
 | 
				
			||||||
 | 
					  gamemode: "survival",
 | 
				
			||||||
 | 
					  "force-gamemode": true,
 | 
				
			||||||
 | 
					  difficulty: "easy",
 | 
				
			||||||
 | 
					  "allow-cheats": false,
 | 
				
			||||||
 | 
					  "max-players": 20,
 | 
				
			||||||
 | 
					  "online-mode": false,
 | 
				
			||||||
 | 
					  "allow-list": true,
 | 
				
			||||||
 | 
					  "server-port": 19135,
 | 
				
			||||||
 | 
					  "server-portv6": 19136,
 | 
				
			||||||
 | 
					  "view-distance": 32,
 | 
				
			||||||
 | 
					  "tick-distance": 8,
 | 
				
			||||||
 | 
					  "player-idle-timeout": 0,
 | 
				
			||||||
 | 
					  "max-threads": 16,
 | 
				
			||||||
 | 
					  "level-name": "string",
 | 
				
			||||||
 | 
					  "level-seed": null,
 | 
				
			||||||
 | 
					  "default-player-permission-level": "member",
 | 
				
			||||||
 | 
					  "texturepack-required": true,
 | 
				
			||||||
 | 
					  "content-log-file-enabled": false,
 | 
				
			||||||
 | 
					  "compression-threshold": 0,
 | 
				
			||||||
 | 
					  "server-authoritative-movement": "server-auth-with-rewind",
 | 
				
			||||||
 | 
					  "player-movement-score-threshold": 0.9,
 | 
				
			||||||
 | 
					  "player-movement-action-direction-threshold": 0.6,
 | 
				
			||||||
 | 
					  "player-movement-distance-threshold": 0.6,
 | 
				
			||||||
 | 
					  "player-movement-duration-threshold-in-ms": 0.6,
 | 
				
			||||||
 | 
					  "correct-player-movement": false,
 | 
				
			||||||
 | 
					  "server-authoritative-block-breaking": false,
 | 
				
			||||||
 | 
					  "chat-restriction": "Disabled",
 | 
				
			||||||
 | 
					  "disable-player-interaction": false
 | 
				
			||||||
 | 
					}));
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
							
								
								
									
										186
									
								
								src/git.ts
									
									
									
									
									
								
							
							
						
						
									
										186
									
								
								src/git.ts
									
									
									
									
									
								
							@@ -1,186 +0,0 @@
 | 
				
			|||||||
import * as child_process from "node:child_process";
 | 
					 | 
				
			||||||
import * as util from "node:util";
 | 
					 | 
				
			||||||
// import * as fs from "node:fs/promises";
 | 
					 | 
				
			||||||
import * as fsOld from "node:fs";
 | 
					 | 
				
			||||||
import * as path from "node:path";
 | 
					 | 
				
			||||||
import admZip from "adm-zip";
 | 
					 | 
				
			||||||
const execFile = util.promisify(child_process.execFile);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type fnWithData = (err: Error|undefined, data: string) => void;
 | 
					 | 
				
			||||||
export type fn = (err?: Error) => void;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class git {
 | 
					 | 
				
			||||||
  public readonly repoRoot: string;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  public async status() {
 | 
					 | 
				
			||||||
    const data = await execFile("git", ["status", "-s"], {cwd: this.repoRoot});
 | 
					 | 
				
			||||||
    const status: {file: string, action: "new"|"modificated"|"deleted"|"under"}[] = [];
 | 
					 | 
				
			||||||
    for (const line of data.stdout.split(/\r?\n/g)) {
 | 
					 | 
				
			||||||
      const match = line.trim().match(/^(.*)\s+(.*)$/);
 | 
					 | 
				
			||||||
      if (!match) continue;
 | 
					 | 
				
			||||||
      const [, action, filePath] = match;
 | 
					 | 
				
			||||||
      if (action.trim() === "??") status.push({file: path.resolve(this.repoRoot, filePath), action: "new"});
 | 
					 | 
				
			||||||
      else if (action.trim() === "M") status.push({file: path.resolve(this.repoRoot, filePath), action: "modificated"});
 | 
					 | 
				
			||||||
      else if (action.trim() === "D") status.push({file: path.resolve(this.repoRoot, filePath), action: "deleted"});
 | 
					 | 
				
			||||||
      else status.push({file: path.resolve(this.repoRoot, filePath), action: "under"});
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return status;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  public add(files: string|string[], callback: (error?: Error) => void) {
 | 
					 | 
				
			||||||
    const args = ["add"];
 | 
					 | 
				
			||||||
    if (typeof files === "string") args.push(files); else if (files instanceof Array) args.push(...files); else throw new Error("Files is not a string or array");
 | 
					 | 
				
			||||||
    this.status().then(async repoStatus => {
 | 
					 | 
				
			||||||
      if (repoStatus.length === 0) throw new Error("No changes");
 | 
					 | 
				
			||||||
      await execFile("git", args, {cwd: this.repoRoot});
 | 
					 | 
				
			||||||
    }).then(() => callback()).catch(err => callback(err));
 | 
					 | 
				
			||||||
    return this;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  public addSync(files: string|string[]): Promise<void> {
 | 
					 | 
				
			||||||
    return new Promise<void>((done, reject) => this.add(files, (err) => !!err ? reject(err) : done()));
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  public commit(message: string, body: string[], callback: fn): this;
 | 
					 | 
				
			||||||
  public commit(message: string, callback: (error: Error) => void): this;
 | 
					 | 
				
			||||||
  public commit(message: string, body: string[]): this;
 | 
					 | 
				
			||||||
  public commit(message: string): this;
 | 
					 | 
				
			||||||
  public commit(message: string, body?: string[]|fn, callback?: fn): this {
 | 
					 | 
				
			||||||
    if (!message) throw new Error("No commit message");
 | 
					 | 
				
			||||||
    else if (message.length > 72) throw new Error("Message length is long");
 | 
					 | 
				
			||||||
    const messages = ["-m", message];
 | 
					 | 
				
			||||||
    if (typeof body === "function") {callback = body; body = undefined;}
 | 
					 | 
				
			||||||
    if (body instanceof Array) messages.forEach(message => messages.push("-m", message));
 | 
					 | 
				
			||||||
    execFile("git", ["commit", "-m", ...messages], {cwd: this.repoRoot}).then(() => callback(undefined)).catch(err => callback(err));
 | 
					 | 
				
			||||||
    return this;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  public commitSync(message: string): Promise<void>;
 | 
					 | 
				
			||||||
  public commitSync(message: string, body: string[]): Promise<void>;
 | 
					 | 
				
			||||||
  public commitSync(message: string, body?: string[]): Promise<void> {
 | 
					 | 
				
			||||||
    return new Promise<void>((done, reject) => this.commit(message, body, (err) => !!err ? reject(err) : done()));
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  public push(branch: string, remote: string, force: boolean, callback: fn): this;
 | 
					 | 
				
			||||||
  public push(branch: string, remote: string, force: boolean): this;
 | 
					 | 
				
			||||||
  public push(branch: string, remote: string): this;
 | 
					 | 
				
			||||||
  public push(branch: string): this;
 | 
					 | 
				
			||||||
  public push(): this;
 | 
					 | 
				
			||||||
  public push(branch?: string, remote?: string, force?: boolean, callback?: fn): this {
 | 
					 | 
				
			||||||
    this.remote("show", async (err, data) => {
 | 
					 | 
				
			||||||
      if (err) if (callback) return callback(err); else throw err;
 | 
					 | 
				
			||||||
      if (data.length === 0) return callback(new Error("No remotes"));
 | 
					 | 
				
			||||||
      const args = ["push"];
 | 
					 | 
				
			||||||
      if (branch) args.push(branch);
 | 
					 | 
				
			||||||
      if (remote) args.push(remote);
 | 
					 | 
				
			||||||
      if (force) args.push("--force");
 | 
					 | 
				
			||||||
      await execFile("git", args, {cwd: this.repoRoot});
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    return this;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  public pushSync(branch: string, remote: string, force: boolean): Promise<void>;
 | 
					 | 
				
			||||||
  public pushSync(branch: string, remote: string): Promise<void>;
 | 
					 | 
				
			||||||
  public pushSync(branch: string): Promise<void>;
 | 
					 | 
				
			||||||
  public pushSync(): Promise<void>;
 | 
					 | 
				
			||||||
  public pushSync(branch?: string, remote?: string, force?: boolean): Promise<void> {
 | 
					 | 
				
			||||||
    return new Promise<void>((done, reject) => this.push(branch, remote, force, (err) => !!err ? reject(err) : done()));
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  public getZip(gitPath: string = "/", callback: (zipDate: Buffer) => void) {
 | 
					 | 
				
			||||||
    if(!!gitPath) if (!fsOld.existsSync(path.join(this.repoRoot, gitPath))) throw new Error("Path does not exist");
 | 
					 | 
				
			||||||
    new Promise<void>(done => {
 | 
					 | 
				
			||||||
      const newZipFile = new admZip();
 | 
					 | 
				
			||||||
      if (!gitPath) gitPath = "/";
 | 
					 | 
				
			||||||
      newZipFile.addLocalFolder(path.normalize(path.join(this.repoRoot)), "/", (filename) => !/\.git/.test(filename));
 | 
					 | 
				
			||||||
      callback(newZipFile.toBuffer());
 | 
					 | 
				
			||||||
      done();
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    return this;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  public remote(action: "remove", remote: string): this;
 | 
					 | 
				
			||||||
  public remote(action: "setHead"): this;
 | 
					 | 
				
			||||||
  public remote(action: "prune"): this;
 | 
					 | 
				
			||||||
  public remote(action: "show", callback: (error: Error|undefined, data: {name: string, url: string, type?: string}[]) => void): this;
 | 
					 | 
				
			||||||
  public remote(action: "add", config: {gitUrl: string, remoteName?: string, auth?: {username: string, password: string}, user?: {name: string, email: string}}, callback: (error: Error|undefined, data: {url: string, auth?: {username: string, password?: string}}) => void): this;
 | 
					 | 
				
			||||||
  public remote(action: "show"|"remove"|"prune"|"setHead"|"add", ...args: any[]) {
 | 
					 | 
				
			||||||
    if (action === "show") {
 | 
					 | 
				
			||||||
      if (typeof args[0] !== "function") throw new Error("Callback is not a function");
 | 
					 | 
				
			||||||
      execFile("git", ["remote", "show"], {cwd: this.repoRoot}).then(remotes => {
 | 
					 | 
				
			||||||
        const result = remotes.stdout.split(/\r?\n/g).filter(x => !!x.trim()).map(x => {
 | 
					 | 
				
			||||||
          const match = x.trim().match(/^(.*)\s+(.*)\s+(\(\d+\))$/) || x.trim().match(/^(.*)\s+(.*)$/);
 | 
					 | 
				
			||||||
          if (!match) return null;
 | 
					 | 
				
			||||||
          const [, name, url, type] = match;
 | 
					 | 
				
			||||||
          return {name, url, type: type ? type.trim() : undefined} as {name: string, url: string, type?: string};
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        args[0](undefined, result.filter(x => x !== null));
 | 
					 | 
				
			||||||
      }).catch(error => args[0](error));
 | 
					 | 
				
			||||||
    } else if (action === "prune") {
 | 
					 | 
				
			||||||
      if (args[0]) execFile("git", ["remote", "prune", "--dry-run", args[0]], {cwd: this.repoRoot});
 | 
					 | 
				
			||||||
      else {
 | 
					 | 
				
			||||||
        execFile("git", ["remote", "show"], {cwd: this.repoRoot}).then(async ({stdout}) => {
 | 
					 | 
				
			||||||
          const remotes = stdout.split(/\r?\n/g).filter(x => !!x.trim()).map(x => x.trim());
 | 
					 | 
				
			||||||
          for (const remote of remotes) {
 | 
					 | 
				
			||||||
            await execFile("git", ["remote", "prune", "--dry-run", remote], {cwd: this.repoRoot});
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } else if (action === "setHead") execFile("git", ["remote", "set-head", "--auto"], {cwd: this.repoRoot});
 | 
					 | 
				
			||||||
    else if (action === "remove") {execFile("git", ["remote", "remove", args[0]], {cwd: this.repoRoot});}
 | 
					 | 
				
			||||||
    else if (action === "add") {
 | 
					 | 
				
			||||||
      if (typeof args[1] !== "function") throw new Error("Callback is not a function");
 | 
					 | 
				
			||||||
      if (typeof args[0] !== "object" && args[0] instanceof Object) throw new Error("Config is not an object");
 | 
					 | 
				
			||||||
      if (!args[0].gitUrl) throw new Error("No git url");
 | 
					 | 
				
			||||||
      if (args[0].user) {
 | 
					 | 
				
			||||||
        if (!args[0].user.name) throw new Error("No user name");
 | 
					 | 
				
			||||||
        if (!args[0].user.email) throw new Error("No user email");
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      if (args[0].auth) {
 | 
					 | 
				
			||||||
        if (!args[0].auth.username) throw new Error("No auth username");
 | 
					 | 
				
			||||||
        if (!args[0].auth.password) console.warn("Auth password/token is not set, check your config if exist credentials authentication");
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      const config = {
 | 
					 | 
				
			||||||
        url: args[0].gitUrl as string,
 | 
					 | 
				
			||||||
        remoteName: (!!args[0].remoteName ? args[0].name : "origin") as string,
 | 
					 | 
				
			||||||
        user: args[0].user as undefined|{name: string, email: string},
 | 
					 | 
				
			||||||
        auth: args[0].auth as undefined|{username: string, password?: string}
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
      new Promise<void>(async (done): Promise<any> => {
 | 
					 | 
				
			||||||
        const urlParse = new URL(config.url);
 | 
					 | 
				
			||||||
        let url = urlParse.protocol + "//";
 | 
					 | 
				
			||||||
        if (config.auth) {
 | 
					 | 
				
			||||||
          url += config.auth.username;
 | 
					 | 
				
			||||||
          if (config.auth.password) url += ":" + config.auth.password;
 | 
					 | 
				
			||||||
          url += "@";
 | 
					 | 
				
			||||||
        } else if (urlParse.username) {
 | 
					 | 
				
			||||||
          url += urlParse.username;
 | 
					 | 
				
			||||||
          if (urlParse.password) url += ":" + urlParse.password;
 | 
					 | 
				
			||||||
          url += "@";
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        url += urlParse.hostname+urlParse.pathname+urlParse.search;
 | 
					 | 
				
			||||||
        await execFile("git", ["remote", "add", "-f", "--tags", config.remoteName, url], {cwd: this.repoRoot});
 | 
					 | 
				
			||||||
        // Done
 | 
					 | 
				
			||||||
        return done();
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return this;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  /**
 | 
					 | 
				
			||||||
   * Init repository maneger and if not exists create a empty repository
 | 
					 | 
				
			||||||
   */
 | 
					 | 
				
			||||||
  constructor(gitPath: string, config?: {remoteUrl?: string}) {
 | 
					 | 
				
			||||||
    this.repoRoot = path.resolve(gitPath);
 | 
					 | 
				
			||||||
    Object.defineProperty(this, "repoRoot", {value: this.repoRoot, enumerable: true, configurable: false, writable: false}); // Make it non-writable and non-configurable to prevent accidental changes
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!fsOld.existsSync(this.repoRoot)) {
 | 
					 | 
				
			||||||
      fsOld.mkdirSync(this.repoRoot, {recursive: true});
 | 
					 | 
				
			||||||
      child_process.execFileSync("git", ["init", "-b", "main"], {cwd: this.repoRoot});
 | 
					 | 
				
			||||||
    } else if (!fsOld.existsSync(path.join(this.repoRoot, ".git"))) child_process.execFileSync("git", ["init", "-b", "main"], {cwd: this.repoRoot});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Set url
 | 
					 | 
				
			||||||
    if (config?.remoteUrl) this.remote("add", {gitUrl: config.remoteUrl}, () => execFile("git", ["pull", "--all", "--rebase"], {cwd: this.repoRoot}));
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
export default git;
 | 
					 | 
				
			||||||
							
								
								
									
										123
									
								
								src/globalPlatfroms.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								src/globalPlatfroms.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,123 @@
 | 
				
			|||||||
 | 
					import type { customChild } from "./childPromisses";
 | 
				
			||||||
 | 
					import { EventEmitter } from "node:events";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type playerClass = {[player: string]: {action: "connect"|"disconnect"|"unknown"; date: Date; history: Array<{action: "connect"|"disconnect"|"unknown"; date: Date}>}};
 | 
				
			||||||
 | 
					export type playerBase = {playerName: string, connectTime: Date, xuid?: string};
 | 
				
			||||||
 | 
					export type actionsPlayer = {
 | 
				
			||||||
 | 
					  name: "playerConnect"|"playerDisconnect"|"playerUnknown",
 | 
				
			||||||
 | 
					  callback: (data: string, done: (player: playerBase) => void) => void
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type portListen = {port: number, host?: string, type: "TCP"|"UDP"|"TCP/UDP", protocol: "IPv4"|"IPv6"|"IPV4/IPv6"|"Unknown"};
 | 
				
			||||||
 | 
					export type actionsPort = {
 | 
				
			||||||
 | 
					  name: "portListening",
 | 
				
			||||||
 | 
					  callback: (data: string, done: (portInfo: portListen) => void) => void
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type serverStarted = Date;
 | 
				
			||||||
 | 
					export type actionsServerStarted = {
 | 
				
			||||||
 | 
					  name: "serverStarted",
 | 
				
			||||||
 | 
					  callback: (data: string, done: (started: serverStarted) => void) => void
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type actionsServerStop = {
 | 
				
			||||||
 | 
					  name: "serverStop",
 | 
				
			||||||
 | 
					  run: (childProcess: customChild) => void
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type actionTp = {
 | 
				
			||||||
 | 
					  name: "tp",
 | 
				
			||||||
 | 
					  run: (childProcess: customChild, x: number|string, y: number|string, z: number|string) => void
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type actionRun = actionsServerStop|actionTp;
 | 
				
			||||||
 | 
					export type actionCallback = actionsPlayer|actionsPort|actionsServerStarted|actionsServerStarted;
 | 
				
			||||||
 | 
					export type actionConfig = actionCallback|actionRun;
 | 
				
			||||||
 | 
					export class actions {
 | 
				
			||||||
 | 
					  private events = new EventEmitter({captureRejections: false});
 | 
				
			||||||
 | 
					  public childProcess: customChild;
 | 
				
			||||||
 | 
					  private stopServerFunction?: (childProcess: customChild) => void;
 | 
				
			||||||
 | 
					  private tpfunction?: (childProcess: customChild, x: number|string, y: number|string, z: number|string) => void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public on(act: "playerConnect"|"playerDisconnect"|"playerUnknown", fn: (data: playerBase) => void): this;
 | 
				
			||||||
 | 
					  public on(act: "portListening", fn: (data: portListen) => void): this;
 | 
				
			||||||
 | 
					  public on(act: "serverStarted", fn: (data: serverStarted) => void): this;
 | 
				
			||||||
 | 
					  public on(act: "log_stderr", fn: (data: string) => void): this;
 | 
				
			||||||
 | 
					  public on(act: "log_stdout", fn: (data: string) => void): this;
 | 
				
			||||||
 | 
					  public on(act: "exit", fn: (data: {code: number, signal: NodeJS.Signals}) => void): this;
 | 
				
			||||||
 | 
					  public on(act: string, fn: (...args: any[]) => void) {this.events.on(act, fn); return this;}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public once(act: "playerConnect"|"playerDisconnect"|"playerUnknown", fn: (data: playerBase) => void): this;
 | 
				
			||||||
 | 
					  public once(act: "portListening", fn: (data: portListen) => void): this;
 | 
				
			||||||
 | 
					  public once(act: "serverStarted", fn: (data: serverStarted) => void): this;
 | 
				
			||||||
 | 
					  public once(act: "log_stderr", fn: (data: string) => void): this;
 | 
				
			||||||
 | 
					  public once(act: "log_stdout", fn: (data: string) => void): this;
 | 
				
			||||||
 | 
					  public once(act: "exit", fn: (data: {code: number, signal: NodeJS.Signals}) => void): this;
 | 
				
			||||||
 | 
					  public once(act: string, fn: (...args: any[]) => void) {this.events.once(act, fn); return this;}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public stopServer() {
 | 
				
			||||||
 | 
					    if (typeof this.stopServer === "undefined") this.childProcess.kill("SIGKILL");
 | 
				
			||||||
 | 
					    this.stopServerFunction(this.childProcess);
 | 
				
			||||||
 | 
					    return this;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public tp(x: number|string = 0, y: number|string = 0, z: number|string = 0) {
 | 
				
			||||||
 | 
					    if (typeof this.stopServer === "undefined") throw new Error("TP command not configured!");
 | 
				
			||||||
 | 
					    this.tpfunction(this.childProcess, x, y, z);
 | 
				
			||||||
 | 
					    return this;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public runCommand(...command: Array<string|number>) {
 | 
				
			||||||
 | 
					    const psCommand = command.map(a => String(a));
 | 
				
			||||||
 | 
					    this.childProcess.writeStdin(psCommand.join(" "));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public portListening: portListen[] = [];
 | 
				
			||||||
 | 
					  public playerActions: playerClass = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(child: customChild, config: actionConfig[]) {
 | 
				
			||||||
 | 
					    this.childProcess = child;
 | 
				
			||||||
 | 
					    child.on("close", data => this.events.emit("exit", data));
 | 
				
			||||||
 | 
					    child.on("breakStdout", data => this.events.emit("log_stdout", data));
 | 
				
			||||||
 | 
					    child.on("breakStderr", data => this.events.emit("log_stderr", data));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.on("portListening", data => this.portListening.push(data));
 | 
				
			||||||
 | 
					    this.on("playerConnect", (data): any => {
 | 
				
			||||||
 | 
					      if (!this.playerActions[data.playerName]) return this.playerActions[data.playerName] = {
 | 
				
			||||||
 | 
					        action: "connect",
 | 
				
			||||||
 | 
					        date: data.connectTime,
 | 
				
			||||||
 | 
					        history: [{action: "connect", date: data.connectTime}]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      this.playerActions[data.playerName].action = "connect";
 | 
				
			||||||
 | 
					      this.playerActions[data.playerName].date = data.connectTime;
 | 
				
			||||||
 | 
					      this.playerActions[data.playerName].history.push({action: "connect", date: data.connectTime});
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    this.on("playerDisconnect", (data): any => {
 | 
				
			||||||
 | 
					      if (!this.playerActions[data.playerName]) return this.playerActions[data.playerName] = {
 | 
				
			||||||
 | 
					        action: "disconnect",
 | 
				
			||||||
 | 
					        date: data.connectTime,
 | 
				
			||||||
 | 
					        history: [{action: "disconnect", date: data.connectTime}]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      this.playerActions[data.playerName].action = "disconnect";
 | 
				
			||||||
 | 
					      this.playerActions[data.playerName].date = data.connectTime;
 | 
				
			||||||
 | 
					      this.playerActions[data.playerName].history.push({action: "disconnect", date: data.connectTime});
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    this.on("playerUnknown", (data): any => {
 | 
				
			||||||
 | 
					      if (!this.playerActions[data.playerName]) return this.playerActions[data.playerName] = {
 | 
				
			||||||
 | 
					        action: "unknown",
 | 
				
			||||||
 | 
					        date: data.connectTime,
 | 
				
			||||||
 | 
					        history: [{action: "unknown", date: data.connectTime}]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      this.playerActions[data.playerName].action = "unknown";
 | 
				
			||||||
 | 
					      this.playerActions[data.playerName].date = data.connectTime;
 | 
				
			||||||
 | 
					      this.playerActions[data.playerName].history.push({action: "unknown", date: data.connectTime});
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const actions = config.filter((a: actionCallback) => typeof a?.callback === "function") as actionCallback[];
 | 
				
			||||||
 | 
					    child.on("breakStdout", data => actions.forEach(fn => fn.callback(data, (...args: any[]) => this.events.emit(fn.name, ...args))));
 | 
				
			||||||
 | 
					    child.on("breakStderr", data => actions.forEach(fn => fn.callback(data, (...args: any[]) => this.events.emit(fn.name, ...args))));
 | 
				
			||||||
 | 
					    for (const action of (config.filter((a: actionRun) => typeof a?.run === "function") as actionRun[])) {
 | 
				
			||||||
 | 
					      if (action.name === "serverStop") this.stopServerFunction = action.run;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,69 +0,0 @@
 | 
				
			|||||||
import { CronJob } from "cron";
 | 
					 | 
				
			||||||
export type Platform = "bedrock"|"java"|"pocketmine"|"spigot";
 | 
					 | 
				
			||||||
export const PlatformArray = ["bedrock", "java", "pocketmine", "spigot"];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Bds Session on declaretion function types
 | 
					 | 
				
			||||||
export type bdsSessionCommands = {
 | 
					 | 
				
			||||||
  /** Exec any commands in server */
 | 
					 | 
				
			||||||
  execCommand: (...command: Array<string|number>) => bdsSessionCommands;
 | 
					 | 
				
			||||||
  /** Teleport player to Destination */
 | 
					 | 
				
			||||||
  tpPlayer: (player: string, x: number, y: number, z: number) => bdsSessionCommands;
 | 
					 | 
				
			||||||
  /** Change world gamemode */
 | 
					 | 
				
			||||||
  worldGamemode: (gamemode: "survival"|"creative"|"hardcore") => bdsSessionCommands;
 | 
					 | 
				
			||||||
  /** Change gamemode to specified player */
 | 
					 | 
				
			||||||
  userGamemode: (player: string, gamemode: "survival"|"creative"|"hardcore") => bdsSessionCommands;
 | 
					 | 
				
			||||||
  /** Stop Server */
 | 
					 | 
				
			||||||
  stop: () => Promise<number|null>;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
export type startServerOptions = {
 | 
					 | 
				
			||||||
  /** Save only worlds/maps without server software - (Beta) */
 | 
					 | 
				
			||||||
  storageOnlyWorlds?: boolean;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
export type playerAction1 = {player: string, Date: Date; xuid?: string|undefined}
 | 
					 | 
				
			||||||
export type playerAction2 = playerAction1 & {action: "connect"|"disconnect"|"unknown"}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Server events
 | 
					 | 
				
			||||||
export type serverListen = {port: number; protocol?: "TCP"|"UDP"; version?: "IPv4"|"IPv6"|"IPv4/IPv6"};
 | 
					 | 
				
			||||||
export type playerObject = {[player: string]: {action: "connect"|"disconnect"|"unknown"; date: Date; history: Array<{action: "connect"|"disconnect"|"unknown"; date: Date}>}};
 | 
					 | 
				
			||||||
export interface serverOn {
 | 
					 | 
				
			||||||
  (act: "started", fn: (data: Date) => void);
 | 
					 | 
				
			||||||
  (act: "err", fn: (data: Error|number) => void);
 | 
					 | 
				
			||||||
  (act: "closed", fn: (data: number) => void);
 | 
					 | 
				
			||||||
  (act: "player_ban", fn: (data: playerAction1) => void);
 | 
					 | 
				
			||||||
  (act: "player", fn: (data: playerAction2) => void);
 | 
					 | 
				
			||||||
  (act: "player_connect", fn: (data: playerAction1) => void);
 | 
					 | 
				
			||||||
  (act: "player_disconnect", fn: (data: playerAction1) => void);
 | 
					 | 
				
			||||||
  (act: "player_unknown", fn: (data: playerAction1) => void);
 | 
					 | 
				
			||||||
  (act: "port_listen", fn: (data: {port: number; protocol?: "TCP"|"UDP"; version?: "IPv4"|"IPv6"|"IPv4/IPv6"}) => void);
 | 
					 | 
				
			||||||
  (act: "log", fn: (data: string) => void);
 | 
					 | 
				
			||||||
  (act: "log_stdout", fn: (data: string) => void);
 | 
					 | 
				
			||||||
  (act: "log_stderr", fn: (data: string) => void);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Type to Bds Session (All Platforms)
 | 
					 | 
				
			||||||
export type BdsSession = {
 | 
					 | 
				
			||||||
  /** Server Session ID */
 | 
					 | 
				
			||||||
  id: string;
 | 
					 | 
				
			||||||
  logFile?: string;
 | 
					 | 
				
			||||||
  /** register cron job to create backups */
 | 
					 | 
				
			||||||
  creteBackup: (crontime: string|Date, option?: {type: "zip", pathStorage?: string}) => CronJob;
 | 
					 | 
				
			||||||
  /** Get server players historic connections */
 | 
					 | 
				
			||||||
  Player: playerObject;
 | 
					 | 
				
			||||||
  /** Get Server ports. listening. */
 | 
					 | 
				
			||||||
  ports: Array<serverListen>;
 | 
					 | 
				
			||||||
  /** if exists server map get world seed, fist map not get seed */
 | 
					 | 
				
			||||||
  seed?: string|number;
 | 
					 | 
				
			||||||
  /** Basic server functions. */
 | 
					 | 
				
			||||||
  commands: bdsSessionCommands;
 | 
					 | 
				
			||||||
  /** Server actions, example on avaible to connect or banned¹ */
 | 
					 | 
				
			||||||
  server: {
 | 
					 | 
				
			||||||
    /** Server actions */
 | 
					 | 
				
			||||||
    on: serverOn;
 | 
					 | 
				
			||||||
    /** Server actions */
 | 
					 | 
				
			||||||
    once: serverOn;
 | 
					 | 
				
			||||||
    /** Server Started date */
 | 
					 | 
				
			||||||
    startDate: Date;
 | 
					 | 
				
			||||||
    /** Server Started */
 | 
					 | 
				
			||||||
    started: boolean;
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
							
								
								
									
										21
									
								
								src/httpRequest.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/httpRequest.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					import axios from "axios";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function getBuffer(url: string, config?: {body?: any, header?: {[key: string]: string}}): Promise<Buffer> {
 | 
				
			||||||
 | 
					  const Headers = {};
 | 
				
			||||||
 | 
					  let Body: any;
 | 
				
			||||||
 | 
					  if (config) {
 | 
				
			||||||
 | 
					    if (config.header) Object.keys(config.header).forEach(key => Headers[key] = config.header[key]);
 | 
				
			||||||
 | 
					    if (config.body) Body = config.body;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (typeof fetch === "undefined") return axios.get(url, {
 | 
				
			||||||
 | 
					    responseEncoding: "arraybuffer",
 | 
				
			||||||
 | 
					    responseType: "arraybuffer",
 | 
				
			||||||
 | 
					    headers: Headers,
 | 
				
			||||||
 | 
					    data: Body
 | 
				
			||||||
 | 
					  }).then(({data}) => Buffer.from(data));
 | 
				
			||||||
 | 
					  return fetch(url, {
 | 
				
			||||||
 | 
					    method: "GET",
 | 
				
			||||||
 | 
					    body: typeof Body === "object" ? JSON.stringify(Body, null, 2):Body,
 | 
				
			||||||
 | 
					    headers: Headers
 | 
				
			||||||
 | 
					  }).then(res => res.arrayBuffer()).then(res => Buffer.from(res));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,5 +1,4 @@
 | 
				
			|||||||
export * as globalType from "./globalType";
 | 
					export * as Bedrock from "./bedrock";
 | 
				
			||||||
export * as bedrock from "./bedrock/index";
 | 
					export * as Java from "./java";
 | 
				
			||||||
export * as pocketmine from "./pocketmine/index";
 | 
					export * as Spigot from "./spigot";
 | 
				
			||||||
export * as java from "./java/index";
 | 
					export * as PocketmineMP from "./pocketmine";
 | 
				
			||||||
export * as spigot from "./spigot/index";
 | 
					 | 
				
			||||||
							
								
								
									
										49
									
								
								src/java.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/java.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					import * as path from "node:path";
 | 
				
			||||||
 | 
					import * as fs from "node:fs/promises";
 | 
				
			||||||
 | 
					import * as fsOld from "node:fs";
 | 
				
			||||||
 | 
					import { getJavaJar } from "@the-bds-maneger/server_versions";
 | 
				
			||||||
 | 
					import { serverRoot } from "./pathControl";
 | 
				
			||||||
 | 
					import { exec } from "./childPromisses";
 | 
				
			||||||
 | 
					import { actions, actionConfig } from './globalPlatfroms';
 | 
				
			||||||
 | 
					export const serverPath = path.join(serverRoot, "java");
 | 
				
			||||||
 | 
					const jarPath = path.join(serverPath, "server.jar");
 | 
				
			||||||
 | 
					export const started = /\[.*\].*\s+Done\s+\(.*\)\!.*/;
 | 
				
			||||||
 | 
					export const portListen = /Starting\s+Minecraft\s+server\s+on\s+(.*)\:(\d+)/;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function installServer(version: string|boolean) {
 | 
				
			||||||
 | 
					  if (!fsOld.existsSync(serverPath)) await fs.mkdir(serverPath, {recursive: true});
 | 
				
			||||||
 | 
					  await fs.writeFile(jarPath, await getJavaJar(version));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const serverConfig: actionConfig[] = [
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    name: "serverStarted",
 | 
				
			||||||
 | 
					    callback(data, done) {
 | 
				
			||||||
 | 
					      // [22:35:26] [Server thread/INFO]: Done (6.249s)! For help, type "help"
 | 
				
			||||||
 | 
					      if (started.test(data)) done(new Date());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    name: "portListening",
 | 
				
			||||||
 | 
					    callback(data, done) {
 | 
				
			||||||
 | 
					      const portParse = data.match(portListen);
 | 
				
			||||||
 | 
					      if (!!portParse) done({port: parseInt(portParse[2]), host: (portParse[1]||"").trim()||undefined, type: "TCP", protocol: "IPV4/IPv6",});
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    name: "serverStop",
 | 
				
			||||||
 | 
					    run: (child) => child.writeStdin("stop")
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function startServer(Config?: {maxMemory?: number, minMemory?: number}) {
 | 
				
			||||||
 | 
					  if (!fsOld.existsSync(jarPath)) throw new Error("Install server fist.");
 | 
				
			||||||
 | 
					  const command = "java";
 | 
				
			||||||
 | 
					  const args = ["-jar"];
 | 
				
			||||||
 | 
					  if (Config) {
 | 
				
			||||||
 | 
					    if (Config?.minMemory) args.push(`-Xms${Config?.minMemory}m`);
 | 
				
			||||||
 | 
					    if (Config?.maxMemory) args.push(`-Xmx${Config?.maxMemory}m`);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  args.push(jarPath);
 | 
				
			||||||
 | 
					  return new actions(exec(command, args, {cwd: serverPath, maxBuffer: Infinity}), serverConfig);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,40 +0,0 @@
 | 
				
			|||||||
import * as fsOld from "node:fs";
 | 
					 | 
				
			||||||
import * as fs from "node:fs/promises";
 | 
					 | 
				
			||||||
import * as path from "node:path";
 | 
					 | 
				
			||||||
import admZip from "adm-zip";
 | 
					 | 
				
			||||||
import { serverRoot } from "../pathControl";
 | 
					 | 
				
			||||||
const javaPath = path.join(serverRoot, "java");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const filesFoldertoIgnore = ["Server.jar", "eula.txt", "libraries", "logs", "usercache.json", "versions"];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Create backup for Worlds and Settings
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export async function CreateBackup(): Promise<Buffer> {
 | 
					 | 
				
			||||||
  if (!(fsOld.existsSync(javaPath))) throw new Error("Install server");
 | 
					 | 
				
			||||||
  const filesLint = (await fs.readdir(javaPath)).filter(file => !(filesFoldertoIgnore.some(folder => folder === file)));
 | 
					 | 
				
			||||||
  const zip = new admZip();
 | 
					 | 
				
			||||||
  for (const file of filesLint) {
 | 
					 | 
				
			||||||
    const filePath = path.join(javaPath, file);
 | 
					 | 
				
			||||||
    const stats = await fs.stat(filePath);
 | 
					 | 
				
			||||||
    if (stats.isSymbolicLink()) {
 | 
					 | 
				
			||||||
      const realPath = await fs.realpath(filePath);
 | 
					 | 
				
			||||||
      const realStats = await fs.stat(realPath);
 | 
					 | 
				
			||||||
      if (realStats.isDirectory()) zip.addLocalFolder(realPath, file);
 | 
					 | 
				
			||||||
      else zip.addLocalFile(realPath, file);
 | 
					 | 
				
			||||||
    } else if (stats.isDirectory()) zip.addLocalFolder(filePath);
 | 
					 | 
				
			||||||
    else zip.addLocalFile(filePath);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return zip.toBuffer();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Restore backup for Worlds and Settings
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * WARNING: This will overwrite existing files and World folder files
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export async function RestoreBackup(zipBuffer: Buffer): Promise<void> {
 | 
					 | 
				
			||||||
  const zip = new admZip(zipBuffer);
 | 
					 | 
				
			||||||
  await new Promise((resolve, reject) => zip.extractAllToAsync(javaPath, true, true, (err) => !!err ? reject(err) : resolve("")));
 | 
					 | 
				
			||||||
  return;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,21 +0,0 @@
 | 
				
			|||||||
import path from "node:path";
 | 
					 | 
				
			||||||
import fs from "node:fs";
 | 
					 | 
				
			||||||
import * as versionManeger from "@the-bds-maneger/server_versions";
 | 
					 | 
				
			||||||
import * as httpRequests from "../lib/HttpRequests";
 | 
					 | 
				
			||||||
import { serverRoot } from "../pathControl";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function download(version: string|boolean) {
 | 
					 | 
				
			||||||
  const ServerPath = path.join(serverRoot, "java");
 | 
					 | 
				
			||||||
  if (!(await fs.existsSync(ServerPath))) fs.mkdirSync(ServerPath, {recursive: true});
 | 
					 | 
				
			||||||
  if (!(await fs.existsSync(ServerPath))) fs.mkdirSync(ServerPath, {recursive: true});
 | 
					 | 
				
			||||||
    const javaInfo = await versionManeger.findUrlVersion("java", version);
 | 
					 | 
				
			||||||
    await fs.promises.writeFile(path.resolve(ServerPath, "Server.jar"), await httpRequests.getBuffer(String(javaInfo.url)));
 | 
					 | 
				
			||||||
    await fs.promises.writeFile(path.resolve(ServerPath, "eula.txt"), "eula=true");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Return info
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    version: javaInfo.version,
 | 
					 | 
				
			||||||
    publishDate: javaInfo.datePublish,
 | 
					 | 
				
			||||||
    url: javaInfo.url,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,4 +0,0 @@
 | 
				
			|||||||
export {download as DownloadServer} from "./download";
 | 
					 | 
				
			||||||
export * as linkWorld from "./linkWorld";
 | 
					 | 
				
			||||||
export * as server from "./server";
 | 
					 | 
				
			||||||
export * as backup from "./backup";
 | 
					 | 
				
			||||||
@@ -1,43 +0,0 @@
 | 
				
			|||||||
import * as fs from "node:fs/promises";
 | 
					 | 
				
			||||||
import * as fsOld from "node:fs";
 | 
					 | 
				
			||||||
import * as path from "path";
 | 
					 | 
				
			||||||
import { serverRoot, worldStorageRoot } from "../pathControl";
 | 
					 | 
				
			||||||
const filesFoldertoIgnore = ["Server.jar", "eula.txt", "libraries", "logs", "usercache.json", "versions", "banned-ips.json", "banned-players.json", "ops.json", "server.properties", "whitelist.json"];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function linkWorld(): Promise<void> {
 | 
					 | 
				
			||||||
  const worldFolder = path.join(worldStorageRoot, "java");
 | 
					 | 
				
			||||||
  const javaFolder = path.join(serverRoot, "java");
 | 
					 | 
				
			||||||
  if (!fsOld.existsSync(javaFolder)) throw new Error("Server not installed")
 | 
					 | 
				
			||||||
  if (!fsOld.existsSync(worldFolder)) await fs.mkdir(worldFolder, {recursive: true});
 | 
					 | 
				
			||||||
  // From Worlds Folders
 | 
					 | 
				
			||||||
  for (const worldPath of await fs.readdir(worldFolder)) {
 | 
					 | 
				
			||||||
    const serverWorld = path.join(javaFolder, worldPath);
 | 
					 | 
				
			||||||
    const worldStorage = path.join(worldFolder, worldPath);
 | 
					 | 
				
			||||||
    if (fsOld.existsSync(serverWorld)) {
 | 
					 | 
				
			||||||
      if ((await fs.lstat(serverWorld)).isSymbolicLink()) continue;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      await fs.cp(worldStorage, serverWorld, {recursive: true});
 | 
					 | 
				
			||||||
      await fs.rm(worldStorage, {recursive: true});
 | 
					 | 
				
			||||||
      await fs.symlink(worldStorage, serverWorld);
 | 
					 | 
				
			||||||
    } catch (err) {
 | 
					 | 
				
			||||||
      console.log(err);
 | 
					 | 
				
			||||||
      continue
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  // From Server folder
 | 
					 | 
				
			||||||
  for (const worldPath of (await fs.readdir(javaFolder)).filter(x => !filesFoldertoIgnore.includes(x))) {
 | 
					 | 
				
			||||||
    const serverWorld = path.join(worldFolder, worldPath);
 | 
					 | 
				
			||||||
    const worldStorage = path.join(javaFolder, worldPath);
 | 
					 | 
				
			||||||
    if ((await fs.lstat(worldStorage)).isSymbolicLink()) continue;
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      await fs.cp(worldStorage, serverWorld, {recursive: true});
 | 
					 | 
				
			||||||
      await fs.rm(worldStorage, {recursive: true});
 | 
					 | 
				
			||||||
      await fs.symlink(serverWorld, worldStorage);
 | 
					 | 
				
			||||||
    } catch (err) {
 | 
					 | 
				
			||||||
      console.log(err);
 | 
					 | 
				
			||||||
      continue
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,137 +0,0 @@
 | 
				
			|||||||
import path from "node:path";
 | 
					 | 
				
			||||||
import fs from "node:fs";
 | 
					 | 
				
			||||||
import crypto from "crypto";
 | 
					 | 
				
			||||||
import node_cron from "cron";
 | 
					 | 
				
			||||||
import * as child_process from "../lib/childProcess";
 | 
					 | 
				
			||||||
import { backupRoot, serverRoot } from "../pathControl";
 | 
					 | 
				
			||||||
import { BdsSession, bdsSessionCommands } from '../globalType';
 | 
					 | 
				
			||||||
import { CreateBackup } from "./backup";
 | 
					 | 
				
			||||||
import events from "../lib/customEvents";
 | 
					 | 
				
			||||||
import { linkWorld } from "./linkWorld";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const javaSesions: {[key: string]: BdsSession} = {};
 | 
					 | 
				
			||||||
export function getSessions() {return javaSesions;}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const ServerPath = path.join(serverRoot, "java");
 | 
					 | 
				
			||||||
export async function startServer(): Promise<BdsSession> {
 | 
					 | 
				
			||||||
  if (!(fs.existsSync(ServerPath))) throw new Error("Server dont instlled");
 | 
					 | 
				
			||||||
  if (process.env.AUTO_LINK_WORLD === "true" || process.env.AUTO_LINK_WORLDS === "1") await linkWorld();
 | 
					 | 
				
			||||||
  const SessionID = crypto.randomUUID();
 | 
					 | 
				
			||||||
  // Start Server
 | 
					 | 
				
			||||||
  const serverEvents = new events();
 | 
					 | 
				
			||||||
  const StartDate = new Date();
 | 
					 | 
				
			||||||
  const ServerProcess = await child_process.execServer({runOn: "host"}, "java", ["-jar", "Server.jar"], {cwd: ServerPath});
 | 
					 | 
				
			||||||
  // Log Server redirect to callbacks events and exit
 | 
					 | 
				
			||||||
  ServerProcess.on("out", data => serverEvents.emit("log_stdout", data));
 | 
					 | 
				
			||||||
  ServerProcess.on("err", data => serverEvents.emit("log_stderr", data));
 | 
					 | 
				
			||||||
  ServerProcess.on("all", data => serverEvents.emit("log", data));
 | 
					 | 
				
			||||||
  ServerProcess.Exec.on("exit", code => {
 | 
					 | 
				
			||||||
    serverEvents.emit("closed", code);
 | 
					 | 
				
			||||||
    if (code === null) serverEvents.emit("err", new Error("Server exited with code null"));
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Detect server start
 | 
					 | 
				
			||||||
  serverEvents.on("log", lineData => {
 | 
					 | 
				
			||||||
    // [22:35:26] [Server thread/INFO]: Done (6.249s)! For help, type "help"
 | 
					 | 
				
			||||||
    if (/\[.*\].*\s+Done\s+\(.*\)\!.*/.test(lineData)) serverEvents.emit("started", new Date());
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
  // Parse ports
 | 
					 | 
				
			||||||
  serverEvents.on("log", data => {
 | 
					 | 
				
			||||||
    const portParse = data.match(/Starting\s+Minecraft\s+server\s+on\s+(.*)\:(\d+)/);
 | 
					 | 
				
			||||||
    if (!!portParse) serverEvents.emit("port_listen", {port: parseInt(portParse[2]), protocol: "TCP", version: "IPv4/IPv6",});
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Run Command
 | 
					 | 
				
			||||||
  const serverCommands: bdsSessionCommands = {
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Run any commands in server.
 | 
					 | 
				
			||||||
     * @param command - Run any commands in server without parse commands
 | 
					 | 
				
			||||||
     * @returns - Server commands
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    execCommand: (...command) => {
 | 
					 | 
				
			||||||
      ServerProcess.writelf(command.map(a => String(a)).join(" "));
 | 
					 | 
				
			||||||
      return serverCommands;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    tpPlayer: (player: string, x: number, y: number, z: number) => {
 | 
					 | 
				
			||||||
      serverCommands.execCommand("tp", player, x, y, z);
 | 
					 | 
				
			||||||
      return serverCommands;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    worldGamemode: (gamemode: "survival"|"creative"|"hardcore") => {
 | 
					 | 
				
			||||||
      serverCommands.execCommand("gamemode", gamemode);
 | 
					 | 
				
			||||||
      return serverCommands;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    userGamemode: (player: string, gamemode: "survival"|"creative"|"hardcore") => {
 | 
					 | 
				
			||||||
      serverCommands.execCommand("gamemode", gamemode, player);
 | 
					 | 
				
			||||||
      return serverCommands;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    stop: (): Promise<number|null> => {
 | 
					 | 
				
			||||||
      if (ServerProcess.Exec.exitCode !== null||ServerProcess.Exec.killed) return Promise.resolve(ServerProcess.Exec.exitCode);
 | 
					 | 
				
			||||||
      if (ServerProcess.Exec.killed) return Promise.resolve(ServerProcess.Exec.exitCode);
 | 
					 | 
				
			||||||
      ServerProcess.writelf("stop");
 | 
					 | 
				
			||||||
      return ServerProcess.onExit();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const backupCron = (crontime: string|Date, option?: {type: "zip", config?: {pathZip?: string}}): node_cron.CronJob => {
 | 
					 | 
				
			||||||
    // Validate Config
 | 
					 | 
				
			||||||
    if (option) {
 | 
					 | 
				
			||||||
      if (option.type === "zip") {}
 | 
					 | 
				
			||||||
      else option = {type: "zip"};
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    async function lockServerBackup() {
 | 
					 | 
				
			||||||
      serverCommands.execCommand("save hold");
 | 
					 | 
				
			||||||
      await new Promise(accept => setTimeout(accept, 1000));
 | 
					 | 
				
			||||||
      serverCommands.execCommand("save query");
 | 
					 | 
				
			||||||
      await new Promise(accept => setTimeout(accept, 1000));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    async function unLockServerBackup() {
 | 
					 | 
				
			||||||
      serverCommands.execCommand("save resume");
 | 
					 | 
				
			||||||
      await new Promise(accept => setTimeout(accept, 1000));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!option) option = {type: "zip"};
 | 
					 | 
				
			||||||
    const CrontimeBackup = new node_cron.CronJob(crontime, async () => {
 | 
					 | 
				
			||||||
      if (option.type === "zip") {
 | 
					 | 
				
			||||||
        await lockServerBackup();
 | 
					 | 
				
			||||||
        if (!!option?.config?.pathZip) await CreateBackup().then(res => fs.promises.writeFile(path.resolve(backupRoot, option?.config?.pathZip), res)).catch(() => undefined);
 | 
					 | 
				
			||||||
        // else await createZipBackup(true).catch(() => undefined);
 | 
					 | 
				
			||||||
        await unLockServerBackup();
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    CrontimeBackup.start();
 | 
					 | 
				
			||||||
    serverEvents.on("closed", () => CrontimeBackup.stop());
 | 
					 | 
				
			||||||
    return CrontimeBackup;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Session log
 | 
					 | 
				
			||||||
  const logFile = path.resolve(process.env.LOG_PATH||path.resolve(ServerPath, "../log"), `bedrock_${SessionID}.log`);
 | 
					 | 
				
			||||||
  if(!(fs.existsSync(path.parse(logFile).dir))) fs.mkdirSync(path.parse(logFile).dir, {recursive: true});
 | 
					 | 
				
			||||||
  const logStream = fs.createWriteStream(logFile, {flags: "w+"});
 | 
					 | 
				
			||||||
  logStream.write(`[${StartDate.toString()}] Server started\n\n`);
 | 
					 | 
				
			||||||
  ServerProcess.Exec.stdout.pipe(logStream);
 | 
					 | 
				
			||||||
  ServerProcess.Exec.stderr.pipe(logStream);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Session Object
 | 
					 | 
				
			||||||
  const Seesion: BdsSession = {
 | 
					 | 
				
			||||||
    id: SessionID,
 | 
					 | 
				
			||||||
    creteBackup: backupCron,
 | 
					 | 
				
			||||||
    ports: [],
 | 
					 | 
				
			||||||
    Player: {},
 | 
					 | 
				
			||||||
    seed: undefined,
 | 
					 | 
				
			||||||
    commands: serverCommands,
 | 
					 | 
				
			||||||
    server: {
 | 
					 | 
				
			||||||
      on: (act, fn) => serverEvents.on(act, fn),
 | 
					 | 
				
			||||||
      once: (act, fn) => serverEvents.once(act, fn),
 | 
					 | 
				
			||||||
      started: false,
 | 
					 | 
				
			||||||
      startDate: StartDate
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Server Events
 | 
					 | 
				
			||||||
  serverEvents.on("port_listen", port => Seesion.ports.push(port));
 | 
					 | 
				
			||||||
  serverEvents.on("started", StartDate => {Seesion.server.started = true; Seesion.server.startDate = StartDate;});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Return Session
 | 
					 | 
				
			||||||
  javaSesions[SessionID] = Seesion;
 | 
					 | 
				
			||||||
  serverEvents.on("closed", () => delete javaSesions[SessionID]);
 | 
					 | 
				
			||||||
  return Seesion;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,87 +0,0 @@
 | 
				
			|||||||
import axios from "axios";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type githubRelease = {
 | 
					 | 
				
			||||||
  url: string;
 | 
					 | 
				
			||||||
  assets_url: string;
 | 
					 | 
				
			||||||
  upload_url: string;
 | 
					 | 
				
			||||||
  html_url: string;
 | 
					 | 
				
			||||||
  id: number;
 | 
					 | 
				
			||||||
  tarball_url: string;
 | 
					 | 
				
			||||||
  zipball_url: string;
 | 
					 | 
				
			||||||
  body: string;
 | 
					 | 
				
			||||||
  author: {
 | 
					 | 
				
			||||||
    login: string;
 | 
					 | 
				
			||||||
    id: number;
 | 
					 | 
				
			||||||
    node_id: string;
 | 
					 | 
				
			||||||
    avatar_url: string;
 | 
					 | 
				
			||||||
    gravatar_id: string;
 | 
					 | 
				
			||||||
    url: string;
 | 
					 | 
				
			||||||
    html_url: string;
 | 
					 | 
				
			||||||
    followers_url: string;
 | 
					 | 
				
			||||||
    following_url: string;
 | 
					 | 
				
			||||||
    gists_url: string;
 | 
					 | 
				
			||||||
    starred_url: string;
 | 
					 | 
				
			||||||
    subscriptions_url: string;
 | 
					 | 
				
			||||||
    organizations_url: string;
 | 
					 | 
				
			||||||
    repos_url: string;
 | 
					 | 
				
			||||||
    events_url: string;
 | 
					 | 
				
			||||||
    received_events_url: string;
 | 
					 | 
				
			||||||
    type: string;
 | 
					 | 
				
			||||||
    site_admin: boolean;
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
  node_id: string;
 | 
					 | 
				
			||||||
  tag_name: string;
 | 
					 | 
				
			||||||
  target_commitish: string;
 | 
					 | 
				
			||||||
  name: string;
 | 
					 | 
				
			||||||
  draft: boolean;
 | 
					 | 
				
			||||||
  prerelease: boolean;
 | 
					 | 
				
			||||||
  created_at: string;
 | 
					 | 
				
			||||||
  published_at: string;
 | 
					 | 
				
			||||||
  assets: Array<{
 | 
					 | 
				
			||||||
    url: string;
 | 
					 | 
				
			||||||
    id: number;
 | 
					 | 
				
			||||||
    node_id: string;
 | 
					 | 
				
			||||||
    name: string;
 | 
					 | 
				
			||||||
    label: string;
 | 
					 | 
				
			||||||
    content_type: string;
 | 
					 | 
				
			||||||
    state: string;
 | 
					 | 
				
			||||||
    size: number;
 | 
					 | 
				
			||||||
    download_count: number;
 | 
					 | 
				
			||||||
    created_at: string;
 | 
					 | 
				
			||||||
    updated_at: string;
 | 
					 | 
				
			||||||
    browser_download_url: string;
 | 
					 | 
				
			||||||
    uploader: {
 | 
					 | 
				
			||||||
      login: string;
 | 
					 | 
				
			||||||
      id: number;
 | 
					 | 
				
			||||||
      node_id: string;
 | 
					 | 
				
			||||||
      avatar_url: string;
 | 
					 | 
				
			||||||
      gravatar_id: string;
 | 
					 | 
				
			||||||
      url: string;
 | 
					 | 
				
			||||||
      html_url: string;
 | 
					 | 
				
			||||||
      followers_url: string;
 | 
					 | 
				
			||||||
      following_url: string;
 | 
					 | 
				
			||||||
      gists_url: string;
 | 
					 | 
				
			||||||
      starred_url: string;
 | 
					 | 
				
			||||||
      subscriptions_url: string;
 | 
					 | 
				
			||||||
      organizations_url: string;
 | 
					 | 
				
			||||||
      repos_url: string;
 | 
					 | 
				
			||||||
      events_url: string;
 | 
					 | 
				
			||||||
      received_events_url: string;
 | 
					 | 
				
			||||||
      type: string;
 | 
					 | 
				
			||||||
      site_admin: boolean;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }>;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function getBuffer(url: string, headers: {[d: string]: any} = {}): Promise<Buffer> {
 | 
					 | 
				
			||||||
  const dataReponse = await axios.get(url, {
 | 
					 | 
				
			||||||
    headers: (headers||{}),
 | 
					 | 
				
			||||||
    responseType: "arraybuffer",
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
  return Buffer.from(dataReponse.data);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function getGithubRelease(Username: string, Repo: string): Promise<Array<githubRelease>> {
 | 
					 | 
				
			||||||
  const data = await getBuffer(`https://api.github.com/repos/${Username}/${Repo}/releases`);
 | 
					 | 
				
			||||||
  return JSON.parse(data.toString("utf8"));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,114 +0,0 @@
 | 
				
			|||||||
import child_process, { ChildProcess } from "child_process";
 | 
					 | 
				
			||||||
import EventEmitter from "events";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function runAsync(command: string, args: Array<string|number>, options?: {env?: {[key: string]: string}, cwd?: string}): Promise<{stdout: string; stderr: string}> {
 | 
					 | 
				
			||||||
  if (!options) options = {};
 | 
					 | 
				
			||||||
  return await new Promise((resolve, reject) => {
 | 
					 | 
				
			||||||
    child_process.execFile(command, args.map(a => String(a)), {env: {...process.env, ...(options.env||{})}, cwd: options.cwd||process.cwd(), maxBuffer: Infinity}, (err, stdout, stderr) => {
 | 
					 | 
				
			||||||
      if (err) return reject(err);
 | 
					 | 
				
			||||||
      resolve({stdout, stderr});
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function runCommandAsync(command: string, options?: {env?: {[key: string]: string}, cwd?: string}): Promise<{stdout: string; stderr: string}> {
 | 
					 | 
				
			||||||
  if (!options) options = {};
 | 
					 | 
				
			||||||
  return await new Promise((resolve, reject) => {
 | 
					 | 
				
			||||||
    child_process.exec(command, {env: {...process.env, ...(options.env||{})}, cwd: options.cwd||process.cwd(), maxBuffer: Infinity}, (err, stdout, stderr) => {
 | 
					 | 
				
			||||||
      if (err) return reject(err);
 | 
					 | 
				
			||||||
      resolve({stdout, stderr});
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type execOptions = {
 | 
					 | 
				
			||||||
  runOn: "docker";
 | 
					 | 
				
			||||||
  dockerVolumeName: string;
 | 
					 | 
				
			||||||
  dockerImage: string;
 | 
					 | 
				
			||||||
  dockerContainerName: string;
 | 
					 | 
				
			||||||
}|{
 | 
					 | 
				
			||||||
  runOn: "host";
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function execServer(options: execOptions, command: string, args: Array<string|number>, execOption: {env?: {[key: string]: string}, cwd?: string}) {
 | 
					 | 
				
			||||||
  let Exec: ChildProcess;
 | 
					 | 
				
			||||||
  if (options.runOn === "docker") {
 | 
					 | 
				
			||||||
    const { dockerVolumeName, dockerImage, dockerContainerName } = options;
 | 
					 | 
				
			||||||
    if (!dockerVolumeName) throw new Error("Docker volume name is not defined");
 | 
					 | 
				
			||||||
    await runAsync("docker", ["volume", "create", dockerVolumeName]);
 | 
					 | 
				
			||||||
    const dockerArgs: Array<string> = ["run", "--name", dockerContainerName, "--rm", "-i", "--entrypoint=bash", "-e", "volumeMount"];
 | 
					 | 
				
			||||||
    if (!!execOption.cwd) dockerArgs.push("--workdir", execOption.cwd);
 | 
					 | 
				
			||||||
    dockerArgs.push("-v", `${dockerVolumeName}:/data`);
 | 
					 | 
				
			||||||
    if (!!execOption.env) {
 | 
					 | 
				
			||||||
      for (const key in Object.keys(execOption.env)) dockerArgs.push("-e", String(key));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    dockerArgs.push(dockerImage);
 | 
					 | 
				
			||||||
    dockerArgs.push(command, ...args.map(a => String(a)));
 | 
					 | 
				
			||||||
    Exec = child_process.execFile("docker", dockerArgs, {
 | 
					 | 
				
			||||||
      env: {...process.env, ...(execOption.env||{}), volumeMount: "/data"},
 | 
					 | 
				
			||||||
      maxBuffer: Infinity
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  } else if (options.runOn === "host") {
 | 
					 | 
				
			||||||
    Exec = child_process.execFile(command, args.map(a => String(a)), {
 | 
					 | 
				
			||||||
      env: {...process.env, ...(execOption.env||{})},
 | 
					 | 
				
			||||||
      cwd: execOption.cwd||process.cwd(),
 | 
					 | 
				
			||||||
      maxBuffer: Infinity
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  } else throw new Error("Unknown runOn");
 | 
					 | 
				
			||||||
  // server exec functions
 | 
					 | 
				
			||||||
  const execEvent = new EventEmitter();
 | 
					 | 
				
			||||||
  /** log data event */
 | 
					 | 
				
			||||||
  const on = (eventName: "out"|"err"|"all", call: (data: string) => void) => execEvent.on(eventName, call);
 | 
					 | 
				
			||||||
  /** log data event */
 | 
					 | 
				
			||||||
  const once = (eventName: "out"|"err"|"all", call: (data: string) => void) => execEvent.once(eventName, call);
 | 
					 | 
				
			||||||
  /** on server exit is event activate */
 | 
					 | 
				
			||||||
  const onExit = (): Promise<number> => {
 | 
					 | 
				
			||||||
    if (Exec.killed) {
 | 
					 | 
				
			||||||
      if (Exec.exitCode === 0) return Promise.resolve(0);
 | 
					 | 
				
			||||||
      return Promise.reject(Exec.exitCode === null ? 137:Exec.exitCode);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return new Promise<number>((res, rej) => Exec.on("exit", code => {
 | 
					 | 
				
			||||||
      if (code === 0) return res(0);
 | 
					 | 
				
			||||||
      return rej(code === null ? 137 : code);
 | 
					 | 
				
			||||||
    }));
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Storage tmp lines
 | 
					 | 
				
			||||||
  const tempLog = {out: "", err: ""};
 | 
					 | 
				
			||||||
  const parseLog = (to: "out"|"err", data: string) => {
 | 
					 | 
				
			||||||
    // Detect new line and get all line with storage line for run callback else storage line
 | 
					 | 
				
			||||||
    let lines = data.split(/\r?\n/);
 | 
					 | 
				
			||||||
    // if (lines[lines.length - 1] === "") lines.pop();
 | 
					 | 
				
			||||||
    if (lines.length === 1) tempLog[to] += lines[0];
 | 
					 | 
				
			||||||
    else {
 | 
					 | 
				
			||||||
      for (const line of lines.slice(0, -1)) {
 | 
					 | 
				
			||||||
        if (!!tempLog[to]) {
 | 
					 | 
				
			||||||
          execEvent.emit(to, tempLog[to]+line);
 | 
					 | 
				
			||||||
          execEvent.emit("all", tempLog[to]+line);
 | 
					 | 
				
			||||||
          tempLog[to] = "";
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          execEvent.emit(to, line);
 | 
					 | 
				
			||||||
          execEvent.emit("all", line);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  Exec.stdout.on("data", data => parseLog("out", data));
 | 
					 | 
				
			||||||
  Exec.stderr.on("data", data => parseLog("err", data));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Return
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    on,
 | 
					 | 
				
			||||||
    once,
 | 
					 | 
				
			||||||
    onExit,
 | 
					 | 
				
			||||||
    writelf: (data: string|number|Array<string|number>) => {
 | 
					 | 
				
			||||||
      if (typeof data === "string") Exec.stdin.write(data+"\n");
 | 
					 | 
				
			||||||
      else if (Array.isArray(data)) {
 | 
					 | 
				
			||||||
        if (data.length === 0) return;
 | 
					 | 
				
			||||||
        else if (data.length === 1) Exec.stdin.write(data[0]+"\n");
 | 
					 | 
				
			||||||
        else data.forEach(d => Exec.stdin.write(d+"\n"));
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    Exec
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,55 +0,0 @@
 | 
				
			|||||||
import events from "node:events";
 | 
					 | 
				
			||||||
import { playerAction1, playerAction2 } from '../globalType';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export declare interface bdsServerEvent {
 | 
					 | 
				
			||||||
  emit(act: "started", data: Date): boolean;
 | 
					 | 
				
			||||||
  once(act: "started", fn: (data: Date) => void): this;
 | 
					 | 
				
			||||||
  on(act: "started", fn: (data: Date) => void): this;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  emit(act: "err", data: Error|number): boolean;
 | 
					 | 
				
			||||||
  on(act: "err", fn: (data: Error|number) => void): this;
 | 
					 | 
				
			||||||
  once(act: "err", fn: (data: Error|number) => void): this;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  emit(act: "closed", data: number): boolean;
 | 
					 | 
				
			||||||
  once(act: "closed", fn: (data: number) => void): this;
 | 
					 | 
				
			||||||
  on(act: "closed", fn: (data: number) => void): this;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  emit(act: "port_listen", data: {port: number; protocol?: "TCP"|"UDP"; version?: "IPv4"|"IPv6"|"IPv4/IPv6"}): boolean;
 | 
					 | 
				
			||||||
  once(act: "port_listen", fn: (data: {port: number; protocol?: "TCP"|"UDP"; version?: "IPv4"|"IPv6"|"IPv4/IPv6"}) => void): this;
 | 
					 | 
				
			||||||
  on(act: "port_listen", fn: (data: {port: number; protocol?: "TCP"|"UDP"; version?: "IPv4"|"IPv6"|"IPv4/IPv6"}) => void): this;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  emit(act: "log", data: string): boolean;
 | 
					 | 
				
			||||||
  once(act: "log", fn: (data: string) => void): this;
 | 
					 | 
				
			||||||
  on(act: "log", fn: (data: string) => void): this;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  emit(act: "log_stdout", data: string): boolean;
 | 
					 | 
				
			||||||
  once(act: "log_stdout", fn: (data: string) => void): this;
 | 
					 | 
				
			||||||
  on(act: "log_stdout", fn: (data: string) => void): this;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  emit(act: "log_stderr", data: string): boolean;
 | 
					 | 
				
			||||||
  once(act: "log_stderr", fn: (data: string) => void): this;
 | 
					 | 
				
			||||||
  on(act: "log_stderr", fn: (data: string) => void): this;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  emit(act: "player", data: playerAction2): boolean;
 | 
					 | 
				
			||||||
  once(act: "player", fn: (data: playerAction2) => void): this;
 | 
					 | 
				
			||||||
  on(act: "player", fn: (data: playerAction2) => void): this;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  emit(act: "player_ban", data: playerAction1): boolean;
 | 
					 | 
				
			||||||
  once(act: "player_ban", fn: (data: playerAction1) => void): this;
 | 
					 | 
				
			||||||
  on(act: "player_ban", fn: (data: playerAction1) => void): this;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  emit(act: "player_connect", data: playerAction1): boolean;
 | 
					 | 
				
			||||||
  once(act: "player_connect", fn: (data: playerAction1) => void): this;
 | 
					 | 
				
			||||||
  on(act: "player_connect", fn: (data: playerAction1) => void): this;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  emit(act: "player_disconnect", data: playerAction1): boolean;
 | 
					 | 
				
			||||||
  once(act: "player_disconnect", fn: (data: playerAction1) => void): this;
 | 
					 | 
				
			||||||
  on(act: "player_disconnect", fn: (data: playerAction1) => void): this;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  emit(act: "player_unknown", data: playerAction1): boolean;
 | 
					 | 
				
			||||||
  once(act: "player_unknown", fn: (data: playerAction1) => void): this;
 | 
					 | 
				
			||||||
  on(act: "player_unknown", fn: (data: playerAction1) => void): this;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class bdsServerEvent extends events {}
 | 
					 | 
				
			||||||
export default bdsServerEvent;
 | 
					 | 
				
			||||||
@@ -1,33 +0,0 @@
 | 
				
			|||||||
import { promises as fsPromise } from "node:fs";
 | 
					 | 
				
			||||||
import path from "node:path";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default async function Readdir(pathRead: string, filter?: Array<RegExp>) {
 | 
					 | 
				
			||||||
if (!filter) filter = [/.*/];
 | 
					 | 
				
			||||||
  const fixedPath = path.resolve(pathRead);
 | 
					 | 
				
			||||||
  const files: Array<{
 | 
					 | 
				
			||||||
    path: string,
 | 
					 | 
				
			||||||
    name: string
 | 
					 | 
				
			||||||
  }> = [];
 | 
					 | 
				
			||||||
  for (const file of await fsPromise.readdir(fixedPath)) {
 | 
					 | 
				
			||||||
    const FullFilePath = path.join(fixedPath, file);
 | 
					 | 
				
			||||||
    const stats = await fsPromise.stat(FullFilePath);
 | 
					 | 
				
			||||||
    if (stats.isDirectory()) files.push(...(await Readdir(FullFilePath, filter)));
 | 
					 | 
				
			||||||
    else if (stats.isSymbolicLink()) {
 | 
					 | 
				
			||||||
      const realPath = await fsPromise.realpath(FullFilePath);
 | 
					 | 
				
			||||||
      const statsSys = await fsPromise.stat(realPath);
 | 
					 | 
				
			||||||
      if (statsSys.isDirectory()) files.push(...(await Readdir(realPath, filter)));
 | 
					 | 
				
			||||||
      else {
 | 
					 | 
				
			||||||
        if (filter.length === 0||filter.some(x => x.test(realPath))) files.push({
 | 
					 | 
				
			||||||
          path: FullFilePath,
 | 
					 | 
				
			||||||
          name: path.basename(FullFilePath)
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      if (filter.length === 0||filter.some(x => x.test(FullFilePath))) files.push({
 | 
					 | 
				
			||||||
        path: FullFilePath,
 | 
					 | 
				
			||||||
        name: path.basename(FullFilePath)
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return files;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,12 +0,0 @@
 | 
				
			|||||||
export default function parse(ignoreFile: string, filter?: Array<string>) {
 | 
					 | 
				
			||||||
  ignoreFile = ignoreFile.replace(/\r\n/g, "\n").replace(/#.*\n?/gim, "").replace(/^\n/g, "").replace(/\*\*/g, "(.+)").replace(/\*/g, "([^\\/]+)");
 | 
					 | 
				
			||||||
  const allow = (([...ignoreFile.matchAll(/!.*\n?/g)])||[]).filter(x => !!x);
 | 
					 | 
				
			||||||
  ignoreFile = ignoreFile.replace(/!.*\n?/gim, "").replace(/^\n/g, "");
 | 
					 | 
				
			||||||
  const ignore = ignoreFile.split(/\n/g);
 | 
					 | 
				
			||||||
  const objIngore = {
 | 
					 | 
				
			||||||
    allow: allow.length > 0 ? new RegExp("^((" + allow.join(")|(") + "))") : new RegExp("$^"),
 | 
					 | 
				
			||||||
    ignore: ignore.length > 0 ? new RegExp("^((" + ignore.join(")|(") + "))") : new RegExp("$^"),
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
  if (!filter) return objIngore;
 | 
					 | 
				
			||||||
  else return filter.filter(x => objIngore.allow.test(x) && !objIngore.ignore.test(x));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,13 +0,0 @@
 | 
				
			|||||||
import * as net from "node:net";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default async function portIsAllocated(port: number): Promise<boolean> {
 | 
					 | 
				
			||||||
  return new Promise<boolean>((resolve) => {
 | 
					 | 
				
			||||||
    const tester = net.createServer()
 | 
					 | 
				
			||||||
    tester.once("error", () => () => resolve(true));
 | 
					 | 
				
			||||||
    tester.once("listening", () => {
 | 
					 | 
				
			||||||
      tester.once("close", () => resolve(false));
 | 
					 | 
				
			||||||
      tester.close();
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    tester.listen(port);
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										32
									
								
								src/linkWorlds/bedrock_pocketmine.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/linkWorlds/bedrock_pocketmine.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					import * as path from "node:path";
 | 
				
			||||||
 | 
					import * as fs from "node:fs/promises";
 | 
				
			||||||
 | 
					import { existsSync as fsExistsSync } from "node:fs";
 | 
				
			||||||
 | 
					import { worldFolder } from "../pathControl";
 | 
				
			||||||
 | 
					import { serverPath as bedrockServerPath } from "../bedrock";
 | 
				
			||||||
 | 
					import { serverPath as pocketmineServerPath } from "../pocketmine";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const bedrockWorld = path.join(worldFolder, "bedrock");
 | 
				
			||||||
 | 
					export const bedrockServerWorld = path.join(bedrockServerPath, "worlds");
 | 
				
			||||||
 | 
					export async function linkBedrock() {
 | 
				
			||||||
 | 
					  if (!fsExistsSync(bedrockWorld)) await fs.mkdir(bedrockWorld, {recursive: true});
 | 
				
			||||||
 | 
					  if (fsExistsSync(bedrockServerWorld)) {
 | 
				
			||||||
 | 
					    if (await fs.realpath(bedrockWorld) === bedrockServerWorld) return;
 | 
				
			||||||
 | 
					    for (const folder of await fs.readdir(bedrockServerWorld)) await fs.cp(path.join(bedrockServerWorld, folder), path.join(bedrockWorld, folder), {recursive: true, force: true, preserveTimestamps: true, verbatimSymlinks: true});
 | 
				
			||||||
 | 
					    if (!fsExistsSync(bedrockServerWorld+"_backup")) await fs.rename(bedrockServerWorld, bedrockServerWorld+"_backup");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  await fs.symlink(bedrockWorld, bedrockServerWorld);
 | 
				
			||||||
 | 
					  return;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const pocketmineWorld = path.join(worldFolder, "pocketmine");
 | 
				
			||||||
 | 
					export const pocketmineServerWorld = path.join(pocketmineServerPath, "worlds");
 | 
				
			||||||
 | 
					export async function linkPocketmine() {
 | 
				
			||||||
 | 
					  if (!fsExistsSync(pocketmineWorld)) await fs.mkdir(pocketmineWorld, {recursive: true});
 | 
				
			||||||
 | 
					  if (fsExistsSync(pocketmineServerWorld)) {
 | 
				
			||||||
 | 
					    if (await fs.realpath(pocketmineWorld) === pocketmineServerWorld) return;
 | 
				
			||||||
 | 
					    for (const folder of await fs.readdir(pocketmineServerWorld)) await fs.cp(path.join(pocketmineServerWorld, folder), path.join(pocketmineWorld, folder), {recursive: true, force: true, preserveTimestamps: true, verbatimSymlinks: true});
 | 
				
			||||||
 | 
					    if (!fsExistsSync(pocketmineServerWorld+"_backup")) await fs.rename(pocketmineServerWorld, pocketmineServerWorld+"_backup");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  await fs.symlink(pocketmineWorld, pocketmineServerWorld);
 | 
				
			||||||
 | 
					  return;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,7 +1,20 @@
 | 
				
			|||||||
import * as os from "node:os";
 | 
					import * as fs from "node:fs/promises";
 | 
				
			||||||
 | 
					import * as fsOld from "node:fs";
 | 
				
			||||||
import * as path from "node:path";
 | 
					import * as path from "node:path";
 | 
				
			||||||
 | 
					import * as os from "node:os";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const bdsCorePathHome = path.join(os.homedir(), "bds_core");
 | 
					// bds Root
 | 
				
			||||||
export const serverRoot = (!!process.env.SERVER_PATH) ? path.resolve(process.env.SERVER_PATH) : path.join(bdsCorePathHome, "servers");
 | 
					export const bdsRoot = process.env.BDS_HOME||path.join(os.homedir(), ".bdsManeger");
 | 
				
			||||||
export const backupRoot = (!!process.env.BACKUP_PATH) ? path.resolve(process.env.BACKUP_PATH) : path.join(bdsCorePathHome, "backups");
 | 
					if (!fsOld.existsSync(bdsRoot)) fs.mkdir(bdsRoot, {recursive: true}).then(() => console.log("Bds Root created"));
 | 
				
			||||||
export const worldStorageRoot = (!!process.env.WORLD_STORAGE) ? path.resolve(process.env.WORLD_STORAGE) : path.join(bdsCorePathHome, "worlds");
 | 
					
 | 
				
			||||||
 | 
					// Server Folder
 | 
				
			||||||
 | 
					export const serverRoot = path.join(bdsRoot, "Servers");
 | 
				
			||||||
 | 
					if (!fsOld.existsSync(serverRoot)) fs.mkdir(serverRoot, {recursive: true});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Worlds Folder
 | 
				
			||||||
 | 
					export const worldFolder = path.join(bdsRoot, "Worlds");
 | 
				
			||||||
 | 
					if (!fsOld.existsSync(worldFolder)) fs.mkdir(serverRoot, {recursive: true});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Bds backup
 | 
				
			||||||
 | 
					export const backupFolder = path.join(bdsRoot, "Backup");
 | 
				
			||||||
 | 
					if (!fsOld.existsSync(backupFolder)) fs.mkdir(backupFolder, {recursive: true});
 | 
				
			||||||
							
								
								
									
										131
									
								
								src/pocketmine.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								src/pocketmine.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,131 @@
 | 
				
			|||||||
 | 
					import * as path from "node:path";
 | 
				
			||||||
 | 
					import * as fs from "node:fs/promises";
 | 
				
			||||||
 | 
					import * as os from "node:os";
 | 
				
			||||||
 | 
					import * as tar from "tar";
 | 
				
			||||||
 | 
					import { existsSync as  fsExistsSync } from "node:fs";
 | 
				
			||||||
 | 
					import { getPocketminePhar, versionAPIs } from "@the-bds-maneger/server_versions";
 | 
				
			||||||
 | 
					import { execFileAsync, exec } from './childPromisses';
 | 
				
			||||||
 | 
					import { serverRoot } from "./pathControl";
 | 
				
			||||||
 | 
					import { getBuffer } from "./httpRequest";
 | 
				
			||||||
 | 
					import { actionConfig, actions } from './globalPlatfroms';
 | 
				
			||||||
 | 
					import AdmZip from "adm-zip";
 | 
				
			||||||
 | 
					import { promisify } from 'node:util';
 | 
				
			||||||
 | 
					export { pocketmineServerWorld, pocketmineWorld, linkPocketmine } from "./linkWorlds/bedrock_pocketmine";
 | 
				
			||||||
 | 
					export const serverPath = path.join(serverRoot, "pocketmine");
 | 
				
			||||||
 | 
					export const serverPhar = path.join(serverPath, "pocketmine.phar");
 | 
				
			||||||
 | 
					export const phpBinPath = path.join(serverPath, "bin", (process.platform === "win32"?"php":"bin"), "php");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function Readdir(pathRead: string, filter?: RegExp[]) {
 | 
				
			||||||
 | 
					if (!filter) filter = [/.*/];
 | 
				
			||||||
 | 
					  const fixedPath = path.resolve(pathRead);
 | 
				
			||||||
 | 
					  const files: {path: string, name: string}[] = [];
 | 
				
			||||||
 | 
					  for (const file of await fs.readdir(fixedPath)) {
 | 
				
			||||||
 | 
					    const FullFilePath = path.join(fixedPath, file);
 | 
				
			||||||
 | 
					    const stats = await fs.stat(FullFilePath);
 | 
				
			||||||
 | 
					    if (stats.isDirectory()) files.push(...(await Readdir(FullFilePath, filter)));
 | 
				
			||||||
 | 
					    else if (stats.isSymbolicLink()) {
 | 
				
			||||||
 | 
					      const realPath = await fs.realpath(FullFilePath);
 | 
				
			||||||
 | 
					      const statsSys = await fs.stat(realPath);
 | 
				
			||||||
 | 
					      if (statsSys.isDirectory()) files.push(...(await Readdir(realPath, filter)));
 | 
				
			||||||
 | 
					      else if (filter.length === 0||filter.some(x => x.test(realPath))) files.push({
 | 
				
			||||||
 | 
					        path: FullFilePath,
 | 
				
			||||||
 | 
					        name: path.basename(FullFilePath)
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    } else if (filter.length === 0||filter.some(x => x.test(FullFilePath))) files.push({
 | 
				
			||||||
 | 
					      path: FullFilePath,
 | 
				
			||||||
 | 
					      name: path.basename(FullFilePath)
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return files;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function buildPhp() {
 | 
				
			||||||
 | 
					  if (process.platform === "win32") throw new Error("Script is to Linux and MacOS");
 | 
				
			||||||
 | 
					  if (fsExistsSync(path.resolve(serverPath, "bin"))) await fs.rm(path.resolve(serverPath, "bin"), {recursive: true});
 | 
				
			||||||
 | 
					  const tempFolder = path.join(os.tmpdir(), "bdsPhp_"+(Math.random()*19999901).toString(16).replace(".", "").replace(/[0-9]/g, (_, a) =>a=="1"?"a":a=="2"?"b":a=="3"?"S":"k"));
 | 
				
			||||||
 | 
					  if (!fsExistsSync(tempFolder)) fs.mkdir(tempFolder, {recursive: true});
 | 
				
			||||||
 | 
					  await fs.writeFile(path.join(tempFolder, "build.sh"), await getBuffer("https://raw.githubusercontent.com/pmmp/php-build-scripts/stable/compile.sh"));
 | 
				
			||||||
 | 
					  await fs.chmod(path.join(tempFolder, "build.sh"), "777");
 | 
				
			||||||
 | 
					  console.info("Building PHP!");
 | 
				
			||||||
 | 
					  await execFileAsync(path.join(tempFolder, "build.sh"), ["-j"+os.cpus().length], {cwd: tempFolder, stdio: "inherit"});
 | 
				
			||||||
 | 
					  await fs.cp(path.join(tempFolder, "bin", (await fs.readdir(path.join(tempFolder, "bin")))[0]), path.join(serverPath, "bin"), {force: true, recursive: true, preserveTimestamps: true, verbatimSymlinks: true});
 | 
				
			||||||
 | 
					  console.log("PHP Build success!");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function installPhp(): Promise<void> {
 | 
				
			||||||
 | 
					  const file = (await getBuffer(`${versionAPIs[0]}/pocketmine/bin?os=${process.platform}&arch=${process.arch}`).then(res => JSON.parse(res.toString("utf8")) as {url: string, name: string}[]))[0];
 | 
				
			||||||
 | 
					  if (!file) return buildPhp();
 | 
				
			||||||
 | 
					  if (fsExistsSync(path.resolve(serverPath, "bin"))) await fs.rm(path.resolve(serverPath, "bin"), {recursive: true});
 | 
				
			||||||
 | 
					  await fs.mkdir(path.resolve(serverPath, "bin"), {recursive: true});
 | 
				
			||||||
 | 
					  // Tar.gz
 | 
				
			||||||
 | 
					  if (/tar\.gz/.test(file.name)) {
 | 
				
			||||||
 | 
					    await fs.writeFile(path.join(os.tmpdir(), file.name), await getBuffer(file.url));
 | 
				
			||||||
 | 
					    await tar.extract({file: path.join(os.tmpdir(), file.name), C: path.join(serverPath, "bin"), keep: true, p: true, noChmod: false});
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    const zip = new AdmZip(await getBuffer(file.url));
 | 
				
			||||||
 | 
					    await promisify(zip.extractAllToAsync)(serverPath, false, true);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (process.platform === "linux"||process.platform === "android"||process.platform === "darwin") {
 | 
				
			||||||
 | 
					    const ztsFind = await Readdir(path.resolve(serverPath, "bin"), [/.*debug-zts.*/]);
 | 
				
			||||||
 | 
					    if (ztsFind.length > 0) {
 | 
				
			||||||
 | 
					      const phpIniPath = (await Readdir(path.resolve(serverPath, "bin"), [/php\.ini$/]))[0].path;
 | 
				
			||||||
 | 
					      let phpIni = await fs.readFile(phpIniPath, "utf8");
 | 
				
			||||||
 | 
					      if (phpIni.includes("extension_dir")) await fs.writeFile(phpIniPath, phpIni.replace(/extension_dir=.*/g, ""));
 | 
				
			||||||
 | 
					      phpIni = phpIni+`\nextension_dir=${path.resolve(ztsFind[0].path, "..")}`
 | 
				
			||||||
 | 
					      await fs.writeFile(phpIniPath, phpIni);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  // test it's works php
 | 
				
			||||||
 | 
					  await fs.writeFile(path.join(os.tmpdir(), "test.php"), `<?php echo "Hello World";`);
 | 
				
			||||||
 | 
					  await execFileAsync(phpBinPath, ["-f", path.join(os.tmpdir(), "test.php")]).catch(buildPhp);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function installServer(version: string|boolean) {
 | 
				
			||||||
 | 
					  if (!fsExistsSync(serverPath)) await fs.mkdir(serverPath, {recursive: true});
 | 
				
			||||||
 | 
					  await installPhp();
 | 
				
			||||||
 | 
					  await fs.writeFile(serverPhar, await getPocketminePhar(version));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// [16:47:35.405] [Server thread/INFO]: Minecraft network interface running on 0.0.0.0:19132
 | 
				
			||||||
 | 
					export const portListen = /\[.*\]:\s+Minecraft\s+network\s+interface\s+running\s+on\s+(([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+|\[[A-Za-z0-9:]+\]|):([0-9]+))/;
 | 
				
			||||||
 | 
					export const started = /\[.*\].*\s+Done\s+\(.*\)\!.*/;
 | 
				
			||||||
 | 
					export const player = /[.*]:\s+(.*)\s+(.*)\s+the\s+game/gi;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const serverConfig: actionConfig[] = [
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    name: "portListening",
 | 
				
			||||||
 | 
					    callback(data, done) {
 | 
				
			||||||
 | 
					      const portParse = data.match(portListen);
 | 
				
			||||||
 | 
					      if (!portParse) return;
 | 
				
			||||||
 | 
					      const [,, host, port] = portParse;
 | 
				
			||||||
 | 
					      done({
 | 
				
			||||||
 | 
					        protocol: /::/.test(host?.trim())?"IPv6":/[0-9]+\.[0-9]+/.test(host?.trim())?"IPv4":"IPV4/IPv6",
 | 
				
			||||||
 | 
					        type: "UDP",
 | 
				
			||||||
 | 
					        port: parseInt(port),
 | 
				
			||||||
 | 
					        host: host?.trim()
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    name: "serverStarted",
 | 
				
			||||||
 | 
					    callback(data, done) {
 | 
				
			||||||
 | 
					      // [22:35:26] [Server thread/INFO]: Done (6.249s)! For help, type "help"
 | 
				
			||||||
 | 
					      if (started.test(data)) done(new Date());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    name: "playerConnect",
 | 
				
			||||||
 | 
					    callback(data, done) {
 | 
				
			||||||
 | 
					      data;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    name: "serverStop",
 | 
				
			||||||
 | 
					    run: (child) => child.writeStdin("stop")
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function startServer() {
 | 
				
			||||||
 | 
					  if (!fsExistsSync(serverPath)) throw new Error("Install server fist!");
 | 
				
			||||||
 | 
					  return new actions(exec(phpBinPath, [serverPhar], {cwd: serverPath, maxBuffer: Infinity}), serverConfig);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,39 +0,0 @@
 | 
				
			|||||||
import * as httpRequest from "../lib/HttpRequests";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function getPlugins(): Promise<Array<{
 | 
					 | 
				
			||||||
  id: number,
 | 
					 | 
				
			||||||
  name: string,
 | 
					 | 
				
			||||||
  version: string,
 | 
					 | 
				
			||||||
  html_url: string,
 | 
					 | 
				
			||||||
  tagline: string,
 | 
					 | 
				
			||||||
  artifact_url: string,
 | 
					 | 
				
			||||||
  downloads: number,
 | 
					 | 
				
			||||||
  score: number,
 | 
					 | 
				
			||||||
  repo_id: number,
 | 
					 | 
				
			||||||
  repo_name: string,
 | 
					 | 
				
			||||||
  project_id: number,
 | 
					 | 
				
			||||||
  project_name: string,
 | 
					 | 
				
			||||||
  build_id: number,
 | 
					 | 
				
			||||||
  build_number: number,
 | 
					 | 
				
			||||||
  build_commit: string,
 | 
					 | 
				
			||||||
  description_url: string,
 | 
					 | 
				
			||||||
  icon_url: string,
 | 
					 | 
				
			||||||
  changelog_url: string,
 | 
					 | 
				
			||||||
  license: string,
 | 
					 | 
				
			||||||
  license_url: null,
 | 
					 | 
				
			||||||
  is_obsolete: false,
 | 
					 | 
				
			||||||
  is_pre_release: false,
 | 
					 | 
				
			||||||
  is_outdated: false,
 | 
					 | 
				
			||||||
  is_official: false,
 | 
					 | 
				
			||||||
  submission_date: number,
 | 
					 | 
				
			||||||
  state: number,
 | 
					 | 
				
			||||||
  last_state_change_date: number,
 | 
					 | 
				
			||||||
  categories: Array<{ major: true|false, category_name: string }>,
 | 
					 | 
				
			||||||
  keywords: Array<string>,
 | 
					 | 
				
			||||||
  api: Array<{from: string}>,
 | 
					 | 
				
			||||||
  deps: Array<any>,
 | 
					 | 
				
			||||||
  producers: {Collaborator: Array<string>},
 | 
					 | 
				
			||||||
  state_name: string
 | 
					 | 
				
			||||||
}>> {
 | 
					 | 
				
			||||||
  return await httpRequest.getBuffer("https://poggit.pmmp.io/plugins.json").then(async res => JSON.parse(await res.toString("utf8")))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,39 +0,0 @@
 | 
				
			|||||||
import * as fsOld from "node:fs";
 | 
					 | 
				
			||||||
import * as fs from "node:fs/promises";
 | 
					 | 
				
			||||||
import * as path from "node:path";
 | 
					 | 
				
			||||||
import admZip from "adm-zip";
 | 
					 | 
				
			||||||
import { serverRoot } from '../pathControl';
 | 
					 | 
				
			||||||
const javaPath = path.join(serverRoot, "pocketmine");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const filesFoldertoIgnore = ["PocketMine.phar", "bin", "server.log"];
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Create backup for Worlds and Settings
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export async function CreateBackup(): Promise<Buffer> {
 | 
					 | 
				
			||||||
  if (!(fsOld.existsSync(javaPath))) throw new Error("Install server");
 | 
					 | 
				
			||||||
  const filesLint = (await fs.readdir(javaPath)).filter(file => !(filesFoldertoIgnore.some(folder => folder === file)));
 | 
					 | 
				
			||||||
  const zip = new admZip();
 | 
					 | 
				
			||||||
  for (const file of filesLint) {
 | 
					 | 
				
			||||||
    const filePath = path.join(javaPath, file);
 | 
					 | 
				
			||||||
    const stats = await fs.stat(filePath);
 | 
					 | 
				
			||||||
    if (stats.isSymbolicLink()) {
 | 
					 | 
				
			||||||
      const realPath = await fs.realpath(filePath);
 | 
					 | 
				
			||||||
      const realStats = await fs.stat(realPath);
 | 
					 | 
				
			||||||
      if (realStats.isDirectory()) zip.addLocalFolder(realPath, file);
 | 
					 | 
				
			||||||
      else zip.addLocalFile(realPath, file);
 | 
					 | 
				
			||||||
    } else if (stats.isDirectory()) zip.addLocalFolder(filePath);
 | 
					 | 
				
			||||||
    else zip.addLocalFile(filePath);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return zip.toBuffer();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Restore backup for Worlds and Settings
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * WARNING: This will overwrite existing files and World folder files
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export async function RestoreBackup(zipBuffer: Buffer): Promise<void> {
 | 
					 | 
				
			||||||
  const zip = new admZip(zipBuffer);
 | 
					 | 
				
			||||||
  await new Promise((resolve, reject) => zip.extractAllToAsync(javaPath, true, true, (err) => !!err ? reject(err) : resolve("")));
 | 
					 | 
				
			||||||
  return;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,420 +0,0 @@
 | 
				
			|||||||
import path from "node:path";
 | 
					 | 
				
			||||||
import { promises as fsPromise } from "node:fs";
 | 
					 | 
				
			||||||
import { serverRoot } from "../pathControl";
 | 
					 | 
				
			||||||
const serverPath = path.join(serverRoot, "pocketmine");
 | 
					 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
#Properties Config file
 | 
					 | 
				
			||||||
#Wed Apr 20 23:32:32 UTC 2022
 | 
					 | 
				
			||||||
language=eng
 | 
					 | 
				
			||||||
motd=PocketMine-MP Server
 | 
					 | 
				
			||||||
server-name=PocketMine-MP Server
 | 
					 | 
				
			||||||
server-port=19132
 | 
					 | 
				
			||||||
server-portv6=19133
 | 
					 | 
				
			||||||
gamemode=survival
 | 
					 | 
				
			||||||
max-players=20
 | 
					 | 
				
			||||||
view-distance=16
 | 
					 | 
				
			||||||
white-list=on
 | 
					 | 
				
			||||||
enable-query=on
 | 
					 | 
				
			||||||
enable-ipv6=on
 | 
					 | 
				
			||||||
force-gamemode=off
 | 
					 | 
				
			||||||
hardcore=off
 | 
					 | 
				
			||||||
pvp=on
 | 
					 | 
				
			||||||
difficulty=2
 | 
					 | 
				
			||||||
generator-settings=
 | 
					 | 
				
			||||||
level-name=world
 | 
					 | 
				
			||||||
level-seed=
 | 
					 | 
				
			||||||
level-type=DEFAULT
 | 
					 | 
				
			||||||
auto-save=on
 | 
					 | 
				
			||||||
xbox-auth=on
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type pocketmineConfig = {
 | 
					 | 
				
			||||||
  language: "chs"|"deu"|"ell"|"eng"|"fra"|"hrv"|"jpn"|"kor"|"lav"|"nld",
 | 
					 | 
				
			||||||
  motd: string,
 | 
					 | 
				
			||||||
  port: {
 | 
					 | 
				
			||||||
    v4: number,
 | 
					 | 
				
			||||||
    v6: number
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  whiteList: boolean,
 | 
					 | 
				
			||||||
  maxPlayers: number,
 | 
					 | 
				
			||||||
  gamemode: "survival"|"creative"|"hardcore",
 | 
					 | 
				
			||||||
  forceGamemode: boolean,
 | 
					 | 
				
			||||||
  pvp: boolean,
 | 
					 | 
				
			||||||
  difficulty: "peaceful"|"easy"|"normal"|"hard",
 | 
					 | 
				
			||||||
  worldName: string,
 | 
					 | 
				
			||||||
  worldSeed: string,
 | 
					 | 
				
			||||||
  worldType: "default"|"flat",
 | 
					 | 
				
			||||||
  xboxAuth: boolean
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function CreateServerConfig(config: pocketmineConfig) {
 | 
					 | 
				
			||||||
  const lang = config.language||"eng";
 | 
					 | 
				
			||||||
  const serverMotd = config.motd||"PocketMine-MP Server";
 | 
					 | 
				
			||||||
  const serverPortv4 = config.port.v4||19132;
 | 
					 | 
				
			||||||
  const serverPortv6 = config.port.v6||19133;
 | 
					 | 
				
			||||||
  const gamemode = config.gamemode||"survival";
 | 
					 | 
				
			||||||
  const maxPlayers = config.maxPlayers||20;
 | 
					 | 
				
			||||||
  const viewDistance = 16;
 | 
					 | 
				
			||||||
  const whiteList = (config.whiteList||false)?"on":"off";
 | 
					 | 
				
			||||||
  const enableQuery = (false)?"on":"off";
 | 
					 | 
				
			||||||
  const enableIPv6 = (true)? "on":"off";
 | 
					 | 
				
			||||||
  const forceGamemode = (true)?"on":"off";
 | 
					 | 
				
			||||||
  const hardcore = (gamemode === "hardcore")?"on":"off";
 | 
					 | 
				
			||||||
  const pvp = (config.pvp||true)?"on":"off";
 | 
					 | 
				
			||||||
  const difficulty = config.difficulty||"normal";
 | 
					 | 
				
			||||||
  const generatorSettings = "";
 | 
					 | 
				
			||||||
  const levelName = config.worldName||"world";
 | 
					 | 
				
			||||||
  const levelSeed = config.worldSeed||"";
 | 
					 | 
				
			||||||
  const levelType = config.worldType||"default";
 | 
					 | 
				
			||||||
  const autoSave = (true)?"on":"off";
 | 
					 | 
				
			||||||
  const xboxAuth = (config.xboxAuth||false)?"on":"off";
 | 
					 | 
				
			||||||
  const configPath = path.join(serverPath, "server.properties");
 | 
					 | 
				
			||||||
  const configContent = [
 | 
					 | 
				
			||||||
    `language=${lang}`,
 | 
					 | 
				
			||||||
    `motd=${serverMotd}`,
 | 
					 | 
				
			||||||
    `server-port=${serverPortv4}`,
 | 
					 | 
				
			||||||
    `server-portv6=${serverPortv6}`,
 | 
					 | 
				
			||||||
    `gamemode=${gamemode}`,
 | 
					 | 
				
			||||||
    `max-players=${maxPlayers}`,
 | 
					 | 
				
			||||||
    `view-distance=${viewDistance}`,
 | 
					 | 
				
			||||||
    `white-list=${whiteList}`,
 | 
					 | 
				
			||||||
    `enable-query=${enableQuery}`,
 | 
					 | 
				
			||||||
    `enable-ipv6=${enableIPv6}`,
 | 
					 | 
				
			||||||
    `force-gamemode=${forceGamemode}`,
 | 
					 | 
				
			||||||
    `hardcore=${hardcore}`,
 | 
					 | 
				
			||||||
    `pvp=${pvp}`,
 | 
					 | 
				
			||||||
    `difficulty=${difficulty}`,
 | 
					 | 
				
			||||||
    `generator-settings=${generatorSettings}`,
 | 
					 | 
				
			||||||
    `level-name=${levelName}`,
 | 
					 | 
				
			||||||
    `level-seed=${levelSeed}`,
 | 
					 | 
				
			||||||
    `level-type=${levelType}`,
 | 
					 | 
				
			||||||
    `auto-save=${autoSave}`,
 | 
					 | 
				
			||||||
    `xbox-auth=${xboxAuth}`
 | 
					 | 
				
			||||||
  ];
 | 
					 | 
				
			||||||
  await fsPromise.writeFile(configPath, configContent.join("\n"));
 | 
					 | 
				
			||||||
  return {lang, serverMotd, serverPortv4, serverPortv6, gamemode, maxPlayers, viewDistance, whiteList, enableQuery, enableIPv6, forceGamemode, hardcore, pvp, difficulty, generatorSettings, levelName, levelSeed, levelType, autoSave, xboxAuth};
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// new config in to pocketmine.yml
 | 
					 | 
				
			||||||
// Example
 | 
					 | 
				
			||||||
// TODO: yaml lang parse with js-yaml
 | 
					 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
# Main configuration file for PocketMine-MP
 | 
					 | 
				
			||||||
# These settings are the ones that cannot be included in server.properties
 | 
					 | 
				
			||||||
# Some of these settings are safe, others can break your server if modified incorrectly
 | 
					 | 
				
			||||||
# New settings/defaults won't appear automatically in this file when upgrading.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
settings:
 | 
					 | 
				
			||||||
  #Whether to send all strings translated to server locale or let the device handle them
 | 
					 | 
				
			||||||
  force-language: false
 | 
					 | 
				
			||||||
  shutdown-message: "Server closed"
 | 
					 | 
				
			||||||
  #Allow listing plugins via Query
 | 
					 | 
				
			||||||
  query-plugins: true
 | 
					 | 
				
			||||||
  #Enable plugin and core profiling by default
 | 
					 | 
				
			||||||
  enable-profiling: false
 | 
					 | 
				
			||||||
  #Will only add results when tick measurement is below or equal to given value (default 20)
 | 
					 | 
				
			||||||
  profile-report-trigger: 20
 | 
					 | 
				
			||||||
  #Number of AsyncTask workers.
 | 
					 | 
				
			||||||
  #Used for plugin asynchronous tasks, world generation, compression and web communication.
 | 
					 | 
				
			||||||
  #Set this approximately to your number of cores.
 | 
					 | 
				
			||||||
  #If set to auto, it'll try to detect the number of cores (or use 2)
 | 
					 | 
				
			||||||
  async-workers: auto
 | 
					 | 
				
			||||||
  #Whether to allow running development builds. Dev builds might crash, break your plugins, corrupt your world and more.
 | 
					 | 
				
			||||||
  #It is recommended to avoid using development builds where possible.
 | 
					 | 
				
			||||||
  enable-dev-builds: false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
memory:
 | 
					 | 
				
			||||||
  #Global soft memory limit in megabytes. Set to 0 to disable
 | 
					 | 
				
			||||||
  #This will trigger low-memory-triggers and fire an event to free memory when the usage goes over this
 | 
					 | 
				
			||||||
  global-limit: 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  #Main thread soft memory limit in megabytes. Set to 0 to disable
 | 
					 | 
				
			||||||
  #This will trigger low-memory-triggers and fire an event to free memory when the usage goes over this
 | 
					 | 
				
			||||||
  main-limit: 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  #Main thread hard memory limit in megabytes. Set to 0 to disable
 | 
					 | 
				
			||||||
  #This will stop the server when the limit is surpassed
 | 
					 | 
				
			||||||
  main-hard-limit: 1024
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  #AsyncWorker threads' hard memory limit in megabytes. Set to 0 to disable
 | 
					 | 
				
			||||||
  #This will crash the task currently executing on the worker if the task exceeds the limit
 | 
					 | 
				
			||||||
  #NOTE: THIS LIMIT APPLIES PER WORKER, NOT TO THE WHOLE PROCESS.
 | 
					 | 
				
			||||||
  async-worker-hard-limit: 256
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  #Period in ticks to check memory (default 1 second)
 | 
					 | 
				
			||||||
  check-rate: 20
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  #Continue firing low-memory-triggers and event while on low memory
 | 
					 | 
				
			||||||
  continuous-trigger: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  #Only if memory.continuous-trigger is enabled. Specifies the rate in memory.check-rate steps (default 30 seconds)
 | 
					 | 
				
			||||||
  continuous-trigger-rate: 30
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  garbage-collection:
 | 
					 | 
				
			||||||
    #Period in ticks to fire the garbage collector manually (default 30 minutes), set to 0 to disable
 | 
					 | 
				
			||||||
    #This only affects the main thread. Other threads should fire their own collections
 | 
					 | 
				
			||||||
    period: 36000
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #Fire asynchronous tasks to collect garbage from workers
 | 
					 | 
				
			||||||
    collect-async-worker: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #Trigger on low memory
 | 
					 | 
				
			||||||
    low-memory-trigger: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  #Settings controlling memory dump handling.
 | 
					 | 
				
			||||||
  memory-dump:
 | 
					 | 
				
			||||||
    #Dump memory from async workers as well as the main thread. If you have issues with segfaults when dumping memory, disable this setting.
 | 
					 | 
				
			||||||
    dump-async-worker: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  max-chunks:
 | 
					 | 
				
			||||||
    #Cap maximum render distance per player when low memory is triggered. Set to 0 to disable cap.
 | 
					 | 
				
			||||||
    chunk-radius: 4
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #Do chunk garbage collection on trigger
 | 
					 | 
				
			||||||
    trigger-chunk-collect: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  world-caches:
 | 
					 | 
				
			||||||
    #Disallow adding to world chunk-packet caches when memory is low
 | 
					 | 
				
			||||||
    disable-chunk-cache: true
 | 
					 | 
				
			||||||
    #Clear world caches when memory is low
 | 
					 | 
				
			||||||
    low-memory-trigger: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
network:
 | 
					 | 
				
			||||||
  #Threshold for batching packets, in bytes. Only these packets will be compressed
 | 
					 | 
				
			||||||
  #Set to 0 to compress everything, -1 to disable.
 | 
					 | 
				
			||||||
  batch-threshold: 256
 | 
					 | 
				
			||||||
  #Compression level used when sending batched packets. Higher = more CPU, less bandwidth usage
 | 
					 | 
				
			||||||
  compression-level: 6
 | 
					 | 
				
			||||||
  #Use AsyncTasks for compression. Adds half/one tick delay, less CPU load on main thread
 | 
					 | 
				
			||||||
  async-compression: false
 | 
					 | 
				
			||||||
  #Experimental. Use UPnP to automatically port forward
 | 
					 | 
				
			||||||
  upnp-forwarding: false
 | 
					 | 
				
			||||||
  #Maximum size in bytes of packets sent over the network (default 1492 bytes). Packets larger than this will be
 | 
					 | 
				
			||||||
  #fragmented or split into smaller parts. Clients can request MTU sizes up to but not more than this number.
 | 
					 | 
				
			||||||
  max-mtu-size: 1492
 | 
					 | 
				
			||||||
  #Enable encryption of Minecraft network traffic. This has an impact on performance, but prevents hackers from stealing sessions and pretending to be other players.
 | 
					 | 
				
			||||||
  #DO NOT DISABLE THIS unless you understand the risks involved.
 | 
					 | 
				
			||||||
  enable-encryption: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
debug:
 | 
					 | 
				
			||||||
  #If > 1, it will show debug messages in the console
 | 
					 | 
				
			||||||
  level: 1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
player:
 | 
					 | 
				
			||||||
  #Choose whether to enable player data saving.
 | 
					 | 
				
			||||||
  save-player-data: true
 | 
					 | 
				
			||||||
  #If true, checks that joining players' Xbox user ID (XUID) match what was previously recorded.
 | 
					 | 
				
			||||||
  #This also prevents non-XBL players using XBL players' usernames to steal their data on servers with xbox-auth=off.
 | 
					 | 
				
			||||||
  verify-xuid: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
level-settings:
 | 
					 | 
				
			||||||
  #The default format that worlds will use when created
 | 
					 | 
				
			||||||
  default-format: leveldb
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
chunk-sending:
 | 
					 | 
				
			||||||
  #To change server normal render distance, change view-distance in server.properties.
 | 
					 | 
				
			||||||
  #Amount of chunks sent to players per tick
 | 
					 | 
				
			||||||
  per-tick: 4
 | 
					 | 
				
			||||||
  #Radius of chunks that need to be sent before spawning the player
 | 
					 | 
				
			||||||
  spawn-radius: 4
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
chunk-ticking:
 | 
					 | 
				
			||||||
  #Max amount of chunks processed each tick
 | 
					 | 
				
			||||||
  per-tick: 40
 | 
					 | 
				
			||||||
  #Radius of chunks around a player to tick
 | 
					 | 
				
			||||||
  tick-radius: 3
 | 
					 | 
				
			||||||
  #Number of blocks inside ticking areas' subchunks that get ticked every tick. Higher values will accelerate events
 | 
					 | 
				
			||||||
  #like tree and plant growth, but at a higher performance cost.
 | 
					 | 
				
			||||||
  blocks-per-subchunk-per-tick: 3
 | 
					 | 
				
			||||||
  #IDs of blocks not to perform random ticking on.
 | 
					 | 
				
			||||||
  disable-block-ticking:
 | 
					 | 
				
			||||||
    #- grass
 | 
					 | 
				
			||||||
    #- ice
 | 
					 | 
				
			||||||
    #- fire
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
chunk-generation:
 | 
					 | 
				
			||||||
  #Max. amount of chunks in the waiting queue to be populated
 | 
					 | 
				
			||||||
  population-queue-size: 32
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ticks-per:
 | 
					 | 
				
			||||||
  autosave: 6000
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
auto-report:
 | 
					 | 
				
			||||||
  #Send crash reports for processing
 | 
					 | 
				
			||||||
  enabled: true
 | 
					 | 
				
			||||||
  send-code: true
 | 
					 | 
				
			||||||
  send-settings: true
 | 
					 | 
				
			||||||
  send-phpinfo: false
 | 
					 | 
				
			||||||
  use-https: true
 | 
					 | 
				
			||||||
  host: crash.pmmp.io
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
anonymous-statistics:
 | 
					 | 
				
			||||||
  #Sends anonymous statistics for data aggregation, plugin usage tracking
 | 
					 | 
				
			||||||
  enabled: false #TODO: re-enable this when we have a new stats host
 | 
					 | 
				
			||||||
  host: stats.pocketmine.net
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
auto-updater:
 | 
					 | 
				
			||||||
  enabled: true
 | 
					 | 
				
			||||||
  on-update:
 | 
					 | 
				
			||||||
    warn-console: true
 | 
					 | 
				
			||||||
  #Can be development, alpha, beta or stable.
 | 
					 | 
				
			||||||
  preferred-channel: stable
 | 
					 | 
				
			||||||
  #If using a development version, it will suggest changing the channel
 | 
					 | 
				
			||||||
  suggest-channels: true
 | 
					 | 
				
			||||||
  host: update.pmmp.io
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
timings:
 | 
					 | 
				
			||||||
  #Choose the host to use for viewing your timings results.
 | 
					 | 
				
			||||||
  host: timings.pmmp.io
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
console:
 | 
					 | 
				
			||||||
  #Choose whether to enable server stats reporting on the console title.
 | 
					 | 
				
			||||||
  #NOTE: The title ticker will be disabled regardless if console colours are not enabled.
 | 
					 | 
				
			||||||
  title-tick: true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
aliases:
 | 
					 | 
				
			||||||
  ##This section allows you to add, remove or remap command aliases.
 | 
					 | 
				
			||||||
  ##A single alias can call one or more other commands (or aliases).
 | 
					 | 
				
			||||||
  ##Aliases defined here will override any command aliases declared by plugins or PocketMine-MP itself.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  ##To remove an alias, set it to [], like so (note that prefixed aliases like "pocketmine:stop" will remain and can't
 | 
					 | 
				
			||||||
  ##be removed):
 | 
					 | 
				
			||||||
  #stop: []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  ##Commands are not removed, only their aliases. You can still refer to a command using its full (prefixed)
 | 
					 | 
				
			||||||
  ##name, even if all its aliases are overwritten. The full name is usually something like "pocketmine:commandname" or
 | 
					 | 
				
			||||||
  ##"pluginname:commandname".
 | 
					 | 
				
			||||||
  #abort: [pocketmine:stop]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  ##To add an alias, list the command(s) that it calls:
 | 
					 | 
				
			||||||
  #showtheversion: [version]
 | 
					 | 
				
			||||||
  #savestop: [save-all, stop]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  ##To invoke another command with arguments, use $1 to pass the first argument, $2 for the second etc:
 | 
					 | 
				
			||||||
  #giveadmin: [op $1] ## `giveadmin alex` -> `op alex`
 | 
					 | 
				
			||||||
  #kill: [suicide, say "I tried to kill $1"] ## `kill alex` -> `suicide` + `say "I tried to kill alex"`
 | 
					 | 
				
			||||||
  #giverandom: [give $1 $2, say "Someone has just received a $2!"] ## `giverandom alex diamond` -> `give alex diamond` + `say "Someone has just received a diamond!"`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  ##To change an existing command alias and make it do something else:
 | 
					 | 
				
			||||||
  #tp: [suicide]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
worlds:
 | 
					 | 
				
			||||||
  #These settings will override the generator set in server.properties and allows loading multiple worlds
 | 
					 | 
				
			||||||
  #Example:
 | 
					 | 
				
			||||||
  #world:
 | 
					 | 
				
			||||||
  # seed: 404
 | 
					 | 
				
			||||||
  # generator: FLAT
 | 
					 | 
				
			||||||
  # preset: 2;bedrock,59xstone,3xdirt,grass;1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
plugins:
 | 
					 | 
				
			||||||
  #Setting this to true will cause the legacy structure to be used where plugin data is placed inside the --plugins dir.
 | 
					 | 
				
			||||||
  #False will place plugin data under plugin_data under --data.
 | 
					 | 
				
			||||||
  #This option exists for backwards compatibility with existing installations.
 | 
					 | 
				
			||||||
  legacy-data-dir: false
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
// TODO: in json
 | 
					 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
  "settings": {
 | 
					 | 
				
			||||||
    "force-language": false,
 | 
					 | 
				
			||||||
    "shutdown-message": "Server closed",
 | 
					 | 
				
			||||||
    "query-plugins": true,
 | 
					 | 
				
			||||||
    "enable-profiling": false,
 | 
					 | 
				
			||||||
    "profile-report-trigger": 20,
 | 
					 | 
				
			||||||
    "async-workers": "auto",
 | 
					 | 
				
			||||||
    "enable-dev-builds": false
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "memory": {
 | 
					 | 
				
			||||||
    "global-limit": 0,
 | 
					 | 
				
			||||||
    "main-limit": 0,
 | 
					 | 
				
			||||||
    "main-hard-limit": 1024,
 | 
					 | 
				
			||||||
    "async-worker-hard-limit": 256,
 | 
					 | 
				
			||||||
    "check-rate": 20,
 | 
					 | 
				
			||||||
    "continuous-trigger": true,
 | 
					 | 
				
			||||||
    "continuous-trigger-rate": 30,
 | 
					 | 
				
			||||||
    "garbage-collection": {
 | 
					 | 
				
			||||||
      "period": 36000,
 | 
					 | 
				
			||||||
      "collect-async-worker": true,
 | 
					 | 
				
			||||||
      "low-memory-trigger": true
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "memory-dump": {
 | 
					 | 
				
			||||||
      "dump-async-worker": true
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "max-chunks": {
 | 
					 | 
				
			||||||
      "chunk-radius": 4,
 | 
					 | 
				
			||||||
      "trigger-chunk-collect": true
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "world-caches": {
 | 
					 | 
				
			||||||
      "disable-chunk-cache": true,
 | 
					 | 
				
			||||||
      "low-memory-trigger": true
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "network": {
 | 
					 | 
				
			||||||
    "batch-threshold": 256,
 | 
					 | 
				
			||||||
    "compression-level": 6,
 | 
					 | 
				
			||||||
    "async-compression": false,
 | 
					 | 
				
			||||||
    "upnp-forwarding": false,
 | 
					 | 
				
			||||||
    "max-mtu-size": 1492,
 | 
					 | 
				
			||||||
    "enable-encryption": true
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "debug": {
 | 
					 | 
				
			||||||
    "level": 1
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "player": {
 | 
					 | 
				
			||||||
    "save-player-data": true,
 | 
					 | 
				
			||||||
    "verify-xuid": true
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "level-settings": {
 | 
					 | 
				
			||||||
    "default-format": "leveldb"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "chunk-sending": {
 | 
					 | 
				
			||||||
    "per-tick": 4,
 | 
					 | 
				
			||||||
    "spawn-radius": 4
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "chunk-ticking": {
 | 
					 | 
				
			||||||
    "per-tick": 40,
 | 
					 | 
				
			||||||
    "tick-radius": 3,
 | 
					 | 
				
			||||||
    "blocks-per-subchunk-per-tick": 3,
 | 
					 | 
				
			||||||
    "disable-block-ticking": null
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "chunk-generation": {
 | 
					 | 
				
			||||||
    "population-queue-size": 32
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "ticks-per": {
 | 
					 | 
				
			||||||
    "autosave": 6000
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "auto-report": {
 | 
					 | 
				
			||||||
    "enabled": true,
 | 
					 | 
				
			||||||
    "send-code": true,
 | 
					 | 
				
			||||||
    "send-settings": true,
 | 
					 | 
				
			||||||
    "send-phpinfo": false,
 | 
					 | 
				
			||||||
    "use-https": true,
 | 
					 | 
				
			||||||
    "host": "crash.pmmp.io"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "anonymous-statistics": {
 | 
					 | 
				
			||||||
    "enabled": false,
 | 
					 | 
				
			||||||
    "host": "stats.pocketmine.net"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "auto-updater": {
 | 
					 | 
				
			||||||
    "enabled": true,
 | 
					 | 
				
			||||||
    "on-update": {
 | 
					 | 
				
			||||||
      "warn-console": true
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "preferred-channel": "stable",
 | 
					 | 
				
			||||||
    "suggest-channels": true,
 | 
					 | 
				
			||||||
    "host": "update.pmmp.io"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "timings": {
 | 
					 | 
				
			||||||
    "host": "timings.pmmp.io"
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "console": {
 | 
					 | 
				
			||||||
    "title-tick": true
 | 
					 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  "aliases": null,
 | 
					 | 
				
			||||||
  "worlds": null,
 | 
					 | 
				
			||||||
  "plugins": {
 | 
					 | 
				
			||||||
    "legacy-data-dir": false
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
@@ -1,90 +0,0 @@
 | 
				
			|||||||
import * as path from "node:path";
 | 
					 | 
				
			||||||
import * as fs from "node:fs";
 | 
					 | 
				
			||||||
import * as os from "node:os";
 | 
					 | 
				
			||||||
import * as child_process from "node:child_process";
 | 
					 | 
				
			||||||
import { randomBytes } from "node:crypto";
 | 
					 | 
				
			||||||
import adm_zip from "adm-zip";
 | 
					 | 
				
			||||||
import tar from "tar";
 | 
					 | 
				
			||||||
import Readdirrec from "../lib/listRecursive";
 | 
					 | 
				
			||||||
import * as versionManeger from "@the-bds-maneger/server_versions";
 | 
					 | 
				
			||||||
import * as httpRequests from "../lib/HttpRequests";
 | 
					 | 
				
			||||||
import { serverRoot } from "../pathControl";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function buildLocal(serverPath: string) {
 | 
					 | 
				
			||||||
  if (process.platform === "win32") throw new Error("Current only to unix support");
 | 
					 | 
				
			||||||
  const randomFolder = path.join(os.tmpdir(), "bdscore_php_"+randomBytes(8).toString("hex"));
 | 
					 | 
				
			||||||
  await new Promise<void>((resolve, reject) => {
 | 
					 | 
				
			||||||
    child_process.execFile("git", ["clone", "--depth", "1", "https://github.com/pmmp/php-build-scripts.git", randomFolder], (err) => {
 | 
					 | 
				
			||||||
      if (!!err) return reject(err);
 | 
					 | 
				
			||||||
      const cpuCores = os.cpus().length * 4||2;
 | 
					 | 
				
			||||||
      const compiler = child_process.execFile("./compile.sh", ["-j"+cpuCores], {cwd: randomFolder}, err2 => {
 | 
					 | 
				
			||||||
        if (!!err2) return reject(err2);
 | 
					 | 
				
			||||||
        resolve();
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
      compiler.stdout.on("data", data => process.stdout.write(data));
 | 
					 | 
				
			||||||
      compiler.stderr.on("data", data => process.stdout.write(data));
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
  await Readdirrec(path.join(randomFolder, "bin/php7")).then(files => files.map(file => {
 | 
					 | 
				
			||||||
    console.log("Move '%s' to PHP Folder", file.path);
 | 
					 | 
				
			||||||
    return fs.promises.cp(file.path, path.join(file.path.replace(path.join(randomFolder, "bin/php7"), serverPath)));
 | 
					 | 
				
			||||||
  })).then(res => Promise.all(res));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async function InstallPrebuildPHP(serverPath: string) {
 | 
					 | 
				
			||||||
  const nameTest = (name: string) => (process.platform === "win32" ? /\.zip/:/\.tar\.gz/).test(name) && RegExp(process.platform).test(name) && RegExp(process.arch).test(name);
 | 
					 | 
				
			||||||
  const Release = (await httpRequests.getGithubRelease("The-Bds-Maneger", "PocketMinePHPAutoBinBuilds")).map(release => {
 | 
					 | 
				
			||||||
    release.assets = release.assets.filter(asset => nameTest(asset.name));
 | 
					 | 
				
			||||||
    return release;
 | 
					 | 
				
			||||||
  }).filter(res => res.assets.length >= 1);
 | 
					 | 
				
			||||||
  if (Release.length === 0) throw new Error("No file found for this Platform and Arch");
 | 
					 | 
				
			||||||
  const urlBin = Release[0].assets[0].browser_download_url;
 | 
					 | 
				
			||||||
  if (!urlBin) throw new Error("No file found for this Platform and Arch");
 | 
					 | 
				
			||||||
  if (/\.tar\.gz/.test(urlBin)) {
 | 
					 | 
				
			||||||
    const tmpFileTar = path.join(os.tmpdir(), Buffer.from(Math.random().toString()).toString("hex")+"bdscore_php.tar.gz");
 | 
					 | 
				
			||||||
    await fs.promises.writeFile(tmpFileTar, await httpRequests.getBuffer(urlBin));
 | 
					 | 
				
			||||||
    if (fs.existsSync(path.join(serverPath, "bin"))) {
 | 
					 | 
				
			||||||
      await fs.promises.rm(path.join(serverPath, "bin"), {recursive: true});
 | 
					 | 
				
			||||||
      await fs.promises.mkdir(path.join(serverPath, "bin"));
 | 
					 | 
				
			||||||
    } else await fs.promises.mkdir(path.join(serverPath, "bin"));
 | 
					 | 
				
			||||||
    await tar.x({
 | 
					 | 
				
			||||||
      file: tmpFileTar,
 | 
					 | 
				
			||||||
      C: path.join(serverPath, "bin"),
 | 
					 | 
				
			||||||
      keep: true,
 | 
					 | 
				
			||||||
      p: true,
 | 
					 | 
				
			||||||
      noChmod: false
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    await fs.promises.rm(tmpFileTar, {force: true});
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    const PHPZip = new adm_zip(await httpRequests.getBuffer(urlBin));
 | 
					 | 
				
			||||||
    if (fs.existsSync(path.resolve(serverPath, "bin"))) await fs.promises.rm(path.resolve(serverPath, "bin"), {recursive: true});
 | 
					 | 
				
			||||||
    await new Promise((res,rej) => PHPZip.extractAllToAsync(serverPath, false, true, err => err?rej(err):res("")));
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (process.platform === "linux"||process.platform === "android"||process.platform === "darwin") {
 | 
					 | 
				
			||||||
    const ztsFind = await Readdirrec(path.resolve(serverPath, "bin"), [/.*debug-zts.*/]);
 | 
					 | 
				
			||||||
    if (ztsFind.length === 0) return urlBin;
 | 
					 | 
				
			||||||
    const phpIniPath = (await Readdirrec(path.resolve(serverPath, "bin"), [/php\.ini$/]))[0].path;
 | 
					 | 
				
			||||||
    let phpIni = await fs.promises.readFile(phpIniPath, "utf8");
 | 
					 | 
				
			||||||
    if (phpIni.includes("extension_dir")) {
 | 
					 | 
				
			||||||
      await fs.promises.writeFile(phpIniPath, phpIni.replace(/extension_dir=.*/g, ""));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    phpIni = phpIni+`\nextension_dir=${ztsFind[0].path}`
 | 
					 | 
				
			||||||
    await fs.promises.writeFile(phpIniPath, phpIni);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return urlBin;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function download(version: string|boolean) {
 | 
					 | 
				
			||||||
  const ServerPath = path.join(serverRoot, "pocketmine");
 | 
					 | 
				
			||||||
  if (!(await fs.existsSync(ServerPath))) fs.mkdirSync(ServerPath, {recursive: true});
 | 
					 | 
				
			||||||
  const pocketmineInfo = await versionManeger.findUrlVersion("pocketmine", version);
 | 
					 | 
				
			||||||
  await fs.promises.writeFile(path.resolve(ServerPath, "PocketMine.phar"), await httpRequests.getBuffer(String(pocketmineInfo.url)));
 | 
					 | 
				
			||||||
  await buildLocal(ServerPath).catch(err => {console.log("Error on build in system, error:\n%o\nDownloading pre build files", err); return InstallPrebuildPHP(ServerPath);});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Return info
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    version: pocketmineInfo.version,
 | 
					 | 
				
			||||||
    publishDate: pocketmineInfo.datePublish,
 | 
					 | 
				
			||||||
    url: pocketmineInfo.url,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,6 +0,0 @@
 | 
				
			|||||||
export { download as DownloadServer} from "./download";
 | 
					 | 
				
			||||||
export * as linkWorld from "./linkWorld";
 | 
					 | 
				
			||||||
export * as addons from "./addons";
 | 
					 | 
				
			||||||
export * as config from "./config";
 | 
					 | 
				
			||||||
export * as server from "./server";
 | 
					 | 
				
			||||||
export * as backup from "./backup";
 | 
					 | 
				
			||||||
@@ -1,21 +0,0 @@
 | 
				
			|||||||
import * as fs from "node:fs/promises";
 | 
					 | 
				
			||||||
import * as fsOld from "node:fs";
 | 
					 | 
				
			||||||
import * as path from "path";
 | 
					 | 
				
			||||||
import { serverRoot, worldStorageRoot } from "../pathControl";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function linkWorld(): Promise<void> {
 | 
					 | 
				
			||||||
  const worldFolder = path.join(worldStorageRoot, "pocketmine");
 | 
					 | 
				
			||||||
  const pocketmineFolder = path.join(serverRoot, "pocketmine");
 | 
					 | 
				
			||||||
  if (!fsOld.existsSync(pocketmineFolder)) throw new Error("Server not installed")
 | 
					 | 
				
			||||||
  if (!fsOld.existsSync(worldFolder)) await fs.mkdir(worldFolder, {recursive: true});
 | 
					 | 
				
			||||||
  const pocketmineServerWorld = path.join(pocketmineFolder, "worlds");
 | 
					 | 
				
			||||||
  if (fsOld.existsSync(pocketmineServerWorld)) {
 | 
					 | 
				
			||||||
    if ((await fs.lstat(pocketmineServerWorld)).isSymbolicLink()) return;
 | 
					 | 
				
			||||||
    for (const folder of await fs.readdir(pocketmineServerWorld)) {
 | 
					 | 
				
			||||||
      await fs.rename(path.join(pocketmineServerWorld, folder), path.join(worldFolder, folder))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    await fs.rmdir(pocketmineServerWorld);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  await fs.symlink(worldFolder, pocketmineServerWorld, "dir");
 | 
					 | 
				
			||||||
  return;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,192 +0,0 @@
 | 
				
			|||||||
import path from "node:path";
 | 
					 | 
				
			||||||
import fs from "node:fs";
 | 
					 | 
				
			||||||
import crypto from "crypto";
 | 
					 | 
				
			||||||
import node_cron from "cron";
 | 
					 | 
				
			||||||
import * as child_process from "../lib/childProcess";
 | 
					 | 
				
			||||||
import { backupRoot, serverRoot } from "../pathControl";
 | 
					 | 
				
			||||||
import { BdsSession, bdsSessionCommands, serverListen, playerAction2 } from '../globalType';
 | 
					 | 
				
			||||||
import { CreateBackup } from "./backup";
 | 
					 | 
				
			||||||
import events from "../lib/customEvents";
 | 
					 | 
				
			||||||
import { linkWorld } from "./linkWorld";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const pocketmineSesions: {[key: string]: BdsSession} = {};
 | 
					 | 
				
			||||||
export function getSessions() {return pocketmineSesions;}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const ServerPath = path.join(serverRoot, "pocketmine");
 | 
					 | 
				
			||||||
export async function startServer(): Promise<BdsSession> {
 | 
					 | 
				
			||||||
  if (!(fs.existsSync(ServerPath))) throw new Error("Server dont installed");
 | 
					 | 
				
			||||||
  if (process.env.AUTO_LINK_WORLD === "true" || process.env.AUTO_LINK_WORLDS === "1") await linkWorld();
 | 
					 | 
				
			||||||
  const SessionID = crypto.randomUUID();
 | 
					 | 
				
			||||||
  const Process: {command: string; args: Array<string>} = {command: "", args: []};
 | 
					 | 
				
			||||||
  if (process.platform === "win32") Process.command = path.resolve(ServerPath, "bin/php/php.exe");
 | 
					 | 
				
			||||||
  else {
 | 
					 | 
				
			||||||
    Process.command = path.resolve(ServerPath, "bin/bin/php");
 | 
					 | 
				
			||||||
    await child_process.runAsync("chmod", ["a+x", Process.command]);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  Process.args.push(path.join(ServerPath, "PocketMine.phar"), "--no-wizard", "--enable-ansi");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Start Server
 | 
					 | 
				
			||||||
  const serverEvents = new events();
 | 
					 | 
				
			||||||
  const StartDate = new Date();
 | 
					 | 
				
			||||||
  const ServerProcess = await child_process.execServer({runOn: "host"}, Process.command, Process.args, {cwd: ServerPath});
 | 
					 | 
				
			||||||
  const { onExit, on: execOn } = ServerProcess;
 | 
					 | 
				
			||||||
  // Log Server redirect to callbacks events and exit
 | 
					 | 
				
			||||||
  execOn("out", data => serverEvents.emit("log_stdout", data));
 | 
					 | 
				
			||||||
  execOn("err", data => serverEvents.emit("log_stderr", data));
 | 
					 | 
				
			||||||
  execOn("all", data => serverEvents.emit("log", data));
 | 
					 | 
				
			||||||
  onExit().catch(err => {serverEvents.emit("err", err);return null}).then(code => serverEvents.emit("closed", code));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // On server started
 | 
					 | 
				
			||||||
  serverEvents.on("log", lineData => {
 | 
					 | 
				
			||||||
    // [22:52:05.580] [Server thread/INFO]: Done (0.583s)! For help, type "help" or "?"
 | 
					 | 
				
			||||||
    if (/\[.*\].*\s+Done\s+\(.*\)\!.*/.test(lineData)) serverEvents.emit("started", new Date());
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Port listen
 | 
					 | 
				
			||||||
  serverEvents.on("log", data => {
 | 
					 | 
				
			||||||
    // [16:49:31.284] [Server thread/INFO]: Minecraft network interface running on [::]:19133
 | 
					 | 
				
			||||||
    // [16:49:31.273] [Server thread/INFO]: Minecraft network interface running on 0.0.0.0:19132
 | 
					 | 
				
			||||||
    if (/\[.*\]:\s+Minecraft\s+network\s+interface\s+running\s+on\s+.*/gi.test(data)) {
 | 
					 | 
				
			||||||
      const matchString = data.match(/\[.*\]:\s+Minecraft\s+network\s+interface\s+running\s+on\s+(.*)/);
 | 
					 | 
				
			||||||
      if (!!matchString) {
 | 
					 | 
				
			||||||
        const portParse = matchString[1];
 | 
					 | 
				
			||||||
        const portObject: serverListen = {port: 0, version: "IPv4", protocol: "UDP"};
 | 
					 | 
				
			||||||
        const isIpv6 = /\[.*\]:/.test(portParse);
 | 
					 | 
				
			||||||
        if (!isIpv6) portObject.port = parseInt(portParse.split(":")[1]);
 | 
					 | 
				
			||||||
        else {
 | 
					 | 
				
			||||||
          portObject.port = parseInt(portParse.replace(/\[.*\]:/, "").trim())
 | 
					 | 
				
			||||||
          portObject.version = "IPv6";
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        serverEvents.emit("port_listen", portObject);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Player Actions
 | 
					 | 
				
			||||||
  serverEvents.on("log", data => {
 | 
					 | 
				
			||||||
    const actionDate = new Date();
 | 
					 | 
				
			||||||
    if (/\[.*\]:\s+(.*)\s+(.*)\s+the\s+game/gi.test(data)) {
 | 
					 | 
				
			||||||
      const [action, player] = (data.match(/[.*]:\s+(.*)\s+(.*)\s+the\s+game/gi)||[]).slice(1, 3);
 | 
					 | 
				
			||||||
      const playerAction: playerAction2 = {player: player, action: "unknown", Date: actionDate};
 | 
					 | 
				
			||||||
      if (action === "joined") playerAction.action = "connect";
 | 
					 | 
				
			||||||
      else if (action === "left") playerAction.action = "disconnect";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // Server player event
 | 
					 | 
				
			||||||
      serverEvents.emit("player", playerAction);
 | 
					 | 
				
			||||||
      delete playerAction.action;
 | 
					 | 
				
			||||||
      if (action === "connect") serverEvents.emit("player_connect", playerAction);
 | 
					 | 
				
			||||||
      else if (action === "disconnect") serverEvents.emit("player_disconnect", playerAction);
 | 
					 | 
				
			||||||
      else serverEvents.emit("player_unknown", playerAction);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Run Command
 | 
					 | 
				
			||||||
  const serverCommands: bdsSessionCommands = {
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Run any commands in server.
 | 
					 | 
				
			||||||
     * @param command - Run any commands in server without parse commands
 | 
					 | 
				
			||||||
     * @returns - Server commands
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    execCommand: (...command) => {
 | 
					 | 
				
			||||||
      ServerProcess.writelf(command.map(a => String(a)).join(" "));
 | 
					 | 
				
			||||||
      return serverCommands;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    tpPlayer: (player: string, x: number, y: number, z: number) => {
 | 
					 | 
				
			||||||
      serverCommands.execCommand("tp", player, x, y, z);
 | 
					 | 
				
			||||||
      return serverCommands;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    worldGamemode: (gamemode: "survival"|"creative"|"hardcore") => {
 | 
					 | 
				
			||||||
      serverCommands.execCommand("gamemode", gamemode);
 | 
					 | 
				
			||||||
      return serverCommands;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    userGamemode: (player: string, gamemode: "survival"|"creative"|"hardcore") => {
 | 
					 | 
				
			||||||
      serverCommands.execCommand("gamemode", gamemode, player);
 | 
					 | 
				
			||||||
      return serverCommands;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    stop: (): Promise<number|null> => {
 | 
					 | 
				
			||||||
      if (ServerProcess.Exec.exitCode !== null||ServerProcess.Exec.killed) return Promise.resolve(ServerProcess.Exec.exitCode);
 | 
					 | 
				
			||||||
      if (ServerProcess.Exec.killed) return Promise.resolve(ServerProcess.Exec.exitCode);
 | 
					 | 
				
			||||||
      ServerProcess.writelf("stop");
 | 
					 | 
				
			||||||
      return ServerProcess.onExit();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const backupCron = (crontime: string|Date, option?: {type: "zip", config?: {pathZip?: string}}): node_cron.CronJob => {
 | 
					 | 
				
			||||||
    // Validate Config
 | 
					 | 
				
			||||||
    if (option) {
 | 
					 | 
				
			||||||
      if (option.type === "zip") {}
 | 
					 | 
				
			||||||
      else option = {type: "zip"};
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    async function lockServerBackup() {
 | 
					 | 
				
			||||||
      serverCommands.execCommand("save hold");
 | 
					 | 
				
			||||||
      await new Promise(accept => setTimeout(accept, 1000));
 | 
					 | 
				
			||||||
      serverCommands.execCommand("save query");
 | 
					 | 
				
			||||||
      await new Promise(accept => setTimeout(accept, 1000));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    async function unLockServerBackup() {
 | 
					 | 
				
			||||||
      serverCommands.execCommand("save resume");
 | 
					 | 
				
			||||||
      await new Promise(accept => setTimeout(accept, 1000));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!option) option = {type: "zip"};
 | 
					 | 
				
			||||||
    const CrontimeBackup = new node_cron.CronJob(crontime, async () => {
 | 
					 | 
				
			||||||
      if (option.type === "zip") {
 | 
					 | 
				
			||||||
        await lockServerBackup();
 | 
					 | 
				
			||||||
        if (!!option?.config?.pathZip) await CreateBackup().then(res=> fs.promises.writeFile(path.resolve(backupRoot, option?.config?.pathZip), res)).catch(() => undefined);
 | 
					 | 
				
			||||||
        // else await createZipBackup(true).catch(() => undefined);
 | 
					 | 
				
			||||||
        await unLockServerBackup();
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    CrontimeBackup.start();
 | 
					 | 
				
			||||||
    serverEvents.on("closed", () => CrontimeBackup.stop());
 | 
					 | 
				
			||||||
    return CrontimeBackup;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Session log
 | 
					 | 
				
			||||||
  const logFile = path.resolve(process.env.LOG_PATH||path.resolve(ServerPath, "../log"), `bedrock_${SessionID}.log`);
 | 
					 | 
				
			||||||
  if(!(fs.existsSync(path.parse(logFile).dir))) fs.mkdirSync(path.parse(logFile).dir, {recursive: true});
 | 
					 | 
				
			||||||
  const logStream = fs.createWriteStream(logFile, {flags: "w+"});
 | 
					 | 
				
			||||||
  logStream.write(`[${StartDate.toString()}] Server started\n\n`);
 | 
					 | 
				
			||||||
  ServerProcess.Exec.stdout.pipe(logStream);
 | 
					 | 
				
			||||||
  ServerProcess.Exec.stderr.pipe(logStream);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Session Object
 | 
					 | 
				
			||||||
  const Seesion: BdsSession = {
 | 
					 | 
				
			||||||
    id: SessionID,
 | 
					 | 
				
			||||||
    creteBackup: backupCron,
 | 
					 | 
				
			||||||
    ports: [],
 | 
					 | 
				
			||||||
    Player: {},
 | 
					 | 
				
			||||||
    seed: undefined,
 | 
					 | 
				
			||||||
    commands: serverCommands,
 | 
					 | 
				
			||||||
    server: {
 | 
					 | 
				
			||||||
      on: (act, fn) => serverEvents.on(act, fn),
 | 
					 | 
				
			||||||
      once: (act, fn) => serverEvents.once(act, fn),
 | 
					 | 
				
			||||||
      startDate: StartDate,
 | 
					 | 
				
			||||||
      started: false
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  serverEvents.on("started", StartDate => {Seesion.server.startDate = StartDate; Seesion.server.started = true;});
 | 
					 | 
				
			||||||
  serverEvents.on("port_listen", portObject => Seesion.ports.push(portObject));
 | 
					 | 
				
			||||||
  serverEvents.on("player", playerAction => {
 | 
					 | 
				
			||||||
    if (!Seesion.Player[playerAction.player]) Seesion.Player[playerAction.player] = {
 | 
					 | 
				
			||||||
      action: playerAction.action,
 | 
					 | 
				
			||||||
      date: playerAction.Date,
 | 
					 | 
				
			||||||
      history: [{
 | 
					 | 
				
			||||||
        action: playerAction.action,
 | 
					 | 
				
			||||||
        date: playerAction.Date
 | 
					 | 
				
			||||||
      }]
 | 
					 | 
				
			||||||
    }; else {
 | 
					 | 
				
			||||||
      Seesion.Player[playerAction.player].action = playerAction.action;
 | 
					 | 
				
			||||||
      Seesion.Player[playerAction.player].date = playerAction.Date;
 | 
					 | 
				
			||||||
      Seesion.Player[playerAction.player].history.push({
 | 
					 | 
				
			||||||
        action: playerAction.action,
 | 
					 | 
				
			||||||
        date: playerAction.Date
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Return Session
 | 
					 | 
				
			||||||
  pocketmineSesions[SessionID] = Seesion;
 | 
					 | 
				
			||||||
  serverEvents.on("closed", () => delete pocketmineSesions[SessionID]);
 | 
					 | 
				
			||||||
  return Seesion;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										50
									
								
								src/spigot.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/spigot.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					import * as path from "node:path";
 | 
				
			||||||
 | 
					import * as fs from "node:fs/promises";
 | 
				
			||||||
 | 
					import * as fsOld from "node:fs";
 | 
				
			||||||
 | 
					import { getSpigotJar } from "@the-bds-maneger/server_versions";
 | 
				
			||||||
 | 
					import { serverRoot } from "./pathControl";
 | 
				
			||||||
 | 
					import { exec } from "./childPromisses";
 | 
				
			||||||
 | 
					import { actions, actionConfig } from './globalPlatfroms';
 | 
				
			||||||
 | 
					export const serverPath = path.join(serverRoot, "spigot");
 | 
				
			||||||
 | 
					const jarPath = path.join(serverPath, "server.jar");
 | 
				
			||||||
 | 
					export const started = /\[.*\].*\s+Done\s+\(.*\)\!.*/;
 | 
				
			||||||
 | 
					export const portListen = /Starting\s+Minecraft\s+server\s+on\s+(.*)\:(\d+)/;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function installServer(version: string|boolean) {
 | 
				
			||||||
 | 
					  if (!fsOld.existsSync(serverPath)) await fs.mkdir(serverPath, {recursive: true});
 | 
				
			||||||
 | 
					  await fs.writeFile(jarPath, await getSpigotJar(version));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const serverConfig: actionConfig[] = [
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    name: "serverStarted",
 | 
				
			||||||
 | 
					    callback(data, done) {
 | 
				
			||||||
 | 
					      // [22:35:26] [Server thread/INFO]: Done (6.249s)! For help, type "help"
 | 
				
			||||||
 | 
					      if (started.test(data)) done(new Date());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    name: "portListening",
 | 
				
			||||||
 | 
					    callback(data, done) {
 | 
				
			||||||
 | 
					      const portParse = data.match(portListen);
 | 
				
			||||||
 | 
					      if (!!portParse) done({port: parseInt(portParse[2]), host: (portParse[1]||"").trim()||undefined, type: "TCP", protocol: "IPV4/IPv6",});
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    name: "serverStop",
 | 
				
			||||||
 | 
					    run: (child) => child.writeStdin("stop")
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function startServer(Config?: {maxMemory?: number, minMemory?: number}) {
 | 
				
			||||||
 | 
					  if (!fsOld.existsSync(jarPath)) throw new Error("Install server fist.");
 | 
				
			||||||
 | 
					  const command = "java";
 | 
				
			||||||
 | 
					  const args = ["-jar"];
 | 
				
			||||||
 | 
					  if (Config) {
 | 
				
			||||||
 | 
					    if (Config?.minMemory) args.push(`-Xms${Config?.minMemory}m`);
 | 
				
			||||||
 | 
					    if (Config?.maxMemory) args.push(`-Xmx${Config?.maxMemory}m`);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  args.push(jarPath);
 | 
				
			||||||
 | 
					  return new actions(exec(command, args, {cwd: serverPath, maxBuffer: Infinity}), serverConfig);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,40 +0,0 @@
 | 
				
			|||||||
import * as fsOld from "node:fs";
 | 
					 | 
				
			||||||
import * as fs from "node:fs/promises";
 | 
					 | 
				
			||||||
import * as path from "node:path";
 | 
					 | 
				
			||||||
import admZip from "adm-zip";
 | 
					 | 
				
			||||||
import { serverRoot } from '../pathControl';
 | 
					 | 
				
			||||||
const javaPath = path.join(serverRoot, "spigot");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const filesFoldertoIgnore = ["Server.jar", "bundler", "eula.txt", "help.yml", "logs", "usercache.json"];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Create backup for Worlds and Settings
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export async function CreateBackup(): Promise<Buffer> {
 | 
					 | 
				
			||||||
  if (!(fsOld.existsSync(javaPath))) throw new Error("Install server");
 | 
					 | 
				
			||||||
  const filesLint = (await fs.readdir(javaPath)).filter(file => !(filesFoldertoIgnore.some(folder => folder === file)));
 | 
					 | 
				
			||||||
  const zip = new admZip();
 | 
					 | 
				
			||||||
  for (const file of filesLint) {
 | 
					 | 
				
			||||||
    const filePath = path.join(javaPath, file);
 | 
					 | 
				
			||||||
    const stats = await fs.stat(filePath);
 | 
					 | 
				
			||||||
    if (stats.isSymbolicLink()) {
 | 
					 | 
				
			||||||
      const realPath = await fs.realpath(filePath);
 | 
					 | 
				
			||||||
      const realStats = await fs.stat(realPath);
 | 
					 | 
				
			||||||
      if (realStats.isDirectory()) zip.addLocalFolder(realPath, file);
 | 
					 | 
				
			||||||
      else zip.addLocalFile(realPath, file);
 | 
					 | 
				
			||||||
    } else if (stats.isDirectory()) zip.addLocalFolder(filePath);
 | 
					 | 
				
			||||||
    else zip.addLocalFile(filePath);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return zip.toBuffer();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Restore backup for Worlds and Settings
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * WARNING: This will overwrite existing files and World folder files
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
export async function RestoreBackup(zipBuffer: Buffer): Promise<void> {
 | 
					 | 
				
			||||||
  const zip = new admZip(zipBuffer);
 | 
					 | 
				
			||||||
  await new Promise((resolve, reject) => zip.extractAllToAsync(javaPath, true, true, (err) => !!err ? reject(err) : resolve("")));
 | 
					 | 
				
			||||||
  return;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,21 +0,0 @@
 | 
				
			|||||||
import path from "node:path";
 | 
					 | 
				
			||||||
import fs from "node:fs";
 | 
					 | 
				
			||||||
import * as versionManeger from "@the-bds-maneger/server_versions";
 | 
					 | 
				
			||||||
import * as httpRequests from "../lib/HttpRequests";
 | 
					 | 
				
			||||||
import { serverRoot } from "../pathControl";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function download(version: string|boolean) {
 | 
					 | 
				
			||||||
  const ServerPath = path.join(serverRoot, "spigot");
 | 
					 | 
				
			||||||
  if (!(await fs.existsSync(ServerPath))) fs.mkdirSync(ServerPath, {recursive: true});
 | 
					 | 
				
			||||||
  if (!(await fs.existsSync(ServerPath))) fs.mkdirSync(ServerPath, {recursive: true});
 | 
					 | 
				
			||||||
  const spigotSearch = await versionManeger.findUrlVersion("spigot", version);
 | 
					 | 
				
			||||||
  await fs.promises.writeFile(path.resolve(ServerPath, "Server.jar"), await httpRequests.getBuffer(String(spigotSearch.url)));
 | 
					 | 
				
			||||||
  await fs.promises.writeFile(path.resolve(ServerPath, "eula.txt"), "eula=true");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Return info
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    version: spigotSearch.version,
 | 
					 | 
				
			||||||
    publishDate: spigotSearch.datePublish,
 | 
					 | 
				
			||||||
    url: spigotSearch.url,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,3 +0,0 @@
 | 
				
			|||||||
export { download as DownloadServer } from "./download";
 | 
					 | 
				
			||||||
export * as server from "./server";
 | 
					 | 
				
			||||||
export * as backup from "./backup";
 | 
					 | 
				
			||||||
@@ -1,43 +0,0 @@
 | 
				
			|||||||
import * as fs from "node:fs/promises";
 | 
					 | 
				
			||||||
import * as fsOld from "node:fs";
 | 
					 | 
				
			||||||
import * as path from "path";
 | 
					 | 
				
			||||||
import { serverRoot, worldStorageRoot } from "../pathControl";
 | 
					 | 
				
			||||||
const filesFoldertoIgnore = ["Server.jar", "eula.txt", "libraries", "logs", "usercache.json", "versions", "banned-ips.json", "banned-players.json", "ops.json", "server.properties", "whitelist.json"];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function linkWorld(): Promise<void> {
 | 
					 | 
				
			||||||
  const worldFolder = path.join(worldStorageRoot, "java");
 | 
					 | 
				
			||||||
  const javaFolder = path.join(serverRoot, "java");
 | 
					 | 
				
			||||||
  if (!fsOld.existsSync(javaFolder)) throw new Error("Server not installed")
 | 
					 | 
				
			||||||
  if (!fsOld.existsSync(worldFolder)) await fs.mkdir(worldFolder, {recursive: true});
 | 
					 | 
				
			||||||
  // From Worlds Folders
 | 
					 | 
				
			||||||
  for (const worldPath of await fs.readdir(worldFolder)) {
 | 
					 | 
				
			||||||
    const serverWorld = path.join(javaFolder, worldPath);
 | 
					 | 
				
			||||||
    const worldStorage = path.join(worldFolder, worldPath);
 | 
					 | 
				
			||||||
    if (fsOld.existsSync(serverWorld)) {
 | 
					 | 
				
			||||||
      if ((await fs.lstat(serverWorld)).isSymbolicLink()) continue;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      await fs.cp(worldStorage, serverWorld, {recursive: true});
 | 
					 | 
				
			||||||
      await fs.rm(worldStorage, {recursive: true});
 | 
					 | 
				
			||||||
      await fs.symlink(worldStorage, serverWorld);
 | 
					 | 
				
			||||||
    } catch (err) {
 | 
					 | 
				
			||||||
      console.log(err);
 | 
					 | 
				
			||||||
      continue
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  // From Server folder
 | 
					 | 
				
			||||||
  for (const worldPath of (await fs.readdir(javaFolder)).filter(x => !filesFoldertoIgnore.includes(x))) {
 | 
					 | 
				
			||||||
    const serverWorld = path.join(worldFolder, worldPath);
 | 
					 | 
				
			||||||
    const worldStorage = path.join(javaFolder, worldPath);
 | 
					 | 
				
			||||||
    if ((await fs.lstat(worldStorage)).isSymbolicLink()) continue;
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      await fs.cp(worldStorage, serverWorld, {recursive: true});
 | 
					 | 
				
			||||||
      await fs.rm(worldStorage, {recursive: true});
 | 
					 | 
				
			||||||
      await fs.symlink(serverWorld, worldStorage);
 | 
					 | 
				
			||||||
    } catch (err) {
 | 
					 | 
				
			||||||
      console.log(err);
 | 
					 | 
				
			||||||
      continue
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,135 +0,0 @@
 | 
				
			|||||||
import path from "node:path";
 | 
					 | 
				
			||||||
import fs from "node:fs";
 | 
					 | 
				
			||||||
import crypto from "node:crypto";
 | 
					 | 
				
			||||||
import node_cron from "cron";
 | 
					 | 
				
			||||||
import * as child_process from "../lib/childProcess";
 | 
					 | 
				
			||||||
import { backupRoot, serverRoot } from "../pathControl";
 | 
					 | 
				
			||||||
import { BdsSession, bdsSessionCommands } from '../globalType';
 | 
					 | 
				
			||||||
import { CreateBackup } from "./backup";
 | 
					 | 
				
			||||||
import events from "../lib/customEvents";
 | 
					 | 
				
			||||||
import { linkWorld } from "./linkWorld";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const javaSesions: {[key: string]: BdsSession} = {};
 | 
					 | 
				
			||||||
export function getSessions() {return javaSesions;}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const ServerPath = path.join(serverRoot, "spigot");
 | 
					 | 
				
			||||||
export async function startServer(): Promise<BdsSession> {
 | 
					 | 
				
			||||||
  if (!(fs.existsSync((ServerPath)))) throw new Error("server dont installed");
 | 
					 | 
				
			||||||
  if (process.env.AUTO_LINK_WORLD === "true" || process.env.AUTO_LINK_WORLDS === "1") await linkWorld();
 | 
					 | 
				
			||||||
  const SessionID = crypto.randomUUID();
 | 
					 | 
				
			||||||
  // Start Server
 | 
					 | 
				
			||||||
  const serverEvents = new events();
 | 
					 | 
				
			||||||
  const StartDate = new Date();
 | 
					 | 
				
			||||||
  const ServerProcess = await child_process.execServer({runOn: "host"}, "java", ["-jar", "Server.jar"], {cwd: ServerPath});
 | 
					 | 
				
			||||||
  const { onExit, on: execOn } = ServerProcess;
 | 
					 | 
				
			||||||
  // Log Server redirect to callbacks events and exit
 | 
					 | 
				
			||||||
  execOn("out", data => serverEvents.emit("log_stdout", data));
 | 
					 | 
				
			||||||
  execOn("err", data => serverEvents.emit("log_stderr", data));
 | 
					 | 
				
			||||||
  execOn("all", data => serverEvents.emit("log", data));
 | 
					 | 
				
			||||||
  onExit().catch(err => {serverEvents.emit("err", err);return null}).then(code => serverEvents.emit("closed", code));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Server Start
 | 
					 | 
				
			||||||
  serverEvents.on("log", lineData => {
 | 
					 | 
				
			||||||
    // [22:35:26] [Server thread/INFO]: Done (6.249s)! For help, type "help"
 | 
					 | 
				
			||||||
    if (/\[.*\].*\s+Done\s+\(.*\)\!.*/.test(lineData)) serverEvents.emit("started", new Date());
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Parse ports
 | 
					 | 
				
			||||||
  serverEvents.on("log", data => {
 | 
					 | 
				
			||||||
    const portParse = data.match(/Starting\s+Minecraft\s+server\s+on\s+(.*)\:(\d+)/);
 | 
					 | 
				
			||||||
    if (!!portParse) serverEvents.emit("port_listen", {port: parseInt(portParse[2]), version: "IPv4/IPv6", protocol: "TCP"});
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Run Command
 | 
					 | 
				
			||||||
  const serverCommands: bdsSessionCommands = {
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Run any commands in server.
 | 
					 | 
				
			||||||
     * @param command - Run any commands in server without parse commands
 | 
					 | 
				
			||||||
     * @returns - Server commands
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    execCommand: (...command) => {
 | 
					 | 
				
			||||||
      ServerProcess.writelf(command.map(a => String(a)).join(" "));
 | 
					 | 
				
			||||||
      return serverCommands;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    tpPlayer: (player: string, x: number, y: number, z: number) => {
 | 
					 | 
				
			||||||
      serverCommands.execCommand("tp", player, x, y, z);
 | 
					 | 
				
			||||||
      return serverCommands;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    worldGamemode: (gamemode: "survival"|"creative"|"hardcore") => {
 | 
					 | 
				
			||||||
      serverCommands.execCommand("gamemode", gamemode);
 | 
					 | 
				
			||||||
      return serverCommands;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    userGamemode: (player: string, gamemode: "survival"|"creative"|"hardcore") => {
 | 
					 | 
				
			||||||
      serverCommands.execCommand("gamemode", gamemode, player);
 | 
					 | 
				
			||||||
      return serverCommands;
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    stop: (): Promise<number|null> => {
 | 
					 | 
				
			||||||
      if (ServerProcess.Exec.exitCode !== null||ServerProcess.Exec.killed) return Promise.resolve(ServerProcess.Exec.exitCode);
 | 
					 | 
				
			||||||
      if (ServerProcess.Exec.killed) return Promise.resolve(ServerProcess.Exec.exitCode);
 | 
					 | 
				
			||||||
      ServerProcess.writelf("stop");
 | 
					 | 
				
			||||||
      return ServerProcess.onExit();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const backupCron = (crontime: string|Date, option?: {type: "zip", config?: {pathZip?: string}}): node_cron.CronJob => {
 | 
					 | 
				
			||||||
    // Validate Config
 | 
					 | 
				
			||||||
    if (option) {
 | 
					 | 
				
			||||||
      if (option.type === "zip") {}
 | 
					 | 
				
			||||||
      else option = {type: "zip"};
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    async function lockServerBackup() {
 | 
					 | 
				
			||||||
      serverCommands.execCommand("save hold");
 | 
					 | 
				
			||||||
      await new Promise(accept => setTimeout(accept, 1000));
 | 
					 | 
				
			||||||
      serverCommands.execCommand("save query");
 | 
					 | 
				
			||||||
      await new Promise(accept => setTimeout(accept, 1000));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    async function unLockServerBackup() {
 | 
					 | 
				
			||||||
      serverCommands.execCommand("save resume");
 | 
					 | 
				
			||||||
      await new Promise(accept => setTimeout(accept, 1000));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!option) option = {type: "zip"};
 | 
					 | 
				
			||||||
    const CrontimeBackup = new node_cron.CronJob(crontime, async () => {
 | 
					 | 
				
			||||||
      if (option.type === "zip") {
 | 
					 | 
				
			||||||
        await lockServerBackup();
 | 
					 | 
				
			||||||
        if (!!option?.config?.pathZip) await CreateBackup().then(res => fs.promises.writeFile(path.resolve(backupRoot, option?.config?.pathZip), res)).catch(() => undefined);
 | 
					 | 
				
			||||||
        // else await createZipBackup(true).catch(() => undefined);
 | 
					 | 
				
			||||||
        await unLockServerBackup();
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    CrontimeBackup.start();
 | 
					 | 
				
			||||||
    serverEvents.on("closed", () => CrontimeBackup.stop());
 | 
					 | 
				
			||||||
    return CrontimeBackup;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Session log
 | 
					 | 
				
			||||||
  const logFile = path.resolve(process.env.LOG_PATH||path.resolve(ServerPath, "../log"), `bedrock_${SessionID}.log`);
 | 
					 | 
				
			||||||
  if(!(fs.existsSync(path.parse(logFile).dir))) fs.mkdirSync(path.parse(logFile).dir, {recursive: true});
 | 
					 | 
				
			||||||
  const logStream = fs.createWriteStream(logFile, {flags: "w+"});
 | 
					 | 
				
			||||||
  logStream.write(`[${StartDate.toString()}] Server started\n\n`);
 | 
					 | 
				
			||||||
  ServerProcess.Exec.stdout.pipe(logStream);
 | 
					 | 
				
			||||||
  ServerProcess.Exec.stderr.pipe(logStream);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Session Object
 | 
					 | 
				
			||||||
  const Seesion: BdsSession = {
 | 
					 | 
				
			||||||
    id: SessionID,
 | 
					 | 
				
			||||||
    creteBackup: backupCron,
 | 
					 | 
				
			||||||
    ports: [],
 | 
					 | 
				
			||||||
    Player: {},
 | 
					 | 
				
			||||||
    seed: undefined,
 | 
					 | 
				
			||||||
    commands: serverCommands,
 | 
					 | 
				
			||||||
    server: {
 | 
					 | 
				
			||||||
      on: (act, fn) => serverEvents.on(act, fn),
 | 
					 | 
				
			||||||
      once: (act, fn) => serverEvents.once(act, fn),
 | 
					 | 
				
			||||||
      started: false,
 | 
					 | 
				
			||||||
      startDate: StartDate
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  serverEvents.on("started", StartDate => {Seesion.server.startDate = StartDate; Seesion.server.started = true;});
 | 
					 | 
				
			||||||
  serverEvents.on("port_listen", portObject => Seesion.ports.push(portObject));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Return Session
 | 
					 | 
				
			||||||
  javaSesions[SessionID] = Seesion;
 | 
					 | 
				
			||||||
  serverEvents.on("closed", () => delete javaSesions[SessionID]);
 | 
					 | 
				
			||||||
  return Seesion;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,49 +0,0 @@
 | 
				
			|||||||
import * as fs from "node:fs/promises";
 | 
					 | 
				
			||||||
import * as fsOld from "node:fs";
 | 
					 | 
				
			||||||
import * as path from "node:path";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async function readDirAndFilter(dir: string, test: Array<RegExp> = [/.*/]) {
 | 
					 | 
				
			||||||
  if (!(fsOld.existsSync(dir))) throw new Error(`${dir} does not exist`);
 | 
					 | 
				
			||||||
  const files = await fs.readdir(dir);
 | 
					 | 
				
			||||||
  const parseFiles: Array<string> = []
 | 
					 | 
				
			||||||
  await Promise.all(files.map(async (fd) => {
 | 
					 | 
				
			||||||
    const stat = await fs.stat(path.join(dir, fd));
 | 
					 | 
				
			||||||
    if (stat.isDirectory()) return readDirAndFilter(path.join(dir, fd), test).then(res => parseFiles.push(...res)).catch(err => console.error(err));
 | 
					 | 
				
			||||||
    else if (stat.isFile()) {
 | 
					 | 
				
			||||||
      const match = test.some(reg => reg.test(fd));
 | 
					 | 
				
			||||||
      if (match) parseFiles.push(path.join(dir, fd));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }));
 | 
					 | 
				
			||||||
  return parseFiles;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async function runTest() {
 | 
					 | 
				
			||||||
  const mainFind = path.join(process.cwd(), "src");
 | 
					 | 
				
			||||||
  const testsFiles = await readDirAndFilter(mainFind, [/.*\.test\.ts$/]);
 | 
					 | 
				
			||||||
  for (const file of testsFiles) {
 | 
					 | 
				
			||||||
    console.log("************** Start Script: %s **************", file);
 | 
					 | 
				
			||||||
    const testScript = await import(file) as {[key: string]: () => Promise<void>};
 | 
					 | 
				
			||||||
    if (!!testScript.default) {
 | 
					 | 
				
			||||||
      console.log("************** Start Test: %s **************", file);
 | 
					 | 
				
			||||||
      await testScript.default();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    for (const key in testScript) {
 | 
					 | 
				
			||||||
      if (key === "default") continue;
 | 
					 | 
				
			||||||
      console.log("************** Start Test: %s **************", key);
 | 
					 | 
				
			||||||
      await testScript[key]();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    console.log("************** End Script: %s **************", file);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
runTest().then(() => {
 | 
					 | 
				
			||||||
  console.log("Test passed");
 | 
					 | 
				
			||||||
  process.exitCode = 0;
 | 
					 | 
				
			||||||
}).catch((err: Error) => {
 | 
					 | 
				
			||||||
  console.error("Test failed");
 | 
					 | 
				
			||||||
  console.error(err);
 | 
					 | 
				
			||||||
  process.exitCode = 1;
 | 
					 | 
				
			||||||
}).then(() => {
 | 
					 | 
				
			||||||
  console.log("Exit with code: %d", process.exitCode);
 | 
					 | 
				
			||||||
  return process.exit();
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
							
								
								
									
										14
									
								
								tests/bedrock.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								tests/bedrock.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					import { installServer, startServer } from "../src/bedrock";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe("Bedrock", () => {
 | 
				
			||||||
 | 
					  it("Install and Start", async function(){
 | 
				
			||||||
 | 
					    this.timeout(1000*60*60*15);
 | 
				
			||||||
 | 
					    await installServer("latest");
 | 
				
			||||||
 | 
					    const serverManeger = await startServer();
 | 
				
			||||||
 | 
					    serverManeger.on("log_stdout", console.log);
 | 
				
			||||||
 | 
					    serverManeger.on("log_stderr", console.log);
 | 
				
			||||||
 | 
					    serverManeger.on("portListening", console.log);
 | 
				
			||||||
 | 
					    serverManeger.once("serverStarted", () => serverManeger.stopServer());
 | 
				
			||||||
 | 
					    return new Promise((done, reject) => serverManeger.on("exit", ({code}) => code === 0?done():reject(new Error("Exit another code "+code))));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										22
									
								
								tests/pocketmine.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								tests/pocketmine.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					import { installServer, startServer } from "../src/pocketmine";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe("Pocketmine", () => {
 | 
				
			||||||
 | 
					  it("Install and Start", async function() {
 | 
				
			||||||
 | 
					    this.timeout(1000*60*60*15);
 | 
				
			||||||
 | 
					    await installServer("latest");
 | 
				
			||||||
 | 
					    const serverManeger = await startServer();
 | 
				
			||||||
 | 
					    serverManeger.on("log_stdout", console.log);
 | 
				
			||||||
 | 
					    serverManeger.on("log_stderr", console.info);
 | 
				
			||||||
 | 
					    serverManeger.on("portListening", console.log);
 | 
				
			||||||
 | 
					    serverManeger.on("log_stdout", data => {
 | 
				
			||||||
 | 
					      if(/set-up.*wizard/.test(data)) {
 | 
				
			||||||
 | 
					        serverManeger.runCommand("eng");
 | 
				
			||||||
 | 
					        serverManeger.runCommand("y");
 | 
				
			||||||
 | 
					        serverManeger.runCommand("y");
 | 
				
			||||||
 | 
					        serverManeger.runCommand("");
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    serverManeger.on("serverStarted", () => serverManeger.stopServer());
 | 
				
			||||||
 | 
					    return new Promise((done, reject) => serverManeger.on("exit", ({code}) => code === 0?done():reject(new Error("Exit another code "+code))));
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@@ -1,25 +1,28 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "include": ["src/**/*"],
 | 
						"compilerOptions": {
 | 
				
			||||||
  "exclude": [
 | 
							"esModuleInterop": true,
 | 
				
			||||||
    "node_modules/",
 | 
							"moduleResolution": "node",
 | 
				
			||||||
    "test/"
 | 
							"module": "commonjs",
 | 
				
			||||||
  ],
 | 
							"outDir": "./dist",
 | 
				
			||||||
  "compilerOptions": {
 | 
							"declaration": true,
 | 
				
			||||||
    "esModuleInterop": true,
 | 
							"strict": false,
 | 
				
			||||||
    "moduleResolution": "node",
 | 
							"noUnusedLocals": true,
 | 
				
			||||||
    "module": "commonjs",
 | 
							"noImplicitReturns": true,
 | 
				
			||||||
    "outDir": "./dist/cjs",
 | 
							"noFallthroughCasesInSwitch": true,
 | 
				
			||||||
    "declaration": true,
 | 
							"skipLibCheck": true,
 | 
				
			||||||
    "declarationDir": "./dist/dts",
 | 
							"allowJs": true,
 | 
				
			||||||
    "strict": false,
 | 
							"target": "ES6",
 | 
				
			||||||
    "noUnusedLocals": true,
 | 
							"paths": {
 | 
				
			||||||
    "noImplicitReturns": true,
 | 
								"@/*": [
 | 
				
			||||||
    "noFallthroughCasesInSwitch": true,
 | 
									"./node_modules/@*"
 | 
				
			||||||
    "skipLibCheck": true,
 | 
								]
 | 
				
			||||||
    "allowJs": true,
 | 
							}
 | 
				
			||||||
    "target": "ES6",
 | 
						},
 | 
				
			||||||
    "paths": {
 | 
						"include": [
 | 
				
			||||||
      "@/*": ["./node_modules/@*"]
 | 
							"src/**/*"
 | 
				
			||||||
    }
 | 
						],
 | 
				
			||||||
  }
 | 
						"exclude": [
 | 
				
			||||||
 | 
							"node_modules/",
 | 
				
			||||||
 | 
							"test/"
 | 
				
			||||||
 | 
						]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user