├── requirements.txt ├── py3wifi ├── __init__.py ├── exceptions.py ├── client.py └── api.py ├── .gitignore ├── LICENSE ├── setup.py └── README.md /requirements.txt: -------------------------------------------------------------------------------- 1 | requests -------------------------------------------------------------------------------- /py3wifi/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | :authors: drygdryg 4 | :license: MIT 5 | :copyright: (c) 2020 drygdryg 6 | """ 7 | from .exceptions import * 8 | from .api import Api 9 | from .client import Client 10 | 11 | __author__ = 'drygdryg' 12 | __version__ = '0.0.3' 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # Distribution / packaging 7 | .Python 8 | build/ 9 | develop-eggs/ 10 | dist/ 11 | downloads/ 12 | eggs/ 13 | .eggs/ 14 | lib/ 15 | lib64/ 16 | parts/ 17 | sdist/ 18 | var/ 19 | wheels/ 20 | share/python-wheels/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | MANIFEST 25 | 26 | # Environments 27 | .env 28 | .venv 29 | env/ 30 | venv/ 31 | ENV/ 32 | env.bak/ 33 | venv.bak/ -------------------------------------------------------------------------------- /py3wifi/exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | class ApiError(Exception): 3 | pass 4 | 5 | 6 | class ClientError(Exception): 7 | pass 8 | 9 | 10 | class AuthError(ApiError, ClientError): 11 | pass 12 | 13 | 14 | class AccountBlocked(AuthError, ClientError): 15 | pass 16 | 17 | 18 | class LoginRequired(AuthError, ClientError): 19 | pass 20 | 21 | 22 | class PasswordRequired(AuthError, ClientError): 23 | pass 24 | 25 | 26 | class NoKeysReceived(AuthError): 27 | pass 28 | 29 | 30 | class LowLevelError(ClientError): 31 | pass 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Victor Golovanenko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from io import open 3 | from setuptools import setup 4 | 5 | """ 6 | :authors: drygdryg 7 | :license: MIT 8 | :copyright: (c) 2020 drygdryg 9 | """ 10 | 11 | version = '0.0.3' 12 | 13 | with open('README.md', encoding='utf-8') as f: 14 | long_description = f.read() 15 | 16 | setup( 17 | name='py3wifi', 18 | version=version, 19 | description='Wrapper for 3WiFi Wireless Database', 20 | long_description=long_description, 21 | long_description_content_type='text/markdown', 22 | license='MIT', 23 | 24 | author='drygdryg', 25 | author_email='drygdryg2014@yandex.com', 26 | url='https://github.com/drygdryg/py3wifi', 27 | download_url='https://github.com/drygdryg/py3wifi/archive/v{}.zip'.format(version), 28 | 29 | keywords='wrapper api 3wifi', 30 | 31 | packages=['py3wifi'], 32 | python_requires='>=3.6', 33 | install_requires=[ 34 | 'requests' 35 | ], 36 | 37 | classifiers=[ 38 | 'License :: OSI Approved :: MIT License', 39 | 'Operating System :: OS Independent', 40 | 'Programming Language :: Python :: 3.6', 41 | 'Programming Language :: Python :: 3.7', 42 | 'Programming Language :: Python :: 3.8', 43 | 'Topic :: Internet :: WWW/HTTP', 44 | 'Topic :: Software Development :: Libraries', 45 | 'Intended Audience :: Developers', 46 | 'Environment :: Web Environment' 47 | ] 48 | ) 49 | -------------------------------------------------------------------------------- /py3wifi/client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import requests 3 | 4 | from .exceptions import (AuthError, AccountBlocked, LoginRequired, 5 | PasswordRequired, LowLevelError) 6 | 7 | 8 | class Client(): 9 | def __init__(self, login=None, password=None, server=None): 10 | self.login = login 11 | self.password = password 12 | self.server = server or 'https://3wifi.stascorp.com' 13 | 14 | self.http = requests.Session() 15 | 16 | def auth(self): 17 | if not self.login: 18 | raise LoginRequired('Login is required to login') 19 | if not self.password: 20 | raise PasswordRequired('Password is required to login') 21 | 22 | r = self.http.post( 23 | f'{self.server}/user.php?a=login', 24 | data={ 25 | 'login': self.login, 26 | 'password': self.password 27 | } 28 | ).json() 29 | 30 | if r['result']: 31 | return True 32 | else: 33 | if 'error' in r: 34 | if r['error'] == 'loginfail': 35 | raise AuthError('Invalid login or password') 36 | elif r['error'] == 'lowlevel': 37 | raise AccountBlocked('The account is blocked') 38 | else: 39 | raise AuthError(r['error']) 40 | else: 41 | raise AuthError() 42 | 43 | def logout(self): 44 | r = self.http.get(f'{self.server}/user.php?a=token').json() 45 | if r['result']: 46 | token = r['token'] 47 | r = self.http.get( 48 | f'{self.server}/user.php?a=logout&token={token}' 49 | ).json() 50 | return r['result'] 51 | else: 52 | return False 53 | 54 | def request(self, action, data={}): 55 | r = self.http.post( 56 | f'{self.server}/3wifi.php?a={action}', 57 | data=data 58 | ).json() 59 | if r['result']: 60 | return r 61 | elif 'error' in r: 62 | error = r['error'] 63 | if error == 'unauthorized': 64 | self.auth() 65 | return self.request(action, data) 66 | elif error == 'lowlevel': 67 | raise LowLevelError('You don\'t have access to this action') 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python wrapper for [3WiFi Wireless Database](https://3wifi.stascorp.com/) 2 | [![PyPI pyversions](https://img.shields.io/pypi/pyversions/py3wifi.svg)](https://pypi.python.org/pypi/py3wifi/) 3 | [![PyPI license](https://img.shields.io/pypi/l/py3wifi.svg)](https://pypi.python.org/pypi/py3wifi/) 4 | [![PyPI version shields.io](https://img.shields.io/pypi/v/py3wifi.svg)](https://pypi.python.org/pypi/py3wifi/) 5 | ![PyPI - Downloads](https://img.shields.io/pypi/dm/py3wifi) 6 | ## Install 7 | ``` 8 | pip install py3wifi 9 | ``` 10 | Or you can install from source with: 11 | ``` 12 | https://github.com/drygdryg/py3wifi.git 13 | cd py3wifi 14 | python setup.py install 15 | ``` 16 | ## API usage 17 | ### Authorization 18 | ```python 19 | import py3wifi 20 | 21 | api_session = py3wifi.Api(login='mylogin', password='mypassword') 22 | api_session.auth() 23 | ``` 24 | Or with read API key: 25 | ```python 26 | api_session = py3wifi.Api(read_key='abcdefghijklmnopqrstuvwxyz123456') 27 | ``` 28 | ### Calling methods 29 | ```python 30 | >>> api_session.method('apiquery', params={'bssid': '4C:F2:BF:30:81:A4'}) 31 | {'4C:F2:BF:30:81:A4': [{'time': '2017-07-27 10:39:43', 'bssid': '4C:F2:BF:30:81:A4', 'essid': 'IDNet-41', 'sec': 'WPA2', 'key': '87059894216', 'wps': '12345678', 'lat': 54.89953995, 'lon': 69.14550781}]} 32 | ``` 33 | Or: 34 | ```python 35 | >>> api = api_session.get_api() 36 | >>> api.apiquery(bssid='4C:F2:BF:30:81:A4') 37 | {'4C:F2:BF:30:81:A4': [{'time': '2017-07-27 10:39:43', 'bssid': '4C:F2:BF:30:81:A4', 'essid': 'IDNet-41', 'sec': 'WPA2', 'key': '87059894216', 'wps': '12345678', 'lat': 54.89953995, 'lon': 69.14550781}]} 38 | ``` 39 | See https://3wifi.stascorp.com/apidoc for detailed API guide. 40 | ## Usage of 3WiFi AJAX interface 41 | ```python 42 | import py3wifi 43 | 44 | client = py3wifi.Client(login='mylogin', password='mypassword') 45 | client.auth() 46 | ``` 47 | ```python 48 | >>> client.request('find', {'bssid': '04:95:E6:6C:36:*', 'essid': 'Tenda_◯', 'wps': '□□□□□□□□'}) 49 | {'result': True, 'data': [{'id': 93806549, 'time': '2019-12-08 17:30:55', 'comment': 'Router Scan', 'range': '192.168.0.0/16', 'nowifi': False, 'hidden': False, 'bssid': '04:95:E6:6C:36:50', 'essid': 'Tenda_6C3650', 'sec': 'None', 'key': '', 'wps': '21847080', 'lat': 44.52231216, 'lon': 33.59743881, 'fav': False}], 'found': 1, 'page': {'current': 1, 'count': 1}, 'time': 0.013512849807739} 50 | ``` -------------------------------------------------------------------------------- /py3wifi/api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import requests 3 | 4 | from .exceptions import (ApiError, AuthError, AccountBlocked, 5 | LoginRequired, PasswordRequired, NoKeysReceived) 6 | 7 | 8 | class Api(): 9 | def __init__(self, login=None, password=None, read_key=None, server=None): 10 | self.login = login 11 | self.password = password 12 | self.key = read_key 13 | self.server = server or 'https://3wifi.stascorp.com' 14 | 15 | self.http = requests.Session() 16 | 17 | def auth(self): 18 | if not self.login: 19 | raise LoginRequired('Login is required to login') 20 | if not self.password: 21 | raise PasswordRequired('Password is required to login') 22 | 23 | r = self.http.post( 24 | f'{self.server}/api/apikeys', 25 | data={ 26 | 'login': self.login, 27 | 'password': self.password, 28 | 'genread': True 29 | } 30 | ).json() 31 | 32 | if r['result']: 33 | try: 34 | self.key = list(filter(lambda x: x['access'] == 'read', r['data']))[0]['key'] 35 | except IndexError: 36 | raise NoKeysReceived('No read API key received') 37 | else: 38 | if r['error'] == 'loginfail': 39 | raise AuthError('Invalid login or password') 40 | elif r['error'] == 'lowlevel': 41 | raise AccountBlocked('The account is blocked') 42 | else: 43 | raise ApiError(r['error']) 44 | 45 | def method(self, method, params=None): 46 | if (not self.key) and (method in ('apiquery', 'apiwps', 'apiranges')): 47 | raise ApiError('This method requires authorization') 48 | 49 | params = params.copy() if params else {} 50 | 51 | if 'key' not in params: 52 | params['key'] = self.key 53 | 54 | response = self.http.post( 55 | f'{self.server}/api/' + method, 56 | json=params 57 | ).json() 58 | 59 | if response['result']: 60 | return response['data'] 61 | elif 'error' in response: 62 | if response['error'] == 'form': 63 | raise ApiError('Some form fields are incorrect') 64 | else: 65 | raise ApiError(response['error']) 66 | else: 67 | raise ApiError() 68 | 69 | def get_api(self): 70 | return ApiMethod(self) 71 | 72 | 73 | class ApiMethod(): 74 | def __init__(self, api, method=None): 75 | self._api = api 76 | self._method = method 77 | 78 | def __getattr__(self, method): 79 | if '_' in method: 80 | m = method.split('_') 81 | method = m[0] + ''.join(i.title() for i in m[1:]) 82 | 83 | return ApiMethod( 84 | self._api, 85 | (self._method + '.' if self._method else '') + method 86 | ) 87 | 88 | def __call__(self, **kwargs): 89 | return self._api.method(self._method, kwargs) 90 | --------------------------------------------------------------------------------