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/apihelper.py ADDED
@@ -0,0 +1,241 @@
1
+ import requests
2
+ from typing import Dict, Any, List, Optional
3
+
4
+ from maxibot.core.network.client import Client
5
+
6
+
7
+ proxy = None
8
+ ignore_warnings = True
9
+
10
+
11
+ class Api:
12
+ """
13
+ Клиент для рабты с api MAX
14
+ """
15
+ def __init__(self, token: str):
16
+ """
17
+ Docstring for __init__
18
+
19
+ :param token: Токен бота
20
+ :type token: str
21
+ """
22
+ if ignore_warnings:
23
+ requests.packages.urllib3.disable_warnings()
24
+ self.client = Client(token=token, proxy=proxy)
25
+
26
+ def get_my_info(self) -> Dict[str, Any]:
27
+ """
28
+ Получает информацию о текущем боте
29
+
30
+ :return: Информация о боте
31
+ :rtype: Dict[str, Any]
32
+ """
33
+ return self.client.request("GET", "/me")
34
+
35
+ def get_updates(self, allowed_updates: List[str], extra: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
36
+ """
37
+ Получает новые обновления от API через лонгполлинг
38
+
39
+ :param allowed_updates: Список типов обновлений, которые нужно получать
40
+ :param extra: Дополнительные параметры запроса
41
+
42
+ :return: Список обновлений
43
+ :rtype: Dict[str, Any]
44
+ """
45
+ params = extra or {}
46
+
47
+ if allowed_updates:
48
+ params["types"] = ",".join(allowed_updates)
49
+
50
+ return self.client.request("GET", "/updates", params=params)
51
+
52
+ def get_message(self, msg_id: str):
53
+ """
54
+ Получает сообщение по `msg_id`
55
+ """
56
+ return self.client.request("GET", f"/messages/{msg_id}")
57
+
58
+ def send_message(
59
+ self,
60
+ chat_id: str = None,
61
+ msg_id: str = None,
62
+ text: str = None,
63
+ method: str = "POST",
64
+ attachments: Optional[List[Dict[str, Any]]] = None,
65
+ parse_mode: str = "markdown",
66
+ notify: bool = True
67
+ ) -> Dict[str, Any]:
68
+ """
69
+ Отправляет/удаляет/обновляет сообщение в чате
70
+
71
+ :param chat_id: Идентификатор чата
72
+ :type chat_id: str
73
+
74
+ :param text: Текст сообщения
75
+ :type text: str
76
+
77
+ :param attachments: Вложения сообщения
78
+ :type text: Optional[List[Dict[str, Any]]]
79
+
80
+ :return: Информация об отправленном сообщении
81
+ :rtype: Dict[str, Any]
82
+ """
83
+ # query параметры запроса
84
+ if chat_id:
85
+ params = {"chat_id": chat_id}
86
+ elif msg_id and method in ("DELETE", "PUT"):
87
+ params = {"message_id": msg_id}
88
+
89
+ data = {}
90
+ if text:
91
+ data = {"text": text}
92
+
93
+ if attachments:
94
+ data["attachments"] = attachments
95
+
96
+ if parse_mode:
97
+ data["format"] = parse_mode
98
+
99
+ if notify:
100
+ data["notify"] = notify
101
+
102
+ return self.client.request(method, "/messages", params=params, data=data)
103
+
104
+ def get_upload_file_url(self, type_attach: str):
105
+ """
106
+ Апи метод для получения url загрузки файла.
107
+
108
+ :param type_attach: Тип файла, который требуется загрузить
109
+ :type type_attach: str
110
+
111
+ :return: Json с url для загрузки файла
112
+ :rtype: Dict[str: Any]
113
+ """
114
+ return self.client.request("POST", f"/uploads?type={type_attach}")
115
+
116
+ def load_file(self, url: str, files: Dict, content_types: str = None):
117
+ """
118
+ Апи метод для получения url загрузки файла.
119
+
120
+ :param type_attach: Тип файла, который требуется загрузить
121
+ :type type_attach: str
122
+
123
+ :return: Json с url для загрузки файла
124
+ :rtype: Dict[str: Any]
125
+ """
126
+ return self.client.request(method="POST", url=url, files=files, content_types=content_types)
127
+
128
+ def answer_callback(
129
+ self,
130
+ callback_id: str,
131
+ text: Optional[str] = None,
132
+ notification: Optional[str] = None,
133
+ attachments: Optional[List[Dict[str, Any]]] = None,
134
+ link: Optional[Dict[str, Any]] = None,
135
+ notify: bool = True,
136
+ format: Optional[str] = None,
137
+ ) -> Dict[str, Any]:
138
+ """
139
+ Метод позволяет отправить уведомление пользователю и/или обновить
140
+ исходное сообщение после нажатия на inline-кнопку.
141
+
142
+ :param callback_id: Уникальный идентификатор callback-запроса.
143
+ Получается из поля `callback.callback_id` в обновлении
144
+ :type callback_id: str
145
+
146
+ :param text: Новый текст сообщения. Если указан, сообщение будет обновлено
147
+ :type text: Optional[str]
148
+
149
+ :param notification: Текст всплывающего уведомления для пользователя.
150
+ Пользователь увидит это уведомление как всплывающее сообщение
151
+ Пока не очень работает, в тесте :)
152
+ :type notification: Optional[str]
153
+
154
+ :param attachments: Новые вложения сообщения. Если указаны, сообщение будет обновлено.
155
+ Для полной замены вложений передайте новый список.
156
+ Чтобы удалить все вложения, передайте пустой список.
157
+ :type attachments: Optional[List[Dict[str, Any]]]
158
+
159
+ :param link: Ссылка на сообщение для reply/forward формата.
160
+ Должен содержать поля `type` ("reply" или "forward") и `mid`
161
+ :type link: Optional[Dict[str, Any]]
162
+
163
+ :param notify: Отправлять ли системное уведомление в чат об изменении сообщения.
164
+ По умолчанию True - участники увидят "Сообщение было изменено"
165
+ :type notify: bool
166
+
167
+ :param format: Формат текста сообщения. Доступные значения: "markdown", "html"
168
+ :type format: Optional[str]
169
+
170
+ :return: Ответ от MAX API
171
+ :rtype: Dict[str, Any]
172
+
173
+ :raises HTTPError: При ошибке HTTP запроса
174
+
175
+ Примеры использования:
176
+
177
+ 1. Только уведомление:
178
+ api.answer_callback(
179
+ callback_id="callback123",
180
+ notification="Действие выполнено!"
181
+ )
182
+
183
+ 2. Обновление сообщения с уведомлением:
184
+ api.answer_callback(
185
+ callback_id="callback123",
186
+ text="**Сообщение обновлено!**",
187
+ notification="Обновление выполнено",
188
+ format="markdown",
189
+ notify=False # Не показывать "Сообщение было изменено" в чате
190
+ )
191
+
192
+ 3, 4. Пока в тесте
193
+ 3. Обновление с новыми вложениями:
194
+ api.answer_callback(
195
+ callback_id="callback123",
196
+ text="Вот новые вложения:",
197
+ attachments=[
198
+ {
199
+ "type": "photo",
200
+ "payload": {"url": "https://example.com/photo.jpg"}
201
+ }
202
+ ],
203
+ notification="Фотография добавлена"
204
+ )
205
+
206
+ 4. Удаление всех вложений (оставить только текст):
207
+ api.answer_callback(
208
+ callback_id="callback123",
209
+ text="Вложения удалены",
210
+ attachments=[], # Пустой список удалит все вложения
211
+ notification="Вложения удалены"
212
+ )
213
+ """
214
+ params = {"callback_id": callback_id}
215
+ data: Dict[str, Any] = {}
216
+
217
+ # Если нужно изменить сообщение (text, attachments, link, format)
218
+ if text is not None or attachments is not None or link is not None or format is not None:
219
+ msg: Dict[str, Any] = {"notify": notify}
220
+ if text is not None:
221
+ msg["text"] = text
222
+ if attachments is not None:
223
+ msg["attachments"] = attachments
224
+ if link is not None:
225
+ msg["link"] = link
226
+ if format is not None:
227
+ msg["format"] = format
228
+ data["message"] = msg
229
+
230
+ # Если нужно отправить уведомление
231
+ if notification is not None:
232
+ data["notification"] = notification
233
+
234
+ print(f"Answer params: {params}")
235
+ print(f"Answer data: {data}")
236
+
237
+ # Если data пустой, отправляем пустой объект
238
+ if not data:
239
+ data = {}
240
+
241
+ return self.client.request("POST", "/answers", params=params, data=data)
@@ -0,0 +1,45 @@
1
+ from typing import Any, Union
2
+
3
+ from ...apihelper import Api
4
+
5
+
6
+ class Photo:
7
+ """
8
+ Класс формирования объекта attachments для метода MaxiBot.send_photo
9
+ """
10
+
11
+ def __init__(self, photo: Union[Any, str], api: Api):
12
+ try:
13
+ self.api = api
14
+ self.photo = photo
15
+ upload_url = self._get_upload_url().get("url")
16
+ if not upload_url:
17
+ return []
18
+ load_file_result = self._load_file_to_max(url=upload_url)
19
+ self.token_dict = list(list(load_file_result.values())[0].values())[0]
20
+ except Exception:
21
+ return []
22
+
23
+ def _get_upload_url(self, type_attach: str = "image"):
24
+ """
25
+ Шаг 1.
26
+ Метод получения url для загрузки фото
27
+ """
28
+ return self.api.get_upload_file_url(type_attach=type_attach)
29
+
30
+ def _load_file_to_max(self, url: str):
31
+ """
32
+ Шаг 2.
33
+ Метод загрузки файла по url для загрузки фото
34
+ """
35
+ files = {"data": self.photo}
36
+ return self.api.load_file(url=url, files=files)
37
+
38
+ def to_dict(self):
39
+ """
40
+ Метод формирования необходимого для API MAX форматат attachments для image
41
+ """
42
+ return {
43
+ "type": "image",
44
+ "payload": self.token_dict
45
+ }
@@ -0,0 +1,118 @@
1
+ import requests
2
+ import json
3
+
4
+ from typing import Dict, Any, Optional
5
+
6
+
7
+ class Client:
8
+ """
9
+ Класс низкоуровневых запросов к API MAX
10
+ """
11
+ # BASE_URL = "https://botapi.max.ru"
12
+ BASE_URL = "https://platform-api.max.ru"
13
+
14
+ def __init__(self, token: str, proxy: Optional[dict] = {"https": "", "http": ""}):
15
+ """
16
+ Инициализация клиента
17
+
18
+ :param token: Description
19
+ :type token: str
20
+ """
21
+ self.token = token
22
+ self.proxy = proxy
23
+ self.session = requests.Session()
24
+
25
+ def _make_url(self, path: str) -> str:
26
+ """
27
+ Метод формирует полную ссылку к API запросу
28
+
29
+ :param path: API метод
30
+ :type path: str
31
+ :return: Полный URL запроса к API
32
+ :rtype: str
33
+ """
34
+ url = f"{self.BASE_URL}{path}"
35
+ # if "?" in url:
36
+ # url += f"&access_token={self.token}"
37
+ # else:
38
+ # url += f"?access_token={self.token}"
39
+ return url
40
+
41
+ def request(
42
+ self,
43
+ method: str,
44
+ path: str = None,
45
+ url: str = None,
46
+ params: Optional[Dict[str, Any]] = None,
47
+ data: Optional[Dict[str, Any]] = None,
48
+ files: Optional[Dict[str, Any]] = None,
49
+ content_types: Optional[str] = None
50
+ ) -> Dict[str, Any]:
51
+ """
52
+ Главные метод по отправке запроса к API MAX
53
+
54
+ :param method: HTTP-метод (GET, POST, PUT, DELETE)
55
+ :type method: str
56
+
57
+ :param path: Путь к методу API
58
+ :type path: str
59
+
60
+ :param params: Параметры запроса
61
+ :type params: Optional[Dict[str, Any]]
62
+
63
+ :param data: Данные для отправки в теле запроса
64
+ :type data: Optional[Dict[str, Any]]
65
+
66
+ :param files: Файлы для отправкиescription
67
+ :type files: Optional[Dict[str, Any]]
68
+
69
+ :return: Ответ API MAX на заданный метод
70
+ :rtype: Dict[str, Any]
71
+ """
72
+ url = self._make_url(path) if not url else url
73
+ header = {
74
+ "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 "
75
+ "(KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36",
76
+ "Authorization": self.token
77
+ }
78
+ if content_types:
79
+ header["Content-Type"] = content_types
80
+ if data and not files:
81
+ header["Content-Type"] = "application/json"
82
+ data = json.dumps(data)
83
+ # print(f"Request: {method} {url}")
84
+ # if params:
85
+ # print(f"Params: {params}")
86
+ # if data:
87
+ # print(f"Data: {data}")
88
+
89
+ response = self.session.request(
90
+ method=method,
91
+ url=url,
92
+ params=params,
93
+ data=data,
94
+ files=files,
95
+ headers=header,
96
+ verify=False,
97
+ proxies=self.proxy,
98
+ timeout=60
99
+ )
100
+ try:
101
+ response.raise_for_status()
102
+ result = response.json()
103
+ # print(f"Response: {result}")
104
+ return result
105
+ except requests.exceptions.HTTPError as e:
106
+ error_text = f"HTTP error: {e}"
107
+ try:
108
+ error_json = response.json()
109
+ error_text = f"{error_text}, API response: {error_json}"
110
+ except Exception:
111
+ error_text = f"{error_text}, Response text: {response.text}"
112
+
113
+ print(error_text)
114
+ # raise Exception(error_text)
115
+ return error_text
116
+ except Exception as e:
117
+ print(f"Request error: {e}")
118
+ raise
@@ -0,0 +1,75 @@
1
+ import asyncio
2
+ import traceback
3
+
4
+ from typing import Callable, List, Optional, Dict, Any
5
+
6
+ # from api import Api
7
+
8
+
9
+ class Polling:
10
+ """
11
+ Класс получения обновлений из API MAX через поллинг
12
+ """
13
+
14
+ def __init__(self, api, allowed_updates: Optional[List[str]] = None):
15
+ """
16
+ Инициализация класса
17
+
18
+ :param api: Клиент АПИ
19
+ :type api: Api
20
+
21
+ :param allowed_updates: Клиент АПИ
22
+ :type allowed_updates: Optional[List[str]]
23
+ """
24
+ self.api = api
25
+ self.allowed_updates = allowed_updates
26
+ self.is_running = False
27
+ self.marker = None
28
+
29
+ def stop(self):
30
+ """
31
+ Метод остановки поллинга
32
+ """
33
+ self.is_running = False
34
+
35
+ async def loop(self, handler: Callable[[Dict[str, Any]], None]):
36
+ """
37
+ Главный цикл поллинга
38
+
39
+ :param handler: Description
40
+ :type handler: Callable[[Dict[str, Any]], None]
41
+ """
42
+ self.is_running = True
43
+ print("Starting polling loop")
44
+
45
+ while self.is_running:
46
+ try:
47
+ updates_data = await self._get_updates()
48
+ if "marker" in updates_data.keys():
49
+ self.marker = updates_data["marker"]
50
+ updates = updates_data.get("updates", [])
51
+ for update in updates:
52
+ try:
53
+ handler(update)
54
+ except Exception:
55
+ print(f"Error handling update {traceback.format_exc()}")
56
+
57
+ except Exception:
58
+ print(f"Some error in get updates {traceback.format_exc()}")
59
+
60
+ async def _get_updates(self) -> Dict[str, Any]:
61
+ """
62
+ Метод получения обновлений по боту из API MAX
63
+
64
+ :return: Description
65
+ :rtype: Dict[str, Any]
66
+ """
67
+ params = {}
68
+ if self.marker is not None:
69
+ params["marker"] = self.marker
70
+ updates_data = await asyncio.to_thread(
71
+ self.api.get_updates,
72
+ self.allowed_updates or [],
73
+ params
74
+ )
75
+ return updates_data