mcserver-cli 4.3.0__tar.gz → 4.4.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.
Files changed (81) hide show
  1. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/CHANGELOG.md +15 -1
  2. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/NOTES.txt +2 -1
  3. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/PKG-INFO +14 -7
  4. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/README.md +13 -6
  5. mcserver_cli-4.4.0/src/mcserver/__init__.py +1 -0
  6. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/cli.py +16 -2
  7. mcserver_cli-4.4.0/src/mcserver/commands/op_list.py +35 -0
  8. mcserver_cli-4.4.0/src/mcserver/commands/op_revoke.py +43 -0
  9. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/commands/whitelist_list.py +11 -11
  10. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/commands/whitelist_remove.py +6 -4
  11. mcserver_cli-4.4.0/src/mcserver/server_files/ops.py +114 -0
  12. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/server_files/paths.py +1 -0
  13. mcserver_cli-4.4.0/src/mcserver/server_files/whitelist.py +102 -0
  14. mcserver_cli-4.3.0/src/mcserver/__init__.py +0 -1
  15. mcserver_cli-4.3.0/src/mcserver/commands/op_list.py +0 -32
  16. mcserver_cli-4.3.0/src/mcserver/commands/op_revoke.py +0 -9
  17. mcserver_cli-4.3.0/src/mcserver/server_files/ops.py +0 -143
  18. mcserver_cli-4.3.0/src/mcserver/server_files/whitelist.py +0 -138
  19. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/.gitignore +0 -0
  20. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/CONTRIBUTING.md +0 -0
  21. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/LICENSE +0 -0
  22. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/legacy/mcserver_v2.0.0.py +0 -0
  23. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/pyproject.toml +0 -0
  24. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/__main__.py +0 -0
  25. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/__main__.py +0 -0
  26. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/app.py +0 -0
  27. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/caching.py +0 -0
  28. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/commands/__init__.py +0 -0
  29. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/commands/add_projects.py +0 -0
  30. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/commands/import_setup.py +0 -0
  31. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/commands/initialize_server.py +0 -0
  32. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/commands/list_projects.py +0 -0
  33. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/commands/op_grant.py +0 -0
  34. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/commands/print_help.py +0 -0
  35. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/commands/remove_projects.py +0 -0
  36. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/commands/search_projects.py +0 -0
  37. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/commands/server_status.py +0 -0
  38. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/commands/show_projects.py +0 -0
  39. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/commands/start_server.py +0 -0
  40. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/commands/stop_server.py +0 -0
  41. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/commands/update_projects.py +0 -0
  42. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/commands/whitelist_add.py +0 -0
  43. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/config.py +0 -0
  44. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/constants.py +0 -0
  45. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/fabricmc/meta.py +0 -0
  46. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/legacy_fabric/meta.py +0 -0
  47. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/metadata.py +0 -0
  48. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/minecraft/api.py +0 -0
  49. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/minecraft/player_identity.py +0 -0
  50. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/modrinth/apis/__init__.py +0 -0
  51. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/modrinth/apis/get_project_informations.py +0 -0
  52. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/modrinth/apis/get_project_versions.py +0 -0
  53. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/modrinth/apis/search_projects.py +0 -0
  54. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/modrinth/constants.py +0 -0
  55. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/modrinth/modpack.py +0 -0
  56. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/modrinth/paths.py +0 -0
  57. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/modrinth/project_records.py +0 -0
  58. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/modrinth/projects.py +0 -0
  59. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/modrinth/shared.py +0 -0
  60. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/modrinth/slug_index.py +0 -0
  61. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/modrinth/typings/__init__.py +0 -0
  62. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/modrinth/typings/project_information.py +0 -0
  63. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/modrinth/typings/project_version.py +0 -0
  64. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/modrinth/typings/search_projects.py +0 -0
  65. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/modrinth/typings/shared.py +0 -0
  66. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/mojang/eula.py +0 -0
  67. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/mojang/manifest.py +0 -0
  68. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/mysterious.py +0 -0
  69. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/networking.py +0 -0
  70. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/papermc/api.py +0 -0
  71. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/paths.py +0 -0
  72. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/purpurmc/api.py +0 -0
  73. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/quiltmc/meta.py +0 -0
  74. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/resolver.py +0 -0
  75. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/server_files/banned_ips.py +0 -0
  76. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/server_files/banned_players.py +0 -0
  77. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/server_files/properties.py +0 -0
  78. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/server_files/usercaches.py +0 -0
  79. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/shared.py +0 -0
  80. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/src/mcserver/state.py +0 -0
  81. {mcserver_cli-4.3.0 → mcserver_cli-4.4.0}/zip_app.py +0 -0
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+
9
+ ## [4.4.0] - 2026-11-04
10
+
11
+ ### Added
12
+
13
+ - `op remove` to remove players based on UUIDs or names
14
+ - Player count to `whitelist list`
15
+
16
+ ### Fixes
17
+
18
+ - Wrong description for `whitelist remove`
19
+ - Fixing the wrong singular text in whitelist when no whitelisted player
20
+
8
21
  ## [4.3.0] - 2026-11-02
