feat: add models part 4

This commit is contained in:
firedotguy
2026-02-05 00:20:26 +03:00
parent dd7b8c077e
commit 55630bc23f
6 changed files with 208 additions and 41 deletions

View File

@@ -23,11 +23,16 @@ from itd.models.post import Post, NewPost
from itd.models.clan import Clan from itd.models.clan import Clan
from itd.models.hashtag import Hashtag from itd.models.hashtag import Hashtag
from itd.models.user import User, UserProfileUpdate, UserPrivacy, UserFollower, UserWhoToFollow from itd.models.user import User, UserProfileUpdate, UserPrivacy, UserFollower, UserWhoToFollow
from itd.models.pagination import Pagination from itd.models.pagination import Pagination, PostsPagintaion, LikedPostsPagintaion
from itd.models.verification import Verification, VerificationStatus from itd.models.verification import Verification, VerificationStatus
from itd.enums import PostsTab
from itd.request import set_cookies from itd.request import set_cookies
from itd.exceptions import NoCookie, NoAuthData, SamePassword, InvalidOldPassword, NotFound, ValidationError, UserBanned, PendingRequestExists, Forbidden, UsernameTaken, CantFollowYourself, Unauthorized from itd.exceptions import (
NoCookie, NoAuthData, SamePassword, InvalidOldPassword, NotFound, ValidationError, UserBanned,
PendingRequestExists, Forbidden, UsernameTaken, CantFollowYourself, Unauthorized,
CantRepostYourPost, AlreadyReposted
)
def refresh_on_error(func): def refresh_on_error(func):
@@ -614,6 +619,20 @@ class Client:
@refresh_on_error @refresh_on_error
def create_post(self, content: str, wall_recipient_id: UUID | None = None, attach_ids: list[UUID] = []) -> NewPost: def create_post(self, content: str, wall_recipient_id: UUID | None = None, attach_ids: list[UUID] = []) -> NewPost:
"""Создать пост
Args:
content (str): Содержимое
wall_recipient_id (UUID | None, optional): UUID пользователя (чтобы создать пост ему на стене). Defaults to None.
attach_ids (list[UUID], optional): UUID вложений. Defaults to [].
Raises:
NotFound: Пользователь не найден
ValidationError: Ошибка валидации
Returns:
NewPost: Новый пост
"""
res = create_post(self.token, content, wall_recipient_id, attach_ids) res = create_post(self.token, content, wall_recipient_id, attach_ids)
if res.json().get('error', {}).get('code') == 'NOT_FOUND': if res.json().get('error', {}).get('code') == 'NOT_FOUND':
raise NotFound('Wall recipient') raise NotFound('Wall recipient')
@@ -624,36 +643,166 @@ class Client:
return NewPost.model_validate(res.json()) return NewPost.model_validate(res.json())
@refresh_on_error @refresh_on_error
def get_posts(self, username: str | None = None, limit: int = 20, cursor: int = 0, sort: str = '', tab: str = ''): def get_posts(self, cursor: int = 0, tab: PostsTab = PostsTab.POPULAR) -> tuple[list[Post], PostsPagintaion]:
return get_posts(self.token, username, limit, cursor, sort, tab) """Получить список постов
Args:
cursor (int, optional): Страница. Defaults to 0.
tab (PostsTab, optional): Вкладка (популярное или подписки). Defaults to PostsTab.POPULAR.
Returns:
list[Post]: Список постов
Pagination: Пагинация
"""
res = get_posts(self.token, cursor, tab)
res.raise_for_status()
data = res.json()['data']
return [Post.model_validate(post) for post in data['posts']], PostsPagintaion.model_validate(data['pagination'])
@refresh_on_error @refresh_on_error
def get_post(self, id: str): def get_post(self, id: UUID) -> Post:
return get_post(self.token, id) """Получить пост
Args:
id (UUID): UUID поста
Raises:
NotFound: Пост не найден
Returns:
Post: Пост
"""
res = get_post(self.token, id)
if res.json().get('error', {}).get('code') == 'NOT_FOUND':
raise NotFound('Post')
res.raise_for_status()
return Post.model_validate(res.json()['data'])
@refresh_on_error @refresh_on_error
def edit_post(self, id: str, content: str): def edit_post(self, id: UUID, content: str) -> str:
return edit_post(self.token, id, content) """Редактировать пост
Args:
id (UUID): UUID поста
content (str): Содержимое
Raises:
NotFound: Пост не найден
Forbidden: Нет доступа
ValidationError: Ошибка валидации
Returns:
str: Новое содержимое
"""
res = edit_post(self.token, id, content)
if res.json().get('error', {}).get('code') == 'NOT_FOUND':
raise NotFound('Post')
if res.json().get('error', {}).get('code') == 'FORBIDDEN':
raise Forbidden('edit post')
if res.status_code == 422 and 'found' in res.json():
raise ValidationError(*list(res.json()['found'].items())[0])
res.raise_for_status()
return res.json()['content']
@refresh_on_error @refresh_on_error
def delete_post(self, id: str): def delete_post(self, id: UUID) -> None:
return delete_post(self.token, id) """Удалить пост
Args:
id (UUID): UUID поста
Raises:
NotFound: Пост не найден
Forbidden: Нет доступа
"""
res = delete_post(self.token, id)
if res.status_code == 204:
return
if res.json().get('error', {}).get('code') == 'NOT_FOUND':
raise NotFound('Post')
if res.json().get('error', {}).get('code') == 'FORBIDDEN':
raise Forbidden('delete post')
res.raise_for_status()
@refresh_on_error @refresh_on_error
def pin_post(self, id: str): def pin_post(self, id: UUID):
return pin_post(self.token, id) """Закрепить пост
Args:
id (UUID): UUID поста
Raises:
NotFound: Пост не найден
Forbidden: Нет доступа
"""
res = pin_post(self.token, id)
if res.json().get('error', {}).get('code') == 'NOT_FOUND':
raise NotFound('Post')
if res.json().get('error', {}).get('code') == 'FORBIDDEN':
raise Forbidden('pin post')
res.raise_for_status()
@refresh_on_error @refresh_on_error
def repost(self, id: str, content: str | None = None): def repost(self, id: UUID, content: str | None = None) -> NewPost:
return repost(self.token, id, content) """Репостнуть пост
Args:
id (UUID): UUID поста
content (str | None, optional): Содержимое (доп. комментарий). Defaults to None.
Raises:
NotFound: Пост не найден
AlreadyReposted: Пост уже репостнут
CantRepostYourPost: Нельзя репостить самого себя
ValidationError: Ошибка валидации
Returns:
NewPost: Новый пост
"""
res = repost(self.token, id, content)
if res.json().get('error', {}).get('code') == 'NOT_FOUND':
raise NotFound('Post')
if res.json().get('error', {}).get('code') == 'CONFLICT':
raise AlreadyReposted()
if res.status_code == 422 and res.json().get('message') == 'Cannot repost your own post':
raise CantRepostYourPost()
if res.status_code == 422 and 'found' in res.json():
raise ValidationError(*list(res.json()['found'].items())[0])
res.raise_for_status()
return NewPost.model_validate(res.json())
@refresh_on_error @refresh_on_error
def view_post(self, id: str): def view_post(self, id: UUID) -> None:
return view_post(self.token, id) """Просмотреть пост
Args:
id (UUID): UUID поста
Raises:
NotFound: Пост не найден
"""
res = view_post(self.token, id)
if res.json().get('error', {}).get('code') == 'NOT_FOUND':
raise NotFound('Post')
res.raise_for_status()
@refresh_on_error @refresh_on_error
def get_liked_posts(self, username: str, limit: int = 20, cursor: int = 0): def get_liked_posts(self, username_or_id: str | UUID, limit: int = 20, cursor: int = 0):
return get_liked_posts(self.token, username, limit, cursor) res = get_liked_posts(self.token, username_or_id, limit, cursor)
if res.json().get('error', {}).get('code') == 'NOT_FOUND':
raise NotFound('User')
res.raise_for_status()
data = res.json()['data']
return [Post.model_validate(post) for post in data['posts']], LikedPostsPagintaion.model_validate(data['pagination'])
@refresh_on_error @refresh_on_error

