mcserver-cli 4.4.0__tar.gz → 4.5.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 (82) hide show
  1. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/CHANGELOG.md +15 -3
  2. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/NOTES.txt +3 -1
  3. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/PKG-INFO +20 -5
  4. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/README.md +19 -4
  5. mcserver_cli-4.5.0/src/mcserver/__init__.py +1 -0
  6. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/cli.py +54 -2
  7. mcserver_cli-4.5.0/src/mcserver/commands/op_grant.py +103 -0
  8. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/commands/op_revoke.py +3 -12
  9. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/commands/search_projects.py +8 -4
  10. mcserver_cli-4.5.0/src/mcserver/commands/whitelist_add.py +81 -0
  11. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/commands/whitelist_remove.py +3 -12
  12. mcserver_cli-4.5.0/src/mcserver/minecraft/api.py +112 -0
  13. mcserver_cli-4.5.0/src/mcserver/minecraft/player_identity.py +41 -0
  14. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/modrinth/apis/get_project_informations.py +1 -1
  15. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/modrinth/apis/search_projects.py +1 -2
  16. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/modrinth/slug_index.py +12 -18
  17. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/server_files/ops.py +82 -26
  18. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/server_files/paths.py +1 -0
  19. mcserver_cli-4.5.0/src/mcserver/server_files/server_properties.py +42 -0
  20. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/server_files/whitelist.py +74 -26
  21. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/shared.py +1 -1
  22. mcserver_cli-4.4.0/src/mcserver/__init__.py +0 -1
  23. mcserver_cli-4.4.0/src/mcserver/commands/op_grant.py +0 -9
  24. mcserver_cli-4.4.0/src/mcserver/commands/whitelist_add.py +0 -9
  25. mcserver_cli-4.4.0/src/mcserver/minecraft/api.py +0 -94
  26. mcserver_cli-4.4.0/src/mcserver/minecraft/player_identity.py +0 -21
  27. mcserver_cli-4.4.0/src/mcserver/modrinth/projects.py +0 -80
  28. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/.gitignore +0 -0
  29. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/CONTRIBUTING.md +0 -0
  30. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/LICENSE +0 -0
  31. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/legacy/mcserver_v2.0.0.py +0 -0
  32. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/pyproject.toml +0 -0
  33. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/__main__.py +0 -0
  34. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/__main__.py +0 -0
  35. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/app.py +0 -0
  36. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/caching.py +0 -0
  37. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/commands/__init__.py +0 -0
  38. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/commands/add_projects.py +0 -0
  39. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/commands/import_setup.py +0 -0
  40. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/commands/initialize_server.py +0 -0
  41. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/commands/list_projects.py +0 -0
  42. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/commands/op_list.py +0 -0
  43. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/commands/print_help.py +0 -0
  44. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/commands/remove_projects.py +0 -0
  45. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/commands/server_status.py +0 -0
  46. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/commands/show_projects.py +0 -0
  47. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/commands/start_server.py +0 -0
  48. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/commands/stop_server.py +0 -0
  49. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/commands/update_projects.py +0 -0
  50. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/commands/whitelist_list.py +0 -0
  51. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/config.py +0 -0
  52. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/constants.py +0 -0
  53. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/fabricmc/meta.py +0 -0
  54. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/legacy_fabric/meta.py +0 -0
  55. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/metadata.py +0 -0
  56. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/modrinth/apis/__init__.py +0 -0
  57. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/modrinth/apis/get_project_versions.py +0 -0
  58. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/modrinth/constants.py +0 -0
  59. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/modrinth/modpack.py +0 -0
  60. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/modrinth/paths.py +0 -0
  61. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/modrinth/project_records.py +0 -0
  62. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/modrinth/shared.py +0 -0
  63. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/modrinth/typings/__init__.py +0 -0
  64. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/modrinth/typings/project_information.py +0 -0
  65. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/modrinth/typings/project_version.py +0 -0
  66. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/modrinth/typings/search_projects.py +0 -0
  67. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/modrinth/typings/shared.py +0 -0
  68. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/mojang/eula.py +0 -0
  69. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/mojang/manifest.py +0 -0
  70. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/mysterious.py +0 -0
  71. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/networking.py +0 -0
  72. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/papermc/api.py +0 -0
  73. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/paths.py +0 -0
  74. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/purpurmc/api.py +0 -0
  75. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/quiltmc/meta.py +0 -0
  76. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/resolver.py +0 -0
  77. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/server_files/banned_ips.py +0 -0
  78. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/server_files/banned_players.py +0 -0
  79. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/server_files/properties.py +0 -0
  80. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/server_files/usercaches.py +0 -0
  81. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/src/mcserver/state.py +0 -0
  82. {mcserver_cli-4.4.0 → mcserver_cli-4.5.0}/zip_app.py +0 -0
