feat: add models and partially custom error messages

This commit is contained in:
firedotguy
2026-01-31 12:10:20 +03:00
parent c7e3812ee8
commit a388426d8d
15 changed files with 352 additions and 71 deletions

View File

@@ -1,3 +1,4 @@
from uuid import UUID
from _io import BufferedReader
from typing import cast
@@ -14,7 +15,14 @@ from itd.routes.search import search
from itd.routes.files import upload_file
from itd.routes.auth import refresh_token, change_password, logout
from itd.routes.verification import verificate, get_verification_status
from itd.models.clan import Clan
from itd.models.user import User, UserProfileUpdate, UserPrivacy, UserFollower, UserWhoToFollow
from itd.models.pagination import Pagination
from itd.models.verification import Verification, VerificationStatus
from itd.request import set_cookies
from itd.exceptions import NoCookie, NoAuthData, SamePassword, InvalidOldPassword, UserNotFound, InvalidProfileData, UserBanned, PendingRequestExists
def refresh_on_error(func):
@@ -39,79 +47,261 @@ class Client:
set_cookies(self.cookies)
self.refresh_auth()
else:
raise ValueError('Provide token or cookie')
raise NoAuthData()
def refresh_auth(self):
if self.cookies:
self.token = refresh_token(self.cookies)
return self.token
else:
print('no cookies')
def refresh_auth(self) -> str:
"""Обновить access token
@refresh_on_error
def change_password(self, old: str, new: str):
Raises:
NoCookie: Нет cookie
Returns:
str: Токен
"""
print('refresh token')
if not self.cookies:
print('no cookies')
return
return change_password(self.cookies, self.token, old, new)
raise NoCookie()
res = refresh_token(self.cookies)
res.raise_for_status()
self.token = res.json()['accessToken']
return self.token
@refresh_on_error
def logout(self):
def change_password(self, old: str, new: str) -> dict:
"""Смена пароля
Args:
old (str): Страый пароль
new (str): Новый пароль
Raises:
NoCookie: Нет cookie
SamePassword: Одинаковые пароли
InvalidOldPassword: Старый пароль неверный
Returns:
dict: Ответ API `{'message': 'Password changed successfully'}`
"""
if not self.cookies:
print('no cookies')
return
return logout(self.cookies)
raise NoCookie()
res = change_password(self.cookies, self.token, old, new)
if res.json().get('error', {}).get('code') == 'SAME_PASSWORD':
raise SamePassword()
if res.json().get('error', {}).get('code') == 'INVALID_OLD_PASSWORD':
raise InvalidOldPassword()
res.raise_for_status()
return res.json()
@refresh_on_error
def get_user(self, username: str) -> dict:
return get_user(self.token, username)
def logout(self) -> dict:
"""Выход из аккаунта
Raises:
NoCookie: Нет cookie
Returns:
dict: Ответ API
"""
if not self.cookies:
raise NoCookie()
res = logout(self.cookies)
res.raise_for_status()
return res.json()
@refresh_on_error
def get_me(self) -> dict:
def get_user(self, username: str) -> User:
"""Получить пользователя
Args:
username (str): username или "me"
Raises:
UserNotFound: Пользователь не найден
UserBanned: Пользователь заблокирован
Returns:
User: Пользователь
"""
res = get_user(self.token, username)
if res.json().get('error', {}).get('code') == 'NOT_FOUND':
raise UserNotFound()
if res.json().get('error', {}).get('code') == 'USER_BLOCKED':
raise UserBanned()
res.raise_for_status()
return User.model_validate(res.json())
@refresh_on_error
def get_me(self) -> User:
"""Получить текущего пользователя (me)
Returns:
User: Пользователь
"""
return self.get_user('me')
@refresh_on_error
def update_profile(self, username: str | None = None, display_name: str | None = None, bio: str | None = None, banner_id: str | None = None) -> dict:
return update_profile(self.token, bio, display_name, username, banner_id)
def update_profile(self, username: str | None = None, display_name: str | None = None, bio: str | None = None, banner_id: UUID | None = None) -> UserProfileUpdate:
"""Обновить профиль
Args:
username (str | None, optional): username. Defaults to None.
display_name (str | None, optional): Отображаемое имя. Defaults to None.
bio (str | None, optional): Биография (о себе). Defaults to None.
banner_id (UUID | None, optional): UUID баннера. Defaults to None.
Raises:
InvalidProfileData: Неправильные данные (валидация не прошла)
Returns:
UserProfileUpdate: Обновленный профиль
"""
res = update_profile(self.token, bio, display_name, username, banner_id)
if res.status_code == 422 and 'found' in res.json():
raise InvalidProfileData(*list(res.json()['found'].items())[0])
res.raise_for_status()
return UserProfileUpdate.model_validate(res.json())
@refresh_on_error
def update_privacy(self, wall_closed: bool = False, private: bool = False):
return update_privacy(self.token, wall_closed, private)
def update_privacy(self, wall_closed: bool = False, private: bool = False) -> UserPrivacy:
"""Обновить настройки приватности
Args:
wall_closed (bool, optional): Закрыть стену. Defaults to False.
private (bool, optional): Приватность. На данный момент неизвестно, что делает этот параметр. Defaults to False.
Returns:
UserPrivacy: Обновленные данные приватности
"""
res = update_privacy(self.token, wall_closed, private)
res.raise_for_status()
return UserPrivacy.model_validate(res.json())
@refresh_on_error
def follow(self, username: str) -> dict:
return follow(self.token, username)
def follow(self, username: str) -> int:
"""Подписаться на пользователя
Args:
username (str): username
Raises:
UserNotFound: Пользователь не найден
Returns:
int: Число подписчиков после подписки
"""
res = follow(self.token, username)
if res.json().get('error', {}).get('code') == 'NOT_FOUND':
raise UserNotFound()
res.raise_for_status()
return res.json()['followersCount']
@refresh_on_error
def unfollow(self, username: str) -> dict:
return unfollow(self.token, username)
def unfollow(self, username: str) -> int:
"""Отписаться от пользователя
Args:
username (str): username
Raises:
UserNotFound: Пользователь не найден
Returns:
int: Число подписчиков после отписки
"""
res = unfollow(self.token, username)
if res.json().get('error', {}).get('code') == 'NOT_FOUND':
raise UserNotFound()
res.raise_for_status()
return res.json()['followersCount']
@refresh_on_error
def get_followers(self, username: str) -> dict:
return get_followers(self.token, username)
def get_followers(self, username: str, limit: int = 30, page: int = 1) -> tuple[list[UserFollower], Pagination]:
"""Получить подписчиков пользователя
Args:
username (str): username
limit (int, optional): Лимит. Defaults to 30.
page (int, optional): Страница (при дозагрузке, увеличивайте на 1). Defaults to 1.
Raises:
UserNotFound: Пользователь не найден
Returns:
list[UserFollower]: Список подписчиков
Pagination: Данные пагинации (лимит, страница, сколько всего, есть ли еще)
"""
res = get_followers(self.token, username, limit, page)
if res.json().get('error', {}).get('code') == 'NOT_FOUND':
raise UserNotFound()
res.raise_for_status()
return [UserFollower.model_validate(user) for user in res.json()['data']['users']], Pagination.model_validate(res.json()['data']['pagination'])
@refresh_on_error
def get_following(self, username: str) -> dict:
return get_following(self.token, username)
def get_following(self, username: str, limit: int = 30, page: int = 1) -> tuple[list[UserFollower], Pagination]:
"""Получить подписки пользователя
Args:
username (str): username
limit (int, optional): Лимит. Defaults to 30.
page (int, optional): Страница (при дозагрузке, увеличивайте на 1). Defaults to 1.
Raises:
UserNotFound: Пользователь не найден
Returns:
list[UserFollower]: Список подписок
Pagination: Данные пагинации (лимит, страница, сколько всего, есть ли еще)
"""
res = get_following(self.token, username, limit, page)
if res.json().get('error', {}).get('code') == 'NOT_FOUND':
raise UserNotFound()
res.raise_for_status()
return [UserFollower.model_validate(user) for user in res.json()['data']['users']], Pagination.model_validate(res.json()['data']['pagination'])
@refresh_on_error
def verificate(self, file_url: str):
return verificate(self.token, file_url)
def verificate(self, file_url: str) -> Verification:
res = verificate(self.token, file_url)
if res.json().get('error', {}).get('code') == 'PENDING_REQUEST_EXISTS':
raise PendingRequestExists()
res.raise_for_status()
return Verification.model_validate(res.json())
@refresh_on_error
def get_verification_status(self):
return get_verification_status(self.token)
def get_verification_status(self) -> VerificationStatus:
res = get_verification_status(self.token)
res.raise_for_status()
return VerificationStatus.model_validate(res.json())
@refresh_on_error
def get_who_to_follow(self) -> dict:
return get_who_to_follow(self.token)
def get_who_to_follow(self) -> list[UserWhoToFollow]:
res = get_who_to_follow(self.token)
res.raise_for_status()
return [UserWhoToFollow.model_validate(user) for user in res.json()['users']]
@refresh_on_error
def get_top_clans(self) -> dict:
return get_top_clans(self.token)
def get_top_clans(self) -> list[Clan]:
res = get_top_clans(self.token)
res.raise_for_status()
return [Clan.model_validate(clan) for clan in res.json()['clans']]
@refresh_on_error
def get_platform_status(self) -> dict: