17 Commits

Author SHA1 Message Date
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
SantaSpeen 0cf61563be Development Status :: 3 - Alpha 2022-09-27 23:16:06 +03:00
SantaSpeen a6f1655449 * Перетащил файлы из прошлого проекта
- Поменял нейминиги
  - Оптимизировал `request_handler.py`
  - Оптимизировал `errors.py`
  - Оптимизировал `auth.py`
  - Оптимизировал `api/api.py`
  - Добавил `api/api.pyi`
* Добавил пример: `/examples/auth.py`
* Обновил зависимости
2022-09-27 23:14:42 +03:00
SantaSpeen c6bfda72df Добавил данные в ченчлог 2022-09-27 23:06:10 +03:00
SantaSpeen 8e9716cd06 Добавил эндпоинты 2022-09-27 20:56:59 +03:00
SantaSpeen 9f50168205 Немного изменил инфу о себе 2022-09-27 20:56:40 +03:00
24 changed files with 851 additions and 74 deletions
+2
View File
@@ -130,3 +130,5 @@ dmypy.json
# PyCharm # PyCharm
.idea/ .idea/
poetry.lock
+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.
```
+9
View File
@@ -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' __title__ = 'anixart'
__description__ = 'Wrapper for using the Anixart API.' __description__ = 'Wrapper for using the Anixart API.'
__url__ = 'https://github.com/SantaSpeen/anixart' __url__ = 'https://github.com/SantaSpeen/anixart'
__version__ = '0.0.1' __version__ = '0.2.1'
__build__ = 1 __build__ = 3
__author__ = 'Maxim Khomutov' __author__ = 'SantaSpeen'
__author_email__ = 'SantaSpeen@gmail.com' __author_email__ = 'SantaSpeen@gmail.com'
__license__ = "FPA" __license__ = "FPA"
__copyright__ = 'Copyright 2022 Maxim Khomutov' __copyright__ = 'Copyright 2022 © SantaSpeen'
+78
View File
@@ -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.")
+65
View File
@@ -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: ...
+1
View File
@@ -0,0 +1 @@
from .account import AnixartAccount, AnixartAccountToken
+112
View File
@@ -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}")'
+7
View File
@@ -0,0 +1,7 @@
from enum import IntEnum
class AnixartAuthError(IntEnum):
""" Error codes for AnixartApi authentication."""
INCORRECT_LOGIN = 1
INCORRECT_PASSWORD = 2
+67
View File
@@ -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})
+272
View File
@@ -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
+24
View File
@@ -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
+15
View File
@@ -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): ...
+91
View File
@@ -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
+43
View File
@@ -2,6 +2,49 @@
## Anixart API Wrapper ## 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 ### 27.09.2022
#### Version: 0.0.1, Build: 1 #### Version: 0.0.1, Build: 1
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)
+9
View File
@@ -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())
+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.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"
View File
-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 :: 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",
)