Skip to content

Commit

Permalink
swagger api
Browse files Browse the repository at this point in the history
  • Loading branch information
deluxghost committed Nov 2, 2018
1 parent c6211df commit 69d9311
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 40 deletions.
3 changes: 3 additions & 0 deletions ASF/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
__version__ = '2.0.0'

from .models import IPC # noqa: F401
137 changes: 137 additions & 0 deletions ASF/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import aiohttp
import pyswagger

from . import utils


class IPC:

def __init__(self, ipc='http://127.0.0.1:1242/', password='', timeout=5):
self._ipc = ipc
self._password = password
self._timeout = timeout

async def __aenter__(self):
self._swagger = pyswagger.App.create(utils.build_url(self._ipc, '/swagger/ASF/swagger.json'))
for api in self._swagger.op.values():
p = self
p_path = ''
for node in api.path.strip(utils.sep).split(utils.sep):
p_path += f'/{node}'
if node.startswith('{') and node.endswith('}'):
arg = node[1:-1]
p._append(self, arg)
p = p[arg]
else:
if not hasattr(p, node):
setattr(p, node, Endpoint(self))
p = getattr(p, node)
p._path = p_path
headers = dict()
if self._password:
headers['Authentication'] = self._password
timeout = aiohttp.ClientTimeout(total=self._timeout)
self._session = aiohttp.ClientSession(headers=headers, timeout=timeout)
return self

async def __aexit__(self, exc_type, exc_val, exc_tb):
await self._session.close()


class Endpoint:

def __init__(self, ipc):
self._kw = dict()
self._path = None
self._ipc = ipc

def __getitem__(self, key):
return self._kw[key]

def _append(self, ipc, kw):
if kw not in self._kw:
self._kw[kw] = Endpoint(ipc)

async def _request(self, method, body=None, params=None, **kw):
session = self._ipc._session
url = utils.build_url(self._ipc._ipc, self._path)
for k, v in kw.items():
url = url.replace(f'{{{k}}}', utils.quote(v))
async with session.request(method, url, json=body, params=params) as resp:
try:
json_data = await resp.json()
except Exception:
json_data = None
text = await resp.text()
return ASFResponse(resp, json_data, text)

async def ws(self, **kw):
session = self._ipc._session
url = utils.build_url(self._ipc._ipc, self._path)
for k, v in kw.items():
url = url.replace(f'{{{k}}}', utils.quote(v))
async with session.ws_connect(url) as ws:
async for msg in ws:
if msg.type == aiohttp.WSMsgType.TEXT:
try:
json_data = msg.json()
except Exception:
json_data = None
text = msg.data
yield WSResponse(url, json_data, text)
elif msg.type == aiohttp.WSMsgType.ERROR or msg.type == aiohttp.WSMsgType.CLOSE:
break

async def get(self, **kw):
return await self._request('get', **kw)

async def post(self, **kw):
return await self._request('post', **kw)

async def put(self, **kw):
return await self._request('put', **kw)

async def delete(self, **kw):
return await self._request('delete', **kw)


class WSResponse:

def __init__(self, url, json_data, text):
self.url = url
if json_data:
self.ok = True
self.message = json_data.get('Message')
self.result = json_data.get('Result')
self.success = json_data.get('Success')
else:
self.ok = False
self.message = text
self.result = None
self.success = False
self.OK = self.ok
self.Message = self.message
self.Result = self.result
self.Success = self.success


class ASFResponse:

def __init__(self, resp, json_data, text):
status = resp.status
reason = resp.reason
self.url = resp.url
if json_data:
self.ok = True
self.message = json_data.get('Message')
self.result = json_data.get('Result')
self.success = json_data.get('Success')
else:
self.ok = False
self.message = text if text else f'{status} - {reason}'
self.result = None
self.success = False
self.OK = self.ok
self.Message = self.message
self.Result = self.result
self.Success = self.success
25 changes: 25 additions & 0 deletions ASF/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import urllib.parse as urlparse

sep = '/'


def build_url(ipc, endpoint):
ipc_split = list(urlparse.urlsplit(ipc))
ipc_split[2] = ipc_split[2].rstrip(sep)
endpoint = endpoint.strip(sep)
ipc_split[2] = ipc_split[2] + sep + endpoint
return urlparse.urlunsplit(ipc_split)


def quote(keyword):
return urlparse.quote(keyword)


def path_parse(path):
paths = path.strip(sep).split(sep)
token = list()
for index, node in enumerate(paths):
if index != 0 and node.startswith('{') and node.endswith('}'):
token[-1] = {

}
1 change: 0 additions & 1 deletion ASF_IPC/__init__.py

This file was deleted.

16 changes: 0 additions & 16 deletions ASF_IPC/models.py

This file was deleted.

10 changes: 0 additions & 10 deletions ASF_IPC/utils.py

This file was deleted.