9
22
 
10
23
  ### Added
@@ -234,7 +247,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
234
247
  - Better mod conflict error
235
248
  - Faster mod resolving
236
249
 
237
- [unreleased]: https://github.com/Othecat35/MCServer/compare/v4.3.0...HEAD
250
+ [unreleased]: https://github.com/Othecat35/MCServer/compare/v4.4.0...HEAD
251
+ [4.4.0]: https://github.com/Othecat35/MCServer/compare/v4.3.0...v4.4.0
238
252
  [4.3.0]: https://github.com/Othecat35/MCServer/compare/v4.2.0...v4.3.0
239
253
  [4.2.0]: https://github.com/Othecat35/MCServer/compare/v4.1.0...v4.2.0
240
254
  [4.1.0]: https://github.com/Othecat35/MCServer/compare/v4.0.0...v4.1.0
@@ -390,4 +390,5 @@ it can be the default config of all your new Minecraft server derived from
390
390
  and yea
391
391
 
392
392
 
393
- 2026-11-01, let's start not using AI?
393
+ Ah yes... rcon, and also swrver management protocol.
394
+ Don't forget to add a warning fof whitelist remove if the server is running to run whitelist remove
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: mcserver-cli
3
- Version: 4.3.0
3
+ Version: 4.4.0
4
4
  Summary: A CLI tool to manage Minecraft: Java Edition servers.
5
5
  Project-URL: Homepage, https://github.com/Othecat35/MCServer
6
6
  Project-URL: Repository, https://github.com/Othecat35/MCServer.git
@@ -25,8 +25,7 @@ Supported platforms:
25
25
  - JRE (depends on Minecraft version)
26
26
 
27
27
  ## How to install
28
- - Using pip:
29
- 1. Run `pip install mcserver-cli`
28
+ - Using pip: Run `pip install mcserver-cli`
30
29
 
31
30
  - System-wide (Requires root):
32
31
  1. Download the `mcserver` script file from GitHub releases
@@ -44,28 +43,36 @@ Supported platforms:
44
43
  ## Usage
45
44
  - Initialize a server: `mcserver init [options]`
46
45
  - List operator players: `mcserver op list`
46
+ - Revoke operator players: `mcserver op revoke <players>`
47
+ - Search projects: `mcserver search [query] [options]`
47
48
  - Show information about projects: `mcserver show <projects>`
48
49
  - Start the server: `mcserver start`
49
- - Stop the server: `mcserver stop [--force-stop]`
50
+ - Stop the server: `mcserver stop`
50
51
  - List whitelisted players: `mcserver whitelist list`
52
+ - Remove whitelisted players: `mcserver whitelist remove <players>`
51
53
 
52
- ### Example
54
+ ### Examples
53
55
  - Create and start a Fabric 1.20.1 server on current directory
54
56
  ```bash
55
57
  mcserver init --game-version=1.20.1 --loader=fabric
56
58
  mcserver start
57
59
  ```
58
60
 
61
+ - Search top 50 Fabric mods for Minecraft 1.20.1 in modrinth
62
+ ```bash
63
+ mcserver search --loader=fabric --game-version=1.20.1 --limit=50
64
+ ```
65
+
59
66
  - List all operator and whitelisted players on current directory
60
67
  ```bash
61
68
  mcserver op list
62
69
  mcserver whitelist list
63
70
  ```
64
71
 
65
- - Stop (sigterm) a server on current directory
72
+ - Stop a server on current directory with sigterm
66
73
  ```bash
67
74
  mcserver stop
68
75
  ```
69
76
 
70
77
  ## Note
71
- This is a **Personal Project**, things may be unstable.
78
+ This is a **Personal Project** by Othecat35, so don't expect much from me :>
@@ -12,8 +12,7 @@ Supported platforms:
12
12
  - JRE (depends on Minecraft version)
13
13
 
14
14
  ## How to install
15
- - Using pip:
16
- 1. Run `pip install mcserver-cli`
15
+ - Using pip: Run `pip install mcserver-cli`
17
16
 
18
17
  - System-wide (Requires root):
19
18
  1. Download the `mcserver` script file from GitHub releases
@@ -31,28 +30,36 @@ Supported platforms:
31
30
  ## Usage
