maxbot-api-client-python 1.1.0__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.
- maxbot_api_client_python/__init__.py +7 -0
- maxbot_api_client_python/api.py +35 -0
- maxbot_api_client_python/client.py +195 -0
- maxbot_api_client_python/exceptions.py +36 -0
- maxbot_api_client_python/tools/__init__.py +8 -0
- maxbot_api_client_python/tools/bots.py +52 -0
- maxbot_api_client_python/tools/chats.py +445 -0
- maxbot_api_client_python/tools/helpers.py +259 -0
- maxbot_api_client_python/tools/messages.py +194 -0
- maxbot_api_client_python/tools/subscriptions.py +101 -0
- maxbot_api_client_python/tools/uploads.py +91 -0
- maxbot_api_client_python/types/__init__.py +4 -0
- maxbot_api_client_python/types/constants.py +109 -0
- maxbot_api_client_python/types/models.py +458 -0
- maxbot_api_client_python/utils.py +68 -0
- maxbot_api_client_python-1.1.0.dist-info/METADATA +241 -0
- maxbot_api_client_python-1.1.0.dist-info/RECORD +20 -0
- maxbot_api_client_python-1.1.0.dist-info/WHEEL +5 -0
- maxbot_api_client_python-1.1.0.dist-info/licenses/LICENSE +21 -0
- maxbot_api_client_python-1.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
from maxbot_api_client_python.client import Client, decode, adecode
|
|
2
|
+
from maxbot_api_client_python.types.constants import Paths
|
|
3
|
+
from maxbot_api_client_python.types import models
|
|
4
|
+
|
|
5
|
+
class Chats:
|
|
6
|
+
def __init__(self, client: Client):
|
|
7
|
+
self.client = client
|
|
8
|
+
|
|
9
|
+
def GetChats(self, **kwargs) -> models.GetChatsResp:
|
|
10
|
+
"""
|
|
11
|
+
Returns information about chats that the bot participated in: a result list and a marker pointing to the next page.
|
|
12
|
+
|
|
13
|
+
Example:
|
|
14
|
+
response = api.chats.GetChats(
|
|
15
|
+
count=20
|
|
16
|
+
)
|
|
17
|
+
"""
|
|
18
|
+
req = models.GetChatsReq(**kwargs)
|
|
19
|
+
return decode(self.client, "GET", Paths.CHATS, models.GetChatsResp, query=req.model_dump(exclude_none=True))
|
|
20
|
+
|
|
21
|
+
def GetChat(self, **kwargs) -> models.ChatInfo:
|
|
22
|
+
"""
|
|
23
|
+
Returns info about a specific chat.
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
response = api.chats.GetChat(
|
|
27
|
+
chat_id=123456789
|
|
28
|
+
)
|
|
29
|
+
"""
|
|
30
|
+
req = models.GetChatReq(**kwargs)
|
|
31
|
+
path = Paths.CHATS_ID.format(req.chat_id)
|
|
32
|
+
return decode(self.client, "GET", path, models.ChatInfo)
|
|
33
|
+
|
|
34
|
+
def EditChat(self, **kwargs) -> models.ChatInfo:
|
|
35
|
+
"""
|
|
36
|
+
Modifies the properties of a chat, such as its title, icon, or notification settings.
|
|
37
|
+
|
|
38
|
+
Example:
|
|
39
|
+
response = api.chats.EditChat(
|
|
40
|
+
chat_id=123456789,
|
|
41
|
+
title="Updated Chat Title",
|
|
42
|
+
notify=True
|
|
43
|
+
)
|
|
44
|
+
"""
|
|
45
|
+
req = models.EditChatReq(**kwargs)
|
|
46
|
+
path = Paths.CHATS_ID.format(req.chat_id)
|
|
47
|
+
return decode(self.client, "PATCH", path, models.ChatInfo, payload=req)
|
|
48
|
+
|
|
49
|
+
def DeleteChat(self, **kwargs) -> models.SimpleQueryResult:
|
|
50
|
+
"""
|
|
51
|
+
Permanently deletes a chat for the bot.
|
|
52
|
+
|
|
53
|
+
Example:
|
|
54
|
+
response = api.chats.DeleteChat(
|
|
55
|
+
chat_id=123456789
|
|
56
|
+
)
|
|
57
|
+
"""
|
|
58
|
+
req = models.DeleteChatReq(**kwargs)
|
|
59
|
+
path = Paths.CHATS_ID.format(req.chat_id)
|
|
60
|
+
return decode(self.client, "DELETE", path, models.SimpleQueryResult)
|
|
61
|
+
|
|
62
|
+
def SendAction(self, **kwargs) -> models.SimpleQueryResult:
|
|
63
|
+
"""
|
|
64
|
+
Broadcasts a temporary status action (e.g., "typing...", "recording video...") to the chat participants.
|
|
65
|
+
|
|
66
|
+
Example:
|
|
67
|
+
response = api.chats.SendAction(
|
|
68
|
+
chat_id=123456789,
|
|
69
|
+
action="mark_seen"
|
|
70
|
+
)
|
|
71
|
+
"""
|
|
72
|
+
req = models.SendActionReq(**kwargs)
|
|
73
|
+
path = Paths.CHATS_ACTIONS.format(req.chat_id)
|
|
74
|
+
return decode(self.client, "POST", path, models.SimpleQueryResult, payload=req)
|
|
75
|
+
|
|
76
|
+
def GetPinnedMessage(self, **kwargs) -> models.Message:
|
|
77
|
+
"""
|
|
78
|
+
Retrieves the currently pinned message in the specified chat.
|
|
79
|
+
|
|
80
|
+
Example:
|
|
81
|
+
response = api.chats.GetPinnedMessage(
|
|
82
|
+
chat_id=123456789
|
|
83
|
+
)
|
|
84
|
+
"""
|
|
85
|
+
req = models.GetPinnedMessageReq(**kwargs)
|
|
86
|
+
path = Paths.CHATS_PIN.format(req.chat_id)
|
|
87
|
+
return decode(self.client, "GET", path, models.Message)
|
|
88
|
+
|
|
89
|
+
def PinMessage(self, **kwargs) -> models.SimpleQueryResult:
|
|
90
|
+
"""
|
|
91
|
+
Pins a specific message in the chat.
|
|
92
|
+
You can optionally specify whether to notify chat members about the new pinned message.
|
|
93
|
+
|
|
94
|
+
Example:
|
|
95
|
+
response = api.chats.PinMessage(
|
|
96
|
+
chat_id=123456789,
|
|
97
|
+
message_id="mid:987654321...",
|
|
98
|
+
notify=True
|
|
99
|
+
)
|
|
100
|
+
"""
|
|
101
|
+
req = models.PinMessageReq(**kwargs)
|
|
102
|
+
path = Paths.CHATS_PIN.format(req.chat_id)
|
|
103
|
+
return decode(self.client, "PUT", path, models.SimpleQueryResult, payload=req)
|
|
104
|
+
|
|
105
|
+
def UnpinMessage(self, **kwargs) -> models.SimpleQueryResult:
|
|
106
|
+
"""
|
|
107
|
+
Removes the pinned message from the specified chat.
|
|
108
|
+
|
|
109
|
+
Example:
|
|
110
|
+
response = api.chats.UnpinMessage(
|
|
111
|
+
chat_id=123456789
|
|
112
|
+
)
|
|
113
|
+
"""
|
|
114
|
+
req = models.UnpinMessageReq(**kwargs)
|
|
115
|
+
path = Paths.CHATS_PIN.format(req.chat_id)
|
|
116
|
+
return decode(self.client, "DELETE", path, models.SimpleQueryResult)
|
|
117
|
+
|
|
118
|
+
def GetChatMembership(self, **kwargs) -> models.ChatMember:
|
|
119
|
+
"""
|
|
120
|
+
Returns chat membership info for the current bot.
|
|
121
|
+
|
|
122
|
+
Example:
|
|
123
|
+
response = api.chats.GetChatMembership(
|
|
124
|
+
chat_id=123456789
|
|
125
|
+
)
|
|
126
|
+
"""
|
|
127
|
+
req = models.GetChatMembershipReq(**kwargs)
|
|
128
|
+
path = Paths.CHATS_MEMBERS_ME.format(req.chat_id)
|
|
129
|
+
return decode(self.client, "GET", path, models.ChatMember)
|
|
130
|
+
|
|
131
|
+
def LeaveChat(self, **kwargs) -> models.SimpleQueryResult:
|
|
132
|
+
"""
|
|
133
|
+
Removes the bot from chat members.
|
|
134
|
+
|
|
135
|
+
Example:
|
|
136
|
+
response = api.chats.LeaveChat(
|
|
137
|
+
chat_id=123456789
|
|
138
|
+
)
|
|
139
|
+
"""
|
|
140
|
+
req = models.LeaveChatReq(**kwargs)
|
|
141
|
+
path = Paths.CHATS_MEMBERS_ME.format(req.chat_id)
|
|
142
|
+
return decode(self.client, "DELETE", path, models.SimpleQueryResult)
|
|
143
|
+
|
|
144
|
+
def GetChatAdmins(self, **kwargs) -> models.GetChatAdminsResp:
|
|
145
|
+
"""
|
|
146
|
+
Retrieves a list of administrators for the specified group chat.
|
|
147
|
+
|
|
148
|
+
Example:
|
|
149
|
+
response = api.chats.GetChatAdmins(
|
|
150
|
+
chat_id=123456789
|
|
151
|
+
)
|
|
152
|
+
"""
|
|
153
|
+
req = models.GetChatAdminsReq(**kwargs)
|
|
154
|
+
path = Paths.CHATS_MEMBERS_ADMIN.format(req.chat_id)
|
|
155
|
+
return decode(self.client, "GET", path, models.GetChatAdminsResp)
|
|
156
|
+
|
|
157
|
+
def SetChatAdmins(self, **kwargs) -> models.SimpleQueryResult:
|
|
158
|
+
"""
|
|
159
|
+
Assigns administrator rights to specific users in a group chat.
|
|
160
|
+
|
|
161
|
+
Example:
|
|
162
|
+
response = api.chats.SetChatAdmins(
|
|
163
|
+
chat_id=123456789,
|
|
164
|
+
admins=[
|
|
165
|
+
ChatAdmin(user_id=98765, role="admin"),
|
|
166
|
+
ChatAdmin(user_id=43210, role="admin")
|
|
167
|
+
]
|
|
168
|
+
)
|
|
169
|
+
"""
|
|
170
|
+
req = models.SetChatAdminsReq(**kwargs)
|
|
171
|
+
path = Paths.CHATS_MEMBERS_ADMIN.format(req.chat_id)
|
|
172
|
+
return decode(self.client, "POST", path, models.SimpleQueryResult, payload=req)
|
|
173
|
+
|
|
174
|
+
def DeleteAdmin(self, **kwargs) -> models.SimpleQueryResult:
|
|
175
|
+
"""
|
|
176
|
+
Revokes administrator rights from a specific user in a group chat.
|
|
177
|
+
|
|
178
|
+
Example:
|
|
179
|
+
response = api.chats.DeleteAdmin(
|
|
180
|
+
chat_id=123456789,
|
|
181
|
+
user_id=98765
|
|
182
|
+
)
|
|
183
|
+
"""
|
|
184
|
+
req = models.DeleteAdminReq(**kwargs)
|
|
185
|
+
path = Paths.CHATS_MEMBERS_ADMIN_ID.format(req.chat_id, req.user_id)
|
|
186
|
+
return decode(self.client, "DELETE", path, models.SimpleQueryResult)
|
|
187
|
+
|
|
188
|
+
def GetChatMembers(self, **kwargs) -> models.GetChatAdminsResp:
|
|
189
|
+
"""
|
|
190
|
+
Returns users participated in the chat.
|
|
191
|
+
|
|
192
|
+
Example:
|
|
193
|
+
response = api.chats.GetChatMembers(
|
|
194
|
+
chat_id=123456789,
|
|
195
|
+
count=20
|
|
196
|
+
)
|
|
197
|
+
"""
|
|
198
|
+
req = models.GetChatMembersReq(**kwargs)
|
|
199
|
+
path = Paths.CHATS_MEMBERS.format(req.chat_id)
|
|
200
|
+
return decode(self.client, "GET", path, models.GetChatAdminsResp, query=req.model_dump(exclude_none=True))
|
|
201
|
+
|
|
202
|
+
def AddMembers(self, **kwargs) -> models.AddMembersResp:
|
|
203
|
+
"""
|
|
204
|
+
Adds one or more users to a group chat.
|
|
205
|
+
|
|
206
|
+
Example:
|
|
207
|
+
response = api.chats.AddMembers(
|
|
208
|
+
chat_id=123456789,
|
|
209
|
+
user_ids=[11111, 22222]
|
|
210
|
+
)
|
|
211
|
+
"""
|
|
212
|
+
req = models.AddMembersReq(**kwargs)
|
|
213
|
+
path = Paths.CHATS_MEMBERS.format(req.chat_id)
|
|
214
|
+
return decode(self.client, "POST", path, models.AddMembersResp, payload=req)
|
|
215
|
+
|
|
216
|
+
def DeleteMember(self, **kwargs) -> models.SimpleQueryResult:
|
|
217
|
+
"""
|
|
218
|
+
Removes a specific user from a group chat.
|
|
219
|
+
You can optionally block the user from rejoining.
|
|
220
|
+
|
|
221
|
+
Example:
|
|
222
|
+
response = api.chats.DeleteMember(
|
|
223
|
+
chat_id=123456789,
|
|
224
|
+
user_id=98765,
|
|
225
|
+
block=True
|
|
226
|
+
)
|
|
227
|
+
"""
|
|
228
|
+
req = models.DeleteMemberReq(**kwargs)
|
|
229
|
+
path = Paths.CHATS_MEMBERS.format(req.chat_id)
|
|
230
|
+
return decode(self.client, "DELETE", path, models.SimpleQueryResult, query=req.model_dump(exclude_none=True))
|
|
231
|
+
|
|
232
|
+
async def GetChatsAsync(self, **kwargs) -> models.GetChatsResp:
|
|
233
|
+
"""
|
|
234
|
+
Async version of GetChats.
|
|
235
|
+
|
|
236
|
+
Example:
|
|
237
|
+
response = await api.chats.GetChatsAsync(
|
|
238
|
+
count=20
|
|
239
|
+
)
|
|
240
|
+
"""
|
|
241
|
+
req = models.GetChatsReq(**kwargs)
|
|
242
|
+
return await adecode(self.client, "GET", Paths.CHATS, models.GetChatsResp, query=req.model_dump(exclude_none=True))
|
|
243
|
+
|
|
244
|
+
async def GetChatAsync(self, **kwargs) -> models.ChatInfo:
|
|
245
|
+
"""
|
|
246
|
+
Async version of GetChat.
|
|
247
|
+
|
|
248
|
+
Example:
|
|
249
|
+
response = await api.chats.GetChatAsync(
|
|
250
|
+
chat_id=123456789
|
|
251
|
+
)
|
|
252
|
+
"""
|
|
253
|
+
req = models.GetChatReq(**kwargs)
|
|
254
|
+
path = Paths.CHATS_ID.format(req.chat_id)
|
|
255
|
+
return await adecode(self.client, "GET", path, models.ChatInfo)
|
|
256
|
+
|
|
257
|
+
async def EditChatAsync(self, **kwargs) -> models.ChatInfo:
|
|
258
|
+
"""
|
|
259
|
+
Async version of EditChat.
|
|
260
|
+
|
|
261
|
+
Example:
|
|
262
|
+
response = await api.chats.EditChatAsync(
|
|
263
|
+
chat_id=123456789,
|
|
264
|
+
title="Updated Chat Title"
|
|
265
|
+
)
|
|
266
|
+
"""
|
|
267
|
+
req = models.EditChatReq(**kwargs)
|
|
268
|
+
path = Paths.CHATS_ID.format(req.chat_id)
|
|
269
|
+
return await adecode(self.client, "PATCH", path, models.ChatInfo, payload=req)
|
|
270
|
+
|
|
271
|
+
async def DeleteChatAsync(self, **kwargs) -> models.SimpleQueryResult:
|
|
272
|
+
"""
|
|
273
|
+
Async version of DeleteChat.
|
|
274
|
+
|
|
275
|
+
Example:
|
|
276
|
+
response = await api.chats.DeleteChatAsync(
|
|
277
|
+
chat_id=123456789
|
|
278
|
+
)
|
|
279
|
+
"""
|
|
280
|
+
req = models.DeleteChatReq(**kwargs)
|
|
281
|
+
path = Paths.CHATS_ID.format(req.chat_id)
|
|
282
|
+
return await adecode(self.client, "DELETE", path, models.SimpleQueryResult)
|
|
283
|
+
|
|
284
|
+
async def SendActionAsync(self, **kwargs) -> models.SimpleQueryResult:
|
|
285
|
+
"""
|
|
286
|
+
Async version of SendAction.
|
|
287
|
+
|
|
288
|
+
Example:
|
|
289
|
+
response = await api.chats.SendActionAsync(
|
|
290
|
+
chat_id=123456789,
|
|
291
|
+
action="typing"
|
|
292
|
+
)
|
|
293
|
+
"""
|
|
294
|
+
req = models.SendActionReq(**kwargs)
|
|
295
|
+
path = Paths.CHATS_ACTIONS.format(req.chat_id)
|
|
296
|
+
return await adecode(self.client, "POST", path, models.SimpleQueryResult, payload=req)
|
|
297
|
+
|
|
298
|
+
async def GetPinnedMessageAsync(self, **kwargs) -> models.Message:
|
|
299
|
+
"""
|
|
300
|
+
Async version of GetPinnedMessage.
|
|
301
|
+
|
|
302
|
+
Example:
|
|
303
|
+
response = await api.chats.GetPinnedMessageAsync(
|
|
304
|
+
chat_id=123456789
|
|
305
|
+
)
|
|
306
|
+
"""
|
|
307
|
+
req = models.GetPinnedMessageReq(**kwargs)
|
|
308
|
+
path = Paths.CHATS_PIN.format(req.chat_id)
|
|
309
|
+
return await adecode(self.client, "GET", path, models.Message)
|
|
310
|
+
|
|
311
|
+
async def PinMessageAsync(self, **kwargs) -> models.SimpleQueryResult:
|
|
312
|
+
"""
|
|
313
|
+
Async version of PinMessage.
|
|
314
|
+
|
|
315
|
+
Example:
|
|
316
|
+
response = await api.chats.PinMessageAsync(
|
|
317
|
+
chat_id=123456789,
|
|
318
|
+
message_id="mid:987654321..."
|
|
319
|
+
)
|
|
320
|
+
"""
|
|
321
|
+
req = models.PinMessageReq(**kwargs)
|
|
322
|
+
path = Paths.CHATS_PIN.format(req.chat_id)
|
|
323
|
+
return await adecode(self.client, "PUT", path, models.SimpleQueryResult, payload=req)
|
|
324
|
+
|
|
325
|
+
async def UnpinMessageAsync(self, **kwargs) -> models.SimpleQueryResult:
|
|
326
|
+
"""
|
|
327
|
+
Async version of UnpinMessage.
|
|
328
|
+
|
|
329
|
+
Example:
|
|
330
|
+
response = await api.chats.UnpinMessageAsync(
|
|
331
|
+
chat_id=123456789
|
|
332
|
+
)
|
|
333
|
+
"""
|
|
334
|
+
req = models.UnpinMessageReq(**kwargs)
|
|
335
|
+
path = Paths.CHATS_PIN.format(req.chat_id)
|
|
336
|
+
return await adecode(self.client, "DELETE", path, models.SimpleQueryResult)
|
|
337
|
+
|
|
338
|
+
async def GetChatMembershipAsync(self, **kwargs) -> models.ChatMember:
|
|
339
|
+
"""
|
|
340
|
+
Async version of GetChatMembership.
|
|
341
|
+
|
|
342
|
+
Example:
|
|
343
|
+
response = await api.chats.GetChatMembershipAsync(
|
|
344
|
+
chat_id=123456789
|
|
345
|
+
)
|
|
346
|
+
"""
|
|
347
|
+
req = models.GetChatMembershipReq(**kwargs)
|
|
348
|
+
path = Paths.CHATS_MEMBERS_ME.format(req.chat_id)
|
|
349
|
+
return await adecode(self.client, "GET", path, models.ChatMember)
|
|
350
|
+
|
|
351
|
+
async def LeaveChatAsync(self, **kwargs) -> models.SimpleQueryResult:
|
|
352
|
+
"""
|
|
353
|
+
Async version of LeaveChat.
|
|
354
|
+
|
|
355
|
+
Example:
|
|
356
|
+
response = await api.chats.LeaveChatAsync(
|
|
357
|
+
chat_id=123456789
|
|
358
|
+
)
|
|
359
|
+
"""
|
|
360
|
+
req = models.LeaveChatReq(**kwargs)
|
|
361
|
+
path = Paths.CHATS_MEMBERS_ME.format(req.chat_id)
|
|
362
|
+
return await adecode(self.client, "DELETE", path, models.SimpleQueryResult)
|
|
363
|
+
|
|
364
|
+
async def GetChatAdminsAsync(self, **kwargs) -> models.GetChatAdminsResp:
|
|
365
|
+
"""
|
|
366
|
+
Async version of GetChatAdmins.
|
|
367
|
+
|
|
368
|
+
Example:
|
|
369
|
+
response = await api.chats.GetChatAdminsAsync(
|
|
370
|
+
chat_id=123456789
|
|
371
|
+
)
|
|
372
|
+
"""
|
|
373
|
+
req = models.GetChatAdminsReq(**kwargs)
|
|
374
|
+
path = Paths.CHATS_MEMBERS_ADMIN.format(req.chat_id)
|
|
375
|
+
return await adecode(self.client, "GET", path, models.GetChatAdminsResp)
|
|
376
|
+
|
|
377
|
+
async def SetChatAdminsAsync(self, **kwargs) -> models.SimpleQueryResult:
|
|
378
|
+
"""
|
|
379
|
+
Async version of SetChatAdmins.
|
|
380
|
+
|
|
381
|
+
Example:
|
|
382
|
+
response = await api.chats.SetChatAdminsAsync(
|
|
383
|
+
chat_id=123456789,
|
|
384
|
+
admins=[ChatAdmin(user_id=98765, role="admin")]
|
|
385
|
+
)
|
|
386
|
+
"""
|
|
387
|
+
req = models.SetChatAdminsReq(**kwargs)
|
|
388
|
+
path = Paths.CHATS_MEMBERS_ADMIN.format(req.chat_id)
|
|
389
|
+
return await adecode(self.client, "POST", path, models.SimpleQueryResult, payload=req)
|
|
390
|
+
|
|
391
|
+
async def DeleteAdminAsync(self, **kwargs) -> models.SimpleQueryResult:
|
|
392
|
+
"""
|
|
393
|
+
Async version of DeleteAdmin.
|
|
394
|
+
|
|
395
|
+
Example:
|
|
396
|
+
response = await api.chats.DeleteAdminAsync(
|
|
397
|
+
chat_id=123456789,
|
|
398
|
+
user_id=98765
|
|
399
|
+
)
|
|
400
|
+
"""
|
|
401
|
+
req = models.DeleteAdminReq(**kwargs)
|
|
402
|
+
path = Paths.CHATS_MEMBERS_ADMIN_ID.format(req.chat_id, req.user_id)
|
|
403
|
+
return await adecode(self.client, "DELETE", path, models.SimpleQueryResult)
|
|
404
|
+
|
|
405
|
+
async def GetChatMembersAsync(self, **kwargs) -> models.GetChatAdminsResp:
|
|
406
|
+
"""
|
|
407
|
+
Async version of GetChatMembers.
|
|
408
|
+
|
|
409
|
+
Example:
|
|
410
|
+
response = await api.chats.GetChatMembersAsync(
|
|
411
|
+
chat_id=123456789,
|
|
412
|
+
count=20
|
|
413
|
+
)
|
|
414
|
+
"""
|
|
415
|
+
req = models.GetChatMembersReq(**kwargs)
|
|
416
|
+
path = Paths.CHATS_MEMBERS.format(req.chat_id)
|
|
417
|
+
return await adecode(self.client, "GET", path, models.GetChatAdminsResp, query=req.model_dump(exclude_none=True))
|
|
418
|
+
|
|
419
|
+
async def AddMembersAsync(self, **kwargs) -> models.AddMembersResp:
|
|
420
|
+
"""
|
|
421
|
+
Async version of AddMembers.
|
|
422
|
+
|
|
423
|
+
Example:
|
|
424
|
+
response = await api.chats.AddMembersAsync(
|
|
425
|
+
chat_id=123456789,
|
|
426
|
+
user_ids=[11111, 22222]
|
|
427
|
+
)
|
|
428
|
+
"""
|
|
429
|
+
req = models.AddMembersReq(**kwargs)
|
|
430
|
+
path = Paths.CHATS_MEMBERS.format(req.chat_id)
|
|
431
|
+
return await adecode(self.client, "POST", path, models.AddMembersResp, payload=req)
|
|
432
|
+
|
|
433
|
+
async def DeleteMemberAsync(self, **kwargs) -> models.SimpleQueryResult:
|
|
434
|
+
"""
|
|
435
|
+
Async version of DeleteMember.
|
|
436
|
+
|
|
437
|
+
Example:
|
|
438
|
+
response = await api.chats.DeleteMemberAsync(
|
|
439
|
+
chat_id=123456789,
|
|
440
|
+
user_id=98765
|
|
441
|
+
)
|
|
442
|
+
"""
|
|
443
|
+
req = models.DeleteMemberReq(**kwargs)
|
|
444
|
+
path = Paths.CHATS_MEMBERS.format(req.chat_id)
|
|
445
|
+
return await adecode(self.client, "DELETE", path, models.SimpleQueryResult, query=req.model_dump(exclude_none=True))
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import aiofiles, asyncio, httpx, logging, mimetypes, os, time
|
|
2
|
+
from typing import Any, List, Optional
|
|
3
|
+
from urllib.parse import urlparse
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from maxbot_api_client_python.tools.uploads import Uploads
|
|
7
|
+
from maxbot_api_client_python.client import Client, decode, adecode
|
|
8
|
+
from maxbot_api_client_python.types.constants import AttachmentType, Paths, UploadType
|
|
9
|
+
from maxbot_api_client_python.types.models import Message, SendFileReq, SendMessageReq, UploadFileReq, Attachment
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
class Helpers:
|
|
14
|
+
def __init__(self, client: Client):
|
|
15
|
+
self.client = client
|
|
16
|
+
self.uploads = Uploads(client)
|
|
17
|
+
|
|
18
|
+
def SendFile(self, **kwargs) -> Optional[Message]:
|
|
19
|
+
"""
|
|
20
|
+
A helper that simplifies sending files to a chat.
|
|
21
|
+
It automatically determines whether the provided file_source is a direct URL or a local file path.
|
|
22
|
+
|
|
23
|
+
Example:
|
|
24
|
+
# Sending a file via URL:
|
|
25
|
+
response = api.helpers.SendFile(
|
|
26
|
+
chat_id=123456789,
|
|
27
|
+
text="Check out this image!",
|
|
28
|
+
file_source="https://example.com/image.png"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Sending a local file:
|
|
32
|
+
response = api.helpers.SendFile(
|
|
33
|
+
chat_id=123456789,
|
|
34
|
+
text="Here is the report.",
|
|
35
|
+
file_source="/local/path/to/report.pdf"
|
|
36
|
+
)
|
|
37
|
+
"""
|
|
38
|
+
req = SendFileReq(**kwargs)
|
|
39
|
+
if self._is_url(req.file_source):
|
|
40
|
+
return self.sendFileByUrl(req)
|
|
41
|
+
return self.sendFileByUpload(req)
|
|
42
|
+
|
|
43
|
+
def sendFileByUrl(self, req: SendFileReq) -> Optional[Message]:
|
|
44
|
+
ext = self._get_extension(req.file_source)
|
|
45
|
+
upload_type = self._determine_upload_type(ext)
|
|
46
|
+
|
|
47
|
+
if upload_type == UploadType.IMAGE:
|
|
48
|
+
attachment = Attachment(type=AttachmentType.IMAGE, payload={"url": req.file_source})
|
|
49
|
+
return self.sendFileInternal(req, attachment)
|
|
50
|
+
|
|
51
|
+
temp_path = None
|
|
52
|
+
try:
|
|
53
|
+
temp_path = self._download_temp_file(req.file_source)
|
|
54
|
+
req.file_source = temp_path
|
|
55
|
+
return self.sendFileByUpload(req)
|
|
56
|
+
except Exception as e:
|
|
57
|
+
logger.error(f"File processing/upload error: {e}")
|
|
58
|
+
return None
|
|
59
|
+
finally:
|
|
60
|
+
if temp_path and os.path.exists(temp_path):
|
|
61
|
+
os.remove(temp_path)
|
|
62
|
+
|
|
63
|
+
def sendFileByUpload(self, req: SendFileReq) -> Optional[Message]:
|
|
64
|
+
ext = self._get_extension(req.file_source)
|
|
65
|
+
upload_type = self._determine_upload_type(ext)
|
|
66
|
+
|
|
67
|
+
upload_req = UploadFileReq(type=upload_type, file_path=req.file_source)
|
|
68
|
+
upload_resp = self.uploads.UploadFile(**upload_req.model_dump_json(indent=4))
|
|
69
|
+
|
|
70
|
+
if not upload_resp or not upload_resp.token:
|
|
71
|
+
logger.error("Upload failed: No token returned.")
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
attachment = self._build_attachment_from_token(upload_type, upload_resp.token, Path(req.file_source).name)
|
|
75
|
+
return self.sendFileInternal(req, attachment)
|
|
76
|
+
|
|
77
|
+
def sendFileInternal(self, req: SendFileReq, attachment: Attachment) -> Message:
|
|
78
|
+
attachments: List[Attachment] = req.attachments or []
|
|
79
|
+
attachments.append(attachment)
|
|
80
|
+
|
|
81
|
+
request_data = SendMessageReq(
|
|
82
|
+
user_id=req.user_id,
|
|
83
|
+
chat_id=req.chat_id,
|
|
84
|
+
text=req.text,
|
|
85
|
+
format=req.format,
|
|
86
|
+
attachments=attachments,
|
|
87
|
+
notify=req.notify,
|
|
88
|
+
link=req.link,
|
|
89
|
+
disable_link_preview=req.disable_link_preview
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
last_err: Optional[Exception] = None
|
|
93
|
+
for _ in range(self.client.max_retries):
|
|
94
|
+
try:
|
|
95
|
+
return decode(self.client, "POST", Paths.MESSAGES, Message, query=request_data.model_dump(exclude_none=True), payload=request_data)
|
|
96
|
+
except Exception as e:
|
|
97
|
+
last_err = e
|
|
98
|
+
if "not.ready" in str(e).lower():
|
|
99
|
+
time.sleep(self.client.retry_delay_sec)
|
|
100
|
+
continue
|
|
101
|
+
break
|
|
102
|
+
|
|
103
|
+
if last_err:
|
|
104
|
+
raise last_err
|
|
105
|
+
raise Exception("Unknown error in sendFileInternal")
|
|
106
|
+
|
|
107
|
+
async def SendFileAsync(self, **kwargs) -> Optional[Message]:
|
|
108
|
+
"""
|
|
109
|
+
Async version of SendFile.
|
|
110
|
+
|
|
111
|
+
Example:
|
|
112
|
+
# Sending a local file asynchronously:
|
|
113
|
+
response = await api.helpers.SendFileAsync(
|
|
114
|
+
chat_id=123456789,
|
|
115
|
+
text="Here is the report.",
|
|
116
|
+
file_source="/local/path/to/report.pdf"
|
|
117
|
+
)
|
|
118
|
+
"""
|
|
119
|
+
req = SendFileReq(**kwargs)
|
|
120
|
+
if self._is_url(req.file_source):
|
|
121
|
+
return await self.sendFileByUrlAsync(req)
|
|
122
|
+
return await self.sendFileByUploadAsync(req)
|
|
123
|
+
|
|
124
|
+
async def sendFileByUrlAsync(self, req: SendFileReq) -> Optional[Message]:
|
|
125
|
+
ext = self._get_extension(req.file_source)
|
|
126
|
+
upload_type = self._determine_upload_type(ext)
|
|
127
|
+
|
|
128
|
+
if upload_type == UploadType.IMAGE:
|
|
129
|
+
attachment = Attachment(type=AttachmentType.IMAGE, payload={"url": req.file_source})
|
|
130
|
+
return await self.sendFileInternalAsync(req, attachment)
|
|
131
|
+
|
|
132
|
+
temp_path = None
|
|
133
|
+
try:
|
|
134
|
+
temp_path = await self._download_temp_file_async(req.file_source)
|
|
135
|
+
req.file_source = temp_path
|
|
136
|
+
return await self.sendFileByUploadAsync(req)
|
|
137
|
+
except Exception as e:
|
|
138
|
+
logger.error(f"Async file processing/upload error: {e}")
|
|
139
|
+
return None
|
|
140
|
+
finally:
|
|
141
|
+
if temp_path and os.path.exists(temp_path):
|
|
142
|
+
await asyncio.to_thread(os.remove, temp_path)
|
|
143
|
+
|
|
144
|
+
async def sendFileByUploadAsync(self, req: SendFileReq) -> Optional[Message]:
|
|
145
|
+
ext = self._get_extension(req.file_source)
|
|
146
|
+
u_type = self._determine_upload_type(ext)
|
|
147
|
+
|
|
148
|
+
upload_req = UploadFileReq(type=u_type, file_path=req.file_source)
|
|
149
|
+
upload_resp = await self.uploads.UploadFileAsync(**upload_req.model_dump_json(indent=4))
|
|
150
|
+
|
|
151
|
+
if not upload_resp or not upload_resp.token:
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
attachment = self._build_attachment_from_token(u_type, upload_resp.token, Path(req.file_source).name)
|
|
155
|
+
return await self.sendFileInternalAsync(req, attachment)
|
|
156
|
+
|
|
157
|
+
async def sendFileInternalAsync(self, req: SendFileReq, attachment: Attachment) -> Message:
|
|
158
|
+
attachments: List[Attachment] = req.attachments or []
|
|
159
|
+
attachments.append(attachment)
|
|
160
|
+
|
|
161
|
+
request_data = SendMessageReq(
|
|
162
|
+
**req.model_dump(exclude={"file_source", "attachments"}),
|
|
163
|
+
attachments=attachments
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
last_err: Optional[Exception] = None
|
|
167
|
+
for _ in range(self.client.max_retries):
|
|
168
|
+
try:
|
|
169
|
+
return await adecode(
|
|
170
|
+
self.client, "POST", Paths.MESSAGES, Message,
|
|
171
|
+
query=request_data.model_dump(exclude_none=True),
|
|
172
|
+
payload=request_data
|
|
173
|
+
)
|
|
174
|
+
except Exception as e:
|
|
175
|
+
last_err = e
|
|
176
|
+
if "not.ready" in str(e).lower():
|
|
177
|
+
await asyncio.sleep(self.client.retry_delay_sec)
|
|
178
|
+
continue
|
|
179
|
+
break
|
|
180
|
+
|
|
181
|
+
if last_err:
|
|
182
|
+
raise last_err
|
|
183
|
+
raise Exception("Unknown error in sendFileInternalAsync")
|
|
184
|
+
|
|
185
|
+
def _is_url(self, source: str) -> bool:
|
|
186
|
+
try:
|
|
187
|
+
result = urlparse(source)
|
|
188
|
+
return all([result.scheme, result.netloc])
|
|
189
|
+
except ValueError:
|
|
190
|
+
return False
|
|
191
|
+
|
|
192
|
+
def _get_extension(self, source: str) -> str:
|
|
193
|
+
parsed = urlparse(source)
|
|
194
|
+
path = parsed.path if parsed.scheme and parsed.netloc else source
|
|
195
|
+
return Path(path).suffix.lower()
|
|
196
|
+
|
|
197
|
+
def _determine_upload_type(self, ext: str) -> UploadType:
|
|
198
|
+
if ext in {".jpg", ".jpeg", ".png", ".webp"}:
|
|
199
|
+
return UploadType.IMAGE
|
|
200
|
+
if ext in {".mp4", ".avi", ".mov"}:
|
|
201
|
+
return UploadType.VIDEO
|
|
202
|
+
if ext in {".mp3", ".ogg", ".wav"}:
|
|
203
|
+
return UploadType.AUDIO
|
|
204
|
+
return UploadType.FILE
|
|
205
|
+
|
|
206
|
+
def _build_attachment_from_token(self, u_type: UploadType, token: str, filename: str) -> Attachment:
|
|
207
|
+
payload: dict[str, Any] = {"token": token}
|
|
208
|
+
if u_type == UploadType.FILE:
|
|
209
|
+
payload["filename"] = filename
|
|
210
|
+
return Attachment(type=AttachmentType(u_type.value), payload=payload)
|
|
211
|
+
|
|
212
|
+
def _download_temp_file(self, url_str: str) -> str:
|
|
213
|
+
headers = {"User-Agent": "maxbot-client/1.0"}
|
|
214
|
+
with httpx.Client() as client:
|
|
215
|
+
with client.stream("GET", url_str, headers=headers, follow_redirects=True) as resp:
|
|
216
|
+
resp.raise_for_status()
|
|
217
|
+
|
|
218
|
+
content_disp = resp.headers.get("Content-Disposition", "")
|
|
219
|
+
filename = None
|
|
220
|
+
if "filename=" in content_disp:
|
|
221
|
+
filename = content_disp.split("filename=")[1].strip('"')
|
|
222
|
+
|
|
223
|
+
if not filename:
|
|
224
|
+
content_type = resp.headers.get("Content-Type", "")
|
|
225
|
+
ext = mimetypes.guess_extension(content_type.split(";")[0])
|
|
226
|
+
if ext:
|
|
227
|
+
filename = f"file{ext}"
|
|
228
|
+
|
|
229
|
+
if not filename:
|
|
230
|
+
filename = Path(urlparse(url_str).path).name or "temp_file.bin"
|
|
231
|
+
|
|
232
|
+
temp_path = f"temp_{int(time.time())}_{filename}"
|
|
233
|
+
|
|
234
|
+
with open(temp_path, "wb") as f:
|
|
235
|
+
for chunk in resp.iter_bytes(chunk_size=8192):
|
|
236
|
+
f.write(chunk)
|
|
237
|
+
return temp_path
|
|
238
|
+
|
|
239
|
+
async def _download_temp_file_async(self, url_str: str) -> str:
|
|
240
|
+
headers = {"User-Agent": "maxbot-client/1.0"}
|
|
241
|
+
async with httpx.AsyncClient() as client:
|
|
242
|
+
async with client.stream("GET", url_str, headers=headers, follow_redirects=True) as resp:
|
|
243
|
+
resp.raise_for_status()
|
|
244
|
+
|
|
245
|
+
content_type = resp.headers.get("Content-Type", "").split(";")[0]
|
|
246
|
+
extension = mimetypes.guess_extension(content_type) or ".bin"
|
|
247
|
+
|
|
248
|
+
url_path_name = Path(urlparse(url_str).path).name
|
|
249
|
+
if url_path_name == "uc" or not url_path_name:
|
|
250
|
+
filename = f"file{extension}"
|
|
251
|
+
else:
|
|
252
|
+
filename = url_path_name if "." in url_path_name else f"{url_path_name}{extension}"
|
|
253
|
+
|
|
254
|
+
temp_path = f"temp_{int(time.time())}_{filename}"
|
|
255
|
+
|
|
256
|
+
async with aiofiles.open(temp_path, "wb") as f:
|
|
257
|
+
async for chunk in resp.aiter_bytes(chunk_size=8192):
|
|
258
|
+
await f.write(chunk)
|
|
259
|
+
return temp_path
|