Skip to content

Commit f901059

Browse files
committed
Moved mojang.api.session to mojang.account.session
1 parent 74cef82 commit f901059

File tree

5 files changed

+139
-0
lines changed

5 files changed

+139
-0
lines changed

mojang/account/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .base import status, get_uuid, get_uuids, names

mojang/account/_auth.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from requests.auth import AuthBase
2+
3+
class BearerAuth(AuthBase):
4+
5+
def __init__(self, token: str):
6+
self.__token = token
7+
8+
def __call__(self, r):
9+
r.headers['Authorization'] = 'Bearer {}'.format(self.__token)
10+
return r

mojang/account/_structures.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import validators
2+
import re
3+
import requests
4+
from os import path
15
import datetime as dt
26
from typing import NamedTuple, Tuple, Union
37

@@ -39,3 +43,79 @@ def first(self) -> Union[None, NameInfo]:
3943
first = list(filter(lambda n: n.changed_to_at == None, self))
4044
if len(first) > 0:
4145
return first[0]
46+
47+
## Session
48+
class NameChange(NamedTuple):
49+
allowed: bool
50+
created_at: dt.datetime
51+
52+
class _SkinCapeBase(NamedTuple):
53+
source: str
54+
variant: str = None
55+
56+
@classmethod
57+
def _filename_from_url(cls, url: str):
58+
url_path = urlparse(url).path
59+
match = re.match('^([\w,\s-]+)\.([A-Za-z]{3})$', path.basename(url_path))
60+
if match:
61+
return match.groups()
62+
63+
@classmethod
64+
def _filename_from_headers(cls, headers: dict):
65+
# Check content-disposition
66+
if 'content-disposition' in headers.keys():
67+
cdisp = headers['content-disposition']
68+
file_names = re.findall('filename=(.+)', cdisp)
69+
if len(file_names) > 0:
70+
return file_names[0][0], file_names[0][1][1:]
71+
72+
# Check content-type
73+
if 'content-type' in headers.keys():
74+
ctype = headers['content-type']
75+
if (not 'text' in ctype) and (not 'html' in ctype):
76+
return ctype.split('/')
77+
78+
@classmethod
79+
def _download_bytes(cls, url: str):
80+
response = requests.get(url)
81+
if response.ok:
82+
filename = cls._filename_from_headers(response.headers) or cls._filename_from_url(url) or ['download', None]
83+
return filename, response.content
84+
85+
@property
86+
def data(self):
87+
if not hasattr(self, '_data'):
88+
_data = b''
89+
_extension = None
90+
91+
if validators.url(self.source):
92+
response = self._download_bytes(self.source)
93+
if response:
94+
_extension = response[0][1]
95+
_data = response[1]
96+
elif path.exists(self.source):
97+
basename = path.basename(self.source)
98+
_extension = path.splitext(basename)[1][1:]
99+
100+
with open(self.source, 'rb') as fp:
101+
_data = fp.read()
102+
103+
if _extension != 'png':
104+
pass # TODO: Raise Exception
105+
106+
object.__setattr__(self, '_data', _data)
107+
108+
return self._data
109+
110+
def save(self, dest: str):
111+
if not dest.endswith('.png'):
112+
dest += '.png'
113+
114+
with open(dest, 'wb') as fp:
115+
fp.write(self.data)
116+
117+
class Skin(_SkinCapeBase):
118+
pass
119+
120+
class Cape(_SkinCapeBase):
121+
pass

mojang/account/_urls.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11

22
class URLs:
33

4+
# Base URLs
45
@classmethod
56
def status_check(cls):
67
return 'https://status.mojang.com/check'
@@ -16,3 +17,20 @@ def uuids(cls):
1617
@classmethod
1718
def name_history(cls, uuid: str):
1819
return f'https://api.mojang.com/user/profiles/{uuid}/names'
20+
21+
# Session URLs
22+
@classmethod
23+
def name_change(cls):
24+
return 'https://api.minecraftservices.com/minecraft/profile/namechange'
25+
26+
@classmethod
27+
def change_name(cls, name: str):
28+
return f'https://api.minecraftservices.com/minecraft/profile/name/{name}'
29+
30+
@classmethod
31+
def change_skin(cls):
32+
return 'https://api.minecraftservices.com/minecraft/profile/skins'
33+
34+
@classmethod
35+
def reset_skin(cls, uuid: str):
36+
return f'https://api.mojang.com/user/profile/{uuid}/skin'

mojang/account/session.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import requests
2+
3+
from ._urls import URLs
4+
from ._structures import NameChange, Skin
5+
from ._auth import BearerAuth
6+
7+
# TODO: Handle errors and exception
8+
9+
def get_user_name_change(access_token: str):
10+
response = requests.get(URLs.name_change(), auth=BearerAuth(access_token))
11+
12+
data = response.json()
13+
data['created_at'] = dt.datetime.strptime(data.pop('createdAt'), '%Y-%m-%dT%H:%M:%SZ')
14+
data['allowed'] = data.pop('nameChangeAllowed')
15+
16+
return NameChange(**data)
17+
18+
def change_user_name(access_token: str, name: str):
19+
response = requests.put(URLs.change_name(name), auth=BearerAuth(access_token))
20+
21+
def change_user_skin(access_token: str, path: str, variant='classic'):
22+
skin = Skin(source=path, variant=variant)
23+
files = [
24+
('variant', skin.variant),
25+
('file', ('image.png', skin.data, f'image/png'))
26+
]
27+
response = requests.post(URLs.change_skin(), auth=BearerAuth(access_token), files=files, headers={'content-type': None})
28+
29+
def reset_user_skin(access_token: str, uuid: str):
30+
response = requests.delete(URLs.reset_skin(uuid), auth=BearerAuth(access_token))

0 commit comments

Comments
 (0)