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.
Files changed (61) hide show
  1. mcserver/__init__.py +1 -0
  2. mcserver/__main__.py +3 -0
  3. mcserver/app.py +24 -0
  4. mcserver/caching.py +0 -0
  5. mcserver/cli.py +326 -0
  6. mcserver/commands/__init__.py +39 -0
  7. mcserver/commands/add_projects.py +7 -0
  8. mcserver/commands/import_setup.py +6 -0
  9. mcserver/commands/initialize_server.py +119 -0
  10. mcserver/commands/list_projects.py +6 -0
  11. mcserver/commands/op_grant.py +7 -0
  12. mcserver/commands/op_list.py +32 -0
  13. mcserver/commands/op_revoke.py +7 -0
  14. mcserver/commands/print_help.py +7 -0
  15. mcserver/commands/remove_projects.py +6 -0
  16. mcserver/commands/search_projects.py +7 -0
  17. mcserver/commands/server_status.py +6 -0
  18. mcserver/commands/show_projects.py +26 -0
  19. mcserver/commands/start_server.py +150 -0
  20. mcserver/commands/stop_server.py +46 -0
  21. mcserver/commands/update_projects.py +6 -0
  22. mcserver/commands/whitelist_add.py +7 -0
  23. mcserver/commands/whitelist_list.py +31 -0
  24. mcserver/commands/whitelist_remove.py +31 -0
  25. mcserver/commandsa.py +1297 -0
  26. mcserver/config.py +37 -0
  27. mcserver/constants.py +14 -0
  28. mcserver/fabricmc/meta.py +41 -0
  29. mcserver/legacyfabric/meta.py +37 -0
  30. mcserver/metadata.py +33 -0
  31. mcserver/minecraft/api.py +94 -0
  32. mcserver/minecraft/player_identity.py +21 -0
  33. mcserver/modrinth/api.py +657 -0
  34. mcserver/modrinth/modpack.py +101 -0
  35. mcserver/modrinth/project_records/metadata.py +0 -0
  36. mcserver/modrinth/project_records/relationships.py +203 -0
  37. mcserver/modrinth/project_records/shared.py +9 -0
  38. mcserver/modrinth/projects.py +80 -0
  39. mcserver/modrinth/shared.py +13 -0
  40. mcserver/modrinth/slug_index.py +36 -0
  41. mcserver/mojang/eula.py +28 -0
  42. mcserver/mojang/manifest.py +69 -0
  43. mcserver/mysterious.py +5 -0
  44. mcserver/networking.py +152 -0
  45. mcserver/papermc/api.py +81 -0
  46. mcserver/purpurmc/api.py +81 -0
  47. mcserver/quiltmc/meta.py +54 -0
  48. mcserver/resolver.py +164 -0
  49. mcserver/server_files/banned_ips.py +120 -0
  50. mcserver/server_files/banned_players.py +166 -0
  51. mcserver/server_files/ops.py +143 -0
  52. mcserver/server_files/properties.py +41 -0
  53. mcserver/server_files/usercaches.py +0 -0
  54. mcserver/server_files/whitelist.py +163 -0
  55. mcserver/shared.py +198 -0
  56. mcserver/state.py +77 -0
  57. mcserver_cli-3.0.0.dist-info/METADATA +63 -0
  58. mcserver_cli-3.0.0.dist-info/RECORD +61 -0
  59. mcserver_cli-3.0.0.dist-info/WHEEL +4 -0
  60. mcserver_cli-3.0.0.dist-info/entry_points.txt +2 -0
  61. mcserver_cli-3.0.0.dist-info/licenses/LICENSE +9 -0
