35 Commits

Author SHA1 Message Date
SantaSpeen 0682bdb42b Endpoint constructor 2025-04-04 18:17:36 +03:00
SantaSpeen 63bf159679 [~] minor 2025-04-04 18:17:23 +03:00
SantaSpeen 8a944319d6 [+] Fixes for post 2025-04-04 17:57:52 +03:00
SantaSpeen aee5c542b4 Данные о версии 0.3.0.1 2025-04-02 18:21:08 +03:00
SantaSpeen c740242c3f Обновил ошибки в хандлере 2025-04-02 18:17:36 +03:00
SantaSpeen 2f18c5ce85 [^] Изменил версию и статус 2025-04-02 18:08:52 +03:00
SantaSpeen 9c94b68d55 [+] Profile.from_response 2025-04-02 18:07:54 +03:00
SantaSpeen bb852d2b3a [~] Починил авторизацию 2025-04-02 17:55:28 +03:00
SantaSpeen d9a577ee14 [~] Ошибки поменялись? 2025-04-02 17:55:16 +03:00
SantaSpeen 52ebb42aa2 [~] Промежуточный вариант 2025-04-02 17:47:27 +03:00
SantaSpeen d726088002 [~] Вычисляю где и как кто какал 2025-04-02 17:47:08 +03:00
SantaSpeen 577e8dbf2a [+] По дефолту теперь AnixartAccountGuest 2025-04-02 17:46:53 +03:00
SantaSpeen c68735df97 [+] profile.objects.PROFILE
[+] profile.objects.PROFILE_NICK_HISTORY
2025-04-02 17:46:28 +03:00
SantaSpeen dd550855c0 [+] AnixartAccountGuest
[+] AnixartAccount.login
[+] auth.objects
2025-04-02 17:43:46 +03:00
SantaSpeen 733da877fa [+] secrets.txt 2025-04-02 16:58:52 +03:00
SantaSpeen 14fb575548 [~] Надо бы сделать 2025-04-02 16:23:36 +03:00
SantaSpeen 8762cca288 Обновил импорты 2025-04-02 16:04:10 +03:00
SantaSpeen d288837bb4 [~] 2025-04-02 16:03:39 +03:00
SantaSpeen 3bd27840e1 [+] AnixartApiErrors
[+] Описание для AnixartAPIError
2025-04-02 16:03:27 +03:00
SantaSpeen 87fa813700 [>] {api/, request_handler} > api.py 2025-04-02 16:02:46 +03:00
SantaSpeen 29205efe4f [+] AnixartAccountSaved 2025-04-02 16:02:01 +03:00
SantaSpeen 68e45336ad [+] Версия ?.?.? 2025-04-02 15:35:06 +03:00
SantaSpeen ae1f97a07c [+] 0.3.0? 2025-04-02 15:33:25 +03:00
SantaSpeen 85c7605b2c [~] Обновил год в LICENSE 2025-04-02 15:29:24 +03:00
SantaSpeen 82874814e0 [!] Глобальные изменения
[+] Блок auth
[>] __version__ > __meta__
[>] errors > exceptions
2025-04-02 15:20:46 +03:00
SantaSpeen 44e0485f99 [~] Изменил версию 2025-04-02 15:19:22 +03:00
SantaSpeen daec2199b6 [~] Обновил команду) 2025-04-02 14:31:09 +03:00
SantaSpeen fbb738fd1c [+] poetry 2025-04-02 14:30:51 +03:00
SantaSpeen c4d758f65a [+] poetry.lock 2025-04-02 14:30:20 +03:00
SantaSpeen 04be5ed2a9 Немного изменил нейминг 2022-09-27 23:36:32 +03:00
SantaSpeen 87ff4463c4 Перетащил <br/> 2022-09-27 23:33:38 +03:00
SantaSpeen 403e7c8b79 Как работать с библиотекой 2022-09-27 23:33:09 +03:00
SantaSpeen 2f0e4e3942 Создал список примеров 2022-09-27 23:30:03 +03:00
SantaSpeen 1c8d56dca6 Добавил "Обратная связь" 2022-09-27 23:29:43 +03:00
SantaSpeen 326034fda9 Добавил способ установки
Изменил инфу по поводу доков
2022-09-27 23:25:04 +03:00
30 changed files with 612 additions and 421 deletions
+3
View File
@@ -130,3 +130,6 @@ dmypy.json
# PyCharm # PyCharm
.idea/ .idea/
poetry.lock
secrets.txt
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2022 Maxim Khomutov Copyright (c) 2025 Maxim Khomutov
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without limitation in the rights to use, copy, modify, merge, publish, and/ or distribute copies of the Software in an educational or personal context, subject to the following conditions: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without limitation in the rights to use, copy, modify, merge, publish, and/ or distribute copies of the Software in an educational or personal context, subject to the following conditions:
+19 -10
View File
@@ -4,16 +4,25 @@
Враппер для использования Anixart API.\ Враппер для использования Anixart API.\
Библиотека создана только для ознакомления c API. Библиотека создана только для ознакомления c API.
**Автор презирает и не поддерживает создание авторегов / ботов для накрутки / спам ботов.** **Автор не поддерживает и презирает создание авторегов / ботов для накрутки / спам ботов.**
### Вся документация в папке [docs](./docs) ### Установка
```shell
pip install anixart
```
### Как работать с библиотекой
#### Вся документация в папке [docs](https://github.com/SantaSpeen/anixart/tree/master/docs)
_(Пока что не дошли руки написать доки, гляньте в [examples](https://github.com/SantaSpeen/anixart/tree/master/examples) как работать с библиотекой)_
## Обратная связь
Если у вас возникли вопросы, или вы хотите каким-либо образом помочь автору вы можете написать ему в телеграм: [@SantaSpeen](https://t.me/SantaSpeen)
<br/>
## License ## License
```text
Copyright (c) 2022 Maxim Khomutov Проект распространяется под лицензией `FPA License`. Подробнее в [LICENSE](LICENSE)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without limitation in the rights to use, copy, modify, merge, publish, and/ or distribute copies of the Software in an educational or personal context, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
Permission is granted to sell and/ or distribute copies of the Software in a commercial context, subject to the following conditions:
- Substantial changes: adding, removing, or modifying large parts, shall be developed in the Software. Reorganizing logic in the software does not warrant a substantial change.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
```
+6 -3
View File
@@ -1,8 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from .__version__ import __license__, __description__ from .__meta__ import *
from .__version__ import __version__, __url__, __build__, __title__, __author__, __author_email__, __copyright__
from .api import AnixartAPI
from .auth import AnixartAccount, AnixartAccountGuest, AnixartAccountSaved
from .endpoints import * from .endpoints import *
from .api.api import AnixartUserAccount, AnixartAPI from . import enums
from . import exceptions
+115
View File
@@ -0,0 +1,115 @@
# -*- coding: utf-8 -*-
import requests
from .__meta__ import __version__, __build__
from .auth import AnixartAccount, AnixartAccountGuest
from .enums import AnixartApiErrors
from .exceptions import AnixartAPIRequestError, AnixartAPIError
from .exceptions import AnixartInitError
debug = True
class AnixartAPI:
API_URL = "https://api.anixart.tv/"
def __init__(self, account: AnixartAccount = None):
if account is None:
account = AnixartAccountGuest()
self.use_account(account)
def use_account(self, account: AnixartAccount):
if not isinstance(account, AnixartAccount):
raise AnixartInitError(f'Use class "AnixartAccount" for user. But {type(account)} given.')
self.__account = account
self.__account._set_api(self)
self.__account.login()
self.__token = account.token
self._session = account.session
self._session.headers = {
'User-Agent': f'AnixartPyAPI/{__version__}-{__build__} (Linux; Android 15; AnixartPyAPI Build/{__build__})'
}
@property
def account(self):
return self.__account
def __parse_response(self, res: requests.Response):
if debug:
print(f"[D] -> {res.request.method} body='{res.request.body!s}' url='{res.url!s}'")
print(f"[D] <- {res.status_code}, {len(res.text)=}")
if res.status_code != 200:
e = AnixartAPIRequestError("Bad Request: Invalid request parameters.")
e.message = (
f"Bad Request: Invalid request parameters.\n"
f"Request: {res.request.method} {res.url}\n"
f"Status code: {res.status_code}\n"
f"Response: {res.text}\n"
f"Client headers: {self._session.headers}\n"
f"Client payload: {res.request.body}"
)
e.code = 400
raise e
if not res.text:
raise AnixartAPIError("AnixartAPI send unknown error: Empty response. Is provided data correct?")
try:
response = res.json()
except ValueError as e:
raise AnixartAPIError("Failed to parse JSON response")
if debug:
print(response)
if response['code'] != 0:
code = response['code']
if code in AnixartApiErrors:
e = AnixartAPIError(f"AnixartAPI send error: {AnixartApiErrors(code).name}")
e.message = AnixartApiErrors(code).name
else:
e = AnixartAPIError(f"AnixartAPI send unknown error, code: {response['code']}")
e.code = response['code']
raise e
return response
def _post(self, method: str, _json: bool = False, **kwargs):
url = self.API_URL + method
kwargs["token"] = kwargs.get("token", self.__token)
if kwargs["token"]:
url += f"?token={self.__token}"
req_settings = {"url": url}
if _json:
self._session.headers["Content-Type"] = "application/json; charset=UTF-8"
req_settings.update({"json": kwargs})
else:
req_settings.update({"data": kwargs})
res = self._session.post(**req_settings)
return self.__parse_response(res)
def _get(self, method: str, **kwargs):
if self.__token:
kwargs["token"] = kwargs.get("token", self.__token)
res = self._session.get(self.API_URL + method, params=kwargs)
return self.__parse_response(res)
def execute(self, http_method, endpoint, **kwargs):
http_method = http_method.upper()
if http_method == "GET":
return self._get(endpoint, **kwargs)
elif http_method == "POST":
return self._post(endpoint, **kwargs)
else:
raise AnixartAPIRequestError("Allow only GET and POST requests.")
def get(self, endpoint, *args, **kwargs):
return self.execute("GET", endpoint.format(*args), **kwargs)
def post(self, endpoint, *args, **kwargs):
return self.execute("POST", endpoint.format(*args), **kwargs)
def __str__(self):
return f'AnixartAPI(account={self.__account!r})'
def __repr__(self):
return f"<{self}>"
-78
View File
@@ -1,78 +0,0 @@
import logging
import requests
from ..auth import AnixartAuth
from ..errors import AnixartInitError, AnixartAPIRequestError
from ..request_handler import AnixartRequestsHandler
_log_name = "file:%-29s -> %s" % ("<anixart.api:%-4i>", "%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.")
-65
View File
@@ -1,65 +0,0 @@
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 <anixart.api.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 <anixart.api.AnixUserAccount>` object
:return: :class:`AnixAPIRequests <anixart.api.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: ...
-67
View File
@@ -1,67 +0,0 @@
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})
+1
View File
@@ -0,0 +1 @@
from .account import AnixartAccount, AnixartAccountSaved, AnixartAccountGuest
+125
View File
@@ -0,0 +1,125 @@
import json
from pathlib import Path
import requests
from anixart import endpoints
from anixart.exceptions import AnixartInitError
class AnixartAccount:
guest = False
def __init__(self, username: str, password: str):
self._username = username
self._password = password
if not isinstance(username, str) or not isinstance(password, str):
raise AnixartInitError("Auth data must be strings.")
self._id = None
self._token = None
self._session = requests.Session()
self._api = None
def _set_api(self, api):
self._api = api
@property
def id(self):
return self._id
@property
def username(self):
return self._username
@property
def session(self):
return self._session
@property
def token(self):
return self._token
def to_file(self, filename: str | Path) -> "AnixartAccountSaved":
"""Save the account information to a file."""
acc = AnixartAccountSaved.from_account(filename, self)
acc.save()
return acc
@classmethod
def from_file(cls, filename: str | Path) -> "AnixartAccountSaved":
"""Load the account information from a file."""
acc = AnixartAccountSaved(filename)
acc.load()
return acc
def login(self):
"""Login to Anixart and return the token."""
res = self._api.post(endpoints.SING_IN, login=self.username, password=self._password)
uid = res["profile"]["id"]
token = res["profileToken"]["token"]
self._id = uid
self._token = token
def __str__(self):
return f'AnixartAccount(login={self._username!r}, password={"*" * len(self._password)!r})'
def __repr__(self):
return f"<{self}>"
class AnixartAccountSaved(AnixartAccount):
def __init__(self, account_file: str | Path = "anixart_account.json"):
super().__init__("", "")
self._file = Path(account_file)
def save(self):
"""Save the account information to a file."""
data = {
"id": self._id,
"token": self._token
}
with open(self._file, 'w') as f:
json.dump(data, f, indent=4)
def load(self):
"""Load the account information from a file."""
if not self._file.exists():
raise AnixartInitError(f"Account file {self._file} does not exist.")
with open(self._file, 'r') as f:
data = json.load(f)
self._id = data.get("id")
self._username = data.get("username")
self._token = data.get("token")
if not self._id or not self._token:
raise AnixartInitError("id and token must be provided in the account file.")
@classmethod
def from_account(cls, account_file: str | Path, account: AnixartAccount):
c = cls(account_file)
c._username = account.username
c._password = account._password
c._token = account.token
return c
def login(self):
"""Login to Anixart using the saved credentials."""
# Проверяем токен, если просрочен, то логинимся
# Если токен валиден, то просто дальше работаем
# TODO: Implement login logic here
pass
def __str__(self):
return f'AnixartAccountSaved(account_file={self._file!r}")'
class AnixartAccountGuest(AnixartAccount):
guest = True
def __init__(self):
super().__init__("", "")
self._token = ""
def login(self): ...
def __str__(self):
return f'AnixartAccountGuest(token={self._token!r}")'
+11
View File
@@ -0,0 +1,11 @@
from enum import IntEnum
class AnixartAuthErrors(IntEnum):
INCORRECT_LOGIN = 2
INCORRECT_PASSWORD = 3
def errors_handler(error):
"""Handle errors and return a JSON response."""
pass
+7
View File
@@ -0,0 +1,7 @@
from dataclasses import dataclass
@dataclass
class ProfileToken:
id: int
token: str
+77 -21
View File
@@ -1,29 +1,85 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from dataclasses import dataclass, field
class AnixartComment: from typing import Literal, Any
DISLIKE = 1
LIKE = 2
class AnixartProfileVotedSort: @dataclass
LAST_FIRST = 1 class Endpoint:
OLD_FIRST = 2 path: str
STAR_5 = 3 method: Literal["GET", "POST"]
STAR_4 = 4 required_args: dict[str, type] = field(default_factory=dict)
STAR_3 = 5
STAR_2 = 6
STAR_1 = 7
_json = False
_API_ENDPOINT = "https://api.anixart.tv/"
class AnixartLists: def __post_init__(self):
WATCHING = 1 if self.method not in ["GET", "POST"]:
IN_PLANS = 2 raise ValueError("Method must be either GET or POST.")
WATCHED = 3 if not isinstance(self.required_args, dict):
POSTPONED = 4 raise ValueError("Required arguments must be a dictionary.")
DROPPED = 5 if not all(isinstance(v, type) for v in self.required_args.values()):
raise ValueError("All values in required arguments must be types.")
def _post(self, method: str, **kwargs):
headers = {}
url = self._API_ENDPOINT + method
if token:=kwargs.get("token"):
kwargs["token"] = token
url += f"?token={token}"
req_settings = {"url": url}
if self._json:
headers["Content-Type"] = "application/json; charset=UTF-8"
req_settings.update({"json": kwargs})
else:
req_settings.update({"data": kwargs})
return req_settings, headers
def _get(self, method: str, **kwargs):
headers = {}
url = self._API_ENDPOINT + method
if token:=kwargs.get("token"):
kwargs["token"] = token
req_settings = {"url": url, "params": kwargs}
return req_settings, headers
def _check_arguments(self, **kwargs):
"""Check if all required arguments are present."""
missing_args = [] # (arg, reason)
for arg, arg_type in self.required_args.items():
if arg not in kwargs:
missing_args.append((arg, "missing"))
elif not isinstance(kwargs[arg], arg_type):
missing_args.append((arg, f"invalid type: {type(kwargs[arg])}"))
if missing_args:
pretty_args = ", ".join(f"{arg} ({reason})" for arg, reason in missing_args)
raise ValueError(f"Missing or invalid arguments: {pretty_args}")
def build_request(self, **kwargs) -> tuple[dict[str, dict[str, Any] | str], dict[str, str]]:
"""
Build the request for the endpoint.
:param kwargs: Arguments to be passed to the endpoint.
:return: A tuple containing the HTTP method, headers, and request settings.
"""
self._check_arguments(**kwargs)
if self.method == "POST":
return self._post(self.path, **kwargs)
if self.method == "GET":
return self._get(self.path, **kwargs)
def endpoint(path: str, method: Literal["GET", "POST"], required_args: dict[str, type]) -> Endpoint:
return Endpoint(path, method, required_args)
class AnixartAuthEndpoints:
"""Anixart API authentication endpoints."""
login = endpoint("/auth/signIn", "POST", {"login": str, "password": str})
class AnixartEndpoints:
"""Anixart API endpoints."""
def __init__(self):
pass
API_URL = "https://api.anixart.tv"
# ----------- # AUTH # ----------- # # ----------- # AUTH # ----------- #
@@ -42,8 +98,8 @@ _AUTH_SING_IN_WITH_VK = "/auth/vk" # {vkAccessToken}
# SETTINGS_RELEASE_TYPE # SETTINGS_RELEASE_TYPE
# GET # GET
PROFILE = "/profile/{}" # + profile id PROFILE = "/profile/{}" # + profile id (Токен нужен только что бы был is_my_profile)
PROFILE_NICK_HISTORY = "/profile/login/history/all/{}/{}" # profile id / page PROFILE_NICK_HISTORY = "/profile/login/history/all/{}/{}" # profile id / page (Токен не нужен)
PROFILE_BLACKLIST = "/profile/blocklist/all/{}" # page PROFILE_BLACKLIST = "/profile/blocklist/all/{}" # page
PROFILE_BLACKLIST_ADD = "/profile/blocklist/add/{}" # profile id PROFILE_BLACKLIST_ADD = "/profile/blocklist/add/{}" # profile id
+28
View File
@@ -0,0 +1,28 @@
from enum import IntEnum
class AnixartApiErrors(IntEnum):
""" Error codes for AnixartApi authentication."""
INCORRECT_LOGIN = 2
INCORRECT_PASSWORD = 3
class AnixartComment(IntEnum):
DISLIKE = 1
LIKE = 2
class AnixartProfileVotedSort(IntEnum):
LAST_FIRST = 1
OLD_FIRST = 2
STAR_5 = 3
STAR_4 = 4
STAR_3 = 5
STAR_2 = 6
STAR_1 = 7
class AnixartLists(IntEnum):
WATCHING = 1
IN_PLANS = 2
WATCHED = 3
POSTPONED = 4
DROPPED = 5
-16
View File
@@ -1,16 +0,0 @@
# -*- coding: utf-8 -*-
class AnixartInitError(Exception):
pass
class AnixartAuthError(Exception):
pass
class AnixartAPIRequestError(Exception):
pass
class AnixartAPIError(Exception):
pass
+17
View File
@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
class AnixartBasError(Exception): ...
# Init errors
class AnixartInitError(AnixartBasError, TypeError): ...
# API errors
class AnixartAPIError(AnixartBasError):
message = "unknown error"
code = 0
class AnixartAuthError(AnixartAPIError): ...
class AnixartAPIRequestError(AnixartAPIError): ...
+1
View File
@@ -0,0 +1 @@
from .objects import Profile, ProfileFull, ProfileVote, ProfileRoles, ProfileHistory, ProfileFriendsPreview
+11
View File
@@ -0,0 +1,11 @@
from enum import IntEnum
class AnixartProfileErrors(IntEnum):
""" Error codes for AnixartApi authentication."""
PROFILE_NOT_FOUND = 2
def errors_handler(error):
"""Handle errors and return a JSON response."""
pass
+118
View File
@@ -0,0 +1,118 @@
from dataclasses import dataclass
@dataclass
class ProfileHistory:
# TODO: Надо ещё изучить релизы
pass
@dataclass
class ProfileVote:
# TODO: Надо ещё изучить релизы
pass
@dataclass
class ProfileRoles:
# TODO: Надо ещё изучить роли (У меня их нет(()
pass
@dataclass
class Profile:
id: int
login: str
avatar: str
@dataclass
class ProfileFriendsPreview(Profile):
friend_count: int
friend_status: int
is_sponsor: bool
is_online: bool
is_verified: bool
is_social: bool
badge_id: None
badge_name: None
badge_type: None
badge_url: None
@dataclass
class ProfileFull(Profile):
status: str
rating_score: int
history: list[ProfileHistory]
votes: list[ProfileVote]
roles: list[ProfileRoles]
friends_preview: list[ProfileFriendsPreview]
collections_preview: list # TODO: Коллекции изучить
release_comments_preview: list # Тут вообще не понял
comments_preview: list # Тут типа превью к коментам
release_videos_preview: list # Тут вообще не понял
watch_dynamics: list # Тут вообще не понял
last_activity_time: int
register_date: int
vk_page: str
tg_page: str
inst_page: str
tt_page: str
discord_page: str
ban_expires: int
ban_reason: str
privilege_level: int
watching_count: int
plan_count: int
completed_count: int
hold_on_count: int
dropped_count: int
favorite_count: int
comment_count: int
collection_count: int
video_count: int
friend_count: int
subscription_count: int
watched_episode_count: int
watched_time: int
sponsorshipExpires: int
is_private: bool
is_sponsor: bool
is_banned: bool
is_perm_banned: bool
is_bookmarks_transferred: bool
is_sponsor_transferred: bool
is_vk_bound: bool
is_google_bound: bool
is_release_type_notifications_enabled: bool
is_episode_notifications_enabled: bool
is_first_episode_notification_enabled: bool
is_related_release_notifications_enabled: bool
is_report_process_notifications_enabled: bool
is_comment_notifications_enabled: bool
is_my_collection_comment_notifications_enabled: bool
is_my_article_comment_notifications_enabled: bool
is_verified: bool
is_blocked: bool
is_me_blocked: bool
is_stats_hidden: bool
is_counts_hidden: bool
is_social_hidden: bool
is_friend_requests_disallowed: bool
is_online: bool
badge: None
friend_status: None
is_my_profile: bool = False
@classmethod
def from_response(cls, response: dict) -> "Profile":
profile = {"is_my_profile": response['is_my_profile'], **response['profile']}
return cls(**profile)
class _login:
pos_id: int
id: int
newLogin: str
timestamp: int
class ProfileLoginsHistory:
content: list[_login]
total_count: int
# total_page_count: int
# current_page: int
-91
View File
@@ -1,91 +0,0 @@
# -*- 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" % ("<Anixart.request_handler:%-3i>", "%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
+26 -4
View File
@@ -2,8 +2,31 @@
## Anixart API Wrapper ## Anixart API Wrapper
### 02.04.2025
#### Version: 0.3.0.1
##### Changes:
* Глобальная переборка всего проекта по `SDK-like` принципу
* Переработан блок `API`:
* `AnixartAPI` - Теперь является основным классом для работы с API.
* Добавлен блок `auth`:
* Работа с аккаунтом:
* `AnixartAccount` - Основной класс взаимодействия с аккаунтом (логин, пароль)
* `AnixartAccountSaved` - Работа с сохранённым аккаунтом
* `AnixartAccountGuest` - Авторизация без логина и пароля (Как гость)
* Добавлены ошибки
* `INCORRECT_LOGIN` - Неверный логин
* `INCORRECT_PASSWORD` - Неверный пароль
* Добавлен блок `profile`:
* Добавлены объекты для работы с `/profile/`:
* `Profile` - ДатаКласс для работы с профилем (`endpoints.PROFILE`) __не закончены подклассы__
* `ProfileLoginsHistory` - ДатаКласс для работы с историей логинов (`endpoints.PROFILE_NICK_HISTORY`) __не закончен__
* Добавлены ошибки
* `PROFILE_NOT_FOUND` - Профиль не найден (или невалидный)
### 28.09.2022 ### 28.09.2022
#### Version: 0.2.1, Build: 3 #### Version: 0.2.1
##### Changes: ##### Changes:
@@ -29,8 +52,7 @@ _Из прошлых версий_
- Выявить и удалить не используемые - Выявить и удалить не используемые
### 27.09.2022 ### 27.09.2022
[//]: # ( Да, я не билдил, это не ошибка ) #### Version: 0.1.0
#### Version: 0.1.0, Build: 1
##### Changes: ##### Changes:
@@ -46,7 +68,7 @@ _Из прошлых версий_
### 27.09.2022 ### 27.09.2022
#### Version: 0.0.1, Build: 1 #### Version: 0.0.1
##### Changes: ##### Changes:
View File
+1 -1
View File
@@ -17,4 +17,4 @@
* 2 * 2
* ... * ...
3. [CHANGELOG](./CHANGELOG.md) 3. [CHANGELOG](./CHANGELOG.md)
4. [LICENSE](./License.md) 4. [LICENSE](./LICENSE.md)
+14 -5
View File
@@ -1,9 +1,18 @@
from anixart import AnixartAPI, AnixartUserAccount, PROFILE from anixart import AnixartAPI, AnixartAccount
from anixart import endpoints
from anixart.exceptions import AnixartAPIRequestError
from anixart.profile import Profile
user = AnixartUserAccount("SantaSpeen", "I_H@ve_Very_Secret_P@ssw0rd!") anix = AnixartAPI() # По умолчанию используется гость
anix = AnixartAPI(user)
# acc = AnixartAccount("SantaSpeen", "I_H@ve_Very_Secret_P@ssw0rd!")
# # id у аккаунта появляется только после
# anix.use_account(acc)
if __name__ == '__main__': if __name__ == '__main__':
me = anix.execute("GET", PROFILE.format(user.id)) try:
print(me.json()) raw = anix.get(endpoints.PROFILE, 1)
print(Profile.from_response(raw))
except AnixartAPIRequestError as e:
print(e.message)
print(e.code)
+3
View File
@@ -0,0 +1,3 @@
## Директория с примерами
* Пример авторизации и вывода информации о себе: [auth.py](./auth.py)
+3
View File
@@ -0,0 +1,3 @@
[virtualenvs]
create = true
in-project = true
+25
View File
@@ -0,0 +1,25 @@
[project]
name = "anixart"
version = "0.3.0.1"
description = "Wrapper for using the Anixart API."
authors = [
{name = "SantaSpeen",email = "santaspeen@gmail.com"}
]
license = {text = "FPA License"}
readme = "README.md"
requires-python = ">=3.10,<4.0"
dependencies = ["requests (>=2.32.3,<3.0.0)"]
dynamic = [ "classifiers" ]
[project.urls]
repository = "https://github.com/SantaSpeen/anixart"
[tool.poetry]
classifiers = [
"Development Status :: 2 - Pre-Alpha",
"Topic :: Software Development :: Libraries :: Python Modules"
]
[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"
-1
View File
@@ -1 +0,0 @@
requests~=2.28.1
-58
View File
@@ -1,58 +0,0 @@
# -*- coding: utf-8 -*-
import os
import sys
from setuptools import setup
here = os.path.abspath(os.path.dirname(__file__))
packages = ['anixart']
requires = ['requests']
# 'setup.py publish' shortcut.
if sys.argv[-1] == 'publish':
os.system('py -m build')
os.system('py -m twine upload --repository testpypi dist/*')
os.system('py -m twine upload --repository pypi dist/*')
sys.exit()
about = {}
with open(os.path.join(here, 'anixart', '__version__.py'), 'r', encoding='utf-8') as f:
exec(f.read(), about)
with open('README.md', 'r', encoding='utf-8') as f:
readme = f.read()
setup(
name=about['__title__'],
version=about['__version__'],
description=about['__description__'],
long_description=readme,
long_description_content_type='text/markdown',
author=about['__author__'],
author_email=about['__author_email__'],
url=about['__url__'],
packages=packages,
package_data={'': ['LICENSE']},
package_dir={'anixart': 'anixart'},
include_package_data=True,
install_requires=requires,
license=about['__license__'],
classifiers=[
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"Natural Language :: Russian",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"License :: Other/Proprietary License",
"Operating System :: OS Independent",
],
project_urls={
'Documentation': 'https://anixart.readthedocs.io/',
'Source': 'https://github.com/SantaSpeen/anixart',
},
python_requires=">=3.7",
)