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 *
|
||||
from .spworlds import *
|
||||
from .types import *
|
||||
"""pyspapi - API wrapper for SP servers written in Python
|
||||
TODO: заполнить описание"""
|
||||
|
||||
__author__ = 'deesiigneer'
|
||||
__url__ = 'https://github.com/deesiigneer/pyspapi'
|
||||
__description__ = 'API wrapper for SP servers written in Python.'
|
||||
__license__ = 'MIT'
|
||||
__version__ = "3.2.0"
|
||||
import importlib.metadata
|
||||
from .spworlds import SPAPI
|
||||
|
||||
__all__ = [SPAPI]
|
||||
|
||||
__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
|
||||
|
||||
__all__ = [APISession]
|
||||
|
||||
@@ -8,17 +8,20 @@ import aiohttp
|
||||
|
||||
from ..exceptions import ValidationError, SPAPIError
|
||||
|
||||
log = getLogger('pyspapi')
|
||||
log = getLogger("pyspapi")
|
||||
|
||||
|
||||
class APISession(object):
|
||||
|
||||
def __init__(self, card_id: str,
|
||||
def __init__(
|
||||
self,
|
||||
card_id: str,
|
||||
token: str,
|
||||
timeout: int = 5,
|
||||
sleep_time: float = 0.2,
|
||||
retries: int = 0,
|
||||
raise_exception: bool = False):
|
||||
raise_exception: bool = False,
|
||||
proxy: str = None,
|
||||
):
|
||||
self.__url = "https://spworlds.ru/api/public/"
|
||||
self.__id = card_id
|
||||
self.__token = token
|
||||
@@ -26,23 +29,29 @@ class APISession(object):
|
||||
self.__retries = retries
|
||||
self.__timeout = timeout
|
||||
self.__raise_exception = raise_exception
|
||||
self.__proxy = proxy
|
||||
self.session: Optional[aiohttp.ClientSession] = None
|
||||
|
||||
async def __aenter__(self):
|
||||
print("proxy=", self.__proxy)
|
||||
self.session = aiohttp.ClientSession(
|
||||
json_serialize=json.dumps,
|
||||
timeout=aiohttp.ClientTimeout(total=self.__timeout))
|
||||
timeout=aiohttp.ClientTimeout(total=self.__timeout),
|
||||
proxy=self.__proxy,
|
||||
)
|
||||
return self
|
||||
|
||||
async def __aexit__(self, *err):
|
||||
await self.session.close()
|
||||
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
|
||||
headers = {
|
||||
'Authorization': f"Bearer {str(b64encode(str(f'{self.__id}:{self.__token}').encode('utf-8')), 'utf-8')}",
|
||||
'User-Agent': 'https://github.com/deesiigneer/pyspapi',
|
||||
"Authorization": f"Bearer {str(b64encode(str(f'{self.__id}:{self.__token}').encode('utf-8')), 'utf-8')}",
|
||||
"User-Agent": "https://github.com/deesiigneer/pyspapi",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
@@ -50,9 +59,13 @@ class APISession(object):
|
||||
while True:
|
||||
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:
|
||||
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:
|
||||
errors = await resp.json()
|
||||
log.error(f"[pyspapi] Validation error: {errors}")
|
||||
@@ -69,7 +82,7 @@ class APISession(object):
|
||||
|
||||
return await resp.json()
|
||||
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:
|
||||
return None
|
||||
await asyncio.sleep(self.__sleep_timeout)
|
||||
|
||||
@@ -4,11 +4,11 @@ from hmac import new, compare_digest
|
||||
from typing import Optional
|
||||
|
||||
from .api import APISession
|
||||
from .types import User
|
||||
from .types.me import Account
|
||||
from .types.payment import Item
|
||||
from pyspapi.types import User
|
||||
from pyspapi.types.me import Account
|
||||
from pyspapi.types.payment import Item
|
||||
|
||||
__all__ = ['SPAPI']
|
||||
__all__ = ["SPAPI"]
|
||||
|
||||
|
||||
class SPAPI(APISession):
|
||||
@@ -19,12 +19,16 @@ class SPAPI(APISession):
|
||||
информацией о пользователе, транзакциями и платежами, а также верификацией вебхуков.
|
||||
"""
|
||||
|
||||
def __init__(self, card_id: str,
|
||||
def __init__(
|
||||
self,
|
||||
card_id: str,
|
||||
token: str,
|
||||
timeout: int = 5,
|
||||
sleep_time: float = 0.2,
|
||||
retries: int = 0,
|
||||
raise_exception: bool = False):
|
||||
raise_exception: bool = False,
|
||||
proxy: str = None,
|
||||
):
|
||||
"""
|
||||
Инициализирует объект SPAPI.
|
||||
|
||||
@@ -40,8 +44,12 @@ class SPAPI(APISession):
|
||||
:type retries: int
|
||||
:param raise_exception: Поднимать исключения при ошибке, если True.
|
||||
: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.__token = token
|
||||
|
||||
@@ -59,7 +67,7 @@ class SPAPI(APISession):
|
||||
:return: Текущий баланс карты.
|
||||
:rtype: int
|
||||
"""
|
||||
return int((await super().get('card'))['balance'])
|
||||
return int((await super().get("card"))["balance"])
|
||||
|
||||
@property
|
||||
async def webhook(self) -> Optional[str]:
|
||||
@@ -69,7 +77,7 @@ class SPAPI(APISession):
|
||||
:return: URL вебхука.
|
||||
:rtype: str
|
||||
"""
|
||||
return str((await super().get('card'))['webhook'])
|
||||
return str((await super().get("card"))["webhook"])
|
||||
|
||||
@property
|
||||
async def me(self) -> Optional[Account]:
|
||||
@@ -79,16 +87,17 @@ class SPAPI(APISession):
|
||||
:return: Объект Account, представляющий аккаунт текущего пользователя.
|
||||
:rtype: :class:`Account`
|
||||
"""
|
||||
me = await super().get('accounts/me')
|
||||
me = await super().get("accounts/me")
|
||||
return Account(
|
||||
account_id=me['id'],
|
||||
username=me['username'],
|
||||
minecraftuuid=me['minecraftUUID'],
|
||||
status=me['status'],
|
||||
roles=me['roles'],
|
||||
cities=me['cities'],
|
||||
cards=me['cards'],
|
||||
created_at=me['createdAt'])
|
||||
account_id=me["id"],
|
||||
username=me["username"],
|
||||
minecraftuuid=me["minecraftUUID"],
|
||||
status=me["status"],
|
||||
roles=me["roles"],
|
||||
cities=me["cities"],
|
||||
cards=me["cards"],
|
||||
created_at=me["createdAt"],
|
||||
)
|
||||
|
||||
async def get_user(self, discord_id: int) -> Optional[User]:
|
||||
"""
|
||||
@@ -100,11 +109,16 @@ class SPAPI(APISession):
|
||||
:return: Объект User, представляющий пользователя.
|
||||
:rtype: :class:`User`
|
||||
"""
|
||||
user = await super().get(f'users/{discord_id}')
|
||||
user = await super().get(f"users/{discord_id}")
|
||||
if user:
|
||||
cards = await super().get(f"accounts/{user['username']}/cards")
|
||||
return User(user['username'], user['uuid'], 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: Баланс после транзакции.
|
||||
:rtype: int
|
||||
"""
|
||||
data = {
|
||||
'receiver': receiver,
|
||||
'amount': amount,
|
||||
'comment': comment
|
||||
}
|
||||
data = {"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
|
||||
"""
|
||||
data = {
|
||||
'items': items,
|
||||
'redirectUrl': redirect_url,
|
||||
'webhookUrl': webhook_url,
|
||||
'data': data
|
||||
"items": items,
|
||||
"redirectUrl": redirect_url,
|
||||
"webhookUrl": webhook_url,
|
||||
"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]:
|
||||
"""
|
||||
@@ -157,9 +169,9 @@ class SPAPI(APISession):
|
||||
:param url: Новый URL вебхука.
|
||||
: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:
|
||||
"""
|
||||
@@ -172,8 +184,8 @@ class SPAPI(APISession):
|
||||
:return: True, если заголовок из вебхука достоверен, иначе False.
|
||||
:rtype: bool
|
||||
"""
|
||||
hmac_data = b64encode(new(self.__token.encode('utf-8'), data, sha256).digest())
|
||||
return compare_digest(hmac_data, header.encode('utf-8'))
|
||||
hmac_data = b64encode(new(self.__token.encode("utf-8"), data, sha256).digest())
|
||||
return compare_digest(hmac_data, header.encode("utf-8"))
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from .payment import Item
|
||||
from .users import User
|
||||
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