mirror of
https://github.com/deesiigneer/pyspapi.git
synced 2026-04-20 04:25:25 +00:00
refactor: improve code structure and add proxy support in APISession and SPAPI
This commit is contained in:
@@ -1,9 +1,13 @@
|
|||||||
from .api import *
|
"""pyspapi - API wrapper for SP servers written in Python
|
||||||
from .spworlds import *
|
TODO: заполнить описание"""
|
||||||
from .types import *
|
|
||||||
|
|
||||||
__author__ = 'deesiigneer'
|
import importlib.metadata
|
||||||
__url__ = 'https://github.com/deesiigneer/pyspapi'
|
from .spworlds import SPAPI
|
||||||
__description__ = 'API wrapper for SP servers written in Python.'
|
|
||||||
__license__ = 'MIT'
|
__all__ = [SPAPI]
|
||||||
__version__ = "3.2.0"
|
|
||||||
|
__author__: str = "deesiigneer"
|
||||||
|
__url__: str = "https://github.com/deesiigneer/pyspapi"
|
||||||
|
__description__: str = "API wrapper for SP servers written in Python."
|
||||||
|
__license__: str = "MIT"
|
||||||
|
__version__: str = importlib.metadata.version("pyspapi")
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
from .api import APISession
|
from .api import APISession
|
||||||
|
|
||||||
|
__all__ = [APISession]
|
||||||
|
|||||||
@@ -8,17 +8,20 @@ import aiohttp
|
|||||||
|
|
||||||
from ..exceptions import ValidationError, SPAPIError
|
from ..exceptions import ValidationError, SPAPIError
|
||||||
|
|
||||||
log = getLogger('pyspapi')
|
log = getLogger("pyspapi")
|
||||||
|
|
||||||
|
|
||||||
class APISession(object):
|
class APISession(object):
|
||||||
|
def __init__(
|
||||||
def __init__(self, card_id: str,
|
self,
|
||||||
token: str,
|
card_id: str,
|
||||||
timeout: int = 5,
|
token: str,
|
||||||
sleep_time: float = 0.2,
|
timeout: int = 5,
|
||||||
retries: int = 0,
|
sleep_time: float = 0.2,
|
||||||
raise_exception: bool = False):
|
retries: int = 0,
|
||||||
|
raise_exception: bool = False,
|
||||||
|
proxy: str = None,
|
||||||
|
):
|
||||||
self.__url = "https://spworlds.ru/api/public/"
|
self.__url = "https://spworlds.ru/api/public/"
|
||||||
self.__id = card_id
|
self.__id = card_id
|
||||||
self.__token = token
|
self.__token = token
|
||||||
@@ -26,23 +29,29 @@ class APISession(object):
|
|||||||
self.__retries = retries
|
self.__retries = retries
|
||||||
self.__timeout = timeout
|
self.__timeout = timeout
|
||||||
self.__raise_exception = raise_exception
|
self.__raise_exception = raise_exception
|
||||||
|
self.__proxy = proxy
|
||||||
self.session: Optional[aiohttp.ClientSession] = None
|
self.session: Optional[aiohttp.ClientSession] = None
|
||||||
|
|
||||||
async def __aenter__(self):
|
async def __aenter__(self):
|
||||||
|
print("proxy=", self.__proxy)
|
||||||
self.session = aiohttp.ClientSession(
|
self.session = aiohttp.ClientSession(
|
||||||
json_serialize=json.dumps,
|
json_serialize=json.dumps,
|
||||||
timeout=aiohttp.ClientTimeout(total=self.__timeout))
|
timeout=aiohttp.ClientTimeout(total=self.__timeout),
|
||||||
|
proxy=self.__proxy,
|
||||||
|
)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
async def __aexit__(self, *err):
|
async def __aexit__(self, *err):
|
||||||
await self.session.close()
|
await self.session.close()
|
||||||
self.session = None
|
self.session = None
|
||||||
|
|
||||||
async def request(self, method: str, endpoint: str, data: Optional[Dict] = None) -> Any:
|
async def request(
|
||||||
|
self, method: str, endpoint: str, data: Optional[Dict] = None
|
||||||
|
) -> Any:
|
||||||
url = self.__url + endpoint
|
url = self.__url + endpoint
|
||||||
headers = {
|
headers = {
|
||||||
'Authorization': f"Bearer {str(b64encode(str(f'{self.__id}:{self.__token}').encode('utf-8')), 'utf-8')}",
|
"Authorization": f"Bearer {str(b64encode(str(f'{self.__id}:{self.__token}').encode('utf-8')), 'utf-8')}",
|
||||||
'User-Agent': 'https://github.com/deesiigneer/pyspapi',
|
"User-Agent": "https://github.com/deesiigneer/pyspapi",
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,9 +59,13 @@ class APISession(object):
|
|||||||
while True:
|
while True:
|
||||||
attempt += 1
|
attempt += 1
|
||||||
if attempt > 1:
|
if attempt > 1:
|
||||||
log.warning(f'[pyspapi] Repeat attempt {attempt}: {method.upper()} {url}')
|
log.warning(
|
||||||
|
f"[pyspapi] Repeat attempt {attempt}: {method.upper()} {url}"
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
async with self.session.request(method, url, json=data, headers=headers) as resp:
|
async with self.session.request(
|
||||||
|
method, url, json=data, headers=headers
|
||||||
|
) as resp:
|
||||||
if resp.status == 422:
|
if resp.status == 422:
|
||||||
errors = await resp.json()
|
errors = await resp.json()
|
||||||
log.error(f"[pyspapi] Validation error: {errors}")
|
log.error(f"[pyspapi] Validation error: {errors}")
|
||||||
@@ -69,7 +82,7 @@ class APISession(object):
|
|||||||
|
|
||||||
return await resp.json()
|
return await resp.json()
|
||||||
except (aiohttp.ClientError, asyncio.TimeoutError) as e:
|
except (aiohttp.ClientError, asyncio.TimeoutError) as e:
|
||||||
log.exception(f"[pyspapi] Connection error: {e}")
|
log.exception(f"[pyspapi] Connection error: {e} \n attempt {attempt}")
|
||||||
if attempt > self.__retries:
|
if attempt > self.__retries:
|
||||||
return None
|
return None
|
||||||
await asyncio.sleep(self.__sleep_timeout)
|
await asyncio.sleep(self.__sleep_timeout)
|
||||||
|
|||||||
@@ -4,27 +4,31 @@ from hmac import new, compare_digest
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from .api import APISession
|
from .api import APISession
|
||||||
from .types import User
|
from pyspapi.types import User
|
||||||
from .types.me import Account
|
from pyspapi.types.me import Account
|
||||||
from .types.payment import Item
|
from pyspapi.types.payment import Item
|
||||||
|
|
||||||
__all__ = ['SPAPI']
|
__all__ = ["SPAPI"]
|
||||||
|
|
||||||
|
|
||||||
class SPAPI(APISession):
|
class SPAPI(APISession):
|
||||||
"""
|
"""
|
||||||
pyspapi — высокоуровневый клиент для взаимодействия с SPWorldsAPI.
|
pyspapi — высокоуровневый клиент для взаимодействия с SPWorlds API.
|
||||||
|
|
||||||
Предоставляет удобные методы для работы с балансом карты, вебхуками,
|
Предоставляет удобные методы для работы с балансом карты, вебхуками,
|
||||||
информацией о пользователе, транзакциями и платежами, а также верификацией вебхуков.
|
информацией о пользователе, транзакциями и платежами, а также верификацией вебхуков.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, card_id: str,
|
def __init__(
|
||||||
token: str,
|
self,
|
||||||
timeout: int = 5,
|
card_id: str,
|
||||||
sleep_time: float = 0.2,
|
token: str,
|
||||||
retries: int = 0,
|
timeout: int = 5,
|
||||||
raise_exception: bool = False):
|
sleep_time: float = 0.2,
|
||||||
|
retries: int = 0,
|
||||||
|
raise_exception: bool = False,
|
||||||
|
proxy: str = None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Инициализирует объект SPAPI.
|
Инициализирует объект SPAPI.
|
||||||
|
|
||||||
@@ -40,8 +44,12 @@ class SPAPI(APISession):
|
|||||||
:type retries: int
|
:type retries: int
|
||||||
:param raise_exception: Поднимать исключения при ошибке, если True.
|
:param raise_exception: Поднимать исключения при ошибке, если True.
|
||||||
:type raise_exception: bool
|
:type raise_exception: bool
|
||||||
|
:param proxy: Прокся!
|
||||||
|
:type proxy: str
|
||||||
"""
|
"""
|
||||||
super().__init__(card_id, token, timeout, sleep_time, retries, raise_exception)
|
super().__init__(
|
||||||
|
card_id, token, timeout, sleep_time, retries, raise_exception, proxy
|
||||||
|
)
|
||||||
self.__card_id = card_id
|
self.__card_id = card_id
|
||||||
self.__token = token
|
self.__token = token
|
||||||
|
|
||||||
@@ -59,7 +67,7 @@ class SPAPI(APISession):
|
|||||||
:return: Текущий баланс карты.
|
:return: Текущий баланс карты.
|
||||||
:rtype: int
|
:rtype: int
|
||||||
"""
|
"""
|
||||||
return int((await super().get('card'))['balance'])
|
return int((await super().get("card"))["balance"])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
async def webhook(self) -> Optional[str]:
|
async def webhook(self) -> Optional[str]:
|
||||||
@@ -69,7 +77,7 @@ class SPAPI(APISession):
|
|||||||
:return: URL вебхука.
|
:return: URL вебхука.
|
||||||
:rtype: str
|
:rtype: str
|
||||||
"""
|
"""
|
||||||
return str((await super().get('card'))['webhook'])
|
return str((await super().get("card"))["webhook"])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
async def me(self) -> Optional[Account]:
|
async def me(self) -> Optional[Account]:
|
||||||
@@ -79,16 +87,17 @@ class SPAPI(APISession):
|
|||||||
:return: Объект Account, представляющий аккаунт текущего пользователя.
|
:return: Объект Account, представляющий аккаунт текущего пользователя.
|
||||||
:rtype: :class:`Account`
|
:rtype: :class:`Account`
|
||||||
"""
|
"""
|
||||||
me = await super().get('accounts/me')
|
me = await super().get("accounts/me")
|
||||||
return Account(
|
return Account(
|
||||||
account_id=me['id'],
|
account_id=me["id"],
|
||||||
username=me['username'],
|
username=me["username"],
|
||||||
minecraftuuid=me['minecraftUUID'],
|
minecraftuuid=me["minecraftUUID"],
|
||||||
status=me['status'],
|
status=me["status"],
|
||||||
roles=me['roles'],
|
roles=me["roles"],
|
||||||
cities=me['cities'],
|
cities=me["cities"],
|
||||||
cards=me['cards'],
|
cards=me["cards"],
|
||||||
created_at=me['createdAt'])
|
created_at=me["createdAt"],
|
||||||
|
)
|
||||||
|
|
||||||
async def get_user(self, discord_id: int) -> Optional[User]:
|
async def get_user(self, discord_id: int) -> Optional[User]:
|
||||||
"""
|
"""
|
||||||
@@ -100,11 +109,16 @@ class SPAPI(APISession):
|
|||||||
:return: Объект User, представляющий пользователя.
|
:return: Объект User, представляющий пользователя.
|
||||||
:rtype: :class:`User`
|
:rtype: :class:`User`
|
||||||
"""
|
"""
|
||||||
user = await super().get(f'users/{discord_id}')
|
user = await super().get(f"users/{discord_id}")
|
||||||
cards = await super().get(f"accounts/{user['username']}/cards")
|
if user:
|
||||||
return User(user['username'], user['uuid'], cards)
|
cards = await super().get(f"accounts/{user['username']}/cards")
|
||||||
|
return User(user["username"], user["uuid"], cards)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
async def create_transaction(self, receiver: str, amount: int, comment: str) -> Optional[int]:
|
async def create_transaction(
|
||||||
|
self, receiver: str, amount: int, comment: str
|
||||||
|
) -> Optional[int]:
|
||||||
"""
|
"""
|
||||||
Создает транзакцию.
|
Создает транзакцию.
|
||||||
|
|
||||||
@@ -118,15 +132,13 @@ class SPAPI(APISession):
|
|||||||
:return: Баланс после транзакции.
|
:return: Баланс после транзакции.
|
||||||
:rtype: int
|
:rtype: int
|
||||||
"""
|
"""
|
||||||
data = {
|
data = {"receiver": receiver, "amount": amount, "comment": comment}
|
||||||
'receiver': receiver,
|
|
||||||
'amount': amount,
|
|
||||||
'comment': comment
|
|
||||||
}
|
|
||||||
|
|
||||||
return int((await super().post('transactions', data))['balance'])
|
return int((await super().post("transactions", data))["balance"])
|
||||||
|
|
||||||
async def create_payment(self, webhook_url: str, redirect_url: str, data: str, items: list[Item]) -> Optional[str]:
|
async def create_payment(
|
||||||
|
self, webhook_url: str, redirect_url: str, data: str, items: list[Item]
|
||||||
|
) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
Создает платеж.
|
Создает платеж.
|
||||||
|
|
||||||
@@ -142,13 +154,13 @@ class SPAPI(APISession):
|
|||||||
:rtype: str
|
:rtype: str
|
||||||
"""
|
"""
|
||||||
data = {
|
data = {
|
||||||
'items': items,
|
"items": items,
|
||||||
'redirectUrl': redirect_url,
|
"redirectUrl": redirect_url,
|
||||||
'webhookUrl': webhook_url,
|
"webhookUrl": webhook_url,
|
||||||
'data': data
|
"data": data,
|
||||||
}
|
}
|
||||||
|
|
||||||
return str((await super().post('payments', data))['url'])
|
return str((await super().post("payments", data))["url"])
|
||||||
|
|
||||||
async def update_webhook(self, url: str) -> Optional[dict]:
|
async def update_webhook(self, url: str) -> Optional[dict]:
|
||||||
"""
|
"""
|
||||||
@@ -157,9 +169,9 @@ class SPAPI(APISession):
|
|||||||
:param url: Новый URL вебхука.
|
:param url: Новый URL вебхука.
|
||||||
:return: Ответ API в виде словаря или None при ошибке.
|
:return: Ответ API в виде словаря или None при ошибке.
|
||||||
"""
|
"""
|
||||||
data = {'url': url}
|
data = {"url": url}
|
||||||
|
|
||||||
return await super().put('card/webhook', data)
|
return await super().put("card/webhook", data)
|
||||||
|
|
||||||
def webhook_verify(self, data: str, header: str) -> bool:
|
def webhook_verify(self, data: str, header: str) -> bool:
|
||||||
"""
|
"""
|
||||||
@@ -172,8 +184,8 @@ class SPAPI(APISession):
|
|||||||
:return: True, если заголовок из вебхука достоверен, иначе False.
|
:return: True, если заголовок из вебхука достоверен, иначе False.
|
||||||
:rtype: bool
|
:rtype: bool
|
||||||
"""
|
"""
|
||||||
hmac_data = b64encode(new(self.__token.encode('utf-8'), data, sha256).digest())
|
hmac_data = b64encode(new(self.__token.encode("utf-8"), data, sha256).digest())
|
||||||
return compare_digest(hmac_data, header.encode('utf-8'))
|
return compare_digest(hmac_data, header.encode("utf-8"))
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
def to_dict(self) -> dict:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
from .payment import Item
|
|
||||||
from .users import User
|
|
||||||
from .me import Account
|
from .me import Account
|
||||||
|
from .payment import Item
|
||||||
|
from .users import Cards, User
|
||||||
|
|
||||||
|
__all__ = [Account, Item, Cards, User]
|
||||||
|
|||||||
Reference in New Issue
Block a user