mirror of
https://github.com/SantaSpeen/anixart.git
synced 2026-05-20 00:20:26 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 85c7605b2c | |||
| 82874814e0 | |||
| 44e0485f99 | |||
| daec2199b6 | |||
| fbb738fd1c | |||
| c4d758f65a | |||
| 04be5ed2a9 | |||
| 87ff4463c4 | |||
| 403e7c8b79 | |||
| 2f0e4e3942 | |||
| 1c8d56dca6 | |||
| 326034fda9 | |||
| 0cf61563be | |||
| a6f1655449 | |||
| c6bfda72df | |||
| 8e9716cd06 | |||
| 9f50168205 |
@@ -130,3 +130,5 @@ dmypy.json
|
||||
|
||||
# PyCharm
|
||||
.idea/
|
||||
|
||||
poetry.lock
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from .__meta__ import *
|
||||
|
||||
from .endpoints import *
|
||||
from .api.api import AnixartUserAccount, AnixartAPI
|
||||
|
||||
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'
|
||||
@@ -0,0 +1,78 @@
|
||||
import logging
|
||||
|
||||
import requests
|
||||
|
||||
from ..auth import AnixartAuth
|
||||
from ..exceptions 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.")
|
||||
@@ -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 <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: ...
|
||||
@@ -0,0 +1 @@
|
||||
from .account import AnixartAccount, AnixartAccountToken
|
||||
@@ -0,0 +1,112 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
|
||||
from anixart.exceptions import AnixartInitError
|
||||
|
||||
|
||||
class AnixartAccount:
|
||||
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._token = None
|
||||
self._session = requests.Session()
|
||||
|
||||
@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):
|
||||
"""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):
|
||||
"""Load the account information from a file."""
|
||||
acc = AnixartAccountSaved(filename)
|
||||
acc.load()
|
||||
return acc
|
||||
|
||||
def login(self):
|
||||
"""Login to Anixart and return the token."""
|
||||
# TODO: Implement login logic here
|
||||
|
||||
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 = {
|
||||
"username": self._username,
|
||||
"password": self._password,
|
||||
"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._username = data.get("username")
|
||||
self._password = data.get("password")
|
||||
self._token = data.get("token")
|
||||
if not self._username or not self._password:
|
||||
raise AnixartInitError("Login and password 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 AnixartAccountToken(AnixartAccount):
|
||||
|
||||
def __init__(self, token):
|
||||
super().__init__("mradx", "") # Пасхалка)
|
||||
self._token = token
|
||||
|
||||
def login(self):
|
||||
"""Login to Anixart and return information about the tokens."""
|
||||
# TODO: Implement login logic here
|
||||
pass
|
||||
|
||||
def __str__(self):
|
||||
return f'AnixartAccountToken(token={self._token!r}")'
|
||||
@@ -0,0 +1,7 @@
|
||||
from enum import IntEnum
|
||||
|
||||
|
||||
class AnixartAuthError(IntEnum):
|
||||
""" Error codes for AnixartApi authentication."""
|
||||
INCORRECT_LOGIN = 1
|
||||
INCORRECT_PASSWORD = 2
|
||||
@@ -0,0 +1,67 @@
|
||||
import json
|
||||
import logging
|
||||
import os.path
|
||||
|
||||
from .endpoints import SING_IN, CHANGE_PASSWORD, PROFILE
|
||||
from .exceptions 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})
|
||||
@@ -0,0 +1,272 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
API_URL = "https://api.anixart.tv"
|
||||
|
||||
# ----------- # 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}
|
||||
|
||||
# ----------- # 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
|
||||
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"
|
||||
|
||||
# ----------- # 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
|
||||
@@ -0,0 +1,24 @@
|
||||
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
|
||||
@@ -0,0 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
class AnixartBasError(Exception): ...
|
||||
|
||||
# Init errors
|
||||
|
||||
class AnixartInitError(AnixartBasError, TypeError): ...
|
||||
|
||||
# API errors
|
||||
class AnixartAPIError(AnixartBasError): ...
|
||||
|
||||
class AnixartAuthError(AnixartAPIError): ...
|
||||
|
||||
class AnixartAPIRequestError(AnixartAPIError): ...
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
|
||||
import requests
|
||||
|
||||
from .__meta__ import __version__, __build__
|
||||
from .endpoints import API_URL
|
||||
from .exceptions 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
|
||||
@@ -2,6 +2,49 @@
|
||||
|
||||
## 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
|
||||
|
||||
##### Changes:
|
||||
|
||||
* Изменил информацию в `__version__.py`
|
||||
* Добавил эндпоинты из прошлого проекта
|
||||
|
||||
##### TODOs:
|
||||
|
||||
* Проверить эндпоинты через чарлес
|
||||
- Задокументировать методы
|
||||
- Выявить и удалить не рабочие
|
||||
- Выявить и удалить не используемые
|
||||
|
||||
|
||||
### 27.09.2022
|
||||
#### Version: 0.0.1, Build: 1
|
||||
|
||||
|
||||
+1
-1
@@ -17,4 +17,4 @@
|
||||
* 2
|
||||
* ...
|
||||
3. [CHANGELOG](./CHANGELOG.md)
|
||||
4. [LICENSE](./License.md)
|
||||
4. [LICENSE](./LICENSE.md)
|
||||
@@ -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())
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
## Директория с примерами
|
||||
|
||||
* Пример авторизации и вывода информации о себе: [auth.py](./auth.py)
|
||||
@@ -0,0 +1,3 @@
|
||||
[virtualenvs]
|
||||
create = true
|
||||
in-project = true
|
||||
@@ -0,0 +1,25 @@
|
||||
[project]
|
||||
name = "anixart"
|
||||
version = "0.2.2"
|
||||
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 :: 3 - 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,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