View File

@@ -27,3 +27,7 @@ class ReportTargetReason(Enum):
class AttachType(Enum): class AttachType(Enum):
AUDIO = 'audio' AUDIO = 'audio'
IMAGE = 'image' IMAGE = 'image'
class PostsTab(Enum):
FOLLOWING = 'following'
POPULAR = 'popular'

View File

@@ -71,5 +71,13 @@ class CantFollowYourself(Exception):
return 'Cannot follow yourself' return 'Cannot follow yourself'
class Unauthorized(Exception): class Unauthorized(Exception):
def __str__(self) -> str: def __str__(self):
return 'Auth required - refresh token' return 'Auth required - refresh token'
class CantRepostYourPost(Exception):
def __str__(self):
return 'Cannot repost your own post'
class AlreadyReposted(Exception):
def __str__(self):
return 'Post already reposted'

View File

@@ -1,4 +1,5 @@
from uuid import UUID from uuid import UUID
from datetime import datetime
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
@@ -8,3 +9,15 @@ class Pagination(BaseModel):
total: int | None = None total: int | None = None
has_more: bool = Field(True, alias='hasMore') has_more: bool = Field(True, alias='hasMore')
next_cursor: UUID | None = Field(None, alias='nextCursor') next_cursor: UUID | None = Field(None, alias='nextCursor')
class PostsPagintaion(BaseModel):
limit: int = 20
next_cursor: int = Field(1, alias='nextCursor')
has_more: bool = Field(True, alias='hasMore')
class LikedPostsPagintaion(BaseModel):
limit: int = 20
next_cursor: datetime | None = Field(None, alias='nextCursor')
has_more: bool = Field(True, alias='hasMore')

