bsm-api-client 1.2.0__tar.gz → 1.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {bsm_api_client-1.2.0/src/bsm_api_client.egg-info → bsm_api_client-1.3.0}/PKG-INFO +5 -4
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/pyproject.toml +5 -4
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client/api_client.py +2 -0
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client/cli/__main__.py +4 -2
- bsm_api_client-1.3.0/src/bsm_api_client/cli/account.py +74 -0
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client/cli/addon.py +10 -2
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client/cli/auth.py +17 -2
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client/cli/backup.py +37 -8
- bsm_api_client-1.3.0/src/bsm_api_client/cli/content.py +20 -0
- bsm_api_client-1.3.0/src/bsm_api_client/cli/decorators.py +73 -0
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client/cli/main_menus.py +1 -1
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client/cli/server.py +44 -27
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client/cli/world.py +28 -6
- bsm_api_client-1.3.0/src/bsm_api_client/client/_account_methods.py +102 -0
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client/client/_content_methods.py +40 -0
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client/client/_manager_methods.py +3 -2
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client/client/_server_info_methods.py +8 -6
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client/models.py +68 -0
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0/src/bsm_api_client.egg-info}/PKG-INFO +5 -4
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client.egg-info/SOURCES.txt +4 -0
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client.egg-info/requires.txt +4 -3
- bsm_api_client-1.3.0/test/test_account_methods.py +142 -0
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/test/test_content_methods.py +151 -1
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/test/test_server_info_methods.py +4 -4
- bsm_api_client-1.2.0/src/bsm_api_client/cli/decorators.py +0 -39
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/LICENSE +0 -0
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/README.md +0 -0
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/setup.cfg +0 -0
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client/__init__.py +0 -0
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client/cli/__init__.py +0 -0
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client/cli/allowlist.py +0 -0
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client/cli/config.py +0 -0
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client/cli/permissions.py +0 -0
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client/cli/player.py +0 -0
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client/cli/plugins.py +0 -0
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client/cli/properties.py +0 -0
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client/cli/system.py +0 -0
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client/client/__init__.py +0 -0
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client/client/_plugin_methods.py +0 -0
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client/client/_server_action_methods.py +0 -0
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client/client_base.py +0 -0
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client/exceptions.py +0 -0
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client.egg-info/dependency_links.txt +0 -0
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client.egg-info/entry_points.txt +0 -0
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/src/bsm_api_client.egg-info/top_level.txt +0 -0
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/test/test_api_client.py +0 -0
- {bsm_api_client-1.2.0 → bsm_api_client-1.3.0}/test/test_manager_methods.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bsm-api-client
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: A Python client library for the Bedrock Server Manager API
|
|
5
5
|
Author-email: DMedina559 <dmedina559-github@outlook.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/DMedina559/bsm-api-client
|
|
@@ -21,12 +21,13 @@ Requires-Python: >=3.8
|
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
23
|
Requires-Dist: aiohttp<4.0.0,>=3.8.0
|
|
24
|
-
Requires-Dist: pydantic<2.
|
|
24
|
+
Requires-Dist: pydantic<2.13,>=2.11.0
|
|
25
25
|
Provides-Extra: dev
|
|
26
26
|
Requires-Dist: pytest<8.5,>=8.4.0; extra == "dev"
|
|
27
|
-
Requires-Dist: pytest-asyncio<1.
|
|
28
|
-
Requires-Dist: black<25.
|
|
27
|
+
Requires-Dist: pytest-asyncio<1.3.0,>=1.1.0; extra == "dev"
|
|
28
|
+
Requires-Dist: black<25.12,>=25.1.0; extra == "dev"
|
|
29
29
|
Requires-Dist: flake8<7.4,>=7.3.0; extra == "dev"
|
|
30
|
+
Requires-Dist: bedrock-server-manager<3.7.0,>=3.6.0b1; extra == "dev"
|
|
30
31
|
Provides-Extra: cli
|
|
31
32
|
Requires-Dist: click<8.3,>=8.2.0; extra == "cli"
|
|
32
33
|
Requires-Dist: questionary<2.2,>=2.1.0; extra == "cli"
|
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
|
|
6
6
|
[project]
|
|
7
7
|
name = "bsm-api-client"
|
|
8
|
-
version = "1.
|
|
8
|
+
version = "1.3.0"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name="DMedina559", email="dmedina559-github@outlook.com" },
|
|
11
11
|
]
|
|
@@ -28,15 +28,16 @@ classifiers = [
|
|
|
28
28
|
]
|
|
29
29
|
dependencies = [
|
|
30
30
|
"aiohttp >= 3.8.0,<4.0.0",
|
|
31
|
-
"pydantic >= 2.11.0,<2.
|
|
31
|
+
"pydantic >= 2.11.0,< 2.13",
|
|
32
32
|
]
|
|
33
33
|
|
|
34
34
|
[project.optional-dependencies]
|
|
35
35
|
dev = [
|
|
36
36
|
"pytest >=8.4.0,<8.5",
|
|
37
|
-
"pytest-asyncio >= 1.1.0,<1.
|
|
38
|
-
"black >=25.1.0,<25.
|
|
37
|
+
"pytest-asyncio >= 1.1.0,< 1.3.0",
|
|
38
|
+
"black >=25.1.0,<25.12",
|
|
39
39
|
"flake8 >=7.3.0,<7.4",
|
|
40
|
+
"bedrock-server-manager >=3.6.0b1,<3.7.0",
|
|
40
41
|
]
|
|
41
42
|
cli = [
|
|
42
43
|
"click >=8.2.0,<8.3",
|
|
@@ -12,6 +12,7 @@ from .client._server_info_methods import ServerInfoMethodsMixin
|
|
|
12
12
|
from .client._server_action_methods import ServerActionMethodsMixin
|
|
13
13
|
from .client._content_methods import ContentMethodsMixin
|
|
14
14
|
from .client._plugin_methods import PluginMethodsMixin
|
|
15
|
+
from .client._account_methods import AccountMethodsMixin
|
|
15
16
|
|
|
16
17
|
_LOGGER = logging.getLogger(__name__.split(".")[0] + ".client")
|
|
17
18
|
|
|
@@ -23,6 +24,7 @@ class BedrockServerManagerApi(
|
|
|
23
24
|
ServerActionMethodsMixin,
|
|
24
25
|
ContentMethodsMixin,
|
|
25
26
|
PluginMethodsMixin,
|
|
27
|
+
AccountMethodsMixin,
|
|
26
28
|
):
|
|
27
29
|
"""API Client for the Bedrock Server Manager.
|
|
28
30
|
|
|
@@ -15,7 +15,6 @@ from .auth import auth
|
|
|
15
15
|
from .server import server
|
|
16
16
|
from .addon import addon
|
|
17
17
|
from .backup import backup
|
|
18
|
-
from .cleanup import cleanup
|
|
19
18
|
from .player import player
|
|
20
19
|
from .plugins import plugin
|
|
21
20
|
from .allowlist import allowlist
|
|
@@ -23,6 +22,8 @@ from .permissions import permissions
|
|
|
23
22
|
from .properties import properties
|
|
24
23
|
from .system import system
|
|
25
24
|
from .world import world
|
|
25
|
+
from .account import account
|
|
26
|
+
from .content import content
|
|
26
27
|
from .main_menus import main_menu
|
|
27
28
|
from .decorators import AsyncGroup
|
|
28
29
|
|
|
@@ -60,7 +61,6 @@ cli.add_command(auth)
|
|
|
60
61
|
cli.add_command(server)
|
|
61
62
|
cli.add_command(addon)
|
|
62
63
|
cli.add_command(backup)
|
|
63
|
-
cli.add_command(cleanup)
|
|
64
64
|
cli.add_command(player)
|
|
65
65
|
cli.add_command(plugin)
|
|
66
66
|
cli.add_command(allowlist)
|
|
@@ -68,6 +68,8 @@ cli.add_command(permissions)
|
|
|
68
68
|
cli.add_command(properties)
|
|
69
69
|
cli.add_command(system)
|
|
70
70
|
cli.add_command(world)
|
|
71
|
+
cli.add_command(account)
|
|
72
|
+
cli.add_command(content)
|
|
71
73
|
|
|
72
74
|
if __name__ == "__main__":
|
|
73
75
|
cli()
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# src/bsm_api_client/cli/account.py
|
|
2
|
+
"""CLI commands for account management."""
|
|
3
|
+
import click
|
|
4
|
+
from .decorators import pass_async_context
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@click.group()
|
|
8
|
+
def account():
|
|
9
|
+
"""Commands for managing your account."""
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@account.command()
|
|
14
|
+
@pass_async_context
|
|
15
|
+
async def details(ctx):
|
|
16
|
+
"""Get your account details."""
|
|
17
|
+
client = ctx.obj["client"]
|
|
18
|
+
details = await client.async_get_account_details()
|
|
19
|
+
click.echo(details.model_dump_json(indent=2))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@account.command()
|
|
23
|
+
@click.option("--theme", prompt="Theme name", help="The name of the theme to set.")
|
|
24
|
+
@pass_async_context
|
|
25
|
+
async def update_theme(ctx, theme):
|
|
26
|
+
"""Update your theme."""
|
|
27
|
+
from bsm_api_client.models import ThemeUpdate
|
|
28
|
+
|
|
29
|
+
client = ctx.obj["client"]
|
|
30
|
+
payload = ThemeUpdate(theme=theme)
|
|
31
|
+
response = await client.async_update_theme(payload)
|
|
32
|
+
click.echo(response.model_dump_json(indent=2))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@account.command()
|
|
36
|
+
@click.option("--full-name", prompt="Full Name", help="Your full name.")
|
|
37
|
+
@click.option("--email", prompt="Email", help="Your email address.")
|
|
38
|
+
@pass_async_context
|
|
39
|
+
async def update_profile(ctx, full_name, email):
|
|
40
|
+
"""Update your profile."""
|
|
41
|
+
from bsm_api_client.models import ProfileUpdate
|
|
42
|
+
|
|
43
|
+
client = ctx.obj["client"]
|
|
44
|
+
payload = ProfileUpdate(full_name=full_name, email=email)
|
|
45
|
+
response = await client.async_update_profile(payload)
|
|
46
|
+
click.echo(response.model_dump_json(indent=2))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@account.command()
|
|
50
|
+
@click.option(
|
|
51
|
+
"--current-password",
|
|
52
|
+
prompt=True,
|
|
53
|
+
hide_input=True,
|
|
54
|
+
confirmation_prompt=False,
|
|
55
|
+
help="Your current password.",
|
|
56
|
+
)
|
|
57
|
+
@click.option(
|
|
58
|
+
"--new-password",
|
|
59
|
+
prompt=True,
|
|
60
|
+
hide_input=True,
|
|
61
|
+
confirmation_prompt=True,
|
|
62
|
+
help="Your new password.",
|
|
63
|
+
)
|
|
64
|
+
@pass_async_context
|
|
65
|
+
async def change_password(ctx, current_password, new_password):
|
|
66
|
+
"""Change your password."""
|
|
67
|
+
from bsm_api_client.models import ChangePasswordRequest
|
|
68
|
+
|
|
69
|
+
client = ctx.obj["client"]
|
|
70
|
+
payload = ChangePasswordRequest(
|
|
71
|
+
current_password=current_password, new_password=new_password
|
|
72
|
+
)
|
|
73
|
+
response = await client.async_change_password(payload)
|
|
74
|
+
click.echo(response.model_dump_json(indent=2))
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import click
|
|
2
2
|
import os
|
|
3
3
|
import questionary
|
|
4
|
+
from .decorators import pass_async_context, monitor_task
|
|
4
5
|
from bsm_api_client.models import FileNamePayload
|
|
5
6
|
|
|
6
7
|
|
|
@@ -21,7 +22,7 @@ def addon():
|
|
|
21
22
|
type=click.Path(exists=True, dir_okay=False, resolve_path=True),
|
|
22
23
|
help="Path to the addon file (.mcpack, .mcaddon); skips interactive menu.",
|
|
23
24
|
)
|
|
24
|
-
@
|
|
25
|
+
@pass_async_context
|
|
25
26
|
async def install_addon(ctx, server_name: str, addon_file_path: str):
|
|
26
27
|
"""Installs a behavior or resource pack addon to a specified server."""
|
|
27
28
|
client = ctx.obj.get("client")
|
|
@@ -62,7 +63,14 @@ async def install_addon(ctx, server_name: str, addon_file_path: str):
|
|
|
62
63
|
|
|
63
64
|
payload = FileNamePayload(filename=addon_filename)
|
|
64
65
|
response = await client.async_install_server_addon(server_name, payload)
|
|
65
|
-
if response.
|
|
66
|
+
if response.task_id:
|
|
67
|
+
await monitor_task(
|
|
68
|
+
client,
|
|
69
|
+
response.task_id,
|
|
70
|
+
f"Addon '{addon_filename}' installed successfully",
|
|
71
|
+
"Failed to install addon",
|
|
72
|
+
)
|
|
73
|
+
elif response.status == "success":
|
|
66
74
|
click.secho(f"Addon '{addon_filename}' installed successfully.", fg="green")
|
|
67
75
|
else:
|
|
68
76
|
click.secho(f"Failed to install addon: {response.message}", fg="red")
|
|
@@ -3,6 +3,13 @@ from .config import Config
|
|
|
3
3
|
from bsm_api_client import BedrockServerManagerApi, AuthError
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
def _validate_and_get_url(url: str) -> str:
|
|
7
|
+
"""Validates and returns a url with a scheme."""
|
|
8
|
+
if not url.startswith("http://") and not url.startswith("https://"):
|
|
9
|
+
return f"http://{url}"
|
|
10
|
+
return url
|
|
11
|
+
|
|
12
|
+
|
|
6
13
|
@click.group()
|
|
7
14
|
def auth():
|
|
8
15
|
"""Manages authentication."""
|
|
@@ -29,7 +36,8 @@ async def login(ctx, base_url, username, password, verify_ssl, token):
|
|
|
29
36
|
config = ctx.obj["config"]
|
|
30
37
|
|
|
31
38
|
if base_url:
|
|
32
|
-
|
|
39
|
+
validated_url = _validate_and_get_url(base_url)
|
|
40
|
+
config.set("base_url", validated_url)
|
|
33
41
|
|
|
34
42
|
config.set("verify_ssl", verify_ssl)
|
|
35
43
|
|
|
@@ -42,6 +50,9 @@ async def login(ctx, base_url, username, password, verify_ssl, token):
|
|
|
42
50
|
await interactive_login(ctx)
|
|
43
51
|
return
|
|
44
52
|
|
|
53
|
+
if username and not password:
|
|
54
|
+
password = click.prompt("Password", hide_input=True)
|
|
55
|
+
|
|
45
56
|
client = BedrockServerManagerApi(
|
|
46
57
|
base_url=config.base_url,
|
|
47
58
|
username=username,
|
|
@@ -64,8 +75,12 @@ async def interactive_login(ctx):
|
|
|
64
75
|
username = click.prompt("Username")
|
|
65
76
|
password = click.prompt("Password", hide_input=True)
|
|
66
77
|
|
|
78
|
+
validated_url = _validate_and_get_url(config.base_url)
|
|
79
|
+
if validated_url != config.base_url:
|
|
80
|
+
config.set("base_url", validated_url)
|
|
81
|
+
|
|
67
82
|
client = BedrockServerManagerApi(
|
|
68
|
-
base_url=
|
|
83
|
+
base_url=validated_url,
|
|
69
84
|
username=username,
|
|
70
85
|
password=password,
|
|
71
86
|
verify_ssl=config.verify_ssl,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import click
|
|
2
2
|
import os
|
|
3
3
|
import questionary
|
|
4
|
+
from .decorators import pass_async_context, monitor_task
|
|
4
5
|
from bsm_api_client.models import BackupActionPayload, RestoreActionPayload
|
|
5
6
|
|
|
6
7
|
|
|
@@ -27,7 +28,7 @@ def backup():
|
|
|
27
28
|
"file_to_backup",
|
|
28
29
|
help="Specific file to back up (required if --type=config).",
|
|
29
30
|
)
|
|
30
|
-
@
|
|
31
|
+
@pass_async_context
|
|
31
32
|
async def create_backup(ctx, server_name: str, backup_type: str, file_to_backup: str):
|
|
32
33
|
"""Creates a backup of specified server data."""
|
|
33
34
|
client = ctx.obj.get("client")
|
|
@@ -51,16 +52,30 @@ async def create_backup(ctx, server_name: str, backup_type: str, file_to_backup:
|
|
|
51
52
|
)
|
|
52
53
|
response = await client.async_trigger_server_backup(server_name, payload)
|
|
53
54
|
|
|
54
|
-
if response.
|
|
55
|
-
|
|
55
|
+
if response.task_id:
|
|
56
|
+
await monitor_task(
|
|
57
|
+
client,
|
|
58
|
+
response.task_id,
|
|
59
|
+
"Backup completed successfully",
|
|
60
|
+
"Failed to create backup",
|
|
61
|
+
)
|
|
56
62
|
click.echo("Pruning old backups...")
|
|
57
63
|
prune_response = await client.async_prune_server_backups(server_name)
|
|
58
|
-
if prune_response.
|
|
64
|
+
if prune_response.task_id:
|
|
65
|
+
await monitor_task(
|
|
66
|
+
client,
|
|
67
|
+
prune_response.task_id,
|
|
68
|
+
"Pruning complete",
|
|
69
|
+
"Failed to prune backups",
|
|
70
|
+
)
|
|
71
|
+
elif prune_response.status == "success":
|
|
59
72
|
click.secho("Pruning complete.", fg="green")
|
|
60
73
|
else:
|
|
61
74
|
click.secho(
|
|
62
75
|
f"Failed to prune backups: {prune_response.message}", fg="red"
|
|
63
76
|
)
|
|
77
|
+
elif response.status == "success":
|
|
78
|
+
click.secho("Backup completed successfully.", fg="green")
|
|
64
79
|
else:
|
|
65
80
|
click.secho(f"Failed to create backup: {response.message}", fg="red")
|
|
66
81
|
|
|
@@ -79,7 +94,7 @@ async def create_backup(ctx, server_name: str, backup_type: str, file_to_backup:
|
|
|
79
94
|
type=click.Path(exists=True, dir_okay=False, resolve_path=True),
|
|
80
95
|
help="Path to the backup file to restore; skips interactive menu.",
|
|
81
96
|
)
|
|
82
|
-
@
|
|
97
|
+
@pass_async_context
|
|
83
98
|
async def restore_backup(ctx, server_name: str, backup_file_path: str):
|
|
84
99
|
"""Restores server data from a specified backup file."""
|
|
85
100
|
client = ctx.obj.get("client")
|
|
@@ -116,7 +131,14 @@ async def restore_backup(ctx, server_name: str, backup_file_path: str):
|
|
|
116
131
|
)
|
|
117
132
|
response = await client.async_restore_server_backup(server_name, payload)
|
|
118
133
|
|
|
119
|
-
if response.
|
|
134
|
+
if response.task_id:
|
|
135
|
+
await monitor_task(
|
|
136
|
+
client,
|
|
137
|
+
response.task_id,
|
|
138
|
+
"Restore completed successfully",
|
|
139
|
+
"Failed to restore backup",
|
|
140
|
+
)
|
|
141
|
+
elif response.status == "success":
|
|
120
142
|
click.secho("Restore completed successfully.", fg="green")
|
|
121
143
|
else:
|
|
122
144
|
click.secho(f"Failed to restore backup: {response.message}", fg="red")
|
|
@@ -133,7 +155,7 @@ async def restore_backup(ctx, server_name: str, backup_file_path: str):
|
|
|
133
155
|
required=True,
|
|
134
156
|
help="Name of the server whose backups to prune.",
|
|
135
157
|
)
|
|
136
|
-
@
|
|
158
|
+
@pass_async_context
|
|
137
159
|
async def prune_backups(ctx, server_name: str):
|
|
138
160
|
"""Deletes old backups for a server."""
|
|
139
161
|
client = ctx.obj.get("client")
|
|
@@ -144,7 +166,14 @@ async def prune_backups(ctx, server_name: str):
|
|
|
144
166
|
try:
|
|
145
167
|
click.echo(f"Pruning old backups for server '{server_name}'...")
|
|
146
168
|
response = await client.async_prune_server_backups(server_name)
|
|
147
|
-
if response.
|
|
169
|
+
if response.task_id:
|
|
170
|
+
await monitor_task(
|
|
171
|
+
client,
|
|
172
|
+
response.task_id,
|
|
173
|
+
"Pruning complete",
|
|
174
|
+
"Failed to prune backups",
|
|
175
|
+
)
|
|
176
|
+
elif response.status == "success":
|
|
148
177
|
click.secho("Pruning complete.", fg="green")
|
|
149
178
|
else:
|
|
150
179
|
click.secho(f"Failed to prune backups: {response.message}", fg="red")
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# src/bsm_api_client/cli/content.py
|
|
2
|
+
"""CLI commands for content management."""
|
|
3
|
+
import click
|
|
4
|
+
from .decorators import pass_async_context
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@click.group()
|
|
8
|
+
def content():
|
|
9
|
+
"""Commands for managing content."""
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@content.command()
|
|
14
|
+
@click.argument("file_path", type=click.Path(exists=True, dir_okay=False))
|
|
15
|
+
@pass_async_context
|
|
16
|
+
async def upload(ctx, file_path):
|
|
17
|
+
"""Upload a content file."""
|
|
18
|
+
client = ctx.obj["client"]
|
|
19
|
+
response = await client.async_upload_content(file_path)
|
|
20
|
+
click.echo(response)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import functools
|
|
3
|
+
import time
|
|
4
|
+
import click
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AsyncGroup(click.Group):
|
|
8
|
+
def __init__(self, *args, **kwargs):
|
|
9
|
+
super().__init__(*args, **kwargs)
|
|
10
|
+
self.async_context_settings = {}
|
|
11
|
+
|
|
12
|
+
def context(self, f):
|
|
13
|
+
self.async_context_settings["context"] = f
|
|
14
|
+
return f
|
|
15
|
+
|
|
16
|
+
def invoke(self, ctx):
|
|
17
|
+
ctx.obj = ctx.obj or {}
|
|
18
|
+
if self.async_context_settings.get("context"):
|
|
19
|
+
|
|
20
|
+
async def runner():
|
|
21
|
+
async with self.async_context_settings["context"](ctx):
|
|
22
|
+
result = super(AsyncGroup, self).invoke(ctx)
|
|
23
|
+
if asyncio.iscoroutine(result):
|
|
24
|
+
await result
|
|
25
|
+
|
|
26
|
+
return asyncio.run(runner())
|
|
27
|
+
|
|
28
|
+
result = super().invoke(ctx)
|
|
29
|
+
if asyncio.iscoroutine(result):
|
|
30
|
+
return asyncio.run(result)
|
|
31
|
+
return result
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def pass_async_context(f):
|
|
35
|
+
@functools.wraps(f)
|
|
36
|
+
def wrapper(*args, **kwargs):
|
|
37
|
+
ctx = click.get_current_context()
|
|
38
|
+
return f(ctx, *args, **kwargs)
|
|
39
|
+
|
|
40
|
+
return wrapper
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
async def monitor_task(
|
|
44
|
+
client, task_id: str, success_message: str, failure_message: str
|
|
45
|
+
):
|
|
46
|
+
"""Polls the status of a background task until it completes."""
|
|
47
|
+
click.echo("Task started in the background. Polling for completion...")
|
|
48
|
+
while True:
|
|
49
|
+
try:
|
|
50
|
+
status_response = await client.async_get_task_status(task_id)
|
|
51
|
+
status = status_response.get("status")
|
|
52
|
+
message = status_response.get("message", "No message provided.")
|
|
53
|
+
|
|
54
|
+
if status == "success":
|
|
55
|
+
click.secho(f"{success_message}: {message}", fg="green")
|
|
56
|
+
break
|
|
57
|
+
elif status == "error":
|
|
58
|
+
click.secho(
|
|
59
|
+
f"{failure_message}: {message}",
|
|
60
|
+
fg="red",
|
|
61
|
+
)
|
|
62
|
+
break
|
|
63
|
+
elif status == "pending":
|
|
64
|
+
# Still waiting, continue loop
|
|
65
|
+
pass
|
|
66
|
+
else:
|
|
67
|
+
# Handle unexpected status
|
|
68
|
+
click.secho(f"Unknown task status received: {status}", fg="yellow")
|
|
69
|
+
|
|
70
|
+
time.sleep(2)
|
|
71
|
+
except Exception as e:
|
|
72
|
+
click.secho(f"An error occurred while monitoring task: {e}", fg="red")
|
|
73
|
+
break
|
|
@@ -82,7 +82,7 @@ async def main_menu(ctx: click.Context):
|
|
|
82
82
|
await ctx.invoke(list_servers)
|
|
83
83
|
|
|
84
84
|
# --- Dynamically build menu choices ---
|
|
85
|
-
response = await client.
|
|
85
|
+
response = await client.async_get_servers()
|
|
86
86
|
server_names = (
|
|
87
87
|
[s["name"] for s in response.servers] if response.servers else []
|
|
88
88
|
)
|
|
@@ -2,7 +2,7 @@ import os
|
|
|
2
2
|
import time
|
|
3
3
|
import click
|
|
4
4
|
import questionary
|
|
5
|
-
from .decorators import pass_async_context
|
|
5
|
+
from .decorators import pass_async_context, monitor_task
|
|
6
6
|
from bsm_api_client.models import InstallServerPayload, CommandPayload
|
|
7
7
|
|
|
8
8
|
|
|
@@ -61,7 +61,7 @@ async def list_servers(ctx, loop, server_name):
|
|
|
61
61
|
return
|
|
62
62
|
|
|
63
63
|
async def _display_status():
|
|
64
|
-
response = await client.
|
|
64
|
+
response = await client.async_get_servers()
|
|
65
65
|
all_servers = response.servers
|
|
66
66
|
|
|
67
67
|
if server_name:
|
|
@@ -120,7 +120,7 @@ async def start_server(ctx, server_name: str):
|
|
|
120
120
|
@click.option(
|
|
121
121
|
"-s", "--server", "server_name", required=True, help="Name of the server to stop."
|
|
122
122
|
)
|
|
123
|
-
@
|
|
123
|
+
@pass_async_context
|
|
124
124
|
async def stop_server(ctx, server_name: str):
|
|
125
125
|
"""Sends a graceful stop command to a running Bedrock server."""
|
|
126
126
|
client = ctx.obj.get("client")
|
|
@@ -131,7 +131,14 @@ async def stop_server(ctx, server_name: str):
|
|
|
131
131
|
click.echo(f"Attempting to stop server '{server_name}'...")
|
|
132
132
|
try:
|
|
133
133
|
response = await client.async_stop_server(server_name)
|
|
134
|
-
if response.
|
|
134
|
+
if response.task_id:
|
|
135
|
+
await monitor_task(
|
|
136
|
+
client,
|
|
137
|
+
response.task_id,
|
|
138
|
+
"Server stopped successfully",
|
|
139
|
+
"Failed to stop server",
|
|
140
|
+
)
|
|
141
|
+
elif response.status == "success":
|
|
135
142
|
click.secho(f"Stop signal sent to server '{server_name}'.", fg="green")
|
|
136
143
|
else:
|
|
137
144
|
click.secho(f"Failed to stop server: {response.message}", fg="red")
|
|
@@ -147,7 +154,7 @@ async def stop_server(ctx, server_name: str):
|
|
|
147
154
|
required=True,
|
|
148
155
|
help="Name of the server to restart.",
|
|
149
156
|
)
|
|
150
|
-
@
|
|
157
|
+
@pass_async_context
|
|
151
158
|
async def restart_server(ctx, server_name: str):
|
|
152
159
|
"""Gracefully restarts a specific Bedrock server."""
|
|
153
160
|
client = ctx.obj.get("client")
|
|
@@ -158,7 +165,14 @@ async def restart_server(ctx, server_name: str):
|
|
|
158
165
|
click.echo(f"Attempting to restart server '{server_name}'...")
|
|
159
166
|
try:
|
|
160
167
|
response = await client.async_restart_server(server_name)
|
|
161
|
-
if response.
|
|
168
|
+
if response.task_id:
|
|
169
|
+
await monitor_task(
|
|
170
|
+
client,
|
|
171
|
+
response.task_id,
|
|
172
|
+
"Server restarted successfully",
|
|
173
|
+
"Failed to restart server",
|
|
174
|
+
)
|
|
175
|
+
elif response.status == "success":
|
|
162
176
|
click.secho(f"Restart signal sent to server '{server_name}'.", fg="green")
|
|
163
177
|
else:
|
|
164
178
|
click.secho(f"Failed to restart server: {response.message}", fg="red")
|
|
@@ -233,23 +247,12 @@ async def install(ctx):
|
|
|
233
247
|
install_result = await client.async_install_new_server(payload)
|
|
234
248
|
|
|
235
249
|
if install_result.task_id:
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if status_response["status"] == "success":
|
|
243
|
-
click.secho(
|
|
244
|
-
"Server installation completed successfully.", fg="green"
|
|
245
|
-
)
|
|
246
|
-
break
|
|
247
|
-
elif status_response["status"] == "error":
|
|
248
|
-
click.secho(
|
|
249
|
-
f"Installation failed: {status_response['message']}", fg="red"
|
|
250
|
-
)
|
|
251
|
-
return
|
|
252
|
-
time.sleep(2)
|
|
250
|
+
await monitor_task(
|
|
251
|
+
client,
|
|
252
|
+
install_result.task_id,
|
|
253
|
+
"Server installation completed successfully",
|
|
254
|
+
"Installation failed",
|
|
255
|
+
)
|
|
253
256
|
elif install_result.status == "success":
|
|
254
257
|
click.secho("Server files installed successfully.", fg="green")
|
|
255
258
|
else:
|
|
@@ -283,7 +286,7 @@ async def install(ctx):
|
|
|
283
286
|
@click.option(
|
|
284
287
|
"-s", "--server", "server_name", required=True, help="Name of the server to update."
|
|
285
288
|
)
|
|
286
|
-
@
|
|
289
|
+
@pass_async_context
|
|
287
290
|
async def update(ctx, server_name: str):
|
|
288
291
|
"""Checks for and applies updates to an existing Bedrock server."""
|
|
289
292
|
client = ctx.obj.get("client")
|
|
@@ -294,7 +297,14 @@ async def update(ctx, server_name: str):
|
|
|
294
297
|
click.echo(f"Checking for updates for server '{server_name}'...")
|
|
295
298
|
try:
|
|
296
299
|
response = await client.async_update_server(server_name)
|
|
297
|
-
if response.
|
|
300
|
+
if response.task_id:
|
|
301
|
+
await monitor_task(
|
|
302
|
+
client,
|
|
303
|
+
response.task_id,
|
|
304
|
+
"Server update completed successfully",
|
|
305
|
+
"Failed to update server",
|
|
306
|
+
)
|
|
307
|
+
elif response.status == "success":
|
|
298
308
|
click.secho("Update check complete.", fg="green")
|
|
299
309
|
else:
|
|
300
310
|
click.secho(f"Failed to update server: {response.message}", fg="red")
|
|
@@ -307,7 +317,7 @@ async def update(ctx, server_name: str):
|
|
|
307
317
|
"-s", "--server", "server_name", required=True, help="Name of the server to delete."
|
|
308
318
|
)
|
|
309
319
|
@click.option("-y", "--yes", is_flag=True, help="Bypass the confirmation prompt.")
|
|
310
|
-
@
|
|
320
|
+
@pass_async_context
|
|
311
321
|
async def delete_server(ctx, server_name: str, yes: bool):
|
|
312
322
|
"""Deletes all data for a server, including world, configs, and backups."""
|
|
313
323
|
client = ctx.obj.get("client")
|
|
@@ -329,7 +339,14 @@ async def delete_server(ctx, server_name: str, yes: bool):
|
|
|
329
339
|
click.echo(f"Proceeding with deletion of server '{server_name}'...")
|
|
330
340
|
try:
|
|
331
341
|
response = await client.async_delete_server(server_name)
|
|
332
|
-
if response.
|
|
342
|
+
if response.task_id:
|
|
343
|
+
await monitor_task(
|
|
344
|
+
client,
|
|
345
|
+
response.task_id,
|
|
346
|
+
"Server deleted successfully",
|
|
347
|
+
"Failed to delete server",
|
|
348
|
+
)
|
|
349
|
+
elif response.status == "success":
|
|
333
350
|
click.secho(
|
|
334
351
|
f"Server '{server_name}' and all its data have been deleted.",
|
|
335
352
|
fg="green",
|