feat: add models final part 5
This commit is contained in:
177
itd/client.py
177
itd/client.py
@@ -2,6 +2,7 @@ from warnings import deprecated
|
||||
from uuid import UUID
|
||||
from _io import BufferedReader
|
||||
from typing import cast
|
||||
from datetime import datetime
|
||||
|
||||
from requests.exceptions import ConnectionError, HTTPError
|
||||
|
||||
@@ -10,7 +11,7 @@ from itd.routes.etc import get_top_clans, get_who_to_follow, get_platform_status
|
||||
from itd.routes.comments import get_comments, add_comment, delete_comment, like_comment, unlike_comment, add_reply_comment
|
||||
from itd.routes.hashtags import get_hashtags, get_posts_by_hashtag
|
||||
from itd.routes.notifications import get_notifications, mark_as_read, mark_all_as_read, get_unread_notifications_count
|
||||
from itd.routes.posts import create_post, get_posts, get_post, edit_post, delete_post, pin_post, repost, view_post, get_liked_posts, restore_post, like_post, delete_like_post
|
||||
from itd.routes.posts import create_post, get_posts, get_post, edit_post, delete_post, pin_post, repost, view_post, get_liked_posts, restore_post, like_post, unlike_post
|
||||
from itd.routes.reports import report
|
||||
from itd.routes.search import search
|
||||
from itd.routes.files import upload_file
|
||||
@@ -19,19 +20,21 @@ from itd.routes.verification import verify, get_verification_status
|
||||
|
||||
from itd.models.comment import Comment
|
||||
from itd.models.notification import Notification
|
||||
from itd.models.post import Post, NewPost, LikePostResponse
|
||||
from itd.models.post import Post, NewPost
|
||||
from itd.models.clan import Clan
|
||||
from itd.models.hashtag import Hashtag
|
||||
from itd.models.user import User, UserProfileUpdate, UserPrivacy, UserFollower, UserWhoToFollow
|
||||
from itd.models.pagination import Pagination, PostsPagintaion, LikedPostsPagintaion
|
||||
from itd.models.verification import Verification, VerificationStatus
|
||||
from itd.models.report import NewReport
|
||||
from itd.models.file import File
|
||||
|
||||
from itd.enums import PostsTab
|
||||
from itd.enums import PostsTab, ReportTargetType, ReportTargetReason
|
||||
from itd.request import set_cookies
|
||||
from itd.exceptions import (
|
||||
NoCookie, NoAuthData, SamePassword, InvalidOldPassword, NotFound, ValidationError, UserBanned,
|
||||
PendingRequestExists, Forbidden, UsernameTaken, CantFollowYourself, Unauthorized,
|
||||
CantRepostYourPost, AlreadyReposted
|
||||
CantRepostYourPost, AlreadyReposted, AlreadyReported, TooLarge
|
||||
)
|
||||
|
||||
|
||||
@@ -797,7 +800,21 @@ class Client:
|
||||
res.raise_for_status()
|
||||
|
||||
@refresh_on_error
|
||||
def get_liked_posts(self, username_or_id: str | UUID, limit: int = 20, cursor: int = 0) -> tuple[list[Post], LikedPostsPagintaion]:
|
||||
def get_liked_posts(self, username_or_id: str | UUID, limit: int = 20, cursor: datetime | None = None) -> tuple[list[Post], LikedPostsPagintaion]:
|
||||
"""Получить список лайкнутых постов пользователя
|
||||
|
||||
Args:
|
||||
username_or_id (str | UUID): UUID или username пользователя
|
||||
limit (int, optional): Лимит. Defaults to 20.
|
||||
cursor (datetime | None, optional): Сдвиг (next_cursor). Defaults to None.
|
||||
|
||||
Raises:
|
||||
NotFound: Пользователь не найден
|
||||
|
||||
Returns:
|
||||
list[Post]: Список постов
|
||||
LikedPostsPagintaion: Пагинация
|
||||
"""
|
||||
res = get_liked_posts(self.token, username_or_id, limit, cursor)
|
||||
if res.json().get('error', {}).get('code') == 'NOT_FOUND':
|
||||
raise NotFound('User')
|
||||
@@ -808,41 +825,114 @@ class Client:
|
||||
|
||||
|
||||
@refresh_on_error
|
||||
def report(self, id: str, type: str = 'post', reason: str = 'other', description: str = ''):
|
||||
return report(self.token, id, type, reason, description)
|
||||
def report(self, id: UUID, type: ReportTargetType = ReportTargetType.POST, reason: ReportTargetReason = ReportTargetReason.OTHER, description: str | None = None) -> NewReport:
|
||||
"""Отправить жалобу
|
||||
|
||||
@refresh_on_error
|
||||
def report_user(self, id: str, reason: str = 'other', description: str = ''):
|
||||
return report(self.token, id, 'user', reason, description)
|
||||
Args:
|
||||
id (UUID): UUID цели
|
||||
type (ReportTargetType, optional): Тип цели (пост/пользователь/комментарий). Defaults to ReportTargetType.POST.
|
||||
reason (ReportTargetReason, optional): Причина. Defaults to ReportTargetReason.OTHER.
|
||||
description (str | None, optional): Описание. Defaults to None.
|
||||
|
||||
@refresh_on_error
|
||||
def report_post(self, id: str, reason: str = 'other', description: str = ''):
|
||||
return report(self.token, id, 'post', reason, description)
|
||||
Raises:
|
||||
NotFound: Цель не найдена
|
||||
AlreadyReported: Жалоба уже отправлена
|
||||
ValidationError: Ошибка валидации
|
||||
|
||||
@refresh_on_error
|
||||
def report_comment(self, id: str, reason: str = 'other', description: str = ''):
|
||||
return report(self.token, id, 'comment', reason, description)
|
||||
Returns:
|
||||
NewReport: Новая жалоба
|
||||
"""
|
||||
res = report(self.token, id, type, reason, description)
|
||||
|
||||
if res.json().get('error', {}).get('code') == 'VALIDATION_ERROR' and 'не найден' in res.json()['error'].get('message', ''):
|
||||
raise NotFound(type.value.title())
|
||||
if res.json().get('error', {}).get('code') == 'VALIDATION_ERROR' and 'Вы уже отправляли жалобу' in res.json()['error'].get('message', ''):
|
||||
raise AlreadyReported(type.value.title())
|
||||
if res.status_code == 422 and 'found' in res.json():
|
||||
raise ValidationError(*list(res.json()['found'].items())[-1])
|
||||
res.raise_for_status()
|
||||
|
||||
return NewReport.model_validate(res.json()['data'])
|
||||
|
||||
|
||||
@refresh_on_error
|
||||
def search(self, query: str, user_limit: int = 5, hashtag_limit: int = 5):
|
||||
return search(self.token, query, user_limit, hashtag_limit)
|
||||
def search(self, query: str, user_limit: int = 5, hashtag_limit: int = 5) -> tuple[list[UserWhoToFollow], list[Hashtag]]:
|
||||
"""Поиск по пользователям и хэштэгам
|
||||
|
||||
Args:
|
||||
query (str): Запрос
|
||||
user_limit (int, optional): Лимит пользователей. Defaults to 5.
|
||||
hashtag_limit (int, optional): Лимит хэштэгов. Defaults to 5.
|
||||
|
||||
Raises:
|
||||
TooLarge: Слишком длинный запрос
|
||||
|
||||
Returns:
|
||||
list[UserWhoToFollow]: Список пользователей
|
||||
list[Hashtag]: Список хэштэгов
|
||||
"""
|
||||
res = search(self.token, query, user_limit, hashtag_limit)
|
||||
|
||||
if res.status_code == 414:
|
||||
raise TooLarge()
|
||||
res.raise_for_status()
|
||||
data = res.json()['data']
|
||||
|
||||
return [UserWhoToFollow.model_validate(user) for user in data['users']], [Hashtag.model_validate(hashtag) for hashtag in data['hashtags']]
|
||||
|
||||
@refresh_on_error
|
||||
def search_user(self, query: str, limit: int = 5):
|
||||
return search(self.token, query, limit, 0)
|
||||
def search_user(self, query: str, limit: int = 5) -> list[UserWhoToFollow]:
|
||||
"""Поиск пользователей
|
||||
|
||||
Args:
|
||||
query (str): Запрос
|
||||
limit (int, optional): Лимит. Defaults to 5.
|
||||
|
||||
Returns:
|
||||
list[UserWhoToFollow]: Список пользователей
|
||||
"""
|
||||
return self.search(query, limit, 0)[0]
|
||||
|
||||
@refresh_on_error
|
||||
def search_hashtag(self, query: str, limit: int = 5):
|
||||
return search(self.token, query, 0, limit)
|
||||
def search_hashtag(self, query: str, limit: int = 5) -> list[Hashtag]:
|
||||
"""Поиск хэштэгов
|
||||
|
||||
Args:
|
||||
query (str): Запрос
|
||||
limit (int, optional): Лимит. Defaults to 5.
|
||||
|
||||
Returns:
|
||||
list[Hashtag]: Список хэштэгов
|
||||
"""
|
||||
return self.search(query, 0, limit)[1]
|
||||
|
||||
|
||||
@refresh_on_error
|
||||
def upload_file(self, name: str, data: BufferedReader):
|
||||
return upload_file(self.token, name, data).json()
|
||||
def upload_file(self, name: str, data: BufferedReader) -> File:
|
||||
"""Загрузить файл
|
||||
|
||||
Args:
|
||||
name (str): Имя файла
|
||||
data (BufferedReader): Содержимое (open('имя', 'rb'))
|
||||
|
||||
Returns:
|
||||
File: Файл
|
||||
"""
|
||||
res = upload_file(self.token, name, data)
|
||||
res.raise_for_status()
|
||||
|
||||
return File.model_validate(res.json())
|
||||
|
||||
def update_banner(self, name: str) -> UserProfileUpdate:
|
||||
id = self.upload_file(name, cast(BufferedReader, open(name, 'rb')))['id']
|
||||
"""Обновить банер (шорткат из upload_file + update_profile)
|
||||
|
||||
Args:
|
||||
name (str): Имя файла
|
||||
|
||||
Returns:
|
||||
UserProfileUpdate: Обновленный профиль
|
||||
"""
|
||||
id = self.upload_file(name, cast(BufferedReader, open(name, 'rb'))).id
|
||||
return self.update_profile(banner_id=id)
|
||||
|
||||
@refresh_on_error
|
||||
@@ -852,28 +942,45 @@ class Client:
|
||||
Args:
|
||||
post_id: UUID поста
|
||||
"""
|
||||
restore_post(self.token, post_id)
|
||||
res = restore_post(self.token, post_id)
|
||||
res.raise_for_status()
|
||||
|
||||
@refresh_on_error
|
||||
def like_post(self, post_id: UUID) -> LikePostResponse:
|
||||
"""Поставить лайк на пост
|
||||
def like_post(self, post_id: UUID) -> int:
|
||||
"""Лайкнуть пост
|
||||
|
||||
Args:
|
||||
post_id: UUID поста
|
||||
post_id (UUID): UUID поста
|
||||
|
||||
Raises:
|
||||
NotFound: Пост не найден
|
||||
|
||||
Returns:
|
||||
int: Количество лайков
|
||||
"""
|
||||
res = like_post(self.token, post_id)
|
||||
|
||||
if res.status_code == 404:
|
||||
raise NotFound("Post not found")
|
||||
return LikePostResponse.model_validate(res.json())
|
||||
raise NotFound("Post")
|
||||
|
||||
return res.json()['likesCount']
|
||||
|
||||
@refresh_on_error
|
||||
def delete_like_post(self, post_id: UUID) -> LikePostResponse:
|
||||
def unlike_post(self, post_id: UUID) -> int:
|
||||
"""Убрать лайк с поста
|
||||
|
||||
Args:
|
||||
post_id: UUID поста
|
||||
post_id (UUID): UUID поста
|
||||
|
||||
Raises:
|
||||
NotFound: Пост не найден
|
||||
|
||||
Returns:
|
||||
int: Количество лайков
|
||||
"""
|
||||
res = delete_like_post(self.token, post_id)
|
||||
res = unlike_post(self.token, post_id)
|
||||
|
||||
if res.status_code == 404:
|
||||
raise NotFound("Post not found")
|
||||
return LikePostResponse.model_validate(res.json())
|
||||
|
||||
return res.json()['likesCount']
|
||||
|
||||
@@ -11,15 +11,17 @@ class InvalidCookie(Exception):
|
||||
self.code = code
|
||||
def __str__(self):
|
||||
if self.code == 'SESSION_NOT_FOUND':
|
||||
return f'Invalid cookie data: Session not found (incorrect refresh token)'
|
||||
return 'Invalid cookie data: Session not found (incorrect refresh token)'
|
||||
elif self.code == 'REFRESH_TOKEN_MISSING':
|
||||
return f'Invalid cookie data: No refresh token'
|
||||
return 'Invalid cookie data: No refresh token'
|
||||
elif self.code == 'SESSION_EXPIRED':
|
||||
return 'Invalid cookie data: Session expired'
|
||||
# SESSION_REVOKED
|
||||
return f'Invalid cookie data: Session revoked (logged out)'
|
||||
return 'Invalid cookie data: Session revoked (logged out)'
|
||||
|
||||
class InvalidToken(Exception):
|
||||
def __str__(self):
|
||||
return f'Invalid access token'
|
||||
return 'Invalid access token'
|
||||
|
||||
class SamePassword(Exception):
|
||||
def __str__(self):
|
||||
@@ -30,7 +32,7 @@ class InvalidOldPassword(Exception):
|
||||
return 'Old password is incorrect'
|
||||
|
||||
class NotFound(Exception):
|
||||
def __init__(self, obj):
|
||||
def __init__(self, obj: str):
|
||||
self.obj = obj
|
||||
def __str__(self):
|
||||
return f'{self.obj} not found'
|
||||
@@ -81,3 +83,13 @@ class CantRepostYourPost(Exception):
|
||||
class AlreadyReposted(Exception):
|
||||
def __str__(self):
|
||||
return 'Post already reposted'
|
||||
|
||||
class AlreadyReported(Exception):
|
||||
def __init__(self, obj: str) -> None:
|
||||
self.obj = obj
|
||||
def __str__(self):
|
||||
return f'{self.obj} already reported'
|
||||
|
||||
class TooLarge(Exception):
|
||||
def __str__(self):
|
||||
return 'Search query too large'
|
||||
|
||||
@@ -13,7 +13,7 @@ class Pagination(BaseModel):
|
||||
|
||||
class PostsPagintaion(BaseModel):
|
||||
limit: int = 20
|
||||
next_cursor: int = Field(1, alias='nextCursor')
|
||||
next_cursor: int | None = Field(1, alias='nextCursor')
|
||||
has_more: bool = Field(True, alias='hasMore')
|
||||
|
||||
|
||||
|
||||
6
itd/models/pin.py
Normal file
6
itd/models/pin.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
class Pin(BaseModel):
|
||||
slug: str
|
||||
name: str
|
||||
description: str
|
||||
@@ -44,8 +44,3 @@ class Post(_Post, PostShort):
|
||||
|
||||
class NewPost(_Post):
|
||||
author: UserNewPost
|
||||
|
||||
|
||||
class LikePostResponse(BaseModel):
|
||||
liked: bool
|
||||
likes_count: int = Field(alias="likesCount")
|
||||
|
||||
@@ -5,12 +5,15 @@ from pydantic import BaseModel, Field
|
||||
|
||||
from itd.enums import ReportTargetType, ReportTargetReason
|
||||
|
||||
class Report(BaseModel):
|
||||
|
||||
class NewReport(BaseModel):
|
||||
id: UUID
|
||||
created_at: datetime = Field(alias='createdAt')
|
||||
|
||||
|
||||
class Report(NewReport):
|
||||
reason: ReportTargetReason
|
||||
description: str | None = None
|
||||
|
||||
target_type: ReportTargetType = Field(alias='targetType')
|
||||
target_id: UUID
|
||||
|
||||
created_at: datetime = Field(alias='createdAt')
|
||||
@@ -3,6 +3,8 @@ from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from itd.models.pin import Pin
|
||||
|
||||
|
||||
class UserPrivacy(BaseModel):
|
||||
private: bool | None = Field(None, alias='isPrivate') # none for not me
|
||||
@@ -24,6 +26,7 @@ class UserNewPost(BaseModel):
|
||||
username: str | None = None
|
||||
display_name: str = Field(alias='displayName')
|
||||
avatar: str
|
||||
pin: Pin | None = None
|
||||
|
||||
verified: bool = False
|
||||
|
||||
|
||||
@@ -85,11 +85,11 @@ def auth_fetch(cookies: str, method: str, url: str, params: dict = {}, token: st
|
||||
try:
|
||||
if res.json().get('error', {}).get('code') == 'RATE_LIMIT_EXCEEDED':
|
||||
raise RateLimitExceeded(res.json()['error'].get('retryAfter', 0))
|
||||
if res.json().get('error', {}).get('code') in ('SESSION_NOT_FOUND', 'REFRESH_TOKEN_MISSING', 'SESSION_REVOKED'):
|
||||
if res.json().get('error', {}).get('code') in ('SESSION_NOT_FOUND', 'REFRESH_TOKEN_MISSING', 'SESSION_REVOKED', 'SESSION_EXPIRED'):
|
||||
raise InvalidCookie(res.json()['error']['code'])
|
||||
if res.json().get('error', {}).get('code') == 'UNAUTHORIZED':
|
||||
raise Unauthorized()
|
||||
except JSONDecodeError:
|
||||
pass
|
||||
print('fail to parse json')
|
||||
|
||||
return res
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from datetime import datetime
|
||||
from uuid import UUID
|
||||
|
||||
from itd.request import fetch
|
||||
@@ -36,8 +37,8 @@ def repost(token: str, id: UUID, content: str | None = None):
|
||||
def view_post(token: str, id: UUID):
|
||||
return fetch(token, 'post', f'posts/{id}/view')
|
||||
|
||||
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_or_id}/liked', {'limit': limit})
|
||||
def get_liked_posts(token: str, username_or_id: str | UUID, limit: int = 20, cursor: datetime | None = None):
|
||||
return fetch(token, 'get', f'posts/user/{username_or_id}/liked', {'limit': limit, 'cursor': cursor})
|
||||
|
||||
def restore_post(token: str, post_id: UUID):
|
||||
return fetch(token, "post", f"posts/{post_id}/restore",)
|
||||
@@ -45,5 +46,5 @@ def restore_post(token: str, post_id: UUID):
|
||||
def like_post(token: str, post_id: UUID):
|
||||
return fetch(token, "post", f"posts/{post_id}/like")
|
||||
|
||||
def delete_like_post(token: str, post_id: UUID):
|
||||
def unlike_post(token: str, post_id: UUID):
|
||||
return fetch(token, "delete", f"posts/{post_id}/like")
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
from itd.request import fetch
|
||||
from uuid import UUID
|
||||
|
||||
def report(token: str, id: str, type: str = 'post', reason: str = 'other', description: str = ''):
|
||||
return fetch(token, 'post', 'reports', {'targetId': id, 'targetType': type, 'reason': reason, 'description': description})
|
||||
from itd.request import fetch
|
||||
from itd.enums import ReportTargetReason, ReportTargetType
|
||||
|
||||
def report(token: str, id: UUID, type: ReportTargetType = ReportTargetType.POST, reason: ReportTargetReason = ReportTargetReason.OTHER, description: str | None = None):
|
||||
if description is None:
|
||||
description = ''
|
||||
return fetch(token, 'post', 'reports', {'targetId': str(id), 'targetType': type.value, 'reason': reason.value, 'description': description})
|
||||
Reference in New Issue
Block a user