32
31
  - Initialize a server: `mcserver init [options]`
33
32
  - List operator players: `mcserver op list`
33
+ - Revoke operator players: `mcserver op revoke <players>`
34
+ - Search projects: `mcserver search [query] [options]`
34
35
  - Show information about projects: `mcserver show <projects>`
35
36
  - Start the server: `mcserver start`
36
- - Stop the server: `mcserver stop [--force-stop]`
37
+ - Stop the server: `mcserver stop`
37
38
  - List whitelisted players: `mcserver whitelist list`
39
+ - Remove whitelisted players: `mcserver whitelist remove <players>`
38
40
 
39
- ### Example
41
+ ### Examples
40
42
  - Create and start a Fabric 1.20.1 server on current directory
41
43
  ```bash
42
44
  mcserver init --game-version=1.20.1 --loader=fabric
43
45
  mcserver start
44
46
  ```
45
47
 
48
+ - Search top 50 Fabric mods for Minecraft 1.20.1 in modrinth
49
+ ```bash
50
+ mcserver search --loader=fabric --game-version=1.20.1 --limit=50
51
+ ```
52
+
46
53
  - List all operator and whitelisted players on current directory
47
54
  ```bash
48
55
  mcserver op list
49
56
  mcserver whitelist list
50
57
  ```
51
58
 
52
- - Stop (sigterm) a server on current directory
59
+ - Stop a server on current directory with sigterm
53
60
  ```bash
54
61
  mcserver stop
55
62
  ```
56
63
 
57
64
  ## Note
58
- This is a **Personal Project**, things may be unstable.
65
+ This is a **Personal Project** by Othecat35, so don't expect much from me :>
@@ -0,0 +1 @@
1
+ __version__ = "4.4.0"
@@ -224,6 +224,18 @@ def main() -> int:
224
224
 
225
225
  op_list_command.set_defaults(command_name="op_list")
226
226
 
227
+ # 'op revoke'
228
+ op_revoke_command = op_subcommands.add_parser(
229
+ "revoke",
230
+ help="Revoke operator players permission",
231
+ description="Revoke operator players permission.",
232
+ )
233
+
234
+ op_revoke_command.set_defaults(command_name="op_revoke")
235
+ op_revoke_command.add_argument(
236
+ "players", nargs="+", type=str, help="List of player names or UUIDs"
237
+ )
238
+
227
239
  # 'search' command
