mcserver-cli 3.0.0__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.
- mcserver/__init__.py +1 -0
- mcserver/__main__.py +3 -0
- mcserver/app.py +24 -0
- mcserver/caching.py +0 -0
- mcserver/cli.py +326 -0
- mcserver/commands/__init__.py +39 -0
- mcserver/commands/add_projects.py +7 -0
- mcserver/commands/import_setup.py +6 -0
- mcserver/commands/initialize_server.py +119 -0
- mcserver/commands/list_projects.py +6 -0
- mcserver/commands/op_grant.py +7 -0
- mcserver/commands/op_list.py +32 -0
- mcserver/commands/op_revoke.py +7 -0
- mcserver/commands/print_help.py +7 -0
- mcserver/commands/remove_projects.py +6 -0
- mcserver/commands/search_projects.py +7 -0
- mcserver/commands/server_status.py +6 -0
- mcserver/commands/show_projects.py +26 -0
- mcserver/commands/start_server.py +150 -0
- mcserver/commands/stop_server.py +46 -0
- mcserver/commands/update_projects.py +6 -0
- mcserver/commands/whitelist_add.py +7 -0
- mcserver/commands/whitelist_list.py +31 -0
- mcserver/commands/whitelist_remove.py +31 -0
- mcserver/commandsa.py +1297 -0
- mcserver/config.py +37 -0
- mcserver/constants.py +14 -0
- mcserver/fabricmc/meta.py +41 -0
- mcserver/legacyfabric/meta.py +37 -0
- mcserver/metadata.py +33 -0
- mcserver/minecraft/api.py +94 -0
- mcserver/minecraft/player_identity.py +21 -0
- mcserver/modrinth/api.py +657 -0
- mcserver/modrinth/modpack.py +101 -0
- mcserver/modrinth/project_records/metadata.py +0 -0
- mcserver/modrinth/project_records/relationships.py +203 -0
- mcserver/modrinth/project_records/shared.py +9 -0
- mcserver/modrinth/projects.py +80 -0
- mcserver/modrinth/shared.py +13 -0
- mcserver/modrinth/slug_index.py +36 -0
- mcserver/mojang/eula.py +28 -0
- mcserver/mojang/manifest.py +69 -0
- mcserver/mysterious.py +5 -0
- mcserver/networking.py +152 -0
- mcserver/papermc/api.py +81 -0
- mcserver/purpurmc/api.py +81 -0
- mcserver/quiltmc/meta.py +54 -0
- mcserver/resolver.py +164 -0
- mcserver/server_files/banned_ips.py +120 -0
- mcserver/server_files/banned_players.py +166 -0
- mcserver/server_files/ops.py +143 -0
- mcserver/server_files/properties.py +41 -0
- mcserver/server_files/usercaches.py +0 -0
- mcserver/server_files/whitelist.py +163 -0
- mcserver/shared.py +198 -0
- mcserver/state.py +77 -0
- mcserver_cli-3.0.0.dist-info/METADATA +63 -0
- mcserver_cli-3.0.0.dist-info/RECORD +61 -0
- mcserver_cli-3.0.0.dist-info/WHEEL +4 -0
- mcserver_cli-3.0.0.dist-info/entry_points.txt +2 -0
- mcserver_cli-3.0.0.dist-info/licenses/LICENSE +9 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Modules
|
|
2
|
+
# Standard
|
|
3
|
+
import json
|
|
4
|
+
import logging as log
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Literal, TypedDict
|
|
7
|
+
from uuid import UUID
|
|
8
|
+
|
|
9
|
+
# MCServer
|
|
10
|
+
from mcserver.minecraft.player_identity import (
|
|
11
|
+
PlayerID,
|
|
12
|
+
PlayerUUID,
|
|
13
|
+
normalize_player_uuid,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# Paths
|
|
17
|
+
banned_players_file = Path("banned-players.json")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# TypedDicts
|
|
21
|
+
class BannedPlayerEntry(TypedDict):
|
|
22
|
+
uuid: str
|
|
23
|
+
name: str
|
|
24
|
+
created: str # Time Format: yyyy-MM-dd HH:mm:ss Z
|
|
25
|
+
source: str
|
|
26
|
+
expires: str | Literal["forever"] # Time format: yyyy-MM-dd HH:mm:ss Z
|
|
27
|
+
reason: str
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class BannedPlayer(TypedDict):
|
|
31
|
+
player_uuid: PlayerUUID
|
|
32
|
+
player_name: str
|
|
33
|
+
created_time: str # Time format: yyyy-MM-dd HH:mm:ss Z
|
|
34
|
+
ban_source: str
|
|
35
|
+
expires_time: str | Literal["forever"] # Time format: yyyy-MM-dd HH:mm:ss Z
|
|
36
|
+
ban_reason: str
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Functions
|
|
40
|
+
# Add Players
|
|
41
|
+
def add_player(
|
|
42
|
+
player_uuid: PlayerUUID,
|
|
43
|
+
player_name: str,
|
|
44
|
+
created_time: str,
|
|
45
|
+
ban_source: str,
|
|
46
|
+
expires_time: str | Literal["forever"] = "forever",
|
|
47
|
+
ban_reason: str = "Banned via MCServer.",
|
|
48
|
+
) -> None:
|
|
49
|
+
player_uuid = normalize_player_uuid(player_uuid)
|
|
50
|
+
banned_player: BannedPlayer = {
|
|
51
|
+
"player_uuid": player_uuid,
|
|
52
|
+
"player_name": player_name,
|
|
53
|
+
"created_time": created_time,
|
|
54
|
+
"ban_source": ban_source,
|
|
55
|
+
"expires_time": expires_time,
|
|
56
|
+
"ban_reason": ban_reason,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
add_players(banned_player)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def add_players(banned_players: list[BannedPlayer] | BannedPlayer) -> None:
|
|
63
|
+
if not isinstance(banned_players, list):
|
|
64
|
+
banned_players = [banned_players]
|
|
65
|
+
|
|
66
|
+
with open(banned_players_file, mode="r+") as file:
|
|
67
|
+
banned_players_data = json.load(file)
|
|
68
|
+
for player in banned_players:
|
|
69
|
+
player_uuid = str(normalize_player_uuid(player["player_uuid"]))
|
|
70
|
+
player_name = player["player_name"]
|
|
71
|
+
ban_reason = player["ban_reason"]
|
|
72
|
+
|
|
73
|
+
banned_player_entry: BannedPlayerEntry = {
|
|
74
|
+
"uuid": player_uuid,
|
|
75
|
+
"name": player_name,
|
|
76
|
+
"created": player["created_time"],
|
|
77
|
+
"source": player["ban_source"],
|
|
78
|
+
"expires": player["expires_time"],
|
|
79
|
+
"reason": ban_reason,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
banned_players_data.append(banned_player_entry)
|
|
83
|
+
log.debug(
|
|
84
|
+
f"Added player '{player_name}' ({player_uuid}) to the banned player list with reason '{ban_reason}'"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
file.seek(0)
|
|
88
|
+
json.dump(banned_players_data, file, indent=2)
|
|
89
|
+
file.truncate()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# Remove Players
|
|
93
|
+
def remove_player(
|
|
94
|
+
player_uuid: PlayerUUID | None = None, player_name: str | None = None
|
|
95
|
+
) -> None:
|
|
96
|
+
if player_uuid is None and player_name is None:
|
|
97
|
+
raise ValueError(
|
|
98
|
+
"At least one of 'player_uuid' or 'player_name' must be provided"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
player_id: PlayerID = {}
|
|
102
|
+
if player_uuid is not None:
|
|
103
|
+
player_uuid = normalize_player_uuid(player_uuid)
|
|
104
|
+
player_id["player_uuid"] = normalize_player_uuid(player_uuid)
|
|
105
|
+
|
|
106
|
+
if player_name is not None:
|
|
107
|
+
player_id["player_name"] = player_name
|
|
108
|
+
|
|
109
|
+
remove_players(player_id)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def remove_players(player_ids: list[PlayerID] | PlayerID) -> None:
|
|
113
|
+
if not isinstance(player_ids, list):
|
|
114
|
+
player_ids = [player_ids]
|
|
115
|
+
|
|
116
|
+
player_uuids: set[PlayerUUID | None] = set()
|
|
117
|
+
player_names: set[str | None] = set()
|
|
118
|
+
for player_id in player_ids:
|
|
119
|
+
if "player_uuid" in player_id:
|
|
120
|
+
player_uuids.add(player_id["player_uuid"])
|
|
121
|
+
|
|
122
|
+
if "player_name" in player_id:
|
|
123
|
+
player_names.add(player_id["player_name"])
|
|
124
|
+
|
|
125
|
+
with open(banned_players_file, mode="r+") as file:
|
|
126
|
+
banned_players_data: list[BannedPlayerEntry] = json.load(file)
|
|
127
|
+
new_banned_players_data: list[BannedPlayerEntry] = []
|
|
128
|
+
for player in banned_players_data:
|
|
129
|
+
player_uuid = player["uuid"]
|
|
130
|
+
player_name = player["name"]
|
|
131
|
+
|
|
132
|
+
if player_uuid in player_uuids or player_name in player_names:
|
|
133
|
+
log.debug(
|
|
134
|
+
f"Removed player '{player_name}' ({player_uuid}) from the banned player list"
|
|
135
|
+
)
|
|
136
|
+
continue
|
|
137
|
+
|
|
138
|
+
new_banned_players_data.append(player)
|
|
139
|
+
|
|
140
|
+
file.seek(0)
|
|
141
|
+
json.dump(new_banned_players_data, file, indent=2)
|
|
142
|
+
file.truncate()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# TODO: add 'update_player' and 'update_players', just use plain dict.update() because it has no depth
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# List Players
|
|
149
|
+
def list_players() -> list[BannedPlayer]:
|
|
150
|
+
banned_players_data: list[BannedPlayerEntry] = json.loads(
|
|
151
|
+
banned_players_file.read_text()
|
|
152
|
+
)
|
|
153
|
+
banned_players: list[BannedPlayer] = []
|
|
154
|
+
for entry in banned_players_data:
|
|
155
|
+
banned_player: BannedPlayer = {
|
|
156
|
+
"player_uuid": UUID(entry["uuid"]),
|
|
157
|
+
"player_name": entry["name"],
|
|
158
|
+
"created_time": entry["created"],
|
|
159
|
+
"ban_source": entry["source"],
|
|
160
|
+
"expires_time": entry["expires"],
|
|
161
|
+
"ban_reason": entry["reason"],
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
banned_players.append(banned_player)
|
|
165
|
+
|
|
166
|
+
return banned_players
|
|
@@ -0,0 +1,143 @@
|
|
|
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
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Imports
|
|
2
|
+
from typing import TypedDict
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# TypedDicts
|
|
6
|
+
class ParsedProperties(TypedDict):
|
|
7
|
+
comments: list[str]
|
|
8
|
+
data: dict[str, str]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Functions
|
|
12
|
+
def loads(properties_data: str) -> ParsedProperties:
|
|
13
|
+
parsed_properties: ParsedProperties = {"comments": [], "data": {}}
|
|
14
|
+
|
|
15
|
+
for line in properties_data.splitlines():
|
|
16
|
+
line = line.strip()
|
|
17
|
+
if not line:
|
|
18
|
+
continue
|
|
19
|
+
|
|
20
|
+
if line.startswith("#"):
|
|
21
|
+
parsed_properties["comments"].append(line)
|
|
22
|
+
|
|
23
|
+
if not "=" in line:
|
|
24
|
+
continue
|
|
25
|
+
|
|
26
|
+
key, value = line.split("=", 1)
|
|
27
|
+
parsed_properties[key.strip()] = value
|
|
28
|
+
|
|
29
|
+
return parsed_properties
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Dump only support comments that are oon top of the file
|
|
33
|
+
def dumps(parsed_properties: ParsedProperties) -> str:
|
|
34
|
+
lines: list[str] = []
|
|
35
|
+
for comment in parsed_properties["comments"]:
|
|
36
|
+
lines.append(f"#{comment}")
|
|
37
|
+
|
|
38
|
+
for key, value in parsed_properties["data"].items():
|
|
39
|
+
lines.append(f"{key}={value}")
|
|
40
|
+
|
|
41
|
+
return "\n".join(lines)
|
|
File without changes
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Modules
|
|
2
|
+
# Standard
|
|
3
|
+
import json
|
|
4
|
+
import logging as log
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import TypedDict
|
|
7
|
+
from uuid import UUID
|
|
8
|
+
|
|
9
|
+
# MCServer
|
|
10
|
+
from mcserver.minecraft.player_identity import (
|
|
11
|
+
PlayerID,
|
|
12
|
+
PlayerUUID,
|
|
13
|
+
normalize_player_uuid,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# Paths
|
|
17
|
+
whitelist_file = Path("whitelist.json")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# TypedDicts
|
|
21
|
+
class WhitelistEntry(TypedDict):
|
|
22
|
+
uuid: str
|
|
23
|
+
name: str
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class WhitelistPlayer(TypedDict):
|
|
27
|
+
player_uuid: PlayerUUID
|
|
28
|
+
player_name: str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Functions
|
|
32
|
+
# Add Players
|
|
33
|
+
def add_player(player_uuid: PlayerUUID, player_name: str) -> None:
|
|
34
|
+
whitelist_player: WhitelistPlayer = {
|
|
35
|
+
"player_uuid": player_uuid,
|
|
36
|
+
"player_name": player_name,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
add_players(whitelist_player)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def add_players(whitelist_players: list[WhitelistPlayer] | WhitelistPlayer) -> None:
|
|
43
|
+
if not isinstance(whitelist_players, list):
|
|
44
|
+
whitelist_players = [whitelist_players]
|
|
45
|
+
|
|
46
|
+
with open(whitelist_file, mode="r+") as file:
|
|
47
|
+
whitelist_data = json.load(file)
|
|
48
|
+
|
|
49
|
+
for player in whitelist_players:
|
|
50
|
+
whitelist_entry: WhitelistEntry = {
|
|
51
|
+
"uuid": str(normalize_player_uuid(player["player_uuid"])),
|
|
52
|
+
"name": player.get("player_name", ""),
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
whitelist_data.append(whitelist_entry)
|
|
56
|
+
|
|
57
|
+
file.seek(0)
|
|
58
|
+
json.dump(whitelist_data, file, indent=2)
|
|
59
|
+
file.truncate()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# Remove Players
|
|
63
|
+
def remove_player(
|
|
64
|
+
player_uuid: PlayerUUID | None = None, player_name: str | None = None
|
|
65
|
+
) -> None:
|
|
66
|
+
if player_uuid is None and player_name is None:
|
|
67
|
+
raise ValueError(
|
|
68
|
+
"At least one of 'player_uuid' or 'player_name' must be provided"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
player_id: PlayerID = {}
|
|
72
|
+
if player_uuid is not None:
|
|
73
|
+
player_uuid = normalize_player_uuid(player_uuid)
|
|
74
|
+
player_id["player_uuid"] = normalize_player_uuid(player_uuid)
|
|
75
|
+
|
|
76
|
+
if player_name is not None:
|
|
77
|
+
player_id["player_name"] = player_name
|
|
78
|
+
|
|
79
|
+
remove_players(player_id)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def remove_players(player_ids: list[PlayerID] | PlayerID) -> None:
|
|
83
|
+
"""Remove player entries matching the exact UUIDs or names
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
player_ids: Player UUIDs and/or names object
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
None: Returns nothing
|
|
90
|
+
|
|
91
|
+
Raises:
|
|
92
|
+
FileNotFoundError: The 'whitelist.json' file not found
|
|
93
|
+
"""
|
|
94
|
+
if not isinstance(player_ids, list):
|
|
95
|
+
player_ids = [player_ids]
|
|
96
|
+
|
|
97
|
+
player_uuids: set[PlayerUUID | None] = set()
|
|
98
|
+
player_names: set[str | None] = set()
|
|
99
|
+
for player_id in player_ids:
|
|
100
|
+
if "player_uuid" in player_id:
|
|
101
|
+
player_uuids.add(player_id["player_uuid"])
|
|
102
|
+
|
|
103
|
+
if "player_name" in player_id:
|
|
104
|
+
player_names.add(player_id["player_name"])
|
|
105
|
+
|
|
106
|
+
if not whitelist_file.exists():
|
|
107
|
+
raise FileNotFoundError(f"File '{whitelist_file}' is not found.")
|
|
108
|
+
|
|
109
|
+
with open(whitelist_file, mode="r+") as file:
|
|
110
|
+
whitelist_data: list[WhitelistEntry] = json.load(file)
|
|
111
|
+
new_whitelist_data: list[WhitelistEntry] = []
|
|
112
|
+
for player in whitelist_data:
|
|
113
|
+
player_uuid = player["uuid"]
|
|
114
|
+
player_name = player["name"]
|
|
115
|
+
|
|
116
|
+
if player_uuid in player_uuids or player_name in player_names:
|
|
117
|
+
log.debug(
|
|
118
|
+
f"Removed player '{player_name}' ({player_uuid}) from the whitelisted player list"
|
|
119
|
+
)
|
|
120
|
+
continue
|
|
121
|
+
|
|
122
|
+
new_whitelist_data.append(player)
|
|
123
|
+
|
|
124
|
+
file.seek(0)
|
|
125
|
+
json.dump(new_whitelist_data, file, indent=2)
|
|
126
|
+
file.truncate()
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# TODO: add 'update_player' and 'update_players', just use plain dict.update() because it has no depth
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# List Players
|
|
133
|
+
def list_players() -> list[WhitelistPlayer]:
|
|
134
|
+
whitelist_data: list[WhitelistEntry] = json.loads(whitelist_file.read_text())
|
|
135
|
+
|
|
136
|
+
whitelist_players: list[WhitelistPlayer] = []
|
|
137
|
+
for entry in whitelist_data:
|
|
138
|
+
player_uuid: str | None = entry.get("uuid", None)
|
|
139
|
+
player_name: str | None = entry.get("name", None)
|
|
140
|
+
|
|
141
|
+
if player_uuid is None or player_name is None:
|
|
142
|
+
error_message = f"Invalid whitelist entry!"
|
|
143
|
+
if player_uuid:
|
|
144
|
+
error_message += f" UUID: '{player_uuid}'"
|
|
145
|
+
|
|
146
|
+
if player_name:
|
|
147
|
+
error_message += f" Name: '{player_name}'"
|
|
148
|
+
|
|
149
|
+
log.error(error_message)
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
whitelist_player: WhitelistPlayer = {
|
|
154
|
+
"player_uuid": UUID(player_uuid),
|
|
155
|
+
"player_name": entry["name"],
|
|
156
|
+
}
|
|
157
|
+
except ValueError:
|
|
158
|
+
log.error(f"Invalid UUID in whitelist: '{player_uuid}'")
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
whitelist_players.append(whitelist_player)
|
|
162
|
+
|
|
163
|
+
return whitelist_players
|