mirror of
https://github.com/deesiigneer/pyspapi.git
synced 2026-04-20 12:35:26 +00:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b738db3252 | ||
|
|
b3d56a6059 | ||
| 18c1ff1daf | |||
|
|
eff14052fd | ||
|
|
3f935a060b | ||
|
|
3ecf1fff8a | ||
|
|
74a46277f8 | ||
|
|
63ee509067 | ||
|
|
4ecafef192 | ||
|
|
d44199c4cc | ||
|
|
056ebce615 | ||
|
|
b00b2b76cd | ||
|
|
da86771e3c | ||
|
|
479a02b95f | ||
|
|
5c905ea097 | ||
|
|
83d9663da3 | ||
|
|
93760d2d87 | ||
|
|
108d30ebac | ||
|
|
b4ddd1fd93 | ||
|
|
5449659566 | ||
|
|
85ef993011 | ||
|
|
8e6664bc51 | ||
|
|
0d46f0a126 | ||
|
|
f5484d3900 | ||
|
|
1c35daf142 | ||
|
|
91ed017ac6 | ||
|
|
1806ba7929 | ||
|
|
f4623e9204 | ||
|
|
906060a4b7 | ||
|
|
6bf342a9f1 | ||
|
|
a6cbf2adcc | ||
|
|
59d43736c0 | ||
|
|
9429c443c4 | ||
|
|
152b5272c1 | ||
|
|
81d8b1de35 | ||
|
|
0667922c8c | ||
|
|
9a61797253 | ||
|
|
c9c1bc8e81 |
25
.github/workflows/python-publish.yml
vendored
25
.github/workflows/python-publish.yml
vendored
@@ -12,28 +12,27 @@ on:
|
|||||||
release:
|
release:
|
||||||
types: [published]
|
types: [published]
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
regular:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python-version: [ 3.9 ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Set up Python
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v3
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: '3.x'
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install build
|
pip install twine
|
||||||
- name: Build package
|
- name: Compile package
|
||||||
run: python -m build
|
run: |
|
||||||
|
python3 setup.py sdist
|
||||||
- name: Publish package
|
- name: Publish package
|
||||||
uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
with:
|
with:
|
||||||
user: __token__
|
user: __token__
|
||||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||||
|
|||||||
21
.readthedocs.yml
Normal file
21
.readthedocs.yml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
version: 2
|
||||||
|
formats: []
|
||||||
|
|
||||||
|
build:
|
||||||
|
os: ubuntu-lts-latest
|
||||||
|
tools:
|
||||||
|
python: '3.8'
|
||||||
|
|
||||||
|
|
||||||
|
sphinx:
|
||||||
|
configuration: docs/conf.py
|
||||||
|
fail_on_warning: false
|
||||||
|
builder: html
|
||||||
|
|
||||||
|
python:
|
||||||
|
install:
|
||||||
|
- method: pip
|
||||||
|
path: .
|
||||||
|
extra_requirements:
|
||||||
|
- docs
|
||||||
|
- requirements: docs/requirements.txt
|
||||||
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
|
||||||
|
|||||||
3
MANIFEST.in
Normal file
3
MANIFEST.in
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
include README.rst
|
||||||
|
include LICENSE
|
||||||
|
include requirements.txt
|
||||||
104
README.md
104
README.md
@@ -1,104 +0,0 @@
|
|||||||
# pyspapi
|
|
||||||
[API](https://github.com/sp-worlds/api-docs) обертка для серверов СП, написанная на Python.
|
|
||||||
|
|
||||||
## Установка
|
|
||||||
**Требуется *Python 3.7* или выше**
|
|
||||||
|
|
||||||
*Windows*
|
|
||||||
```commandline
|
|
||||||
pip install pyspapi
|
|
||||||
```
|
|
||||||
*Linux*
|
|
||||||
```commandline
|
|
||||||
sudo apt pip3 install pyspapi
|
|
||||||
```
|
|
||||||
|
|
||||||
## Примеры
|
|
||||||
### [Оплата](https://github.com/sp-worlds/api-docs/blob/main/PAYMENTS.md)
|
|
||||||
|
|
||||||
```Python
|
|
||||||
import spapi
|
|
||||||
|
|
||||||
api = spapi.Api(card_id='CARD_ID',
|
|
||||||
token='TOKEN')
|
|
||||||
|
|
||||||
print(api.payment(amount=1,
|
|
||||||
redirecturl='https://www.google.com/',
|
|
||||||
webhookurl='https://www.yourwebhook.com/',
|
|
||||||
data='Какие-то данные'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
```
|
|
||||||
- `amount` - Стоимость покупки в АРах
|
|
||||||
- `redirectUrl` - URL страницы, на которую попадет пользователь после оплаты
|
|
||||||
- `webhookUrl` - URL, куда наш сервер направит запрос, чтобы оповестить ваш сервер об успешной оплате
|
|
||||||
- `data` - Строка до 100 символов, сюда можно поместить любые полезные данных.
|
|
||||||
#### [Получение данных об успешной оплате](https://github.com/sp-worlds/api-docs/blob/main/PAYMENTS.md#%D0%BF%D0%BE%D0%BB%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D0%B5-%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85-%D0%BE%D0%B1-%D1%83%D1%81%D0%BF%D0%B5%D1%88%D0%BD%D0%BE%D0%B9-%D0%BE%D0%BF%D0%BB%D0%B0%D1%82%D0%B5)
|
|
||||||
После успешной оплаты на URL указанный в `webhookUrl` придет POST запрос.
|
|
||||||
|
|
||||||
*Тело запроса будет в формате JSON:*
|
|
||||||
|
|
||||||
- `payer` - Ник игрока, который совершил оплату
|
|
||||||
- `amount` - Стоимость покупки
|
|
||||||
- `data` - Данные, которые вы отдали при создании запроса на оплату
|
|
||||||
|
|
||||||
Для проверки достоверности webhook'a используйте:
|
|
||||||
```Python
|
|
||||||
import spapi
|
|
||||||
|
|
||||||
api = spapi.Api(card_id='CARD_ID',
|
|
||||||
token='TOKEN')
|
|
||||||
|
|
||||||
print(api.webhook_verify(data='webhook_data',
|
|
||||||
header='webhook_header'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
```
|
|
||||||
В ответ вы получите:
|
|
||||||
- True - webhook достоверен
|
|
||||||
- False - webhook не является достоверным
|
|
||||||
|
|
||||||
### [Переводы](https://github.com/sp-worlds/api-docs/blob/main/TRANSACTIONS.md)
|
|
||||||
|
|
||||||
```Python
|
|
||||||
import spapi
|
|
||||||
|
|
||||||
api = spapi.Api(card_id='CARD_ID',
|
|
||||||
token='TOKEN')
|
|
||||||
|
|
||||||
print(api.transaction(receiver='12345',
|
|
||||||
amount=1,
|
|
||||||
comment="test"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
```
|
|
||||||
- `receiver` - Номер карты получателя
|
|
||||||
- `amount` - Количество АР для перевода
|
|
||||||
- `comment` - Комментарий к переводу
|
|
||||||
|
|
||||||
### [Проверка наличия проходки](https://github.com/sp-worlds/api-docs/blob/main/USERS.md)
|
|
||||||
|
|
||||||
```Python
|
|
||||||
import spapi
|
|
||||||
|
|
||||||
api = spapi.Api(card_id='CARD_ID',
|
|
||||||
token='TOKEN')
|
|
||||||
|
|
||||||
print(api.check_user(discord_user_id=123456789012345678)
|
|
||||||
)
|
|
||||||
|
|
||||||
```
|
|
||||||
- `discord_user_id` - ID пользователя в Discord.
|
|
||||||
|
|
||||||
*В ответ вы получите JSON:*
|
|
||||||
|
|
||||||
- `username` - Ник пользователя или null, если у пользователя нет входа на сервер.
|
|
||||||
|
|
||||||
|
|
||||||
## Ссылки
|
|
||||||
|
|
||||||
- [Discord сервер разработчика](https://discord.gg/sJYtYnhN)
|
|
||||||
- [Документация API сайтов СП](https://github.com/sp-worlds/api-docs)
|
|
||||||
69
README.rst
Normal file
69
README.rst
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
.. image:: https://raw.githubusercontent.com/deesiigneer/pyspapi/main/assets/repo-banner.png
|
||||||
|
:alt: pyspapi
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/discord/850091193190973472?color=5865F2&label=discord
|
||||||
|
:target: https://discord.gg/VbyHaKRAaN
|
||||||
|
:alt: Discord server invite
|
||||||
|
.. image:: https://img.shields.io/github/v/release/deesiigneer/pyspapi?include_prereleases&label=github%20release
|
||||||
|
:target: https://github.com/deesiigneer/pyspapi/
|
||||||
|
:alt: GitHub release (latest by date including pre-releases)
|
||||||
|
.. image:: https://img.shields.io/pypi/v/pyspapi.svg
|
||||||
|
:target: https://pypi.org/project/pyspapi/
|
||||||
|
:alt: PyPI downloads info
|
||||||
|
.. image:: https://img.shields.io/pypi/dm/pyspapi?color=informational&label=pypi%20downloads
|
||||||
|
:target: https://pypi.org/project/pyspapi/
|
||||||
|
:alt: PyPI version info
|
||||||
|
.. image:: https://img.shields.io/readthedocs/pyspapi
|
||||||
|
:target: https://pyspapi.readthedocs.io/
|
||||||
|
:alt: pyspapi documentation
|
||||||
|
|
||||||
|
pyspapi
|
||||||
|
========
|
||||||
|
|
||||||
|
`API <https://github.com/sp-worlds/api-docs>`_ wrapper for SP servers written in Python.
|
||||||
|
|
||||||
|
Installation
|
||||||
|
-------------
|
||||||
|
**Requires Python 3.8 or higher**
|
||||||
|
|
||||||
|
*Windows*
|
||||||
|
|
||||||
|
|
||||||
|
.. code:: sh
|
||||||
|
|
||||||
|
pip install pyspapi
|
||||||
|
|
||||||
|
*Linux/macOS*
|
||||||
|
|
||||||
|
.. code:: sh
|
||||||
|
|
||||||
|
pip3 install pyspapi
|
||||||
|
|
||||||
|
Quick example
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Checking the balance
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
.. code:: py
|
||||||
|
|
||||||
|
from pyspapi import SPAPI
|
||||||
|
from asyncio import get_event_loop
|
||||||
|
|
||||||
|
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>`_
|
||||||
|
|
||||||
|
Links
|
||||||
|
------
|
||||||
|
|
||||||
|
- `Discord server <https://discord.gg/VbyHaKRAaN>`_
|
||||||
|
- `pyspapi documentation <https://pyspapi.readthedocs.io/>`_
|
||||||
|
- `PyPi <https://pypi.org/project/pyspapi/>`_
|
||||||
|
- `API documentation for SP sites <https://github.com/sp-worlds/api-docs>`_
|
||||||
BIN
assets/logo.png
Normal file
BIN
assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
BIN
assets/pyspapi_repo_banner.png
Normal file
BIN
assets/pyspapi_repo_banner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
BIN
assets/repo-banner.png
Normal file
BIN
assets/repo-banner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
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)
|
||||||
BIN
docs/_static/404.png
vendored
Normal file
BIN
docs/_static/404.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
19
docs/_static/custom.css
vendored
Normal file
19
docs/_static/custom.css
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/* Background of stable should be green */
|
||||||
|
.version-switcher__container a[data-version-name*="stable"] {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-switcher__container a[data-version-name*="stable"] span {
|
||||||
|
color: var(--pst-color-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.version-switcher__container a[data-version-name*="stable"] span:before {
|
||||||
|
content: "";
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
background-color: var(--pst-color-success);
|
||||||
|
opacity: 0.1;
|
||||||
|
}
|
||||||
17
docs/_static/switcher.json
vendored
Normal file
17
docs/_static/switcher.json
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "latest",
|
||||||
|
"version": "latest",
|
||||||
|
"url": "https://pyspapi.readthedocs.io/ru/latest/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stable",
|
||||||
|
"version": "stable",
|
||||||
|
"url": "https://pyspapi.readthedocs.io/ru/stable/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "v3-asyncio",
|
||||||
|
"version": "v3-asyncio",
|
||||||
|
"url": "https://pyspapi.readthedocs.io/ru/v3-asyncio/"
|
||||||
|
}
|
||||||
|
]
|
||||||
30
docs/api.rst
Normal file
30
docs/api.rst
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
.. currentmodule:: pyspapi
|
||||||
|
|
||||||
|
API Reference
|
||||||
|
===============
|
||||||
|
|
||||||
|
The following section outlines the API of pyspapi.
|
||||||
|
|
||||||
|
Version Info
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
There are two main ways to query version information.
|
||||||
|
|
||||||
|
.. data:: version_info
|
||||||
|
|
||||||
|
A named tuple that is similar to :obj:`py:sys.version_info`.
|
||||||
|
|
||||||
|
Just like :obj:`py:sys.version_info` the valid values for ``releaselevel`` are
|
||||||
|
'alpha', 'beta', 'candidate' and 'final'.
|
||||||
|
|
||||||
|
.. data:: __version__
|
||||||
|
|
||||||
|
A string representation of the version.
|
||||||
|
|
||||||
|
``pyspapi``
|
||||||
|
-----------
|
||||||
|
|
||||||
|
``SPAPI``
|
||||||
|
~~~~~~~~~
|
||||||
|
.. autoclass:: SPAPI
|
||||||
|
:members:
|
||||||
84
docs/conf.py
Normal file
84
docs/conf.py
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
from re import search, MULTILINE
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
project = 'pyspapi'
|
||||||
|
copyright = '2022, deesiigneer'
|
||||||
|
author = 'deesiigneer'
|
||||||
|
with open("../pyspapi/__init__.py") as f:
|
||||||
|
match = search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', f.read(), MULTILINE)
|
||||||
|
|
||||||
|
if not match or match.group(1) is None:
|
||||||
|
raise RuntimeError("The version could not be resolved")
|
||||||
|
|
||||||
|
version = match.group(1)
|
||||||
|
|
||||||
|
# The full version, including alpha/beta/rc tags.
|
||||||
|
release = version
|
||||||
|
|
||||||
|
# -- General configuration
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.abspath(".."))
|
||||||
|
|
||||||
|
|
||||||
|
extensions = [
|
||||||
|
'sphinx.ext.duration',
|
||||||
|
'sphinx.ext.doctest',
|
||||||
|
'sphinx.ext.autodoc',
|
||||||
|
'sphinx.ext.autosummary',
|
||||||
|
'sphinx.ext.intersphinx',
|
||||||
|
]
|
||||||
|
|
||||||
|
autodoc_member_order = "bysource"
|
||||||
|
autodoc_typehinta = "none"
|
||||||
|
|
||||||
|
intersphinx_mapping = {
|
||||||
|
'python': ('https://docs.python.org/3/', 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']
|
||||||
|
language = 'en'
|
||||||
|
locale_dirs = ["locale/"]
|
||||||
|
exclude_patterns = []
|
||||||
|
html_static_path = ["_static"]
|
||||||
|
html_theme = "pydata_sphinx_theme"
|
||||||
|
html_logo = "./images/logo.png"
|
||||||
|
html_favicon = "./images/logo.ico"
|
||||||
|
html_theme_options = {
|
||||||
|
"external_links": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/deesiigneer/pyspapi/releases",
|
||||||
|
"name": "Changelog",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"icon_links": [
|
||||||
|
{
|
||||||
|
"name": "GitHub",
|
||||||
|
"url": "https://github.com/deesiigneer/pyspapi",
|
||||||
|
"icon": "fab fa-brands fa-github",
|
||||||
|
"type": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Discord",
|
||||||
|
"url": "https://discord.gg/VbyHaKRAaN",
|
||||||
|
"icon": "fab fa-brands fa-discord",
|
||||||
|
"type": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PyPi",
|
||||||
|
"url": "https://pypi.org/project/pyspapi/",
|
||||||
|
"icon": "fab fa-brands fa-python",
|
||||||
|
"type": "fontawesome"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"header_links_before_dropdown": 4,
|
||||||
|
"show_toc_level": 1,
|
||||||
|
"navbar_start": ["navbar-logo"],
|
||||||
|
"navigation_with_keys": True,
|
||||||
|
}
|
||||||
|
html_css_files = ["custom.css"]
|
||||||
BIN
docs/images/logo.ico
Normal file
BIN
docs/images/logo.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.3 KiB |
BIN
docs/images/logo.png
Normal file
BIN
docs/images/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
35
docs/index.rst
Normal file
35
docs/index.rst
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
:theme_html_remove_secondary_sidebar:
|
||||||
|
|
||||||
|
Welcome to pyspapi
|
||||||
|
===================
|
||||||
|
|
||||||
|
API wrapper for SP servers written in Python.
|
||||||
|
|
||||||
|
Getting started
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Is this your first time using the library? This is the place to get started!
|
||||||
|
|
||||||
|
- **First steps:** :ref:`Quickstart <quickstart>`
|
||||||
|
- **Examples:** Many examples are available in the `examples directory <https://github.com/deesiigneer/pyspapi/tree/main/examples/>`_.
|
||||||
|
|
||||||
|
Getting help
|
||||||
|
------------
|
||||||
|
|
||||||
|
If you're having trouble with something, these resources might help.
|
||||||
|
|
||||||
|
- Ask questions in `Discord <https://discord.gg/VbyHaKRAaN>`_ server.
|
||||||
|
- If you're looking for something specific, try the :ref:`searching <search>`.
|
||||||
|
- Report bugs in the `issue tracker <https://github.com/deesiigneer/pyspapi/issues>`_.
|
||||||
|
- Ask in `GitHub discussions page <https://github.com/deesiigneer/pyspapi/discussions>`_.
|
||||||
|
|
||||||
|
Manuals
|
||||||
|
-------
|
||||||
|
|
||||||
|
These pages go into great detail about everything the API can do.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
api
|
||||||
|
quickstart
|
||||||
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
|
||||||
|
|
||||||
|
if "%1" == "" goto help
|
||||||
|
|
||||||
|
%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.http://sphinx-doc.org/
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||||
|
goto end
|
||||||
|
|
||||||
|
:help
|
||||||
|
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||||
|
|
||||||
|
:end
|
||||||
|
popd
|
||||||
36
docs/quickstart.rst
Normal file
36
docs/quickstart.rst
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
:orphan:
|
||||||
|
|
||||||
|
.. _quickstart:
|
||||||
|
|
||||||
|
.. currentmodule:: pyspapi
|
||||||
|
|
||||||
|
Quickstart
|
||||||
|
==========
|
||||||
|
|
||||||
|
This page gives a brief introduction to the library.
|
||||||
|
|
||||||
|
Checking balance
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Let's output the amount of money remaining in the card account to the console.
|
||||||
|
|
||||||
|
It looks something like this:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyspapi import SPAPI
|
||||||
|
from asyncio import get_event_loop
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
You can find more examples in the `examples directory <https://github.com/deesiigneer/pyspapi/tree/main/examples/>`_ on GitHub.
|
||||||
1
docs/requirements.txt
Normal file
1
docs/requirements.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pydata_sphinx_theme
|
||||||
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())
|
||||||
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())
|
||||||
21
examples/payments.py
Normal file
21
examples/payments.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from pyspapi import SPAPI
|
||||||
|
from pyspapi.types import Item
|
||||||
|
from asyncio import get_event_loop
|
||||||
|
|
||||||
|
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():
|
||||||
|
print(await spapi.create_payment(items=items,
|
||||||
|
redirect_url='https://www.google.com/',
|
||||||
|
webhook_url='https://www.google.com/',
|
||||||
|
data='some-data'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
loop = get_event_loop()
|
||||||
|
loop.run_until_complete(main())
|
||||||
16
examples/transaction.py
Normal file
16
examples/transaction.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from pyspapi import SPAPI
|
||||||
|
from asyncio import get_event_loop
|
||||||
|
|
||||||
|
spapi = SPAPI(card_id='CARD_ID', token='TOKEN')
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
new_balance = await spapi.create_transaction(receiver='77552',
|
||||||
|
amount=1,
|
||||||
|
comment="test"
|
||||||
|
)
|
||||||
|
print(new_balance)
|
||||||
|
|
||||||
|
|
||||||
|
loop = get_event_loop()
|
||||||
|
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())
|
||||||
8
pyspapi/__init__.py
Normal file
8
pyspapi/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from .spworlds import *
|
||||||
|
|
||||||
|
|
||||||
|
__author__ = 'deesiigneer'
|
||||||
|
__url__ = 'https://github.com/deesiigneer/pyspapi'
|
||||||
|
__description__ = 'API wrapper for SP servers written in Python.'
|
||||||
|
__license__ = 'MIT'
|
||||||
|
__version__ = "3.1.0"
|
||||||
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}")
|
||||||
0
pyspapi/exceptions.py
Normal file
0
pyspapi/exceptions.py
Normal file
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()
|
||||||
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
requirements.txt
Normal file
1
requirements.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
aiohttp>=3.8.0
|
||||||
50
setup.py
50
setup.py
@@ -1,20 +1,46 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
from os import path
|
requirements = []
|
||||||
this_directory = path.abspath(path.dirname(__file__))
|
with open("requirements.txt") as f:
|
||||||
with open(path.join(this_directory, 'README.md'), encoding='utf-8') as f:
|
requirements = f.read().splitlines()
|
||||||
description = f.read()
|
|
||||||
|
|
||||||
requires = ['requests==2.25.1']
|
version = ""
|
||||||
|
with open("pyspapi/__init__.py") as f:
|
||||||
|
match = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', f.read(), re.MULTILINE)
|
||||||
|
if match is None or match.group(1) is None:
|
||||||
|
raise RuntimeError('Version is not set')
|
||||||
|
|
||||||
|
version = match.group(1)
|
||||||
|
|
||||||
|
if not version:
|
||||||
|
raise RuntimeError("Version is not set")
|
||||||
|
|
||||||
|
readme = ""
|
||||||
|
with open("README.rst") as f:
|
||||||
|
readme = f.read()
|
||||||
|
|
||||||
|
packages = [
|
||||||
|
"pyspapi"
|
||||||
|
]
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='pyspapi',
|
name='pyspapi',
|
||||||
version='1.0.2',
|
license='MIT',
|
||||||
description='API wrapper for SP servers written in Python',
|
|
||||||
long_description=description,
|
|
||||||
long_description_content_type='text/markdown',
|
|
||||||
author='deesiigneer',
|
author='deesiigneer',
|
||||||
author_email='xdeesiigneerx@gmail.com',
|
version=version,
|
||||||
packages=['spapi'],
|
url='https://github.com/deesiigneer/pyspapi',
|
||||||
install_requires=requires,
|
project_urls={
|
||||||
|
"Documentation": "https://pyspapi.readthedocs.io/ru/latest/",
|
||||||
|
"GitHub": "https://github.com/deesiigneer/pyspapi",
|
||||||
|
"Discord": "https://discord.com/invite/VbyHaKRAaN"
|
||||||
|
},
|
||||||
|
description='API wrapper for SP servers written in Python',
|
||||||
|
long_description=readme,
|
||||||
|
long_description_content_type='text/x-rst',
|
||||||
|
packages=packages,
|
||||||
|
include_package_data=True,
|
||||||
|
install_requires=requirements,
|
||||||
|
python_requires='>=3.8.0',
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
# pyspapi by deesiigneer
|
|
||||||
#
|
|
||||||
|
|
||||||
import hmac
|
|
||||||
import hashlib
|
|
||||||
import base64
|
|
||||||
|
|
||||||
from requests import post, get
|
|
||||||
|
|
||||||
|
|
||||||
class Error(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ApiError(Error):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Api:
|
|
||||||
|
|
||||||
def __init__(self, card_id: str, token: str):
|
|
||||||
self.id = card_id
|
|
||||||
self.token = token
|
|
||||||
self.header = {
|
|
||||||
'Authorization': f"Bearer {str(base64.b64encode(str(f'{self.id}:{self.token}').encode('utf-8')), 'utf-8')}",
|
|
||||||
}
|
|
||||||
|
|
||||||
def _fetch(self, path, data):
|
|
||||||
if path is None:
|
|
||||||
result = get(url=f'https://spworlds.ru/api/public/users/{data}',
|
|
||||||
headers=self.header)
|
|
||||||
else:
|
|
||||||
result = post(
|
|
||||||
url=f'https://spworlds.ru/api/public/{path}',
|
|
||||||
headers=self.header,
|
|
||||||
json=data
|
|
||||||
)
|
|
||||||
if result.status_code == [200, 400]:
|
|
||||||
ApiError(f'Ошибка при запросе к API {result.status_code}')
|
|
||||||
|
|
||||||
return result.json()
|
|
||||||
|
|
||||||
def payment(self, amount, redirecturl, webhookurl, data):
|
|
||||||
"""
|
|
||||||
Создание запроса на оплату.
|
|
||||||
|
|
||||||
:param amount: Стоимость покупки в АРах.
|
|
||||||
:param redirecturl: URL страницы, на которую попадет пользователь после оплаты.
|
|
||||||
:param webhookurl: URL, куда наш сервер направит запрос, чтобы оповестить ваш сервер об успешной оплате.
|
|
||||||
:param data: Строка до 100 символов, сюда можно поместить любые полезные данных.
|
|
||||||
|
|
||||||
:return: url - Ссылка на страницу оплаты, на которую стоит перенаправить пользователя.
|
|
||||||
"""
|
|
||||||
return self._fetch('payment', data={'amount': amount,
|
|
||||||
'redirectUrl': redirecturl,
|
|
||||||
'webhookUrl': webhookurl,
|
|
||||||
'data': data})
|
|
||||||
|
|
||||||
def webhook_verify(self, data, header):
|
|
||||||
"""
|
|
||||||
Проверяет достоверность webhook'а.
|
|
||||||
|
|
||||||
:param data : data из webhook.
|
|
||||||
:param header : headers из webhook.
|
|
||||||
|
|
||||||
:return: Если header из webhook'а достоверен возвращает True, иначе False
|
|
||||||
"""
|
|
||||||
hmac_data = base64.b64encode(hmac.new(self.token.encode('utf-8'), data, hashlib.sha256).digest())
|
|
||||||
return hmac.compare_digest(hmac_data, header.encode('utf-8'))
|
|
||||||
|
|
||||||
def transaction(self, receiver, amount, comment):
|
|
||||||
"""
|
|
||||||
Перевод АР на карту.
|
|
||||||
|
|
||||||
:param receiver : Номер карты получателя.
|
|
||||||
|
|
||||||
:param amount: Количество АР для перевода.
|
|
||||||
|
|
||||||
:param comment: Комментарий для перевода.
|
|
||||||
"""
|
|
||||||
return self._fetch('transactions', data={'receiver': receiver,
|
|
||||||
'amount': amount,
|
|
||||||
'comment': comment})
|
|
||||||
|
|
||||||
def check_user(self, discord_user_id):
|
|
||||||
"""
|
|
||||||
Проверка на наличие проходки
|
|
||||||
:param discord_user_id: ID пользователя в Discord.
|
|
||||||
|
|
||||||
:return: username - Ник пользователя или null, если у пользователя нет проходки на сервер.
|
|
||||||
"""
|
|
||||||
return self._fetch(None, data=discord_user_id)
|
|
||||||
Reference in New Issue
Block a user