@@ -0,0 +1,101 @@
1
+ # Modules
2
+ # Standard
3
+ import json
4
+ import zipfile
5
+ from pathlib import Path
6
+ from typing import Literal, NotRequired, TypedDict
7
+
8
+
9
+ # TYpedDict
10
+ class FileHashes(TypedDict):
11
+ sha1: str # original: sha1
12
+ sha512: str # original: sha512
13
+
14
+
15
+ class ModEnvironment(TypedDict):
16
+ client_side: Literal[
17
+ "required", "optional", "unsupported", "unknown"
18
+ ] # original: client
19
+ server_side: Literal[
20
+ "required", "optional", "unsupported", "unknown"
21
+ ] # original: server
22
+
23
+
24
+ class ModpackFile(TypedDict):
25
+ file_path: Path # original: path
26
+ hashes: FileHashes # original: hashes
27
+ mod_environment: NotRequired[ModEnvironment] # original: env
28
+ download_urls: list[str] # original: downloads
29
+ file_size: int # original: fileSize
30
+
31
+
32
+ class ModpackIndex(TypedDict):
33
+ format_version: int # original: formatVersion
34
+ game_name: Literal["minecraft"] # original: game
35
+ version_id: str # original: versionId
36
+ pack_name: str # original: name
37
+ summary: NotRequired[str] # original: summary
38
+ files: list[ModpackFile] # original: files
39
+ dependencies: dict[str, str] # original: dependencies
40
+
41
+
42
+ # Functions
43
+ def read_index(file_path: Path | str) -> ModpackIndex:
44
+ file_path = Path(file_path)
45
+
46
+ pack_index = {}
47
+ with zipfile.ZipFile(file_path) as zip_file:
48
+ pack_index = json.loads(zip_file.read("modrinth.index.json").decode())
49
+
50
+ files = []
51
+ for file in pack_index["files"]:
52
+ files.append(
53
+ {
54
+ "file_path": Path(file["path"]),
55
+ "hashes": file["hashes"],
56
+ "mod_environment": file.get("env"),
57
+ "download_urls": file["downloads"],
58
+ "file_size": file["fileSize"],
59
+ }
60
+ )
61
+
62
+ return {
63
+ "format_version": pack_index["formatVersion"],
64
+ "game_name": pack_index["game"],
65
+ "version_id": pack_index["versionId"],
66
+ "pack_name": pack_index["name"],
67
+ "summary": pack_index.get("summary"),
68
+ "files": files,
69
+ "dependencies": pack_index["dependencies"],
70
+ }
71
+
72
+
73
+ def _extract_zip(
74
+ zip_file: zipfile.ZipFile, zip_path: str, path: Path | str = Path.cwd()
75
+ ):
76
+ path = Path(path)
77
+ for entry in zip_file.namelist():
78
+ if not entry.startswith(zip_path):
79
+ continue
80
+
81
+ destination_path = path / entry.removeprefix(zip_path)
82
+ destination_path.parent.mkdir(parents=True, exist_ok=True)
83
+
84
+ if entry.endswith("/"):
85
+ destination_path.mkdir(exist_ok=True)
86
+ else:
87
+ destination_path.write_bytes(zip_file.read(entry))
88
+
89
+
90
+ def apply_overrides(file_path: str | Path) -> None:
91
+ file_path = Path(file_path)
92
+
93
+ with zipfile.ZipFile(file_path) as zip_file:
94
+ _extract_zip(zip_file, "overrides/")
95
+
96
+
97
+ def apply_server_overrides(file_path: str | Path) -> None:
98
+ file_path = Path(file_path)
99
+
100
+ with zipfile.ZipFile(file_path) as zip_file:
101
+ _extract_zip(zip_file, "server-overrides/")
File without changes
@@ -0,0 +1,203 @@
1
+ # Imports
2
+ # Standard
3
+ import json
4
+ import logging as log
5
+ from typing import TypedDict
6
+
7
+ # MCServer
8
+ from .shared import project_records_dir
9
+ from .shared import DependencyTypes
10
+
11
+ # Types
12
+ dependency_types: list[str] = ["embedded", "optional", "required", "incompatible"]
13
+
14
+
15
+ # TypedDicts
16
+ class ProjectRelationships(TypedDict):
17
+ dependencies: dict[str, DependencyTypes]
18
+ dependents: dict[str, DependencyTypes]
19
+
20
+
21
+ # Functions
22
+ def create(
23
+ project_id: str,
24
+ dependencies: dict[str, DependencyTypes] | None = None,
25
+ dependents: dict[str, DependencyTypes] | None = None,
26
+ ) -> None:
27
+ """Create a new project relationships record
28
+
29
+ Args:
30
+ project_id: The Modrinth project ID
31
+ dependencies: List of dependencies and its type
32
+ dependents: List of dependents and its type
33
+
34
+ Raises:
35
+ FileNotFoundError: When project_records_dir does not exist
36
+ FileExistError: When project already have a record
37
+
38
+ Returns:
39
+ None
40
+ """
41
+ if dependencies is None:
42
+ dependencies = {}
43
+
44
+ if dependents is None:
45
+ dependents = {}
46
+
47
+ if not project_records_dir.exists():
48
+ raise FileNotFoundError(f"Directory '{project_records_dir}' does not exist")
49
+
50
+ project_record_dir = project_records_dir / project_id
51
+ relationships_file = project_record_dir / "relationships.json"
52
+ if relationships_file.exists():
53
+ raise FileExistsError(
54
+ f"Relationships for project ID '{project_id}' already exists"
55
+ )
56
+
57
+ project_relationships: ProjectRelationships = {"dependencies": {}, "dependents": {}}
58
+ for dependency_id, dependency_type in dependencies.items():
59
+ if dependency_type not in dependency_types:
60
+ log.error(
61
+ f"Invalid dependency type '{dependency_type}' for dependency ID '{dependency_id}' of project ID '{project_id}', ignoring..."
62
+ )
63
+ continue
64
+
65
+ project_relationships["dependencies"][dependency_id] = dependency_type
66
+
67
+ for dependent_id, dependency_type in dependents.items():
68
+ if dependency_type not in dependency_types:
69
+ log.error(
70
+ f"Invalid dependency type '{dependency_type}' for dependent ID '{dependent_id}' of project ID '{project_id}', ignoring..."
71
+ )
72
+ continue
73
+
74
+ project_relationships["dependents"][dependent_id] = dependency_type
75
+
76
+ relationships_data = json.dumps(project_relationships, indent=2)
77
+ relationships_file.write_text(relationships_data)
78
+
79
+
80
+ def read(project_id: str) -> ProjectRelationships:
81
+ """Read a project's relationships record
82
+
83
+ Args:
84
+ project_id: The Modrinth project ID
85
+
86
+ Raises:
87
+ FileNotFoundError: Project has no relationships record or project_records_dir does not exist
88
+ json.JSONDecodeError: Project relationships record file is corrupted
89
+
90
+ Returns:
91
+ List of dependencies and dependents of the project
92
+ """
93
+ if not project_records_dir.exists():
94
+ raise FileNotFoundError(f"Directory '{project_records_dir}' does not exist")
95
+
96
+ project_record_dir = project_records_dir / project_id
97
+ relationships_file = project_record_dir / "relationships.json"
98
+ project_relationships: ProjectRelationships = {"dependencies": {}, "dependents": {}}
99
+
100
+ if not relationships_file.exists():
101
+ raise FileNotFoundError(
102
+ f"Relationships file for project ID '{project_id}' does not exist"
103
+ )
104
+
105
+ relationships_data = relationships_file.read_text()
106
+ relationships_json: ProjectRelationships = {"dependencies": {}, "dependents": {}}
107
+
108
+ try:
109
+ relationships_json = json.loads(relationships_data)
110
+ except json.JSONDecodeError as error:
111
+ raise error
112
+
113
+ dependencies = relationships_json["dependencies"]
114
+ dependents = relationships_json["dependents"]
115
+
116
+ for dependency_id, dependency_type in dependencies.items():
117
+ if dependency_type not in dependency_types:
118
+ log.error(
119
+ f"Invalid type '{dependency_type}' for dependency ID '{dependency_id}' of project ID '{project_id}', ignoring..."
120
+ )
121
+ continue
122
+
123
+ project_relationships["dependencies"][dependency_id] = dependency_type
124
+
125
+ for dependent_id, dependent_type in dependents.items():
126
+ if dependent_type not in dependency_types:
127
+ log.error(
128
+ f"Invalid type '{dependent_type}' for dependent ID '{dependent_id}' of project ID '{project_id}', ignoring..."
129
+ )
130
+ continue
131
+
132
+ project_relationships["dependents"][dependent_id] = dependent_type
133
+
134
+ return project_relationships
135
+
136
+
137
+ def update(
138
+ project_id: str,
139
+ new_dependencies: dict[str, DependencyTypes] | None = None,
140
+ new_dependents: dict[str, DependencyTypes] | None = None,
141
+ ) -> None:
142
+ """Update an existing project relationships record
143
+
144
+ Args:
145
+ project_id: The Modrinth project ID
146
+ new_dependencies: New dependencies relationships data
147
+ new_dependents: New dependents relationships data
148
+
149
+ Raises:
150
+ FileNotFoundError: Project has no relationships record or project_records_dir does not exist
151
+ json.JSONDecodeError: Project relationships record file is corrupted
152
+ """
153
+ if new_dependencies is None:
154
+ new_dependencies = {}
155
+
156
+ if new_dependents is None:
157
+ new_dependents = {}
158
+
159
+ if not project_records_dir.exists():
160
+ raise FileNotFoundError(f"Directory '{project_records_dir}' does not exist")
161
+
162
+ project_record_dir = project_records_dir / project_id
163
+ relationships_file = project_record_dir / "relationships.json"
164
+ if not relationships_file.exists():
165
+ raise FileNotFoundError(
166
+ f"Relationships file for project ID '{project_id}' does not exist"
167
+ )
168
+
169
+ project_relationships: ProjectRelationships = {"dependencies": {}, "dependents": {}}
170
+ relationships_data = relationships_file.read_text()
171
+
172
+ try:
173
+ relationships_json = json.loads(relationships_data)
174
+ except json.JSONDecodeError as error:
175
+ raise error
176
+
177
+ project_relationships.update(relationships_json)
178
+ relationships_data = json.dumps(project_relationships, indent=2)
179
+ relationships_file.write_text(relationships_data)
180
+
181
+
182
+ def delete(project_id: str):
183
+ """ "Delete a project's relationships record
184
+
185
+ Args:
186
+ project_id: The Modrinth project ID
187
+
188
+ Raises:
189
+ FileNotFoundError: Project has no relationships record or project_records_dir does not exist
190
+ """
191
+ if not project_records_dir.exists():
192
+ raise FileNotFoundError(
193
+ f"Relationships file for project ID '{project_id}' does not exist"
194
+ )
195
+
196
+ project_record_dir = project_records_dir / project_id
197
+ relationships_file = project_record_dir / "relationships.json"
198
+ if not relationships_file.exists():
199
+ raise FileNotFoundError(
200
+ f"Relationships file for project ID '{project_id}' does not exist"
201
+ )
202
+
203
+ relationships_file.unlink()
@@ -0,0 +1,9 @@
1
+ # Imports
2
+ # Standard
3
+ from pathlib import Path
4
+
5
+ # MCServer
6
+ from ..shared import modrinth_dir
7
+ from ..shared import DependencyTypes
8
+
9
+ project_records_dir: Path = modrinth_dir / "project_records"
@@ -0,0 +1,80 @@
1
+ # Modules
2
+ # Standard
3
+ import json
4
+
5
+ from ..shared import merge_dict
6
+
7
+ # MCServer
8
+ from .shared import modrinth_dir
9
+
10
+ # Paths
11
+ projects_dir = modrinth_dir / "projects"
12
+
13
+
14
+ # Functions
15
+ def create_project_entry(
16
+ project_id: str, metadata: dict | None = None, relationship: dict | None = None
17
+ ) -> None:
18
+ if metadata is None:
19
+ metadata = {}
20
+ if relationship is None:
21
+ relationship = {}
22
+
23
+ project_entry = projects_dir / project_id
24
+ project_metadata = project_entry / "metadata.json"
25
+ project_relationship = project_entry / "relationship.json"
26
+
27
+ if project_entry.exists():
28
+ raise FileExistsError(f"Project index for '{project_id}' already exists")
29
+
30
+ project_entry.mkdir(parents=True)
31
+ project_metadata.write_text(json.dumps(metadata, indent=2))
32
+ project_relationship.write_text(json.dumps(relationship, indent=2))
33
+
34
+
35
+ def read_project_entry(project_id: str) -> dict:
36
+ project_index = projects_dir / project_id
37
+ project_metadata = project_index / "metadata.json"
38
+ project_relationship = project_index / "relationship.json"
39
+
40
+ return {
41
+ "metadata": json.loads(project_metadata.read_text()),
42
+ "relationship": json.loads(project_relationship.read_text()),
43
+ }
44
+
45
+
46
+ def update_project_entry(
47
+ project_id: str,
48
+ new_metadata: dict | None = None,
49
+ new_relationship: dict | None = None,
50
+ ) -> None:
51
+ if new_metadata is None:
52
+ new_metadata = {}
53
+ if new_relationship is None:
54
+ new_relationship = {}
55
+
56
+ project_index = projects_dir / project_id
57
+ project_metadata = project_index / "metadata.json"
58
+ project_relationship = project_index / "relationship.json"
59
+
60
+ old_data = read_project_entry(project_id)
61
+ project_metadata.write_text(
62
+ json.dumps(merge_dict(old_data["metadata"], new_metadata), indent=2)
63
+ )
64
+ project_relationship.write_text(
65
+ json.dumps(merge_dict(old_data["relationship"], new_relationship), indent=2)
66
+ )
67
+
68
+
69
+ def delete_project_entry(project_id: str) -> None:
70
+ project_index = projects_dir / project_id
71
+ project_metadata = project_index / "metadata.json"
72
+ project_relationship = project_index / "relationship.json"
73
+
74
+ project_metadata.unlink()
75
+ project_relationship.unlink()
76
+ project_index.rmdir()
77
+
78
+
79
+ def project_entry_exists(project_id: str) -> bool:
80
+ return (projects_dir / project_id).exists()
@@ -0,0 +1,13 @@
1
+ # Modules
2
+ # Standard
3
+ from pathlib import Path
4
+ from typing import Literal, TypeAlias
5
+
6
+ # MCServer
7
+ from ..shared import mcserver_dir
8
+
9
+ # TypeAliases
10
+ DependencyTypes: TypeAlias = Literal["embedded", "optional", "required", "incompatible"]
11
+
12
+ # Variables
13
+ modrinth_dir: Path = mcserver_dir / "modrinth"
@@ -0,0 +1,36 @@
1
+ # Modules
2
+ # Standard
3
+ import json
4
+
5
+ # MCServer
6
+ from .shared import modrinth_dir
7
+
8
+ # Variables
9
+ _slug_index = None
10
+
11
+ # Paths
12
+ slug_index_file = modrinth_dir / "slug_index.json"
13
+
14
+
15
+ # Functions
16
+ def _load_slug_index() -> None:
17
+ global _slug_index
18
+ if _slug_index is None:
19
+ _slug_index = json.loads(slug_index_file.read_text())
20
+
21
+
22
+ def slug_to_id(project_slug: str) -> str:
23
+ _load_slug_index()
24
+ project_id: str = ""
25
+ if _slug_index is not None:
26
+ project_id = _slug_index.get(project_slug, project_slug)["id"]
27
+
28
+ return project_id
29
+
30
+
31
+ def add_slug(project_slug: str, project_id: str) -> None:
32
+ global _slug_index
33
+ if _slug_index is None:
34
+ _slug_index = {}
35
+
36
+ _slug_index[project_slug] = project_id
@@ -0,0 +1,28 @@
1
+ # Modules
2
+ # Standard
3
+ import time
4
+ from pathlib import Path
5
+
6
+ # Paths
7
+ eula_file = Path("eula.txt")
8
+
9
+
10
+ # Functions
11
+ def is_eula_agreed() -> bool:
12
+ try:
13
+ for line in eula_file.read_text().split("\n"):
14
+ if line.startswith("eula=true"):
15
+ return True
16
+ except FileNotFoundError:
17
+ pass
18
+
19
+ return False
20
+
21
+
22
+ def eula_agree() -> None:
23
+ timestamp = time.strftime("%a %b %d %H:%M:%S GMT %Y", time.gmtime())
24
+ eula_file.write_text(
25
+ f"""#By changing the setting below to TRUE you are indicating your agreement to our EULA (https://aka.ms/MinecraftEULA).
26
+ #{timestamp}
27
+ eula=true"""
28
+ )
@@ -0,0 +1,69 @@
1
+ # Modules
2
+ # Standard
3
+ import json
4
+ from typing import TypedDict
5
+
6
+ # MCServer
7
+ from .. import networking
8
+ from ..constants import mojang_manifest_url
9
+
10
+
11
+ # TypedDict
12
+ class LatestVersion(TypedDict):
13
+ release_version: str # original: release
14
+ snapshot_version: str # original: snapshot
15
+
16
+
17
+ class GameVersion(TypedDict):
18
+ version_id: str # original: id
19
+ version_type: str # original: type
20
+ package_url: str # original: url
21
+ release_time: str # original: releaseTime
22
+ package_sha1: str # original: sha1
23
+ compliance_level: int # original: complianceLevel
24
+
25
+
26
+ class VersionManifest(TypedDict):
27
+ latest_game_version: LatestVersion # original: latest
28
+ game_versions: list[GameVersion] # original: versions
29
+
30
+
31
+ # Functions
32
+ def get_version_manifest() -> VersionManifest:
33
+ response = networking.request(
34
+ f"{mojang_manifest_url}/mc/game/version_manifest_v2.json"
35
+ )
36
+ response_json = json.loads(response["text"])
37
+
38
+ latest_version = response_json["latest"]
39
+
40
+ game_versions: list[GameVersion] = []
41
+ for version in response_json["versions"]:
42
+ game_version: GameVersion = {
43
+ "version_id": version["id"],
44
+ "version_type": version["type"],
45
+ "release_time": version["releaseTime"],
46
+ "package_url": version["url"],
47
+ "package_sha1": version["sha1"],
48
+ "compliance_level": version["complianceLevel"],
49
+ }
50
+
51
+ game_versions.append(game_version)
52
+
53
+ version_manifest: VersionManifest = {
54
+ "latest_game_version": {
55
+ "release_version": latest_version["release"],
56
+ "snapshot_version": latest_version["snapshot"],
57
+ },
58
+ "game_versions": game_versions,
59
+ }
60
+
61
+ return version_manifest
62
+
63
+
64
+ def get_latest_release_version() -> str:
65
+ return get_version_manifest()["latest_game_version"]["release_version"]
66
+
67
+
68
+ def get_latest_snapshot_version() -> str:
69
+ return get_version_manifest()["latest_game_version"]["snapshot_version"]
mcserver/mysterious.py ADDED
@@ -0,0 +1,5 @@
1
+ def nothing_to_see_here_move_along():
2
+ import sys
3
+
4
+ print("The cake is a lie.")
5
+ sys.exit(127)