bsm-cli 1.6.0b3__py3-none-any.whl
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_cli/__init__.py +0 -0
- bsm_cli/__main__.py +88 -0
- bsm_cli/account.py +75 -0
- bsm_cli/addon.py +362 -0
- bsm_cli/allowlist.py +229 -0
- bsm_cli/auth.py +106 -0
- bsm_cli/backup.py +263 -0
- bsm_cli/bans.py +190 -0
- bsm_cli/config.py +89 -0
- bsm_cli/content.py +21 -0
- bsm_cli/decorators.py +138 -0
- bsm_cli/main_menus.py +293 -0
- bsm_cli/permissions.py +191 -0
- bsm_cli/player.py +59 -0
- bsm_cli/plugins.py +296 -0
- bsm_cli/properties.py +197 -0
- bsm_cli/server.py +471 -0
- bsm_cli/system.py +142 -0
- bsm_cli/users.py +245 -0
- bsm_cli/world.py +171 -0
- bsm_cli-1.6.0b3.dist-info/METADATA +77 -0
- bsm_cli-1.6.0b3.dist-info/RECORD +26 -0
- bsm_cli-1.6.0b3.dist-info/WHEEL +5 -0
- bsm_cli-1.6.0b3.dist-info/entry_points.txt +2 -0
- bsm_cli-1.6.0b3.dist-info/licenses/LICENSE +21 -0
- bsm_cli-1.6.0b3.dist-info/top_level.txt +1 -0
bsm_cli/main_menus.py
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import questionary
|
|
3
|
+
from bsm_cli.server import list_servers
|
|
4
|
+
from questionary import Separator
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
async def _world_management_menu(ctx: click.Context, server_name: str):
|
|
8
|
+
"""Displays a sub-menu for world management actions."""
|
|
9
|
+
world_group = ctx.obj["cli"].get_command(ctx, "world")
|
|
10
|
+
if not world_group:
|
|
11
|
+
click.secho("Error: World command group not found.", fg="red")
|
|
12
|
+
return
|
|
13
|
+
|
|
14
|
+
menu_map = {
|
|
15
|
+
"Install/Replace World": world_group.get_command(ctx, "install"),
|
|
16
|
+
"Export Current World": world_group.get_command(ctx, "export"),
|
|
17
|
+
"Reset Current World": world_group.get_command(ctx, "reset"),
|
|
18
|
+
"Back": None,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
while True:
|
|
22
|
+
choice = await questionary.select(
|
|
23
|
+
f"World Management for '{server_name}':",
|
|
24
|
+
choices=list(menu_map.keys()),
|
|
25
|
+
use_indicator=True,
|
|
26
|
+
).ask_async()
|
|
27
|
+
|
|
28
|
+
if choice is None or choice == "Back":
|
|
29
|
+
return
|
|
30
|
+
command = menu_map.get(choice)
|
|
31
|
+
if command:
|
|
32
|
+
await ctx.invoke(command, server_name=server_name)
|
|
33
|
+
break
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
async def _backup_restore_menu(ctx: click.Context, server_name: str):
|
|
37
|
+
"""Displays a sub-menu for backup and restore actions."""
|
|
38
|
+
backup_group = ctx.obj["cli"].get_command(ctx, "backup")
|
|
39
|
+
if not backup_group:
|
|
40
|
+
click.secho("Error: Backup command group not found.", fg="red")
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
menu_map = {
|
|
44
|
+
"Create Backup": backup_group.get_command(ctx, "create"),
|
|
45
|
+
"Restore from Backup": backup_group.get_command(ctx, "restore"),
|
|
46
|
+
"Prune Old Backups": backup_group.get_command(ctx, "prune"),
|
|
47
|
+
"Back": None,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
while True:
|
|
51
|
+
choice = await questionary.select(
|
|
52
|
+
f"Backup/Restore for '{server_name}':",
|
|
53
|
+
choices=list(menu_map.keys()),
|
|
54
|
+
use_indicator=True,
|
|
55
|
+
).ask_async()
|
|
56
|
+
|
|
57
|
+
if choice is None or choice == "Back":
|
|
58
|
+
return
|
|
59
|
+
command = menu_map.get(choice)
|
|
60
|
+
if command:
|
|
61
|
+
await ctx.invoke(command, server_name=server_name)
|
|
62
|
+
break
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async def _bans_menu(ctx: click.Context, server_name: str):
|
|
66
|
+
"""Displays a sub-menu for ban management actions."""
|
|
67
|
+
bans_group = ctx.obj["cli"].get_command(ctx, "bans")
|
|
68
|
+
if not bans_group:
|
|
69
|
+
click.secho("Error: Bans command group not found.", fg="red")
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
menu_map = {
|
|
73
|
+
"List Bans": bans_group.get_command(ctx, "list"),
|
|
74
|
+
"Add Ban": bans_group.get_command(ctx, "add"),
|
|
75
|
+
"Remove Ban": bans_group.get_command(ctx, "remove"),
|
|
76
|
+
"Back": None,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
while True:
|
|
80
|
+
choice = await questionary.select(
|
|
81
|
+
f"Bans for '{server_name}':",
|
|
82
|
+
choices=list(menu_map.keys()),
|
|
83
|
+
use_indicator=True,
|
|
84
|
+
).ask_async()
|
|
85
|
+
|
|
86
|
+
if choice is None or choice == "Back":
|
|
87
|
+
return
|
|
88
|
+
command = menu_map.get(choice)
|
|
89
|
+
if command:
|
|
90
|
+
await ctx.invoke(command, server_name=server_name)
|
|
91
|
+
break
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
async def main_menu(ctx: click.Context): # noqa: C901
|
|
95
|
+
"""Displays the main application menu and drives interactive mode."""
|
|
96
|
+
client = ctx.obj.get("client")
|
|
97
|
+
if not client:
|
|
98
|
+
click.secho(
|
|
99
|
+
"You are not logged in. Please run `bsm-cli auth login` first.", fg="red"
|
|
100
|
+
)
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
cli = ctx.obj["cli"]
|
|
104
|
+
|
|
105
|
+
while True:
|
|
106
|
+
try:
|
|
107
|
+
click.clear()
|
|
108
|
+
click.secho("BSM API Client - Main Menu", fg="magenta", bold=True)
|
|
109
|
+
|
|
110
|
+
await ctx.invoke(list_servers)
|
|
111
|
+
|
|
112
|
+
# --- Dynamically build menu choices ---
|
|
113
|
+
response = await client.async_get_servers()
|
|
114
|
+
server_names = (
|
|
115
|
+
[s.name for s in response.servers] if response.servers else []
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
from questionary import Choice
|
|
119
|
+
|
|
120
|
+
menu_choices: list[Choice | Separator | str] = ["Install New Server"]
|
|
121
|
+
if server_names:
|
|
122
|
+
menu_choices.append("Manage Existing Server")
|
|
123
|
+
|
|
124
|
+
menu_choices.append("Manage Plugins")
|
|
125
|
+
menu_choices.append("Manage Users")
|
|
126
|
+
menu_choices.append(Separator("--- Application ---"))
|
|
127
|
+
menu_choices.append("Exit")
|
|
128
|
+
|
|
129
|
+
choice = await questionary.select(
|
|
130
|
+
"\nChoose an action:",
|
|
131
|
+
choices=menu_choices, # type: ignore
|
|
132
|
+
use_indicator=True,
|
|
133
|
+
).ask_async()
|
|
134
|
+
|
|
135
|
+
if choice is None or choice == "Exit":
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
if choice == "Install New Server":
|
|
139
|
+
server_group = cli.get_command(ctx, "server")
|
|
140
|
+
install_cmd = server_group.get_command(ctx, "install")
|
|
141
|
+
await ctx.invoke(install_cmd)
|
|
142
|
+
click.pause("Press any key to return to the main menu...")
|
|
143
|
+
|
|
144
|
+
elif choice == "Manage Existing Server":
|
|
145
|
+
server_name = await questionary.select(
|
|
146
|
+
"Select a server:", choices=server_names
|
|
147
|
+
).ask_async()
|
|
148
|
+
if server_name:
|
|
149
|
+
await manage_server_menu(ctx, server_name)
|
|
150
|
+
|
|
151
|
+
elif choice == "Manage Plugins":
|
|
152
|
+
plugin_group = cli.get_command(ctx, "plugin")
|
|
153
|
+
await ctx.invoke(plugin_group)
|
|
154
|
+
click.pause("Press any key to return to the main menu...")
|
|
155
|
+
|
|
156
|
+
elif choice == "Manage Users":
|
|
157
|
+
users_group = cli.get_command(ctx, "users")
|
|
158
|
+
await ctx.invoke(users_group)
|
|
159
|
+
click.pause("Press any key to return to the main menu...")
|
|
160
|
+
|
|
161
|
+
except (click.Abort, KeyboardInterrupt):
|
|
162
|
+
click.echo("\nAction cancelled. Returning to the main menu.")
|
|
163
|
+
click.pause()
|
|
164
|
+
except Exception as e:
|
|
165
|
+
click.secho(f"\nAn unexpected error occurred: {e}", fg="red")
|
|
166
|
+
click.pause("Press any key to return to the main menu...")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
async def manage_server_menu(ctx: click.Context, server_name: str): # noqa: C901
|
|
170
|
+
"""Displays the menu for managing a specific, existing server."""
|
|
171
|
+
cli = ctx.obj["cli"]
|
|
172
|
+
|
|
173
|
+
def get_cmd(group_name, cmd_name):
|
|
174
|
+
"""Helper to safely retrieve a command object from the CLI."""
|
|
175
|
+
group = cli.get_command(ctx, group_name)
|
|
176
|
+
return group.get_command(ctx, cmd_name) if group else None
|
|
177
|
+
|
|
178
|
+
from typing import Any, Dict, Optional, Tuple
|
|
179
|
+
|
|
180
|
+
import click
|
|
181
|
+
|
|
182
|
+
# ---- Define static menu sections ----
|
|
183
|
+
control_map: Dict[str, Tuple[Optional[click.Command], Dict[str, Any]]] = {
|
|
184
|
+
"Start Server": (get_cmd("server", "start"), {}),
|
|
185
|
+
"Stop Server": (get_cmd("server", "stop"), {}),
|
|
186
|
+
"Restart Server": (get_cmd("server", "restart"), {}),
|
|
187
|
+
"Send Command to Server": (get_cmd("server", "send-command"), {}),
|
|
188
|
+
}
|
|
189
|
+
management_map = {
|
|
190
|
+
"Backup or Restore": _backup_restore_menu,
|
|
191
|
+
"Manage World": _world_management_menu,
|
|
192
|
+
"Install Addon": (get_cmd("addon", "install"), {}),
|
|
193
|
+
"Manage Addons": (get_cmd("addon", "manage"), {}),
|
|
194
|
+
}
|
|
195
|
+
config_map: Dict[str, Any] = {
|
|
196
|
+
"Configure Properties": (get_cmd("properties", "set"), {}),
|
|
197
|
+
"Configure Allowlist": (get_cmd("allowlist", "add"), {}),
|
|
198
|
+
"Configure Permissions": (get_cmd("permissions", "set"), {}),
|
|
199
|
+
"Configure Ban List": _bans_menu,
|
|
200
|
+
}
|
|
201
|
+
maintenance_map: Dict[str, Tuple[Optional[click.Command], Dict[str, Any]]] = {
|
|
202
|
+
"Update Server": (get_cmd("server", "update"), {}),
|
|
203
|
+
"Delete Server": (get_cmd("server", "delete"), {}),
|
|
204
|
+
}
|
|
205
|
+
system_map: Dict[str, Tuple[Optional[click.Command], Dict[str, Any]]] = {
|
|
206
|
+
"Configure Settings": (get_cmd("system", "settings"), {}),
|
|
207
|
+
"Monitor Resource Usage": (get_cmd("system", "monitor"), {}),
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
# ---- Combine all maps for easy lookup ----
|
|
211
|
+
full_menu_map = {
|
|
212
|
+
**control_map,
|
|
213
|
+
**management_map,
|
|
214
|
+
**config_map,
|
|
215
|
+
**system_map,
|
|
216
|
+
**maintenance_map,
|
|
217
|
+
"Back to Main Menu": "back",
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
# ---- Build the final choices list for questionary ----
|
|
221
|
+
menu_choices = [
|
|
222
|
+
Separator("--- Server Control ---"),
|
|
223
|
+
*control_map.keys(),
|
|
224
|
+
Separator("--- Management ---"),
|
|
225
|
+
*management_map.keys(),
|
|
226
|
+
Separator("--- Configuration ---"),
|
|
227
|
+
*config_map.keys(),
|
|
228
|
+
]
|
|
229
|
+
|
|
230
|
+
if system_map:
|
|
231
|
+
menu_choices.extend(
|
|
232
|
+
[Separator("--- System & Monitoring ---"), *system_map.keys()]
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
menu_choices.extend(
|
|
236
|
+
[
|
|
237
|
+
Separator("--- Maintenance ---"),
|
|
238
|
+
*maintenance_map.keys(),
|
|
239
|
+
Separator("--------------------"),
|
|
240
|
+
"Back to Main Menu",
|
|
241
|
+
]
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
while True:
|
|
245
|
+
click.clear()
|
|
246
|
+
click.secho(f"--- Managing Server: {server_name} ---", fg="magenta", bold=True)
|
|
247
|
+
await ctx.invoke(list_servers, server_name=server_name) # type: ignore
|
|
248
|
+
|
|
249
|
+
choice = await questionary.select(
|
|
250
|
+
f"\nSelect an action for '{server_name}':",
|
|
251
|
+
choices=menu_choices, # type: ignore
|
|
252
|
+
use_indicator=True,
|
|
253
|
+
).ask_async()
|
|
254
|
+
|
|
255
|
+
if choice is None or choice == "Back to Main Menu":
|
|
256
|
+
return
|
|
257
|
+
|
|
258
|
+
action = full_menu_map.get(choice)
|
|
259
|
+
if not action:
|
|
260
|
+
continue
|
|
261
|
+
|
|
262
|
+
try:
|
|
263
|
+
if callable(action) and not hasattr(action, "commands"):
|
|
264
|
+
await action(ctx, server_name)
|
|
265
|
+
elif isinstance(action, tuple):
|
|
266
|
+
command_obj, kwargs = action
|
|
267
|
+
if not command_obj:
|
|
268
|
+
continue
|
|
269
|
+
if hasattr(command_obj, "name") and command_obj.name == "send-command":
|
|
270
|
+
cmd_str = await questionary.text(
|
|
271
|
+
"Enter command to send:"
|
|
272
|
+
).ask_async()
|
|
273
|
+
if cmd_str:
|
|
274
|
+
kwargs["command_parts"] = cmd_str.split()
|
|
275
|
+
else:
|
|
276
|
+
continue
|
|
277
|
+
kwargs["server_name"] = server_name
|
|
278
|
+
await ctx.invoke(command_obj, **kwargs)
|
|
279
|
+
if hasattr(command_obj, "name") and command_obj.name == "delete":
|
|
280
|
+
click.echo("\nServer has been deleted. Returning to main menu.")
|
|
281
|
+
click.pause()
|
|
282
|
+
return
|
|
283
|
+
elif hasattr(action, "commands"):
|
|
284
|
+
ctx.invoke(action, server_name=server_name) # type: ignore
|
|
285
|
+
|
|
286
|
+
click.pause("\nPress any key to return to the server menu...")
|
|
287
|
+
|
|
288
|
+
except Exception as e:
|
|
289
|
+
import traceback
|
|
290
|
+
|
|
291
|
+
click.secho(f"An error occurred while executing '{choice}': {e}", fg="red")
|
|
292
|
+
click.secho(traceback.format_exc(), fg="red", dim=True)
|
|
293
|
+
click.pause()
|
bsm_cli/permissions.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import questionary
|
|
3
|
+
|
|
4
|
+
from bsm_api_client.models import PermissionsSetPayload, PlayerPermissionPayload
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@click.group()
|
|
8
|
+
def permissions():
|
|
9
|
+
"""Manages player permission levels on a server."""
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@permissions.command("set")
|
|
14
|
+
@click.option(
|
|
15
|
+
"-s",
|
|
16
|
+
"--server",
|
|
17
|
+
"server_name",
|
|
18
|
+
required=True,
|
|
19
|
+
help="The name of the target server.",
|
|
20
|
+
)
|
|
21
|
+
@click.option(
|
|
22
|
+
"-p",
|
|
23
|
+
"--player",
|
|
24
|
+
"player_name",
|
|
25
|
+
help="The gamertag of the player. Skips interactive mode.",
|
|
26
|
+
)
|
|
27
|
+
@click.option(
|
|
28
|
+
"-l",
|
|
29
|
+
"--level",
|
|
30
|
+
type=click.Choice(["visitor", "member", "operator"], case_sensitive=False),
|
|
31
|
+
help="The permission level to grant. Skips interactive mode.",
|
|
32
|
+
)
|
|
33
|
+
@click.pass_context
|
|
34
|
+
async def set_perm(ctx, server_name: str, player_name: str, level: str):
|
|
35
|
+
"""Sets a permission level for a player on a specific server."""
|
|
36
|
+
client = ctx.obj.get("client")
|
|
37
|
+
if not client:
|
|
38
|
+
click.secho("You are not logged in.", fg="red")
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
if not player_name or not level:
|
|
43
|
+
click.secho(
|
|
44
|
+
f"Player or level not specified; starting interactive editor for '{server_name}'...",
|
|
45
|
+
fg="yellow",
|
|
46
|
+
)
|
|
47
|
+
await interactive_permissions_workflow(client, server_name)
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
click.echo(f"Finding player '{player_name}' in global database...")
|
|
51
|
+
all_players_resp = await client.async_get_players()
|
|
52
|
+
player_data = next(
|
|
53
|
+
(
|
|
54
|
+
p
|
|
55
|
+
for p in all_players_resp.players or []
|
|
56
|
+
if p.get("name", "").lower() == player_name.lower()
|
|
57
|
+
),
|
|
58
|
+
None,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
if not player_data or not player_data.get("xuid"):
|
|
62
|
+
click.secho(
|
|
63
|
+
f"Error: Player '{player_name}' not found in the global player database.",
|
|
64
|
+
fg="red",
|
|
65
|
+
)
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
xuid = player_data["xuid"]
|
|
69
|
+
click.echo(
|
|
70
|
+
f"Setting permission for {player_name} (XUID: {xuid}) to '{level}'..."
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
payload = PermissionsSetPayload(
|
|
74
|
+
permissions=[
|
|
75
|
+
PlayerPermissionPayload(
|
|
76
|
+
name=player_name, xuid=xuid, permission_level=level
|
|
77
|
+
)
|
|
78
|
+
]
|
|
79
|
+
)
|
|
80
|
+
response = await client.async_set_server_permissions(server_name, payload)
|
|
81
|
+
|
|
82
|
+
if response.status == "success":
|
|
83
|
+
click.secho("Permission updated successfully.", fg="green")
|
|
84
|
+
else:
|
|
85
|
+
click.secho(f"Failed to set permission: {response.message}", fg="red")
|
|
86
|
+
|
|
87
|
+
except Exception as e:
|
|
88
|
+
click.secho(f"An error occurred: {e}", fg="red")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@permissions.command("list")
|
|
92
|
+
@click.option(
|
|
93
|
+
"-s", "--server", "server_name", required=True, help="The name of the server."
|
|
94
|
+
)
|
|
95
|
+
@click.pass_context
|
|
96
|
+
async def list_perms(ctx, server_name: str):
|
|
97
|
+
"""Lists all configured player permissions for a specific server."""
|
|
98
|
+
client = ctx.obj.get("client")
|
|
99
|
+
if not client:
|
|
100
|
+
click.secho("You are not logged in.", fg="red")
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
response = await client.async_get_server_permissions_data(server_name)
|
|
104
|
+
|
|
105
|
+
if response.status == "success":
|
|
106
|
+
permissions = response.permissions
|
|
107
|
+
if not permissions:
|
|
108
|
+
click.secho(
|
|
109
|
+
f"The permissions file for server '{server_name}' is empty.",
|
|
110
|
+
fg="yellow",
|
|
111
|
+
)
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
click.secho(f"\nPermissions for '{server_name}':", bold=True)
|
|
115
|
+
for p in permissions:
|
|
116
|
+
level = p.get("permission_level", "unknown").lower()
|
|
117
|
+
level_color = {"operator": "red", "member": "green", "visitor": "blue"}.get(
|
|
118
|
+
level, "white"
|
|
119
|
+
)
|
|
120
|
+
level_styled = click.style(level.capitalize(), fg=level_color, bold=True)
|
|
121
|
+
|
|
122
|
+
name = p.get("name", "Unknown Player")
|
|
123
|
+
xuid = p.get("xuid", "N/A")
|
|
124
|
+
click.echo(f" - {name:<20} (XUID: {xuid:<18}) {level_styled}")
|
|
125
|
+
else:
|
|
126
|
+
click.secho(f"Failed to list permissions: {response.message}", fg="red")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
async def interactive_permissions_workflow(client, server_name: str):
|
|
130
|
+
"""Guides the user through an interactive workflow to set a player's permission level."""
|
|
131
|
+
click.secho("\n--- Interactive Permission Configuration ---", bold=True)
|
|
132
|
+
|
|
133
|
+
while True:
|
|
134
|
+
player_response = await client.async_get_players()
|
|
135
|
+
all_players = player_response.players or []
|
|
136
|
+
|
|
137
|
+
if not all_players:
|
|
138
|
+
click.secho(
|
|
139
|
+
"No players found in the global player database (players.json).",
|
|
140
|
+
fg="yellow",
|
|
141
|
+
)
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
player_map = {f"{p['name']} (XUID: {p['xuid']})": p for p in all_players}
|
|
145
|
+
choices = sorted(list(player_map.keys())) + ["Cancel"]
|
|
146
|
+
|
|
147
|
+
player_choice_str = await questionary.select(
|
|
148
|
+
"Select a player to configure permissions for:", choices=choices
|
|
149
|
+
).ask_async()
|
|
150
|
+
|
|
151
|
+
if not player_choice_str or player_choice_str == "Cancel":
|
|
152
|
+
click.secho("Exiting interactive permissions editor.", fg="blue")
|
|
153
|
+
break
|
|
154
|
+
|
|
155
|
+
selected_player = player_map[player_choice_str]
|
|
156
|
+
permission = await questionary.select(
|
|
157
|
+
f"Select permission level for {selected_player['name']}:",
|
|
158
|
+
choices=["member", "operator", "visitor", "Cancel"],
|
|
159
|
+
default="member",
|
|
160
|
+
).ask_async()
|
|
161
|
+
|
|
162
|
+
if not permission or permission == "Cancel":
|
|
163
|
+
click.secho("Operation cancelled.", fg="blue")
|
|
164
|
+
continue
|
|
165
|
+
|
|
166
|
+
payload = PermissionsSetPayload(
|
|
167
|
+
permissions=[
|
|
168
|
+
PlayerPermissionPayload(
|
|
169
|
+
name=selected_player["name"],
|
|
170
|
+
xuid=selected_player["xuid"],
|
|
171
|
+
permission_level=permission,
|
|
172
|
+
)
|
|
173
|
+
]
|
|
174
|
+
)
|
|
175
|
+
perm_response = await client.async_set_server_permissions(server_name, payload)
|
|
176
|
+
|
|
177
|
+
if perm_response.status == "success":
|
|
178
|
+
click.secho(
|
|
179
|
+
f"Permission for {selected_player['name']} set to '{permission}'.",
|
|
180
|
+
fg="green",
|
|
181
|
+
)
|
|
182
|
+
else:
|
|
183
|
+
click.secho(f"Failed to set permission: {perm_response.message}", fg="red")
|
|
184
|
+
|
|
185
|
+
if (
|
|
186
|
+
await questionary.confirm(
|
|
187
|
+
"Configure another player?", default=True
|
|
188
|
+
).ask_async()
|
|
189
|
+
is False
|
|
190
|
+
):
|
|
191
|
+
break
|
bsm_cli/player.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@click.group()
|
|
5
|
+
def player():
|
|
6
|
+
"""Manages the central player database."""
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@player.command("scan")
|
|
11
|
+
@click.pass_context
|
|
12
|
+
async def scan_for_players(ctx):
|
|
13
|
+
"""Scans all server logs to discover player gamertags and XUIDs."""
|
|
14
|
+
client = ctx.obj.get("client")
|
|
15
|
+
if not client:
|
|
16
|
+
click.secho("You are not logged in.", fg="red")
|
|
17
|
+
return
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
click.echo("Scanning all server logs for player data...")
|
|
21
|
+
response = await client.async_scan_players()
|
|
22
|
+
if response.status == "success":
|
|
23
|
+
click.secho("Player database updated successfully.", fg="green")
|
|
24
|
+
else:
|
|
25
|
+
click.secho(f"Failed to scan for players: {response.message}", fg="red")
|
|
26
|
+
except Exception as e:
|
|
27
|
+
click.secho(f"An error occurred during scan: {e}", fg="red")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@player.command("add")
|
|
31
|
+
@click.option(
|
|
32
|
+
"-p",
|
|
33
|
+
"--player",
|
|
34
|
+
"players",
|
|
35
|
+
multiple=True,
|
|
36
|
+
required=True,
|
|
37
|
+
help="Player to add in 'Gamertag:XUID' format. Use multiple times for multiple players.",
|
|
38
|
+
)
|
|
39
|
+
@click.pass_context
|
|
40
|
+
async def add_players(ctx, players):
|
|
41
|
+
"""Manually adds or updates player entries in the central player database."""
|
|
42
|
+
client = ctx.obj.get("client")
|
|
43
|
+
if not client:
|
|
44
|
+
click.secho("You are not logged in.", fg="red")
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
from bsm_api_client.models import AddPlayersPayload
|
|
49
|
+
|
|
50
|
+
player_list = list(players)
|
|
51
|
+
click.echo(f"Adding/updating {len(player_list)} player(s) in the database...")
|
|
52
|
+
payload = AddPlayersPayload(players=player_list)
|
|
53
|
+
response = await client.async_add_players(payload)
|
|
54
|
+
if response.status == "success":
|
|
55
|
+
click.secho("Players added/updated successfully.", fg="green")
|
|
56
|
+
else:
|
|
57
|
+
click.secho(f"Failed to add players: {response.message}", fg="red")
|
|
58
|
+
except Exception as e:
|
|
59
|
+
click.secho(f"An error occurred while adding players: {e}", fg="red")
|