Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6236c5981b | ||
|
|
27bd528883 | ||
|
|
63dfedc917 | ||
|
|
006bda53c1 | ||
|
|
201f720843 | ||
|
|
8aef43e11d | ||
|
|
506e6a5d09 | ||
|
|
f33ed4f76a | ||
|
|
2d27507338 | ||
|
|
b2bd982c1b | ||
|
|
7d5275c880 | ||
|
|
a2d019e1d6 | ||
|
|
844671ab4e | ||
|
|
38805156bf | ||
|
|
ae8d4c04d5 | ||
|
|
65cd617a1f | ||
|
|
55630bc23f | ||
|
|
60ae5feb21 | ||
|
|
dd7b8c077e | ||
|
|
2f026a32d7 | ||
|
|
dc72c6fb4b | ||
|
|
b82d4fc30b | ||
|
|
295501462a | ||
|
|
eb83c724cc | ||
|
|
a547bbfdfb | ||
|
|
6ac12c6f79 | ||
|
|
0c212e41cb | ||
|
|
80a5a33443 | ||
|
|
7ce699e42a | ||
|
|
3ad003a0b1 | ||
|
|
2ccd26fc9b | ||
|
|
af0b2a1acc | ||
|
|
c9a5dcad10 | ||
|
|
1db59149f4 | ||
|
|
ba78457de5 | ||
|
|
2a9f7da9a9 | ||
|
|
8d03171925 | ||
|
|
a388426d8d | ||
|
|
c7e3812ee8 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,5 +1,7 @@
|
|||||||
test.py
|
test.py
|
||||||
|
like.py
|
||||||
venv/
|
venv/
|
||||||
__pycache__/
|
__pycache__/
|
||||||
dist
|
dist
|
||||||
itd_sdk.egg-info
|
itd_sdk.egg-info
|
||||||
|
nowkie.gif
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# pyITDclient
|
# itd-sdk
|
||||||
Клиент ITD для python
|
Клиент ITD для python
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
1013
itd/client.py
1013
itd/client.py
File diff suppressed because it is too large
Load Diff
34
itd/enums.py
Normal file
34
itd/enums.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
class NotificationType(Enum):
|
||||||
|
WALL_POST = 'wall_post'
|
||||||
|
REPLY = 'reply'
|
||||||
|
REPOST = 'repost'
|
||||||
|
COMMENT = 'comment'
|
||||||
|
FOLLOW = 'follow'
|
||||||
|
LIKE = 'like'
|
||||||
|
|
||||||
|
class NotificationTargetType(Enum):
|
||||||
|
POST = 'post'
|
||||||
|
|
||||||
|
class ReportTargetType(Enum):
|
||||||
|
POST = 'post'
|
||||||
|
USER = 'user'
|
||||||
|
COMMENT = 'comment'
|
||||||
|
|
||||||
|
class ReportTargetReason(Enum):
|
||||||
|
SPAM = 'spam' # спам
|
||||||
|
VIOLENCE = 'violence' # насилие
|
||||||
|
HATE = 'hate' # ненависть
|
||||||
|
ADULT = 'adult' # 18+
|
||||||
|
FRAUD = 'fraud' # обман\мошенничество
|
||||||
|
OTHER = 'other' # другое
|
||||||
|
|
||||||
|
class AttachType(Enum):
|
||||||
|
AUDIO = 'audio'
|
||||||
|
IMAGE = 'image'
|
||||||
|
VIDEO = 'video'
|
||||||
|
|
||||||
|
class PostsTab(Enum):
|
||||||
|
FOLLOWING = 'following'
|
||||||
|
POPULAR = 'popular'
|
||||||
101
itd/exceptions.py
Normal file
101
itd/exceptions.py
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
class NoCookie(Exception):
|
||||||
|
def __str__(self):
|
||||||
|
return 'No cookie for refresh-token required action'
|
||||||
|
|
||||||
|
class NoAuthData(Exception):
|
||||||
|
def __str__(self):
|
||||||
|
return 'No auth data. Provide token or cookies'
|
||||||
|
|
||||||
|
class InvalidCookie(Exception):
|
||||||
|
def __init__(self, code: str):
|
||||||
|
self.code = code
|
||||||
|
def __str__(self):
|
||||||
|
if self.code == 'SESSION_NOT_FOUND':
|
||||||
|
return 'Invalid cookie data: Session not found (incorrect refresh token)'
|
||||||
|
elif self.code == 'REFRESH_TOKEN_MISSING':
|
||||||
|
return 'Invalid cookie data: No refresh token'
|
||||||
|
elif self.code == 'SESSION_EXPIRED':
|
||||||
|
return 'Invalid cookie data: Session expired'
|
||||||
|
# SESSION_REVOKED
|
||||||
|
return 'Invalid cookie data: Session revoked (logged out)'
|
||||||
|
|
||||||
|
class InvalidToken(Exception):
|
||||||
|
def __str__(self):
|
||||||
|
return 'Invalid access token'
|
||||||
|
|
||||||
|
class SamePassword(Exception):
|
||||||
|
def __str__(self):
|
||||||
|
return 'Old and new password must not equals'
|
||||||
|
|
||||||
|
class InvalidOldPassword(Exception):
|
||||||
|
def __str__(self):
|
||||||
|
return 'Old password is incorrect'
|
||||||
|
|
||||||
|
class NotFound(Exception):
|
||||||
|
def __init__(self, obj: str):
|
||||||
|
self.obj = obj
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.obj} not found'
|
||||||
|
|
||||||
|
class UserBanned(Exception):
|
||||||
|
def __str__(self):
|
||||||
|
return 'User banned'
|
||||||
|
|
||||||
|
class ValidationError(Exception):
|
||||||
|
def __init__(self, name: str, value: str):
|
||||||
|
self.name = name
|
||||||
|
self.value = value
|
||||||
|
def __str__(self):
|
||||||
|
return f'Failed validation on {self.name}: "{self.value}"'
|
||||||
|
|
||||||
|
class PendingRequestExists(Exception):
|
||||||
|
def __str__(self):
|
||||||
|
return 'Pending verifiaction request already exists'
|
||||||
|
|
||||||
|
class RateLimitExceeded(Exception):
|
||||||
|
def __init__(self, retry_after: int):
|
||||||
|
self.retry_after = retry_after
|
||||||
|
def __str__(self):
|
||||||
|
return f'Rate limit exceeded - too much requests. Retry after {self.retry_after} seconds'
|
||||||
|
|
||||||
|
class Forbidden(Exception):
|
||||||
|
def __init__(self, action: str):
|
||||||
|
self.action = action
|
||||||
|
def __str__(self):
|
||||||
|
return f'Forbidden to {self.action}'
|
||||||
|
|
||||||
|
class UsernameTaken(Exception):
|
||||||
|
def __str__(self):
|
||||||
|
return 'Username is already taken'
|
||||||
|
|
||||||
|
class CantFollowYourself(Exception):
|
||||||
|
def __str__(self):
|
||||||
|
return 'Cannot follow yourself'
|
||||||
|
|
||||||
|
class Unauthorized(Exception):
|
||||||
|
def __str__(self):
|
||||||
|
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'
|
||||||
|
|
||||||
|
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'
|
||||||
|
|
||||||
|
class PinNotOwned(Exception):
|
||||||
|
def __init__(self, pin: str) -> None:
|
||||||
|
self.pin = pin
|
||||||
|
def __str__(self):
|
||||||
|
return f'You do not own "{self.pin}" pin'
|
||||||
0
itd/models/__init__.py
Normal file
0
itd/models/__init__.py
Normal file
21
itd/models/_text.py
Normal file
21
itd/models/_text.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from uuid import UUID
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field, field_validator
|
||||||
|
|
||||||
|
|
||||||
|
class TextObject(BaseModel):
|
||||||
|
id: UUID
|
||||||
|
content: str
|
||||||
|
|
||||||
|
created_at: datetime = Field(alias='createdAt')
|
||||||
|
|
||||||
|
model_config = {'populate_by_name': True}
|
||||||
|
|
||||||
|
@field_validator('created_at', mode='plain')
|
||||||
|
@classmethod
|
||||||
|
def validate_created_at(cls, v: str):
|
||||||
|
try:
|
||||||
|
return datetime.strptime(v + '00', '%Y-%m-%d %H:%M:%S.%f%z')
|
||||||
|
except ValueError:
|
||||||
|
return datetime.strptime(v, '%Y-%m-%dT%H:%M:%S.%fZ')
|
||||||
6
itd/models/clan.py
Normal file
6
itd/models/clan.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
|
class Clan(BaseModel):
|
||||||
|
avatar: str
|
||||||
|
member_count: int = Field(0, alias='memberCount')
|
||||||
17
itd/models/comment.py
Normal file
17
itd/models/comment.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from pydantic import Field
|
||||||
|
|
||||||
|
from itd.models._text import TextObject
|
||||||
|
from itd.models.user import UserPost
|
||||||
|
from itd.models.file import Attach
|
||||||
|
|
||||||
|
|
||||||
|
class Comment(TextObject):
|
||||||
|
author: UserPost
|
||||||
|
|
||||||
|
likes_count: int = Field(0, alias='likesCount')
|
||||||
|
replies_count: int = Field(0, alias='repliesCount')
|
||||||
|
is_liked: bool = Field(False, alias='isLiked')
|
||||||
|
|
||||||
|
attachments: list[Attach] = []
|
||||||
|
replies: list['Comment'] = []
|
||||||
|
reply_to: UserPost | None = None # author of replied comment, if this comment is reply
|
||||||
29
itd/models/file.py
Normal file
29
itd/models/file.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from itd.enums import AttachType
|
||||||
|
|
||||||
|
class File(BaseModel):
|
||||||
|
id: UUID
|
||||||
|
url: str
|
||||||
|
filename: str
|
||||||
|
mime_type: str = Field(alias='mimeType')
|
||||||
|
size: int
|
||||||
|
|
||||||
|
|
||||||
|
class PostAttach(BaseModel):
|
||||||
|
id: UUID
|
||||||
|
type: AttachType = AttachType.IMAGE
|
||||||
|
url: str
|
||||||
|
thumbnail_url: str | None = Field(None, alias='thumbnailUrl')
|
||||||
|
width: int | None = None
|
||||||
|
height: int | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class Attach(PostAttach):
|
||||||
|
filename: str
|
||||||
|
mime_type: str = Field(alias='mimeType')
|
||||||
|
size: int
|
||||||
|
duration: int | None = None
|
||||||
|
order: int = 0
|
||||||
8
itd/models/hashtag.py
Normal file
8
itd/models/hashtag.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
class Hashtag(BaseModel):
|
||||||
|
id: UUID
|
||||||
|
name: str
|
||||||
|
posts_count: int = Field(0, alias='postsCount')
|
||||||
22
itd/models/notification.py
Normal file
22
itd/models/notification.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
from uuid import UUID
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from itd.enums import NotificationType, NotificationTargetType
|
||||||
|
from itd.models.user import UserNotification
|
||||||
|
|
||||||
|
class Notification(BaseModel):
|
||||||
|
id: UUID
|
||||||
|
type: NotificationType
|
||||||
|
|
||||||
|
target_type: NotificationTargetType | None = Field(None, alias='targetType') # none - follows, other - NotificationTragetType.POST
|
||||||
|
target_id: UUID | None = Field(None, alias='targetId') # none - follows
|
||||||
|
|
||||||
|
preview: str | None = None # follow - none, comment/reply - content, repost - original post content, like - post content, wall_post - wall post content
|
||||||
|
|
||||||
|
read: bool = False
|
||||||
|
read_at: datetime | None = Field(None, alias='readAt')
|
||||||
|
created_at: datetime = Field(alias='createdAt')
|
||||||
|
|
||||||
|
actor: UserNotification
|
||||||
23
itd/models/pagination.py
Normal file
23
itd/models/pagination.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
from uuid import UUID
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
class Pagination(BaseModel):
|
||||||
|
page: int | None = 1
|
||||||
|
limit: int = 20
|
||||||
|
total: int | None = None
|
||||||
|
has_more: bool = Field(True, alias='hasMore')
|
||||||
|
next_cursor: UUID | None = Field(None, alias='nextCursor')
|
||||||
|
|
||||||
|
|
||||||
|
class PostsPagintaion(BaseModel):
|
||||||
|
limit: int = 20
|
||||||
|
next_cursor: int | None = 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')
|
||||||
12
itd/models/pin.py
Normal file
12
itd/models/pin.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
class ShortPin(BaseModel):
|
||||||
|
slug: str
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
|
||||||
|
|
||||||
|
class Pin(ShortPin):
|
||||||
|
granted_at: datetime = Field(alias='grantedAt')
|
||||||
46
itd/models/post.py
Normal file
46
itd/models/post.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from pydantic import Field, BaseModel
|
||||||
|
|
||||||
|
from itd.models.user import UserPost, UserNewPost
|
||||||
|
from itd.models._text import TextObject
|
||||||
|
from itd.models.file import PostAttach
|
||||||
|
from itd.models.comment import Comment
|
||||||
|
|
||||||
|
|
||||||
|
class _PostShort(TextObject):
|
||||||
|
likes_count: int = Field(0, alias='likesCount')
|
||||||
|
comments_count: int = Field(0, alias='commentsCount')
|
||||||
|
reposts_count: int = Field(0, alias='repostsCount')
|
||||||
|
views_count: int = Field(0, alias='viewsCount')
|
||||||
|
|
||||||
|
|
||||||
|
class PostShort(_PostShort):
|
||||||
|
author: UserPost
|
||||||
|
|
||||||
|
|
||||||
|
class OriginalPost(PostShort):
|
||||||
|
is_deleted: bool = Field(False, alias='isDeleted')
|
||||||
|
|
||||||
|
|
||||||
|
class _Post(_PostShort):
|
||||||
|
is_liked: bool = Field(False, alias='isLiked')
|
||||||
|
is_reposted: bool = Field(False, alias='isReposted')
|
||||||
|
is_viewed: bool = Field(False, alias='isViewed')
|
||||||
|
is_owner: bool = Field(False, alias='isOwner')
|
||||||
|
|
||||||
|
attachments: list[PostAttach] = []
|
||||||
|
comments: list[Comment] = []
|
||||||
|
|
||||||
|
original_post: OriginalPost | None = None
|
||||||
|
|
||||||
|
wall_recipient_id: UUID | None = Field(None, alias='wallRecipientId')
|
||||||
|
wall_recipient: UserPost | None = Field(None, alias='wallRecipient')
|
||||||
|
|
||||||
|
|
||||||
|
class Post(_Post, PostShort):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NewPost(_Post):
|
||||||
|
author: UserNewPost
|
||||||
19
itd/models/report.py
Normal file
19
itd/models/report.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
from uuid import UUID
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from itd.enums import ReportTargetType, ReportTargetReason
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
64
itd/models/user.py
Normal file
64
itd/models/user.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
from uuid import UUID
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from itd.models.pin import ShortPin
|
||||||
|
|
||||||
|
|
||||||
|
class UserPrivacy(BaseModel):
|
||||||
|
private: bool | None = Field(None, alias='isPrivate') # none for not me
|
||||||
|
wall_closed: bool = Field(False, alias='wallClosed')
|
||||||
|
|
||||||
|
model_config = {'populate_by_name': True}
|
||||||
|
|
||||||
|
|
||||||
|
class UserProfileUpdate(BaseModel):
|
||||||
|
id: UUID
|
||||||
|
username: str | None = None
|
||||||
|
display_name: str = Field(alias='displayName')
|
||||||
|
bio: str | None = None
|
||||||
|
|
||||||
|
updated_at: datetime | None = Field(None, alias='updatedAt')
|
||||||
|
|
||||||
|
|
||||||
|
class UserNewPost(BaseModel):
|
||||||
|
username: str | None = None
|
||||||
|
display_name: str = Field(alias='displayName')
|
||||||
|
avatar: str
|
||||||
|
pin: ShortPin | None = None
|
||||||
|
|
||||||
|
verified: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
class UserNotification(UserNewPost):
|
||||||
|
id: UUID
|
||||||
|
|
||||||
|
|
||||||
|
class UserPost(UserNotification, UserNewPost):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UserWhoToFollow(UserPost):
|
||||||
|
followers_count: int = Field(0, alias='followersCount')
|
||||||
|
|
||||||
|
|
||||||
|
class UserFollower(UserPost):
|
||||||
|
is_following: bool = Field(False, alias='isFollowing') # none for me
|
||||||
|
|
||||||
|
|
||||||
|
class UserSearch(UserFollower, UserWhoToFollow):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class User(UserSearch, UserPrivacy):
|
||||||
|
banner: str | None = None
|
||||||
|
bio: str | None = None
|
||||||
|
pinned_post_id: UUID | None = Field(None, alias='pinnedPostId')
|
||||||
|
|
||||||
|
following_count: int = Field(0, alias='followingCount')
|
||||||
|
posts_count: int = Field(0, alias='postsCount')
|
||||||
|
|
||||||
|
is_followed: bool | None = Field(None, alias='isFollowedBy') # none for me
|
||||||
|
|
||||||
|
created_at: datetime = Field(alias='createdAt')
|
||||||
23
itd/models/verification.py
Normal file
23
itd/models/verification.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
from uuid import UUID
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
class Verification(BaseModel):
|
||||||
|
id: UUID
|
||||||
|
user_id: UUID = Field(alias='userId')
|
||||||
|
video_url: str = Field(alias='videoUrl')
|
||||||
|
status: str # should be enum, but we dont know all statuses (what status for accepted?)
|
||||||
|
|
||||||
|
reject_reason: str | None = Field(None, alias='rejectionReason')
|
||||||
|
reviewer: str | None = Field(None, alias='reviewedBy')
|
||||||
|
reviewed_at: datetime | None = Field(None, alias='reviewedAt')
|
||||||
|
|
||||||
|
created_at: datetime = Field(alias='createdAt')
|
||||||
|
updated_at: datetime = Field(alias='updatedAt')
|
||||||
|
|
||||||
|
|
||||||
|
class VerificationStatus(BaseModel):
|
||||||
|
status: str # should be enum, but we dont know all statuses (what status for accepted?)
|
||||||
|
request_id: UUID = Field(alias='requestId')
|
||||||
|
submitted_at: datetime = Field(alias='submittedAt')
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
from _io import BufferedReader
|
from _io import BufferedReader
|
||||||
|
|
||||||
from requests import Session
|
from requests import Session
|
||||||
|
from requests.exceptions import JSONDecodeError
|
||||||
|
|
||||||
|
from itd.exceptions import InvalidToken, InvalidCookie, RateLimitExceeded, Unauthorized
|
||||||
|
|
||||||
s = Session()
|
s = Session()
|
||||||
|
|
||||||
@@ -27,10 +30,20 @@ def fetch(token: str, method: str, url: str, params: dict = {}, files: dict[str,
|
|||||||
if method == "get":
|
if method == "get":
|
||||||
res = s.get(base, timeout=120 if files else 20, params=params, headers=headers)
|
res = s.get(base, timeout=120 if files else 20, params=params, headers=headers)
|
||||||
else:
|
else:
|
||||||
res = s.request(method.upper(), base, timeout=20, json=params, headers=headers, files=files)
|
res = s.request(method.upper(), base, timeout=120 if files else 20, json=params, headers=headers, files=files)
|
||||||
|
|
||||||
|
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') == 'UNAUTHORIZED':
|
||||||
|
raise Unauthorized()
|
||||||
|
except JSONDecodeError:
|
||||||
|
pass # todo
|
||||||
|
|
||||||
|
if not res.ok:
|
||||||
|
print(res.text)
|
||||||
|
return res
|
||||||
|
|
||||||
res.raise_for_status()
|
|
||||||
return res.json()
|
|
||||||
|
|
||||||
def set_cookies(cookies: str):
|
def set_cookies(cookies: str):
|
||||||
for cookie in cookies.split('; '):
|
for cookie in cookies.split('; '):
|
||||||
@@ -65,5 +78,18 @@ def auth_fetch(cookies: str, method: str, url: str, params: dict = {}, token: st
|
|||||||
res = s.get(f'https://xn--d1ah4a.com/api/{url}', timeout=20, params=params, headers=headers)
|
res = s.get(f'https://xn--d1ah4a.com/api/{url}', timeout=20, params=params, headers=headers)
|
||||||
else:
|
else:
|
||||||
res = s.request(method, f'https://xn--d1ah4a.com/api/{url}', timeout=20, json=params, headers=headers)
|
res = s.request(method, f'https://xn--d1ah4a.com/api/{url}', timeout=20, json=params, headers=headers)
|
||||||
res.raise_for_status()
|
|
||||||
return res.json()
|
# print(res.text)
|
||||||
|
if res.text == 'UNAUTHORIZED':
|
||||||
|
raise InvalidToken()
|
||||||
|
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', 'SESSION_EXPIRED'):
|
||||||
|
raise InvalidCookie(res.json()['error']['code'])
|
||||||
|
if res.json().get('error', {}).get('code') == 'UNAUTHORIZED':
|
||||||
|
raise Unauthorized()
|
||||||
|
except JSONDecodeError:
|
||||||
|
print('fail to parse json')
|
||||||
|
|
||||||
|
return res
|
||||||
|
|||||||
0
itd/routes/__init__.py
Normal file
0
itd/routes/__init__.py
Normal file
@@ -1,10 +1,12 @@
|
|||||||
|
from requests import Response
|
||||||
|
|
||||||
from itd.request import auth_fetch
|
from itd.request import auth_fetch
|
||||||
|
|
||||||
def refresh_token(cookies: str):
|
def refresh_token(cookies: str) -> Response:
|
||||||
return auth_fetch(cookies, 'post', 'v1/auth/refresh')['accessToken']
|
return auth_fetch(cookies, 'post', 'v1/auth/refresh')
|
||||||
|
|
||||||
def change_password(cookies: str, token: str, old: str, new: str):
|
def change_password(cookies: str, token: str, old: str, new: str) -> Response:
|
||||||
return auth_fetch(cookies, 'post', 'v1/auth/change-password', {'newPassword': new, 'oldPassword': old}, token)
|
return auth_fetch(cookies, 'post', 'v1/auth/change-password', {'newPassword': new, 'oldPassword': old}, token)
|
||||||
|
|
||||||
def logout(cookies: str):
|
def logout(cookies: str) -> Response:
|
||||||
return auth_fetch(cookies, 'post', 'v1/auth/logout')
|
return auth_fetch(cookies, 'post', 'v1/auth/logout')
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
|
from uuid import UUID
|
||||||
|
|
||||||
from itd.request import fetch
|
from itd.request import fetch
|
||||||
|
|
||||||
def add_comment(token: str, post_id: str, content: str, reply_comment_id: str | None = None):
|
def add_comment(token: str, post_id: UUID, content: str, attachment_ids: list[UUID] = []):
|
||||||
data = {'content': content}
|
return fetch(token, 'post', f'posts/{post_id}/comments', {'content': content, "attachmentIds": list(map(str, attachment_ids))})
|
||||||
if reply_comment_id:
|
|
||||||
data['replyTo'] = str(reply_comment_id)
|
|
||||||
return fetch(token, 'post', f'posts/{post_id}/comments', data)
|
|
||||||
|
|
||||||
def get_comments(token: str, post_id: str, limit: int = 20, cursor: int = 0, sort: str = 'popular'):
|
def add_reply_comment(token: str, comment_id: UUID, content: str, author_id: UUID, attachment_ids: list[UUID] = []):
|
||||||
|
return fetch(token, 'post', f'comments/{comment_id}/replies', {'content': content, 'replyToUserId': str(author_id), "attachmentIds": list(map(str, attachment_ids))})
|
||||||
|
|
||||||
|
def get_comments(token: str, post_id: UUID, limit: int = 20, cursor: int = 0, sort: str = 'popular'):
|
||||||
return fetch(token, 'get', f'posts/{post_id}/comments', {'limit': limit, 'sort': sort, 'cursor': cursor})
|
return fetch(token, 'get', f'posts/{post_id}/comments', {'limit': limit, 'sort': sort, 'cursor': cursor})
|
||||||
|
|
||||||
def like_comment(token: str, comment_id: str):
|
def like_comment(token: str, comment_id: UUID):
|
||||||
return fetch(token, 'post', f'comments/{comment_id}/like')
|
return fetch(token, 'post', f'comments/{comment_id}/like')
|
||||||
|
|
||||||
def unlike_comment(token: str, comment_id: str):
|
def unlike_comment(token: str, comment_id: UUID):
|
||||||
return fetch(token, 'delete', f'comments/{comment_id}/like')
|
return fetch(token, 'delete', f'comments/{comment_id}/like')
|
||||||
|
|
||||||
def delete_comment(token: str, comment_id: str):
|
def delete_comment(token: str, comment_id: UUID):
|
||||||
return fetch(token, 'delete', f'comments/{comment_id}')
|
return fetch(token, 'delete', f'comments/{comment_id}')
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
|
from warnings import deprecated
|
||||||
|
from uuid import UUID
|
||||||
from itd.request import fetch
|
from itd.request import fetch
|
||||||
|
|
||||||
|
@deprecated("get_hastags устарела используйте get_hashtags")
|
||||||
def get_hastags(token: str, limit: int = 10):
|
def get_hastags(token: str, limit: int = 10):
|
||||||
return fetch(token, 'get', 'hashtags/trending', {'limit': limit})
|
return fetch(token, 'get', 'hashtags/trending', {'limit': limit})
|
||||||
|
|
||||||
def get_posts_by_hastag(token: str, hashtag: str, limit: int = 20, cursor: int = 0):
|
def get_hashtags(token: str, limit: int = 10):
|
||||||
|
return fetch(token, 'get', 'hashtags/trending', {'limit': limit})
|
||||||
|
|
||||||
|
@deprecated("get_posts_by_hastag устерла используй get_posts_by_hashtag")
|
||||||
|
def get_posts_by_hastag(token: str, hashtag: str, limit: int = 20, cursor: UUID | None = None):
|
||||||
|
return fetch(token, 'get', f'hashtags/{hashtag}/posts', {'limit': limit, 'cursor': cursor})
|
||||||
|
|
||||||
|
def get_posts_by_hashtag(token: str, hashtag: str, limit: int = 20, cursor: UUID | None = None):
|
||||||
return fetch(token, 'get', f'hashtags/{hashtag}/posts', {'limit': limit, 'cursor': cursor})
|
return fetch(token, 'get', f'hashtags/{hashtag}/posts', {'limit': limit, 'cursor': cursor})
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
|
from uuid import UUID
|
||||||
|
|
||||||
from itd.request import fetch
|
from itd.request import fetch
|
||||||
|
|
||||||
def get_notifications(token: str, limit: int = 20, cursor: int = 0, type: str | None = None):
|
def get_notifications(token: str, limit: int = 20, offset: int = 0):
|
||||||
data = {'limit': str(limit), 'cursor': str(cursor)}
|
return fetch(token, 'get', 'notifications', {'limit': limit, 'offset': offset})
|
||||||
if type:
|
|
||||||
data['type'] = type
|
|
||||||
return fetch(token, 'get', 'notifications', data)
|
|
||||||
|
|
||||||
def mark_as_read(token: str, id: str):
|
def mark_as_read(token: str, id: UUID):
|
||||||
return fetch(token, 'post', f'notification/{id}/read')
|
return fetch(token, 'post', f'notifications/{id}/read')
|
||||||
|
|
||||||
def mark_all_as_read(token: str):
|
def mark_all_as_read(token: str):
|
||||||
return fetch(token, 'post', f'notification/read-all')
|
return fetch(token, 'post', f'notifications/read-all')
|
||||||
|
|
||||||
def get_unread_notifications_count(token: str):
|
def get_unread_notifications_count(token: str):
|
||||||
return fetch(token, 'get', 'notifications/count')
|
return fetch(token, 'get', 'notifications/count')
|
||||||
10
itd/routes/pins.py
Normal file
10
itd/routes/pins.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from itd.request import fetch
|
||||||
|
|
||||||
|
def get_pins(token: str):
|
||||||
|
return fetch(token, 'get', 'users/me/pins')
|
||||||
|
|
||||||
|
def remove_pin(token: str):
|
||||||
|
return fetch(token, 'delete', 'users/me/pin')
|
||||||
|
|
||||||
|
def set_pin(token: str, slug: str):
|
||||||
|
return fetch(token, 'put', 'users/me/pin', {'slug': slug})
|
||||||
@@ -1,45 +1,50 @@
|
|||||||
from itd.request import fetch
|
from datetime import datetime
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
def create_post(token: str, content: str, wall_recipient_id: int | None = None, attach_ids: list[str] = []):
|
from itd.request import fetch
|
||||||
|
from itd.enums import PostsTab
|
||||||
|
|
||||||
|
def create_post(token: str, content: str, wall_recipient_id: UUID | None = None, attachment_ids: list[UUID] = []):
|
||||||
data: dict = {'content': content}
|
data: dict = {'content': content}
|
||||||
if wall_recipient_id:
|
if wall_recipient_id:
|
||||||
data['wallRecipientId'] = wall_recipient_id
|
data['wallRecipientId'] = str(wall_recipient_id)
|
||||||
if attach_ids:
|
if attachment_ids:
|
||||||
data['attachmentIds'] = attach_ids
|
data['attachmentIds'] = list(map(str, attachment_ids))
|
||||||
|
|
||||||
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: str):
|
|
||||||
return fetch(token, 'get', f'posts/{id}')
|
return fetch(token, 'get', f'posts/{id}')
|
||||||
|
|
||||||
def edit_post(token: str, id: str, content: str):
|
def edit_post(token: str, id: UUID, content: str):
|
||||||
return fetch(token, 'put', f'posts/{id}', {'content': content})
|
return fetch(token, 'put', f'posts/{id}', {'content': content})
|
||||||
|
|
||||||
def delete_post(token: str, id: str):
|
def delete_post(token: str, id: UUID):
|
||||||
return fetch(token, 'delete', f'posts/{id}')
|
return fetch(token, 'delete', f'posts/{id}')
|
||||||
|
|
||||||
def pin_post(token: str, id: str):
|
def pin_post(token: str, id: UUID):
|
||||||
return fetch(token, 'post', f'posts/{id}/pin')
|
return fetch(token, 'post', f'posts/{id}/pin')
|
||||||
|
|
||||||
def repost(token: str, id: str, content: str | None = None):
|
def repost(token: str, id: UUID, content: str | None = None):
|
||||||
data = {}
|
data = {}
|
||||||
if content:
|
if content:
|
||||||
data['content'] = content
|
data['content'] = content
|
||||||
return fetch(token, 'post', f'posts/{id}/repost', data)
|
return fetch(token, 'post', f'posts/{id}/repost', data)
|
||||||
|
|
||||||
def view_post(token: str, id: str):
|
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: datetime | None = None):
|
||||||
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, 'cursor': cursor})
|
||||||
|
|
||||||
|
def restore_post(token: str, post_id: UUID):
|
||||||
|
return fetch(token, "post", f"posts/{post_id}/restore",)
|
||||||
|
|
||||||
|
def like_post(token: str, post_id: UUID):
|
||||||
|
return fetch(token, "post", f"posts/{post_id}/like")
|
||||||
|
|
||||||
|
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 = ''):
|
from itd.request import fetch
|
||||||
return fetch(token, 'post', 'reports', {'targetId': id, 'targetType': type, 'reason': reason, 'description': description})
|
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})
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
|
from uuid import UUID
|
||||||
|
|
||||||
from itd.request import fetch
|
from itd.request import fetch
|
||||||
|
|
||||||
|
|
||||||
def get_user(token: str, username: str):
|
def get_user(token: str, username: str):
|
||||||
return fetch(token, 'get', f'users/{username}')
|
return fetch(token, 'get', f'users/{username}')
|
||||||
|
|
||||||
def update_profile(token: str, bio: str | None = None, display_name: str | None = None, username: str | None = None, banner_id: str | None = None):
|
def update_profile(token: str, bio: str | None = None, display_name: str | None = None, username: str | None = None, banner_id: UUID | None = None):
|
||||||
data = {}
|
data = {}
|
||||||
if bio:
|
if bio:
|
||||||
data['bio'] = bio
|
data['bio'] = bio
|
||||||
@@ -13,14 +15,14 @@ def update_profile(token: str, bio: str | None = None, display_name: str | None
|
|||||||
if username:
|
if username:
|
||||||
data['username'] = username
|
data['username'] = username
|
||||||
if banner_id:
|
if banner_id:
|
||||||
data['bannerId'] = banner_id
|
data['bannerId'] = str(banner_id)
|
||||||
return fetch(token, 'put', 'users/me', data)
|
return fetch(token, 'put', 'users/me', data)
|
||||||
|
|
||||||
def update_privacy(token: str, wall_closed: bool = False, private: bool = False):
|
def update_privacy(token: str, wall_closed: bool = False, private: bool = False):
|
||||||
data = {}
|
data = {}
|
||||||
if wall_closed:
|
if wall_closed is not None:
|
||||||
data['wallClosed'] = wall_closed
|
data['wallClosed'] = wall_closed
|
||||||
if private:
|
if private is not None:
|
||||||
data['isPrivate'] = private
|
data['isPrivate'] = private
|
||||||
return fetch(token, 'put', 'users/me/privacy', data)
|
return fetch(token, 'put', 'users/me/privacy', data)
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
|
from warnings import deprecated
|
||||||
from itd.request import fetch
|
from itd.request import fetch
|
||||||
|
|
||||||
def verificate(token: str, file_url: str):
|
def verify(token: str, file_url: str):
|
||||||
# {"success":true,"request":{"id":"fc54e54f-8586-4d8c-809e-df93161f99da","userId":"9096a85b-c319-483e-8940-6921be427ad0","videoUrl":"https://943701f000610900cbe86b72234e451d.bckt.ru/videos/354f28a6-9ac7-48a6-879a-a454062b1d6b.mp4","status":"pending","rejectionReason":null,"reviewedBy":null,"reviewedAt":null,"createdAt":"2026-01-30T12:58:14.228Z","updatedAt":"2026-01-30T12:58:14.228Z"}}
|
# {"success":true,"request":{"id":"fc54e54f-8586-4d8c-809e-df93161f99da","userId":"9096a85b-c319-483e-8940-6921be427ad0","videoUrl":"https://943701f000610900cbe86b72234e451d.bckt.ru/videos/354f28a6-9ac7-48a6-879a-a454062b1d6b.mp4","status":"pending","rejectionReason":null,"reviewedBy":null,"reviewedAt":null,"createdAt":"2026-01-30T12:58:14.228Z","updatedAt":"2026-01-30T12:58:14.228Z"}}
|
||||||
return fetch(token, 'post', 'verification/submit', {'videoUrl': file_url})
|
return fetch(token, 'post', 'verification/submit', {'videoUrl': file_url})
|
||||||
|
|
||||||
|
@deprecated("verificate устарела используйте verify")
|
||||||
|
def verificate(token: str, file_url: str):
|
||||||
|
return verify(token, file_url)
|
||||||
|
|
||||||
|
|
||||||
def get_verification_status(token: str):
|
def get_verification_status(token: str):
|
||||||
return fetch(token, 'get', 'verification/status')
|
return fetch(token, 'get', 'verification/status')
|
||||||
BIN
nowkie.gif
BIN
nowkie.gif
Binary file not shown.
|
Before Width: | Height: | Size: 13 MiB |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "itd-sdk"
|
name = "itd-sdk"
|
||||||
version = "0.2.0"
|
version = "1.0.1"
|
||||||
description = "ITD client for python"
|
description = "ITD client for python"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [
|
authors = [
|
||||||
@@ -12,6 +12,6 @@ authors = [
|
|||||||
]
|
]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"requests"
|
"requests", "pydantic"
|
||||||
]
|
]
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
pydantic==2.11.9
|
||||||
|
requests==2.32.3
|
||||||
4
setup.py
4
setup.py
@@ -2,10 +2,10 @@ from setuptools import setup, find_packages
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='itd-sdk',
|
name='itd-sdk',
|
||||||
version='0.2.0',
|
version='1.0.1',
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'requests'
|
'requests', 'pydantic'
|
||||||
],
|
],
|
||||||
python_requires=">=3.9"
|
python_requires=">=3.9"
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user