maxibot 0.98.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.
- maxibot/__init__.py +644 -0
- maxibot/apihelper.py +241 -0
- maxibot/core/attachments/photo.py +45 -0
- maxibot/core/network/client.py +118 -0
- maxibot/core/network/polling.py +75 -0
- maxibot/types.py +753 -0
- maxibot/util.py +98 -0
- maxibot-0.98.1.dist-info/METADATA +56 -0
- maxibot-0.98.1.dist-info/RECORD +11 -0
- maxibot-0.98.1.dist-info/WHEEL +5 -0
- maxibot-0.98.1.dist-info/top_level.txt +1 -0
maxibot/__init__.py
ADDED
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
import traceback
|
|
5
|
+
|
|
6
|
+
from typing import Dict, Any, List, Optional, Callable, Union
|
|
7
|
+
|
|
8
|
+
from maxibot.apihelper import Api
|
|
9
|
+
from maxibot.types import Message, CallbackQuery, InputMedia
|
|
10
|
+
from maxibot.types import UpdateType, InlineKeyboardMarkup
|
|
11
|
+
from maxibot.util import extract_command, get_text, get_parce_mode
|
|
12
|
+
from maxibot.core.attachments.photo import Photo
|
|
13
|
+
from maxibot.core.network.polling import Polling
|
|
14
|
+
|
|
15
|
+
HandlerFunc = Callable[[Message], None]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MaxiBot:
|
|
19
|
+
"""
|
|
20
|
+
Главный класс бота
|
|
21
|
+
"""
|
|
22
|
+
def __init__(self, token: str):
|
|
23
|
+
"""
|
|
24
|
+
Метод инициализации бота
|
|
25
|
+
:param token: Токен бота
|
|
26
|
+
:type token: str
|
|
27
|
+
"""
|
|
28
|
+
self.api = Api(token=token)
|
|
29
|
+
self.handlers = {
|
|
30
|
+
"update": [], # Общие обработчики для всех типов обновлений
|
|
31
|
+
UpdateType.MESSAGE_CREATED: [],
|
|
32
|
+
UpdateType.MESSAGE_CALLBACK: [],
|
|
33
|
+
UpdateType.BOT_STARTED: [],
|
|
34
|
+
UpdateType.MESSAGE_EDITED: [],
|
|
35
|
+
UpdateType.MESSAGE_DELETED: [],
|
|
36
|
+
UpdateType.MESSAGE_CHAT_CREATED: [],
|
|
37
|
+
}
|
|
38
|
+
self.message_handlers = []
|
|
39
|
+
self.callback_query_handlers = []
|
|
40
|
+
self.poll = None
|
|
41
|
+
self.is_running = False
|
|
42
|
+
self.count_retries = 10
|
|
43
|
+
|
|
44
|
+
@staticmethod
|
|
45
|
+
def _build_handler_dict(handler: HandlerFunc, **filters):
|
|
46
|
+
"""
|
|
47
|
+
Функция, которая формирует словарь для добавления в список обработчиков событий (handler)
|
|
48
|
+
|
|
49
|
+
:param handler: Description
|
|
50
|
+
:type handler: HandlerFunc
|
|
51
|
+
:param filters: Description
|
|
52
|
+
"""
|
|
53
|
+
return {
|
|
54
|
+
'function': handler,
|
|
55
|
+
'filters': {ftype: fvalue for ftype, fvalue in filters.items() if fvalue is not None}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
def polling(self, allowed_updates: Optional[List[str]] = None):
|
|
59
|
+
"""
|
|
60
|
+
Функция, которая запускает корутину
|
|
61
|
+
"""
|
|
62
|
+
asyncio.run(self.start(allowed_updates=allowed_updates))
|
|
63
|
+
|
|
64
|
+
def stop(self):
|
|
65
|
+
"""
|
|
66
|
+
Метод останавливает поллинг бота
|
|
67
|
+
"""
|
|
68
|
+
if not self.is_running:
|
|
69
|
+
print("Bot is not running")
|
|
70
|
+
return None
|
|
71
|
+
if self.poll:
|
|
72
|
+
self.poll.stop()
|
|
73
|
+
self.is_running = False
|
|
74
|
+
|
|
75
|
+
async def start(self, allowed_updates: Optional[List[str]] = None):
|
|
76
|
+
"""
|
|
77
|
+
Метод запускает получение обновлений по боту
|
|
78
|
+
|
|
79
|
+
:param allowed_updates: Description
|
|
80
|
+
:type allowed_updates: Optional[List[str]]
|
|
81
|
+
"""
|
|
82
|
+
if self.is_running:
|
|
83
|
+
print("Bot is already running")
|
|
84
|
+
return None
|
|
85
|
+
self.is_running = True
|
|
86
|
+
self.poll = Polling(api=self.api, allowed_updates=allowed_updates)
|
|
87
|
+
await self.poll.loop(self._process_update)
|
|
88
|
+
|
|
89
|
+
# def on(self, update_type: str):
|
|
90
|
+
# """
|
|
91
|
+
# Декоратор для регистрации обработчика определенного типа обновлений
|
|
92
|
+
|
|
93
|
+
# :param update_type: Тип обновления (см. UpdateType)
|
|
94
|
+
# """
|
|
95
|
+
# def decorator(func: HandlerFunc):
|
|
96
|
+
# self.handlers.setdefault(update_type, []).append(func)
|
|
97
|
+
# return func
|
|
98
|
+
# return decorator
|
|
99
|
+
|
|
100
|
+
def message_handler(
|
|
101
|
+
self,
|
|
102
|
+
commands: Optional[List[str]] = None,
|
|
103
|
+
regexp: Optional[str] = None,
|
|
104
|
+
func: Optional[Callable] = None,
|
|
105
|
+
content_types: Optional[List[str]] = None,
|
|
106
|
+
):
|
|
107
|
+
"""
|
|
108
|
+
Декоратор для регистрации обработчика текстовых сообщений по шаблону
|
|
109
|
+
|
|
110
|
+
:param pattern: Шаблон текста (точное совпадение или регулярное выражение)
|
|
111
|
+
:type pattern: str
|
|
112
|
+
"""
|
|
113
|
+
def decorator(funcs: HandlerFunc):
|
|
114
|
+
handler_dict = self._build_handler_dict(
|
|
115
|
+
funcs,
|
|
116
|
+
commands=commands,
|
|
117
|
+
regexp=regexp,
|
|
118
|
+
func=func,
|
|
119
|
+
content_types=content_types
|
|
120
|
+
)
|
|
121
|
+
self.message_handlers.append(handler_dict)
|
|
122
|
+
return funcs
|
|
123
|
+
return decorator
|
|
124
|
+
|
|
125
|
+
def run_handler(self, context: Message, message_handlers: List[Dict]):
|
|
126
|
+
"""
|
|
127
|
+
Метод запуска обработчиков событий текстового сообщения
|
|
128
|
+
|
|
129
|
+
:param context: Description
|
|
130
|
+
:type context: Context
|
|
131
|
+
"""
|
|
132
|
+
for handler in message_handlers:
|
|
133
|
+
if self._check_filters(context=context, handler=handler):
|
|
134
|
+
handler.get("function")(context)
|
|
135
|
+
break
|
|
136
|
+
|
|
137
|
+
def _test_filter(self, message_filter: str, filter_value: List, context: Message):
|
|
138
|
+
"""
|
|
139
|
+
Метод проверки соответствия сообщения всем фильтрам текстовых сообщений
|
|
140
|
+
|
|
141
|
+
:param message_filter: Description
|
|
142
|
+
:type message_filter: str
|
|
143
|
+
:param filter_value: Description
|
|
144
|
+
:type filter_value: List
|
|
145
|
+
:param context: Description
|
|
146
|
+
:type context: Context
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
text = context.text
|
|
150
|
+
if message_filter == 'content_types':
|
|
151
|
+
return context.content_type in filter_value
|
|
152
|
+
if message_filter == 'regexp':
|
|
153
|
+
return re.search(filter_value, text, re.IGNORECASE)
|
|
154
|
+
elif message_filter == 'commands':
|
|
155
|
+
return extract_command(text) in filter_value
|
|
156
|
+
# elif message_filter == 'chat_types':
|
|
157
|
+
# return context.chat.type in filter_value
|
|
158
|
+
elif message_filter == 'func':
|
|
159
|
+
# print("FUUUUUUUUUUUUUUUUUUUUUUUUNCCCCCCCCCCCCCCCCC")
|
|
160
|
+
return filter_value(context)
|
|
161
|
+
return False
|
|
162
|
+
|
|
163
|
+
def _check_filters(self, context, handler: Dict):
|
|
164
|
+
"""
|
|
165
|
+
Проверка текстового сообщения на фильтры
|
|
166
|
+
|
|
167
|
+
:param context: Сообщение
|
|
168
|
+
:type context: Context
|
|
169
|
+
"""
|
|
170
|
+
if handler['filters']:
|
|
171
|
+
if isinstance(context, CallbackQuery):
|
|
172
|
+
# Сначала проверяем фильтр по data
|
|
173
|
+
if 'data' in handler['filters']:
|
|
174
|
+
filter_data = handler['filters']['data']
|
|
175
|
+
if context.data != filter_data:
|
|
176
|
+
return False
|
|
177
|
+
func_filter = handler['filters'].get('func')
|
|
178
|
+
if func_filter:
|
|
179
|
+
try:
|
|
180
|
+
return func_filter(context)
|
|
181
|
+
except Exception as e:
|
|
182
|
+
print(f"Error in filter function: {e}")
|
|
183
|
+
return False
|
|
184
|
+
|
|
185
|
+
return True
|
|
186
|
+
elif isinstance(context, Message):
|
|
187
|
+
for message_filter, filter_value in handler['filters'].items():
|
|
188
|
+
if filter_value is None:
|
|
189
|
+
continue
|
|
190
|
+
if not self._test_filter(message_filter, filter_value, context):
|
|
191
|
+
return False
|
|
192
|
+
return True
|
|
193
|
+
return False
|
|
194
|
+
|
|
195
|
+
def _process_text_message(self, context: Message):
|
|
196
|
+
"""
|
|
197
|
+
Обрабатывает входящее сообщение
|
|
198
|
+
|
|
199
|
+
:param context: Контекст обновления
|
|
200
|
+
:type context: Context
|
|
201
|
+
"""
|
|
202
|
+
# if text.startswith("/"):
|
|
203
|
+
# print("Command send. Do nothing now))")
|
|
204
|
+
self.run_handler(context=context, message_handlers=self.message_handlers)
|
|
205
|
+
# for pattern, handler in self.message_handlers:
|
|
206
|
+
# if pattern == text or re.search(pattern, text):
|
|
207
|
+
# handler(context)
|
|
208
|
+
|
|
209
|
+
def _process_update(self, update: Dict[str, Any]):
|
|
210
|
+
"""
|
|
211
|
+
Метод для обработки входящего полученного обновления
|
|
212
|
+
|
|
213
|
+
:param update: Данные по обновлениям
|
|
214
|
+
:type update: Dict[str, Any]
|
|
215
|
+
"""
|
|
216
|
+
try:
|
|
217
|
+
# print("===============\nUPDATE RECEIVED\n===============")
|
|
218
|
+
# print(f"Update type: {update.get('update_type')}")
|
|
219
|
+
# print(f"Full update: {json.dumps(update, indent=2)}")
|
|
220
|
+
|
|
221
|
+
update_type = update.get("update_type")
|
|
222
|
+
if update_type == UpdateType.MESSAGE_CREATED and "message" in update.keys():
|
|
223
|
+
context = Message(update, self.api)
|
|
224
|
+
self._process_text_message(context)
|
|
225
|
+
elif update_type == UpdateType.MESSAGE_CALLBACK:
|
|
226
|
+
print("Processing message_callback...")
|
|
227
|
+
if "callback" in update:
|
|
228
|
+
callback = CallbackQuery(update, self.api)
|
|
229
|
+
# print(f"Created callback: id={callback.id}, data={callback.data}")
|
|
230
|
+
self._process_callback_query(callback)
|
|
231
|
+
except Exception:
|
|
232
|
+
print(f"Error while processing update: {traceback.format_exc()}")
|
|
233
|
+
|
|
234
|
+
def _check_text_length(self, text):
|
|
235
|
+
"""
|
|
236
|
+
Проверки длины строки
|
|
237
|
+
"""
|
|
238
|
+
return text is not None and not (len(text) < 4000)
|
|
239
|
+
|
|
240
|
+
def send_photo(
|
|
241
|
+
self,
|
|
242
|
+
chat_id: Union[int, str],
|
|
243
|
+
photo: Union[Any, str],
|
|
244
|
+
caption: Optional[str] = None,
|
|
245
|
+
parse_mode: Optional[str] = None,
|
|
246
|
+
reply_markup: Union[InlineKeyboardMarkup, Any] = None
|
|
247
|
+
):
|
|
248
|
+
"""
|
|
249
|
+
Отправляет сообщение с фото
|
|
250
|
+
|
|
251
|
+
:param chat_id: Чат, куда надо отправить сообщение
|
|
252
|
+
:type chat_id: Union[int, str]
|
|
253
|
+
|
|
254
|
+
:param photo: Объект фото
|
|
255
|
+
:type photo: Union[Any, str]
|
|
256
|
+
|
|
257
|
+
:param caption: Текст сообщения под фото
|
|
258
|
+
:type caption: Optional[str]
|
|
259
|
+
|
|
260
|
+
:param parse_mode: Разметка сообщения
|
|
261
|
+
:type parse_mode: Optional[str]
|
|
262
|
+
|
|
263
|
+
:return: Информация об отправленном сообщении
|
|
264
|
+
:rtype: Dict[str, Any]
|
|
265
|
+
"""
|
|
266
|
+
|
|
267
|
+
if self._check_text_length(text=caption):
|
|
268
|
+
raise ValueError(f'caption должен быть меньше 4000 символов.\nСейчас их {len(caption)}')
|
|
269
|
+
final_attachments = []
|
|
270
|
+
if isinstance(photo, InputMedia):
|
|
271
|
+
final_attachments.append(photo.to_dict(api=self.api))
|
|
272
|
+
final_attachments.append(InputMedia(media=photo).to_dict(api=self.api))
|
|
273
|
+
if reply_markup:
|
|
274
|
+
if hasattr(reply_markup, 'to_attachment'):
|
|
275
|
+
final_attachments.append(reply_markup.to_attachment())
|
|
276
|
+
else:
|
|
277
|
+
final_attachments.append(reply_markup)
|
|
278
|
+
return Message(
|
|
279
|
+
update=self.api.send_message(
|
|
280
|
+
chat_id=chat_id,
|
|
281
|
+
text=caption,
|
|
282
|
+
attachments=final_attachments,
|
|
283
|
+
parse_mode=parse_mode
|
|
284
|
+
),
|
|
285
|
+
api=self.api
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
def send_media_group(
|
|
289
|
+
self,
|
|
290
|
+
chat_id: Union[int, str],
|
|
291
|
+
media: list,
|
|
292
|
+
caption: Optional[str] = None,
|
|
293
|
+
parse_mode: Optional[str] = None,
|
|
294
|
+
reply_markup: Union[InlineKeyboardMarkup, Any] = None
|
|
295
|
+
):
|
|
296
|
+
"""
|
|
297
|
+
Отправляет сообщение с фото
|
|
298
|
+
|
|
299
|
+
:param chat_id: Чат, куда надо отправить сообщение
|
|
300
|
+
:type chat_id: Union[int, str]
|
|
301
|
+
|
|
302
|
+
:param photo: Объект фото
|
|
303
|
+
:type photo: Union[Any, str]
|
|
304
|
+
|
|
305
|
+
:param caption: Текст сообщения под фото
|
|
306
|
+
:type caption: Optional[str]
|
|
307
|
+
|
|
308
|
+
:param parse_mode: Разметка сообщения
|
|
309
|
+
:type parse_mode: Optional[str]
|
|
310
|
+
|
|
311
|
+
:return: Информация об отправленном сообщении
|
|
312
|
+
:rtype: Dict[str, Any]
|
|
313
|
+
"""
|
|
314
|
+
|
|
315
|
+
if self._check_text_length(text=caption):
|
|
316
|
+
raise ValueError(f'caption должен быть меньше 4000 символов.\nСейчас их {len(caption)}')
|
|
317
|
+
final_attachments = []
|
|
318
|
+
for photo in media:
|
|
319
|
+
if isinstance(photo, InputMedia):
|
|
320
|
+
final_attachments.append(photo.to_dict(api=self.api))
|
|
321
|
+
else:
|
|
322
|
+
final_attachments.append(InputMedia(media=photo).to_dict(api=self.api))
|
|
323
|
+
if reply_markup:
|
|
324
|
+
if hasattr(reply_markup, 'to_attachment'):
|
|
325
|
+
final_attachments.append(reply_markup.to_attachment())
|
|
326
|
+
else:
|
|
327
|
+
final_attachments.append(reply_markup)
|
|
328
|
+
return Message(
|
|
329
|
+
update=self.api.send_message(
|
|
330
|
+
chat_id=chat_id,
|
|
331
|
+
text=caption,
|
|
332
|
+
attachments=final_attachments,
|
|
333
|
+
parse_mode=parse_mode
|
|
334
|
+
),
|
|
335
|
+
api=self.api
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
def send_document(
|
|
339
|
+
self,
|
|
340
|
+
chat_id: Union[int, str],
|
|
341
|
+
document: Union[Any, str],
|
|
342
|
+
caption: Optional[str] = None,
|
|
343
|
+
parse_mode: Optional[str] = None,
|
|
344
|
+
reply_markup: Union[InlineKeyboardMarkup, Any] = None,
|
|
345
|
+
visible_file_name: Optional[str] = None
|
|
346
|
+
):
|
|
347
|
+
"""
|
|
348
|
+
Отправляет сообщение с файлом
|
|
349
|
+
|
|
350
|
+
:param chat_id: Чат, куда надо отправить сообщение
|
|
351
|
+
:type chat_id: Union[int, str]
|
|
352
|
+
|
|
353
|
+
:param document: Объект файла
|
|
354
|
+
:type document: Union[Any, str]
|
|
355
|
+
|
|
356
|
+
:param caption: Текст сообщения под фото
|
|
357
|
+
:type caption: Optional[str]
|
|
358
|
+
|
|
359
|
+
:param parse_mode: Разметка сообщения
|
|
360
|
+
:type parse_mode: Optional[str]
|
|
361
|
+
|
|
362
|
+
:return: Информация об отправленном сообщении
|
|
363
|
+
:rtype: Dict[str, Any]
|
|
364
|
+
"""
|
|
365
|
+
|
|
366
|
+
if self._check_text_length(text=caption):
|
|
367
|
+
raise ValueError(f'caption должен быть меньше 4000 символов.\nСейчас их {len(caption)}')
|
|
368
|
+
final_attachments = []
|
|
369
|
+
if isinstance(document, InputMedia) and document.type == "file":
|
|
370
|
+
final_attachments.append(document.to_dict(api=self.api))
|
|
371
|
+
else:
|
|
372
|
+
final_attachments.append(
|
|
373
|
+
InputMedia(type="file", media=document).to_dict(api=self.api, file_name=visible_file_name)
|
|
374
|
+
)
|
|
375
|
+
if reply_markup:
|
|
376
|
+
if hasattr(reply_markup, 'to_attachment'):
|
|
377
|
+
final_attachments.append(reply_markup.to_attachment())
|
|
378
|
+
else:
|
|
379
|
+
final_attachments.append(reply_markup)
|
|
380
|
+
for _ in range(self.count_retries):
|
|
381
|
+
response = self.api.send_message(
|
|
382
|
+
chat_id=chat_id,
|
|
383
|
+
text=caption,
|
|
384
|
+
attachments=final_attachments,
|
|
385
|
+
parse_mode=parse_mode.lower()
|
|
386
|
+
)
|
|
387
|
+
if isinstance(response, str):
|
|
388
|
+
continue
|
|
389
|
+
break
|
|
390
|
+
return Message(update=response, api=self.api)
|
|
391
|
+
|
|
392
|
+
def delete_message(
|
|
393
|
+
self,
|
|
394
|
+
chat_id: Union[str, int],
|
|
395
|
+
message_id: str,
|
|
396
|
+
):
|
|
397
|
+
"""
|
|
398
|
+
Метод удаления сообщения `message_id` в чате `chat_id`
|
|
399
|
+
|
|
400
|
+
:param chat_id: Айди чата
|
|
401
|
+
:type chat_id: Union[str, int]
|
|
402
|
+
|
|
403
|
+
:param message_id: Айди сообщения
|
|
404
|
+
:type message_id: int
|
|
405
|
+
"""
|
|
406
|
+
self.api.send_message(msg_id=message_id, method="DELETE")
|
|
407
|
+
return {}
|
|
408
|
+
|
|
409
|
+
def edit_message_text(
|
|
410
|
+
self,
|
|
411
|
+
text: str,
|
|
412
|
+
chat_id: Union[str, int],
|
|
413
|
+
message_id: str,
|
|
414
|
+
reply_markup: Union[InlineKeyboardMarkup, Any] = None,
|
|
415
|
+
parce_mode: Union[str, Any] = None
|
|
416
|
+
):
|
|
417
|
+
"""
|
|
418
|
+
Метод изменения текстового сообщения `message_id` в чате `chat_id`
|
|
419
|
+
|
|
420
|
+
:param text: Текст, на который надо заменить текущий
|
|
421
|
+
:type text: str
|
|
422
|
+
|
|
423
|
+
:param chat_id: Айди чата
|
|
424
|
+
:type chat_id: Union[str, int]
|
|
425
|
+
|
|
426
|
+
:param message_id: Айди сообщения
|
|
427
|
+
:type message_id: int
|
|
428
|
+
"""
|
|
429
|
+
final_attachments = []
|
|
430
|
+
if reply_markup:
|
|
431
|
+
if hasattr(reply_markup, 'to_attachment'):
|
|
432
|
+
final_attachments.append(reply_markup.to_attachment())
|
|
433
|
+
else:
|
|
434
|
+
final_attachments.append(reply_markup)
|
|
435
|
+
self.api.send_message(
|
|
436
|
+
msg_id=message_id,
|
|
437
|
+
text=text,
|
|
438
|
+
method="PUT",
|
|
439
|
+
attachments=final_attachments,
|
|
440
|
+
parse_mode=parce_mode
|
|
441
|
+
)
|
|
442
|
+
return {}
|
|
443
|
+
|
|
444
|
+
def edit_message_media(
|
|
445
|
+
self,
|
|
446
|
+
media: Any,
|
|
447
|
+
chat_id: Union[str, int],
|
|
448
|
+
message_id: str,
|
|
449
|
+
reply_markup: Union[InlineKeyboardMarkup, Any] = None,
|
|
450
|
+
parce_mode: Union[str, Any] = "markdown"
|
|
451
|
+
):
|
|
452
|
+
"""
|
|
453
|
+
Метод изменения медиа сообщения `message_id` в чате `chat_id`
|
|
454
|
+
|
|
455
|
+
:param media: Медиа, на которое надо заменить текущее
|
|
456
|
+
:type media: str
|
|
457
|
+
|
|
458
|
+
:param chat_id: Айди чата
|
|
459
|
+
:type chat_id: Union[str, int]
|
|
460
|
+
|
|
461
|
+
:param message_id: Айди сообщения
|
|
462
|
+
:type message_id: int
|
|
463
|
+
"""
|
|
464
|
+
final_attachments = []
|
|
465
|
+
# if isinstance(media, Photo):
|
|
466
|
+
# final_attachments.append(media.to_dict())
|
|
467
|
+
if isinstance(media, InputMedia):
|
|
468
|
+
final_attachments.append(media.to_dict(api=self.api))
|
|
469
|
+
else:
|
|
470
|
+
final_attachments.append(InputMedia(media=media).to_dict(api=self.api))
|
|
471
|
+
if reply_markup:
|
|
472
|
+
if hasattr(reply_markup, 'to_attachment'):
|
|
473
|
+
final_attachments.append(reply_markup.to_attachment())
|
|
474
|
+
else:
|
|
475
|
+
final_attachments.append(reply_markup)
|
|
476
|
+
text = get_text(media=media)
|
|
477
|
+
parce_mode = get_parce_mode(media=media, parse_mode=parce_mode)
|
|
478
|
+
self.api.send_message(
|
|
479
|
+
msg_id=message_id,
|
|
480
|
+
text=text,
|
|
481
|
+
method="PUT",
|
|
482
|
+
attachments=final_attachments,
|
|
483
|
+
parse_mode=parce_mode
|
|
484
|
+
)
|
|
485
|
+
return {}
|
|
486
|
+
|
|
487
|
+
def edit_message_reply_markup(
|
|
488
|
+
self,
|
|
489
|
+
chat_id: Union[str, int],
|
|
490
|
+
message_id: str,
|
|
491
|
+
reply_markup: Union[InlineKeyboardMarkup, Any] = None,
|
|
492
|
+
parce_mode: Union[str, Any] = "markdown"
|
|
493
|
+
):
|
|
494
|
+
"""
|
|
495
|
+
Метод изменения клавиатуры сообщения `message_id` в чате `chat_id`
|
|
496
|
+
|
|
497
|
+
:param chat_id: Айди чата
|
|
498
|
+
:type chat_id: Union[str, int]
|
|
499
|
+
|
|
500
|
+
:param message_id: Айди сообщения
|
|
501
|
+
:type message_id: int
|
|
502
|
+
|
|
503
|
+
:param reply_markup: Новая клавиатура
|
|
504
|
+
:type reply_markup: Union[InlineKeyboardMarkup, Any]
|
|
505
|
+
"""
|
|
506
|
+
final_attachments = []
|
|
507
|
+
msg: Message = self.get_message(message_id=message_id)
|
|
508
|
+
if msg.photo:
|
|
509
|
+
final_attachments.append(msg.photo.to_dict())
|
|
510
|
+
if reply_markup:
|
|
511
|
+
if hasattr(reply_markup, 'to_attachment'):
|
|
512
|
+
final_attachments.append(reply_markup.to_attachment())
|
|
513
|
+
else:
|
|
514
|
+
final_attachments.append(reply_markup)
|
|
515
|
+
self.api.send_message(
|
|
516
|
+
msg_id=message_id,
|
|
517
|
+
method="PUT",
|
|
518
|
+
attachments=final_attachments,
|
|
519
|
+
parse_mode=parce_mode.lower()
|
|
520
|
+
)
|
|
521
|
+
return {}
|
|
522
|
+
|
|
523
|
+
def send_message(
|
|
524
|
+
self,
|
|
525
|
+
chat_id: Union[str, int],
|
|
526
|
+
text: str,
|
|
527
|
+
attachments: Optional[List[Dict[str, Any]]] = None,
|
|
528
|
+
reply_markup: Optional[Any] = None,
|
|
529
|
+
parce_mode: str = "markdown",
|
|
530
|
+
notify: bool = True
|
|
531
|
+
) -> Message:
|
|
532
|
+
"""
|
|
533
|
+
Отправляет ответ на текущее сообщение/обновление
|
|
534
|
+
|
|
535
|
+
:param text: Текст сообщения
|
|
536
|
+
:type text:
|
|
537
|
+
|
|
538
|
+
:param attachments: Вложения сообщения
|
|
539
|
+
:type attachments:
|
|
540
|
+
|
|
541
|
+
:param keyboard: Объект клавиатуры (будет добавлен к attachments)
|
|
542
|
+
:type keyboard:
|
|
543
|
+
|
|
544
|
+
:return: Информация об отправленном сообщении
|
|
545
|
+
:rtype: Message
|
|
546
|
+
"""
|
|
547
|
+
if self._check_text_length(text=text):
|
|
548
|
+
raise ValueError(f'text должен быть меньше 4000 символов\nСейчас их {len(text)}')
|
|
549
|
+
if isinstance(chat_id, int):
|
|
550
|
+
chat_id = str(chat_id)
|
|
551
|
+
|
|
552
|
+
final_attachments = attachments.copy() if attachments else []
|
|
553
|
+
|
|
554
|
+
# Если передана клавиатура, добавляем её как вложение
|
|
555
|
+
if reply_markup:
|
|
556
|
+
if hasattr(reply_markup, 'to_attachment'):
|
|
557
|
+
final_attachments.append(reply_markup.to_attachment())
|
|
558
|
+
else:
|
|
559
|
+
final_attachments.append(reply_markup)
|
|
560
|
+
|
|
561
|
+
return Message(
|
|
562
|
+
update=self.api.send_message(
|
|
563
|
+
chat_id=chat_id,
|
|
564
|
+
text=text,
|
|
565
|
+
attachments=final_attachments,
|
|
566
|
+
parse_mode=parce_mode.lower(),
|
|
567
|
+
notify=notify
|
|
568
|
+
),
|
|
569
|
+
api=self.api
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
def get_message(self, message_id: str):
|
|
573
|
+
"""
|
|
574
|
+
Метод получения сообщения по айди
|
|
575
|
+
|
|
576
|
+
:param message_id: Айди сообщения
|
|
577
|
+
:type message_id: str
|
|
578
|
+
"""
|
|
579
|
+
msg = self.api.get_message(msg_id=message_id)
|
|
580
|
+
update = {"update_type": "get_message"}
|
|
581
|
+
update["message"] = msg
|
|
582
|
+
return Message(update=update, api=self.api)
|
|
583
|
+
|
|
584
|
+
def callback_query_handler(self, data=None, **kwargs):
|
|
585
|
+
"""
|
|
586
|
+
Декоратор для регистрации обработчиков callback-запросов от inline-кнопок
|
|
587
|
+
|
|
588
|
+
:param data: Данные кнопки для фильтрации (callback_data)
|
|
589
|
+
:type data: Optional[str]
|
|
590
|
+
|
|
591
|
+
:param kwargs: Дополнительные фильтры для обработчика
|
|
592
|
+
|
|
593
|
+
:return: Декоратор для функции-обработчика
|
|
594
|
+
:rtype: Callable
|
|
595
|
+
|
|
596
|
+
Пример использования:
|
|
597
|
+
@bot.callback_query_handler(func=lambda cb: cb.data == "yes")
|
|
598
|
+
def yes_handler(callback):
|
|
599
|
+
callback.answer(notification="да да")
|
|
600
|
+
"""
|
|
601
|
+
def decorator(handler):
|
|
602
|
+
filters = {}
|
|
603
|
+
if data:
|
|
604
|
+
filters['data'] = data
|
|
605
|
+
filters.update(kwargs)
|
|
606
|
+
|
|
607
|
+
handler_dict = self._build_handler_dict(handler, **filters)
|
|
608
|
+
self.callback_query_handlers.append(handler_dict)
|
|
609
|
+
return handler
|
|
610
|
+
|
|
611
|
+
return decorator
|
|
612
|
+
|
|
613
|
+
def add_callback_query_handler(self, handler_dict):
|
|
614
|
+
"""
|
|
615
|
+
Добавляет обработчик callback-запросов напрямую
|
|
616
|
+
|
|
617
|
+
:param handler_dict: Словарь с описанием обработчика
|
|
618
|
+
:type handler_dict: Dict[str, Any]
|
|
619
|
+
|
|
620
|
+
:return: None
|
|
621
|
+
"""
|
|
622
|
+
self.callback_query_handlers.append(handler_dict)
|
|
623
|
+
|
|
624
|
+
def _process_callback_query(self, callback: CallbackQuery):
|
|
625
|
+
"""
|
|
626
|
+
Обрабатывает входящий callback-запрос
|
|
627
|
+
Метод ищет подходящий обработчик среди зарегистрированных и вызывает первый соответствующий фильтрам
|
|
628
|
+
|
|
629
|
+
:param callback: Объект callback-запроса
|
|
630
|
+
:type callback: CallbackQuery
|
|
631
|
+
|
|
632
|
+
:return: None
|
|
633
|
+
"""
|
|
634
|
+
# print(f"Processing callback: id={callback.id}, data={callback.data}")
|
|
635
|
+
# print(f"Callback user: {callback.from_user}")
|
|
636
|
+
|
|
637
|
+
for handler in self.callback_query_handlers:
|
|
638
|
+
# print(f"Checking handler with filters: {handler['filters']}")
|
|
639
|
+
if self._check_filters(callback, handler):
|
|
640
|
+
# print("Handler matched! Calling function...")
|
|
641
|
+
handler["function"](callback)
|
|
642
|
+
break
|
|
643
|
+
else:
|
|
644
|
+
print("No matching handler found for callback")
|