mirror of
https://github.com/SantaSpeen/anixart.git
synced 2026-02-16 10:31:05 +00:00
Compare commits
41 Commits
v0.0.1-pla
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 723cf98475 | |||
| 0682bdb42b | |||
| 63bf159679 | |||
| 8a944319d6 | |||
| aee5c542b4 | |||
| c740242c3f | |||
| 2f18c5ce85 | |||
| 9c94b68d55 | |||
| bb852d2b3a | |||
| d9a577ee14 | |||
| 52ebb42aa2 | |||
| d726088002 | |||
| 577e8dbf2a | |||
| c68735df97 | |||
| dd550855c0 | |||
| 733da877fa | |||
| 14fb575548 | |||
| 8762cca288 | |||
| d288837bb4 | |||
| 3bd27840e1 | |||
| 87fa813700 | |||
| 29205efe4f | |||
| 68e45336ad | |||
| ae1f97a07c | |||
| 85c7605b2c | |||
| 82874814e0 | |||
| 44e0485f99 | |||
| daec2199b6 | |||
| fbb738fd1c | |||
| c4d758f65a | |||
| 04be5ed2a9 | |||
| 87ff4463c4 | |||
| 403e7c8b79 | |||
| 2f0e4e3942 | |||
| 1c8d56dca6 | |||
| 326034fda9 | |||
| 0cf61563be | |||
| a6f1655449 | |||
| c6bfda72df | |||
| 8e9716cd06 | |||
| 9f50168205 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -130,3 +130,6 @@ dmypy.json
|
||||
|
||||
# PyCharm
|
||||
.idea/
|
||||
|
||||
poetry.lock
|
||||
secrets.txt
|
||||
2
LICENSE
2
LICENSE
@@ -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:
|
||||
|
||||
|
||||
29
README.md
29
README.md
@@ -4,16 +4,25 @@
|
||||
Враппер для использования Anixart 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
|
||||
```text
|
||||
Copyright (c) 2022 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:
|
||||
- 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.
|
||||
```
|
||||
|
||||
Проект распространяется под лицензией `FPA License`. Подробнее в [LICENSE](LICENSE)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from .__meta__ import *
|
||||
|
||||
from .api import AnixartAPI
|
||||
|
||||
from . import auth
|
||||
|
||||
from . import enums
|
||||
from . import exceptions
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
__title__ = 'anixart'
|
||||
__description__ = 'Wrapper for using the Anixart API.'
|
||||
__url__ = 'https://github.com/SantaSpeen/anixart'
|
||||
__version__ = '0.0.1'
|
||||
__build__ = 1
|
||||
__author__ = 'Maxim Khomutov'
|
||||
__version__ = '0.2.1'
|
||||
__build__ = 3
|
||||
__author__ = 'SantaSpeen'
|
||||
__author_email__ = 'SantaSpeen@gmail.com'
|
||||
__license__ = "FPA"
|
||||
__copyright__ = 'Copyright 2022 Maxim Khomutov'
|
||||
__copyright__ = 'Copyright 2022 © SantaSpeen'
|
||||
153
anixart/api.py
Normal file
153
anixart/api.py
Normal file
@@ -0,0 +1,153 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import requests
|
||||
|
||||
from .__meta__ import __version__, __build__
|
||||
from .auth import AnixartAccount, AnixartAccountGuest
|
||||
from .endpoints_map import endpoints_map
|
||||
from .exceptions import AnixartAPIRequestError, AnixartAPIError
|
||||
from .exceptions import AnixartInitError
|
||||
|
||||
debug = True
|
||||
|
||||
class _ApiMethodGenerator:
|
||||
def __init__(self, start_path, _callback: callable):
|
||||
self._path = start_path
|
||||
self._callback = _callback
|
||||
|
||||
def __setattr__(self, __name, __value):
|
||||
raise TypeError(f"cannot set '{__name}' attribute of immutable type 'ApiMethodGenerator'")
|
||||
|
||||
def __getattr__(self, item):
|
||||
self._path += f".{item}"
|
||||
return self
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
if debug:
|
||||
print(f"[D] __call__ -> {self._path} args={args} kwargs={kwargs}")
|
||||
return self._callback(self._path, *args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return f"ApiMethodGenerator(path={self._path!r})"
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self}>"
|
||||
|
||||
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:
|
||||
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 __execute_endpoint_from_map(self, key, *args, **kwargs):
|
||||
endpoint = endpoints_map.get(key)
|
||||
if endpoint is None:
|
||||
raise AnixartAPIError("Invalid endpoint.")
|
||||
return self._execute_endpoint(endpoint, *args, **kwargs)
|
||||
|
||||
def _execute_endpoint(self, endpoint, *args, **kwargs):
|
||||
req_settings, headers = endpoint.build_request(*args, **kwargs)
|
||||
self._session.headers.update(headers)
|
||||
if endpoint.is_post:
|
||||
res = self._session.post(**req_settings)
|
||||
else:
|
||||
res = self._session.get(**req_settings)
|
||||
return self.__parse_response(res)
|
||||
|
||||
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 __getattr__(self, item):
|
||||
return _ApiMethodGenerator(item, self.__execute_endpoint_from_map)
|
||||
|
||||
def __str__(self):
|
||||
return f'AnixartAPI(account={self.__account!r})'
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self}>"
|
||||
1
anixart/auth/__init__.py
Normal file
1
anixart/auth/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .accounts import AnixartAccount, AnixartAccountSaved, AnixartAccountGuest
|
||||
125
anixart/auth/account.py
Normal file
125
anixart/auth/account.py
Normal 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}")'
|
||||
|
||||
23
anixart/auth/endpoints.py
Normal file
23
anixart/auth/endpoints.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from anixart.utils import endpoint
|
||||
from .factories import AuthLoginFactory
|
||||
|
||||
|
||||
# ----------- # AUTH # ----------- #
|
||||
#
|
||||
# POST
|
||||
# SING_UP = None
|
||||
# SING_IN = "/auth/signIn"
|
||||
#
|
||||
# !! Not Checked !!
|
||||
# POST
|
||||
# AUTH_SING_IN_WITH_GOOGLE = "/auth/google" # {googleIdToken} or {login, email, googleIdToken}
|
||||
# AUTH_SING_IN_WITH_VK = "/auth/vk" # {vkAccessToken}
|
||||
|
||||
|
||||
class AnixartAuthEndpoints:
|
||||
"""Anixart API authentication endpoints."""
|
||||
singup = None # Удалено дабы исключить автореги (по просьбе аниксарта)
|
||||
login = endpoint("/auth/signIn", "POST", AuthLoginFactory, {}, {"login": str, "password": str})
|
||||
|
||||
# login_google = None # endpoint("/auth/google", "POST", {}, {"googleIdToken": str})
|
||||
# login_vk = None # endpoint("/auth/vk", "POST", {}, {"vkAccessToken": str})
|
||||
41
anixart/auth/factories.py
Normal file
41
anixart/auth/factories.py
Normal file
@@ -0,0 +1,41 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from anixart.exceptions import AnixartAuthError
|
||||
from anixart.utils import BaseParseFactory
|
||||
|
||||
|
||||
@dataclass
|
||||
class _AuthLoginFactoryProfileToken:
|
||||
# { "id": 1, "token": "hash?" }
|
||||
id: int # token id?
|
||||
token: str # token str
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict):
|
||||
return cls(
|
||||
id=data.get("id", -1),
|
||||
token=data.get("token")
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AuthLoginFactory(BaseParseFactory):
|
||||
# { "code": 0, "profile": {...}, "profileToken": {_AuthLoginFactoryProfileToken} }
|
||||
profile: dict # TODO - create a ProfileFactory
|
||||
profileToken: _AuthLoginFactoryProfileToken
|
||||
|
||||
_errors = {
|
||||
2: "Incorrect login",
|
||||
3: "Incorrect password",
|
||||
}
|
||||
_exception = AnixartAuthError
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict):
|
||||
return cls(
|
||||
code=data["code"],
|
||||
profile=data.get("profile", {}),
|
||||
profileToken=_AuthLoginFactoryProfileToken.from_dict(data.get("profileToken", {})),
|
||||
_errors=cls._errors,
|
||||
_exception=cls._exception
|
||||
)
|
||||
9
anixart/endpoints_map.py
Normal file
9
anixart/endpoints_map.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from .auth.factories import AuthLoginFactory
|
||||
|
||||
endpoints_map = {
|
||||
# Auth
|
||||
"auth.login": AuthLoginFactory
|
||||
|
||||
# Profile
|
||||
|
||||
}
|
||||
21
anixart/enums.py
Normal file
21
anixart/enums.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from enum import IntEnum
|
||||
|
||||
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
|
||||
17
anixart/exceptions.py
Normal file
17
anixart/exceptions.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
class AnixartBaseError(Exception): ...
|
||||
|
||||
# Init errors
|
||||
|
||||
class AnixartInitError(AnixartBaseError, TypeError): ...
|
||||
|
||||
# API errors
|
||||
class AnixartAPIError(AnixartBaseError):
|
||||
message = "unknown error"
|
||||
code = 0
|
||||
|
||||
class AnixartAuthError(AnixartAPIError): ...
|
||||
|
||||
class AnixartAPIRequestError(AnixartAPIError): ...
|
||||
|
||||
1
anixart/profile/__init__.py
Normal file
1
anixart/profile/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .factories import Profile, ProfileFull, ProfileVote, ProfileRoles, ProfileHistory, ProfileFriendsPreview
|
||||
80
anixart/profile/endpoints.py
Normal file
80
anixart/profile/endpoints.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from anixart.utils import endpoint
|
||||
|
||||
# # ----------- # PROFILE # ----------- #
|
||||
# # TODO PROFILE: SETTINGS, SETTINGS_RELEASE, SETTINGS_RELEASE_FIRST,
|
||||
# # SETTINGS_COMMENTS, SETTINGS_COLLECTION, EDIT_AVATAR, SETTINGS_RELEASE_LIST,
|
||||
# # SETTINGS_RELEASE_TYPE
|
||||
#
|
||||
# # GET
|
||||
# PROFILE = "/profile/{}" # + profile id (Токен нужен только что бы был is_my_profile)
|
||||
# PROFILE_NICK_HISTORY = "/profile/login/history/all/{}/{}" # profile id / page (Токен не нужен)
|
||||
#
|
||||
# PROFILE_BLACKLIST = "/profile/blocklist/all/{}" # page
|
||||
# PROFILE_BLACKLIST_ADD = "/profile/blocklist/add/{}" # profile id
|
||||
# PROFILE_BLACKLIST_REMOVE = "/profile/blocklist/remove/{}" # profile id
|
||||
#
|
||||
# FRIENDS = "/profile/friend/all/{}/{}" # profile id / page
|
||||
# FRIENDS_RQ_IN = "/profile/friend/requests/in/{}" # page
|
||||
# FRIENDS_RQ_OUT = "/profile/friend/requests/out/{}" # page
|
||||
# FRIENDS_RQ_IN_LAST = "/profile/friend/requests/in/last"
|
||||
# FRIENDS_RQ_OUT_LAST = "/profile/friend/requests/out/last"
|
||||
# FRIENDS_SEND = "/profile/friend/request/send/{}" # profile id
|
||||
# FRIENDS_REMOVE = "/profile/friend/request/remove/{}" # profile id
|
||||
#
|
||||
# VOTE_VOTED = "/profile/vote/release/voted/{}/{}" # profile id / page
|
||||
# # Да, ребята из аниксарта не знают английский; ↓
|
||||
# # noinspection SpellCheckingInspection
|
||||
# VOTE_UNVENTED = "/profile/vote/release/unvoted/{}" # page
|
||||
#
|
||||
# LISTS = "/profile/list/all/{}/{}/{}" # profile id / list id / page
|
||||
#
|
||||
# SETTINGS_NOTIFICATION = "/profile/preference/notification/my"
|
||||
# SETTINGS_NOTIFICATION_RELEASE = "/profile/preference/notification/episode/edit"
|
||||
# SETTINGS_NOTIFICATION_RELEASE_FIRST = "/profile/preference/notification/episode/first/edit"
|
||||
# SETTINGS_NOTIFICATION_COMMENTS = "/profile/preference/notification/comment/edit"
|
||||
# SETTINGS_NOTIFICATION_COLLECTION = "/profile/preference/notification/my/collection/comment/edit"
|
||||
#
|
||||
# CHANGE_PASSWORD = "/profile/preference/password/change"
|
||||
#
|
||||
# # POST
|
||||
# EDIT_STATUS = "/profile/preference/status/edit"
|
||||
# EDIT_SOCIAL = "/profile/preference/social/edit"
|
||||
# EDIT_AVATAR = "/profile/preference/avatar/edit"
|
||||
#
|
||||
# # {"profileStatusNotificationPreferences":[0 - favorite, + all in AnixList]}
|
||||
# SETTINGS_NOTIFICATION_RELEASE_LIST = "/profile/preference/notification/status/edit"
|
||||
# # {"profileTypeNotificationPreferences":[type ids]}
|
||||
# SETTINGS_NOTIFICATION_RELEASE_TYPE = "/profile/preference/notification/type/edit"
|
||||
#
|
||||
# # Not Checked
|
||||
# # GET
|
||||
# PROFILE_SOCIAL = "/profile/social/{}" # profile id
|
||||
#
|
||||
# FRIENDS_RECOMMENDATION = "/profile/friend/recommendations"
|
||||
# FRIENDS_RQ_HIDE = "profile/friend/request/hide/{}" # profile id
|
||||
#
|
||||
# SETTINGS_PROFILE = "/profile/preference/my"
|
||||
# SETTINGS_PROFILE_CHANGE_EMAIL = "/profile/preference/email/change" # {current_password, current, new}
|
||||
# SETTINGS_PROFILE_CHANGE_EMAIL_CONFIRM = "/profile/preference/email/change/confirm" # {current}
|
||||
#
|
||||
# # /profile/preference/social
|
||||
# SETTINGS_PROFILE_STATUS_DELETE = "/profile/preference/status/delete"
|
||||
#
|
||||
# # POST
|
||||
# PROFILE_PROCESS = "/profile/process/{}" # profile id
|
||||
#
|
||||
# SETTINGS_PROFILE_CHANGE_LOGIN = "/profile/preference/email/login/confirm" # {login}
|
||||
# SETTINGS_PROFILE_CHANGE_LOGIN_INFO = "/profile/preference/email/login/info" # {login}
|
||||
#
|
||||
# SETTINGS_PROFILE_BIND_GOOGLE = "/profile/preference/google/bind" # {idToken, }
|
||||
# SETTINGS_PROFILE_UNBIND_GOOGLE = "/profile/preference/google/unbind"
|
||||
# SETTINGS_PROFILE_BIND_VK = "/profile/preference/google/bind" # {accessToken, }
|
||||
# SETTINGS_PROFILE_UNBIND_VK = "/profile/preference/google/unbind"
|
||||
#
|
||||
# SETTINGS_PROFILE_PRIVACY_COUNTS = "/profile/preference/privacy/counts/edit"
|
||||
# SETTINGS_PROFILE_PRIVACY_FRIENDS_REQUESTS = "/profile/preference/privacy/friendRequests/edit"
|
||||
# SETTINGS_PROFILE_PRIVACY_SOCIAL = "/profile/preference/privacy/social/edit"
|
||||
# SETTINGS_PROFILE_PRIVACY_STATS = "/profile/preference/privacy/stats/edit"
|
||||
|
||||
class AnixartProfileEndpoints:
|
||||
profile = endpoint("/profile/{id}", "GET", None, {"id": int}, {})
|
||||
187
anixart/profile/factories.py
Normal file
187
anixart/profile/factories.py
Normal file
@@ -0,0 +1,187 @@
|
||||
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
|
||||
|
||||
|
||||
# From singup
|
||||
# "profile": {
|
||||
# "id": 123,
|
||||
# "login": "loginstr",
|
||||
# "avatar": "*.jpg",
|
||||
# "status": "123",
|
||||
# "badge": null,
|
||||
# "history": [],
|
||||
# "votes": [],
|
||||
# "roles": [],
|
||||
# "last_activity_time": 100046000,
|
||||
# "register_date": 100046000,
|
||||
# "vk_page": "",
|
||||
# "tg_page": "",
|
||||
# "inst_page": "",
|
||||
# "tt_page": "",
|
||||
# "discord_page": "",
|
||||
# "ban_expires": 0,
|
||||
# "ban_reason": null,
|
||||
# "privilege_level": 0,
|
||||
# "watching_count": 1,
|
||||
# "plan_count": 1,
|
||||
# "completed_count": 1,
|
||||
# "hold_on_count": 1,
|
||||
# "dropped_count": 1,
|
||||
# "favorite_count": 1,
|
||||
# "comment_count": 1,
|
||||
# "collection_count": 0,
|
||||
# "video_count": 0,
|
||||
# "friend_count": 1,
|
||||
# "subscription_count": 0,
|
||||
# "watched_episode_count": 1,
|
||||
# "watched_time": 1,
|
||||
# "is_private": false,
|
||||
# "is_sponsor": false,
|
||||
# "is_banned": false,
|
||||
# "is_perm_banned": false,
|
||||
# "is_bookmarks_transferred": false,
|
||||
# "is_sponsor_transferred": false,
|
||||
# "is_vk_bound": true,
|
||||
# "is_google_bound": true,
|
||||
# "is_release_type_notifications_enabled": false,
|
||||
# "is_episode_notifications_enabled": true,
|
||||
# "is_first_episode_notification_enabled": true,
|
||||
# "is_related_release_notifications_enabled": true,
|
||||
# "is_report_process_notifications_enabled": true,
|
||||
# "is_comment_notifications_enabled": true,
|
||||
# "is_my_collection_comment_notifications_enabled": true,
|
||||
# "is_my_article_comment_notifications_enabled": false,
|
||||
# "is_verified": false,
|
||||
# "friends_preview": [],
|
||||
# "collections_preview": [],
|
||||
# "release_comments_preview": [],
|
||||
# "comments_preview": [],
|
||||
# "release_videos_preview": [],
|
||||
# "watch_dynamics": [],
|
||||
# "friend_status": null,
|
||||
# "rating_score": 0,
|
||||
# "is_blocked": false,
|
||||
# "is_me_blocked": false,
|
||||
# "is_stats_hidden": false,
|
||||
# "is_counts_hidden": false,
|
||||
# "is_social_hidden": false,
|
||||
# "is_friend_requests_disallowed": false,
|
||||
# "is_online": false,
|
||||
# "sponsorshipExpires": 0
|
||||
# }
|
||||
303
anixart/utils.py
Normal file
303
anixart/utils.py
Normal file
@@ -0,0 +1,303 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Literal, Any
|
||||
|
||||
from anixart.exceptions import AnixartAPIError
|
||||
|
||||
|
||||
@dataclass
|
||||
class BaseParseFactory:
|
||||
code: int
|
||||
|
||||
_errors: dict[int, str]
|
||||
_exception: type[AnixartAPIError]
|
||||
|
||||
def raise_if_error(self):
|
||||
if self.code != 0:
|
||||
error = self._errors.get(self.code)
|
||||
if error:
|
||||
raise error
|
||||
else:
|
||||
raise ValueError(f"Unknown error code: {self.code}")
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> "BaseParseFactory":
|
||||
return cls(
|
||||
code=data.get("code", 0),
|
||||
_errors={},
|
||||
_exception=AnixartAPIError,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Endpoint:
|
||||
path: str
|
||||
method: Literal["GET", "POST"]
|
||||
parse_factory: type[BaseParseFactory]
|
||||
required_args: dict[str, type] = field(default_factory=dict)
|
||||
required_kwargs: dict[str, type] = field(default_factory=dict)
|
||||
|
||||
|
||||
_json = False
|
||||
_API_ENDPOINT = "https://api.anixart.tv/"
|
||||
|
||||
def __post_init__(self):
|
||||
if self.method not in ["GET", "POST"]:
|
||||
raise ValueError("Method must be either GET or POST.")
|
||||
if not isinstance(self.required_kwargs, dict):
|
||||
raise ValueError("Required arguments must be a dictionary.")
|
||||
if not all(isinstance(v, type) for v in self.required_kwargs.values()):
|
||||
raise ValueError("All values in required arguments must be types.")
|
||||
|
||||
@property
|
||||
def is_get(self) -> bool:
|
||||
return self.method == "GET"
|
||||
|
||||
@property
|
||||
def is_post(self) -> bool:
|
||||
return self.method == "POST"
|
||||
|
||||
@property
|
||||
def is_json(self) -> bool:
|
||||
return self._json
|
||||
|
||||
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, "arg missing"))
|
||||
elif not isinstance(kwargs[arg], arg_type):
|
||||
missing_args.append((arg, f"arg invalid type: {type(kwargs[arg])}"))
|
||||
for arg, arg_type in self.required_kwargs.items():
|
||||
if arg not in kwargs:
|
||||
missing_args.append((arg, "kwarg missing"))
|
||||
elif not isinstance(kwargs[arg], arg_type):
|
||||
missing_args.append((arg, f"kwarg 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) -> None | tuple[dict[str, dict[str, Any] | str], dict[Any, Any]] | 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)
|
||||
args = {arg: kwargs[arg] for arg in self.required_args}
|
||||
if self.method == "POST":
|
||||
return self._post(self.path.format(**args), **kwargs)
|
||||
if self.method == "GET":
|
||||
return self._get(self.path.format(**args), **kwargs)
|
||||
|
||||
|
||||
def endpoint(path: str, method: Literal["GET", "POST"], parse_factory: type[BaseParseFactory], required_args: dict[str, type], required_kwargs: dict[str, type]) -> Endpoint:
|
||||
return Endpoint(path, method, parse_factory, required_args, required_kwargs)
|
||||
|
||||
|
||||
|
||||
# ----------- # COLLECTION # ----------- #
|
||||
|
||||
# GET
|
||||
COLLECTION = "/collection/{}" # collection id
|
||||
COLLECTION_RELEASES = "/collection/{}/releases/{}" # collection id / page
|
||||
COLLECTION_LIST = "/collection/all/{}" # page
|
||||
|
||||
COLLECTION_COMMENTS = "/collection/comment/all/{}/{}" # collection id / page
|
||||
COLLECTION_COMMENTS_VOTE = "/collection/comment/vote/{}/{}" # collection comment id / mark (1, 2)
|
||||
COLLECTION_COMMENTS_VOTES = "/collection/comment/votes/{}/{}" # collection comment id / page
|
||||
COLLECTION_COMMENTS_DELETE = "/collection/comment/delete/{}" # collection comment id
|
||||
|
||||
COLLECTION_FAVORITE = "/collectionFavorite/all/{}" # page
|
||||
COLLECTION_FAVORITE_ADD = "/collectionFavorite/add/{}" # collection id
|
||||
COLLECTION_FAVORITE_DELETE = "/collectionFavorite/delete/{}" # collection id
|
||||
|
||||
# POST
|
||||
COLLECTION_COMMENTS_ADD = "/collection/comment/add/{}" # collection id
|
||||
COLLECTION_COMMENTS_EDIT = "/collection/comment/edit/{}" # collection comment id
|
||||
COLLECTION_COMMENTS_REPLIES = "/collection/comment/replies/{}/{}" # collection comment id / page
|
||||
|
||||
# Not Checked
|
||||
# GET
|
||||
COLLECTION_PROFILE = "/collection/all/profile/{}/{}" # p_id/page
|
||||
COLLECTION_RELEASE_IN = "/collection/all/release/{}/{}" # r_id/page
|
||||
|
||||
COLLECTION_MY = "/collectionMy/{id}/releases"
|
||||
COLLECTION_MY_DELETE = "/collectionMy/delete/{}" # collectionId
|
||||
COLLECTION_MY_ADD_RELEASE = "/collectionMy/release/add/{}" # collectionId
|
||||
|
||||
# POST
|
||||
COLLECTION_COMMENTS_PROCESS = "/collection/comment/process/{}" # commentId
|
||||
COLLECTION_COMMENTS_REPORT = "/collection/comment/report/{}" # commentId
|
||||
|
||||
COLLECTION_REPORT = "/collection/report/{}" # collectionId
|
||||
|
||||
COLLECTION_MY_CREATE = "/collectionMy/create"
|
||||
COLLECTION_MY_EDIT = "/collectionMy/edit/{}" # collectionId
|
||||
COLLECTION_MY_EDIT_IMAGE = "/collectionMy/editImage/{}" # collectionId
|
||||
|
||||
# ----------- # RELEASE # ----------- #
|
||||
|
||||
# GET
|
||||
RELEASE = "/release/{}" # release id
|
||||
RELEASE_VOTE_ADD = "/release/vote/add/{}/{}" # release id / mark 1-5
|
||||
RELEASE_VOTE_DELETE = "/release/vote/delete/{}" # release id
|
||||
RELEASE_RANDOM = "/release/random"
|
||||
|
||||
RELEASE_COMMENTS = "/release/comment/all/{}/{}" # release id / page
|
||||
RELEASE_COMMENTS_VOTE = "/release/comment/vote/{}/{}" # release comment id / mark (1, 2)
|
||||
RELEASE_COMMENTS_VOTES = "/release/comment/votes/{}/{}" # release comment id / page
|
||||
RELEASE_COMMENTS_REPLIES = "/release/comment/replies/{}/{}" # release comment id / page
|
||||
RELEASE_COMMENTS_DELETE = "/release/comment/delete/{}" # release comment id
|
||||
|
||||
# POST
|
||||
RELEASE_COMMENTS_ADD = "/release/comment/add/{}" # release id
|
||||
RELEASE_COMMENTS_EDIT = "/release/comment/edit/{}" # release comment id
|
||||
|
||||
# Not Checked
|
||||
# GET
|
||||
RELEASE_COMMENTS_REPORT = "/release/comment/report/{}" # commentId
|
||||
RELEASE_COMMENTS_PROCESS = "/release/comment/process/{}" # commentId
|
||||
|
||||
RELEASE_PROFILE_COMMENTS = "/release/comment/all/profile/{}/{}" # p_id/page
|
||||
|
||||
RELEASE_STREAMING_PLATFORM = "/release/streaming/platform/{}/" # releaseId
|
||||
|
||||
# POST
|
||||
RELEASE_REPORT = "/release/report/{}" # r_id
|
||||
|
||||
# ----------- # OTHER # ----------- #
|
||||
# TODO OTHER: EXPORT_BOOKMARKS, IMPORT_BOOKMARKS, CAN_IMPORT_BOOKMARKS
|
||||
|
||||
# GET
|
||||
TYPE = "/type/all"
|
||||
TYPE_RELEASE = "/type/{}" # r_id
|
||||
TOGGLES = "/config/toggles?version_code={}&is_beta={}" # version_code: int, is_beta: bool
|
||||
SCHEDULE = "/schedule"
|
||||
|
||||
# POST
|
||||
# {"bookmarksExportProfileLists":[0 - favorite, + all in AnixList]}
|
||||
EXPORT_BOOKMARKS = "/export/bookmarks"
|
||||
# {"completed":[],"dropped":[],"holdOn":[],"plans":[],"watching":[],"selected_importer_name":"Shikimori"}
|
||||
IMPORT_BOOKMARKS = "/import/bookmarks"
|
||||
CAN_IMPORT_BOOKMARKS = "/import/status" # code: 0 - Yes, code: 2 - no
|
||||
|
||||
# Not Checked
|
||||
# GET
|
||||
# page {token, genres: [], studio, category, status, year, episodes, sort,
|
||||
# country, season, duration, ratings: [], extended_mode: bool}
|
||||
FILTER = "/filter/{}"
|
||||
RELATED = "related/{}/{}" # relatedId/page
|
||||
|
||||
# POST
|
||||
VIDEO_PARSE = "/video/parse"
|
||||
|
||||
# ----------- # SEARCH # ----------- #
|
||||
# TODO SEARCH: *
|
||||
|
||||
# { "query": text, "searchBy": 0}
|
||||
# POST
|
||||
SEARCH_COLLECTION = "/search/collections/{}" # page
|
||||
SEARCH_RELEASE = "/search/releases/{}" # page
|
||||
SEARCH_FAVORITE = "/search/favorites/{}" # page
|
||||
SEARCH_COLLECTION_FAVORITE = "/search/favoriteCollections/{}" # page
|
||||
SEARCH_LIST = "/search/profile/list/{}/{}" # list id / page
|
||||
SEARCH_PROFILE = "/search/profiles/{}" # page
|
||||
SEARCH_HISTORY = "/search/history/{}" # page
|
||||
|
||||
#
|
||||
SEARCH_COLLECTION_PROFILE = "/search/profileCollections/{}/{}" # p_id/page
|
||||
|
||||
# ----------- # NOTIFICATIONS # ----------- #
|
||||
# TODO NOTIFICATIONS: *
|
||||
|
||||
# GET
|
||||
NOTIFICATION_READ = "/notification/read"
|
||||
NOTIFICATION_COUNT = "/notification/count"
|
||||
NOTIFICATION_COLLECTION_COMMENTS = "/notification/collectionComments/{}" # page
|
||||
NOTIFICATION_MY_COLLECTION_COMMENTS = "/notification/my/collection/comments/{}" # page
|
||||
NOTIFICATION_RELEASE_COMMENTS = "/notification/releaseComments/{}" # page
|
||||
NOTIFICATION_EPISODES = "/notification/episodes/{}" # page
|
||||
NOTIFICATION_FRIEND = "/notification/friends/{}" # page
|
||||
|
||||
NOTIFICATION_COLLECTION_COMMENTS_DELETE = "/notification/collectionComment/delete/{}" # n_id
|
||||
NOTIFICATION_MY_COLLECTION_COMMENTS_DELETE = "/notification/my/collection/comment/delete/{}" # page
|
||||
NOTIFICATION_RELEASE_COMMENTS_DELETE = "/notification/releaseComment/delete/{}" # page
|
||||
NOTIFICATION_EPISODES_DELETE = "/notification/episode/delete/{}" # page
|
||||
NOTIFICATION_FRIEND_DELETE = "/notification/friends/delete/{}" # page
|
||||
|
||||
# ----------- # DISCOVER # ----------- #
|
||||
# TODO DISCOVER: *
|
||||
|
||||
# Not Checked
|
||||
# POST
|
||||
DISCOVER_COMMENTS = "/discover/comments"
|
||||
DISCOVER_DISCUSSING = "/discover/discussing" # {token}
|
||||
DISCOVER_INTERESTING = "/discover/interesting"
|
||||
DISCOVER_RECOMMENDATION = "/discover/recommendations/{}" # page
|
||||
DISCOVER_WATCHING = "/discover/watching/{}" # page
|
||||
|
||||
# ----------- # EpisodeApi.kt # ----------- #
|
||||
# TODO EpisodeApi.kt: *
|
||||
|
||||
# Здесь методы из com.swiftsoft.anixartd.network.api.EpisodeApi.kt
|
||||
# Если надо, переделай так как тебе надо
|
||||
|
||||
# Not Checked
|
||||
# GET
|
||||
# @GET("episode/target/{releaseId}/{sourceId}/{position}")
|
||||
# @GET("episode/{releaseId}/{typeId}/{sourceId}")
|
||||
# @GET("episode/{releaseId}")
|
||||
# @GET("episode/updates/{releaseId}/{page}")
|
||||
|
||||
# POST
|
||||
# @POST("episode/report/{releaseId}/{sourceId}/{position}")
|
||||
# @POST("episode/unwatch/{releaseId}/{sourceId}/{position}")
|
||||
# @POST("episode/unwatch/{releaseId}/{sourceId}")
|
||||
# @POST("episode/watch/{releaseId}/{sourceId}/{position}")
|
||||
# @POST("episode/watch/{releaseId}/{sourceId}")
|
||||
|
||||
# ----------- # FAVORITE # ----------- #
|
||||
# TODO FAVORITE: *
|
||||
|
||||
# Not Checked
|
||||
# GET
|
||||
FAVORITE = "/favorite/all/{}" # r_id
|
||||
FAVORITE_ADD = "/favorite/add/{}" # r_id
|
||||
FAVORITE_DELETE = "/favorite/delete/{}" # r_id {sort}
|
||||
|
||||
# ----------- # HISTORY # ----------- #
|
||||
# TODO HISTORY: *
|
||||
|
||||
# GET
|
||||
HISTORY = "/history/{}" # page
|
||||
|
||||
# Not Checked
|
||||
# GET
|
||||
HISTORY_ADD = "/history/add/{}/{}/{}" # r_id/s_id/position
|
||||
HISTORY_DELETE = "/history/delete/{}" # r_id
|
||||
@@ -2,8 +2,73 @@
|
||||
|
||||
## 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
|
||||
#### Version: 0.2.1
|
||||
|
||||
##### 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.0.1, Build: 1
|
||||
#### Version: 0.1.0
|
||||
|
||||
##### Changes:
|
||||
|
||||
* Изменил информацию в `__version__.py`
|
||||
* Добавил эндпоинты из прошлого проекта
|
||||
|
||||
##### TODOs:
|
||||
|
||||
* Проверить эндпоинты через чарлес
|
||||
- Задокументировать методы
|
||||
- Выявить и удалить не рабочие
|
||||
- Выявить и удалить не используемые
|
||||
|
||||
|
||||
### 27.09.2022
|
||||
#### Version: 0.0.1
|
||||
|
||||
##### Changes:
|
||||
|
||||
|
||||
@@ -17,4 +17,4 @@
|
||||
* 2
|
||||
* ...
|
||||
3. [CHANGELOG](./CHANGELOG.md)
|
||||
4. [LICENSE](./License.md)
|
||||
4. [LICENSE](./LICENSE.md)
|
||||
@@ -0,0 +1,18 @@
|
||||
from anixart import AnixartAPI, AnixartAccount
|
||||
from anixart import endpoints
|
||||
from anixart.exceptions import AnixartAPIRequestError
|
||||
from anixart.profile import Profile
|
||||
|
||||
anix = AnixartAPI() # По умолчанию используется гость
|
||||
|
||||
# acc = AnixartAccount("SantaSpeen", "I_H@ve_Very_Secret_P@ssw0rd!")
|
||||
# # id у аккаунта появляется только после
|
||||
# anix.use_account(acc)
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
raw = anix.get(endpoints.PROFILE, 1)
|
||||
print(Profile.from_response(raw))
|
||||
except AnixartAPIRequestError as e:
|
||||
print(e.message)
|
||||
print(e.code)
|
||||
|
||||
3
examples/readme.md
Normal file
3
examples/readme.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## Директория с примерами
|
||||
|
||||
* Пример авторизации и вывода информации о себе: [auth.py](./auth.py)
|
||||
3
poetry.toml
Normal file
3
poetry.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[virtualenvs]
|
||||
create = true
|
||||
in-project = true
|
||||
25
pyproject.toml
Normal file
25
pyproject.toml
Normal 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"
|
||||
58
setup.py
58
setup.py
@@ -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 :: 1 - Planning",
|
||||
"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",
|
||||
)
|
||||
Reference in New Issue
Block a user