apsat-core 1.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.
apsat_core/__init__.py ADDED
@@ -0,0 +1,2 @@
1
+ def main() -> None:
2
+ print("Hello from apsat-core!")
apsat_core/consts.py ADDED
@@ -0,0 +1,13 @@
1
+ # Apsat Copyright (C) 2026 Deswatts<Deswatts_Cre@outlook.com>
2
+ # This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
3
+ # This is free software, and you are welcome to redistribute it
4
+ # under certain conditions; type `show c' for details.
5
+
6
+ __all__ = ["TYPE_CAPE", "TYPE_MICROSOFT", "TYPE_PROFILE", "TYPE_SKIN", "TYPE_YGGDRASIL"]
7
+
8
+
9
+ TYPE_YGGDRASIL = int("00010", 2) # 2
10
+ TYPE_MICROSOFT = int("00001", 2) # 1
11
+ TYPE_SKIN = int("00100", 2) # 4
12
+ TYPE_CAPE = int("01000", 2) # 8
13
+ TYPE_PROFILE = int("10000", 2) # 16
apsat_core/download.py ADDED
@@ -0,0 +1,136 @@
1
+ # Apsat Copyright (C) 2026 Deswatts<Deswatts_Cre@outlook.com>
2
+ # This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
3
+ # This is free software, and you are welcome to redistribute it
4
+ # under certain conditions; type `show c' for details.
5
+
6
+
7
+ import hashlib
8
+ import math
9
+ import os
10
+ import threading
11
+
12
+ import requests
13
+
14
+ from deswatts_tools import logutil
15
+
16
+ logger = logutil.setup_logger(__name__)
17
+
18
+
19
+ class DownloadEngine:
20
+ def __init__(self, download_list: list, download_thread: int = 8):
21
+ """
22
+ Multi thread download engine
23
+
24
+ :param download_list: Download task list
25
+ :param download_thread: Max thread count
26
+ :raises ValueError: When you call some unsupport hash type(exam: md5)
27
+
28
+ Param download_list example:
29
+
30
+ .. code:: json
31
+
32
+ [
33
+ {
34
+ "filename": "example.png",
35
+ "url": "https://example.com/example.png",
36
+ "sha256": "add0613512f1b7f485f3fe445ae0bfcd344293dbea23e4bf2f999e0f97b80176"
37
+ },
38
+ ...
39
+ ]
40
+ """
41
+ super().__init__()
42
+
43
+ if not download_list:
44
+ raise ValueError("The param download_list is Empty")
45
+
46
+ download_list_tmp = download_list
47
+ downloads = []
48
+ for i in range(math.ceil(len(download_list) / download_thread)):
49
+ if len(download_list_tmp) == 0:
50
+ break
51
+
52
+ downloads.append(download_list_tmp[0:download_thread])
53
+ for j in range(download_thread):
54
+ try:
55
+ download_list_tmp.pop(0)
56
+ except IndexError:
57
+ break
58
+
59
+ threads = []
60
+ for i in range(len(downloads)):
61
+ threads.append(
62
+ threading.Thread(
63
+ target=self.download_unit, name=f"Download-{i}", args=[downloads[i]]
64
+ )
65
+ )
66
+ for i in threads:
67
+ i.start()
68
+ for i in threads:
69
+ i.join()
70
+
71
+ @staticmethod
72
+ def download_unit(download_list: list):
73
+ for i in download_list:
74
+ if "sha256" in i:
75
+ check = DownloadEngine.check_sha256(i)
76
+ elif "sha1" in i:
77
+ check = DownloadEngine.check_sha1(i)
78
+ else:
79
+ raise ValueError("Unsupport hash type")
80
+
81
+ url = i["url"]
82
+ filename = i["file_name"] if "file_name" in i else i["filename"]
83
+
84
+ if not check:
85
+ with requests.get(url, stream=True) as r:
86
+ r.raise_for_status()
87
+ with open(filename, "wb") as f:
88
+ f.writelines(r.iter_content(chunk_size=4096))
89
+
90
+ logger.info(f"Downloaded: {os.path.abspath(filename)}")
91
+ else:
92
+ logger.info(f"{os.path.abspath(filename)} is exists")
93
+
94
+ @staticmethod
95
+ def check_sha256(task: dict[str, str]):
96
+ filename = task["file_name"] if "file_name" in task else task["filename"]
97
+ target_sha256 = task["sha256"]
98
+
99
+ if not os.path.exists(filename):
100
+ return False
101
+ if filename is os.PathLike:
102
+ filename = str(filename)
103
+
104
+ file_hash = hashlib.sha256()
105
+
106
+ with open(filename, "rb+") as f:
107
+ while True:
108
+ tmp = f.read(4096)
109
+ if tmp == b"":
110
+ break
111
+
112
+ file_hash.update(tmp)
113
+
114
+ return file_hash.hexdigest() == target_sha256
115
+
116
+ @staticmethod
117
+ def check_sha1(task: dict[str, str]):
118
+ filename = task["file_name"]
119
+ target_sha1 = task["sha1"]
120
+
121
+ if not os.path.exists(filename):
122
+ return False
123
+ if filename is os.PathLike:
124
+ filename = str(filename)
125
+
126
+ file_hash = hashlib.sha1()
127
+
128
+ with open(filename, "rb+") as f:
129
+ while True:
130
+ tmp = f.read(4096)
131
+ if tmp == b"":
132
+ break
133
+
134
+ file_hash.update(tmp)
135
+
136
+ return file_hash.hexdigest() == target_sha1
@@ -0,0 +1,19 @@
1
+ # Apsat Copyright (C) 2026 Deswatts<Deswatts_Cre@outlook.com>
2
+ # This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
3
+ # This is free software, and you are welcome to redistribute it
4
+ # under certain conditions; type `show c' for details.
5
+
6
+
7
+ class ProfileException(Exception):
8
+ def __init__(self, *args: object):
9
+ super().__init__(*args)
10
+
11
+
12
+ class ProfileHasNotCustomSkin(ProfileException):
13
+ def __init__(self):
14
+ super().__init__("The Profile dose not have any custom skin!")
15
+
16
+
17
+ class ProfileHasNotCustomCape(ProfileException):
18
+ def __init__(self):
19
+ super().__init__("The Profile dose not have any custom cape!")
@@ -0,0 +1,158 @@
1
+ # Apsat Copyright (C) 2026 Deswatts<Deswatts_Cre@outlook.com>
2
+ # This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
3
+ # This is free software, and you are welcome to redistribute it
4
+ # under certain conditions; type `show c' for details.
5
+
6
+
7
+ import os.path
8
+ import re
9
+
10
+ import requests
11
+ import urllib3
12
+
13
+ from apsat_core import consts, get_profile_microsoft, get_profile_yggdrasil
14
+ from apsat_core.exceptions import ProfileHasNotCustomCape, ProfileHasNotCustomSkin
15
+ from apsat_core.types import (
16
+ NameList,
17
+ ProfileList,
18
+ ProfileType,
19
+ TextureUrlList,
20
+ )
21
+ from deswatts_tools import logutil
22
+
23
+ logger = logutil.setup_logger(__name__)
24
+ check_url_pattern = re.compile(r"http(s?)://")
25
+
26
+
27
+ def _yggdrasil_profile(url: str, names: NameList):
28
+ return get_profile_yggdrasil.get_profile_yggdrasil(names, url)
29
+
30
+
31
+ def _microsoft_profile(names: NameList):
32
+ return get_profile_microsoft.get_profile_microsoft(names)
33
+
34
+
35
+ def _get_textures(profiles: ProfileList):
36
+ return get_profile_microsoft.get_skin(profiles)
37
+
38
+
39
+ def resolve_textures(
40
+ textures: TextureUrlList,
41
+ type: ProfileType | int,
42
+ download_dir: str | os.PathLike[str] | os.PathLike[bytes],
43
+ ):
44
+ if type is ProfileType:
45
+ type = int(type.type, 2)
46
+ if download_dir is os.PathLike:
47
+ download_dir = str(download_dir)
48
+ result = []
49
+ for i in textures:
50
+ if type & consts.TYPE_SKIN == consts.TYPE_SKIN:
51
+ if i["skin_url"] is None:
52
+ raise ProfileHasNotCustomSkin
53
+
54
+ result.append(
55
+ {
56
+ "filename": os.path.join(
57
+ download_dir, i["profile_name"] + "-skin.png"
58
+ ),
59
+ "url": i["skin_url"]["url"],
60
+ "sha256": os.path.basename(
61
+ urllib3.util.url.parse_url(i["skin_url"]["url"]).path
62
+ ),
63
+ }
64
+ )
65
+ elif type & consts.TYPE_CAPE == consts.TYPE_CAPE:
66
+ if i["cape_url"] is None:
67
+ raise ProfileHasNotCustomCape
68
+
69
+ result.append(
70
+ {
71
+ "filename": os.path.join(
72
+ download_dir, i["profile_name"] + "-cape.png"
73
+ ),
74
+ "url": i["cape_url"]["url"],
75
+ "sha256": os.path.basename(
76
+ urllib3.util.url.parse_url(i["cape_url"]["url"]).path
77
+ ),
78
+ }
79
+ )
80
+
81
+ try:
82
+ os.mkdir(download_dir)
83
+ except FileExistsError:
84
+ pass
85
+
86
+ logger.info(f"Resolve the textures: {result}")
87
+
88
+ return result
89
+
90
+
91
+ def _to_absolute_url(url: str, base: str):
92
+ """
93
+ This funtion returns absolute url
94
+ :param url: target url
95
+ :param base: base url
96
+ :return: absolute url
97
+ """
98
+
99
+ if re.match(check_url_pattern, url) is not None:
100
+ return url
101
+ if url[0] == '/' and base[-1] == '/':
102
+ base = base[1:]
103
+ elif url[0] != '/' and base[-1] != '/':
104
+ url += '/'
105
+ return base + url
106
+
107
+
108
+ def _resolve_api_url(source_url: str):
109
+ """
110
+ This function returns real api url
111
+ :param source_url: url from user input
112
+ :return: api url
113
+ """
114
+
115
+ check_url = re.match(check_url_pattern, source_url)
116
+ if check_url is None:
117
+ source_url = "https://" + source_url
118
+
119
+ response = requests.get(source_url)
120
+ if "x-authlib-injector-api-location" in response.headers:
121
+ new_url = _to_absolute_url(response.headers["x-authlib-injector-api-location"], source_url)
122
+ if new_url != source_url:
123
+ return new_url
124
+
125
+ return source_url
126
+
127
+
128
+ def get_profile(
129
+ mode: ProfileType | int, names: NameList, url: str | None = None
130
+ ) -> TextureUrlList | ProfileList | None:
131
+ if mode is ProfileType:
132
+ mode = int(mode.type, 2)
133
+
134
+ if (url is None) and (mode & consts.TYPE_YGGDRASIL == consts.TYPE_YGGDRASIL):
135
+ raise ValueError("Missing url")
136
+
137
+ if mode & consts.TYPE_YGGDRASIL == consts.TYPE_YGGDRASIL:
138
+ url = _resolve_api_url(url)
139
+
140
+ profiles = (
141
+ _yggdrasil_profile(url, names)
142
+ if mode & consts.TYPE_YGGDRASIL == consts.TYPE_YGGDRASIL
143
+ else _microsoft_profile(names)
144
+ )
145
+
146
+ logger.info(f"Got profiles: {profiles}")
147
+
148
+ if mode & consts.TYPE_PROFILE == consts.TYPE_PROFILE:
149
+ textures = profiles
150
+ else:
151
+ textures = _get_textures(profiles)
152
+ logger.info(f"Got textures: {textures}")
153
+
154
+ return textures
155
+
156
+
157
+ if __name__ == '__main__':
158
+ print(get_profile(1, NameList(["a"]), ""))
@@ -0,0 +1,87 @@
1
+ # Apsat Copyright (C) 2026 Deswatts<Deswatts_Cre@outlook.com>
2
+ # This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
3
+ # This is free software, and you are welcome to redistribute it
4
+ # under certain conditions; type `show c' for details.
5
+
6
+
7
+ import json
8
+ import re
9
+ from base64 import b64decode
10
+
11
+ import requests
12
+
13
+ from apsat_core.types import (
14
+ NameList,
15
+ ProfileList,
16
+ TextureList,
17
+ TextureUrlList,
18
+ TextureUrls,
19
+ Url,
20
+ UUIDList,
21
+ )
22
+
23
+ _get_uuids_url = "https://api.mojang.com/profiles/minecraft"
24
+ _get_profiles_url = (
25
+ "https://sessionserver.mojang.com/session/minecraft/profile/<UUID>?unsigned=false"
26
+ )
27
+
28
+ _replace_get_profiles_url_pattern = re.compile(r"<UUID>")
29
+
30
+
31
+ def _get_uuids(profile_names: NameList) -> UUIDList:
32
+ request_data = profile_names
33
+
34
+ request_header = {"Content-Type": "application/json"}
35
+
36
+ r = requests.post(_get_uuids_url, json=request_data, headers=request_header)
37
+ return r.json()
38
+
39
+
40
+ def _get_profiles(uuids: UUIDList):
41
+ result = ProfileList()
42
+ for i in uuids:
43
+ url = re.sub(_replace_get_profiles_url_pattern, i["id"], _get_profiles_url)
44
+ r = requests.get(url)
45
+ result.append(r.json())
46
+ return result
47
+
48
+
49
+ def _resolve_profiles(profiles: ProfileList):
50
+ result = TextureList()
51
+ for i in profiles:
52
+ for j in i["properties"]:
53
+ texture = json.loads(b64decode(j["value"]))
54
+ result.append(texture)
55
+
56
+ return result
57
+
58
+
59
+ def _resolve_texture(textures: TextureList):
60
+ result = TextureUrlList()
61
+ for i in textures:
62
+ tmp_skin = None
63
+ tmp_cape = None
64
+ if "SKIN" in i["textures"]:
65
+ tmp_skin = Url(name="skin", url=i["textures"]["SKIN"]["url"])
66
+ if "CAPE" in i["textures"]:
67
+ tmp_cape = Url(name="cape", url=i["textures"]["CAPE"]["url"])
68
+
69
+ tmp = TextureUrls(
70
+ skin_url=tmp_skin, cape_url=tmp_cape, profile_name=i["profileName"]
71
+ )
72
+ result.append(tmp)
73
+ return result
74
+
75
+
76
+ def get_profile_microsoft(profile_names: NameList):
77
+ uuids = _get_uuids(profile_names)
78
+ profiles = _get_profiles(uuids)
79
+
80
+ return profiles
81
+
82
+
83
+ def get_skin(profiles: ProfileList):
84
+ textures = _resolve_profiles(profiles)
85
+ urls = _resolve_texture(textures)
86
+
87
+ return urls
@@ -0,0 +1,93 @@
1
+ # Apsat Copyright (C) 2026 Deswatts<Deswatts_Cre@outlook.com>
2
+ # This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
3
+ # This is free software, and you are welcome to redistribute it
4
+ # under certain conditions; type `show c' for details.
5
+
6
+
7
+ import json
8
+ import re
9
+ from base64 import b64decode
10
+
11
+ import requests
12
+
13
+ from apsat_core.types import (
14
+ NameList,
15
+ ProfileList,
16
+ TextureList,
17
+ TextureUrlList,
18
+ TextureUrls,
19
+ Url,
20
+ UUIDList,
21
+ )
22
+
23
+ _get_uuids_url = "<URL>/api/profiles/minecraft"
24
+ _get_profiles_url = (
25
+ "<URL>/sessionserver/session/minecraft/profile/<UUID>?unsigned=false"
26
+ )
27
+
28
+ _replace_get_profiles_url_pattern_uuid = re.compile(r"<UUID>")
29
+ _replace_get_profiles_url_pattern_url = re.compile(r"<URL>")
30
+
31
+
32
+ def _get_uuids(profile_names: NameList, url: str) -> UUIDList:
33
+ request_data = profile_names
34
+
35
+ request_header = {"Content-Type": "application/json"}
36
+
37
+ url = re.sub(_replace_get_profiles_url_pattern_url, url, _get_uuids_url)
38
+
39
+ r = requests.post(url, json=request_data, headers=request_header)
40
+ return r.json()
41
+
42
+
43
+ def _get_profiles(uuids: UUIDList, url: str):
44
+ result = ProfileList()
45
+ for i in uuids:
46
+ finall_url = re.sub(
47
+ _replace_get_profiles_url_pattern_url, url, _get_profiles_url
48
+ )
49
+ finall_url = re.sub(_replace_get_profiles_url_pattern_uuid, i["id"], finall_url)
50
+ r = requests.get(finall_url)
51
+ result.append(r.json())
52
+ return result
53
+
54
+
55
+ def _resolve_profiles(profiles: ProfileList):
56
+ result = TextureList()
57
+ for i in profiles:
58
+ for j in i["properties"]:
59
+ texture = json.loads(b64decode(j["value"]))
60
+ result.append(texture)
61
+
62
+ return result
63
+
64
+
65
+ def _resolve_texture(textures: TextureList):
66
+ result = TextureUrlList()
67
+ for i in textures:
68
+ tmp_skin = None
69
+ tmp_cape = None
70
+ if "SKIN" in i["textures"]:
71
+ tmp_skin = Url(name="skin", url=i["textures"]["SKIN"]["url"])
72
+ if "CAPE" in i["textures"]:
73
+ tmp_cape = Url(name="cape", url=i["textures"]["CAPE"]["url"])
74
+
75
+ tmp = TextureUrls(
76
+ skin_url=tmp_skin, cape_url=tmp_cape, profile_name=i["profileName"]
77
+ )
78
+ result.append(tmp)
79
+ return result
80
+
81
+
82
+ def get_profile_yggdrasil(profile_names: NameList, url: str):
83
+ uuids = _get_uuids(profile_names, url)
84
+ profiles = _get_profiles(uuids, url)
85
+
86
+ return profiles
87
+
88
+
89
+ def get_skin(profiles: ProfileList):
90
+ textures = _resolve_profiles(profiles)
91
+ urls = _resolve_texture(textures)
92
+
93
+ return urls
apsat_core/types.py ADDED
@@ -0,0 +1,94 @@
1
+ # Apsat Copyright (C) 2026 Deswatts<Deswatts_Cre@outlook.com>
2
+ # This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
3
+ # This is free software, and you are welcome to redistribute it
4
+ # under certain conditions; type `show c' for details.
5
+
6
+
7
+ from typing import TypedDict
8
+
9
+
10
+ class SkinMetadata(TypedDict):
11
+ model: str | None
12
+
13
+
14
+ class SkinObject(TypedDict):
15
+ url: str
16
+ model: SkinMetadata | None
17
+
18
+
19
+ class CapeObject(TypedDict):
20
+ url: str
21
+
22
+
23
+ class TextureDict(TypedDict):
24
+ SKIN: SkinObject | None
25
+ CAPE: CapeObject | None
26
+
27
+
28
+ class TextureObject(TypedDict):
29
+ timestamp: int
30
+ profileId: str
31
+ profileName: str
32
+ signatureRequired: bool | None
33
+ textures: TextureDict
34
+
35
+
36
+ class ProfileProperties(list):
37
+ name: str
38
+ signature: str | None
39
+ value: str
40
+
41
+
42
+ class Profile(TypedDict):
43
+ id: str
44
+ name: str
45
+ legacy: bool | None
46
+ properties: ProfileProperties
47
+
48
+
49
+ class Url(TypedDict):
50
+ name: str
51
+ url: str
52
+
53
+
54
+ class UUIDList(list):
55
+ uuids: str
56
+
57
+
58
+ class NameList(list):
59
+ names: str
60
+
61
+
62
+ class ProfileList(list):
63
+ profiles: Profile
64
+
65
+
66
+ class TextureList(list):
67
+ texture_objects: TextureObject
68
+
69
+
70
+ class TextureUrls(TypedDict):
71
+ skin_url: Url | None
72
+ cape_url: Url | None
73
+ profile_name: str
74
+
75
+
76
+ class TextureUrlList(list):
77
+ urls: TextureUrls
78
+
79
+
80
+ class ProfileType(int):
81
+ type: str
82
+
83
+ def __new__(cls, mode):
84
+ cls.type = cls.to_binary(mode)
85
+ cls.type = "0" * (5 - len(cls.type)) + cls.type
86
+ return cls
87
+
88
+ @staticmethod
89
+ def to_binary(mode: str):
90
+ if int(mode) // 2 == 0:
91
+ return str(int(mode) % 2)
92
+ else:
93
+ tmp = ProfileType.to_binary(str(int(mode) // 2))
94
+ return tmp + str(int(mode) % 2)
apsat_core/version.py ADDED
@@ -0,0 +1,31 @@
1
+ # Apsat Copyright (C) 2026 Deswatts<Deswatts_Cre@outlook.com>
2
+ # This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
3
+ # This is free software, and you are welcome to redistribute it
4
+ # under certain conditions; type `show c' for details.
5
+
6
+
7
+ __version__ = "1.0.0"
8
+ __title__ = "Apsat"
9
+ __description__ = "This Package is core of the Apsat"
10
+ __license__ = "GNU GPLv3"
11
+ __author__ = "Deswatts<Deswatts_Cre@outlook.com>"
12
+ __copyright__ = "DeswattsTools Copyright (C) 2026 Deswatts<Deswatts_Cre@outlook.com>"
13
+
14
+
15
+ def get_version() -> dict[str, str]:
16
+ """
17
+ 此函数用于获取包版本
18
+ :return: 包版本号
19
+ """
20
+ version_digital = ""
21
+ for i in __version__.split("."):
22
+ version_digital += i
23
+ return {"source": __version__, "digital": version_digital}
24
+
25
+
26
+ def get_name() -> str:
27
+ """
28
+ 此函数用于获取包名
29
+ :return: 包名
30
+ """
31
+ return __title__
@@ -0,0 +1,39 @@
1
+ Metadata-Version: 2.4
2
+ Name: apsat-core
3
+ Version: 1.0.0
4
+ Summary: Core of Apsat
5
+ Keywords: Apsat,core
6
+ Author: Deswatts
7
+ Author-email: Deswatts <Deswatts_Cre@outlook.com>
8
+ License-Expression: GPL-3.0-or-later
9
+ License-File: LICENSE
10
+ Requires-Dist: deswatts-tools>=1.0.0
11
+ Requires-Dist: requests
12
+ Maintainer: Deswatts
13
+ Maintainer-email: Deswatts <Deswatts_Cre@outlook.com>
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+
17
+ ## 简介
18
+
19
+ - 这是 [Apsat](https://github.com/Deswatts/Apsat) 的核心库
20
+ - 包含了对 Minecraft 账户的一部分功能
21
+
22
+ ## 示例
23
+
24
+ ```python
25
+ # This is a example for get profile from microsoft
26
+ from apsat_core import consts, types, get_profile # Import modules
27
+
28
+ user_name = "" # Input the profile name
29
+
30
+
31
+ if __name__ == '__main__':
32
+ profile = get_profile.getprofile(
33
+ types.ProfileType(
34
+ consts.TYPE_PROFILE | consts.TYPE_MICROSOFT
35
+ ),
36
+ user_name
37
+ ) # Get profile
38
+ print(profile) # Print result to console
39
+ ```
@@ -0,0 +1,13 @@
1
+ apsat_core/__init__.py,sha256=WxlM_-bGxNgf21CZKHTe3NId-7cFJ1pDV4e7x_fqq2Q,56
2
+ apsat_core/consts.py,sha256=ORG6RJomFhKvp50wsfvIVUBuhR67tso_ew945WfaieU,531
3
+ apsat_core/download.py,sha256=w2uX3p4h9uAQpA8V_pLPnjocuNYkvxHUHKlgc-GfNKY,4029
4
+ apsat_core/exceptions.py,sha256=SklkCj6dE3Ip0m9C-pU33b1b76a16RlFfvQNXNMW1vI,660
5
+ apsat_core/get_profile.py,sha256=YJ1Qwhz9mKsFihiSjSwD3y5nT9oMEu3mhuvvgZ5pXNI,4503
6
+ apsat_core/get_profile_microsoft.py,sha256=T5L93bIr8VSnoc5RkFn3OHiK9dUfKoWF6KhIYKO8bEw,2261
7
+ apsat_core/get_profile_yggdrasil.py,sha256=d_rBd5IJhPXBLM95IhSc_CxJCJYbt4GD5avkmxTgwhs,2533
8
+ apsat_core/types.py,sha256=kmRrrIDcwn_FtUwSqvhnmVqOBbMfwfZZSyueYHRq604,1724
9
+ apsat_core/version.py,sha256=XYFm6g8oiZeu7MnrGyXWDTMiiPC_HfYX19CzFiwIedw,906
10
+ apsat_core-1.0.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
11
+ apsat_core-1.0.0.dist-info/WHEEL,sha256=-i9oRNYVXXZJUIYl5zclLIg6onEb0NLibTX34uln84w,81
12
+ apsat_core-1.0.0.dist-info/METADATA,sha256=wOMlUl_BrEtGQ2v7YyIc8zJUQBB9FvoVFxBjoEG4QCM,1003
13
+ apsat_core-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.12.13
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any