mirror of
https://github.com/yawaflua/Py-SPW.git
synced 2025-12-08 19:39:36 +02:00
Add more pre-checkers, docstrings
- add SpwCardNotFound error to `send_transaction()` - Add balance checker to `send_transactions()` - Made working docstrings - Correction errors in Parameters
This commit is contained in:
@@ -68,5 +68,5 @@ html_theme_options = {
|
||||
],
|
||||
"source_repository": "https://github.com/teleportx/Py-SPW/",
|
||||
"source_branch": "main",
|
||||
"source_directory": "docs/",
|
||||
"source_directory": "docs/source/",
|
||||
}
|
||||
|
||||
@@ -1,45 +1,83 @@
|
||||
from pydantic import BaseModel, validator
|
||||
import validators
|
||||
|
||||
from . import errors as err
|
||||
|
||||
|
||||
class Payment(BaseModel):
|
||||
"""
|
||||
Класс параметров оплаты.
|
||||
|
||||
:param amount: Сумма которую должен оплатить пользователь.
|
||||
:type amount: int
|
||||
|
||||
:param redirectUrl: Ссылка на которую перенаправит пользователя после успешной оплаты.
|
||||
:type redirectUrl: str
|
||||
|
||||
:param webhookUrl: Ссылка вебхука, туда придет сообщение о успешной оплате.
|
||||
:type webhookUrl: str
|
||||
|
||||
:param data: Полезные данные, которые вы хотите получить в будущем вместе с вебхуком.
|
||||
:type data: str
|
||||
|
||||
:raises BigAmountError: Запрашиваемая сумма слишком большая *(макс. 1728)*
|
||||
:raises LengthError: Строка data слишком длинная *(макс. 100)*
|
||||
:raises IsNotURLError: Параметр не является URL
|
||||
"""
|
||||
|
||||
amount: int
|
||||
redirectUrl: str
|
||||
webhookUrl: str
|
||||
data: str
|
||||
|
||||
@validator('amount')
|
||||
def max_amount(cls, value: int):
|
||||
def _max_amount(cls, value: int):
|
||||
if value > 1728:
|
||||
raise ValueError('amount must be <= 1728')
|
||||
raise err.BigAmountError()
|
||||
return value
|
||||
|
||||
@validator('data')
|
||||
def data_size(cls, value):
|
||||
def _data_size(cls, value):
|
||||
if len(value) > 100:
|
||||
raise ValueError('data length must be <=100.')
|
||||
raise err.LengthError(100)
|
||||
return value
|
||||
|
||||
@validator('redirectUrl', 'webhookUrl')
|
||||
def verify_url(cls, value: str):
|
||||
def _verify_url(cls, value: str):
|
||||
if validators.url(value):
|
||||
return value
|
||||
raise ValueError('is not url')
|
||||
raise err.IsNotURLError()
|
||||
|
||||
|
||||
class Transaction(BaseModel):
|
||||
"""
|
||||
Класс параметров транзакции.
|
||||
|
||||
:param receiver: Карта получателя транзакции.
|
||||
:type receiver: str
|
||||
|
||||
:param amount: Сумма которую должен оплатить пользователь.
|
||||
:type amount: int
|
||||
|
||||
:param comment: Комментарий к транзакции.
|
||||
:type comment: str
|
||||
|
||||
:raises LengthError: Комментарий к транзакции comment слишком длинный *(макс. 32)*
|
||||
:raises IsNotCardError: Неверно указана карта получателя
|
||||
"""
|
||||
|
||||
receiver: str
|
||||
amount: int
|
||||
comment: str
|
||||
|
||||
@validator('comment')
|
||||
def comment_size(cls, value: str):
|
||||
def _comment_size(cls, value: str):
|
||||
if len(value) > 32:
|
||||
raise ValueError('comment length must be <=32.')
|
||||
raise err.LengthError(32)
|
||||
return value
|
||||
|
||||
@validator('receiver')
|
||||
def receiver_type(cls, value: str):
|
||||
def _receiver_type(cls, value: str):
|
||||
if len(value) != 5 or not value.isnumeric():
|
||||
raise ValueError(f'Receiver card (`{value}`) number not valid')
|
||||
raise err.IsNotCardError(value)
|
||||
return value
|
||||
|
||||
@@ -12,6 +12,9 @@ mapi = MAPI()
|
||||
|
||||
|
||||
class SkinVariant(Enum):
|
||||
"""
|
||||
Варианты скинов.
|
||||
"""
|
||||
SLIM = 'slim'
|
||||
CLASSIC = 'classic'
|
||||
|
||||
@@ -27,9 +30,20 @@ class _SkinPart:
|
||||
return self.get_image()
|
||||
|
||||
def get_url(self) -> str:
|
||||
"""
|
||||
Получения ссылки на изображение части скина.
|
||||
|
||||
:return: Ссылка на изображение части скина.
|
||||
"""
|
||||
return self.__skin_part_url
|
||||
|
||||
def get_image(self) -> bytes:
|
||||
"""
|
||||
Получения изображения части скина.
|
||||
|
||||
:return: Изображения части скина.
|
||||
"""
|
||||
|
||||
try:
|
||||
visage_surgeplay_response = rq.get(self.__skin_part_url)
|
||||
if visage_surgeplay_response.status_code != 200:
|
||||
@@ -99,4 +113,7 @@ class User:
|
||||
return self._profile
|
||||
|
||||
def get_skin(self) -> Skin:
|
||||
"""
|
||||
Получения объекта скина пользователя.
|
||||
"""
|
||||
return Skin(self._profile)
|
||||
|
||||
154
pyspw/api.py
154
pyspw/api.py
@@ -29,6 +29,16 @@ class _Card:
|
||||
|
||||
|
||||
class SpApi:
|
||||
"""
|
||||
API класс для работы с spworlds api
|
||||
|
||||
:param card_id: Индефикатор карты
|
||||
:type card_id: str
|
||||
|
||||
:param card_token: Секретный ключ доступа карты
|
||||
:type card_token: str
|
||||
"""
|
||||
|
||||
_spworlds_api_url = 'https://spworlds.ru/api/public'
|
||||
|
||||
def __init__(self, card_id: str, card_token: str):
|
||||
@@ -65,8 +75,9 @@ class SpApi:
|
||||
|
||||
def ping(self) -> bool:
|
||||
"""
|
||||
Проверка работоспособности API
|
||||
:return: Bool работает или нет
|
||||
Проверка работоспособности API.
|
||||
|
||||
:return: Состояние API.
|
||||
"""
|
||||
try:
|
||||
self.get_balance()
|
||||
@@ -77,9 +88,14 @@ class SpApi:
|
||||
|
||||
def get_user(self, discord_id: str) -> User:
|
||||
"""
|
||||
Получение пользователя
|
||||
Получение пользователя.
|
||||
|
||||
:param discord_id: ID пользователя дискорда.
|
||||
:return: Class pyspw.User.User
|
||||
:type discord_id: bool
|
||||
|
||||
:return: Объект пользователя.
|
||||
|
||||
:raises SpwUserNotFound: Пользователь не был найден.
|
||||
"""
|
||||
|
||||
response = self._request(_RequestTypes.GET, f'/users/{discord_id}', ignore_codes=[404])
|
||||
@@ -90,51 +106,75 @@ class SpApi:
|
||||
|
||||
def check_access(self, discord_id: str) -> bool:
|
||||
"""
|
||||
Получение статуса проходки
|
||||
Получение статуса проходки.
|
||||
|
||||
:param discord_id: ID пользователя дискорда.
|
||||
:return: Bool True если у пользователя есть проходка, иначе False
|
||||
:type discord_id: bool
|
||||
|
||||
:return: Состояние проходки пользователя.
|
||||
"""
|
||||
response = self._request(_RequestTypes.GET, f'/users/{discord_id}', ignore_codes=[404])
|
||||
return response.status_code != 404
|
||||
|
||||
def check_webhook(self, webhook_data: str, X_Body_Hash: str) -> bool:
|
||||
"""
|
||||
Валидирует webhook
|
||||
Валидирует webhook.
|
||||
|
||||
:param webhook_data: Тело webhook'а.
|
||||
:type webhook_data: str
|
||||
|
||||
:param X_Body_Hash: Хэдер X-Body-Hash из webhook.
|
||||
:return: Bool True если вебхук пришел от верифицированного сервера, иначе False
|
||||
:type X_Body_Hash: str
|
||||
|
||||
:return: Верефецирован или нет вебхук.
|
||||
"""
|
||||
|
||||
hmac_data = hmac.new(self._card.token.encode('utf-8'), webhook_data.encode('utf-8'), sha256).digest()
|
||||
base64_data = b64encode(hmac_data)
|
||||
return hmac.compare_digest(base64_data, X_Body_Hash.encode('utf-8'))
|
||||
|
||||
def create_payment(self, params: Payment) -> str:
|
||||
def create_payment(self, payment: Payment) -> str:
|
||||
"""
|
||||
Создание ссылки на оплату
|
||||
:param params: class PaymentParams параметров оплаты
|
||||
:return: Str ссылка на страницу оплаты, на которую стоит перенаправить пользователя.
|
||||
"""
|
||||
return self._request(_RequestTypes.POST, '/payment', params.dict()).json()['url']
|
||||
Создание ссылки на оплату.
|
||||
|
||||
def send_transaction(self, params: Transaction) -> None:
|
||||
:param payment: Параметры оплаты.
|
||||
:type payment: Payment
|
||||
|
||||
:return: Ссылку на страницу оплаты, на которую стоит перенаправить пользователя.
|
||||
"""
|
||||
Отправка транзакции
|
||||
:param params: class TransactionParameters параметры транзакции
|
||||
:return: None.
|
||||
return self._request(_RequestTypes.POST, '/payment', payment.dict()).json()['url']
|
||||
|
||||
def send_transaction(self, transaction: Transaction) -> None:
|
||||
"""
|
||||
response = self._request(_RequestTypes.POST, '/transactions', params.dict(), ignore_codes=[400])
|
||||
if response.status_code == 400 and response.json()["message"] == 'Недостаточно средств на карте':
|
||||
raise err.SpwInsufficientFunds()
|
||||
Отправка транзакции.
|
||||
|
||||
:param transaction: Параметры транзакции.
|
||||
:type transaction: Transaction
|
||||
|
||||
:raises SpwInsufficientFunds: Недостаточно средств на карте.
|
||||
:raises SpwCardNotFound: Карта получателя не найдена.
|
||||
"""
|
||||
response = self._request(_RequestTypes.POST, '/transactions', transaction.dict(), ignore_codes=[400])
|
||||
if response.status_code == 400:
|
||||
msg = response.json()["message"]
|
||||
if msg == 'Недостаточно средств на карте':
|
||||
raise err.SpwInsufficientFunds()
|
||||
|
||||
elif msg == 'Карты не существует':
|
||||
raise err.SpwCardNotFound()
|
||||
|
||||
def get_balance(self) -> int:
|
||||
"""
|
||||
Получение баланса
|
||||
:return: Int со значением баланса
|
||||
Получение баланса.
|
||||
|
||||
:return: Значения баланса карты.
|
||||
"""
|
||||
return self._request(_RequestTypes.GET, '/card').json()['balance']
|
||||
|
||||
# Manys
|
||||
# ---------------------------------
|
||||
# ------------- Manys -------------
|
||||
# ---------------------------------
|
||||
|
||||
def _many_req(self, iterable: List, method: Callable, delay: float) -> List:
|
||||
users = []
|
||||
|
||||
@@ -149,36 +189,70 @@ class SpApi:
|
||||
|
||||
def get_users(self, discord_ids: List[str], delay: float = 0.3) -> List[User]:
|
||||
"""
|
||||
Получение пользователей
|
||||
:param delay: Значение задержки между запросами, указывается в секундах
|
||||
:param discord_ids: List с IDs пользователей дискорда.
|
||||
:return: List содержащий Classes pyspw.User.User
|
||||
Получение пользователей.
|
||||
|
||||
:param delay: Значение задержки между запросами, указывается в секундах.
|
||||
:type delay: float
|
||||
|
||||
:param discord_ids: Список discord id пользователей, которых вы бы хотели получить.
|
||||
:type discord_ids: List[str]
|
||||
|
||||
:return: Список с пользователями.
|
||||
|
||||
:raises SpwUserNotFound: Пользователь не был найден.
|
||||
"""
|
||||
return self._many_req(discord_ids, self.get_user, delay)
|
||||
|
||||
def check_accesses(self, discord_ids: List[str], delay: float = 0.3) -> List[bool]:
|
||||
"""
|
||||
Получение статуса проходок
|
||||
:param delay: Значение задержки между запросами, указывается в секундах
|
||||
:param discord_ids: List с IDs пользователей дискорда.
|
||||
:return: List содержащий bool со значением статуса проходки
|
||||
Получение статуса проходок.
|
||||
|
||||
:param delay: Значение задержки между запросами, указывается в секундах.
|
||||
:type delay: float
|
||||
|
||||
:param discord_ids: Список discord id пользователей статусы проходок, которых вы бы хотели получить.
|
||||
:type discord_ids: List[str]
|
||||
|
||||
:return: Список со статусами проходок.
|
||||
"""
|
||||
return self._many_req(discord_ids, self.check_access, delay)
|
||||
|
||||
def create_payments(self, payments: List[Payment], delay: float = 0.5) -> List[str]:
|
||||
"""
|
||||
Создание ссылок на оплату
|
||||
:param payments: Список содержащий classes PaymentParams
|
||||
:param delay: Значение задержки между запросами, указывается в секундах
|
||||
:return: List со ссылками на страницы оплаты, в том порядке, в котором они были в кортеже payments
|
||||
Создание ссылок на оплату.
|
||||
|
||||
:param delay: Значение задержки между запросами, указывается в секундах.
|
||||
:type delay: float
|
||||
|
||||
:param payments: Список параметров оплаты.
|
||||
:type payments: List[Payment]
|
||||
|
||||
:return: Список ссылок на оплату.
|
||||
"""
|
||||
return self._many_req(payments, self.create_payment, delay)
|
||||
|
||||
def send_transactions(self, transactions: List[Transaction], delay: float = 0.5) -> None:
|
||||
"""
|
||||
Отправка транзакций
|
||||
:param delay: Значение задержки между запросами, указывается в секундах
|
||||
:param transactions: Список содержащий classes TransactionParameters
|
||||
:return: List со ссылками на страницы оплаты, в том порядке, в котором они были в кортеже payments
|
||||
Отправка транзакций.
|
||||
|
||||
.. warning::
|
||||
**Важно: Перед множетсвенной отправки транзаций проводится дополнительная проверка на количество средств на карте.
|
||||
В случае если во время совершения транзакций кто-либо еще спишет с этой карты сумму, после которой
|
||||
остаток на карте не будет достаточен для проведения транзакции, то выполнение транзакций прервется,
|
||||
а предыдущие транзации не откатятся.**
|
||||
|
||||
:param delay: Значение задержки между запросами, указывается в секундах.
|
||||
:type delay: float
|
||||
|
||||
:param transactions: Список параметров транзакций
|
||||
:type transactions: List[Transaction]
|
||||
|
||||
:raises SpwInsufficientFunds: Недостаточно средств на карте.
|
||||
:raises SpwCardNotFound: Карта получателя не найдена.
|
||||
"""
|
||||
|
||||
# Additional balance verify
|
||||
if self.get_balance() < sum([tr.amount for tr in transactions]):
|
||||
raise err.SpwInsufficientFunds()
|
||||
|
||||
self._many_req(transactions, self.send_transaction, delay)
|
||||
|
||||
@@ -35,6 +35,11 @@ class SpwInsufficientFunds(SpwApiError):
|
||||
super().__init__("Insufficient funds on the card")
|
||||
|
||||
|
||||
class SpwCardNotFound(SpwApiError):
|
||||
def __init__(self):
|
||||
super().__init__("Receiver card not found")
|
||||
|
||||
|
||||
class MojangApiError(_ApiError):
|
||||
pass
|
||||
|
||||
@@ -51,3 +56,23 @@ class MojangAccountNotFound(MojangApiError):
|
||||
|
||||
class SurgeplayApiError(_ApiError):
|
||||
pass
|
||||
|
||||
|
||||
class LengthError(ValueError):
|
||||
def __init__(self, max_length: int):
|
||||
super().__init__(f"length must be <= {max_length}.")
|
||||
|
||||
|
||||
class BigAmountError(ValueError):
|
||||
def __init__(self):
|
||||
super().__init__(f"amount must be <= 1728.")
|
||||
|
||||
|
||||
class IsNotURLError(ValueError):
|
||||
def __init__(self):
|
||||
super().__init__(f"is not url.")
|
||||
|
||||
|
||||
class IsNotCardError(ValueError):
|
||||
def __init__(self, card: str):
|
||||
super().__init__(f"Receiver card (`{card}`) number not valid")
|
||||
|
||||
Reference in New Issue
Block a user