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 +2 -0
- apsat_core/consts.py +13 -0
- apsat_core/download.py +136 -0
- apsat_core/exceptions.py +19 -0
- apsat_core/get_profile.py +158 -0
- apsat_core/get_profile_microsoft.py +87 -0
- apsat_core/get_profile_yggdrasil.py +93 -0
- apsat_core/types.py +94 -0
- apsat_core/version.py +31 -0
- apsat_core-1.0.0.dist-info/METADATA +39 -0
- apsat_core-1.0.0.dist-info/RECORD +13 -0
- apsat_core-1.0.0.dist-info/WHEEL +4 -0
- apsat_core-1.0.0.dist-info/licenses/LICENSE +674 -0
apsat_core/__init__.py
ADDED
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
|
apsat_core/exceptions.py
ADDED
|
@@ -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,,
|