Skip to content

Commit

Permalink
Moved UserProfile to mojang.account._structures and UserSession to mo…
Browse files Browse the repository at this point in the history
…jang.account._session
  • Loading branch information
Lucino772 committed May 6, 2021
1 parent 6e61e1c commit e27c570
Show file tree
Hide file tree
Showing 12 changed files with 192 additions and 83 deletions.
3 changes: 1 addition & 2 deletions mojang/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,4 @@
'http://textures.minecraft.net/texture/292009a4925b58f02c77dadc3ecef07ea4c7472f64e0fdc32ce5522489362680'
>>> ...
"""
from .account import get_uuid, get_uuids, names, status
from .classes import user, connect
from .account import get_uuid, get_uuids, names, status, user, connect
3 changes: 2 additions & 1 deletion mojang/account/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from .base import status, get_uuid, get_uuids, names
from .base import status, get_uuid, get_uuids, names, user
from ._session import connect
14 changes: 9 additions & 5 deletions mojang/classes/session.py → mojang/account/_session.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import datetime as dt
from dataclasses import dataclass, field

from ..account import session
from ..account.auth import security, yggdrasil
from .profile import UserProfile
from . import session, user
from ._structures import UserProfile
from .auth import security, yggdrasil


def connect(username: str, password: str, client_token: str = None):
auth = yggdrasil.authenticate(username, password, client_token)
return UserSession(auth.access_token, auth.client_token)


@dataclass(init=False)
class UserSession(UserProfile):
created_at: dt.datetime = field()
Expand Down Expand Up @@ -38,7 +40,7 @@ def refresh(self):

def _fetch_data(self):
# Load profile
profile = super().create(self.uuid)
profile = user(self.uuid)
self.names = profile.names
self.skin = profile.skin
self.cape = profile.cape
Expand All @@ -54,6 +56,9 @@ def close(self):
self.__access_token = None
self.__client_token = None

@property
def token_pair(self):
return self.__access_token, self.__client_token

# Security
@property
Expand All @@ -67,7 +72,6 @@ def challenges(self):
def verify(self, answers: list):
return security.verify_ip(self.__access_token, answers)


# Name
def change_name(self, name: str):
session.change_user_name(self.__access_token, name)
Expand Down
22 changes: 18 additions & 4 deletions mojang/account/_structures.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import validators
import datetime as dt
import re
import requests
from dataclasses import dataclass, field
from os import path
import datetime as dt
from typing import NamedTuple, Tuple, Union
from typing import List, NamedTuple, Tuple, Union

import requests
import validators


# Status check
class ServiceStatus(NamedTuple):
Expand Down Expand Up @@ -133,3 +136,14 @@ class AuthenticationInfo(NamedTuple):
class ChallengeInfo(NamedTuple):
id: int
challenge: str

## Profile
@dataclass
class UserProfile:
name: str = field()
uuid: str = field()
is_legacy: bool = field()
is_demo: bool = field()
names: NameInfoList = field()
skin: Skin = field()
cape: Cape = field()
4 changes: 4 additions & 0 deletions mojang/account/_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ def uuids(cls):
def name_history(cls, uuid: str):
return f'https://api.mojang.com/user/profiles/{uuid}/names'

@classmethod
def profile(cls, uuid: str):
return f'https://sessionserver.mojang.com/session/minecraft/profile/{uuid}'

# Session URLs
@classmethod
def name_change(cls):
Expand Down
19 changes: 16 additions & 3 deletions mojang/account/auth/security.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,36 @@
import requests

from ._urls import URLs
from ...exceptions import *
from .._auth import BearerAuth
from .._structures import ChallengeInfo
from ._urls import URLs

# TODO: Handle errors and exception

def check_ip(access_token: str):
response = requests.get(URLs.verify_ip(), auth=BearerAuth(access_token))
try:
handle_response(response, PayloadError, Unauthorized, IPNotSecured)
except IPNotSecured:
return False
else:
return True

def get_challenges(access_token: str):
response = requests.get(URLs.get_challenges(), auth=BearerAuth(access_token))
data = handle_response(response, PayloadError, Unauthorized)

_challenges = []
for item in response.json():
for item in data:
_challenges.append(ChallengeInfo(id=item['answer']['id'], challenge=item['question']['question']))

return _challenges

def verify_ip(access_token: str, answers: list):
answers = map(lambda a: {'id': a[0], 'answer': a[1]}, answers)
response = requests.post(URLs.verify_ip(), auth=BearerAuth(access_token), json=answers)
try:
handle_response(response, PayloadError, Unauthorized, IPVerificationError)
except IPVerificationError:
return False
else:
return True
11 changes: 8 additions & 3 deletions mojang/account/auth/yggdrasil.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import requests

from ._urls import URLs
from ...exceptions import *
from .._structures import AuthenticationInfo
from ._urls import URLs


def authenticate(username: str, password: str, client_token: str = None):
payload = {
Expand All @@ -14,8 +16,8 @@ def authenticate(username: str, password: str, client_token: str = None):
}
}
response = requests.post(URLs.authenticate(), json=payload)
data = handle_response(response, PayloadError, CredentialsError)

data = response.json()
_dict = {
'access_token': data['accessToken'],
'client_token': data['clientToken'],
Expand All @@ -32,8 +34,8 @@ def refresh(access_token: str, client_token: str):
'clientToken': client_token
}
response = requests.post(URLs.refresh(), json=payload)
data = handle_response(response, PayloadError, TokenError)

data = response.json()
_dict = {
'access_token': data['accessToken'],
'client_token': data['clientToken'],
Expand All @@ -50,17 +52,20 @@ def validate(access_token: str, client_token: str):
'clientToken': client_token
}
response = requests.post(URLs.validate(), json=payload)
handle_response(response, PayloadError, TokenError)

def signout(username: str, password: str):
payload = {
'username': username,
'password': password
}
response = requests.post(URLs.signout(), json=payload)
handle_response(response, PayloadError, CredentialsError)

def invalidate(access_token: str, client_token: str):
payload = {
'accessToken': access_token,
'clientToken': client_token
}
response = requests.post(URLs.invalidate(), json=payload)
data = handle_response(response, PayloadError, TokenError)
46 changes: 40 additions & 6 deletions mojang/account/base.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
import base64
import json
from dataclasses import fields

import requests

from ._urls import URLs
from ..exceptions import handle_response
from ._structures import *
from ._urls import URLs

# TODO: Handle errors and exception

def status():
response = requests.get(URLs.status_check())
data = handle_response(response)

_status = []
for service in response.json():
for service in data:
item = list(service.items())[0]
_status.append(ServiceStatus(name=item[0], status=item[1]))

return StatusCheck(_status)

def get_uuid(username: str):
response = requests.get(URLs.uuid(username))
data = handle_response(response)

data = response.json()
data['uuid'] = data.pop('id')

return UUIDInfo(**data)
Expand All @@ -29,8 +34,9 @@ def get_uuids(usernames: list):

for i in range(0, len(usernames), 10):
response = requests.post(URLs.uuids(), json=usernames[i:i+10])
data = handle_response(response)

for item in response.json():
for item in data:
index = usernames.index(item['name'].lower())
item['uuid'] = item.pop('id')
_uuids[index] = UUIDInfo(**item)
Expand All @@ -39,12 +45,40 @@ def get_uuids(usernames: list):

def names(uuid: str):
response = requests.get(URLs.name_history(uuid))
data = handle_response(response)

_names = []
for item in response.json():
for item in data:
changed_to_at = None
if 'changedToAt' in item.keys():
changed_to_at = dt.datetime.fromtimestamp(item['changedToAt'] / 1000)
_names.append(NameInfo(name=item['name'], changed_to_at=changed_to_at))

return NameInfoList(_names)

def user(uuid: str):
response = requests.get(URLs.profile(uuid))
data = handle_response(response)
_dict = dict.fromkeys([f.name for f in fields(UserProfile) if f.init], None)

# Load profile info
_dict['name'] = data['name']
_dict['uuid'] = uuid
_dict['is_legacy'] = data.get('legacy', False)
_dict['is_demo'] = data.get('demo', False)

# Load skin and cape
textures_data = json.loads(base64.b64decode(data['properties'][0]['value']))

skin_data = textures_data['textures'].get('SKIN', None)
if skin_data:
_dict['skin'] = Skin(skin_data['url'], skin_data.get('metadata', {'model': 'classic'})['model'])

cape_data = textures_data['textures'].get('CAPE', None)
if cape_data:
_dict['cape'] = Cape(cape_data['url'])

# Get name history
_dict['names'] = names(uuid)

return UserProfile(**_dict)
14 changes: 9 additions & 5 deletions mojang/account/session.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
import requests
import datetime as dt

from ._urls import URLs
from ._structures import NameChange, Skin
import requests

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

# TODO: Handle errors and exception

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

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))
handle_response(response, InvalidName, UnavailableName, Unauthorized)

def change_user_skin(access_token: str, path: str, variant='classic'):
skin = Skin(source=path, variant=variant)
Expand All @@ -26,6 +28,8 @@ def change_user_skin(access_token: str, path: str, variant='classic'):
('file', ('image.png', skin.data, f'image/png'))
]
response = requests.post(URLs.change_skin(), auth=BearerAuth(access_token), files=files, headers={'content-type': None})
handle_response(response, PayloadError, Unauthorized)

def reset_user_skin(access_token: str, uuid: str):
response = requests.delete(URLs.reset_skin(uuid), auth=BearerAuth(access_token))
handle_response(response, PayloadError, Unauthorized)
2 changes: 0 additions & 2 deletions mojang/classes/__init__.py

This file was deleted.

52 changes: 0 additions & 52 deletions mojang/classes/profile.py

This file was deleted.

0 comments on commit e27c570

Please sign in to comment.