@@ -5,12 +5,23 @@ 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
+ ## [Unreleased]
8
9
 
9
- ## [4.4.0] - 2026-11-04
10
+ ## [4.5.0] - 2026-11-04
10
11
 
11
12
  ### Added
12
13
 
13
- - `op remove` to remove players based on UUIDs or names
14
+ - `whitelist add` and `op add` command (limited to 10 players at once for online mode for name)
15
+
16
+ ### Changed
17
+
18
+ - `op remove` and `whitelist remove` to name is now case insensitive
19
+
20
+ ## [4.4.0] - 2026-11-03
21
+
22
+ ### Added
23
+
24
+ - `op revoke` to revoke operator players permission based on UUIDs or names
14
25
  - Player count to `whitelist list`
15
26
 
16
27
  ### Fixes
@@ -247,7 +258,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
247
258
  - Better mod conflict error
248
259
  - Faster mod resolving
249
260
 
250
- [unreleased]: https://github.com/Othecat35/MCServer/compare/v4.4.0...HEAD
261
+ [unreleased]: https://github.com/Othecat35/MCServer/compare/v4.5.0...HEAD
262
+ [4.4.0]: https://github.com/Othecat35/MCServer/compare/v4.4.0...v4.5.0
251
263
  [4.4.0]: https://github.com/Othecat35/MCServer/compare/v4.3.0...v4.4.0
252
264
  [4.3.0]: https://github.com/Othecat35/MCServer/compare/v4.2.0...v4.3.0
253
265
  [4.2.0]: https://github.com/Othecat35/MCServer/compare/v4.1.0...v4.2.0
@@ -391,4 +391,6 @@ and yea
391
391
 
392
392
 
393
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
394
+ Don't forget to add a warning fof whitelist remove if the server is running to run whitelist remover
395
+
396
+ about whitelist add or op grant, remember to make the internal function does remove and add together, then call it upsert according to deepseek
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: mcserver-cli
3
- Version: 4.4.0
3
+ Version: 4.5.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,7 +25,7 @@ Supported platforms:
25
25
  - JRE (depends on Minecraft version)
26
26
 
27
27
  ## How to install
28
- - Using pip: Run `pip install mcserver-cli`
28
+ - Using pip: Just run `pip install mcserver-cli`
29
29
 
30
30
  - System-wide (Requires root):
31
31
  1. Download the `mcserver` script file from GitHub releases
@@ -52,13 +52,13 @@ Supported platforms:
52
52
  - Remove whitelisted players: `mcserver whitelist remove <players>`
53
53
 
54
54
  ### Examples
55
- - Create and start a Fabric 1.20.1 server on current directory
55
+ - Create and start a Fabric 1.20.1 server in the current directory
56
56
  ```bash
57
57
  mcserver init --game-version=1.20.1 --loader=fabric
58
58
  mcserver start
59
59
  ```
60
60
 
61
- - Search top 50 Fabric mods for Minecraft 1.20.1 in modrinth
61
+ - Search top 50 Fabric mods for Minecraft 1.20.1 on modrinth
62
62
  ```bash
63
63
  mcserver search --loader=fabric --game-version=1.20.1 --limit=50
64
64
  ```
@@ -69,7 +69,22 @@ mcserver op list
69
69
  mcserver whitelist list
70
70
  ```
71
71
 
72
- - Stop a server on current directory with sigterm
72
+ - Adding a player to the whitelist
73
+ ```bash
74
+ mcserver whitelist add Notch
75
+ ```
76
+
77
+ - Granting Notch operator level 4 and can bypass player limit
78
+ ```bash
79
+ mcserver op grant Notch --permission-level=4 --bypasses-player-limit
80
+ ```
81
+
82
+ - Remove me from the whitelist
83
+ ```bash
84
+ mcserver whitelist remove Othecat35
85
+ ```
86
+
87
+ - Stop a running server on current directory with sigterm
73
88
  ```bash