View File

@@ -38,7 +38,7 @@ def fetch(token: str, method: str, url: str, params: dict = {}, files: dict[str,
if res.json().get('error', {}).get('code') == 'UNAUTHORIZED': if res.json().get('error', {}).get('code') == 'UNAUTHORIZED':
raise Unauthorized() raise Unauthorized()
except JSONDecodeError: except JSONDecodeError:
pass pass # todo
if not res.ok: if not res.ok:
print(res.text) print(res.text)

View File

@@ -1,6 +1,7 @@
from uuid import UUID from uuid import UUID
from itd.request import fetch from itd.request import fetch
from itd.enums import PostsTab
def create_post(token: str, content: str, wall_recipient_id: UUID | None = None, attach_ids: list[UUID] = []): def create_post(token: str, content: str, wall_recipient_id: UUID | None = None, attach_ids: list[UUID] = []):
data: dict = {'content': content} data: dict = {'content': content}
@@ -11,16 +12,8 @@ def create_post(token: str, content: str, wall_recipient_id: UUID | None = None,
return fetch(token, 'post', 'posts', data) return fetch(token, 'post', 'posts', data)
def get_posts(token: str, username: str | None = None, limit: int = 20, cursor: int = 0, sort: str = '', tab: str = ''): def get_posts(token: str, cursor: int = 0, tab: PostsTab = PostsTab.POPULAR):
data: dict = {'limit': limit, 'cursor': cursor} return fetch(token, 'get', 'posts', {'cursor': cursor, 'tab': tab.value})
if username:
data['username'] = username
if sort:
data['sort'] = sort
if tab:
data['tab'] = tab
return fetch(token, 'get', 'posts', data)
def get_post(token: str, id: UUID): def get_post(token: str, id: UUID):
return fetch(token, 'get', f'posts/{id}') return fetch(token, 'get', f'posts/{id}')
@@ -43,8 +36,8 @@ def repost(token: str, id: UUID, content: str | None = None):
def view_post(token: str, id: UUID): def view_post(token: str, id: UUID):
return fetch(token, 'post', f'posts/{id}/view') return fetch(token, 'post', f'posts/{id}/view')
def get_liked_posts(token: str, username: str, limit: int = 20, cursor: int = 0): def get_liked_posts(token: str, username_or_id: str | UUID, limit: int = 20, cursor: int = 0):
return fetch(token, 'get', f'posts/user/{username}/liked', {'limit': limit, 'cursor': cursor}) return fetch(token, 'get', f'posts/user/{username_or_id}/liked', {'limit': limit})
# todo post restore # todo post restore
# todo post like # todo post like