itdpy 1.0.2__tar.gz → 1.0.4__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.
- itdpy-1.0.4/PKG-INFO +260 -0
- itdpy-1.0.4/README.md +244 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/client.py +15 -9
- itdpy-1.0.4/itdpy/config.py +20 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/enums.py +1 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/request.py +14 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/streaming.py +31 -4
- itdpy-1.0.4/itdpy.egg-info/PKG-INFO +260 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy.egg-info/SOURCES.txt +0 -1
- {itdpy-1.0.2 → itdpy-1.0.4}/pyproject.toml +1 -1
- itdpy-1.0.2/PKG-INFO +0 -267
- itdpy-1.0.2/README.md +0 -251
- itdpy-1.0.2/itdpy/auth.py +0 -35
- itdpy-1.0.2/itdpy/config.py +0 -12
- itdpy-1.0.2/itdpy.egg-info/PKG-INFO +0 -267
- {itdpy-1.0.2 → itdpy-1.0.4}/LICENSE +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/__init__.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/api/__init__.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/api/_common.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/api/base.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/api/comments.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/api/discovery.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/api/files.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/api/notifications.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/api/pins.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/api/posts.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/api/search.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/api/users.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/exceptions.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/formatting/__init__.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/formatting/parser.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/formatting/validator.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/models/__init__.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/models/base.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/models/clan.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/models/comment.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/models/file.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/models/follow_status.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/models/hashtags.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/models/notification.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/models/pagination.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/models/pin.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/models/portal.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/models/post.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/models/search.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/models/settings_models.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/models/user.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/models/who_to_follow.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy/utils/suggestions.py +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy.egg-info/dependency_links.txt +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy.egg-info/requires.txt +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/itdpy.egg-info/top_level.txt +0 -0
- {itdpy-1.0.2 → itdpy-1.0.4}/setup.cfg +0 -0
itdpy-1.0.4/PKG-INFO
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: itdpy
|
|
3
|
+
Version: 1.0.4
|
|
4
|
+
Summary: Production-ready Python SDK for ITD API
|
|
5
|
+
Author: Gam5510
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Gam5510/ITDpy
|
|
8
|
+
Project-URL: Repository, https://github.com/Gam5510/ITDpy
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: requests>=2.28.0
|
|
13
|
+
Requires-Dist: pydantic>=2.0.0
|
|
14
|
+
Requires-Dist: sseclient-py>=1.8.0
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# ITDpy
|
|
18
|
+
|
|
19
|
+
<p align="center">
|
|
20
|
+
<img src="https://i.postimg.cc/gJ9z8RDk/ITDpy-(1)-pixian-ai.png" width="700">
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
<p align="center">
|
|
24
|
+
<img src="https://img.shields.io/pypi/v/itdpy?nocache=1" alt="PyPI version">
|
|
25
|
+
<img src="https://static.pepy.tech/badge/itdpy?nocache=1" alt="Downloads">
|
|
26
|
+
<img src="https://img.shields.io/github/license/Gam5510/ITDpy" alt="License">
|
|
27
|
+
<a href="https://gam5510.github.io/ITDpy/">
|
|
28
|
+
<img src="https://img.shields.io/badge/docs-online-blue" alt="Docs">
|
|
29
|
+
</a>
|
|
30
|
+
</p>
|
|
31
|
+
|
|
32
|
+
Python SDK для социальной сети итд.com.
|
|
33
|
+
|
|
34
|
+
> Неофициальный API-клиент.
|
|
35
|
+
> SDK предназначен для клиентских приложений, интеграций и сервисов, работающих в рамках правил платформы.
|
|
36
|
+
|
|
37
|
+
## Безопасность и позиция проекта
|
|
38
|
+
|
|
39
|
+
ITDpy не поддерживает спам, массовую автоматизацию, накрутку, ботов для злоупотреблений и другие сценарии, нарушающие правила платформы.
|
|
40
|
+
|
|
41
|
+
Библиотека ориентирована на:
|
|
42
|
+
|
|
43
|
+
- клиентские приложения
|
|
44
|
+
- внутренние сервисы
|
|
45
|
+
- интеграции
|
|
46
|
+
- тестирование API
|
|
47
|
+
|
|
48
|
+
## User-Agent
|
|
49
|
+
|
|
50
|
+
По умолчанию библиотека использует браузерный User-Agent:
|
|
51
|
+
|
|
52
|
+
```text
|
|
53
|
+
Mozilla/5.0 (Linux; Android 11; SM-G991B)
|
|
54
|
+
AppleWebKit/537.36 (KHTML, like Gecko)
|
|
55
|
+
Chrome/120.0.6099.144 Mobile Safari/537.36
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Это сделано для обеспечения стабильной работы, так как некоторые эндпоинты API могут некорректно обрабатывать нестандартные клиенты.
|
|
59
|
+
|
|
60
|
+
Библиотека не использует User-Agent для обхода ограничений и ориентирована на корректное использование API.
|
|
61
|
+
|
|
62
|
+
## Установка
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install itdpy
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Через git
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
git clone https://github.com/Gam5510/ITDpy
|
|
72
|
+
cd ITDpy
|
|
73
|
+
pip install -r requirements.txt
|
|
74
|
+
pip install -e .
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Документация
|
|
78
|
+
|
|
79
|
+
[](https://gam5510.github.io/ITDpy/)
|
|
80
|
+
|
|
81
|
+
Ссылка: [https://gam5510.github.io/ITDpy/](https://gam5510.github.io/ITDpy/)
|
|
82
|
+
|
|
83
|
+
## Быстрый старт
|
|
84
|
+
|
|
85
|
+
> 
|
|
86
|
+
> Как получить токен
|
|
87
|
+
|
|
88
|
+
- Открой `итд.com` в браузере и войди в аккаунт.
|
|
89
|
+
- Открой DevTools (F12).
|
|
90
|
+
- Перейди в `Application` -> `Cookies`.
|
|
91
|
+
- Найди cookie `refresh_token`.
|
|
92
|
+
- Скопируй её значение.
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from itdpy import ITDClient
|
|
96
|
+
|
|
97
|
+
client = ITDClient(refresh_token="YOUR_REFRESH_TOKEN")
|
|
98
|
+
|
|
99
|
+
me = client.users.me()
|
|
100
|
+
print(me.id)
|
|
101
|
+
print(me.username)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Конфигурация
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from itdpy import ITDClient, Config
|
|
108
|
+
|
|
109
|
+
config = Config(
|
|
110
|
+
timeout=30,
|
|
111
|
+
upload_timeout=180,
|
|
112
|
+
max_retries=5,
|
|
113
|
+
backoff_factor=2.0,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
client = ITDClient(
|
|
117
|
+
refresh_token="YOUR_REFRESH_TOKEN",
|
|
118
|
+
config=config,
|
|
119
|
+
)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Кастомный User-Agent
|
|
123
|
+
|
|
124
|
+
Если нужен полностью свой `User-Agent`, можно передать его через `Config.custom_user_agent`:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from itdpy import ITDClient, Config
|
|
128
|
+
|
|
129
|
+
config = Config(
|
|
130
|
+
custom_user_agent="my-app/2.0",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
client = ITDClient(
|
|
134
|
+
refresh_token="YOUR_REFRESH_TOKEN",
|
|
135
|
+
config=config,
|
|
136
|
+
)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Если `custom_user_agent` не задан, библиотека использует стандартный стартовый браузерный `User-Agent`.
|
|
140
|
+
|
|
141
|
+
## User-Agent с данными пользователя
|
|
142
|
+
|
|
143
|
+
Если нужно после авторизации переключиться на `User-Agent` с данными SDK и пользователя, включи `use_user_data_in_user_agent=True`:
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
from itdpy import ITDClient, Config
|
|
147
|
+
|
|
148
|
+
config = Config(
|
|
149
|
+
service="my_app",
|
|
150
|
+
use_user_data_in_user_agent=True,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
client = ITDClient(
|
|
154
|
+
refresh_token="YOUR_REFRESH_TOKEN",
|
|
155
|
+
config=config,
|
|
156
|
+
)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
По умолчанию этот режим выключен.
|
|
160
|
+
|
|
161
|
+
Шаблон можно переопределить через `Config.user_agent_template`:
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
from itdpy import Config
|
|
165
|
+
|
|
166
|
+
config = Config(
|
|
167
|
+
use_user_data_in_user_agent=True,
|
|
168
|
+
user_agent_template="itdpy/{sdk_version} ({parts})",
|
|
169
|
+
)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Доступные поля шаблона:
|
|
173
|
+
|
|
174
|
+
- `{sdk_version}`
|
|
175
|
+
- `{parts}`
|
|
176
|
+
- `{user_id}`
|
|
177
|
+
- `{service}`
|
|
178
|
+
|
|
179
|
+
## Примеры
|
|
180
|
+
|
|
181
|
+
### Получить пост
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
post = client.posts.get("POST_ID")
|
|
185
|
+
print(post.id)
|
|
186
|
+
print(post.to_dict())
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Лента постов
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
posts = client.posts.list(limit=10)
|
|
193
|
+
print(len(posts))
|
|
194
|
+
print(posts[0].id)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Создать пост
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
client.posts.create(
|
|
201
|
+
content="Привет из ITDpy",
|
|
202
|
+
)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Markdown и HTML
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
client.posts.create(
|
|
209
|
+
content="**Жирный** текст",
|
|
210
|
+
parse_md=True,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
client.posts.create(
|
|
214
|
+
content="<b>Жирный</b> текст",
|
|
215
|
+
parse_html=True,
|
|
216
|
+
)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### SSE streaming
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
stream = client.notifications.stream()
|
|
223
|
+
|
|
224
|
+
@stream.on("notification")
|
|
225
|
+
def on_notification(event):
|
|
226
|
+
print(event.data)
|
|
227
|
+
|
|
228
|
+
stream.run()
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### keep_online
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
client.keep_online(
|
|
235
|
+
on_event=lambda event_type, data: print(event_type, data),
|
|
236
|
+
background=True,
|
|
237
|
+
)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Обработка ошибок
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
from itdpy import APIError, NotFoundError, RateLimitError, ValidationError
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
client.posts.get("invalid")
|
|
247
|
+
except NotFoundError:
|
|
248
|
+
print("Не найдено")
|
|
249
|
+
except ValidationError as e:
|
|
250
|
+
print(e.message)
|
|
251
|
+
except RateLimitError as e:
|
|
252
|
+
print(e.retry_after)
|
|
253
|
+
except APIError as e:
|
|
254
|
+
print(e.message)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Прочее
|
|
258
|
+
|
|
259
|
+
Проект активно развивается. Если у вас есть предложения или pull request, создавайте issue в репозитории.
|
|
260
|
+
Мой телеграм для обратной связи: [@gam5510](https://t.me/gam5510)
|
itdpy-1.0.4/README.md
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# ITDpy
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="https://i.postimg.cc/gJ9z8RDk/ITDpy-(1)-pixian-ai.png" width="700">
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<img src="https://img.shields.io/pypi/v/itdpy?nocache=1" alt="PyPI version">
|
|
9
|
+
<img src="https://static.pepy.tech/badge/itdpy?nocache=1" alt="Downloads">
|
|
10
|
+
<img src="https://img.shields.io/github/license/Gam5510/ITDpy" alt="License">
|
|
11
|
+
<a href="https://gam5510.github.io/ITDpy/">
|
|
12
|
+
<img src="https://img.shields.io/badge/docs-online-blue" alt="Docs">
|
|
13
|
+
</a>
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
Python SDK для социальной сети итд.com.
|
|
17
|
+
|
|
18
|
+
> Неофициальный API-клиент.
|
|
19
|
+
> SDK предназначен для клиентских приложений, интеграций и сервисов, работающих в рамках правил платформы.
|
|
20
|
+
|
|
21
|
+
## Безопасность и позиция проекта
|
|
22
|
+
|
|
23
|
+
ITDpy не поддерживает спам, массовую автоматизацию, накрутку, ботов для злоупотреблений и другие сценарии, нарушающие правила платформы.
|
|
24
|
+
|
|
25
|
+
Библиотека ориентирована на:
|
|
26
|
+
|
|
27
|
+
- клиентские приложения
|
|
28
|
+
- внутренние сервисы
|
|
29
|
+
- интеграции
|
|
30
|
+
- тестирование API
|
|
31
|
+
|
|
32
|
+
## User-Agent
|
|
33
|
+
|
|
34
|
+
По умолчанию библиотека использует браузерный User-Agent:
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
Mozilla/5.0 (Linux; Android 11; SM-G991B)
|
|
38
|
+
AppleWebKit/537.36 (KHTML, like Gecko)
|
|
39
|
+
Chrome/120.0.6099.144 Mobile Safari/537.36
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Это сделано для обеспечения стабильной работы, так как некоторые эндпоинты API могут некорректно обрабатывать нестандартные клиенты.
|
|
43
|
+
|
|
44
|
+
Библиотека не использует User-Agent для обхода ограничений и ориентирована на корректное использование API.
|
|
45
|
+
|
|
46
|
+
## Установка
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install itdpy
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Через git
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
git clone https://github.com/Gam5510/ITDpy
|
|
56
|
+
cd ITDpy
|
|
57
|
+
pip install -r requirements.txt
|
|
58
|
+
pip install -e .
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Документация
|
|
62
|
+
|
|
63
|
+
[](https://gam5510.github.io/ITDpy/)
|
|
64
|
+
|
|
65
|
+
Ссылка: [https://gam5510.github.io/ITDpy/](https://gam5510.github.io/ITDpy/)
|
|
66
|
+
|
|
67
|
+
## Быстрый старт
|
|
68
|
+
|
|
69
|
+
> 
|
|
70
|
+
> Как получить токен
|
|
71
|
+
|
|
72
|
+
- Открой `итд.com` в браузере и войди в аккаунт.
|
|
73
|
+
- Открой DevTools (F12).
|
|
74
|
+
- Перейди в `Application` -> `Cookies`.
|
|
75
|
+
- Найди cookie `refresh_token`.
|
|
76
|
+
- Скопируй её значение.
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from itdpy import ITDClient
|
|
80
|
+
|
|
81
|
+
client = ITDClient(refresh_token="YOUR_REFRESH_TOKEN")
|
|
82
|
+
|
|
83
|
+
me = client.users.me()
|
|
84
|
+
print(me.id)
|
|
85
|
+
print(me.username)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Конфигурация
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from itdpy import ITDClient, Config
|
|
92
|
+
|
|
93
|
+
config = Config(
|
|
94
|
+
timeout=30,
|
|
95
|
+
upload_timeout=180,
|
|
96
|
+
max_retries=5,
|
|
97
|
+
backoff_factor=2.0,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
client = ITDClient(
|
|
101
|
+
refresh_token="YOUR_REFRESH_TOKEN",
|
|
102
|
+
config=config,
|
|
103
|
+
)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Кастомный User-Agent
|
|
107
|
+
|
|
108
|
+
Если нужен полностью свой `User-Agent`, можно передать его через `Config.custom_user_agent`:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from itdpy import ITDClient, Config
|
|
112
|
+
|
|
113
|
+
config = Config(
|
|
114
|
+
custom_user_agent="my-app/2.0",
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
client = ITDClient(
|
|
118
|
+
refresh_token="YOUR_REFRESH_TOKEN",
|
|
119
|
+
config=config,
|
|
120
|
+
)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Если `custom_user_agent` не задан, библиотека использует стандартный стартовый браузерный `User-Agent`.
|
|
124
|
+
|
|
125
|
+
## User-Agent с данными пользователя
|
|
126
|
+
|
|
127
|
+
Если нужно после авторизации переключиться на `User-Agent` с данными SDK и пользователя, включи `use_user_data_in_user_agent=True`:
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
from itdpy import ITDClient, Config
|
|
131
|
+
|
|
132
|
+
config = Config(
|
|
133
|
+
service="my_app",
|
|
134
|
+
use_user_data_in_user_agent=True,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
client = ITDClient(
|
|
138
|
+
refresh_token="YOUR_REFRESH_TOKEN",
|
|
139
|
+
config=config,
|
|
140
|
+
)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
По умолчанию этот режим выключен.
|
|
144
|
+
|
|
145
|
+
Шаблон можно переопределить через `Config.user_agent_template`:
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
from itdpy import Config
|
|
149
|
+
|
|
150
|
+
config = Config(
|
|
151
|
+
use_user_data_in_user_agent=True,
|
|
152
|
+
user_agent_template="itdpy/{sdk_version} ({parts})",
|
|
153
|
+
)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Доступные поля шаблона:
|
|
157
|
+
|
|
158
|
+
- `{sdk_version}`
|
|
159
|
+
- `{parts}`
|
|
160
|
+
- `{user_id}`
|
|
161
|
+
- `{service}`
|
|
162
|
+
|
|
163
|
+
## Примеры
|
|
164
|
+
|
|
165
|
+
### Получить пост
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
post = client.posts.get("POST_ID")
|
|
169
|
+
print(post.id)
|
|
170
|
+
print(post.to_dict())
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Лента постов
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
posts = client.posts.list(limit=10)
|
|
177
|
+
print(len(posts))
|
|
178
|
+
print(posts[0].id)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Создать пост
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
client.posts.create(
|
|
185
|
+
content="Привет из ITDpy",
|
|
186
|
+
)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Markdown и HTML
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
client.posts.create(
|
|
193
|
+
content="**Жирный** текст",
|
|
194
|
+
parse_md=True,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
client.posts.create(
|
|
198
|
+
content="<b>Жирный</b> текст",
|
|
199
|
+
parse_html=True,
|
|
200
|
+
)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### SSE streaming
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
stream = client.notifications.stream()
|
|
207
|
+
|
|
208
|
+
@stream.on("notification")
|
|
209
|
+
def on_notification(event):
|
|
210
|
+
print(event.data)
|
|
211
|
+
|
|
212
|
+
stream.run()
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### keep_online
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
client.keep_online(
|
|
219
|
+
on_event=lambda event_type, data: print(event_type, data),
|
|
220
|
+
background=True,
|
|
221
|
+
)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Обработка ошибок
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
from itdpy import APIError, NotFoundError, RateLimitError, ValidationError
|
|
228
|
+
|
|
229
|
+
try:
|
|
230
|
+
client.posts.get("invalid")
|
|
231
|
+
except NotFoundError:
|
|
232
|
+
print("Не найдено")
|
|
233
|
+
except ValidationError as e:
|
|
234
|
+
print(e.message)
|
|
235
|
+
except RateLimitError as e:
|
|
236
|
+
print(e.retry_after)
|
|
237
|
+
except APIError as e:
|
|
238
|
+
print(e.message)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Прочее
|
|
242
|
+
|
|
243
|
+
Проект активно развивается. Если у вас есть предложения или pull request, создавайте issue в репозитории.
|
|
244
|
+
Мой телеграм для обратной связи: [@gam5510](https://t.me/gam5510)
|
|
@@ -40,14 +40,7 @@ class ITDClient:
|
|
|
40
40
|
name="refresh_token",
|
|
41
41
|
value=self._refresh_token,
|
|
42
42
|
domain="xn--d1ah4a.com",
|
|
43
|
-
path="/
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
self._request_handler.session.headers.update(
|
|
47
|
-
{
|
|
48
|
-
"Origin": self.config.base_url,
|
|
49
|
-
"Referer": f"{self.config.base_url}/",
|
|
50
|
-
}
|
|
43
|
+
path="/",
|
|
51
44
|
)
|
|
52
45
|
|
|
53
46
|
def _authenticate(self) -> None:
|
|
@@ -73,6 +66,14 @@ class ITDClient:
|
|
|
73
66
|
self._update_user_agent()
|
|
74
67
|
|
|
75
68
|
def _update_user_agent(self) -> None:
|
|
69
|
+
if self.config.custom_user_agent:
|
|
70
|
+
self._request_handler.session.headers["User-Agent"] = self.config.custom_user_agent
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
if not self.config.use_user_data_in_user_agent:
|
|
74
|
+
self._request_handler.session.headers["User-Agent"] = self.config.initial_user_agent
|
|
75
|
+
return
|
|
76
|
+
|
|
76
77
|
parts = [f"platform=python"]
|
|
77
78
|
if self._user_id:
|
|
78
79
|
parts.insert(0, f"userid={self._user_id}")
|
|
@@ -81,7 +82,12 @@ class ITDClient:
|
|
|
81
82
|
if self.config.service:
|
|
82
83
|
parts.append(f"service={self.config.service}")
|
|
83
84
|
|
|
84
|
-
user_agent =
|
|
85
|
+
user_agent = self.config.user_agent_template.format(
|
|
86
|
+
sdk_version=self.config.sdk_version,
|
|
87
|
+
parts="; ".join(parts),
|
|
88
|
+
user_id=self._user_id or "",
|
|
89
|
+
service=self.config.service or "",
|
|
90
|
+
)
|
|
85
91
|
self._request_handler.session.headers["User-Agent"] = user_agent
|
|
86
92
|
|
|
87
93
|
@property
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class Config:
|
|
6
|
+
base_url: str = "https://xn--d1ah4a.com"
|
|
7
|
+
timeout: int = 20
|
|
8
|
+
upload_timeout: int = 120
|
|
9
|
+
max_retries: int = 3
|
|
10
|
+
backoff_factor: float = 1.5
|
|
11
|
+
sdk_version: str = "1.0.2"
|
|
12
|
+
service: str | None = None
|
|
13
|
+
initial_user_agent: str = (
|
|
14
|
+
"Mozilla/5.0 (Linux; Android 11; SM-G991B)"
|
|
15
|
+
"AppleWebKit/537.36 (KHTML, like Gecko)"
|
|
16
|
+
"Chrome/120.0.6099.144 Mobile Safari/537.36"
|
|
17
|
+
)
|
|
18
|
+
custom_user_agent: str | None = None
|
|
19
|
+
user_agent_template: str = "itdpy/{sdk_version} ({parts})"
|
|
20
|
+
use_user_data_in_user_agent: bool = False
|
|
@@ -37,8 +37,22 @@ class RequestHandler:
|
|
|
37
37
|
adapter = HTTPAdapter(max_retries=retry_strategy)
|
|
38
38
|
session.mount("http://", adapter)
|
|
39
39
|
session.mount("https://", adapter)
|
|
40
|
+
|
|
41
|
+
session.headers.update(self._build_default_headers())
|
|
40
42
|
|
|
41
43
|
return session
|
|
44
|
+
|
|
45
|
+
def _build_default_headers(self) -> dict[str, str]:
|
|
46
|
+
return {
|
|
47
|
+
"User-Agent": self._build_initial_user_agent(),
|
|
48
|
+
"Accept": "application/json",
|
|
49
|
+
"Content-Type": "application/json",
|
|
50
|
+
"Origin": self.config.base_url,
|
|
51
|
+
"Referer": f"{self.config.base_url}/",
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
def _build_initial_user_agent(self) -> str:
|
|
55
|
+
return self.config.custom_user_agent or self.config.initial_user_agent
|
|
42
56
|
|
|
43
57
|
def request(
|
|
44
58
|
self,
|
|
@@ -45,6 +45,7 @@ class NotificationStream:
|
|
|
45
45
|
self._stopped = False
|
|
46
46
|
self._last_event_id: str | None = None
|
|
47
47
|
self._handlers: dict[str, list[_HandlerSubscription]] = {}
|
|
48
|
+
self._active_response: requests.Response | None = None
|
|
48
49
|
|
|
49
50
|
def on(
|
|
50
51
|
self,
|
|
@@ -64,17 +65,23 @@ class NotificationStream:
|
|
|
64
65
|
|
|
65
66
|
def stop(self) -> None:
|
|
66
67
|
self._stopped = True
|
|
68
|
+
self._close_active_response()
|
|
67
69
|
|
|
68
70
|
def run(self) -> None:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
try:
|
|
72
|
+
for _ in self:
|
|
73
|
+
pass
|
|
74
|
+
except KeyboardInterrupt:
|
|
75
|
+
self.stop()
|
|
71
76
|
|
|
72
77
|
def __iter__(self) -> Iterator[StreamEvent]:
|
|
73
78
|
backoff = 1
|
|
74
79
|
|
|
75
80
|
while not self._stopped:
|
|
81
|
+
response: requests.Response | None = None
|
|
76
82
|
try:
|
|
77
83
|
response = self._connect()
|
|
84
|
+
self._active_response = response
|
|
78
85
|
backoff = 1
|
|
79
86
|
|
|
80
87
|
client = self._create_sse_client(response)
|
|
@@ -89,7 +96,9 @@ class NotificationStream:
|
|
|
89
96
|
|
|
90
97
|
self._dispatch(parsed_event)
|
|
91
98
|
yield parsed_event
|
|
92
|
-
|
|
99
|
+
except KeyboardInterrupt:
|
|
100
|
+
self.stop()
|
|
101
|
+
return
|
|
93
102
|
except requests.RequestException as exc:
|
|
94
103
|
error_event = StreamEvent(event="error", data={"message": str(exc)})
|
|
95
104
|
self._dispatch(error_event)
|
|
@@ -98,6 +107,11 @@ class NotificationStream:
|
|
|
98
107
|
error_event = StreamEvent(event="error", data={"message": str(exc)})
|
|
99
108
|
self._dispatch(error_event)
|
|
100
109
|
yield error_event
|
|
110
|
+
finally:
|
|
111
|
+
if response is not None:
|
|
112
|
+
response.close()
|
|
113
|
+
if self._active_response is response:
|
|
114
|
+
self._active_response = None
|
|
101
115
|
|
|
102
116
|
if self._stopped:
|
|
103
117
|
break
|
|
@@ -105,7 +119,11 @@ class NotificationStream:
|
|
|
105
119
|
reconnect_event = StreamEvent(event="reconnecting", data={"delay": backoff})
|
|
106
120
|
self._dispatch(reconnect_event)
|
|
107
121
|
yield reconnect_event
|
|
108
|
-
|
|
122
|
+
try:
|
|
123
|
+
time.sleep(backoff)
|
|
124
|
+
except KeyboardInterrupt:
|
|
125
|
+
self.stop()
|
|
126
|
+
break
|
|
109
127
|
backoff = min(self._next_backoff(backoff), self._max_backoff)
|
|
110
128
|
|
|
111
129
|
def _connect(self) -> requests.Response:
|
|
@@ -189,3 +207,12 @@ class NotificationStream:
|
|
|
189
207
|
if current < 5:
|
|
190
208
|
return 5
|
|
191
209
|
return current * 2
|
|
210
|
+
|
|
211
|
+
def _close_active_response(self) -> None:
|
|
212
|
+
if self._active_response is None:
|
|
213
|
+
return
|
|
214
|
+
|
|
215
|
+
try:
|
|
216
|
+
self._active_response.close()
|
|
217
|
+
finally:
|
|
218
|
+
self._active_response = None
|