Upload 1.5.0 version

Upload docs
This commit is contained in:
Teleport
2023-05-08 21:43:58 +03:00
parent 852a407ceb
commit 6b771f8925
22 changed files with 676 additions and 367 deletions

View File

@@ -1,15 +1,28 @@
# Py SPW
Library fo work with SPworlds API.
Library for work with [SPWorlds](https://spworlds.ru) API in Python.
## Installation
Library written on python 3.10.5
Need python version >=3.7
To install, run this command:
`pip install Py-Spw`
```shell
pip install Py-Spw
```
After import Py-SPW to your project: `import pyspw`
## Quick start
*Checking user access*
```python
import pyspw
api = pyspw.SpApi(card_id='card_id',
card_token='card_token')
print(api.check_access('437610383310716930'))
```
### How to
You can see [examples](https://github.com/teleportx/Py-SPW/tree/main/examples) to help solve your problem
## Links
- [PyPi](https://pypi.org/project/Py-SPW)
- [Documentation](https://github.com/teleport2/Py-SPW/wiki)
- [Author](https://github.com/teleport2)
- [Documentation](https://github.com/teleportx/Py-SPW/wiki)
- [API](https://github.com/sp-worlds/api-docs)

20
docs/Makefile Normal file
View File

@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

35
docs/make.bat Normal file
View File

@@ -0,0 +1,35 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.https://www.sphinx-doc.org/
exit /b 1
)
if "%1" == "" goto help
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd

73
docs/source/conf.py Normal file

File diff suppressed because one or more lines are too long

25
docs/source/index.rst Normal file
View File

@@ -0,0 +1,25 @@
Py-SPW Documentation
====================
Library for work with `SPWorlds <https://spworlds.ru>`_ API in Python.
Getting example
===============
.. literalinclude:: ../../examples/check_access.py
Ask help
=============
* See the code `examples <https://github.com/teleportx/Py-SPW/tree/main/examples>`_
* If you found a bug in a library report it to `issue tracker <https://github.com/teleportx/Py-SPW/issues>`_
* Get help with your code using Py-SPW `discussions <https://github.com/teleportx/Py-SPW/discussions>`_
Documentation
=============
.. toctree::
:maxdepth: 3
reference
pyspw

34
docs/source/pyspw.rst Normal file
View File

@@ -0,0 +1,34 @@
pyspw
=====
Api
---
.. automodule:: pyspw.api
:members:
:undoc-members:
:show-inheritance:
User
----
.. automodule:: pyspw.User
:members:
:undoc-members:
:show-inheritance:
Parameters
----------
.. automodule:: pyspw.Parameters
:members:
:undoc-members:
:show-inheritance:
Errors
------
.. automodule:: pyspw.errors
:members:
:undoc-members:
:show-inheritance:

View File

@@ -0,0 +1,8 @@
API Reference
=============
.. automodule:: pyspw.api
:members: SpApi
:undoc-members:
:noindex:
:show-inheritance:

14
examples/check_access.py Normal file
View File

@@ -0,0 +1,14 @@
import pyspw
# Init library
api = pyspw.SpApi(card_id='card_id',
card_token='card_token')
# Check access to server
print(api.check_access('287598524017803264')) # True
print(api.check_access('289341856083607552')) # False
# Check more than one access
print(api.check_accesses(['403987036219899908', '558667431187447809'], delay=1)) # False, True

View File

@@ -0,0 +1,9 @@
import pyspw
# Init library
api = pyspw.SpApi(card_id='card_id',
card_token='card_token')
# Checking
print(api.ping())

View File

@@ -0,0 +1,48 @@
import json
import pyspw
from pyspw.Parameters import Payment
# Init library
api = pyspw.SpApi(card_id='card_id',
card_token='card_token')
# Constructing payment
payment = Payment(
amount=150, # Payment amount (You can't set more than one shulker diamond ore)
redirectUrl='https://spwdev.xyz/', # URL which redirects user after successful payment
webhookUrl='https://spwdev.xyz/api/webhook_spw', # URL which receive webhook of successful payment with data
data=json.dumps({ # Useful data which received with webhook on webhookUrl
"type": "prepayment",
"order": 987951455
})
)
# Create payment link
print(api.create_payment(payment))
# Create more than one payment link
prepayment = Payment(
amount=150,
redirectUrl='https://spwdev.xyz/',
webhookUrl='https://spwdev.xyz/api/webhook_spw',
data=json.dumps({
"type": "prepayment",
"order": 987951455
})
)
# clone similar payment
post_payment = prepayment
post_payment.data = json.dumps({ # You can access to payment variables
"type": "post-payment",
"order": 987951455
})
# Create payment links
api.create_payments([prepayment, post_payment], delay=0.6)
# !Payments links valid for 5 minutes!

8
examples/get_balance.py Normal file
View File

@@ -0,0 +1,8 @@
import pyspw
# Init library
api = pyspw.SpApi(card_id='card_id',
card_token='card_token')
# Get card balance
print(api.get_balance())

View File

@@ -0,0 +1,37 @@
import pyspw
from pyspw.Parameters import Transaction
# Init library
api = pyspw.SpApi(card_id='card_id',
card_token='card_token')
# Constructing transaction
transaction = Transaction(
receiver='00001', # Card number of receiver
amount=24, # Amount of diamond ore which you want to send
comment='Buy diamond pickaxe' # Comment on transaction
)
# Send transaction
api.send_transaction(transaction)
# Send more than one transaction
salary = Transaction(
receiver='00002',
amount=100,
comment='Salary for the January'
)
# You can get information from Transaction class
tax = Transaction(
receiver='00001',
amount=round(salary.amount * 0.2), # take 20% from salary amount
comment=f'Tax from `{salary.comment}`'
)
# Send transactions
api.send_transactions([tax, salary], delay=0.8)

31
examples/users_actions.py Normal file
View File

@@ -0,0 +1,31 @@
from typing import List
import pyspw
from pyspw.User import User, Skin
# Init library
api = pyspw.SpApi(card_id='card_id',
card_token='card_token')
# get user by discord id
user: User = api.get_user('262632724928397312')
print(user.uuid) # user uuid
print(user.nickname) # user nickname
# working with user skin
skin: Skin = user.get_skin()
print(skin.variant) # skin variant (slim or classic)
print(skin.get_head().get_url()) # get url of head
bust_image: bytes = skin.get_bust().get_image() # get image (bytes) of skin bust
# Get more than one user
users: List[User] = api.get_users(['471286011851177994',
'533953916082323456'], delay=0.4)
# print their uuids
for player in users:
print(player.uuid)

View File

@@ -0,0 +1,19 @@
import json
import pyspw
# Init library
api = pyspw.SpApi(card_id='card_id',
card_token='card_token')
# Data received from webhook
webhook_body = {
"payer": "Nakke_",
"amount": 10,
"data": "brax10"
}
X_Body_Hash = "fba3046f2800197d8829556bdf2d04bf61a307d4ede31eb37fb4078d21e24d3e"
# Verify
print(api.check_webhook(json.dumps(webhook_body), X_Body_Hash)) # True or False

View File

@@ -1,45 +1,45 @@
class PaymentParameters:
def __init__(self, amount: int, redirectUrl: str, webhookUrl: str, data: str):
"""
Создание параметров ссылки на оплату
:param amount: Стоимость покупки в АРах.
:param redirectUrl: URL страницы, на которую попадет пользователь после оплаты.
:param webhookUrl: URL, куда наш сервер направит запрос, чтобы оповестить ваш сервер об успешной оплате.
:param data: Строка до 100 символов, сюда можно помеcтить любые полезные данных.
:return: Str ссылка на страницу оплаты, на которую стоит перенаправить пользователя.
"""
self.amount = amount
self.redirectUrl = redirectUrl
self.webhookUrl = webhookUrl
self.data = data
def __str__(self):
return f'''
amount: {self.amount}
redirectUrl: {self.redirectUrl}
webhookUrl: {self.webhookUrl}
data: {self.data}
'''
from pydantic import BaseModel, validator
import validators
class TransactionParameters:
def __init__(self, receiver: str, amount: int, comment: str = 'No comment'):
"""
Отправка транзакции
:param receiver: Номер карты на которую будет совершена транзакция.
:param amount: Сумма транзакции.
:param comment: Комментарий к транзакции.
:return: None.
"""
class Payment(BaseModel):
amount: int
redirectUrl: str
webhookUrl: str
data: str
self.receiver = receiver
self.amount = amount
self.comment = comment
@validator('amount')
def max_amount(cls, value: int):
if value > 1728:
raise ValueError('amount must be <= 1728')
return value
def __str__(self):
return f'''
receiver: {self.receiver}
amount: {str(self.amount)}
comment: {self.comment}
'''
@validator('data')
def data_size(cls, value):
if len(value) > 100:
raise ValueError('data length must be <=100.')
return value
@validator('redirectUrl', 'webhookUrl')
def verify_url(cls, value: str):
if validators.url(value):
return value
raise ValueError('is not url')
class Transaction(BaseModel):
receiver: str
amount: int
comment: str
@validator('comment')
def comment_size(cls, value: str):
if len(value) > 32:
raise ValueError('comment length must be <=32.')
return value
@validator('receiver')
def receiver_type(cls, value: str):
if len(value) != 5 or not value.isnumeric():
raise ValueError(f'Receiver card (`{value}`) number not valid')
return value

View File

@@ -1,100 +0,0 @@
import requests as rq
from mojang import MojangAPI
from typing import Optional
from . import errors as err
class SkinPart:
def __init__(self, url: str):
self.__skin_part_url = url
def __str__(self):
return self.get_url()
def __bytes__(self):
return self.get_image()
def get_url(self) -> str:
return self.__skin_part_url
def get_image(self) -> bytes:
try:
visage_surgeplay_response = rq.get(self.__skin_part_url)
if visage_surgeplay_response.status_code != 200:
raise err.SurgeplayApiError(f'HTTP status: {visage_surgeplay_response.status_code}')
return visage_surgeplay_response.content
except rq.exceptions.ConnectionError as error:
raise err.SurgeplayApiError(error)
class Cape:
def __init__(self, url: Optional[str]):
self.__skin_part_url = url
def __str__(self):
if self.__skin_part_url is None:
return 'None'
return self.get_url()
def __bytes__(self):
image = self.get_image()
if image is None:
return bytes(0)
return image
def get_url(self) -> Optional[str]:
if self.__skin_part_url is None:
return None
return self.__skin_part_url
def get_image(self) -> Optional[bytes]:
if self.__skin_part_url is None:
return None
try:
visage_surgeplay_response = rq.get(self.__skin_part_url)
if visage_surgeplay_response.status_code != 200:
raise err.SurgeplayApiError(f'HTTP status: {visage_surgeplay_response.status_code}')
return visage_surgeplay_response.content
except rq.exceptions.ConnectionError as error:
raise err.SurgeplayApiError(error)
class Skin:
__visage_surgeplay_url = 'https://visage.surgeplay.com/'
def __init__(self, uuid: str):
self.__uuid = uuid
def __str__(self):
return self.get_skin().__str__()
def get_face(self, image_size: int = 64) -> SkinPart:
return SkinPart(f'https://visage.surgeplay.com/face/{image_size}/{self.__uuid}')
def get_front(self, image_size: int = 64) -> SkinPart:
return SkinPart(f'https://visage.surgeplay.com/front/{image_size}/{self.__uuid}')
def get_front_full(self, image_size: int = 64) -> SkinPart:
return SkinPart(f'https://visage.surgeplay.com/frontfull/{image_size}/{self.__uuid}')
def get_head(self, image_size: int = 64) -> SkinPart:
return SkinPart(f'https://visage.surgeplay.com/head/{image_size}/{self.__uuid}')
def get_bust(self, image_size: int = 64) -> SkinPart:
return SkinPart(f'https://visage.surgeplay.com/bust/{image_size}/{self.__uuid}')
def get_full(self, image_size: int = 64) -> SkinPart:
return SkinPart(f'https://visage.surgeplay.com/full/{image_size}/{self.__uuid}')
def get_skin(self, image_size: int = 64) -> SkinPart:
return SkinPart(f'https://visage.surgeplay.com/skin/{image_size}/{self.__uuid}')
def get_cape(self) -> Cape:
return Cape(MojangAPI.get_profile(self.__uuid).cape_url)

View File

@@ -1,37 +1,102 @@
from typing import List, Dict, Any, Optional
from mojang import MojangAPI
from enum import Enum
from typing import Optional
from .Skin import Skin
import requests as rq
from mojang import API as MAPI
from mojang._types import UserProfile
from . import errors as err
from .errors import MojangAccountNotFound
mapi = MAPI()
class SkinVariant(Enum):
SLIM = 'slim'
CLASSIC = 'classic'
class _SkinPart:
def __init__(self, url: str):
self.__skin_part_url = url
def __str__(self):
return self.get_url()
def __bytes__(self):
return self.get_image()
def get_url(self) -> str:
return self.__skin_part_url
def get_image(self) -> bytes:
try:
visage_surgeplay_response = rq.get(self.__skin_part_url)
if visage_surgeplay_response.status_code != 200:
raise err.SurgeplayApiError(f'HTTP status: {visage_surgeplay_response.status_code}')
return visage_surgeplay_response.content
except rq.exceptions.ConnectionError as error:
raise err.SurgeplayApiError(error)
class Skin:
__visage_surgeplay_url = 'https://visage.surgeplay.com/'
def __init__(self, profile: UserProfile):
self._profile = profile
self._variant = SkinVariant(profile.skin_variant)
@property
def variant(self) -> SkinVariant:
return self._variant
def get_face(self, image_size: int = 64) -> _SkinPart:
return _SkinPart(f'https://visage.surgeplay.com/face/{image_size}/{self._profile.id}')
def get_front(self, image_size: int = 64) -> _SkinPart:
return _SkinPart(f'https://visage.surgeplay.com/front/{image_size}/{self._profile.id}')
def get_front_full(self, image_size: int = 64) -> _SkinPart:
return _SkinPart(f'https://visage.surgeplay.com/frontfull/{image_size}/{self._profile.id}')
def get_head(self, image_size: int = 64) -> _SkinPart:
return _SkinPart(f'https://visage.surgeplay.com/head/{image_size}/{self._profile.id}')
def get_bust(self, image_size: int = 64) -> _SkinPart:
return _SkinPart(f'https://visage.surgeplay.com/bust/{image_size}/{self._profile.id}')
def get_full(self, image_size: int = 64) -> _SkinPart:
return _SkinPart(f'https://visage.surgeplay.com/full/{image_size}/{self._profile.id}')
def get_skin(self, image_size: int = 64) -> _SkinPart:
return _SkinPart(f'https://visage.surgeplay.com/skin/{image_size}/{self._profile.id}')
def get_cape(self) -> Optional[_SkinPart]:
if self._profile.cape_url is None:
return None
return _SkinPart(self._profile.cape_url)
class User:
def __init__(self, nickname: str | None, use_mojang_api: bool = True):
self.nickname = nickname
def __init__(self, nickname: str):
self._nickname = nickname
self._uuid = mapi.get_uuid(nickname)
if self._uuid is None:
raise MojangAccountNotFound(self._nickname)
self._profile = mapi.get_profile(self._uuid)
if self.nickname is not None:
self.access = True
@property
def nickname(self) -> str:
return self._nickname
if use_mojang_api:
self.uuid = MojangAPI.get_uuid(nickname)
@property
def uuid(self) -> str:
return self._uuid
else:
self.uuid = None
self.access = False
@property
def profile(self) -> UserProfile:
return self._profile
def __str__(self):
if self.nickname is None:
return 'None'
return self.nickname
def get_skin(self) -> Optional[Skin]:
if self.uuid is None:
return None
return Skin(self.uuid)
def get_nickname_history(self) -> Optional[List[Dict[str, Any]]]:
if self.uuid is None:
return None
return MojangAPI.get_name_history(self.uuid)
def get_skin(self) -> Skin:
return Skin(self._profile)

View File

@@ -1,11 +1,3 @@
from . import api
from . import errors
from . import User
from . import Skin
from . import Parameters
from .api import *
__all__ = ["SpApi", "errors", "User", "Skin"]
class SpApi(api.Py_SPW):
pass
__version__ = '1.5.0'

View File

@@ -1,42 +1,61 @@
import platform
import sys
from base64 import b64encode
from dataclasses import dataclass
from enum import Enum
from hashlib import sha256
import hmac
import requests as rq
import time
from typing import Optional, List
from typing import List, Callable
import logging
from mojang import MojangAPI
from mojang import API as MAPI
from . import errors as err
from .User import User
from .Parameters import PaymentParameters, TransactionParameters
from .Parameters import Payment, Transaction
# deesiigneer stole some of my ideas and improved them. But I didn't lose my head and improved what deesiigneer improved :)
mapi = MAPI()
class Py_SPW:
__spworlds_api_url = 'https://spworlds.ru/api/public'
class _RequestTypes(Enum):
POST = 'POST'
GET = 'GET'
@dataclass
class _Card:
id: str
token: str
class SpApi:
_spworlds_api_url = 'https://spworlds.ru/api/public'
def __init__(self, card_id: str, card_token: str):
self.__card_token = card_token
self.__authorization = f"Bearer {str(b64encode(str(f'{card_id}:{card_token}').encode('utf-8')), 'utf-8')}"
self._card = _Card(card_id, card_token)
self._authorization = f"Bearer {str(b64encode(str(f'{card_id}:{card_token}').encode('utf-8')), 'utf-8')}"
def __get(self, path: str = None, ignore_status_code: bool = False) -> rq.Response:
def _request(self, method: _RequestTypes, path: str = '', body: dict = None, *,
ignore_codes: list = []) -> rq.Response:
headers = {
'Authorization': self.__authorization,
'User-Agent': 'Py-SPW'
'Authorization': self._authorization,
'User-Agent': f'Py-SPW (Python {platform.python_version()})'
}
try:
response = rq.get(url=self.__spworlds_api_url + path, headers=headers)
response = rq.request(method.value, url=self._spworlds_api_url + path, headers=headers, json=body)
except rq.exceptions.ConnectionError as error:
raise err.SpwApiError(error)
if ignore_status_code:
if response.headers.get('Content-Type') != 'application/json':
raise err.SpwApiDDOS()
if response.ok or response.status_code in ignore_codes:
return response
if response.status_code == 200:
return response
elif response.status_code == 401:
raise err.SpwUnauthorized()
elif response.status_code >= 500:
raise err.SpwApiError(f'HTTP: {response.status_code}, Server Error.')
@@ -45,74 +64,30 @@ class Py_SPW:
raise err.SpwApiError(
f'HTTP: {response.status_code} {response.json()["error"]}. Message: {response.json()["message"]}')
def __post(self, path: str = None, body: dict = None) -> rq.Response:
headers = {
'Authorization': self.__authorization,
'User-Agent': 'Py-SPW'
}
def ping(self) -> bool:
"""
Проверка работоспособности API
:return: Bool работает или нет
"""
try:
response = rq.post(url=self.__spworlds_api_url + path, headers=headers, json=body)
self.get_balance()
return True
except rq.exceptions.ConnectionError as error:
raise err.SpwApiError(error)
except err.SpwApiError:
return False
if response.status_code == 200:
return response
elif response.status_code >= 500:
raise err.SpwApiError(f'HTTP: {response.status_code}, Server Error.')
else:
raise err.SpwApiError(f'HTTP: {response.status_code} {response.json()["error"]}. Message: {response.json()["message"]}')
def get_user(self, discord_id: str, use_mojang_api: bool = True) -> User:
def get_user(self, discord_id: str) -> User:
"""
Получение пользователя
:param use_mojang_api: Если True то будет обращаться к Mojang API для получения UUID, иначе обращаться не будет
:param discord_id: ID пользователя дискорда.
:return: Class pyspw.User.User
"""
response = self.__get(f'/users/{discord_id}', True)
response = self._request(_RequestTypes.GET, f'/users/{discord_id}', ignore_codes=[404])
if response.status_code == 404:
raise err.SpwUserNotFound(discord_id)
if response.status_code == 200:
return User(response.json()['username'], use_mojang_api)
elif response.status_code == 404:
return User(None, use_mojang_api)
elif response.status_code >= 500:
raise err.SpwApiError(f'HTTP: {response.status_code}, Server Error.')
else:
raise err.SpwApiError(f'HTTP: {response.status_code} {response.json()["error"]}. Message: {response.json()["message"]}')
def get_users(self, discord_ids: List[str], delay: float = 0.5, use_mojang_api: bool = True) -> List[User]:
"""
Получение пользователей
:param use_mojang_api: Если True то будет обращаться к Mojang API для получения UUID, иначе обращаться не будет
:param delay: Значение задержки между запросами, указывается в секундах
:param discord_ids: List с IDs пользователей дискорда.
:return: List содержащий Classes pyspw.User.User
"""
users = []
if len(discord_ids) > 100 and delay < 0.5:
logging.warning('You send DOS attack to SPWorlds API. Please set the delay to greater than or equal to 0.5')
for discord_id in discord_ids:
users.append(self.get_user(discord_id, False))
time.sleep(delay)
if use_mojang_api:
nicknames = [user.nickname for user in users]
uuids = MojangAPI.get_uuids(nicknames)
for user in users:
user.uuid = uuids[user.nickname]
return users
return User(response.json()['username'])
def check_access(self, discord_id: str) -> bool:
"""
@@ -120,31 +95,8 @@ class Py_SPW:
:param discord_id: ID пользователя дискорда.
:return: Bool True если у пользователя есть проходка, иначе False
"""
return self.get_user(discord_id, False).access
def check_accesses(self, discord_ids: List[str], delay: float = 0.5) -> List[bool]:
"""
Получение статуса проходок
:param delay: Значение задержки между запросами, указывается в секундах
:param discord_ids: List с IDs пользователей дискорда.
:return: List содержащий bool со значением статуса проходки
"""
accesses = []
users = self.get_users(discord_ids, delay, False)
if len(discord_ids) > 100 and delay < 0.5:
logging.warning('You send DOS attack to SPWorlds API. Please set the delay to greater than or equal to 0.5')
for user in users:
if user is not None:
accesses.append(True)
else:
accesses.append(False)
return accesses
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:
"""
@@ -154,77 +106,80 @@ class Py_SPW:
:return: Bool True если вебхук пришел от верифицированного сервера, иначе False
"""
hmac_data = hmac.new(self.__card_token.encode('utf-8'), webhook_data.encode('utf-8'), sha256).digest()
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: PaymentParameters) -> str:
def create_payment(self, params: Payment) -> str:
"""
Создание ссылки на оплату
:param params: class PaymentParams параметров оплаты
:return: Str ссылка на страницу оплаты, на которую стоит перенаправить пользователя.
"""
return self._request(_RequestTypes.POST, '/payment', params.dict()).json()['url']
body = {
'amount': params.amount,
'redirectUrl': params.redirectUrl,
'webhookUrl': params.webhookUrl,
'data': params.data
}
return self.__post('/payment', body).json()['url']
def create_payments(self, payments: List[PaymentParameters], delay: float = 0.5) -> list:
"""
Создание ссылок на оплату
:param payments: Список содержащий classes PaymentParams
:param delay: Значение задержки между запросами, указывается в секундах
:return: List со ссылками на страницы оплаты, в том порядке, в котором они были в кортеже payments
"""
answer = []
if len(payments) > 100 and delay < 0.5:
logging.warning('You send DOS attack to SPWorlds API. Please set the delay to greater than or equal to 0.5')
for payment in payments:
answer.append(self.create_payment(payment))
time.sleep(delay)
return answer
def send_transaction(self, params: TransactionParameters) -> None:
def send_transaction(self, params: Transaction) -> None:
"""
Отправка транзакции
:param params: class TransactionParameters параметры транзакции
:return: None.
"""
body = {
'receiver': params.receiver,
'amount': params.amount,
'comment': params.comment
}
self.__post('/transactions', body)
def send_transactions(self, transactions: List[TransactionParameters], delay: float = 0.5) -> None:
"""
Отправка транзакций
:param delay: Значение задержки между запросами, указывается в секундах
:param transactions: Список содержащий classes TransactionParameters
:return: List со ссылками на страницы оплаты, в том порядке, в котором они были в кортеже payments
"""
if len(transactions) > 100 and delay < 0.5:
logging.warning('You send DOS attack to SPWorlds API. Please set the delay to greater than or equal to 0.5')
for transaction in transactions:
self.send_transaction(transaction)
time.sleep(delay)
response = self._request(_RequestTypes.POST, '/transactions', params.dict(), ignore_codes=[400])
if response.status_code == 400 and response.json()["message"] == 'Недостаточно средств на карте':
raise err.SpwInsufficientFunds()
def get_balance(self) -> int:
"""
Получение баланса
:return: Int со значением баланса
"""
return self._request(_RequestTypes.GET, '/card').json()['balance']
return self.__get('/card').json()['balance']
# Manys
def _many_req(self, iterable: List, method: Callable, delay: float) -> List:
users = []
if len(iterable) > 100 and delay <= 0.5:
logging.warning('You send DOS attack to SPWorlds API. Please set the delay to greater than to 0.5')
for i in iterable:
users.append(method(i))
time.sleep(delay)
return users
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
"""
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 со значением статуса проходки
"""
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
"""
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
"""
self._many_req(transactions, self.send_transaction, delay)

View File

@@ -1,34 +1,53 @@
class Error(Exception):
class _Error(Exception):
pass
class WebserverError(Error):
class _ApiError(_Error):
pass
class NotFunction(WebserverError):
class SpwApiError(_ApiError):
pass
class ApiError(Error):
class SpwApiDDOS(SpwApiError):
def __init__(self):
super().__init__("SPWorlds DDOS protection block your request")
class SpwUserNotFound(SpwApiError):
def __init__(self, discord_id: str):
self._discord_id = discord_id
super().__init__(f"User with discord id `{discord_id}` not found in spworlds")
@property
def discord_id(self) -> str:
return self._discord_id
class SpwUnauthorized(SpwApiError):
def __init__(self):
super().__init__("Access details are invalid")
class SpwInsufficientFunds(SpwApiError):
def __init__(self):
super().__init__("Insufficient funds on the card")
class MojangApiError(_ApiError):
pass
class SpwApiError(ApiError):
pass
class BadParameter(SpwApiError):
pass
class MojangApiError(ApiError):
pass
class SurgeplayApiError(ApiError):
pass
class BadSkinPartName(SurgeplayApiError):
class MojangAccountNotFound(MojangApiError):
def __init__(self, nickname: str):
self._nickname = nickname
super().__init__(f"Account with name `{nickname}` not found")
@property
def nickname(self) -> str:
return self._nickname
class SurgeplayApiError(_ApiError):
pass

5
requirements.txt Normal file
View File

@@ -0,0 +1,5 @@
requests~=2.30.0
mojang~=1.1.0
pydantic~=1.10.7
setuptools~=65.5.1
validators~=0.20.0

View File

@@ -1,29 +1,28 @@
from os import path
from setuptools import setup
from pyspw import __version__
this_directory = path.abspath(path.dirname(__file__))
with open(path.join(this_directory, 'README.md'), encoding='utf-8') as f:
description_md = f.read()
requirements = [
'requests==2.28.1',
'mojang==0.2.0'
]
requirements = open('requirements.txt', 'r').read().split('\n')
setup(
name='Py-SPW',
version='1.4.3',
version=__version__,
packages=['pyspw'],
url='https://github.com/teleport2/Py-SPW',
url='https://github.com/teleportx/Py-SPW',
license='MIT License',
author='Stepan Khozhempo',
author_email='stepan@m.khoz.ru',
author_email='stepan@khoz.ru',
description='Python library for spworlds API',
long_description=description_md,
long_description_content_type='text/markdown',
install_requires=requirements,
python_requires='>=3.10',
python_requires='>=3.7',
project_urls={
"Docs": "https://github.com/teleport2/Py-SPW/wiki",
"GitHub": "https://github.com/teleport2/Py-SPW"