228
240
  search_command = commands.add_parser(
229
241
  "search",
@@ -336,11 +348,13 @@ def main() -> int:
336
348
  whitelist_remove_command = whitelist_subcommands.add_parser(
337
349
  "remove",
338
350
  help="Remove players from the whitelist",
339
- description="List all whitelisted players.",
351
+ description="Remove players from the whitelist.",
340
352
  )
341
353
 
342
354
  whitelist_remove_command.set_defaults(command_name="whitelist_remove")
343
- whitelist_remove_command.add_argument("players", nargs="+", type=str, help="List of player names or UUIDs")
355
+ whitelist_remove_command.add_argument(
356
+ "players", nargs="+", type=str, help="List of player names or UUIDs"
357
+ )
344
358
 
345
359
  args = parser.parse_args()
346
360
  return args.func(args)
@@ -0,0 +1,35 @@
1
+ import logging
2
+ from argparse import Namespace
3
+
4
+ from ..server_files import ops
5
+ from ..shared import pluralize
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ def run(args: Namespace) -> int:
11
+ ops_file = ops.ops_file_path
12
+ if not ops_file.exists():
13
+ logger.error(f"Operator file '{ops.ops_file_path}' not found.")
14
+ return 1
15
+
16
+ operator_players = ops.list_players()
17
+
18
+ player_count = len(operator_players)
19
+ if player_count == 0:
20
+ logger.info("There's no operator player.")
21
+ return 0
22
+
23
+ player_list: list[str] = []
24
+ for player in operator_players:
25
+ player_list.append(
26
+ f"{player['player_name']} ({player['player_uuid']}) Level: {player['permission_level']}"
27
+ )
28
+
29
+ logger.info(
30
+ f"There {pluralize('is', player_count)} {player_count} {pluralize('operator', player_count)}:"
31
+ )
32
+
33
+ player_list.sort()
34
+ print("\n".join(player_list))
35
+ return 0
@@ -0,0 +1,43 @@
1
+ import logging
2
+ from argparse import Namespace
3
+ from uuid import UUID
4
+
5
+ from ..server_files import ops
6
+ from ..shared import pluralize
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ def run(args: Namespace) -> int:
12
+ player_identifiers: list[str] = args.players
13
+
14
+ player_uuids: list[UUID] = []
15
+ player_names: list[str] = []
16
+ for player in player_identifiers:
17
+ if len(player) > 16:
18
+ logger.debug(f"{player} is probably an UUID")
19
+ player_uuids.append(UUID(player))
20
+ else:
21
+ logger.debug(f"{player} is probably a name")
22
+ player_names.append(player)
23
+
24
+ removed_players = ops.remove_players(player_uuids, player_names)
25
+
26
+ player_count: int = len(removed_players)
27
+ if player_count == 0:
28
+ logger.info("No matching player found.")
29
+ return 0
30
+
31
+ player_list: list[str] = []
32
+ for player in removed_players:
33
+ player_list.append(
34
+ f"{player['player_name']} ({player['player_uuid']}) Level: {player['permission_level']}"
35
+ )
36
+
37
+ logger.info(
38
+ f"There {pluralize('is', player_count)} {player_count} operator {pluralize('player', player_count)} that {pluralize('is', player_count)} removed:"
39
+ )
40
+
41
+ player_list.sort()
42
+ print("\n".join(player_list))
43
+ return 0
@@ -1,5 +1,5 @@
1
- import argparse
2
1
  import logging
2
+ from argparse import Namespace
3
3
 
4
4
  from ..server_files import whitelist
5
5
  from ..shared import pluralize
@@ -7,26 +7,26 @@ from ..shared import pluralize
7
7
  logger = logging.getLogger(__name__)
8
8
 
9
9
 
10
- def run(args: argparse.Namespace) -> int:
10
+ def run(args: Namespace) -> int:
11
11
  whitelist_file = whitelist.whitelist_file_path
12
12
  if not whitelist_file.exists():
13
- logger.error(f"Whitelist file ({whitelist_file}) does not exist!")
13
+ logger.error(f"Whitelist file ({whitelist_file}) not found!")
14
14
  return 1
15
15
 
16
16
  whitelisted_players = whitelist.list_players()
17
17
 
18
+ player_count: int = len(whitelisted_players)
19
+ if player_count == 0:
20
+ logger.info("There are no whitelisted player.")
21
+ return 0
22
+
18
23
  player_list: list[str] = []
19
24
  for player in whitelisted_players:
20
25
  player_list.append(f"{player['player_name']} ({player['player_uuid']})")
21
26
 
22
- player_count: int = len(player_list)
23
- if player_count == 0:
24
- logger.info("There are no whitelisted players.")
25
- return 0
26
- else:
27
- logger.info(
28
- f"There {pluralize('is', player_count)} whitelisted {pluralize('player', player_count)}:"
29
- )
27
+ logger.info(
28
+ f"There {pluralize('is', player_count)} {player_count} whitelisted {pluralize('player', player_count)}:"
29
+ )
30
30
 
31
31
  player_list.sort()
32
32
  print("\n".join(player_list))
@@ -1,20 +1,22 @@
1
- import argparse
2
1
  import logging
2
+ from argparse import Namespace
3
+ from uuid import UUID
3
4
 
4
5
  from ..server_files import whitelist
5
6
  from ..shared import pluralize
6
7
 
7
8
  logger = logging.getLogger(__name__)
8
9
 
9
- def run(args: argparse.Namespace) -> int:
10
+
11
+ def run(args: Namespace) -> int:
10
12
  player_identifiers: list[str] = args.players
11
13
 
12
- player_uuids: list[str] = []
14
+ player_uuids: list[UUID] = []
13
15
  player_names: list[str] = []
14
16
  for player in player_identifiers:
15
17
  if len(player) > 16:
16
18
  logger.debug(f"{player} is probably an UUID")
17
- player_uuids.append(player)
19
+ player_uuids.append(UUID(player))
18
20
  else:
19
21
  logger.debug(f"{player} is probably a name")
20
22
  player_names.append(player)
@@ -0,0 +1,114 @@
1
+ import json
2
+ import logging
3
+ from collections.abc import Sequence
4
+ from typing import TypedDict
5
+ from uuid import UUID
6
+
7
+ from ..minecraft.player_identity import normalize_player_uuid
8
+ from .paths import ops_file_path
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class OperatorPlayer(TypedDict):
14
+ player_uuid: UUID
15
+ player_name: str
16
+ permission_level: int
17
+ bypasses_player_limit: bool
18
+
19
+
20
+ class OperatorFilePlayer(TypedDict):
21
+ uuid: str
22
+ name: str
23
+ level: int
24
+ bypassesPlayerLimit: bool
25
+
26
+
27
+ def remove_players(
28
+ player_uuids: Sequence[UUID | str] | UUID | str | None = None,
29
+ player_names: list[str] | str | None = None,
30
+ ) -> list[OperatorPlayer]:
31
+ """Remove operator players that matches either by UUIDs or names
32
+
33
+ Args:
34
+ player_uuids: List of player UUIDs to be removed
35
+ player_names: List of player names to be removed
36
+
37
+ Raises:
38
+ ValueError: Must provide at least a player UUID or player name
39
+
40
+ Returns:
41
+ List of operator players that are removed"""
42
+ if player_uuids is None:
43
+ player_uuids = []
44
+ elif isinstance(player_uuids, (UUID, str)):
45
+ player_uuids = [player_uuids]
46
+
47
+ if player_names is None:
48
+ player_names = []
49
+ elif isinstance(player_names, str):
50
+ player_names = [player_names]
51
+
52
+ if not player_uuids and not player_names:
53
+ raise ValueError("Must provide at least a player UUID or player name")
54
+
55
+ names: set[str] = set(player_names)
56
+
57
+ uuids: set[UUID] = set()
58
+ for player_uuid in player_uuids:
59
+ uuids.add(normalize_player_uuid(player_uuid))
60
+
61
+ removed_players: list[OperatorPlayer] = []
62
+ with open(ops_file_path, mode="r+") as ops_file:
63
+ ops_data: list[OperatorFilePlayer] = json.load(ops_file)
64
+
65
+ new_ops_data: list[OperatorFilePlayer] = []
66
+ for operator_file_player in ops_data:
67
+ if (
68
+ UUID(operator_file_player["uuid"]) in uuids
69
+ or operator_file_player["name"] in names
70
+ ):
71
+ logger.debug(
72
+ f"Operator player {operator_file_player['name']} ({operator_file_player['uuid']}) is removed"
73
+ )
74
+ operator_player: OperatorPlayer = {
75
+ "player_uuid": UUID(operator_file_player["uuid"]),
76
+ "player_name": operator_file_player["name"],
77
+ "permission_level": operator_file_player["level"],
78
+ "bypasses_player_limit": operator_file_player[
79
+ "bypassesPlayerLimit"
80
+ ],
81
+ }
82
+
83
+ removed_players.append(operator_player)
84
+ continue
85
+
86
+ new_ops_data.append(operator_file_player)
87
+
88
+ ops_file.seek(0)
89
+ json.dump(new_ops_data, ops_file, indent=2)
90
+ ops_file.truncate()
91
+
92
+ return removed_players
93
+
94
+
95
+ def list_players() -> list[OperatorPlayer]:
96
+ """List all operator players
97
+
98
+ Returns:
99
+ List of operator players"""
100
+ logger.debug(f"Reading {ops_file_path}")
101
+ ops_json: list[OperatorFilePlayer] = json.loads(ops_file_path.read_text())
102
+
103
+ operator_players: list[OperatorPlayer] = []
104
+ for player in ops_json:
105
+ operator_player: OperatorPlayer = {
106
+ "player_uuid": UUID(player["uuid"]),
107
+ "player_name": player["name"],
108
+ "permission_level": player["level"],
109
+ "bypasses_player_limit": player["bypassesPlayerLimit"],
110
+ }
111
+
112
+ operator_players.append(operator_player)
113
+
114
+ return operator_players
@@ -1,3 +1,4 @@
1
1
  from ..paths import current_dir
2
2
 
3
+ ops_file_path = current_dir / "ops.json"
3
4
  whitelist_file_path = current_dir / "whitelist.json"
@@ -0,0 +1,102 @@
1
+ import json
2
+ import logging
3
+ from collections.abc import Sequence
4
+ from typing import TypedDict
5
+ from uuid import UUID
6
+
7
+ from ..minecraft.player_identity import normalize_player_uuid
8
+ from .paths import whitelist_file_path
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class WhitelistedPlayer(TypedDict):
14
+ player_uuid: UUID
15
+ player_name: str
16
+
17
+
18
+ class WhitelistedFilePlayer(TypedDict):
19
+ uuid: str
20
+ name: str
21
+
22
+
23
+ def remove_players(
24
+ player_uuids: Sequence[UUID | str] | UUID | str | None = None,
25
+ player_names: list[str] | str | None = None,
26
+ ) -> list[WhitelistedPlayer]:
27
+ """Remove players from the whitelist that matches either by UUIDs or names
28
+
29
+ Args:
30
+ player_uuids: List of player UUIDs to be removed
31
+ player_names: List of player names to be removed
32
+
33
+ Raises:
34
+ ValueError: Must provide at least a player UUID or player name
35
+
36
+ Returns:
37
+ List of whitelisted players that are removed"""
38
+ if player_uuids is None:
39
+ player_uuids = []
40
+ elif isinstance(player_uuids, (UUID, str)):
41
+ player_uuids = [player_uuids]
42
+
43
+ if player_names is None:
44
+ player_names = []
45
+ elif isinstance(player_names, str):
46
+ player_names = [player_names]
47
+
48
+ if not player_uuids and not player_names:
49
+ raise ValueError("Must provide at least a player UUID or player name")
50
+
51
+ names: set[str] = set(player_names)
52
+
53
+ uuids: set[UUID] = set()
54
+ for player_uuid in player_uuids:
55
+ uuids.add(normalize_player_uuid(player_uuid))
56
+
57
+ removed_players: list[WhitelistedPlayer] = []
58
+ with open(whitelist_file_path, mode="r+") as whitelist_file:
59
+ whitelist_data: list[WhitelistedFilePlayer] = json.load(whitelist_file)
60
+
61
+ new_whitelist_data: list[WhitelistedFilePlayer] = []
62
+ for whitelisted_file_player in whitelist_data:
63
+ if (
64
+ UUID(whitelisted_file_player["uuid"]) in uuids
65
+ or whitelisted_file_player["name"] in names
66
+ ):
67
+ logger.debug(
68
+ f"Whitelisted player {whitelisted_file_player['name']} ({whitelisted_file_player['uuid']}) is removed"
69
+ )
70
+ whitelisted_player: WhitelistedPlayer = {
71
+ "player_uuid": UUID(whitelisted_file_player["uuid"]),
72
+ "player_name": whitelisted_file_player["name"],
73
+ }
74
+
75
+ removed_players.append(whitelisted_player)
76
+ continue
77
+
78
+ new_whitelist_data.append(whitelisted_file_player)
79
+
80
+ whitelist_file.seek(0)
81
+ json.dump(new_whitelist_data, whitelist_file, indent=2)
82
+ whitelist_file.truncate()
83
+
84
+ return removed_players
85
+
86
+
87
+ def list_players() -> list[WhitelistedPlayer]:
88
+ logger.debug(f"Reading whitelist file {whitelist_file_path}")
89
+ whitelist_data: list[WhitelistedFilePlayer] = json.loads(
90
+ whitelist_file_path.read_text()
91
+ )
92
+
93
+ whitelisted_players: list[WhitelistedPlayer] = []
94
+ for player in whitelist_data:
95
+ whitelisted_player: WhitelistedPlayer = {
96
+ "player_uuid": UUID(player["uuid"]),
97
+ "player_name": player["name"],
98
+ }
99
+
100
+ whitelisted_players.append(whitelisted_player)
101
+
102
+ return whitelisted_players
@@ -1 +0,0 @@
1
- __version__ = "4.3.0"
@@ -1,32 +0,0 @@
1
- from argparse import Namespace
2
-
3
-
4
- def run(args: Namespace) -> int:
5
- import logging as log
6
-
7
- from ..server_files import ops
8
- from ..shared import pluralize
9
-
10
- if not ops.ops_file.exists():
11
- log.error(f"File '{ops.ops_file}' not found.")
12
- return 1
13
-
14
- operator_players = ops.list_players()
15
-
16
- players: list[str] = []
17
- for player in operator_players:
18
- players.append(
19
- f"{player['player_name']} ({player['player_uuid']}) Level: {player['permission_level']}"
20
- )
21
-
22
- players.sort()
23
- sorted_players = "\n".join(players)
24
-
25
- player_count = len(operator_players)
26
- if player_count == 0:
27
- log.info("There's no operator player.")
28
- return 0
29
-
30
- log.info(f"All {player_count} operator {pluralize('player', player_count)}:")
31
- print(sorted_players)
32
- return 0
@@ -1,9 +0,0 @@
1
- from argparse import Namespace
2
-
3
-
4
- def run(args: Namespace) -> int:
5
- players: list[str] = args.players
6
- import logging as log
7
-
8
- log.error("Command is not implemented yet.")
9
- return 1
@@ -1,143 +0,0 @@
1
- # Modules
2
- # Standard
3
- import json
4
- from pathlib import Path
5
- from typing import Literal, TypedDict
6
- from uuid import UUID
7
-
8
- # MCServer
9
- from mcserver.minecraft.player_identity import (
10
- PlayerID,
11
- PlayerUUID,
12
- normalize_player_uuid,
13
- )
14
-
15
- # Paths
16
- ops_file = Path("ops.json")
17
-
18
-
19
- # TypedDicts
20
- class OperatorEntry(TypedDict):
21
- uuid: str
22
- name: str
23
- level: Literal[0, 1, 2, 3, 4]
24
- bypassesPlayerLimit: bool
25
-
26
-
27
- class OperatorPlayer(TypedDict):
28
- player_uuid: PlayerUUID
29
- player_name: str
30
- permission_level: Literal[0, 1, 2, 3, 4]
31
- bypasses_player_limit: bool
32
-
33
-
34
- # Functions
35
- # Add Players
36
- def add_player(
37
- player_uuid: PlayerUUID,
38
- player_name: str,
39
- permission_level: Literal[0, 1, 2, 3, 4],
40
- bypasses_player_limit: bool = False,
41
- ) -> None:
42
- operator_player: OperatorPlayer = {
43
- "player_uuid": player_uuid,
44
- "player_name": player_name,
45
- "permission_level": permission_level,
46
- "bypasses_player_limit": bypasses_player_limit,
47
- }
48
-
49
- add_players(operator_player)
50
-
51
-
52
- def add_players(operator_players: list[OperatorPlayer] | OperatorPlayer) -> None:
53
- if not isinstance(operator_players, list):
54
- operator_players = [operator_players]
55
-
56
- with open(ops_file, mode="r+") as file:
57
- ops_data: list[OperatorEntry] = json.load(file)
58
-
59
- for player in operator_players:
60
- permission_level = player["permission_level"]
61
- if permission_level < 0 or permission_level > 4:
62
- raise ValueError("Permission level must be between 0 and 4.")
63
-
64
- operator_entry: OperatorEntry = {
65
- "uuid": str(normalize_player_uuid(player["player_uuid"])),
66
- "name": player["player_name"],
67
- "level": permission_level,
68
- "bypassesPlayerLimit": player["bypasses_player_limit"],
69
- }
70
-
71
- ops_data.append(operator_entry)
72
-
73
- file.seek(0)
74
- json.dump(ops_data, file, indent=2)
75
- file.truncate()
76
-
77
-
78
- # Remove Players
79
- def remove_player(
80
- player_uuid: PlayerUUID | None = None, player_name: str | None = None
81
- ) -> None:
82
- if player_uuid is None and player_name is None:
83
- raise ValueError(
84
- "At least one of 'player_uuid' or 'player_name' must be provided"
85
- )
86
-
87
- player_id: PlayerID = {}
88
- if player_uuid is not None:
89
- player_id["player_uuid"] = normalize_player_uuid(player_uuid)
90
-
91
- if player_name is not None:
92
- player_id["player_name"] = player_name
93
-
94
- remove_players(player_id)
95
-
96
-
97
- def remove_players(player_ids: list[PlayerID] | PlayerID) -> None:
98
- if not isinstance(player_ids, list):
99
- player_ids = [player_ids]
100
-
101
- player_uuids: set[PlayerUUID | None] = set()
102
- player_names: set[str | None] = set()
103
-
104
- for player_id in player_ids:
105
- if "player_uuid" in player_id:
106
- player_uuids.add(player_id["player_uuid"])
107
-
108
- if "player_name" in player_id:
109
- player_names.add(player_id["player_name"])
110
-
111
- with open(ops_file, mode="r+") as file:
112
- ops_data: list[OperatorEntry] = json.load(file)
113
-
114
- new_ops_data: list[OperatorEntry] = []
115
- for player in ops_data:
116
- if player["uuid"] in player_uuids or player["name"] in player_names:
117
- continue
118
-
119
- new_ops_data.append(player)
120
-
121
- file.seek(0)
122
- json.dump(new_ops_data, file, indent=2)
123
- file.truncate()
124
-
125
-
126
- # TODO: add 'update_player' and 'update_players', just use plain dict.update() because it has no depth
127
-
128
-
129
- # List Players
130
- def list_players() -> list[OperatorPlayer]:
131
- ops_data: list[OperatorEntry] = json.loads(ops_file.read_text())
132
- operator_players: list[OperatorPlayer] = []
133
- for entry in ops_data:
134
- operator_player: OperatorPlayer = {
135
- "player_uuid": UUID(entry["uuid"]),
136
- "player_name": entry["name"],
137
- "permission_level": entry["level"],
138
- "bypasses_player_limit": entry["bypassesPlayerLimit"],
139
- }
140
-
141
- operator_players.append(operator_player)
142
-
143
- return operator_players
@@ -1,138 +0,0 @@
1
- import json
2
- import logging
3
- from typing import TypedDict
4
-
5
- from ..minecraft.player_identity import PlayerUUID
6
- from .paths import whitelist_file_path
7
-
8
- logger = logging.getLogger(__name__)
9
-
10
-
11
- class WhitelistedPlayer(TypedDict):
12
- player_uuid: PlayerUUID
13
- player_name: str
14
-
15
-
16
- class WhitelistedFilePlayer(TypedDict):
17
- uuid: str
18
- name: str
19
-
20
- def remove_players(player_uuids: list[str] | str | None = None, player_names: list[str] | str | None = None) -> list[WhitelistedPlayer]:
21
- """Remove players from the whitelist that matches either the UUIDs or names
22
-
23
- Args:
24
- player_uuids: List of player UUIDs to be removed
25
- player_names: List of player names to be removed
26
-
27
- Raises:
28
- ValueError: Must provide at least a player UUID or player name
29
-
30
- Returns:
31
- List of whitelisted players that are removed"""
32
- if player_uuids is None:
33
- player_uuids = []
34
-
35
- if player_names is None:
36
- player_names = []
37
-
38
- if isinstance(player_uuids, str):
39
- player_uuids = [player_uuids]
40
-
41
- if isinstance(player_names, str):
42
- player_names = [player_names]
43
-
44
- if not player_uuids and not player_names:
45
- raise ValueError("Must provide at least a player UUID or player name")
46
-
47
- uuids: set[str] = set(player_uuids)
48
- names: set[str] = set(player_names)
49
-
50
- removed_players: list[WhitelistedPlayer] = []
51
- with open(whitelist_file_path, mode="r+") as whitelist_file:
52
- whitelist_data: list[WhitelistedFilePlayer] = json.load(whitelist_file)
53
-
54
- new_whitelist_data: list[WhitelistedFilePlayer] = []
55
- for whitelisted_file_player in whitelist_data:
56
- if whitelisted_file_player["uuid"] in uuids or whitelisted_file_player["name"] in names:
57
- logger.debug(f"Whitelisted player {whitelisted_file_player['name']} ({whitelisted_file_player['uuid']}) is removed")
58
- whitelisted_player: WhitelistedPlayer = {
59
- "player_uuid": whitelisted_file_player["uuid"],
60
- "player_name": whitelisted_file_player["name"]
61
- }
62
-
63
- removed_players.append(whitelisted_player)
64
- continue
65
-
66
- new_whitelist_data.append(whitelisted_file_player)
67
-
68
- whitelist_file.seek(0)
69
- json.dump(new_whitelist_data, whitelist_file, indent=2)
70
- whitelist_file.truncate()
71
-
72
- return removed_players
73
-
74
-
75
-
76
- # def remove_players(player_ids: list[PlayerID] | PlayerID) -> None:
77
- # """Remove player entries matching the exact UUIDs or names
78
-
79
- # Args:
80
- # player_ids: Player UUIDs and/or names object
81
-
82
- # Returns:
83
- # None: Returns nothing
84
-
85
- # Raises:
86
- # FileNotFoundError: The 'whitelist.json' file not found
87
- # """
88
- # if not isinstance(player_ids, list):
89
- # player_ids = [player_ids]
90
-
91
- # player_uuids: set[PlayerUUID | None] = set()
92
- # player_names: set[str | None] = set()
93
- # for player_id in player_ids:
94
- # if "player_uuid" in player_id:
95
- # player_uuids.add(player_id["player_uuid"])
96
-
97
- # if "player_name" in player_id:
98
- # player_names.add(player_id["player_name"])
99
-
100
- # if not whitelist_file.exists():
101
- # raise FileNotFoundError(f"File '{whitelist_file}' is not found.")
102
-
103
- # with open(whitelist_file, mode="r+") as file:
104
- # whitelist_data: list[WhitelistFileEntry] = json.load(file)
105
- # new_whitelist_data: list[WhitelistFileEntry] = []
106
- # for player in whitelist_data:
107
- # player_uuid = player["uuid"]
108
- # player_name = player["name"]
109
-
110
- # if player_uuid in player_uuids or player_name in player_names:
111
- # log.debug(
112
- # f"Removed player '{player_name}' ({player_uuid}) from the whitelisted player list"
113
- # )
114
- # continue
115
-
116
- # new_whitelist_data.append(player)
117
-
118
- # file.seek(0)
119
- # json.dump(new_whitelist_data, file, indent=2)
120
- # file.truncate()
121
-
122
-
123
-
124
- def list_players() -> list[WhitelistedPlayer]:
125
- logger.debug(f"Reading whitelist file {whitelist_file_path}")
126
- whitelist_json = whitelist_file_path.read_text()
127
- whitelist_data: list[WhitelistedFilePlayer] = json.loads(whitelist_json)
128
-
129
- whitelisted_players: list[WhitelistedPlayer] = []
130
- for player in whitelist_data:
131
- whitelisted_player: WhitelistedPlayer = {
132
- "player_uuid": player["uuid"],
133
- "player_name": player["name"],
134
- }
135
-
136
- whitelisted_players.append(whitelisted_player)
137
-
138
- return whitelisted_players
File without changes
File without changes
File without changes