74
89
  mcserver stop
75
90
  ```
@@ -12,7 +12,7 @@ Supported platforms:
12
12
  - JRE (depends on Minecraft version)
13
13
 
14
14
  ## How to install
15
- - Using pip: Run `pip install mcserver-cli`
15
+ - Using pip: Just run `pip install mcserver-cli`
16
16
 
17
17
  - System-wide (Requires root):
18
18
  1. Download the `mcserver` script file from GitHub releases
@@ -39,13 +39,13 @@ Supported platforms:
39
39
  - Remove whitelisted players: `mcserver whitelist remove <players>`
40
40
 
41
41
  ### Examples
42
- - Create and start a Fabric 1.20.1 server on current directory
42
+ - Create and start a Fabric 1.20.1 server in the current directory
43
43
  ```bash
44
44
  mcserver init --game-version=1.20.1 --loader=fabric
45
45
  mcserver start
46
46
  ```
47
47
 
48
- - Search top 50 Fabric mods for Minecraft 1.20.1 in modrinth
48
+ - Search top 50 Fabric mods for Minecraft 1.20.1 on modrinth
49
49
  ```bash
50
50
  mcserver search --loader=fabric --game-version=1.20.1 --limit=50
51
51
  ```
@@ -56,7 +56,22 @@ mcserver op list
56
56
  mcserver whitelist list
57
57
  ```
58
58
 
59
- - Stop a server on current directory with sigterm
59
+ - Adding a player to the whitelist
60
+ ```bash
61
+ mcserver whitelist add Notch
62
+ ```
63
+
64
+ - Granting Notch operator level 4 and can bypass player limit
65
+ ```bash
66
+ mcserver op grant Notch --permission-level=4 --bypasses-player-limit
67
+ ```
68
+
69
+ - Remove me from the whitelist
70
+ ```bash
71
+ mcserver whitelist remove Othecat35
72
+ ```
73
+
74
+ - Stop a running server on current directory with sigterm
60
75
  ```bash
61
76
  mcserver stop
62
77
  ```
@@ -0,0 +1 @@
1
+ __version__ = "4.5.0"
@@ -216,7 +216,40 @@ def main() -> int:
216
216
  op_command.set_defaults(parser=op_command)
217
217
  op_subcommands = op_command.add_subparsers(title="Subcommands")
218
218
 
219
- # op_grant and op_revoke is not going to be implemented in this version
219
+ # 'op grant'
220
+ op_grant_command = op_subcommands.add_parser(
221
+ "grant",
222
+ help="Grant player operator permission",
223
+ description="Add players to the whitelist.",
224
+ )
225
+
226
+ op_grant_command.set_defaults(command_name="op_grant")
227
+ op_grant_command.add_argument(
228
+ "players", nargs="+", type=str, help="List of player names or UUIDs"
229
+ )
230
+
231
+ op_grant_command.add_argument(
232
+ "--bypasses-player-limit",
233
+ action="store_true",
234
+ help="Player can bypass player limit",
235
+ )
236
+
237
+ op_grant_command.add_argument(
238
+ "--online-mode",
239
+ type=str,
240
+ choices=["true","false"],
241
+ metavar="Boolean",
242
+ help="The server's online mode (true/false)",
243
+ )
244
+
245
+ op_grant_command.add_argument(
246
+ "--permission-level",
247
+ type=int,
248
+ choices=[1, 2, 3, 4],
249
+ metavar="Level",
250
+ help="The perator permission level",
251
+ )
252
+
220
253
  # 'op list'
