Skip to content

Commit

Permalink
Moved mojang.api.session to mojang.account.session
Browse files Browse the repository at this point in the history
  • Loading branch information
Lucino772 committed Apr 27, 2021
1 parent 74cef82 commit f901059
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 0 deletions.
1 change: 1 addition & 0 deletions mojang/account/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .base import status, get_uuid, get_uuids, names
10 changes: 10 additions & 0 deletions mojang/account/_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from requests.auth import AuthBase

class BearerAuth(AuthBase):

def __init__(self, token: str):
self.__token = token

def __call__(self, r):
r.headers['Authorization'] = 'Bearer {}'.format(self.__token)
return r
80 changes: 80 additions & 0 deletions mojang/account/_structures.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import validators
import re
import requests
from os import path
import datetime as dt
from typing import NamedTuple, Tuple, Union

Expand Down Expand Up @@ -39,3 +43,79 @@ def first(self) -> Union[None, NameInfo]:
first = list(filter(lambda n: n.changed_to_at == None, self))
if len(first) > 0:
return first[0]

## Session
class NameChange(NamedTuple):
allowed: bool
created_at: dt.datetime

class _SkinCapeBase(NamedTuple):
source: str
variant: str = None

@classmethod
def _filename_from_url(cls, url: str):
url_path = urlparse(url).path
match = re.match('^([\w,\s-]+)\.([A-Za-z]{3})$', path.basename(url_path))
if match:
return match.groups()

@classmethod
def _filename_from_headers(cls, headers: dict):
# Check content-disposition
if 'content-disposition' in headers.keys():
cdisp = headers['content-disposition']
file_names = re.findall('filename=(.+)', cdisp)
if len(file_names) > 0:
return file_names[0][0], file_names[0][1][1:]

# Check content-type
if 'content-type' in headers.keys():
ctype = headers['content-type']
if (not 'text' in ctype) and (not 'html' in ctype):
return ctype.split('/')

@classmethod
def _download_bytes(cls, url: str):
response = requests.get(url)
if response.ok:
filename = cls._filename_from_headers(response.headers) or cls._filename_from_url(url) or ['download', None]
return filename, response.content

@property
def data(self):
if not hasattr(self, '_data'):
_data = b''
_extension = None

if validators.url(self.source):
response = self._download_bytes(self.source)
if response:
_extension = response[0][1]
_data = response[1]
elif path.exists(self.source):
basename = path.basename(self.source)
_extension = path.splitext(basename)[1][1:]

with open(self.source, 'rb') as fp:
_data = fp.read()

if _extension != 'png':
pass # TODO: Raise Exception

object.__setattr__(self, '_data', _data)

return self._data

def save(self, dest: str):
if not dest.endswith('.png'):
dest += '.png'

with open(dest, 'wb') as fp:
fp.write(self.data)

class Skin(_SkinCapeBase):
pass

class Cape(_SkinCapeBase):
pass
18 changes: 18 additions & 0 deletions mojang/account/_urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

class URLs:

# Base URLs
@classmethod
def status_check(cls):
return 'https://status.mojang.com/check'
Expand All @@ -16,3 +17,20 @@ def uuids(cls):
@classmethod
def name_history(cls, uuid: str):
return f'https://api.mojang.com/user/profiles/{uuid}/names'

# Session URLs
@classmethod
def name_change(cls):
return 'https://api.minecraftservices.com/minecraft/profile/namechange'

@classmethod
def change_name(cls, name: str):
return f'https://api.minecraftservices.com/minecraft/profile/name/{name}'

@classmethod
def change_skin(cls):
return 'https://api.minecraftservices.com/minecraft/profile/skins'

@classmethod
def reset_skin(cls, uuid: str):
return f'https://api.mojang.com/user/profile/{uuid}/skin'
30 changes: 30 additions & 0 deletions mojang/account/session.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import requests

from ._urls import URLs
from ._structures import NameChange, Skin
from ._auth import BearerAuth

# TODO: Handle errors and exception

def get_user_name_change(access_token: str):
response = requests.get(URLs.name_change(), auth=BearerAuth(access_token))

data = response.json()
data['created_at'] = dt.datetime.strptime(data.pop('createdAt'), '%Y-%m-%dT%H:%M:%SZ')
data['allowed'] = data.pop('nameChangeAllowed')

return NameChange(**data)

def change_user_name(access_token: str, name: str):
response = requests.put(URLs.change_name(name), auth=BearerAuth(access_token))

def change_user_skin(access_token: str, path: str, variant='classic'):
skin = Skin(source=path, variant=variant)
files = [
('variant', skin.variant),
('file', ('image.png', skin.data, f'image/png'))
]
response = requests.post(URLs.change_skin(), auth=BearerAuth(access_token), files=files, headers={'content-type': None})

def reset_user_skin(access_token: str, uuid: str):
response = requests.delete(URLs.reset_skin(uuid), auth=BearerAuth(access_token))

0 comments on commit f901059

Please sign in to comment.