multibotkit 0.1.34__py3-none-any.whl → 0.2.1__py3-none-any.whl
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.
- multibotkit/dispatchers/yandexmessenger.py +87 -0
- multibotkit/helpers/fb.py +4 -4
- multibotkit/helpers/telegram.py +84 -83
- multibotkit/helpers/viber.py +4 -4
- multibotkit/helpers/vk.py +2 -2
- multibotkit/helpers/yandexmessenger.py +410 -0
- multibotkit/schemas/telegram/incoming.py +13 -17
- multibotkit/schemas/yandexmessenger/__init__.py +0 -0
- multibotkit/schemas/yandexmessenger/incoming.py +77 -0
- multibotkit/schemas/yandexmessenger/outgoing.py +126 -0
- {multibotkit-0.1.34.dist-info → multibotkit-0.2.1.dist-info}/METADATA +19 -8
- {multibotkit-0.1.34.dist-info → multibotkit-0.2.1.dist-info}/RECORD +15 -10
- {multibotkit-0.1.34.dist-info → multibotkit-0.2.1.dist-info}/WHEEL +1 -1
- {multibotkit-0.1.34.dist-info → multibotkit-0.2.1.dist-info/licenses}/LICENSE +0 -0
- {multibotkit-0.1.34.dist-info → multibotkit-0.2.1.dist-info}/top_level.txt +0 -0
multibotkit/helpers/viber.py
CHANGED
|
@@ -179,14 +179,14 @@ are required"
|
|
|
179
179
|
|
|
180
180
|
def sync_set_webhook(self, webhook_data: SetWebhook):
|
|
181
181
|
url = self.VIBER_BASE_URL + "set_webhook"
|
|
182
|
-
data = webhook_data.
|
|
182
|
+
data = webhook_data.model_dump(exclude_none=True)
|
|
183
183
|
data["auth_token"] = self.token
|
|
184
184
|
r = self._perform_sync_request(url=url, data=data)
|
|
185
185
|
return r
|
|
186
186
|
|
|
187
187
|
async def async_set_webhook(self, webhook_data: SetWebhook):
|
|
188
188
|
url = self.VIBER_BASE_URL + "set_webhook"
|
|
189
|
-
data = webhook_data.
|
|
189
|
+
data = webhook_data.model_dump(exclude_none=True)
|
|
190
190
|
data["auth_token"] = self.token
|
|
191
191
|
r = await self._perform_async_request(url=url, data=data)
|
|
192
192
|
return r
|
|
@@ -238,7 +238,7 @@ are required"
|
|
|
238
238
|
)
|
|
239
239
|
|
|
240
240
|
url = self.VIBER_BASE_URL + "send_message"
|
|
241
|
-
data = message.
|
|
241
|
+
data = message.model_dump(exclude_none=True)
|
|
242
242
|
data["auth_token"] = self.token
|
|
243
243
|
r = self._perform_sync_request(url=url, data=data)
|
|
244
244
|
return r
|
|
@@ -280,7 +280,7 @@ are required"
|
|
|
280
280
|
)
|
|
281
281
|
|
|
282
282
|
url = self.VIBER_BASE_URL + "send_message"
|
|
283
|
-
data = message.
|
|
283
|
+
data = message.model_dump(exclude_none=True)
|
|
284
284
|
data["auth_token"] = self.token
|
|
285
285
|
r = await self._perform_async_request(url=url, data=data)
|
|
286
286
|
return r
|
multibotkit/helpers/vk.py
CHANGED
|
@@ -67,7 +67,7 @@ class VKHelper(BaseHelper):
|
|
|
67
67
|
template=template,
|
|
68
68
|
)
|
|
69
69
|
|
|
70
|
-
data = message.
|
|
70
|
+
data = message.model_dump(exclude_none=True)
|
|
71
71
|
if data.get("keyboard"):
|
|
72
72
|
data["keyboard"] = json.dumps(data["keyboard"], ensure_ascii=False)
|
|
73
73
|
if data.get("template"):
|
|
@@ -101,7 +101,7 @@ but not both"
|
|
|
101
101
|
attachment=attachment,
|
|
102
102
|
template=template,
|
|
103
103
|
)
|
|
104
|
-
data = message.
|
|
104
|
+
data = message.model_dump(exclude_none=True)
|
|
105
105
|
if data.get("keyboard"):
|
|
106
106
|
data["keyboard"] = json.dumps(data["keyboard"], ensure_ascii=False)
|
|
107
107
|
if data.get("template"):
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
from json import JSONDecodeError
|
|
2
|
+
from typing import IO, List, Optional, Union
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
from tenacity import (
|
|
6
|
+
retry,
|
|
7
|
+
retry_if_exception_type,
|
|
8
|
+
stop_after_attempt,
|
|
9
|
+
wait_exponential,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
from multibotkit.helpers.base_helper import BaseHelper
|
|
13
|
+
from multibotkit.schemas.yandexmessenger.incoming import Update
|
|
14
|
+
from multibotkit.schemas.yandexmessenger.outgoing import (
|
|
15
|
+
GetUpdatesParams,
|
|
16
|
+
InlineKeyboard,
|
|
17
|
+
SendFileParams,
|
|
18
|
+
SendImageParams,
|
|
19
|
+
SendTextParams,
|
|
20
|
+
SetWebhookParams,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class YandexMessengerHelper(BaseHelper):
|
|
25
|
+
"""
|
|
26
|
+
Sync и async функции для Yandex Messenger Bot API.
|
|
27
|
+
|
|
28
|
+
Базовый URL: https://botapi.messenger.yandex.net/bot/v1/
|
|
29
|
+
Авторизация: Authorization: OAuth <token>
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, token: str):
|
|
33
|
+
self.token = token
|
|
34
|
+
self.base_url = "https://botapi.messenger.yandex.net/bot/v1/"
|
|
35
|
+
self.headers = {"Authorization": f"OAuth {self.token}"}
|
|
36
|
+
|
|
37
|
+
@retry(
|
|
38
|
+
retry=retry_if_exception_type(httpx.HTTPError)
|
|
39
|
+
| retry_if_exception_type(JSONDecodeError),
|
|
40
|
+
reraise=True,
|
|
41
|
+
stop=stop_after_attempt(5),
|
|
42
|
+
wait=wait_exponential(multiplier=1, min=4, max=10),
|
|
43
|
+
)
|
|
44
|
+
def _perform_sync_request(
|
|
45
|
+
self,
|
|
46
|
+
url: str,
|
|
47
|
+
data: Optional[dict] = None,
|
|
48
|
+
use_json: bool = True,
|
|
49
|
+
files: Optional[dict] = None,
|
|
50
|
+
):
|
|
51
|
+
"""Переопределение для добавления OAuth заголовка"""
|
|
52
|
+
if use_json:
|
|
53
|
+
r = httpx.post(url=url, json=data, headers=self.headers)
|
|
54
|
+
else:
|
|
55
|
+
r = httpx.post(url=url, data=data, files=files, headers=self.headers)
|
|
56
|
+
return r.json()
|
|
57
|
+
|
|
58
|
+
@retry(
|
|
59
|
+
retry=retry_if_exception_type(httpx.HTTPError)
|
|
60
|
+
| retry_if_exception_type(JSONDecodeError),
|
|
61
|
+
reraise=True,
|
|
62
|
+
stop=stop_after_attempt(5),
|
|
63
|
+
wait=wait_exponential(multiplier=1, min=4, max=10),
|
|
64
|
+
)
|
|
65
|
+
async def _perform_async_request(
|
|
66
|
+
self,
|
|
67
|
+
url: str,
|
|
68
|
+
data: Optional[dict] = None,
|
|
69
|
+
use_json: bool = True,
|
|
70
|
+
files: Optional[dict] = None,
|
|
71
|
+
):
|
|
72
|
+
"""Переопределение для добавления OAuth заголовка"""
|
|
73
|
+
async with httpx.AsyncClient() as client:
|
|
74
|
+
if use_json:
|
|
75
|
+
r = await client.post(url=url, json=data, headers=self.headers)
|
|
76
|
+
else:
|
|
77
|
+
r = await client.post(
|
|
78
|
+
url=url, data=data, files=files, headers=self.headers
|
|
79
|
+
)
|
|
80
|
+
return r.json()
|
|
81
|
+
|
|
82
|
+
def sync_send_text(
|
|
83
|
+
self,
|
|
84
|
+
text: str,
|
|
85
|
+
chat_id: Optional[str] = None,
|
|
86
|
+
login: Optional[str] = None,
|
|
87
|
+
payload_id: Optional[str] = None,
|
|
88
|
+
reply_message_id: Optional[int] = None,
|
|
89
|
+
disable_notification: Optional[bool] = None,
|
|
90
|
+
important: Optional[bool] = None,
|
|
91
|
+
disable_web_page_preview: Optional[bool] = None,
|
|
92
|
+
thread_id: Optional[int] = None,
|
|
93
|
+
inline_keyboard: Optional[InlineKeyboard] = None,
|
|
94
|
+
) -> dict:
|
|
95
|
+
"""
|
|
96
|
+
Синхронная отправка текстового сообщения.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
text: Текст сообщения (до 6000 символов)
|
|
100
|
+
chat_id: ID чата (для group/channel)
|
|
101
|
+
login: Login пользователя (для private)
|
|
102
|
+
payload_id: Уникальный ID для дедупликации
|
|
103
|
+
reply_message_id: ID сообщения для ответа
|
|
104
|
+
disable_notification: Отключить уведомление
|
|
105
|
+
important: Отметить как важное
|
|
106
|
+
disable_web_page_preview: Отключить превью ссылок
|
|
107
|
+
thread_id: ID треда
|
|
108
|
+
inline_keyboard: Inline клавиатура
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
dict с полями {"ok": true, "message_id": integer}
|
|
112
|
+
"""
|
|
113
|
+
url = self.base_url + "messages/sendText/"
|
|
114
|
+
params = SendTextParams(
|
|
115
|
+
text=text,
|
|
116
|
+
chat_id=chat_id,
|
|
117
|
+
login=login,
|
|
118
|
+
payload_id=payload_id,
|
|
119
|
+
reply_message_id=reply_message_id,
|
|
120
|
+
disable_notification=disable_notification,
|
|
121
|
+
important=important,
|
|
122
|
+
disable_web_page_preview=disable_web_page_preview,
|
|
123
|
+
thread_id=thread_id,
|
|
124
|
+
inline_keyboard=inline_keyboard,
|
|
125
|
+
)
|
|
126
|
+
data = params.model_dump(exclude_none=True)
|
|
127
|
+
|
|
128
|
+
# Сериализация inline_keyboard если есть
|
|
129
|
+
if inline_keyboard:
|
|
130
|
+
data["inline_keyboard"] = inline_keyboard.model_dump(exclude_none=True)[
|
|
131
|
+
"buttons"
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
return self._perform_sync_request(url, data)
|
|
135
|
+
|
|
136
|
+
async def async_send_text(
|
|
137
|
+
self,
|
|
138
|
+
text: str,
|
|
139
|
+
chat_id: Optional[str] = None,
|
|
140
|
+
login: Optional[str] = None,
|
|
141
|
+
payload_id: Optional[str] = None,
|
|
142
|
+
reply_message_id: Optional[int] = None,
|
|
143
|
+
disable_notification: Optional[bool] = None,
|
|
144
|
+
important: Optional[bool] = None,
|
|
145
|
+
disable_web_page_preview: Optional[bool] = None,
|
|
146
|
+
thread_id: Optional[int] = None,
|
|
147
|
+
inline_keyboard: Optional[InlineKeyboard] = None,
|
|
148
|
+
) -> dict:
|
|
149
|
+
"""Асинхронная версия sync_send_text"""
|
|
150
|
+
url = self.base_url + "messages/sendText/"
|
|
151
|
+
params = SendTextParams(
|
|
152
|
+
text=text,
|
|
153
|
+
chat_id=chat_id,
|
|
154
|
+
login=login,
|
|
155
|
+
payload_id=payload_id,
|
|
156
|
+
reply_message_id=reply_message_id,
|
|
157
|
+
disable_notification=disable_notification,
|
|
158
|
+
important=important,
|
|
159
|
+
disable_web_page_preview=disable_web_page_preview,
|
|
160
|
+
thread_id=thread_id,
|
|
161
|
+
inline_keyboard=inline_keyboard,
|
|
162
|
+
)
|
|
163
|
+
data = params.model_dump(exclude_none=True)
|
|
164
|
+
|
|
165
|
+
if inline_keyboard:
|
|
166
|
+
data["inline_keyboard"] = inline_keyboard.model_dump(exclude_none=True)[
|
|
167
|
+
"buttons"
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
return await self._perform_async_request(url, data)
|
|
171
|
+
|
|
172
|
+
def sync_send_image(
|
|
173
|
+
self,
|
|
174
|
+
image: Union[str, IO],
|
|
175
|
+
chat_id: Optional[str] = None,
|
|
176
|
+
login: Optional[str] = None,
|
|
177
|
+
thread_id: Optional[int] = None,
|
|
178
|
+
) -> dict:
|
|
179
|
+
"""
|
|
180
|
+
Синхронная отправка изображения.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
image: Путь к файлу, file_id или IO объект
|
|
184
|
+
chat_id: ID чата (для group/channel)
|
|
185
|
+
login: Login пользователя (для private)
|
|
186
|
+
thread_id: ID треда
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
dict с полями {"ok": true, "message_id": integer}
|
|
190
|
+
"""
|
|
191
|
+
url = self.base_url + "messages/sendImage/"
|
|
192
|
+
params = SendImageParams(
|
|
193
|
+
chat_id=chat_id,
|
|
194
|
+
login=login,
|
|
195
|
+
thread_id=thread_id,
|
|
196
|
+
)
|
|
197
|
+
data = params.model_dump(exclude_none=True)
|
|
198
|
+
|
|
199
|
+
# Обработка различных типов image
|
|
200
|
+
if isinstance(image, str):
|
|
201
|
+
# Если это путь к файлу с расширением
|
|
202
|
+
if image.endswith((".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp")):
|
|
203
|
+
with open(image, "rb") as f:
|
|
204
|
+
files = {"image": f}
|
|
205
|
+
return self._perform_sync_request(
|
|
206
|
+
url, data, use_json=False, files=files
|
|
207
|
+
)
|
|
208
|
+
else:
|
|
209
|
+
# Если это file_id - отправляем как JSON
|
|
210
|
+
data["image"] = image
|
|
211
|
+
return self._perform_sync_request(url, data)
|
|
212
|
+
else:
|
|
213
|
+
# Если это IO объект
|
|
214
|
+
files = {"image": image}
|
|
215
|
+
return self._perform_sync_request(url, data, use_json=False, files=files)
|
|
216
|
+
|
|
217
|
+
async def async_send_image(
|
|
218
|
+
self,
|
|
219
|
+
image: Union[str, IO],
|
|
220
|
+
chat_id: Optional[str] = None,
|
|
221
|
+
login: Optional[str] = None,
|
|
222
|
+
thread_id: Optional[int] = None,
|
|
223
|
+
) -> dict:
|
|
224
|
+
"""Асинхронная версия sync_send_image"""
|
|
225
|
+
url = self.base_url + "messages/sendImage/"
|
|
226
|
+
params = SendImageParams(
|
|
227
|
+
chat_id=chat_id,
|
|
228
|
+
login=login,
|
|
229
|
+
thread_id=thread_id,
|
|
230
|
+
)
|
|
231
|
+
data = params.model_dump(exclude_none=True)
|
|
232
|
+
|
|
233
|
+
if isinstance(image, str):
|
|
234
|
+
if image.endswith((".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp")):
|
|
235
|
+
with open(image, "rb") as f:
|
|
236
|
+
content = f.read()
|
|
237
|
+
files = {"image": content}
|
|
238
|
+
return await self._perform_async_request(
|
|
239
|
+
url, data, use_json=False, files=files
|
|
240
|
+
)
|
|
241
|
+
else:
|
|
242
|
+
data["image"] = image
|
|
243
|
+
return await self._perform_async_request(url, data)
|
|
244
|
+
else:
|
|
245
|
+
files = {"image": image}
|
|
246
|
+
return await self._perform_async_request(
|
|
247
|
+
url, data, use_json=False, files=files
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
def sync_send_file(
|
|
251
|
+
self,
|
|
252
|
+
document: Union[str, IO],
|
|
253
|
+
filename: Optional[str] = None,
|
|
254
|
+
chat_id: Optional[str] = None,
|
|
255
|
+
login: Optional[str] = None,
|
|
256
|
+
thread_id: Optional[int] = None,
|
|
257
|
+
) -> dict:
|
|
258
|
+
"""
|
|
259
|
+
Синхронная отправка файла.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
document: Путь к файлу, file_id или IO объект
|
|
263
|
+
filename: Имя файла (для IO объектов)
|
|
264
|
+
chat_id: ID чата (для group/channel)
|
|
265
|
+
login: Login пользователя (для private)
|
|
266
|
+
thread_id: ID треда
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
dict с полями {"ok": true, "message_id": integer}
|
|
270
|
+
"""
|
|
271
|
+
url = self.base_url + "messages/sendFile/"
|
|
272
|
+
params = SendFileParams(
|
|
273
|
+
chat_id=chat_id,
|
|
274
|
+
login=login,
|
|
275
|
+
thread_id=thread_id,
|
|
276
|
+
)
|
|
277
|
+
data = params.model_dump(exclude_none=True)
|
|
278
|
+
|
|
279
|
+
if isinstance(document, str):
|
|
280
|
+
# Если это путь к файлу
|
|
281
|
+
if not document.startswith(("http://", "https://")):
|
|
282
|
+
# Локальный файл
|
|
283
|
+
fname = filename or document.split("/")[-1]
|
|
284
|
+
with open(document, "rb") as f:
|
|
285
|
+
files = {"document": (fname, f)}
|
|
286
|
+
return self._perform_sync_request(
|
|
287
|
+
url, data, use_json=False, files=files
|
|
288
|
+
)
|
|
289
|
+
else:
|
|
290
|
+
# file_id или URL
|
|
291
|
+
data["document"] = document
|
|
292
|
+
return self._perform_sync_request(url, data)
|
|
293
|
+
else:
|
|
294
|
+
# IO объект
|
|
295
|
+
fname = filename or "file"
|
|
296
|
+
files = {"document": (fname, document)}
|
|
297
|
+
return self._perform_sync_request(url, data, use_json=False, files=files)
|
|
298
|
+
|
|
299
|
+
async def async_send_file(
|
|
300
|
+
self,
|
|
301
|
+
document: Union[str, IO],
|
|
302
|
+
filename: Optional[str] = None,
|
|
303
|
+
chat_id: Optional[str] = None,
|
|
304
|
+
login: Optional[str] = None,
|
|
305
|
+
thread_id: Optional[int] = None,
|
|
306
|
+
) -> dict:
|
|
307
|
+
"""Асинхронная версия sync_send_file"""
|
|
308
|
+
url = self.base_url + "messages/sendFile/"
|
|
309
|
+
params = SendFileParams(
|
|
310
|
+
chat_id=chat_id,
|
|
311
|
+
login=login,
|
|
312
|
+
thread_id=thread_id,
|
|
313
|
+
)
|
|
314
|
+
data = params.model_dump(exclude_none=True)
|
|
315
|
+
|
|
316
|
+
if isinstance(document, str):
|
|
317
|
+
if not document.startswith(("http://", "https://")):
|
|
318
|
+
fname = filename or document.split("/")[-1]
|
|
319
|
+
with open(document, "rb") as f:
|
|
320
|
+
content = f.read()
|
|
321
|
+
files = {"document": (fname, content)}
|
|
322
|
+
return await self._perform_async_request(
|
|
323
|
+
url, data, use_json=False, files=files
|
|
324
|
+
)
|
|
325
|
+
else:
|
|
326
|
+
data["document"] = document
|
|
327
|
+
return await self._perform_async_request(url, data)
|
|
328
|
+
else:
|
|
329
|
+
fname = filename or "file"
|
|
330
|
+
files = {"document": (fname, document)}
|
|
331
|
+
return await self._perform_async_request(
|
|
332
|
+
url, data, use_json=False, files=files
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
def sync_get_updates(
|
|
336
|
+
self,
|
|
337
|
+
limit: Optional[int] = 100,
|
|
338
|
+
offset: Optional[int] = 0,
|
|
339
|
+
) -> dict:
|
|
340
|
+
"""
|
|
341
|
+
Синхронное получение обновлений (long polling).
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
limit: Количество обновлений (1-1000, по умолчанию 100)
|
|
345
|
+
offset: Смещение для пагинации
|
|
346
|
+
|
|
347
|
+
Returns:
|
|
348
|
+
dict с полями {"ok": true, "updates": [...]}
|
|
349
|
+
"""
|
|
350
|
+
url = self.base_url + "messages/getUpdates/"
|
|
351
|
+
params = GetUpdatesParams(limit=limit, offset=offset)
|
|
352
|
+
data = params.model_dump(exclude_none=True)
|
|
353
|
+
|
|
354
|
+
return self._perform_sync_request(url, data)
|
|
355
|
+
|
|
356
|
+
async def async_get_updates(
|
|
357
|
+
self,
|
|
358
|
+
limit: Optional[int] = 100,
|
|
359
|
+
offset: Optional[int] = 0,
|
|
360
|
+
) -> dict:
|
|
361
|
+
"""Асинхронная версия sync_get_updates"""
|
|
362
|
+
url = self.base_url + "messages/getUpdates/"
|
|
363
|
+
params = GetUpdatesParams(limit=limit, offset=offset)
|
|
364
|
+
data = params.model_dump(exclude_none=True)
|
|
365
|
+
|
|
366
|
+
return await self._perform_async_request(url, data)
|
|
367
|
+
|
|
368
|
+
def sync_set_webhook(
|
|
369
|
+
self,
|
|
370
|
+
webhook_url: Optional[str] = None,
|
|
371
|
+
) -> dict:
|
|
372
|
+
"""
|
|
373
|
+
Синхронная установка webhook.
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
webhook_url: URL для webhook (None для отключения)
|
|
377
|
+
|
|
378
|
+
Returns:
|
|
379
|
+
dict с результатом операции
|
|
380
|
+
"""
|
|
381
|
+
url = self.base_url + "self/update/"
|
|
382
|
+
params = SetWebhookParams(webhook_url=webhook_url)
|
|
383
|
+
data = params.model_dump(exclude_none=True)
|
|
384
|
+
|
|
385
|
+
return self._perform_sync_request(url, data)
|
|
386
|
+
|
|
387
|
+
async def async_set_webhook(
|
|
388
|
+
self,
|
|
389
|
+
webhook_url: Optional[str] = None,
|
|
390
|
+
) -> dict:
|
|
391
|
+
"""Асинхронная версия sync_set_webhook"""
|
|
392
|
+
url = self.base_url + "self/update/"
|
|
393
|
+
params = SetWebhookParams(webhook_url=webhook_url)
|
|
394
|
+
data = params.model_dump(exclude_none=True)
|
|
395
|
+
|
|
396
|
+
return await self._perform_async_request(url, data)
|
|
397
|
+
|
|
398
|
+
def parse_updates(self, response: dict) -> List[Update]:
|
|
399
|
+
"""
|
|
400
|
+
Парсинг ответа getUpdates в список Update объектов.
|
|
401
|
+
|
|
402
|
+
Args:
|
|
403
|
+
response: dict ответ от getUpdates
|
|
404
|
+
|
|
405
|
+
Returns:
|
|
406
|
+
List[Update]
|
|
407
|
+
"""
|
|
408
|
+
if response.get("ok") and "updates" in response:
|
|
409
|
+
return [Update.model_validate(update) for update in response["updates"]]
|
|
410
|
+
return []
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
2
|
from typing import Optional, List
|
|
3
3
|
|
|
4
|
-
from pydantic import Field
|
|
4
|
+
from pydantic import ConfigDict, Field
|
|
5
5
|
from pydantic.main import BaseModel
|
|
6
6
|
|
|
7
7
|
|
|
@@ -237,10 +237,12 @@ class WebAppData(BaseModel):
|
|
|
237
237
|
|
|
238
238
|
|
|
239
239
|
class Message(BaseModel):
|
|
240
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
241
|
+
|
|
240
242
|
message_id: int = Field(..., title="Unique message identifier inside this chat")
|
|
241
243
|
date: int = Field(..., title="Date the message was sent in Unix time")
|
|
242
244
|
from_: Optional[User] = Field(
|
|
243
|
-
None, title="Sender, empty for messages sent to channels"
|
|
245
|
+
None, title="Sender, empty for messages sent to channels", alias="from"
|
|
244
246
|
)
|
|
245
247
|
chat: Optional[Chat] = Field(None, title="Conversation the message belongs to")
|
|
246
248
|
text: Optional[str] = Field(None, title="The actual UTF-8 text of the message")
|
|
@@ -284,13 +286,12 @@ the location",
|
|
|
284
286
|
None, title="Service message: data sent by a Web App"
|
|
285
287
|
)
|
|
286
288
|
|
|
287
|
-
class Config:
|
|
288
|
-
fields = {"from_": "from"}
|
|
289
|
-
|
|
290
289
|
|
|
291
290
|
class CallbackQuery(BaseModel):
|
|
291
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
292
|
+
|
|
292
293
|
id: str = Field(..., title="Unique identifier for this query")
|
|
293
|
-
from_: Optional[User] = Field(None, title="Sender data")
|
|
294
|
+
from_: Optional[User] = Field(None, title="Sender data", alias="from")
|
|
294
295
|
message: Optional[Message] = Field(
|
|
295
296
|
None, title="Message with the callback button that originated the query"
|
|
296
297
|
)
|
|
@@ -311,9 +312,6 @@ which the message with the callback button was sent",
|
|
|
311
312
|
unique identifier for the game",
|
|
312
313
|
)
|
|
313
314
|
|
|
314
|
-
class Config:
|
|
315
|
-
fields = {"from_": "from"}
|
|
316
|
-
|
|
317
315
|
|
|
318
316
|
class ChatMember(BaseModel):
|
|
319
317
|
status: str = Field(..., title="The member's status in the chat")
|
|
@@ -368,9 +366,11 @@ class ChatInviteLink(BaseModel):
|
|
|
368
366
|
|
|
369
367
|
|
|
370
368
|
class ChatMemberUpdated(BaseModel):
|
|
369
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
370
|
+
|
|
371
371
|
date: int = Field(..., title="Date the change was done in Unix time")
|
|
372
372
|
from_: User = Field(
|
|
373
|
-
..., title="Performer of the action, which resulted in the change"
|
|
373
|
+
..., title="Performer of the action, which resulted in the change", alias="from"
|
|
374
374
|
)
|
|
375
375
|
chat: Chat = Field(..., title="Chat the user belongs to")
|
|
376
376
|
old_chat_member: ChatMember = Field(
|
|
@@ -393,13 +393,12 @@ class ChatMemberUpdated(BaseModel):
|
|
|
393
393
|
title="Optional. True, if the user joined the chat via a chat folder invite link",
|
|
394
394
|
)
|
|
395
395
|
|
|
396
|
-
class Config:
|
|
397
|
-
fields = {"from_": "from"}
|
|
398
|
-
|
|
399
396
|
|
|
400
397
|
class ChatJoinRequest(BaseModel):
|
|
398
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
399
|
+
|
|
401
400
|
chat: Chat = Field(..., title=" Chat to which the request was sent")
|
|
402
|
-
from_: User = Field(..., title="User that sent the join request")
|
|
401
|
+
from_: User = Field(..., title="User that sent the join request", alias="from")
|
|
403
402
|
user_chat_id: int = Field(
|
|
404
403
|
...,
|
|
405
404
|
title="Identifier of a private chat with the user who sent the join request.",
|
|
@@ -414,9 +413,6 @@ class ChatJoinRequest(BaseModel):
|
|
|
414
413
|
title="Chat invite link that was used by the user to send the join request",
|
|
415
414
|
)
|
|
416
415
|
|
|
417
|
-
class Config:
|
|
418
|
-
fields = {"from_": "from"}
|
|
419
|
-
|
|
420
416
|
|
|
421
417
|
class Update(BaseModel):
|
|
422
418
|
update_id: int = Field(..., title="Id of incoming bot update")
|
|
File without changes
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ChatType(str, Enum):
|
|
8
|
+
"""Типы чатов в Yandex Messenger"""
|
|
9
|
+
|
|
10
|
+
private = "private"
|
|
11
|
+
group = "group"
|
|
12
|
+
channel = "channel"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Sender(BaseModel):
|
|
16
|
+
"""
|
|
17
|
+
Отправитель сообщения.
|
|
18
|
+
Либо login (для пользователей), либо id (для ботов/каналов).
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
login: Optional[str] = Field(None, title="Login пользователя")
|
|
22
|
+
id: Optional[str] = Field(None, title="ID отправителя (для ботов/каналов)")
|
|
23
|
+
display_name: Optional[str] = Field(None, title="Отображаемое имя")
|
|
24
|
+
robot: Optional[bool] = Field(None, title="Является ли отправитель роботом")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Chat(BaseModel):
|
|
28
|
+
"""
|
|
29
|
+
Информация о чате.
|
|
30
|
+
Для private чатов id отсутствует.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
type: ChatType = Field(..., title="Тип чата: private/group/channel")
|
|
34
|
+
id: Optional[str] = Field(
|
|
35
|
+
None, title="ID чата (для group/channel, отсутствует для private)"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Image(BaseModel):
|
|
40
|
+
"""Изображение в сообщении"""
|
|
41
|
+
|
|
42
|
+
file_id: str = Field(..., title="ID файла для повторного использования")
|
|
43
|
+
width: int = Field(..., title="Ширина изображения в пикселях")
|
|
44
|
+
height: int = Field(..., title="Высота изображения в пикселях")
|
|
45
|
+
size: Optional[int] = Field(
|
|
46
|
+
None, title="Размер файла в байтах (опционально для оригиналов)"
|
|
47
|
+
)
|
|
48
|
+
name: Optional[str] = Field(None, title="Имя файла (опционально для оригиналов)")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class File(BaseModel):
|
|
52
|
+
"""Файл в сообщении"""
|
|
53
|
+
|
|
54
|
+
id: str = Field(..., title="ID файла")
|
|
55
|
+
name: str = Field(..., title="Имя файла")
|
|
56
|
+
size: int = Field(..., title="Размер файла в байтах")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class Update(BaseModel):
|
|
60
|
+
"""
|
|
61
|
+
Входящее обновление от Yandex Messenger.
|
|
62
|
+
Соответствует структуре getUpdates response и webhook payload.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
update_id: int = Field(..., title="Уникальный ID обновления")
|
|
66
|
+
message_id: int = Field(..., title="ID сообщения")
|
|
67
|
+
timestamp: int = Field(..., title="Unix timestamp сообщения")
|
|
68
|
+
from_: Sender = Field(..., title="Отправитель сообщения", alias="from")
|
|
69
|
+
chat: Chat = Field(..., title="Информация о чате")
|
|
70
|
+
text: Optional[str] = Field(None, title="Текст сообщения (до 6000 символов)")
|
|
71
|
+
callback_data: Optional[dict] = Field(
|
|
72
|
+
None, title="Данные переданные по нажатию кнопки"
|
|
73
|
+
)
|
|
74
|
+
images: Optional[List[Image]] = Field(None, title="Массив изображений (если есть)")
|
|
75
|
+
file: Optional[File] = Field(None, title="Прикрепленный файл")
|
|
76
|
+
|
|
77
|
+
model_config = ConfigDict(populate_by_name=True)
|