mirror of
https://github.com/yawaflua/Py-SPW.git
synced 2025-12-08 19:39:36 +02:00
Upload 1.5.0 version
Upload docs
This commit is contained in:
27
README.md
27
README.md
@@ -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
20
docs/Makefile
Normal 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
35
docs/make.bat
Normal 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
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
25
docs/source/index.rst
Normal 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
34
docs/source/pyspw.rst
Normal 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:
|
||||
8
docs/source/reference.rst
Normal file
8
docs/source/reference.rst
Normal file
@@ -0,0 +1,8 @@
|
||||
API Reference
|
||||
=============
|
||||
|
||||
.. automodule:: pyspw.api
|
||||
:members: SpApi
|
||||
:undoc-members:
|
||||
:noindex:
|
||||
:show-inheritance:
|
||||
14
examples/check_access.py
Normal file
14
examples/check_access.py
Normal 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
|
||||
9
examples/check_api_work.py
Normal file
9
examples/check_api_work.py
Normal file
@@ -0,0 +1,9 @@
|
||||
import pyspw
|
||||
|
||||
# Init library
|
||||
api = pyspw.SpApi(card_id='card_id',
|
||||
card_token='card_token')
|
||||
|
||||
|
||||
# Checking
|
||||
print(api.ping())
|
||||
48
examples/create_payment_link.py
Normal file
48
examples/create_payment_link.py
Normal 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
8
examples/get_balance.py
Normal 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())
|
||||
37
examples/send_transaction.py
Normal file
37
examples/send_transaction.py
Normal 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
31
examples/users_actions.py
Normal 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)
|
||||
19
examples/validate_webhook.py
Normal file
19
examples/validate_webhook.py
Normal 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
|
||||
@@ -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
|
||||
|
||||
100
pyspw/Skin.py
100
pyspw/Skin.py
@@ -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)
|
||||
123
pyspw/User.py
123
pyspw/User.py
@@ -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)
|
||||
|
||||
@@ -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'
|
||||
|
||||
257
pyspw/api.py
257
pyspw/api.py
@@ -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)
|
||||
|
||||
@@ -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
5
requirements.txt
Normal 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
|
||||
15
setup.py
15
setup.py
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user