mirror of
https://github.com/deesiigneer/pyspapi.git
synced 2026-04-20 12:35:26 +00:00
Compare commits
5 Commits
3.0.0a0
...
v3-(asynci
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3d56a6059 | ||
|
|
eff14052fd | ||
|
|
3f935a060b | ||
|
|
3ecf1fff8a | ||
|
|
74a46277f8 |
@@ -2,7 +2,10 @@ version: 2
|
|||||||
formats: []
|
formats: []
|
||||||
|
|
||||||
build:
|
build:
|
||||||
image: latest
|
os: ubuntu-lts-latest
|
||||||
|
tools:
|
||||||
|
python: '3.8'
|
||||||
|
|
||||||
|
|
||||||
sphinx:
|
sphinx:
|
||||||
configuration: docs/conf.py
|
configuration: docs/conf.py
|
||||||
@@ -10,7 +13,6 @@ sphinx:
|
|||||||
builder: html
|
builder: html
|
||||||
|
|
||||||
python:
|
python:
|
||||||
version: "3.8"
|
|
||||||
install:
|
install:
|
||||||
- method: pip
|
- method: pip
|
||||||
path: .
|
path: .
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2022 Aleksey
|
Copyright (c) 2022 deesiigneer
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
15
README.rst
15
README.rst
@@ -37,7 +37,7 @@ Installation
|
|||||||
|
|
||||||
.. code:: sh
|
.. code:: sh
|
||||||
|
|
||||||
sudo apt pip3 install pyspapi
|
pip3 install pyspapi
|
||||||
|
|
||||||
Quick example
|
Quick example
|
||||||
--------------
|
--------------
|
||||||
@@ -46,9 +46,17 @@ Checking the balance
|
|||||||
~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
.. code:: py
|
.. code:: py
|
||||||
|
|
||||||
import pyspapi
|
from pyspapi import SPAPI
|
||||||
|
from asyncio import get_event_loop
|
||||||
|
|
||||||
print(await pyspapi.API(card_id='card_id', token='token').balance)
|
spapi = SPAPI(card_id='CARD_ID', token='TOKEN')
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
print(await spapi.balance)
|
||||||
|
|
||||||
|
loop = get_event_loop()
|
||||||
|
loop.run_until_complete(main())
|
||||||
|
|
||||||
More examples can be found in the `examples <https://github.com/deesiigneer/pyspapi/tree/main/examples>`_
|
More examples can be found in the `examples <https://github.com/deesiigneer/pyspapi/tree/main/examples>`_
|
||||||
|
|
||||||
@@ -57,6 +65,5 @@ Links
|
|||||||
|
|
||||||
- `Discord server <https://discord.gg/VbyHaKRAaN>`_
|
- `Discord server <https://discord.gg/VbyHaKRAaN>`_
|
||||||
- `pyspapi documentation <https://pyspapi.readthedocs.io/>`_
|
- `pyspapi documentation <https://pyspapi.readthedocs.io/>`_
|
||||||
- `API documentation <https://spworlds.readthedocs.io>`_
|
|
||||||
- `PyPi <https://pypi.org/project/pyspapi/>`_
|
- `PyPi <https://pypi.org/project/pyspapi/>`_
|
||||||
- `API documentation for SP sites <https://github.com/sp-worlds/api-docs>`_
|
- `API documentation for SP sites <https://github.com/sp-worlds/api-docs>`_
|
||||||
|
|||||||
6
docs/_static/custom.css
vendored
6
docs/_static/custom.css
vendored
@@ -1,13 +1,13 @@
|
|||||||
/* Background of stable should be green */
|
/* Background of stable should be green */
|
||||||
#version_switcher a[data-version-name*="stable"] {
|
.version-switcher__container a[data-version-name*="stable"] {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
#version_switcher a[data-version-name*="stable"] span {
|
.version-switcher__container a[data-version-name*="stable"] span {
|
||||||
color: var(--pst-color-success);
|
color: var(--pst-color-success);
|
||||||
}
|
}
|
||||||
|
|
||||||
#version_switcher a[data-version-name*="stable"] span:before {
|
.version-switcher__container a[data-version-name*="stable"] span:before {
|
||||||
content: "";
|
content: "";
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
9
docs/_static/switcher.json
vendored
9
docs/_static/switcher.json
vendored
@@ -2,11 +2,16 @@
|
|||||||
{
|
{
|
||||||
"name": "latest",
|
"name": "latest",
|
||||||
"version": "latest",
|
"version": "latest",
|
||||||
"url": "https://pyspapi.readthedocs.io/en/latest/"
|
"url": "https://pyspapi.readthedocs.io/ru/latest/"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "stable",
|
"name": "stable",
|
||||||
"version": "stable",
|
"version": "stable",
|
||||||
"url": "https://pyspapi.readthedocs.io/en/stable/"
|
"url": "https://pyspapi.readthedocs.io/ru/stable/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "v3-asyncio",
|
||||||
|
"version": "v3-asyncio",
|
||||||
|
"url": "https://pyspapi.readthedocs.io/ru/v3-asyncio/"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
48
docs/api.rst
48
docs/api.rst
@@ -1,4 +1,4 @@
|
|||||||
.. py:currentmodule:: pyspapi
|
.. currentmodule:: pyspapi
|
||||||
|
|
||||||
API Reference
|
API Reference
|
||||||
===============
|
===============
|
||||||
@@ -25,50 +25,6 @@ There are two main ways to query version information.
|
|||||||
-----------
|
-----------
|
||||||
|
|
||||||
``SPAPI``
|
``SPAPI``
|
||||||
~~~~~
|
~~~~~~~~~
|
||||||
.. autoclass:: SPAPI
|
.. autoclass:: SPAPI
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
.. automethod:: SPAPI.event()
|
|
||||||
:decorator:
|
|
||||||
|
|
||||||
.. automethod:: SPAPI.check_user_access
|
|
||||||
:decorator:
|
|
||||||
|
|
||||||
.. automethod:: SPAPI.get_user
|
|
||||||
:decorator:
|
|
||||||
|
|
||||||
.. automethod:: SPAPI.get_users
|
|
||||||
:decorator:
|
|
||||||
|
|
||||||
.. automethod:: SPAPI.payment
|
|
||||||
:decorator:
|
|
||||||
|
|
||||||
.. automethod:: SPAPI.transaction
|
|
||||||
:decorator:
|
|
||||||
|
|
||||||
.. automethod:: SPAPI.webhook_verify
|
|
||||||
:decorator:
|
|
||||||
|
|
||||||
MojangAPI
|
|
||||||
~~~~~
|
|
||||||
.. autoclass:: MojangAPI
|
|
||||||
:members:
|
|
||||||
|
|
||||||
.. automethod:: SPAPI.event()
|
|
||||||
:decorator:
|
|
||||||
|
|
||||||
.. automethod:: SPAPI.get_name_history
|
|
||||||
:decorator:
|
|
||||||
|
|
||||||
.. automethod:: SPAPI.get_profile
|
|
||||||
:decorator:
|
|
||||||
|
|
||||||
.. automethod:: SPAPI.get_username
|
|
||||||
:decorator:
|
|
||||||
|
|
||||||
.. automethod:: SPAPI.get_uuid
|
|
||||||
:decorator:
|
|
||||||
|
|
||||||
.. automethod:: SPAPI.get_uuids
|
|
||||||
:decorator:
|
|
||||||
|
|||||||
22
docs/conf.py
22
docs/conf.py
@@ -1,4 +1,7 @@
|
|||||||
from re import search, MULTILINE
|
from re import search, MULTILINE
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
project = 'pyspapi'
|
project = 'pyspapi'
|
||||||
copyright = '2022, deesiigneer'
|
copyright = '2022, deesiigneer'
|
||||||
@@ -16,6 +19,9 @@ release = version
|
|||||||
|
|
||||||
# -- General configuration
|
# -- General configuration
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.abspath(".."))
|
||||||
|
|
||||||
|
|
||||||
extensions = [
|
extensions = [
|
||||||
'sphinx.ext.duration',
|
'sphinx.ext.duration',
|
||||||
'sphinx.ext.doctest',
|
'sphinx.ext.doctest',
|
||||||
@@ -24,12 +30,19 @@ extensions = [
|
|||||||
'sphinx.ext.intersphinx',
|
'sphinx.ext.intersphinx',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
autodoc_member_order = "bysource"
|
||||||
|
autodoc_typehinta = "none"
|
||||||
|
|
||||||
intersphinx_mapping = {
|
intersphinx_mapping = {
|
||||||
'python': ('https://docs.python.org/3/', None),
|
'python': ('https://docs.python.org/3/', None),
|
||||||
'sphinx': ('https://www.sphinx-doc.org/en/master/', None),
|
'sphinx': ('https://www.sphinx-doc.org/en/master/', None),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
version_match = os.environ.get("READTHEDOCS_VERSION")
|
||||||
|
json_url = f"https://pyspapi.readthedocs.io/ru/{version_match}/_static/switcher.json"
|
||||||
|
|
||||||
intersphinx_disabled_domains = ['std']
|
intersphinx_disabled_domains = ['std']
|
||||||
language = None
|
language = 'en'
|
||||||
locale_dirs = ["locale/"]
|
locale_dirs = ["locale/"]
|
||||||
exclude_patterns = []
|
exclude_patterns = []
|
||||||
html_static_path = ["_static"]
|
html_static_path = ["_static"]
|
||||||
@@ -65,12 +78,7 @@ html_theme_options = {
|
|||||||
],
|
],
|
||||||
"header_links_before_dropdown": 4,
|
"header_links_before_dropdown": 4,
|
||||||
"show_toc_level": 1,
|
"show_toc_level": 1,
|
||||||
"navbar_start": ["navbar-logo", "version-switcher"],
|
"navbar_start": ["navbar-logo"],
|
||||||
"switcher": {
|
|
||||||
"json_url": "https://pyspapi.readthedocs.io/en/latest/_static/switcher.json",
|
|
||||||
"version_match": "latest"
|
|
||||||
},
|
|
||||||
"navigation_with_keys": True,
|
"navigation_with_keys": True,
|
||||||
}
|
}
|
||||||
html_css_files = ["custom.css"]
|
html_css_files = ["custom.css"]
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ Quickstart
|
|||||||
This page gives a brief introduction to the library.
|
This page gives a brief introduction to the library.
|
||||||
|
|
||||||
Checking balance
|
Checking balance
|
||||||
-------------
|
----------------
|
||||||
|
|
||||||
Let's output the amount of money remaining in the card account to the console.
|
Let's output the amount of money remaining in the card account to the console.
|
||||||
|
|
||||||
@@ -18,9 +18,17 @@ It looks something like this:
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
import pyspapi
|
from pyspapi import SPAPI
|
||||||
|
from asyncio import get_event_loop
|
||||||
|
|
||||||
print(pyspapi.SPAPI(card_id='card_id', token='token').balance)
|
spapi = SPAPI(card_id='CARD_ID', token='TOKEN')
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
print(await spapi.balance)
|
||||||
|
|
||||||
|
loop = get_event_loop()
|
||||||
|
loop.run_until_complete(main())
|
||||||
|
|
||||||
Make sure not to name it ``pyspapi`` as that'll conflict with the library.
|
Make sure not to name it ``pyspapi`` as that'll conflict with the library.
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
import pyspapi
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
api = pyspapi.API(card_id='card_id', token='token')
|
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
|
||||||
print(await api.get_name_history(uuid='63ed47877aa3470fbfc46c5356c3d797'))
|
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
loop.run_until_complete(main())
|
|
||||||
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import pyspapi
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
api = pyspapi.API(card_id='card_id', token='token')
|
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
|
||||||
mojang_profile = await api.get_profile(uuid='63ed47877aa3470fbfc46c5356c3d797')
|
|
||||||
print(mojang_profile)
|
|
||||||
print(mojang_profile.id, mojang_profile.timestamp)
|
|
||||||
print(mojang_profile.skin, mojang_profile.skin.model, mojang_profile.skin.cape_url, mojang_profile.skin.url)
|
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
loop.run_until_complete(main())
|
|
||||||
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import pyspapi
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
|
|
||||||
api = pyspapi.API(card_id='card_id', token='token')
|
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
|
||||||
user = await api.get_user(264329096920563714)
|
|
||||||
print(user)
|
|
||||||
print(user.access)
|
|
||||||
# У API есть лимиты, каждый user = 1 запрос, учитывайте это при использовании get_users
|
|
||||||
# https://spworlds.readthedocs.io/ru/latest/index.html#id3
|
|
||||||
users = await api.get_users([262632724928397312, 264329096920563714])
|
|
||||||
for user in users:
|
|
||||||
print(user)
|
|
||||||
if user is not None:
|
|
||||||
print(user.access)
|
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
loop.run_until_complete(main())
|
|
||||||
15
examples/get_user.py
Normal file
15
examples/get_user.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from pyspapi import SPAPI
|
||||||
|
from asyncio import get_event_loop
|
||||||
|
|
||||||
|
spapi = SPAPI(card_id='CARD_ID', token='TOKEN')
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
user = await spapi.get_user(262632724928397312)
|
||||||
|
print(user.username, user.uuid)
|
||||||
|
for card in user.cards:
|
||||||
|
print(card.name, card.number)
|
||||||
|
|
||||||
|
|
||||||
|
loop = get_event_loop()
|
||||||
|
loop.run_until_complete(main())
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import pyspapi
|
|
||||||
|
|
||||||
import pyspapi
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
api = pyspapi.API(card_id='card_id', token='token')
|
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
|
||||||
uuid = await pyspapi.API(card_id='card_id', token='token').get_uuid(username='deesiigneer')
|
|
||||||
print(uuid)
|
|
||||||
print(uuid.id, uuid.name)
|
|
||||||
print(await pyspapi.API(card_id='card_id', token='token').get_uuids(['deesiigneer', '5opka', 'OsterMiner']))
|
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
loop.run_until_complete(main())
|
|
||||||
|
|
||||||
12
examples/me.py
Normal file
12
examples/me.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from pyspapi import SPAPI
|
||||||
|
from asyncio import get_event_loop
|
||||||
|
|
||||||
|
spapi = SPAPI(card_id='CARD_ID', token='TOKEN')
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
me = await spapi.me
|
||||||
|
print(me)
|
||||||
|
|
||||||
|
loop = get_event_loop()
|
||||||
|
loop.run_until_complete(main())
|
||||||
@@ -1,16 +1,21 @@
|
|||||||
import pyspapi
|
from pyspapi import SPAPI
|
||||||
import asyncio
|
from pyspapi.types import Item
|
||||||
|
from asyncio import get_event_loop
|
||||||
|
|
||||||
api = pyspapi.API(card_id='card_id', token='token')
|
spapi = SPAPI(card_id='CARD_ID', token='TOKEN')
|
||||||
|
|
||||||
|
items = [Item('first item', 1, 2, 'first item comment').to_json(),
|
||||||
|
Item('second item', 3, 4, 'second item comment').to_json()]
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
print(await api.payment(amount=1,
|
print(await spapi.create_payment(items=items,
|
||||||
redirect_url='https://www.google.com/',
|
redirect_url='https://www.google.com/',
|
||||||
webhook_url='https://www.google.com/',
|
webhook_url='https://www.google.com/',
|
||||||
data='some-data'
|
data='some-data'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
|
loop = get_event_loop()
|
||||||
loop.run_until_complete(main())
|
loop.run_until_complete(main())
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import pyspapi
|
from pyspapi import SPAPI
|
||||||
import asyncio
|
from asyncio import get_event_loop
|
||||||
|
|
||||||
api = pyspapi.API(card_id='CARD_ID', token='TOKEN')
|
spapi = SPAPI(card_id='CARD_ID', token='TOKEN')
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
print(await api.transaction(receiver=12345,
|
new_balance = await spapi.create_transaction(receiver='77552',
|
||||||
amount=1,
|
amount=1,
|
||||||
comment="test"
|
comment="test"
|
||||||
)
|
)
|
||||||
)
|
print(new_balance)
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
|
loop = get_event_loop()
|
||||||
loop.run_until_complete(main())
|
loop.run_until_complete(main())
|
||||||
|
|||||||
13
examples/webhook.py
Normal file
13
examples/webhook.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
from pyspapi import SPAPI
|
||||||
|
from asyncio import get_event_loop
|
||||||
|
|
||||||
|
spapi = SPAPI(card_id='CARD_ID', token='TOKEN')
|
||||||
|
|
||||||
|
# print(spapi.webhook_verify(data='webhook_data', header='webhook_header'))
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
print(await spapi.update_webhook(url='https://example.com/webhook'))
|
||||||
|
|
||||||
|
loop = get_event_loop()
|
||||||
|
loop.run_until_complete(main())
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import pyspapi
|
|
||||||
|
|
||||||
api = pyspapi.API(card_id='your_card_id', token='your_token')
|
|
||||||
|
|
||||||
api.listener(host='myhost.com', port=80, webhook_path='/webhook/')
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import pyspapi
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
api = pyspapi.API(card_id='your_card_id', token='your_token')
|
|
||||||
|
|
||||||
async def main():
|
|
||||||
print(await api.webhook_verify(data='webhook_data', header='webhook_header'))
|
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
loop.run_until_complete(main())
|
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
from .api import API
|
from .spworlds import *
|
||||||
from .types import SPUser, MojangProfile, Skin, UsernameToUUID
|
|
||||||
|
|
||||||
__version__ = "3.0.0a0"
|
|
||||||
|
__author__ = 'deesiigneer'
|
||||||
|
__url__ = 'https://github.com/deesiigneer/pyspapi'
|
||||||
|
__description__ = 'API wrapper for SP servers written in Python.'
|
||||||
|
__license__ = 'MIT'
|
||||||
|
__version__ = "3.1.0"
|
||||||
|
|||||||
182
pyspapi/api.py
182
pyspapi/api.py
@@ -1,182 +0,0 @@
|
|||||||
import ast
|
|
||||||
import warnings
|
|
||||||
import asyncio
|
|
||||||
from aiohttp import web
|
|
||||||
from sys import version_info
|
|
||||||
from base64 import b64encode, b64decode
|
|
||||||
from hmac import new, compare_digest
|
|
||||||
from hashlib import sha256
|
|
||||||
from logging import getLogger
|
|
||||||
from typing import Any, Dict, List, Optional, Union
|
|
||||||
from .types import SPUser, MojangProfile, UsernameToUUID
|
|
||||||
from .request import Request
|
|
||||||
from .errors import Error
|
|
||||||
|
|
||||||
log = getLogger('pyspapi')
|
|
||||||
|
|
||||||
|
|
||||||
class _BaseAPI:
|
|
||||||
|
|
||||||
_SPWORLDS = "https://spworlds.ru/api/public"
|
|
||||||
_API_MOJANG = "https://api.mojang.com"
|
|
||||||
_SESSIONSERVER_MOJANG = "https://sessionserver.mojang.com"
|
|
||||||
|
|
||||||
def __init__(self, card_id: str, token: str):
|
|
||||||
"""
|
|
||||||
:param card_id:
|
|
||||||
:param token:
|
|
||||||
"""
|
|
||||||
self._id = card_id
|
|
||||||
self._token = token
|
|
||||||
|
|
||||||
self._HEADER = {
|
|
||||||
'Authorization': f"Bearer {str(b64encode(str(f'{self._id}:{self._token}').encode('utf-8')), 'utf-8')}",
|
|
||||||
'User-Agent': f'pyspapi (https://github.com/deesiigneer/pyspapi) '
|
|
||||||
f'Python {version_info.major}.{version_info.minor}.{version_info.micro}'
|
|
||||||
}
|
|
||||||
|
|
||||||
async def webhook_verify(self, data: str, header) -> bool:
|
|
||||||
"""
|
|
||||||
Проверяет достоверность webhook'а. \n
|
|
||||||
:param data: data из webhook.
|
|
||||||
:param header: header X-Body-Hash из webhook.
|
|
||||||
:return: True если header из webhook'а достоверен, иначе False
|
|
||||||
"""
|
|
||||||
print()
|
|
||||||
hmac_data = b64encode(new(self._token.encode('utf-8'), data, sha256).digest())
|
|
||||||
return compare_digest(hmac_data, header.encode('utf-8'))
|
|
||||||
|
|
||||||
def listener(self, host: str = '127.0.0.1', port: int = 80, webhook_path: str = '/webhook/'):
|
|
||||||
app = web.Application()
|
|
||||||
async def handle(request):
|
|
||||||
request_data = await request.read()
|
|
||||||
header = request.headers.get('X-Body-Hash')
|
|
||||||
if header is not None:
|
|
||||||
if await self.webhook_verify(data=request_data, header=header) is True:
|
|
||||||
web.json_response(status=202)
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
web.json_response(status=400)
|
|
||||||
else:
|
|
||||||
return web.json_response(status=404)
|
|
||||||
app.add_routes([web.get(webhook_path, handle)])
|
|
||||||
web.run_app(app, port=port, host=host)
|
|
||||||
|
|
||||||
|
|
||||||
class API(_BaseAPI):
|
|
||||||
"""
|
|
||||||
class API
|
|
||||||
"""
|
|
||||||
|
|
||||||
async def get_user(self, user_id: int) -> Optional[SPUser]:
|
|
||||||
"""
|
|
||||||
Получение информации об игроке SP \n
|
|
||||||
:param user_id: ID пользователя в Discord.
|
|
||||||
:return: Class User.
|
|
||||||
"""
|
|
||||||
sp_user = await Request.get(f'{self._SPWORLDS}/users/{str(user_id)}', self._HEADER)
|
|
||||||
if sp_user is not None:
|
|
||||||
return SPUser(await Request.get(f'{self._SPWORLDS}/users/{str(user_id)}', self._HEADER))
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def get_users(self, user_ids: List[int]) -> Union[SPUser, Any]:
|
|
||||||
"""
|
|
||||||
Получение никнеймов игроков в майнкрафте. **Максимально можно указать 60 user_ids, не используйте эту функцию
|
|
||||||
чаще 1 раза в минуту если указали больше 60 user_ids**\n
|
|
||||||
https://spworlds.readthedocs.io/ru/latest/index.html#id3\n
|
|
||||||
:param user_ids: List[int] ID пользователей в Discord.
|
|
||||||
:return: List[str] который содержит майнкрафт никнеймы игроков в том же порядке, который был задан, None если
|
|
||||||
пользователь не найден или нет проходки.
|
|
||||||
"""
|
|
||||||
if len(user_ids) > 60:
|
|
||||||
user_ids = user_ids[:60]
|
|
||||||
warnings.warn('user_ids больше чем 60. Уменьшено до 60.')
|
|
||||||
tasks = []
|
|
||||||
for user_id in user_ids:
|
|
||||||
tasks.append(self.get_user(user_id))
|
|
||||||
return await asyncio.gather(*tasks, return_exceptions=True)
|
|
||||||
|
|
||||||
async def get_uuid(self, username: str) -> Optional[UsernameToUUID]:
|
|
||||||
"""
|
|
||||||
Получить UUID игрока Minecraft.\n
|
|
||||||
:param username: str никнейм игрока Minecraft.
|
|
||||||
:return: Optional[str] UUID игрока Minecraft.
|
|
||||||
"""
|
|
||||||
response = await Request.get(f'{self._API_MOJANG}/users/profiles/minecraft/{username}')
|
|
||||||
return UsernameToUUID(await Request.get(f'{self._API_MOJANG}/users/profiles/minecraft/{username}'))
|
|
||||||
|
|
||||||
async def get_uuids(self, usernames: list[str]) -> Dict[str, str]:
|
|
||||||
"""
|
|
||||||
Получить UUID's игроков Minecraft. **Не больше 10**\n
|
|
||||||
:param usernames: List[str] Список с никнеймами игроков Minecraft.
|
|
||||||
:return: Dict[str, str] UUID игроков Minecraft.
|
|
||||||
"""
|
|
||||||
if len(usernames) > 10:
|
|
||||||
usernames = usernames[:10]
|
|
||||||
warnings.warn('usernames больше чем 10. Уменьшено до 10.')
|
|
||||||
return await Request.post(f'{self._API_MOJANG}/profiles/minecraft', payload=usernames)
|
|
||||||
|
|
||||||
async def get_name_history(self, uuid: str) -> List[Dict[str, Any]]:
|
|
||||||
"""
|
|
||||||
История никнеймов в Minecraft.\n
|
|
||||||
:param uuid: UUID игрока Minecraft.
|
|
||||||
:return: List[Dict[str, Any]] который содержит name и changed_to_at
|
|
||||||
"""
|
|
||||||
requests = await Request.get(f"{self._API_MOJANG}/user/profiles/{uuid}/names")
|
|
||||||
|
|
||||||
name_data = []
|
|
||||||
for data in requests:
|
|
||||||
name_data_dict = {"name": data["name"]}
|
|
||||||
if data.get("changedToAt"):
|
|
||||||
name_data_dict["changed_to_at"] = data["changedToAt"]
|
|
||||||
else:
|
|
||||||
name_data_dict["changed_to_at"] = 0
|
|
||||||
name_data.append(name_data_dict)
|
|
||||||
return name_data
|
|
||||||
|
|
||||||
async def get_profile(self, uuid: str) -> MojangProfile:
|
|
||||||
response = await Request.get(f'{self._SESSIONSERVER_MOJANG}/session/minecraft/profile/{uuid}')
|
|
||||||
return MojangProfile(ast.literal_eval(b64decode(response["properties"][0]["value"]).decode()))
|
|
||||||
|
|
||||||
async def payment(self, amount: int, redirect_url: str, webhook_url: str, data: str) -> Optional[str]:
|
|
||||||
"""
|
|
||||||
Создание ссылки для оплаты.\n
|
|
||||||
:param amount: Стоимость покупки в АРах.
|
|
||||||
:param redirect_url: URL страницы, на которую попадет пользователь после оплаты.
|
|
||||||
:param webhook_url: URL, куда наш сервер направит запрос, чтобы оповестить ваш сервер об успешной оплате.
|
|
||||||
:param data: Строка до 100 символов, сюда можно поместить любые полезные данных.
|
|
||||||
:return: Ссылку на страницу оплаты, на которую стоит перенаправить пользователя.
|
|
||||||
"""
|
|
||||||
if len(data) > 100:
|
|
||||||
raise Error('В data больше 100 символов')
|
|
||||||
return await Request.post(f'{self._SPWORLDS}/payment',
|
|
||||||
payload={
|
|
||||||
'amount': amount,
|
|
||||||
'redirectUrl': redirect_url,
|
|
||||||
'webhookUrl': webhook_url,
|
|
||||||
'data': data},
|
|
||||||
headers=self._HEADER)
|
|
||||||
|
|
||||||
async def transaction(self, receiver: int, amount: int, comment: str) -> Optional[str]:
|
|
||||||
"""
|
|
||||||
Перевод АР на карту. \n
|
|
||||||
:param receiver: Номер карты получателя.
|
|
||||||
:param amount: Количество АР для перевода.
|
|
||||||
:param comment: Комментарий для перевода.
|
|
||||||
:return: True если перевод успешен, иначе False.
|
|
||||||
"""
|
|
||||||
return 'Удачно' if await Request.post(f'{self._SPWORLDS}/transactions',
|
|
||||||
payload={'receiver': receiver,
|
|
||||||
'amount': amount,
|
|
||||||
'comment': comment},
|
|
||||||
headers=self._HEADER) else 'Что-то пошло не так...'
|
|
||||||
|
|
||||||
@property
|
|
||||||
async def balance(self) -> Optional[int]:
|
|
||||||
"""
|
|
||||||
Проверка баланса карты \n
|
|
||||||
:return: Количество АР на карте.
|
|
||||||
"""
|
|
||||||
balance = await Request.get(f'{self._SPWORLDS}/card', headers=self._HEADER)
|
|
||||||
return balance['balance']
|
|
||||||
2
pyspapi/api/__init__.py
Normal file
2
pyspapi/api/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from .api import APISession
|
||||||
|
|
||||||
90
pyspapi/api/api.py
Normal file
90
pyspapi/api/api.py
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
from base64 import b64encode
|
||||||
|
from logging import getLogger
|
||||||
|
import aiohttp
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
log = getLogger('pyspapi')
|
||||||
|
|
||||||
|
|
||||||
|
class APISession(object):
|
||||||
|
""" Holds aiohttp session for its lifetime and wraps different types of request """
|
||||||
|
|
||||||
|
def __init__(self, card_id: str, token: str, timeout=5, sleep_time=0.2, retries=0):
|
||||||
|
self.__url = "https://spworlds.ru/"
|
||||||
|
self.__id = card_id
|
||||||
|
self.__token = token
|
||||||
|
self.__sleep_timeout = sleep_time
|
||||||
|
self.__retries = retries
|
||||||
|
self.__timeout = timeout
|
||||||
|
self.session = None
|
||||||
|
|
||||||
|
async def __aenter__(self):
|
||||||
|
self.session = aiohttp.ClientSession(
|
||||||
|
json_serialize=json.dumps,
|
||||||
|
timeout=aiohttp.ClientTimeout(total=self.__timeout))
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def __aexit__(self, *err):
|
||||||
|
await self.session.close()
|
||||||
|
self.session = None
|
||||||
|
|
||||||
|
def __get_url(self, endpoint: str) -> str:
|
||||||
|
""" Get URL for requests """
|
||||||
|
url = self.__url
|
||||||
|
api = "api/public"
|
||||||
|
return f"{url}{api}/{endpoint}"
|
||||||
|
|
||||||
|
async def __request(self, method: str, endpoint: str, data=None):
|
||||||
|
url = self.__get_url(endpoint)
|
||||||
|
response = await self.session.request(
|
||||||
|
method=method,
|
||||||
|
url=url,
|
||||||
|
json=data,
|
||||||
|
headers={'Authorization': f"Bearer {str(b64encode(str(f'{self.__id}:{self.__token}').encode('utf-8')), 'utf-8')}",
|
||||||
|
'User-Agent': 'https://github.com/deesiigneer/pyspapi'},
|
||||||
|
ssl=True
|
||||||
|
)
|
||||||
|
if response.status not in [200, 201]:
|
||||||
|
message = await response.json()
|
||||||
|
raise aiohttp.ClientResponseError(
|
||||||
|
code=response.status,
|
||||||
|
message=message['message'],
|
||||||
|
headers=response.headers,
|
||||||
|
history=response.history,
|
||||||
|
request_info=response.request_info
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
|
||||||
|
async def get(self, endpoint, **kwargs):
|
||||||
|
""" GET requests """
|
||||||
|
try:
|
||||||
|
return await self.__request("GET", endpoint, None, **kwargs)
|
||||||
|
except aiohttp.ClientResponseError as e:
|
||||||
|
log.error(f"GET request to {endpoint} failed with status {e.status}: {e.message}")
|
||||||
|
except aiohttp.ClientError as e:
|
||||||
|
log.error(f"GET request to {endpoint} failed: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"GET request to {endpoint} failed: {e}")
|
||||||
|
|
||||||
|
async def post(self, endpoint, data, **kwargs):
|
||||||
|
""" POST requests """
|
||||||
|
try:
|
||||||
|
return await self.__request("POST", endpoint, data, **kwargs)
|
||||||
|
except aiohttp.ClientResponseError as e:
|
||||||
|
log.error(f"POST request to {endpoint} failed with status {e.status}: {e.message}")
|
||||||
|
except aiohttp.ClientError as e:
|
||||||
|
log.error(f"POST request to {endpoint} failed: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"POST request to {endpoint} failed: {e}")
|
||||||
|
|
||||||
|
async def put(self, endpoint, data, **kwargs):
|
||||||
|
""" PUT requests """
|
||||||
|
try:
|
||||||
|
return await self.__request("PUT", endpoint, data, **kwargs)
|
||||||
|
except aiohttp.ClientResponseError as e:
|
||||||
|
log.error(f"PUT request to {endpoint} failed with status {e.status}: {e.message}")
|
||||||
|
except aiohttp.ClientError as e:
|
||||||
|
log.error(f"PUT request to {endpoint} failed: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"PUT request to {endpoint} failed: {e}")
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
class Error(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Unauthorized(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class NotFound(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class TooManyRequests(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class UserNotFound(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def handle(response):
|
|
||||||
if response['error'] == 'Unauthorized':
|
|
||||||
raise Unauthorized(response['message'])
|
|
||||||
elif response['error'] == 'Not Found':
|
|
||||||
raise NotFound(response['message'])
|
|
||||||
elif response['error'] == 'Too Many Requests':
|
|
||||||
raise TooManyRequests(response['message'])
|
|
||||||
else:
|
|
||||||
raise Exception(response)
|
|
||||||
0
pyspapi/exceptions.py
Normal file
0
pyspapi/exceptions.py
Normal file
@@ -1,31 +0,0 @@
|
|||||||
import aiohttp
|
|
||||||
from .errors import handle
|
|
||||||
from typing import Coroutine, Any, TypeVar
|
|
||||||
|
|
||||||
T = TypeVar("T")
|
|
||||||
Response = Coroutine[Any, Any, T]
|
|
||||||
|
|
||||||
class Request:
|
|
||||||
async def get(url: str, headers=None):
|
|
||||||
async with aiohttp.ClientSession() as session:
|
|
||||||
async with session.get(url, headers=headers) as session_response:
|
|
||||||
try:
|
|
||||||
response = await session_response.json()
|
|
||||||
except:
|
|
||||||
response = await session_response.text()
|
|
||||||
if session_response.status == 404:
|
|
||||||
return None
|
|
||||||
if not session_response.ok:
|
|
||||||
handle(response)
|
|
||||||
return response
|
|
||||||
|
|
||||||
async def post(url: str, payload, headers=None):
|
|
||||||
async with aiohttp.ClientSession() as session:
|
|
||||||
session_response = await session.post(url, json=payload, headers=headers)
|
|
||||||
try:
|
|
||||||
response = await session_response.json()
|
|
||||||
except:
|
|
||||||
response = await session_response.text()
|
|
||||||
if not session_response.ok:
|
|
||||||
handle(response)
|
|
||||||
return response
|
|
||||||
205
pyspapi/spworlds.py
Normal file
205
pyspapi/spworlds.py
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
from .api import APISession
|
||||||
|
from .types import User
|
||||||
|
from .types.me import Account
|
||||||
|
from .types.payment import Item
|
||||||
|
from hmac import new, compare_digest
|
||||||
|
from hashlib import sha256
|
||||||
|
from base64 import b64encode
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
|
__all__ = ['SPAPI']
|
||||||
|
|
||||||
|
|
||||||
|
class SPAPI(APISession):
|
||||||
|
"""
|
||||||
|
Представляет собой клиент API для взаимодействия с конкретным сервисом.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, card_id=None, token=None, timeout=5, sleep_time=0.2, retries=0):
|
||||||
|
"""
|
||||||
|
Инициализирует объект SPAPI.
|
||||||
|
|
||||||
|
:param card_id: Идентификатор карты.
|
||||||
|
:type card_id: str
|
||||||
|
:param token: Токен API.
|
||||||
|
:type token: str
|
||||||
|
:param timeout: Таймаут для запросов API в секундах. По умолчанию 5.
|
||||||
|
:type timeout: int, optional
|
||||||
|
:param sleep_time: Время ожидания между повторными запросами в секундах. По умолчанию 0.2.
|
||||||
|
:type sleep_time: float, optional
|
||||||
|
:param retries: Количество повторных попыток для неудачных запросов. По умолчанию 0.
|
||||||
|
:type retries: int, optional
|
||||||
|
"""
|
||||||
|
super().__init__(card_id, token, timeout, sleep_time, retries)
|
||||||
|
self.__card_id = card_id
|
||||||
|
self.__token = token
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
"""
|
||||||
|
Возвращает строковое представление объекта SPAPI.
|
||||||
|
"""
|
||||||
|
return "%s(%s)" % (
|
||||||
|
self.__class__.__name__,
|
||||||
|
self.__dict__
|
||||||
|
)
|
||||||
|
|
||||||
|
async def __get(self, method):
|
||||||
|
"""
|
||||||
|
Выполняет GET-запрос к API.
|
||||||
|
|
||||||
|
:param method: Метод API для вызова.
|
||||||
|
:type method: str
|
||||||
|
|
||||||
|
:return: JSON-ответ от API.
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
async with APISession(self.__card_id, self.__token) as session:
|
||||||
|
response = await session.get(method)
|
||||||
|
response = await response.json()
|
||||||
|
return response
|
||||||
|
|
||||||
|
@property
|
||||||
|
async def balance(self):
|
||||||
|
"""
|
||||||
|
Получает текущий баланс карты.
|
||||||
|
|
||||||
|
:return: Текущий баланс карты.
|
||||||
|
:rtype: int
|
||||||
|
"""
|
||||||
|
card = await self.__get('card')
|
||||||
|
return card['balance']
|
||||||
|
|
||||||
|
@property
|
||||||
|
async def webhook(self) -> str:
|
||||||
|
"""
|
||||||
|
Получает URL вебхука, связанного с картой.
|
||||||
|
|
||||||
|
:return: URL вебхука.
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
card = await self.__get('card')
|
||||||
|
return card['webhook']
|
||||||
|
|
||||||
|
@property
|
||||||
|
async def me(self):
|
||||||
|
"""
|
||||||
|
Получает информацию об аккаунте текущего пользователя.
|
||||||
|
|
||||||
|
:return: Объект Account, представляющий аккаунт текущего пользователя.
|
||||||
|
:rtype: Account
|
||||||
|
"""
|
||||||
|
me = await self.__get('account/me')
|
||||||
|
return Account(
|
||||||
|
account_id=me['id'],
|
||||||
|
username=me['username'],
|
||||||
|
status=me['status'],
|
||||||
|
roles=me['roles'],
|
||||||
|
city=me['city'],
|
||||||
|
cards=me['cards'],
|
||||||
|
created_at=me['createdAt'])
|
||||||
|
|
||||||
|
async def get_user(self, discord_id: int) -> User:
|
||||||
|
"""
|
||||||
|
Получает информацию о пользователе по его ID в Discord.
|
||||||
|
|
||||||
|
:param discord_id: ID пользователя в Discord.
|
||||||
|
:type discord_id: int
|
||||||
|
|
||||||
|
:return: Объект User, представляющий пользователя.
|
||||||
|
:rtype: User
|
||||||
|
"""
|
||||||
|
user = await self.__get(f'users/{discord_id}')
|
||||||
|
cards = await self.__get(f"accounts/{user['username']}/cards")
|
||||||
|
return User(user['username'], user['uuid'], cards)
|
||||||
|
|
||||||
|
async def create_transaction(self, receiver: str, amount: int, comment: str):
|
||||||
|
"""
|
||||||
|
Создает транзакцию.
|
||||||
|
|
||||||
|
:param receiver: Получатель транзакции.
|
||||||
|
:type receiver: str
|
||||||
|
:param amount: Сумма транзакции.
|
||||||
|
:type amount: int
|
||||||
|
:param comment: Комментарий к транзакции.
|
||||||
|
:type comment: str
|
||||||
|
|
||||||
|
:return: Баланс после транзакции.
|
||||||
|
:rtype: int
|
||||||
|
"""
|
||||||
|
async with APISession(self.__card_id, self.__token) as session:
|
||||||
|
data = {
|
||||||
|
'receiver': receiver,
|
||||||
|
'amount': amount,
|
||||||
|
'comment': comment
|
||||||
|
}
|
||||||
|
res = await session.post('transactions', data)
|
||||||
|
res = await res.json()
|
||||||
|
return res['balance']
|
||||||
|
|
||||||
|
async def create_payment(self, webhook_url: str, redirect_url: str, data: str, items) -> str:
|
||||||
|
"""
|
||||||
|
Создает платеж.
|
||||||
|
|
||||||
|
:param webhook_url: URL вебхука для платежа.
|
||||||
|
:type webhook_url: str
|
||||||
|
:param redirect_url: URL для перенаправления после платежа.
|
||||||
|
:type redirect_url: str
|
||||||
|
:param data: Дополнительные данные для платежа.
|
||||||
|
:type data: str
|
||||||
|
:param items: Элементы, включаемые в платеж.
|
||||||
|
|
||||||
|
:return: URL для платежа.
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
async with APISession(self.__card_id, self.__token) as session:
|
||||||
|
data = {
|
||||||
|
'items': items,
|
||||||
|
'redirectUrl': redirect_url,
|
||||||
|
'webhookUrl': webhook_url,
|
||||||
|
'data': data
|
||||||
|
}
|
||||||
|
res = await session.post('payments',data)
|
||||||
|
res = await res.json()
|
||||||
|
return res['url']
|
||||||
|
|
||||||
|
async def update_webhook(self, url: str):
|
||||||
|
"""
|
||||||
|
Обновляет URL вебхука, связанного с картой.
|
||||||
|
|
||||||
|
:param url: Новый URL вебхука.
|
||||||
|
:type url: str
|
||||||
|
|
||||||
|
:return: JSON-ответ от API.
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
async with APISession(self.__card_id, self.__token) as session:
|
||||||
|
data = {
|
||||||
|
'url': url
|
||||||
|
}
|
||||||
|
res = await session.put(endpoint='card/webhook', data=data)
|
||||||
|
if res:
|
||||||
|
res = await res.json()
|
||||||
|
return res
|
||||||
|
|
||||||
|
def webhook_verify(self, data: str, header) -> bool:
|
||||||
|
"""
|
||||||
|
Проверяет достоверность вебхука.
|
||||||
|
|
||||||
|
:param data: Данные из вебхука.
|
||||||
|
:type data: str
|
||||||
|
:param header: Заголовок X-Body-Hash из вебхука.
|
||||||
|
|
||||||
|
: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'))
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
"""
|
||||||
|
Преобразует объект SPAPI в словарь.
|
||||||
|
|
||||||
|
:return: Словарное представление объекта SPAPI.
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
return self.__dict__.copy()
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
class SPUser:
|
|
||||||
def __init__(self, data: dict):
|
|
||||||
self.__data = data
|
|
||||||
|
|
||||||
@property
|
|
||||||
def access(self) -> bool:
|
|
||||||
return True if self.__data['username'] is not None else False
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return self.__data['username']
|
|
||||||
|
|
||||||
|
|
||||||
class MojangProfile:
|
|
||||||
def __init__(self, data: dict):
|
|
||||||
self.__data = data
|
|
||||||
self.skin = Skin(data)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def id(self) -> str:
|
|
||||||
return self.__data['profileId']
|
|
||||||
|
|
||||||
@property
|
|
||||||
def timestamp(self):
|
|
||||||
return self.__data['timestamp']
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return self.__data['profileName']
|
|
||||||
|
|
||||||
|
|
||||||
class Skin:
|
|
||||||
def __init__(self, data: dict):
|
|
||||||
self.__data = data
|
|
||||||
|
|
||||||
@property
|
|
||||||
def url(self) -> str:
|
|
||||||
return self.__data['textures']['SKIN']['url']
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cape_url(self) -> str:
|
|
||||||
return self.__data['textures']['CAPE']['url']
|
|
||||||
|
|
||||||
@property
|
|
||||||
def model(self) -> str:
|
|
||||||
return 'classic' if self.__data['textures']['SKIN'].get('metadata') is None else 'slim'
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return str(self.__data['textures']['SKIN'])
|
|
||||||
|
|
||||||
|
|
||||||
class UsernameToUUID:
|
|
||||||
def __init__(self, data: dict):
|
|
||||||
self.__data = data
|
|
||||||
|
|
||||||
@property
|
|
||||||
def id(self):
|
|
||||||
return self.__data['id']
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
return self.__data['name']
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return str(self.__data['id'])
|
|
||||||
3
pyspapi/types/__init__.py
Normal file
3
pyspapi/types/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from .payment import Item
|
||||||
|
from .users import User
|
||||||
|
from .me import Account
|
||||||
120
pyspapi/types/me.py
Normal file
120
pyspapi/types/me.py
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
class City:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
city_id=None,
|
||||||
|
name=None,
|
||||||
|
description=None,
|
||||||
|
x_cord=None,
|
||||||
|
z_cord=None,
|
||||||
|
is_mayor=None,
|
||||||
|
):
|
||||||
|
self._id = city_id
|
||||||
|
self._name = name
|
||||||
|
self._description = description
|
||||||
|
self._x_cord = x_cord
|
||||||
|
self._z_cord = z_cord
|
||||||
|
self._isMayor = is_mayor
|
||||||
|
|
||||||
|
@property
|
||||||
|
def id(self):
|
||||||
|
return self._id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self):
|
||||||
|
return self._description
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def x_cord(self):
|
||||||
|
return self._x_cord
|
||||||
|
|
||||||
|
@property
|
||||||
|
def z_cord(self):
|
||||||
|
return self._z_cord
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mayor(self):
|
||||||
|
return self._isMayor
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"City(id={self.id}, name={self.name}, description={self.description}, x={self.x_cord}, z={self.z_cord}, is_mayor={self.mayor})"
|
||||||
|
|
||||||
|
|
||||||
|
class Cards:
|
||||||
|
def __init__(self, card_id=None, name=None, number=None, color=None):
|
||||||
|
self._id = card_id
|
||||||
|
self._name = name
|
||||||
|
self._number = number
|
||||||
|
self._color = color
|
||||||
|
|
||||||
|
@property
|
||||||
|
def id(self):
|
||||||
|
return self._id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def number(self):
|
||||||
|
return self._number
|
||||||
|
|
||||||
|
@property
|
||||||
|
def color(self):
|
||||||
|
return self._color
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"Card(id={self.id}, name={self.name}, number={self.number}, color={self.color})"
|
||||||
|
|
||||||
|
|
||||||
|
class Account:
|
||||||
|
def __init__(self, account_id, username, status, roles, created_at, cards, city):
|
||||||
|
self._id = account_id
|
||||||
|
self._username = username
|
||||||
|
self._status = status
|
||||||
|
self._roles = roles
|
||||||
|
self._city = City(**city) if city else None
|
||||||
|
self._cards = [
|
||||||
|
Cards(
|
||||||
|
card_id=card["id"],
|
||||||
|
name=card["name"],
|
||||||
|
number=card["number"],
|
||||||
|
color=card["color"],
|
||||||
|
)
|
||||||
|
for card in cards
|
||||||
|
]
|
||||||
|
self._created_at = created_at
|
||||||
|
|
||||||
|
@property
|
||||||
|
def id(self):
|
||||||
|
return self._id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def username(self):
|
||||||
|
return self._username
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status(self):
|
||||||
|
return self._status
|
||||||
|
|
||||||
|
@property
|
||||||
|
def roles(self):
|
||||||
|
return self._roles
|
||||||
|
|
||||||
|
@property
|
||||||
|
def city(self):
|
||||||
|
return self._city
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cards(self):
|
||||||
|
return self._cards
|
||||||
|
|
||||||
|
@property
|
||||||
|
def created_at(self):
|
||||||
|
return self._created_at
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"Account(id={self.id}, username={self.username}, status={self.status}, roles={self.roles}, city={self.city}, cards={self.cards}, created_at={self.created_at})"
|
||||||
18
pyspapi/types/payment.py
Normal file
18
pyspapi/types/payment.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
class Item:
|
||||||
|
def __init__(self, name: str, count: int, price: int, comment: str):
|
||||||
|
self._name = name
|
||||||
|
self._count = count
|
||||||
|
self._price = price
|
||||||
|
self._comment = comment
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
return {
|
||||||
|
"name": self._name,
|
||||||
|
"count": self._count,
|
||||||
|
"price": self._price,
|
||||||
|
"comment": self._comment
|
||||||
|
}
|
||||||
51
pyspapi/types/users.py
Normal file
51
pyspapi/types/users.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
class Cards:
|
||||||
|
|
||||||
|
def __init__(self, name, number):
|
||||||
|
self._name: str = name
|
||||||
|
self._number: str = number
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def number(self):
|
||||||
|
return self._number
|
||||||
|
|
||||||
|
# def __repr__(self):
|
||||||
|
# return "%s(%s)" % (
|
||||||
|
# self.__class__.__name__,
|
||||||
|
# self.__dict__
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
|
class User:
|
||||||
|
|
||||||
|
def __init__(self, username, uuid, cards):
|
||||||
|
self._username: str = username
|
||||||
|
self._uuid: str = uuid
|
||||||
|
self._cards = [
|
||||||
|
Cards(
|
||||||
|
name=card["name"],
|
||||||
|
number=card["number"],
|
||||||
|
)
|
||||||
|
for card in cards
|
||||||
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def username(self):
|
||||||
|
return self._username
|
||||||
|
|
||||||
|
@property
|
||||||
|
def uuid(self):
|
||||||
|
return self._uuid
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cards(self):
|
||||||
|
return self._cards
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "%s(%s)" % (
|
||||||
|
self.__class__.__name__,
|
||||||
|
self.__dict__
|
||||||
|
)
|
||||||
@@ -1,2 +1 @@
|
|||||||
requests==2.28.1
|
aiohttp>=3.8.0
|
||||||
aiohttp>=3.8.0,<4.0.0
|
|
||||||
3
setup.py
3
setup.py
@@ -32,8 +32,7 @@ setup(
|
|||||||
version=version,
|
version=version,
|
||||||
url='https://github.com/deesiigneer/pyspapi',
|
url='https://github.com/deesiigneer/pyspapi',
|
||||||
project_urls={
|
project_urls={
|
||||||
"pyspapi documentation": "https://pyspapi.readthedocs.io/",
|
"Documentation": "https://pyspapi.readthedocs.io/ru/latest/",
|
||||||
"api documentation": "https://spworlds.readthedocs.io/",
|
|
||||||
"GitHub": "https://github.com/deesiigneer/pyspapi",
|
"GitHub": "https://github.com/deesiigneer/pyspapi",
|
||||||
"Discord": "https://discord.com/invite/VbyHaKRAaN"
|
"Discord": "https://discord.com/invite/VbyHaKRAaN"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user