itd-sdk 2.1.0__tar.gz → 2.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/PKG-INFO +1 -1
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/api/comments.py +3 -3
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/api/posts.py +4 -4
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/base.py +8 -4
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/client.py +2 -0
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/exceptions.py +6 -0
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/post.py +10 -11
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/user.py +12 -10
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/pyproject.toml +1 -1
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/README.md +0 -0
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/__init__.py +0 -0
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/_default.py +0 -0
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/api/__init__.py +0 -0
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/api/auth.py +0 -0
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/api/etc.py +0 -0
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/api/files.py +0 -0
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/api/hashtags.py +0 -0
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/api/notifications.py +0 -0
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/api/pins.py +0 -0
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/api/polls.py +0 -0
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/api/reports.py +0 -0
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/api/search.py +0 -0
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/api/subscription.py +0 -0
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/api/users.py +0 -0
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/api/verification.py +0 -0
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/clan.py +0 -0
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/comment.py +0 -0
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/enums.py +0 -0
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/file.py +0 -0
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/hashtag.py +0 -0
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/logger.py +0 -0
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/notification.py +0 -0
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/pin.py +0 -0
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/poll.py +0 -0
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/report.py +0 -0
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/request.py +0 -0
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/span.py +0 -0
- {itd_sdk-2.1.0 → itd_sdk-2.1.2}/itd/utils.py +0 -0
|
@@ -3,18 +3,18 @@ from uuid import UUID
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
5
|
from itd.base import catch_errors, rate_limit
|
|
6
|
-
from itd.exceptions import ValidationError, NotFoundError, AlreadyDeletedError, BannedWordError
|
|
6
|
+
from itd.exceptions import ValidationError, NotFoundError, AlreadyDeletedError, BannedWordError, ForbiddenError, RequiresSubscriptionError
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
9
|
from itd.client import Client
|
|
10
10
|
|
|
11
11
|
@rate_limit(5, 20, 80)
|
|
12
|
-
@catch_errors(ValidationError(), NotFoundError('Post'), BannedWordError('Comment'))
|
|
12
|
+
@catch_errors(ValidationError(), NotFoundError('Post'), BannedWordError('Comment'), ForbiddenError('comment - some files not owned'), RequiresSubscriptionError('Video uploading'))
|
|
13
13
|
def add_comment(client: Client, post_id: UUID, content: str | None = None, attachment_ids: list[UUID] = []):
|
|
14
14
|
return client.request('post', f'posts/{post_id}/comments', {'content': content or '', "attachmentIds": list(map(str, attachment_ids))})
|
|
15
15
|
|
|
16
16
|
@rate_limit(1, 10, 30)
|
|
17
|
-
@catch_errors(ValidationError(), NotFoundError('Comment'), NotFoundError('User', res_check=lambda res: res.status_code == 500 and 'Failed query' in res.text), BannedWordError('Reply'))
|
|
17
|
+
@catch_errors(ValidationError(), NotFoundError('Comment'), NotFoundError('User', res_check=lambda res: res.status_code == 500 and 'Failed query' in res.text), BannedWordError('Reply'), ForbiddenError('reply - some files not owned'), RequiresSubscriptionError('Video uploading'))
|
|
18
18
|
def add_reply_comment(client: Client, comment_id: UUID, author_id: UUID, content: str | None = None, attachment_ids: list[UUID] = []):
|
|
19
19
|
return client.request('post', f'comments/{comment_id}/replies', {'content': content or '', 'replyToUserId': str(author_id), "attachmentIds": list(map(str, attachment_ids))})
|
|
20
20
|
|
|
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING
|
|
|
6
6
|
from itd.enums import PostsTab, UserPostSorting
|
|
7
7
|
from itd.poll import NewPoll
|
|
8
8
|
from itd.exceptions import (
|
|
9
|
-
NotFoundError, ForbiddenError,
|
|
9
|
+
NotFoundError, ForbiddenError, RequiresSubscriptionError, ValidationError, AlreadyRepostedError, CantRepostYourselfError,
|
|
10
10
|
NotPinnedError, EditExpiredError, BannedWordError
|
|
11
11
|
)
|
|
12
12
|
from itd.base import rate_limit, catch_errors
|
|
@@ -14,7 +14,7 @@ if TYPE_CHECKING:
|
|
|
14
14
|
from itd.client import Client
|
|
15
15
|
|
|
16
16
|
@rate_limit(1, 10, 30)
|
|
17
|
-
@catch_errors(NotFoundError('Wall recipient'), ForbiddenError('post - some files not owned'),
|
|
17
|
+
@catch_errors(NotFoundError('Wall recipient'), ForbiddenError('post - some files not owned'), RequiresSubscriptionError('Video uploading'), BannedWordError('Post'), ValidationError())
|
|
18
18
|
def create_post(
|
|
19
19
|
client: Client,
|
|
20
20
|
content: str | None = None,
|
|
@@ -49,7 +49,7 @@ def get_post(client: Client, id: UUID):
|
|
|
49
49
|
return client.request('get', f'posts/{id}')
|
|
50
50
|
|
|
51
51
|
@rate_limit(None, 1, 5)
|
|
52
|
-
@catch_errors(NotFoundError('Post'), ForbiddenError('edit post'), EditExpiredError())
|
|
52
|
+
@catch_errors(NotFoundError('Post'), ForbiddenError('edit post'), EditExpiredError(), BannedWordError('Post'))
|
|
53
53
|
def edit_post(client: Client, id: UUID, content: str, spans: list[dict] = []):
|
|
54
54
|
return client.request('put', f'posts/{id}', {'content': content, 'spans': spans})
|
|
55
55
|
|
|
@@ -74,7 +74,7 @@ def unpin_post(client: Client, id: UUID):
|
|
|
74
74
|
return client.request('delete', f'posts/{id}/pin')
|
|
75
75
|
|
|
76
76
|
@rate_limit(1, 10, 30)
|
|
77
|
-
@catch_errors(NotFoundError('Post'), AlreadyRepostedError(), CantRepostYourselfError(), ValidationError())
|
|
77
|
+
@catch_errors(NotFoundError('Post'), AlreadyRepostedError(), CantRepostYourselfError(), ValidationError(), BannedWordError('Post'))
|
|
78
78
|
def repost(client: Client, id: UUID, content: str | None = None):
|
|
79
79
|
data = {}
|
|
80
80
|
if content:
|
|
@@ -12,7 +12,7 @@ from pydantic_core import PydanticUndefinedType
|
|
|
12
12
|
|
|
13
13
|
from itd._default import get_default_client
|
|
14
14
|
from itd.logger import get_logger
|
|
15
|
-
from itd.exceptions import ITDException, ValidationError, RateLimitError, UnauthorizedError, AccessTokenExpiredError, DEFAULT_ERRORS
|
|
15
|
+
from itd.exceptions import ITDException, ValidationError, RateLimitError, UnauthorizedError, AccessTokenExpiredError, DEFAULT_ERRORS, InternalError
|
|
16
16
|
from itd.enums import All, ALL, DebugResponseMode, RateLimitMode, BATCH, Batch
|
|
17
17
|
if TYPE_CHECKING:
|
|
18
18
|
from itd.client import Client
|
|
@@ -370,12 +370,16 @@ def rate_limit(delay_min: float | None = None, delay_mid: float | None = None, d
|
|
|
370
370
|
sleep(max(delay, 0))
|
|
371
371
|
client.last_actions[func.__name__] = datetime.now()
|
|
372
372
|
|
|
373
|
+
if not client.config.retry_on_rate_limits:
|
|
374
|
+
return func(client, *args, **kwargs)
|
|
375
|
+
|
|
373
376
|
while True:
|
|
374
377
|
try:
|
|
375
378
|
return func(client, *args, **kwargs)
|
|
376
|
-
except RateLimitError as e:
|
|
377
|
-
|
|
378
|
-
|
|
379
|
+
except (RateLimitError, InternalError) as e:
|
|
380
|
+
retry_after = getattr(e, 'retry_after', client.config.rate_limit_wait) or client.config.rate_limit_wait
|
|
381
|
+
l.info('rate limit on %s; wait %ss', func.__name__, retry_after)
|
|
382
|
+
sleep(retry_after)
|
|
379
383
|
|
|
380
384
|
return wrapper
|
|
381
385
|
return decorator
|
|
@@ -45,6 +45,8 @@ class Config:
|
|
|
45
45
|
solve_challenge: bool = True
|
|
46
46
|
load_comments_from_post: bool = False
|
|
47
47
|
parse_mode: ParseMode = ParseMode.NO
|
|
48
|
+
rate_limit_wait: int = 10 # delay before next attempt (after rate limit error) if retry_after is not provided
|
|
49
|
+
retry_on_rate_limits: bool = True
|
|
48
50
|
|
|
49
51
|
def __post_init__(self):
|
|
50
52
|
if self.rate_limit_default:
|
|
@@ -129,6 +129,11 @@ class RequiresVerificationError(NoRightsError):
|
|
|
129
129
|
def __init__(self, obj: str):
|
|
130
130
|
self.text = f'{obj} allowed only for verificated users'
|
|
131
131
|
|
|
132
|
+
class RequiresSubscriptionError(NoRightsError):
|
|
133
|
+
code = 'VIDEO_REQUIRES_NUKSTA'
|
|
134
|
+
def __init__(self, obj: str):
|
|
135
|
+
self.text = f'{obj} allowed only for users with НУКСТА subscription'
|
|
136
|
+
|
|
132
137
|
|
|
133
138
|
class UsernameTakenError(ValidateError):
|
|
134
139
|
code = 'USERNAME_TAKEN'
|
|
@@ -226,6 +231,7 @@ class NotPinnedError(ITDException):
|
|
|
226
231
|
text = 'Post not found or is not pinned'
|
|
227
232
|
|
|
228
233
|
class InternalError(ITDException):
|
|
234
|
+
status_code = 502
|
|
229
235
|
code = 'INTERNAL_ERROR'
|
|
230
236
|
text = 'Internal server error'
|
|
231
237
|
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
from uuid import UUID
|
|
2
2
|
from datetime import datetime
|
|
3
|
-
from typing import Literal, overload
|
|
3
|
+
from typing import Literal, overload, TYPE_CHECKING
|
|
4
4
|
from time import sleep
|
|
5
5
|
|
|
6
6
|
from pydantic import Field, BaseModel, field_validator
|
|
7
|
-
from pydantic.fields import FieldInfo
|
|
8
7
|
|
|
9
8
|
from itd.base import ITDBaseModel, refresh_wrapper, ITDList
|
|
10
9
|
from itd.client import Client
|
|
@@ -238,7 +237,7 @@ class Post(ITDBaseModel):
|
|
|
238
237
|
client (Client | None, optional): Клиент. Defaults to None.
|
|
239
238
|
"""
|
|
240
239
|
pin_post(client or self.client, self.id)
|
|
241
|
-
self.
|
|
240
|
+
self.is_pinned = True
|
|
242
241
|
(client or self.client).user.pinned_post_id = self.id
|
|
243
242
|
|
|
244
243
|
def unpin(self, client: Client | None = None) -> None:
|
|
@@ -248,7 +247,7 @@ class Post(ITDBaseModel):
|
|
|
248
247
|
client (Client | None, optional): Клиент. Defaults to None.
|
|
249
248
|
"""
|
|
250
249
|
unpin_post(client or self.client, self.id)
|
|
251
|
-
self.
|
|
250
|
+
self.is_pinned = False
|
|
252
251
|
(client or self.client).user.pinned_post_id = None
|
|
253
252
|
|
|
254
253
|
def delete(self, client: Client | None = None) -> None:
|
|
@@ -314,13 +313,13 @@ class Post(ITDBaseModel):
|
|
|
314
313
|
def url(self) -> str:
|
|
315
314
|
return f'https://xn--d1ah4a.com/@{self.author.username}/post/{self.id}'
|
|
316
315
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
316
|
+
if not TYPE_CHECKING:
|
|
317
|
+
def __getattribute__(self, name: str):
|
|
318
|
+
value = super().__getattribute__(name)
|
|
319
|
+
if name == 'comments' and getattr(value, '_post_id', None) is None:
|
|
320
|
+
value = Comments()
|
|
321
|
+
value._post_id = self.id
|
|
322
|
+
return value
|
|
324
323
|
|
|
325
324
|
|
|
326
325
|
|
|
@@ -106,10 +106,11 @@ class Privacy(ITDBaseModel):
|
|
|
106
106
|
instance._user = user
|
|
107
107
|
return instance
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
109
|
+
if not TYPE_CHECKING:
|
|
110
|
+
def __getattribute__(self, name: str):
|
|
111
|
+
if name in ('is_private', 'wall_access', 'likes_visibility') and object.__getattribute__(self, '_user'):
|
|
112
|
+
setattr(self, name, getattr(object.__getattribute__(self, '_user'), name))
|
|
113
|
+
return super().__getattribute__(name)
|
|
113
114
|
|
|
114
115
|
|
|
115
116
|
class _PrivacyValidate(BaseModel, Privacy):
|
|
@@ -204,7 +205,7 @@ class _UserBase(ITDBaseModel):
|
|
|
204
205
|
|
|
205
206
|
@property
|
|
206
207
|
def liked_posts(self) -> LikedPosts:
|
|
207
|
-
if not hasattr(self, '
|
|
208
|
+
if not hasattr(self, '_liked_posts'):
|
|
208
209
|
from itd.post import LikedPosts
|
|
209
210
|
self._liked_posts = LikedPosts(self, client=self.client)
|
|
210
211
|
return self._liked_posts
|
|
@@ -515,11 +516,12 @@ class Me(_UserBase):
|
|
|
515
516
|
self._pins = [Pin(pin, self) for pin in get_pins(self.client).json()['data']['pins']]
|
|
516
517
|
return self._pins
|
|
517
518
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
value
|
|
522
|
-
|
|
519
|
+
if not TYPE_CHECKING:
|
|
520
|
+
def __getattribute__(self, name: str):
|
|
521
|
+
value = super().__getattribute__(name)
|
|
522
|
+
if name == 'pin' and value is not None and getattr(value, '_user', None) is None:
|
|
523
|
+
value._user = self
|
|
524
|
+
return value
|
|
523
525
|
|
|
524
526
|
|
|
525
527
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|