221
254
  op_list_command = op_subcommands.add_parser(
222
255
  "list", help="List operator players", description="List all operator players."
@@ -334,7 +367,26 @@ def main() -> int:
334
367
  whitelist_command.set_defaults(parser=whitelist_command)
335
368
  whitelist_subcommands = whitelist_command.add_subparsers(title="Subcommands")
336
369
 
337
- # whitelist_add and whitelist_remove is not going to be implemented in this version
370
+ # 'whitelist add'
371
+ whitelist_add_command = whitelist_subcommands.add_parser(
372
+ "add",
373
+ help="Add players to the whitelist",
374
+ description="Add players to the whitelist.",
375
+ )
376
+
377
+ whitelist_add_command.set_defaults(command_name="whitelist_add")
378
+ whitelist_add_command.add_argument(
379
+ "players", nargs="+", type=str, help="List of player names or UUIDs"
380
+ )
381
+
382
+ whitelist_add_command.add_argument(
383
+ "--online-mode",
384
+ type=str,
385
+ choices=["true","false"],
386
+ metavar="Boolean",
387
+ help="The server's online mode (true/false)",
388
+ )
389
+
338
390
  # 'whitelist list'
339
391
  whitelist_list_command = whitelist_subcommands.add_parser(
340
392
  "list",
@@ -0,0 +1,103 @@
1
+ import logging
2
+ from argparse import Namespace
3
+ from uuid import UUID
4
+
5
+ from ..minecraft.player_identity import separate_uuids_and_names
6
+ from ..server_files.ops import OperatorPlayer
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ def run(args: Namespace) -> int:
12
+ player_ids: list[str] = args.players
13
+ online_mode: str | None = args.online_mode
14
+ permission_level: int | None = args.permission_level
15
+ bypasses_player_limit: bool = args.bypasses_player_limit
16
+
17
+ # UUID vs name seperator
18
+ player_uuids, player_names = separate_uuids_and_names(player_ids)
19
+ uuids: set[UUID] = set(player_uuids)
20
+ names: set[str] = set(player_names)
21
+
22
+ player_uuids: list[UUID] = list(uuids)
23
+ player_names: list[str] = list(names)
24
+
25
+ is_online_mode: bool = True
26
+ if online_mode is None:
27
+ from ..server_files import server_properties
28
+ is_online_mode = server_properties.get_online_mode()
29
+ else:
30
+ is_online_mode = online_mode != "false"
31
+
32
+ if permission_level is None:
33
+ from ..server_files import server_properties
34
+ permission_level = server_properties.get_operator_permission_level()
35
+
36
+ operator_players: list[OperatorPlayer] = []
37
+ if is_online_mode:
38
+ from ..minecraft import api as minecraft_api
39
+ for player_uuid in player_uuids:
40
+ player_profile = minecraft_api.get_player_from_uuid(player_uuid)
41
+ operator_player: OperatorPlayer = {
42
+ "player_uuid": player_profile["player_uuid"],
43
+ "player_name": player_profile["player_name"],
44
+ "permission_level": permission_level,
45
+ "bypasses_player_limit": bypasses_player_limit
46
+ }
47
+
48
+ operator_players.append(operator_player)
49
+ if len(player_names) > 10:
50
+ logger.error("Cannot add more than 10 players by name at once.")
51
+ return 1
52
+
53
+ player_profiles = minecraft_api.get_players_from_names(player_names)
54
+ for player_profile in player_profiles:
55
+ operator_player: OperatorPlayer = {
56
+ "player_uuid": player_profile["player_uuid"],
57
+ "player_name": player_profile["player_name"],
58
+ "permission_level": permission_level,
59
+ "bypasses_player_limit": bypasses_player_limit
60
+ }
61
+
62
+ operator_players.append(operator_player)
63
+ else:
64
+ for player_uuid in uuids:
65
+ logger.error(f"Cannot add by UUID in offline mode: {player_uuid}, use name instead.")
66
+ return 1
67
+
68
+ from ..minecraft.player_identity import get_offline_player_identities
69
+ player_profiles = get_offline_player_identities(player_names)
70
+ for player_profile in player_profiles:
71
+ operator_player: OperatorPlayer = {
72
+ "player_uuid": player_profile["player_uuid"],
73
+ "player_name": player_profile["player_name"],
74
+ "permission_level": permission_level,
75
+ "bypasses_player_limit": bypasses_player_limit
76
+ }
77
+
78
+ operator_players.append(operator_player)
79
+
80
+ from ..server_files import ops
81
+ ops.upsert_players(operator_players)
82
+
83
+ from ..shared import pluralize
84
+ player_count: int = len(operator_players)
85
+ if player_count == 0:
86
+ logger.info("There are no player to be granted operator.")
87
+ return 0
88
+
89
+ player_list: list[str] = []
90
+ for player in operator_players:
91
+ player_list.append(f"{player['player_name']} ({player['player_uuid']})")
92
+
93
+ info_message = f"Granted {player_count} {pluralize('player', player_count)} operator with permission level {permission_level}"
94
+
95
+ if bypasses_player_limit:
96
+ info_message += " and can bypass player limit"
97
+
98
+ info_message += ":"
99
+ logger.info(info_message)
100
+
101
+ player_list.sort()
102
+ print("\n".join(player_list))
103
+ return 0
@@ -1,7 +1,7 @@
1
1
  import logging
2
2
  from argparse import Namespace
3
- from uuid import UUID
4
3
 
4
+ from ..minecraft.player_identity import separate_uuids_and_names
5
5
  from ..server_files import ops
6
6
  from ..shared import pluralize
7
7
 
@@ -9,18 +9,9 @@ logger = logging.getLogger(__name__)
9
9
 
10
10
 
11
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)
12
+ player_ids: list[str] = args.players
23
13
 
14
+ player_uuids, player_names = separate_uuids_and_names(player_ids)
24
15
  removed_players = ops.remove_players(player_uuids, player_names)
25
16
 
26
17
  player_count: int = len(removed_players)
@@ -7,6 +7,7 @@ from mcserver import paths
7
7
  from .. import config
8
8
  from ..modrinth import apis as modrinth_apis
9
9
  from ..modrinth.shared import SearchSortIndex
10
+ from ..modrinth.typings import Environment
10
11
  from ..shared import (
11
12
  default_configs,
12
13
  format_number,
@@ -98,7 +99,10 @@ def run(args: Namespace) -> int:
98
99
  logger.error("Vanilla does not support mods or plugin.")
99
100
  return 1
100
101
 
101
- project_label, _ = get_loader_context(loader_name)
102
+ if loader_name is not None:
103
+ project_label, _ = get_loader_context(loader_name)
104
+ else:
105
+ project_label = "project"
102
106
 
103
107
  search_offset = search_limit * (page_number - 1)
104
108
  search_facets = [
@@ -158,13 +162,13 @@ def run(args: Namespace) -> int:
158
162
  project_author = f"by {hit['author_username']}"
159
163
 
160
164
  environments: list[Environment] = hit["environments"]
161
- if len(environments) == 0:
162
- environments.append("No environment")
163
-
164
165
  readable_environments: list[str] = []
165
166
  for environment in environments:
166
167
  readable_environments.append(project_environment_name(environment))
167
168
 
169
+ if len(readable_environments) == 0:
170
+ readable_environments.append("No environment")
171
+
168
172
  print(f"{hit['project_name']} ({slug_or_id}) {project_author}")
169
173
  print(
170
174
  f"> Downloads: {format_number(hit['download_count'])} | Follows: {format_number(hit['follower_count'])}"
@@ -0,0 +1,81 @@
1
+ import logging
2
+ from argparse import Namespace
3
+ from uuid import UUID
4
+
5
+ from ..minecraft.player_identity import separate_uuids_and_names
6
+ from ..server_files.whitelist import WhitelistedPlayer
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ def run(args: Namespace) -> int:
12
+ player_ids: list[str] = args.players
13
+ online_mode: str | None = args.online_mode
14
+
15
+ # UUID vs name seperator
16
+ player_uuids, player_names = separate_uuids_and_names(player_ids)
17
+ uuids: set[UUID] = set(player_uuids)
18
+ names: set[str] = set(player_names)
19
+
20
+ player_uuids: list[UUID] = list(uuids)
21
+ player_names: list[str] = list(names)
22
+
23
+ is_online_mode: bool = True
24
+ if online_mode is None:
25
+ from ..server_files import server_properties
26
+ is_online_mode = server_properties.get_online_mode()
27
+ else:
28
+ is_online_mode = online_mode != "false"
29
+
30
+ whitelisted_players: list[WhitelistedPlayer] = []
31
+ if is_online_mode:
32
+ from ..minecraft import api as minecraft_api
33
+ for player_uuid in player_uuids:
34
+ player_profile = minecraft_api.get_player_from_uuid(player_uuid)
35
+ whitelisted_player: WhitelistedPlayer = {
36
+ "player_uuid": player_profile["player_uuid"],
37
+ "player_name": player_profile["player_name"]
38
+ }
39
+
40
+ whitelisted_players.append(whitelisted_player)
41
+ if len(player_names) > 10:
42
+ logger.error("Cannot add more than 10 players by name at once.")
43
+ return 1
44
+
45
+ player_profiles = minecraft_api.get_players_from_names(player_names)
46
+ for player_profile in player_profiles:
47
+ whitelisted_player: WhitelistedPlayer = {
48
+ "player_uuid": player_profile["player_uuid"],
49
+ "player_name": player_profile["player_name"]
50
+ }
51
+
52
+ whitelisted_players.append(whitelisted_player)
53
+ else:
54
+ for player_uuid in uuids:
55
+ logger.error(f"Cannot add by UUID in offline mode: {player_uuid}, use name instead.")
56
+ return 1
57
+
58
+ from ..minecraft.player_identity import get_offline_player_identities
59
+ whitelisted_players = get_offline_player_identities(player_names)
60
+
61
+ from ..server_files import whitelist
62
+ whitelist.upsert_players(whitelisted_players)
63
+
64
+ from ..shared import pluralize
65
+ player_count: int = len(whitelisted_players)
66
+ if player_count == 0:
67
+ logger.info("There are no whitelisted player.")
68
+ return 0
69
+
70
+ player_list: list[str] = []
71
+ for player in whitelisted_players:
72
+ player_list.append(f"{player['player_name']} ({player['player_uuid']})")
73
+
74
+ logger.info(
75
+ f"Added {player_count} {pluralize('player', player_count)} to the whitelist:"
76
+ )
77
+
78
+ player_list.sort()
79
+ print("\n".join(player_list))
80
+
81
+ return 0
@@ -1,7 +1,7 @@
1
1
  import logging
2
2
  from argparse import Namespace
3
- from uuid import UUID
4
3
 
4
+ from ..minecraft.player_identity import separate_uuids_and_names
5
5
  from ..server_files import whitelist
6
6
  from ..shared import pluralize
7
7
 
@@ -9,18 +9,9 @@ logger = logging.getLogger(__name__)
9
9
 
10
10
 
11
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)
12
+ player_ids: list[str] = args.players
23
13
 
14
+ player_uuids, player_names = separate_uuids_and_names(player_ids)
24
15
  removed_players = whitelist.remove_players(player_uuids, player_names)
25
16
 
26
17
  player_count: int = len(removed_players)
@@ -0,0 +1,112 @@
1
+ # Modules
2
+ # Standard
3
+ import json
4
+ from typing import NotRequired
5
+ from uuid import UUID
6
+
7
+ from .. import networking
8
+ from ..constants import minecraft_api_url
9
+ from .player_identity import PlayerIdentity
10
+
11
+
12
+ class GetPlayerError(Exception):
13
+ pass
14
+
15
+
16
+ # TypedDict
17
+ class PlayerProfile(PlayerIdentity):
18
+ is_legacy: NotRequired[bool]
19
+ is_demo: NotRequired[bool]
20
+
21
+
22
+ # Functions
23
+ def get_player_from_name(player_name: str) -> PlayerIdentity:
24
+ response = networking.request(
25
+ f"{minecraft_api_url}/minecraft/profile/lookup/name/{player_name}"
26
+ )
27
+
28
+ if "json" not in response:
29
+ raise GetPlayerError("Response does not have JSON")
30
+
31
+ response_json = response["json"]
32
+ if not isinstance(response_json, dict):
33
+ raise TypeError("Response JSON is not a dictionary")
34
+
35
+ player_profile: PlayerProfile = {
36
+ "player_uuid": UUID(response_json["id"]),
37
+ "player_name": response_json["name"],
38
+ }
39
+
40
+ if "legacy" in response_json:
41
+ player_profile["is_legacy"] = response_json["legacy"]
42
+
43
+ if "demo" in response_json:
44
+ player_profile["is_demo"] = response_json["demo"]
45
+
46
+ return player_profile
47
+
48
+
49
+ def get_players_from_names(player_names: list[str] | str) -> list[PlayerProfile]:
50
+ if isinstance(player_names, str):
51
+ player_names = [player_names]
52
+
53
+ if len(player_names) > 10:
54
+ raise ValueError("Cannot query more than 10 player names at once")
55
+
56
+ data_payload = json.dumps(player_names).encode("utf-8")
57
+ response = networking.request(
58
+ f"{minecraft_api_url}/minecraft/profile/lookup/bulk/byname",
59
+ data=data_payload,
60
+ headers={"Content-Type": "application/json"},
61
+ method="POST",
62
+ )
63
+
64
+ if "json" not in response:
65
+ raise GetPlayerError("Response does not have JSON")
66
+
67
+ response_json = response["json"]
68
+ if not isinstance(response_json, list):
69
+ raise TypeError("Response JSON is not a list")
70
+
71
+ player_identities: list[PlayerProfile] = []
72
+ for player in response_json:
73
+ player_profile: PlayerProfile = {
74
+ "player_uuid": UUID(player["id"]),
75
+ "player_name": player["name"],
76
+ }
77
+
78
+ if "legacy" in player:
79
+ player_profile["is_legacy"] = player["legacy"]
80
+
81
+ if "demo" in player:
82
+ player_profile["is_demo"] = player["demo"]
83
+
84
+ player_identities.append(player_profile)
85
+
86
+ return player_identities
87
+
88
+
89
+ def get_player_from_uuid(player_uuid: UUID) -> PlayerProfile:
90
+ response = networking.request(
91
+ f"{minecraft_api_url}/minecraft/profile/lookup/{player_uuid}"
92
+ )
93
+
94
+ if "json" not in response:
95
+ raise GetPlayerError("Response does not have JSON")
96
+
97
+ response_json = response["json"]
98
+ if not isinstance(response_json, dict):
99
+ raise TypeError("Response JSON is not a dictionary")
100
+
101
+ player_profile: PlayerProfile = {
102
+ "player_uuid": UUID(response_json["id"]),
103
+ "player_name": response_json["name"],
104
+ }
105
+
106
+ if "legacy" in response_json:
107
+ player_profile["is_legacy"] = response_json["legacy"]
108
+
109
+ if "demo" in response_json:
110
+ player_profile["is_demo"] = response_json["demo"]
111
+
112
+ return player_profile
@@ -0,0 +1,41 @@
1
+ # Modules
2
+ import hashlib
3
+ import logging
4
+ from typing import TypedDict
5
+ from uuid import UUID
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+ # TypedDicts
10
+ class PlayerIdentity(TypedDict):
11
+ player_uuid: UUID
12
+ player_name: str
13
+
14
+
15
+ def separate_uuids_and_names(player_ids: list[str]) -> tuple[list[UUID], list[str]]:
16
+ player_uuids: list[UUID] = []
17
+ player_names: list[str] = []
18
+ for player_id in player_ids:
19
+ try:
20
+ player_uuids.append(UUID(player_id))
21
+ except ValueError:
22
+ player_names.append(player_id)
23
+
24
+ return (player_uuids, player_names)
25
+
26
+ def get_offline_player_identities(
27
+ player_names: list[str] | str,
28
+ ) -> list[PlayerIdentity]:
29
+ player_identities: list[PlayerIdentity] = []
30
+ for player_name in player_names:
31
+ offline_player = f"OfflinePlayer:{player_name}".encode()
32
+ player_uuid: UUID = UUID(hashlib.md5(offline_player).hexdigest())
33
+ logger.debug(f"Offline UUID for '{player_name}' is '{player_uuid}'")
34
+ player_identity: PlayerIdentity = {
35
+ "player_uuid": player_uuid,
36
+ "player_name": player_name,
37
+ }
38
+
39
+ player_identities.append(player_identity)
40
+
41
+ return player_identities
@@ -163,7 +163,7 @@ def get_project_informations(project_ids: list[str] | str) -> list[ProjectInform
163
163
  and requested_project_status not in shared.REQUESTED_PROJECT_STATUS
164
164
  ):
165
165
  logger.warning(
166
- f"Unknown requested_project_status status: {monetization_status}"
166
+ f"Unknown requested_project_status: {requested_project_status}"
167
167
  )
168
168
 
169
169
  project_information["requested_project_status"] = requested_project_status
@@ -1,14 +1,13 @@
1
1
  import json
2
2
  import logging
3
3
 
4
- from mcserver.modrinth.typings.search_projects import DisclosureType
5
-
6
4
  from ... import networking
7
5
  from ...constants import modrinth_api_url
8
6
  from .. import shared
9
7
  from ..shared import SearchSortIndex
10
8
  from ..typings import (
11
9
  AllProjectType,
10
+ DisclosureType,
12
11
  Environment,
13
12
  EnvironmentSide,
14
13
  ProjectType,