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/types.py ADDED
@@ -0,0 +1,753 @@
1
+ from dataclasses import dataclass
2
+ from datetime import datetime
3
+ from typing import List, Dict, Any, Optional
4
+
5
+ from maxibot.apihelper import Api
6
+ from maxibot.util import is_pil_image, pil_image_to_bytes
7
+
8
+
9
+ class UpdateType:
10
+ """
11
+ Типы обновлений, которые можно получать от MAX API
12
+ """
13
+ MESSAGE_CREATED = "message_created"
14
+ MESSAGE_CALLBACK = "message_callback"
15
+ BOT_STARTED = "bot_started"
16
+ MESSAGE_EDITED = "message_edited"
17
+ MESSAGE_DELETED = "message_deleted"
18
+ MESSAGE_CHAT_CREATED = "message_chat_created"
19
+
20
+
21
+ class InlineKeyboardButton:
22
+ """
23
+ Класс для создания inline-кнопок в сообщениях
24
+
25
+ :param text: Текст на кнопке
26
+ :type text: str
27
+
28
+ :param url: URL ссылка для кнопки типа "link"
29
+ :type url: Optional[str]
30
+
31
+ :param callback_data: Данные для callback-кнопки
32
+ :type callback_data: Optional[str]
33
+ """
34
+ MAX_URL_LEN = 2048
35
+
36
+ def __init__(
37
+ self,
38
+ text: str,
39
+ url: Optional[str] = None,
40
+ callback_data: Optional[str] = None,
41
+ ):
42
+ self.text = text
43
+ self.url = url
44
+ self.callback_data = callback_data
45
+
46
+ if not (url or callback_data):
47
+ raise ValueError("url или callback_data обязан быть")
48
+ if url and callback_data:
49
+ raise ValueError("укажите что-то одно")
50
+ if url and len(url) > self.MAX_URL_LEN:
51
+ raise ValueError(f"url не может быть длиннее {self.MAX_URL_LEN} символов")
52
+
53
+ def to_dict(self) -> Dict[str, Any]:
54
+ """
55
+ Преобразует кнопку в словарь для отправки в MAX API
56
+
57
+ :return: Словарь с данными кнопки в формате MAX API
58
+ :rtype: Dict[str, Any]
59
+ """
60
+ if self.url:
61
+ return {"type": "link", "text": self.text, "url": self.url}
62
+ return {
63
+ "type": "callback",
64
+ "text": self.text,
65
+ "payload": self.callback_data
66
+ }
67
+
68
+ def is_special(self) -> bool:
69
+ """
70
+ Проверяет, является ли кнопка специальной (ограничивает ряд до 3 кнопок)
71
+
72
+ :return: True если кнопка специальная (link), False если обычная (callback)
73
+ :rtype: bool
74
+ """
75
+ return self.url is not None # link
76
+
77
+
78
+ class InlineKeyboardMarkup:
79
+ """
80
+ Класс для создания inline-клавиатур в сообщениях
81
+
82
+ :param row_width: Ширина ряда по умолчанию (сколько кнопок в ряду)
83
+ :type row_width: int
84
+ """
85
+ MAX_ROWS = 30
86
+ MAX_BUTTONS = 210
87
+ MAX_ROW_REGULAR = 7
88
+ MAX_ROW_SPECIAL = 3
89
+
90
+ def __init__(self, row_width: int = 1):
91
+ self.row_width = row_width
92
+ self.keyboard: List[List[InlineKeyboardButton]] = []
93
+
94
+ def add(self, *args: InlineKeyboardButton, row_width=None) -> 'InlineKeyboardMarkup':
95
+ """
96
+ Добавляет кнопки в клавиатуру, автоматически разбивая на ряды
97
+
98
+ :param args: Кнопки для добавления
99
+ :type args: InlineKeyboardButton
100
+
101
+ :param row_width: Ширина ряда для этих кнопок (если не указано, используется self.row_width)
102
+ :type row_width: Optional[int]
103
+
104
+ :return: Текущий объект клавиатуры (для цепочки вызовов)
105
+ :rtype: InlineKeyboardMarkup
106
+ """
107
+ width = row_width or self.row_width
108
+ row = []
109
+ for btn in args:
110
+ row.append(btn)
111
+ if len(row) == width:
112
+ self._append_row(row)
113
+ row = []
114
+ if row:
115
+ self._append_row(row)
116
+ return self
117
+
118
+ def row(self, *args: InlineKeyboardButton) -> 'InlineKeyboardMarkup':
119
+ """
120
+ Добавляет ряд кнопок в клавиатуру
121
+
122
+ :param args: Кнопки для добавления в ряд
123
+ :type args: InlineKeyboardButton
124
+
125
+ :return: Текущий объект клавиатуры (для цепочки вызовов)
126
+ :rtype: InlineKeyboardMarkup
127
+ """
128
+ if args:
129
+ self._append_row(list(args))
130
+ return self
131
+
132
+ def to_attachment(self) -> Dict[str, Any]:
133
+ """
134
+ Преобразует клавиатуру в attachment для отправки в сообщении
135
+
136
+ :return: Словарь с данными клавиатуры в формате MAX API
137
+ :rtype: Dict[str, Any]
138
+ """
139
+ self._validate()
140
+ return {
141
+ "type": "inline_keyboard",
142
+ "payload": {"buttons": [[btn.to_dict() for btn in row] for row in self.keyboard]},
143
+ }
144
+
145
+ def _append_row(self, row: List[InlineKeyboardButton]):
146
+ """
147
+ Метод для добавления ряда кнопок
148
+
149
+ :param row: Ряд кнопок для добавления
150
+ :type row: List[InlineKeyboardButton]
151
+ """
152
+ self.keyboard.append(row)
153
+
154
+ def _validate(self):
155
+ """
156
+ Метод для валидации клавиатуры
157
+
158
+ :raises ValueError: Если превышены лимиты на количество кнопок или рядов
159
+ """
160
+ total = sum(len(r) for r in self.keyboard)
161
+ if total > self.MAX_BUTTONS:
162
+ raise ValueError(f"Максимум {self.MAX_BUTTONS} кнопок")
163
+ if len(self.keyboard) > self.MAX_ROWS:
164
+ raise ValueError(f"Максимум {self.MAX_ROWS} рядов")
165
+
166
+ for row in self.keyboard:
167
+ special_in_row = any(btn.is_special() for btn in row)
168
+ limit = self.MAX_ROW_SPECIAL if special_in_row else self.MAX_ROW_REGULAR
169
+ if len(row) > limit:
170
+ raise ValueError(
171
+ f"Ряд содержит {len(row)} кнопок, но максимум {limit} "
172
+ f"(из-за special-кнопок)" if special_in_row else ""
173
+ )
174
+
175
+
176
+ class ImagePayload:
177
+ """
178
+ Класс для хранения данных изображения
179
+
180
+ :param payload: Словарь с данными изображения
181
+ :type payload: Dict[str, Any]
182
+ """
183
+
184
+ def __init__(self, payload: Dict[str, Any]):
185
+ self.photo_id = payload.get("photo_id")
186
+ self.token = payload.get("token")
187
+ self.url = payload.get("url")
188
+
189
+
190
+ class ImageAttachment:
191
+ """
192
+ Класс для работы с вложениями типа "image"
193
+
194
+ :param attach: Словарь с данными вложения
195
+ :type attach: Dict[str, Any]
196
+ """
197
+
198
+ def __init__(self, attach: Dict[str, Any]):
199
+ self.payload = ImagePayload(payload=attach.get("payload"))
200
+ self.type = attach.get("type")
201
+
202
+ def to_dict(self) -> Dict[str, Any]:
203
+ """
204
+ Преобразует объект в словарь
205
+
206
+ :return: Словарь с данными изображения
207
+ :rtype: Dict[str, Any]
208
+ """
209
+ return {
210
+ "payload": {
211
+ "photo_id": self.payload.photo_id,
212
+ "token": self.payload.token,
213
+ "url": self.payload.url
214
+ },
215
+ "type": self.type
216
+ }
217
+
218
+
219
+ class Recipient:
220
+ """
221
+ Класс получателя сообщения
222
+
223
+ :param rec: Словарь recipient из ответа MAX API
224
+ :type rec: Dict[str, Any]
225
+ """
226
+
227
+ def __init__(self, rec: Dict[str, Any]):
228
+ self.chat_id = rec.get("chat_id")
229
+ self.chat_type = rec.get("chat_type")
230
+ self.user_id = rec.get("user_id")
231
+
232
+
233
+ class Body:
234
+ """
235
+ Класс тела сообщения
236
+
237
+ :param body: Словарь body из ответа MAX API
238
+ :type body: Dict[str, Any]
239
+ """
240
+
241
+ def __init__(self, body: Dict[str, Any]):
242
+ self.mid = body.get("mid")
243
+ self.seq = body.get("seq")
244
+ self.text = body.get("text")
245
+ self.attachments = body.get("attachments")
246
+
247
+
248
+ class User:
249
+ """
250
+ Класс пользователя
251
+
252
+ :param update: Обновление от MAX API
253
+ :type update: Dict[str, Any]
254
+ """
255
+
256
+ def __init__(self, update: Dict[str, Any]):
257
+ if update.get("callback"):
258
+ self.id = update.get("message").get("recipient").get("chat_id")
259
+ self.real_id = update.get("callback").get("user").get("user_id")
260
+ self.is_bot = update.get("callback").get("user").get("is_bot")
261
+ self.first_name = update.get("callback").get("user").get("first_name")
262
+ self.username = update.get("callback").get("user").get("name")
263
+ self.last_name = update.get("callback").get("user").get("last_name")
264
+ self.language_code = update.get("user_locale")
265
+ else:
266
+ self.id = update.get("message").get("recipient").get("chat_id")
267
+ self.real_id = update.get("message").get("sender").get("user_id")
268
+ self.is_bot = update.get("message").get("sender").get("is_bot")
269
+ self.first_name = update.get("message").get("sender").get("first_name")
270
+ self.username = update.get("message").get("sender").get("name")
271
+ self.last_name = update.get("message").get("sender").get("last_name")
272
+ self.language_code = update.get("user_locale")
273
+
274
+
275
+ class Chat:
276
+ """
277
+ Класс чата
278
+
279
+ :param update: Обновление от MAX API
280
+ :type update: Dict[str, Any]
281
+ """
282
+
283
+ def __init__(self, update: Dict[str, Any]):
284
+ self.id = update.get("message").get("recipient").get("chat_id")
285
+ self.type = update.get("message").get("recipient").get("chat_type")
286
+ self.user_id = update.get("message").get("recipient").get("user_id")
287
+
288
+
289
+ class ChatLink:
290
+ """
291
+ Класс ссылки на чат
292
+
293
+ :param update: Обновление от MAX API
294
+ :type update: Dict[str, Any]
295
+ """
296
+
297
+ def __init__(self, update: Dict[str, Any]):
298
+ self.id = update.get("chat_id")
299
+
300
+
301
+ class Link:
302
+ """
303
+ Класс ссылки на сообщение
304
+
305
+ :param link: Словарь с данными ссылки
306
+ :type link: Dict[str, Any]
307
+ """
308
+
309
+ def __init__(self, link: Dict[str, Any]):
310
+ if link:
311
+ self.type = link.get("type")
312
+ self.message_id: str = None
313
+ self.from_user: Optional[User] = None
314
+ self.chat: ChatLink = ChatLink(update=link)
315
+
316
+
317
+ class Photo:
318
+ """
319
+ Класс для работы с фотографиями
320
+
321
+ :param update: Обновление от MAX API
322
+ :type update: Dict[str, Any]
323
+ """
324
+
325
+ def __init__(self, update: Dict[str, Any]):
326
+ attach = update.get("message").get("body").get("attachments")
327
+ if attach:
328
+ for att in attach:
329
+ if att.get("type") == "image":
330
+ self.file_id = att.get("payload").get("photo_id")
331
+ self.token: str = att.get("payload").get("token")
332
+ self.url: str = att.get("payload").get("url")
333
+
334
+
335
+ class InputMedia:
336
+ """
337
+ Класс формирования объекта attachments для отправки медиа
338
+
339
+ :param type: Тип медиа (photo/file)
340
+ :type type: str
341
+
342
+ :param media: Байты медиа
343
+ :type media: bytes
344
+
345
+ :param caption: Подпись к медиа
346
+ :type caption: Optional[str]
347
+
348
+ :param parse_mode: Режим парсинга текста (markdown/html)
349
+ :type parse_mode: Optional[str]
350
+ """
351
+ compare_types = {
352
+ "photo": "image",
353
+ "file": "file"
354
+ }
355
+
356
+ def __init__(self, type: str = None, media: bytes = None, caption: str = None, parse_mode: str = None):
357
+ self.type = type if type else "photo"
358
+ self.media = media
359
+ self.caption = caption
360
+ self.parse_mode = parse_mode
361
+ self.api = None
362
+
363
+ def _get_upload_url(self, type_attach: str = "photo") -> Dict[str, Any]:
364
+ """
365
+ Шаг 1. Получение URL для загрузки файла
366
+
367
+ :param type_attach: Тип вложения
368
+ :type type_attach: str
369
+
370
+ :return: Ответ API с URL для загрузки
371
+ :rtype: Dict[str, Any]
372
+ """
373
+ return self.api.get_upload_file_url(type_attach=self.compare_types.get(type_attach))
374
+
375
+ def _load_file_to_max(self, url: str, file_name: str = None) -> Dict[str, Any]:
376
+ """
377
+ Шаг 2. Загрузка файла на сервер MAX API
378
+
379
+ :param url: URL для загрузки
380
+ :type url: str
381
+
382
+ :param file_name: Имя файла
383
+ :type file_name: Optional[str]
384
+
385
+ :return: Ответ API после загрузки
386
+ :rtype: Dict[str, Any]
387
+ """
388
+ if file_name:
389
+ files = {"data": (file_name, self.media, "text/plain")}
390
+ return self.api.load_file(url=url, files=files, content_types=None)
391
+ else:
392
+ files = {"data": self.media}
393
+ return self.api.load_file(url=url, files=files)
394
+
395
+ def to_dict(self, api: Api, file_name: str = None) -> Dict[str, Any]:
396
+ """
397
+ Формирование attachments для отправки медиа
398
+
399
+ :param api: Объект API
400
+ :type api: Api
401
+
402
+ :param file_name: Имя файла
403
+ :type file_name: Optional[str]
404
+
405
+ :return: Словарь с данными вложения
406
+ :rtype: Dict[str, Any]
407
+ """
408
+ self.api = api
409
+ upload_url = self._get_upload_url(type_attach=self.type).get("url")
410
+ if not upload_url:
411
+ return []
412
+ if is_pil_image(self.media):
413
+ self.media = pil_image_to_bytes(self.media)
414
+ load_file_result = self._load_file_to_max(url=upload_url, file_name=file_name)
415
+ if file_name:
416
+ token_dict = {"token": load_file_result.get("token")}
417
+ else:
418
+ token_dict = list(list(load_file_result.values())[0].values())[0]
419
+ return {
420
+ "type": self.compare_types.get(self.type),
421
+ "payload": token_dict
422
+ }
423
+
424
+
425
+ class InputMediaPhoto(InputMedia):
426
+ """
427
+ Класс для отправки фотографий
428
+
429
+ :param media: Байты изображения
430
+ :type media: bytes
431
+
432
+ :param caption: Подпись к фото
433
+ :type caption: Optional[str]
434
+
435
+ :param parse_mode: Режим парсинга текста
436
+ :type parse_mode: Optional[str]
437
+ """
438
+
439
+ def __init__(self, media=None, caption=None, parse_mode=None):
440
+ super().__init__(type="photo", media=media, caption=caption, parse_mode=parse_mode)
441
+
442
+
443
+ class InputMediaVideo(InputMedia):
444
+ """
445
+ Класс для отправки видео
446
+
447
+ :param media: Байты видео
448
+ :type media: bytes
449
+
450
+ :param caption: Подпись к видео
451
+ :type caption: Optional[str]
452
+
453
+ :param parse_mode: Режим парсинга текста
454
+ :type parse_mode: Optional[str]
455
+ """
456
+
457
+ def __init__(self, media=None, caption=None, parse_mode=None):
458
+ super().__init__(type="video", media=media, caption=caption, parse_mode=parse_mode)
459
+
460
+
461
+ class Message:
462
+ """
463
+ Класс для работы с сообщениями (аналог telebot.types.Message)
464
+
465
+ :param update: Обновление от MAX API
466
+ :type update: Dict[str, Any]
467
+
468
+ :param api: Объект API
469
+ :type api: Api
470
+ """
471
+
472
+ @staticmethod
473
+ def _get_photo_from_attachments(update: Dict[str, Any]) -> Optional[ImageAttachment]:
474
+ """
475
+ Извлечение фото из вложений сообщения
476
+
477
+ :param update: Обновление от MAX API
478
+ :type update: Dict[str, Any]
479
+
480
+ :return: Объект ImageAttachment или None
481
+ :rtype: Optional[ImageAttachment]
482
+ """
483
+ if update.get("message"):
484
+ update = update.get("message")
485
+ if update.get("body"):
486
+ update = update.get("body")
487
+ if update.get("attachments"):
488
+ attachs = update.get("attachments")
489
+ for attach in attachs:
490
+ if attach.get("type") == "image":
491
+ return ImageAttachment(attach=attach)
492
+ return None
493
+
494
+ @staticmethod
495
+ def _get_content_type(update: Dict[str, Any]) -> str:
496
+ """
497
+ Определение типа контента сообщения
498
+
499
+ :param update: Обновление от MAX API
500
+ :type update: Dict[str, Any]
501
+
502
+ :return: Тип контента
503
+ :rtype: str
504
+ """
505
+ if update.get("message").get("body").get("attachments"):
506
+ c_type = update.get("message").get("body").get("attachments")[0].get("type")
507
+ if c_type == "image":
508
+ return "photo"
509
+ else:
510
+ return c_type
511
+ else:
512
+ return "text"
513
+
514
+ @staticmethod
515
+ def _get_msg_id(update: Dict[str, Any]) -> Optional[str]:
516
+ """
517
+ Получение ID сообщения
518
+
519
+ :param update: Обновление от MAX API
520
+ :type update: Dict[str, Any]
521
+
522
+ :return: ID сообщения или None
523
+ :rtype: Optional[str]
524
+ """
525
+ if update.get("message").get("body"):
526
+ return update.get("message").get("body").get("mid")
527
+ elif update.get("message").get("mid"):
528
+ return update.get("message").get("mid")
529
+ else:
530
+ return None
531
+
532
+ @staticmethod
533
+ def _get_msg_timestamp(update: Dict[str, Any]) -> Optional[datetime]:
534
+ """
535
+ Получение времени сообщения
536
+
537
+ :param update: Обновление от MAX API
538
+ :type update: Dict[str, Any]
539
+
540
+ :return: Время сообщения или None
541
+ :rtype: Optional[datetime]
542
+ """
543
+ if update.get("timestamp"):
544
+ time = str(update.get("timestamp"))
545
+ main_time = time[:10]
546
+ milisec = time[10:]
547
+ alltime = float(main_time + "." + milisec)
548
+ return datetime.fromtimestamp(alltime)
549
+ else:
550
+ return None
551
+
552
+ @staticmethod
553
+ def _get_msg_text(update: Dict[str, Any]) -> Optional[str]:
554
+ """
555
+ Получение текста сообщения
556
+
557
+ :param update: Обновление от MAX API
558
+ :type update: Dict[str, Any]
559
+
560
+ :return: Текст сообщения или None
561
+ :rtype: Optional[str]
562
+ """
563
+ if update.get("message").get("body"):
564
+ return update.get("message").get("body").get("text")
565
+ else:
566
+ return None
567
+
568
+ def __init__(self, update: Dict[str, Any], api: Api):
569
+ """
570
+ Инициализация объекта сообщения
571
+ """
572
+ self.update = update
573
+ self.api = api
574
+ self.content_type: str = self._get_content_type(update=update)
575
+ self.id: Optional[str] = self._get_msg_id(update=update)
576
+ self.message_id: Optional[str] = self._get_msg_id(update=update)
577
+ self.from_user: Optional[User] = User(update=update)
578
+ self.date: Optional[datetime] = self._get_msg_timestamp(update=update)
579
+ self.chat: Chat = Chat(update=update)
580
+ self.reply_to_message: Link = Link(link=update.get("message").get("link"))
581
+ self.text: Optional[str] = self._get_msg_text(update=update)
582
+ self.photo: Optional[ImageAttachment] = self._get_photo_from_attachments(update=update)
583
+ self.photo_reply: Photo = Photo(update=update)
584
+ self.update_type = update.get('update_type')
585
+
586
+ # def reply(self, text: str, **kwargs) -> Dict[str, Any]:
587
+ # """
588
+ # Ответ на текущее сообщение
589
+ #
590
+ # :param text: Текст ответа
591
+ # :type text: str
592
+ #
593
+ # :param kwargs: Дополнительные параметры:
594
+ # - parse_mode: Режим парсинга (markdown/html)
595
+ # - reply_markup: Клавиатура
596
+ # - attachments: Вложения
597
+ #
598
+ # :return: Ответ API
599
+ # :rtype: Dict[str, Any]
600
+ # """
601
+ # attachments = kwargs.get('attachments', [])
602
+ # reply_markup = kwargs.get('reply_markup')
603
+ #
604
+ # if reply_markup:
605
+ # attachments.append(reply_markup.to_attachment())
606
+ #
607
+ # return self.api.send_message(
608
+ # chat_id=self.chat.id,
609
+ # text=text,
610
+ # attachments=attachments,
611
+ # link=self.message_id
612
+ # )
613
+
614
+
615
+ class CallbackQuery:
616
+ """
617
+ Класс для обработки callback-запросов от inline-кнопок
618
+
619
+ :param update: Обновление от MAX API с типом 'message_callback'
620
+ :type update: Dict[str, Any]
621
+
622
+ :param api: Объект API для отправки ответов
623
+ :type api: Api
624
+ """
625
+
626
+ def __init__(self, update: Dict[str, Any], api: Api):
627
+ self.api = api
628
+ cb = update.get("callback", {})
629
+ self.id: str = cb.get("callback_id", "")
630
+ self.chat_instance: str = self.id
631
+ self.data: Optional[str] = cb.get("payload")
632
+
633
+ # Если нет в callback, пытаемся извлечь из message attachments
634
+ if not self.data:
635
+ self.data = self._extract_button_data_from_message(update)
636
+
637
+ msg = update.get("message", {})
638
+ self.from_user = User(update=update) if msg else None
639
+ self.message = Message(update=update, api=api) if msg else None
640
+
641
+ def _extract_button_data_from_message(self, update: Dict[str, Any]) -> Optional[str]:
642
+ """
643
+ Извлечение данных кнопки из message attachments
644
+
645
+ :param update: Обновление от MAX API
646
+ :type update: Dict[str, Any]
647
+
648
+ :return: Данные кнопки или None если не найдены
649
+ :rtype: Optional[str]
650
+ """
651
+ callback_id = self.id
652
+ message = update.get("message", {})
653
+
654
+ if not message:
655
+ return None
656
+
657
+ body = message.get("body", {})
658
+ attachments = body.get("attachments", [])
659
+
660
+ for attachment in attachments:
661
+ if attachment.get("callback_id") == callback_id:
662
+ payload = attachment.get("payload", {})
663
+ buttons = payload.get("buttons", [])
664
+
665
+ for row in buttons:
666
+ for button in row:
667
+ if button.get("type") == "callback":
668
+ return button.get("text", "unknown")
669
+
670
+ return None
671
+
672
+ def answer(self, text: Optional[str] = None, **kwargs) -> Dict[str, Any]:
673
+ """
674
+ Ответ на нажатие inline-кнопки в Max API
675
+
676
+ :param text: Текст для обновления сообщения
677
+ :type text: Optional[str]
678
+
679
+ :param kwargs: Дополнительные параметры:
680
+ - notification: Текст уведомления для пользователя
681
+ - attachments: Вложения для обновления сообщения
682
+ - link: Ссылка на сообщение для reply/forward
683
+ - notify: Отправлять ли уведомление о редактировании
684
+ - format: Формат текста (markdown/html)
685
+
686
+ :return: Ответ от API
687
+ :rtype: Dict[str, Any]
688
+ """
689
+ notification = kwargs.pop('notification', None)
690
+ attachments = kwargs.pop('attachments', None)
691
+ link = kwargs.pop('link', None)
692
+ notify = kwargs.pop('notify', True)
693
+ format = kwargs.pop('format', None)
694
+
695
+ should_update_message = (text is not None or
696
+ attachments is not None or
697
+ link is not None)
698
+
699
+ try:
700
+ if not should_update_message and notification:
701
+ return self.api.answer_callback(
702
+ callback_id=self.id,
703
+ notification=notification
704
+ )
705
+ else:
706
+ return self.api.answer_callback(
707
+ callback_id=self.id,
708
+ text=text,
709
+ notification=notification,
710
+ attachments=attachments,
711
+ link=link,
712
+ notify=notify,
713
+ format=format
714
+ )
715
+ except Exception as e:
716
+ if notification:
717
+ try:
718
+ return self.api.answer_callback(
719
+ callback_id=self.id,
720
+ notification=notification
721
+ )
722
+ except:
723
+ return {"success": False, "error": str(e)}
724
+ return {"success": False, "error": str(e)}
725
+
726
+ def answer_notification(self, text: str) -> Dict[str, Any]:
727
+ """
728
+ Отправка только уведомления (без изменения сообщения)
729
+
730
+ :param text: Текст уведомления
731
+ :type text: str
732
+
733
+ :return: Ответ от API
734
+ :rtype: Dict[str, Any]
735
+ """
736
+ return self.answer(notification=text)
737
+
738
+ def answer_update(self, text: str, **kwargs) -> Dict[str, Any]:
739
+ """
740
+ Обновление сообщения с опциональным уведомлением
741
+
742
+ :param text: Текст для обновления сообщения
743
+ :type text: str
744
+
745
+ :param kwargs: Дополнительные параметры
746
+ :type kwargs: Dict[str, Any]
747
+
748
+ :return: Ответ от API
749
+ :rtype: Dict[str, Any]
750
+ """
751
+ if 'notification' not in kwargs:
752
+ kwargs['notification'] = "Обновлено!"
753
+ return self.answer(text=text, **kwargs)