diff --git a/anixart/__init__.py b/anixart/__init__.py index e69de29..dfe52f0 100644 --- a/anixart/__init__.py +++ b/anixart/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- + +from .__version__ import __license__, __description__ +from .__version__ import __version__, __url__, __build__, __title__, __author__, __author_email__, __copyright__ + +from .endpoints import * + +from .api.api import AnixartUserAccount, AnixartAPI diff --git a/anixart/__version__.py b/anixart/__version__.py index 90b533f..d561dbb 100644 --- a/anixart/__version__.py +++ b/anixart/__version__.py @@ -3,8 +3,8 @@ __title__ = 'anixart' __description__ = 'Wrapper for using the Anixart API.' __url__ = 'https://github.com/SantaSpeen/anixart' -__version__ = '0.1.0' -__build__ = 1 +__version__ = '0.2.1' +__build__ = 3 __author__ = 'SantaSpeen' __author_email__ = 'SantaSpeen@gmail.com' __license__ = "FPA" diff --git a/anixart/api/api.py b/anixart/api/api.py new file mode 100644 index 0000000..c21d310 --- /dev/null +++ b/anixart/api/api.py @@ -0,0 +1,78 @@ +import logging + +import requests + +from ..auth import AnixartAuth +from ..errors import AnixartInitError, AnixartAPIRequestError +from ..request_handler import AnixartRequestsHandler + +_log_name = "file:%-29s -> %s" % ("", "%s") + + +class AnixartUserAccount: + def __init__(self, login, password, config_file="anixart_data.json", **kwargs): + self.kwargs = kwargs + log_level = logging.CRITICAL + log_format = '[%(name)-43s] %(levelname)-5s: %(message)s' + if kwargs.get("loglevel") is not None: + log_level = kwargs.get("loglevel") + if kwargs.get("logformat") is not None: + log_format = kwargs.get("logformat") + logging.basicConfig(level=log_level, format=log_format) + init_log = logging.getLogger("anixart.api.AnixUserAccount") + init_log.debug(_log_name, 23, "__init__ - INIT") + self.login = login + self.password = password + if not isinstance(login, str) or not isinstance(password, str): + raise AnixartInitError("Use normal auth data. In string.") + self.token = None + self.id = None + self.config_file = config_file + self.session = requests.Session() + init_log.debug(_log_name, 32, f"{str(self)}") + init_log.debug(_log_name, 33, "__init__() - OK") + + def __str__(self): + return f'AnixartUserAccount(login="{self.login}", password="{self.password}", ' \ + f'config_file="{self.config_file}", kwargs="{self.kwargs}")' + + def get_login(self): + return self.login + + def get_password(self): + return self.password + + def get_token(self): + return self.token + + def get_id(self): + return self.id + + +class AnixartAPI: + + def __init__(self, user: AnixartUserAccount): + init_log = logging.getLogger("anixart.api.AnixartAPI") + init_log.debug(_log_name, 56, "__init__ - INIT") + if not isinstance(user, AnixartUserAccount): + init_log.critical('Use anixart.api.AnixartUserAccount for user.') + raise AnixartInitError('Use class "AnixartUserAccount" for user.') + self.auth = AnixartAuth(user) + if user.token is None or user.id is None: + init_log.debug(_log_name, 62, "Singing in..") + self.auth.sing_in() + self.user = user + init_log.debug(_log_name, 65, "__init__ - OK.") + self.http_handler = AnixartRequestsHandler(user.token, user.session) + + def __str__(self): + return f'AnixAPI({self.user})' + + def execute(self, http_method, endpoint, **kwargs): + http_method = http_method.upper() + if http_method == "GET": + return self.http_handler.get(endpoint, **kwargs) + elif http_method == "POST": + return self.http_handler.post(endpoint, **kwargs) + else: + raise AnixartAPIRequestError("Allow only GET and POST requests.") diff --git a/anixart/api/api.pyi b/anixart/api/api.pyi new file mode 100644 index 0000000..44f106a --- /dev/null +++ b/anixart/api/api.pyi @@ -0,0 +1,65 @@ +import requests + +from ..auth import AnixartAuth +from ..request_handler import AnixartRequestsHandler + + +class AnixartUserAccount: + def __init__(self, login, password, config_file="anixart_data.json", **kwargs): + """ + Info: + Anixart login class object. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Usage: + >>> user = AnixartUserAccount("login", "password", config_file="anixart_data.json") + >>> print(user.login) + Availible params: + ~~~~~~~~~~~~~~~~~ + * login -> Your anixart nick + * password -> Your anixart password + * need_reg -> If you need new account, set True + * mail -> Real email for registration. + * config_file -> Patch to anixart login cache + :param login: Your anixart nick + :param password: Anixart password + :param need_reg: If you need new account, set True + :param email: Real email for registration + :param config_file: Patch to anixart login cache + :type login: str + :type password: str + :type need_reg: bool + :type email: str + :type config_file: str + :return: :class:`AnixUserAccount ` object + """ + self.kwargs: dict = kwargs + self.login: str = login + self.password: str = password + self.config_file: str = config_file + self.token: str = None + self.id: int = None + self.session: requests.Session = requests.Session() + + def __str__(self) -> str: ... + def get_login(self) -> str: ... + def get_password(self) -> str: ... + def get_token(self) -> str: ... + def get_id(self) -> int: ... + +class AnixartAPI: + def __init__(self, user: AnixartUserAccount): + """ + Info: + Anixart API class object. + ~~~~~~~~~~~~~~~~~~~~~~~~~ + Usage: + >>> user = AnixartUserAccount("login", "password", config_file="anixart_data.json") + >>> anix = AnixartAPI(user) + :param user: :class:`AnixUserAccount ` object + :return: :class:`AnixAPIRequests ` object + """ + self.auth = AnixartAuth(user) + self.user = user + self.http_handler = AnixartRequestsHandler(user.token, user.session) + def __str__(self) -> str: ... + def execute(self, http_method: str, endpoint: str) -> requests.Request: ... diff --git a/anixart/auth.py b/anixart/auth.py new file mode 100644 index 0000000..b245066 --- /dev/null +++ b/anixart/auth.py @@ -0,0 +1,67 @@ +import json +import logging +import os.path + +from .endpoints import SING_IN, CHANGE_PASSWORD, PROFILE +from .errors import AnixartAuthError +from .request_handler import AnixartRequestsHandler + + +def _parse_response(data): + ready = data.json() + ready.update({"status_code": data.status_code}) + code = ready['code'] + if code != 0: + if code == 2: + raise AnixartAuthError("Incorrect login.") + if code == 3: + raise AnixartAuthError("Incorrect password.") + print("\n\n" + data.text + "\n\n") + raise AnixartAuthError("Unknown auth error.") + return ready + + +class AnixartAuth(AnixartRequestsHandler): + + def __init__(self, user): + super(AnixartAuth, self).__init__(None, user.session, "anixart.auth.AnixAuth") + self.user = user + self.filename = user.config_file + + def _save_config(self, data): + with open(self.filename, "w") as f: + json.dump(data, f) + return data + + def _open_config(self): + if os.path.isfile(self.filename): + with open(self.filename, "r") as read_file: + data = json.load(read_file) + return data + else: + return None + + def sing_in(self): + config = self._open_config() + if config: + uid = config.get("id") + token = config.get("token") + if not self.get(PROFILE.format(uid), {"token": token}).json().get("is_my_profile") or \ + self.user.login != config.get("login"): + logging.getLogger("anixart.api.AnixAPI").debug("Invalid config file. Re login.") + else: + self.user.id = uid + self.user.token = token + return config + payload = {"login": self.user.login, "password": self.user.password} + res = self.post(SING_IN, payload) + ready = _parse_response(res) + uid = ready["profile"]["id"] + token = ready["profileToken"]["token"] + self.user.id = uid + self.user.token = token + self._save_config({"id": uid, "token": token, "login": self.user.login}) + return ready + + def change_password(self, old, new): + return self.get(CHANGE_PASSWORD, {"current": old, "new": new, "token": self.user.token}) diff --git a/anixart/errors.py b/anixart/errors.py new file mode 100644 index 0000000..e93b52f --- /dev/null +++ b/anixart/errors.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +class AnixartInitError(Exception): + pass + + +class AnixartAuthError(Exception): + pass + + +class AnixartAPIRequestError(Exception): + pass + + +class AnixartAPIError(Exception): + pass diff --git a/anixart/request_handler.py b/anixart/request_handler.py new file mode 100644 index 0000000..a1309a1 --- /dev/null +++ b/anixart/request_handler.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- + +import logging + +import requests + +from .__version__ import __version__, __build__ +from .endpoints import API_URL +from .errors import AnixartAPIRequestError, AnixartAPIError +_log_name = "file:%-28s -> %s" % ("", "%s") + + +def _parse_res_code(res, payload, http_method, http_headers): + json = res.json() + error = json.get("error") + code = json.get("code") + if res.status_code >= 400: + raise AnixartAPIRequestError(f"\n\nAnixartPyAPIWrapper: ERROR\n" + f"Send this info to author: https://t.me/SantaSpeen\n" + f"URL: {http_method} {res.url}\n" + f"Status code: {res.status_code}\n" + f"Res headers: {res.headers}\n" + f"Req headers: {http_headers}\n" + f"Server res: {json}\n" + f"Client req: {payload}\n") + if error: + raise AnixartAPIRequestError(f"Internal server error: {error}; Payload: {payload}") + if code: + if code == 0: + return + else: + raise AnixartAPIError(f"AnixartAPI send error code: {code}; Json: {json}") + + +class AnixartRequestsHandler: + + def __init__(self, token: str = None, session: requests.Session = None, + _log_class="Anixart.request_handler.AnixartRequestsHandler"): + self.__log = logging.getLogger(_log_class) + self.__log.debug(_log_name, 44, f"__init__ - INIT from {self}") + if session: + self.__session = session + else: + self.__log.debug(_log_name, 48, "Create new session.") + self.__session = requests.Session() + self.__session.headers = { + 'User-Agent': f'AnixartPyAPIWrapper/{__version__}-{__build__}' + f' (Linux; Android 12; SantaSpeen AnixartPyAPIWrapper Build/{__build__})'} + self.__token = token + + def post(self, method: str, payload: dict = None, is_json: bool = False, **kwargs): + if payload is None: + payload = {} + url = API_URL + method + if payload.get("token") is None: + if self.__token is not None: + payload.update({"token": self.__token}) + url += "?token=" + self.__token + else: + token = kwargs.get("token") + if token is not None: + payload.update({"token": token}) + url += "?token=" + token + kwargs = {"url": url} + if is_json: + self.__session.headers.update({"Content-Type": "application/json; charset=UTF-8"}) + self.__session.headers.update({"Content-Length": str(len(str(payload)))}) + kwargs.update({"json": payload}) + else: + kwargs.update({"data": payload}) + self.__log.debug(_log_name, 79, f"{'json' if is_json else ''} POST {method}; {payload}") + res = self.__session.post(**kwargs) + _parse_res_code(res, payload, "POST", self.__session.headers) + self.__session.headers["Content-Type"] = "" + self.__session.headers["Content-Length"] = "" + return res + + def get(self, method: str, payload: dict = None, **kwargs): + if payload is None: + payload = {} + if payload.get("token") is None: + if self.__token is not None: + payload.update({"token": self.__token}) + else: + token = kwargs.get("token") + if token is not None: + payload.update({"token": token}) + self.__log.debug(_log_name, 101, f"GET {method}; {payload}") + res = self.__session.get(API_URL + method, params=payload) + _parse_res_code(res, payload, "GET", self.__session.headers) + return res diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index b289bba..0161f04 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,6 +2,32 @@ ## Anixart API Wrapper +### 28.09.2022 +#### Version: 0.2.1, Build: 3 + +##### Changes: + +* Перетащил файлы из прошлого проекта + - Поменял нейминиги + - Оптимизировал `request_handler.py` + - Оптимизировал `errors.py` + - Оптимизировал `auth.py` + - Оптимизировал `api/api.py` + - Добавил `api/api.pyi` +* Добавил пример: `/examples/auth.py` +* Обновил зависимости + +##### TODOs: + +* Метод `AnixartAPI.execute()` пересобрать и сделать нормальный хандлер запроса. + +_Из прошлых версий_ + +* Проверить эндпоинты через чарлес + - Задокументировать методы + - Выявить и удалить не рабочие + - Выявить и удалить не используемые + ### 27.09.2022 [//]: # ( Да, я не билдил, это не ошибка ) #### Version: 0.1.0, Build: 1 diff --git a/examples/auth.py b/examples/auth.py index e69de29..59ec774 100644 --- a/examples/auth.py +++ b/examples/auth.py @@ -0,0 +1,9 @@ +from anixart import AnixartAPI, AnixartUserAccount, PROFILE + +user = AnixartUserAccount("SantaSpeen", "I_H@ve_Very_Secret_P@ssw0rd!") +anix = AnixartAPI(user) + + +if __name__ == '__main__': + me = anix.execute("GET", PROFILE.format(user.id)) + print(me.json()) diff --git a/requirements.txt b/requirements.txt index e69de29..1d769b7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -0,0 +1 @@ +requests~=2.28.1