85 changes: 74 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,21 @@
[![license](https://img.shields.io/github/license/deluxghost/ASF_IPC.svg?style=flat-square)](https://github.com/deluxghost/ASF_IPC/blob/master/LICENSE)
[![PyPI](https://img.shields.io/badge/Python-3.6-blue.svg?style=flat-square)](https://pypi.python.org/pypi/ASF-IPC)
[![PyPI](https://img.shields.io/pypi/v/ASF-IPC.svg?style=flat-square)](https://pypi.python.org/pypi/ASF-IPC)
[![ASF](https://img.shields.io/badge/ASF-3.3.0.3%20supported-orange.svg?style=flat-square)](https://github.com/JustArchi/ArchiSteamFarm)
[![ASF](https://img.shields.io/badge/ASF-3.4.0.5%20supported-orange.svg?style=flat-square)](https://github.com/JustArchi/ArchiSteamFarm)

A simple Python 3.6+ wrapper of [ArchiSteamFarm IPC API](https://github.com/JustArchi/ArchiSteamFarm/wiki/IPC)
A simple asynchronous Python 3.6+ wrapper of [ArchiSteamFarm IPC API](https://github.com/JustArchi/ArchiSteamFarm/wiki/IPC)

**Powered by aiohttp, ASF_IPC is now fully asynchronous. Synchronous methods are no longer supported.** ASF_IPC resolves APIs automatically from the new swagger API of ASF, so ASF_IPC always has the latest APIs data as long as ASF is updated.

## Examples

* [telegram-asf](https://github.com/deluxghost/telegram-asf)
* [telegram-asf](https://github.com/deluxghost/telegram-asf) (outdated)

## Requirements

* Python 3.6+
* requests
* websockets
* aiohttp
* pyswagger

## Installation

Expand All @@ -25,17 +27,78 @@ pip3 install ASF_IPC

## Getting Started

This example shows how to post a command to ASF:
This example shows how to send a command to ASF:

```python
import ASF_IPC as asf
import asyncio
from ASF import IPC

async def command(cmd):
async with IPC(ipc='http://127.0.0.1:1242', password='YOUR IPC PASSWORD') as asf:
resp = await asf.Api.Command['command'].post(command=cmd)
return resp.result

api = asf.IPC(ipc='http://127.0.0.1:1242/', password='YOUR IPC PASSWORD')
command = input('Enter a command:')
output = api.command(command)
cmd = input('Enter a command: ')
loop = asyncio.get_event_loop()
output = loop.run_until_complete(command(cmd))
print(output)
loop.close()
```

## Find the endpoint

To get a list of all endpoints of ASF, open your web browser and visit the swagger page of your ASF instance (usually `http://127.0.0.1:1242/swagger`).

You can see many endpoints with their path, such as `/Api/Bot/{botNames}`, this endpoint in ASF_IPC is `asf.Api.Bot['botNames']`.

Some more examples:

```python
async with IPC(...) as asf:
asf.Api.ASF # /Api/ASF
asf.Api.Command['command'] # /Api/Command/{command}
asf.Api.Bot['botNames'].Pause # /Api/Bot/{botNames}/Pause
asf.Api.WWW.GitHub.Releases # /Api/WWW/GitHub/Releases
asf.Api.WWW.GitHub.Releases['version'] # /Api/WWW/GitHub/Releases/{version}
```

For further usage examples, read our [wiki](https://github.com/deluxghost/ASF_IPC/wiki) pages.
## Send a request

After your endpoint found, you want to send some data to this endpoint, you can use `get()`, `post()`, `put()`, `delete()` methods, these methods have optional arguments:

* `body` (dict): the JSON request body.
* `params` (dict): the parameters in URL after a `?`, e.g. the `params` of `/Api/WWW/GitHub/Releases?count=10` is `{'count': 10}`

If you need to pass parameters in the path of the endpoint, e.g. `{botName}` in `/Api/Bot/{botName}/Redeem`, you can pass them as a keyword parameter of the method.

Some examples:

```python
# POST /Api/Command/status%20asf
resp = await asf.Api.Command['command'].post(command='status asf')
# GET /Api/WWW/GitHub/Releases?count=10
resp = await asf.Api.WWW.GitHub.Releases.get(params={'count': 10})
# POST /Api/Bot/robot with json body {'BotConfig': ...}
resp = await asf.Api.Bot['botName'].post(body={'BotConfig': ...}, botName='robot')
```

## Get a response

After sending a request to the endpoint, we got a response object, which has 3 attributes:

* `Result` or `result` (str): some data returned by ASF.
* `Message` or `message` (str): describes what happened with the request.
* `Success` or `success` (bool): if the request has succeeded.

If ASF_IPC cannot give a value to some attributes, these attributes will be `None` or empty value.

## WebSocket endpoint

Example for `/Api/NLog`:

```python
async def get_log():
async with IPC(ipc='http://127.0.0.1:1242', password='YOUR IPC PASSWORD') as asf:
async for resp in asf.Api.NLog.ws(): # use ws() instead of get(), post()...
print(resp.result)
```
5 changes: 3 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import codecs
import os
import re

from setuptools import setup


Expand All @@ -21,8 +22,8 @@ def find_version(*file_paths):

setup(
name='ASF_IPC',
version=find_version('ASF_IPC', '__init__.py'),
packages=['ASF_IPC'],
version=find_version('ASF', '__init__.py'),
packages=['ASF'],
classifiers=[
'Development Status :: 4 - Beta',
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
Expand Down

0 comments on commit 69d9311

Please sign in to comment.