Files
pyspapi/pyspapi/api.py
2022-07-20 22:20:22 +03:00

269 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import json.decoder
from sys import version_info
import ast
from base64 import b64encode, b64decode
from hmac import new, compare_digest
from hashlib import sha256
from logging import getLogger
from requests import get, post, Response
from typing import Any, Dict, List, Optional
from .models import MojangUserProfile, SPUserProfile
import warnings
log = getLogger('pyspapi')
class _Error(Exception):
"""
"""
def __init__(self, message: Optional[str] = None):
self.message = message if message else self.__class__.__doc__
super().__init__(self.message)
class SPAPI:
"""
class SPAPI
"""
_SPWORLDS_DOMAIN_ = "https://spworlds.ru/api/public"
def __init__(self, card_id: str, token: str):
self.__id = card_id
self.__token = token
self.__header = {
'Authorization': f"Bearer {str(b64encode(str(f'{self.__id}:{self.__token}').encode('utf-8')), 'utf-8')}",
'User-Agent': f'pyspapi (https://github.com/deesiigneer/pyspapi) '
f'Python {version_info.major}.{version_info.minor}.{version_info.micro}'
}
self.balance = self.__check_balance()
def __make_request(self, method: str, path: str, data: Optional[dict]) -> Optional[Response]:
if method == 'GET':
response = get(self._SPWORLDS_DOMAIN_ + path, headers=self.__header)
return response
elif method == 'POST':
response = post(self._SPWORLDS_DOMAIN_ + path, headers=self.__header, json=data)
return response
def get_user(self, user_id: int) -> Optional[SPUserProfile]:
"""
Получение информации об игроке SP \n
:param user_id: ID пользователя в Discord.
:return: Class SPUserProfile.
"""
response = self.__make_request('GET', f'/users/{str(user_id)}', data=None)
if not response.ok:
return None
try:
username = response.json()['username']
return SPUserProfile(access=True if username is not None else False, username=username)
except json.decoder.JSONDecodeError:
return
def get_users(self, user_ids: List[int]) -> List[str]:
"""
Получение никнеймов игроков в майнкрафте. **Не более 10**\n
:param user_ids: List[int] ID пользователей в Discord.
:return: List[str] который содержит майнкрафт никнеймы игроков в том же порядке, который был задан,
None если пользователь не найден или нет проходки.
"""
if len(user_ids) > 10:
user_ids = user_ids[:10]
warnings.warn('user_ids more than 10. Reduced to 10')
nicknames_list = []
for user_id in user_ids:
nicknames_list.append(self.get_user(user_id).username
if self.get_user(user_id) is not None else None)
return nicknames_list
def check_users_access(self, user_ids: List[int]) -> List[bool]:
"""
Проверка наличия проходки у списка пользователей Discord. **Не более 10**\n
:param user_ids: Список(List[int]) содержащий ID пользователей в Discord.
:return: Список(List[bool]) в том же порядке, который был задан.True - проходка имеется, иначе False.
"""
if len(user_ids) > 10:
user_ids = user_ids[:10]
warnings.warn('user_ids more than 10. Reduced to 10')
ids_list = []
for user_id in user_ids:
ids_list.append(self.get_user(user_id).access
if self.get_user(user_id) is not None else None)
return ids_list
def payment(self, amount: int, redirect_url: str, webhook_url: str, data: str) -> Optional[str]:
"""
Создание ссылки для оплаты.\n
:param amount: Стоимость покупки в АРах.
:param redirect_url: URL страницы, на которую попадет пользователь после оплаты.
:param webhook_url: URL, куда наш сервер направит запрос, чтобы оповестить ваш сервер об успешной оплате.
:param data: Строка до 100 символов, сюда можно поместить любые полезные данных.
:return: Ссылку на страницу оплаты, на которую стоит перенаправить пользователя.
"""
if len(data) > 100:
raise _Error('В data больше 100 символов')
body = {
'amount': amount,
'redirectUrl': redirect_url,
'webhookUrl': webhook_url,
'data': data
}
response = self.__make_request('POST', '/payment', data=body)
if not response.ok:
return None
try:
return response.json()['url']
except json.decoder.JSONDecodeError:
return None
def webhook_verify(self, data: str, header) -> bool:
"""
Проверяет достоверность webhook'а. \n
:param data: data из webhook.
:param header: header X-Body-Hash из webhook.
:return: True если header из webhook'а достоверен, иначе False
"""
hmac_data = b64encode(new(self.__token.encode('utf-8'), data, sha256).digest())
return compare_digest(hmac_data, header.encode('utf-8'))
def transaction(self, receiver: int, amount: int, comment: str) -> Optional[str]:
"""
Перевод АР на карту. \n
:param receiver: Номер карты получателя.
:param amount: Количество АР для перевода.
:param comment: Комментарий для перевода.
:return: True если перевод успешен, иначе False.
"""
body = {
'receiver': receiver,
'amount': amount,
'comment': comment
}
response = self.__make_request('POST', '/transactions', data=body)
if not response.ok:
return None
try:
return 'Success' if response.status_code == 200 else 'Fail'
except json.decoder.JSONDecodeError:
return None
def __check_balance(self) -> Optional[int]:
"""
Проверка баланса карты \n
:return: Количество АР на карте.
"""
response = self.__make_request('GET', '/card', None)
if not response.ok:
return None
try:
return response.json()['balance']
except json.decoder.JSONDecodeError:
return None
class MojangAPI:
"""
class MojangAPI
"""
_API_DOMAIN_ = "https://api.mojang.com"
_SESSIONSERVER_DOMAIN_ = "https://sessionserver.mojang.com"
@classmethod
def __make_request(cls, server: str, method: str, path: str, data=Optional[dict]) -> Optional[Response]:
if server == 'API':
if method == 'GET':
return get(cls._API_DOMAIN_ + path)
elif method == 'POST':
return post(cls._API_DOMAIN_ + path, json=data)
elif server == 'SESSION':
if method == 'GET':
return get(cls._SESSIONSERVER_DOMAIN_ + path)
@classmethod
def get_uuid(cls, username: str) -> Optional[str]:
"""
Получить UUID игрока Minecraft.\n
:param username: str никнейм игрока Minecraft.
:return: Optional[str] UUID игрока Minecraft.
"""
response = cls.__make_request('API', 'GET', f'/users/profiles/minecraft/{username}')
if not response.ok:
return None
try:
return response.json()['id']
except json.decoder.JSONDecodeError:
return None
@classmethod
def get_uuids(cls, names: List[str]) -> Dict[str, str]:
"""
Получить UUID's игроков Minecraft.\n
:param names: List[str] Список с никнеймами игроков Minecraft.
:return: Dict[str, str] UUID игрока Minecraft.
"""
if len(names) > 10:
names = names[:10]
response = cls.__make_request('API', 'POST', '/profiles/minecraft', data=names).json()
if not isinstance(response, list):
if response.get('error'):
raise ValueError(response['errorMessage'])
else:
raise _Error(response)
return {uuids['name']: uuids['id'] for uuids in response}
@classmethod
def get_username(cls, uuid: str) -> Optional[str]:
"""
Получить никнейм игрока.\n
:param uuid: UUID игрока Minecraft.
:return: Optional[str] в виде никнейма игрока Minecraft.
"""
response = cls.__make_request('SESSION', 'GET', f'/session/minecraft/profile/{uuid}', None)
if not response.ok:
return None
try:
return response.json()["name"]
except json.decoder.JSONDecodeError:
return None
@classmethod
def get_profile(cls, uuid: str) -> Optional[MojangUserProfile]:
"""
Профиль игрока Minecraft.\n
:param uuid: UUID игрока Minecraft.
:return: Class MojangUserProfile
"""
response = cls.__make_request('SESSION', 'GET', f'/session/minecraft/profile/{uuid}')
if not response.ok:
return None
try:
value = response.json()["properties"][0]["value"]
except (KeyError, json.decoder.JSONDecodeError):
return None
user_profile = ast.literal_eval(b64decode(value).decode())
return MojangUserProfile(user_profile)
@classmethod
def get_name_history(cls, uuid: str) -> List[Dict[str, Any]]:
"""
История никнеймов в Minecraft.\n
:param uuid: UUID игрока Minecraft.
:return: List[Dict[str, Any]] который содержит name и changed_to_at
"""
requests = cls.__make_request('API', 'GET', f"/user/profiles/{uuid}/names")
name_history = requests.json()
name_data = []
for data in name_history:
name_data_dict = {"name": data["name"]}
if data.get("changedToAt"):
name_data_dict["changed_to_at"] = data["changedToAt"]
else:
name_data_dict["changed_to_at"] = 0
name_data.append(name_data_dict)
return name_data