maxbot-api-client-python 1.1.2__py3-none-any.whl → 2.0.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.
@@ -1,101 +1,109 @@
1
- from maxbot_api_client_python.client import Client, decode, adecode
1
+ from typing import Any
2
+
3
+ from maxbot_api_client_python.client import Client
2
4
  from maxbot_api_client_python.types.constants import Paths
3
- from maxbot_api_client_python.types import models
5
+ from maxbot_api_client_python.types.models import GetSubscriptionsResp, GetUpdatesReq, GetUpdatesResp, SimpleQueryResult, SubscribeReq, UnsubscribeReq
4
6
 
5
7
  class Subscriptions:
6
8
  def __init__(self, client: Client):
7
9
  self.client = client
8
10
 
9
- def GetSubscriptions(self) -> models.GetSubscriptionsResp:
11
+ def get_subscriptions(self) -> GetSubscriptionsResp:
10
12
  """
11
13
  Returns a list of all subscriptions if your bot receives data via a Webhook.
12
14
 
13
15
  Example:
14
- response = api.subscriptions.GetSubscriptions()
16
+ response = bot.subscriptions.get_subscriptions()
15
17
  """
16
- return decode(self.client, "GET", Paths.SUBSCRIPTIONS, models.GetSubscriptionsResp)
18
+ return self.client.decode("GET", Paths.SUBSCRIPTIONS, GetSubscriptionsResp)
17
19
 
18
- def Subscribe(self, **kwargs) -> models.SimpleQueryResult:
20
+ def subscribe(self, **kwargs: Any) -> SimpleQueryResult:
19
21
  """
20
22
  Configures the delivery of bot events via Webhook.
21
23
 
22
24
  Example:
23
- response = api.subscriptions.Subscribe(
25
+ response = bot.subscriptions.subscribe(
24
26
  url="https://webhook.site/endpoint"
25
27
  )
26
28
  """
27
- req = models.SubscribeReq(**kwargs)
28
- return decode(self.client, "POST", Paths.SUBSCRIPTIONS, models.SimpleQueryResult, payload=req)
29
+ req = SubscribeReq(**kwargs)
30
+ query, payload = self.client.split_request(req)
31
+ return self.client.decode("POST", Paths.SUBSCRIPTIONS, SimpleQueryResult, query=query, payload=payload)
29
32
 
30
- def Unsubscribe(self, **kwargs) -> models.SimpleQueryResult:
33
+ def unsubscribe(self, **kwargs: Any) -> SimpleQueryResult:
31
34
  """
32
35
  Unsubscribes the bot from receiving updates via Webhook.
33
36
 
34
37
  Example:
35
- response = api.subscriptions.Unsubscribe(
38
+ response = bot.subscriptions.unsubscribe(
36
39
  url="https://webhook.site/endpoint"
37
40
  )
38
41
  """
39
- req = models.UnsubscribeReq(**kwargs)
40
- return decode(self.client, "DELETE", Paths.SUBSCRIPTIONS, models.SimpleQueryResult, query=req.model_dump(exclude_none=True))
42
+ req = UnsubscribeReq(**kwargs)
43
+ query, payload = self.client.split_request(req)
44
+ return self.client.decode("DELETE", Paths.SUBSCRIPTIONS, SimpleQueryResult, query=query, payload=payload)
41
45
 
42
- def GetUpdates(self, **kwargs) -> models.GetUpdatesResp:
46
+ def get_updates(self, **kwargs: Any) -> GetUpdatesResp:
43
47
  """
44
48
  Fetches new events (incoming messages, bot additions, etc.) from the server.
45
49
  Use this method for long-polling. Provide a Marker to acknowledge previous
46
50
  updates and fetch only new ones.
47
51
 
48
52
  Example:
49
- response = api.subscriptions.GetUpdates(
53
+ response = bot.subscriptions.get_updates(
50
54
  marker=123456789,
51
55
  timeout=30 # seconds to wait for new updates
52
56
  )
53
57
  """
54
- req = models.GetUpdatesReq(**kwargs)
55
- return decode(self.client, "GET", Paths.UPDATES, models.GetUpdatesResp, query=req.model_dump(exclude_none=True))
58
+ req = GetUpdatesReq(**kwargs)
59
+ query, payload = self.client.split_request(req)
60
+ return self.client.decode("GET", Paths.UPDATES, GetUpdatesResp, query=query, payload=payload)
56
61
 
57
- async def GetSubscriptionsAsync(self) -> models.GetSubscriptionsResp:
62
+ async def get_subscriptions_async(self) -> GetSubscriptionsResp:
58
63
  """
59
- Async version of GetSubscriptions.
64
+ Async version of get_subscriptions.
60
65
 
61
66
  Example:
62
- response = await api.subscriptions.GetSubscriptionsAsync()
67
+ response = await bot.subscriptions.get_subscriptions_async()
63
68
  """
64
- return await adecode(self.client, "GET", Paths.SUBSCRIPTIONS, models.GetSubscriptionsResp)
69
+ return await self.client.adecode("GET", Paths.SUBSCRIPTIONS, GetSubscriptionsResp)
65
70
 
66
- async def SubscribeAsync(self, **kwargs) -> models.SimpleQueryResult:
71
+ async def subscribe_async(self, **kwargs: Any) -> SimpleQueryResult:
67
72
  """
68
- Async version of Subscribe.
73
+ Async version of subscribe.
69
74
 
70
75
  Example:
71
- response = await api.subscriptions.SubscribeAsync(
76
+ response = await bot.subscriptions.subscribe_async(
72
77
  url="https://webhook.site/endpoint"
73
78
  )
74
79
  """
75
- req = models.SubscribeReq(**kwargs)
76
- return await adecode(self.client, "POST", Paths.SUBSCRIPTIONS, models.SimpleQueryResult, payload=req)
80
+ req = SubscribeReq(**kwargs)
81
+ query, payload = self.client.split_request(req)
82
+ return await self.client.adecode("POST", Paths.SUBSCRIPTIONS, SimpleQueryResult, query=query, payload=payload)
77
83
 
78
- async def UnsubscribeAsync(self, **kwargs) -> models.SimpleQueryResult:
84
+ async def unsubscribe_async(self, **kwargs: Any) -> SimpleQueryResult:
79
85
  """
80
- Async version of Unsubscribe.
86
+ Async version of unsubscribe.
81
87
 
82
88
  Example:
83
- response = await api.subscriptions.UnsubscribeAsync(
89
+ response = await bot.subscriptions.unsubscribe_async(
84
90
  url="https://webhook.site/endpoint"
85
91
  )
86
92
  """
87
- req = models.UnsubscribeReq(**kwargs)
88
- return await adecode(self.client, "DELETE", Paths.SUBSCRIPTIONS, models.SimpleQueryResult, query=req.model_dump(exclude_none=True))
93
+ req = UnsubscribeReq(**kwargs)
94
+ query, payload = self.client.split_request(req)
95
+ return await self.client.adecode("DELETE", Paths.SUBSCRIPTIONS, SimpleQueryResult, query=query, payload=payload)
89
96
 
90
- async def GetUpdatesAsync(self, **kwargs) -> models.GetUpdatesResp:
97
+ async def get_updates_async(self, **kwargs: Any) -> GetUpdatesResp:
91
98
  """
92
- Async version of GetUpdates.
99
+ Async version of get_updates.
93
100
 
94
101
  Example:
95
- response = await api.subscriptions.GetUpdatesAsync(
102
+ response = await bot.subscriptions.get_updates_async(
96
103
  marker=123456789,
97
104
  timeout=30 # seconds to wait for new updates
98
105
  )
99
106
  """
100
- req = models.GetUpdatesReq(**kwargs)
101
- return await adecode(self.client, "GET", Paths.UPDATES, models.GetUpdatesResp, query=req.model_dump(exclude_none=True))
107
+ req = GetUpdatesReq(**kwargs)
108
+ query, payload = self.client.split_request(req)
109
+ return await self.client.adecode("GET", Paths.UPDATES, GetUpdatesResp, query=query, payload=payload)
@@ -1,10 +1,10 @@
1
1
  import asyncio, logging
2
2
 
3
3
  from pathlib import Path
4
- from typing import Optional
5
- from tenacity import retry, stop_after_attempt, wait_exponential
4
+ from typing import Any, Optional
5
+ from tenacity import AsyncRetrying, retry, stop_after_attempt, wait_exponential
6
6
 
7
- from maxbot_api_client_python.client import Client, decode
7
+ from maxbot_api_client_python.client import Client
8
8
  from maxbot_api_client_python.types.constants import Paths, UploadType
9
9
  from maxbot_api_client_python.types.models import UploadedInfo, UploadFileReq, PhotoAttachmentRequestPayload
10
10
 
@@ -20,13 +20,13 @@ class Uploads:
20
20
  reraise=True,
21
21
  )
22
22
 
23
- def UploadFile(self, **kwargs) -> UploadedInfo:
23
+ def upload_file(self, **kwargs: Any) -> UploadedInfo:
24
24
  """
25
25
  Uploads a file to the server and returns the upload metadata.
26
26
  It seamlessly handles both STEP 1 (obtaining the URL) and STEP 2 (streaming the file).
27
27
 
28
28
  Example:
29
- response = api.uploads.UploadFile(
29
+ response = bot.uploads.upload_file(
30
30
  type=UploadType.IMAGE,
31
31
  file_path="/path/to/image.png"
32
32
  )
@@ -57,35 +57,61 @@ class Uploads:
57
57
 
58
58
  def get_upload_url(self, upload_type: UploadType) -> PhotoAttachmentRequestPayload:
59
59
  """Internal helper: Obtains the target URL for uploading."""
60
- return decode(self.client, "POST", Paths.UPLOADS, PhotoAttachmentRequestPayload, query={"type": upload_type.value})
60
+ return self.client.decode("POST", Paths.UPLOADS, PhotoAttachmentRequestPayload, query={"type": upload_type.value})
61
61
 
62
62
  def upload_multipart(self, upload_url: str, file_path: str) -> Optional[UploadedInfo]:
63
63
  """Internal helper: Streams the file to the obtained URL."""
64
64
  path = Path(file_path)
65
65
  try:
66
66
  with open(path, "rb") as f:
67
- files = {"file": (path.name, f)}
68
- return decode(self.client, "POST", upload_url, UploadedInfo, files=files)
67
+ safe_name = path.name[:255]
68
+ files = {"file": (safe_name, f)}
69
+ return self.client.decode("POST", upload_url, UploadedInfo, files=files)
69
70
  except OSError as e:
70
71
  logger.error(f"Failed to read file for upload: {e}")
71
72
  return None
72
73
 
73
- async def UploadFileAsync(self, **kwargs) -> UploadedInfo:
74
+ async def upload_file_async(self, **kwargs: Any) -> UploadedInfo:
74
75
  """
75
- Async version of UploadFile.
76
+ Async version of upload_file.
76
77
 
77
78
  Example:
78
- response = await api.uploads.UploadFileAsync(
79
+ response = await bot.uploads.upload_file_async(
79
80
  type=UploadType.IMAGE,
80
81
  file_path="/path/to/image.png"
81
82
  )
82
83
  """
83
- return await asyncio.to_thread(self.UploadFile, **kwargs)
84
+ req = UploadFileReq(**kwargs)
85
+ async for attempt in AsyncRetrying(
86
+ stop=stop_after_attempt(3),
87
+ wait=wait_exponential(multiplier=1, min=2, max=10),
88
+ reraise=True
89
+ ):
90
+ with attempt:
91
+ init_resp = await self.get_upload_url_async(req.type)
92
+ if init_resp.url:
93
+ if not req.file_path:
94
+ raise ValueError("file_path must be provided for multipart uploads.")
95
+
96
+ multipart_resp = await self.upload_multipart_async(init_resp.url, req.file_path)
97
+
98
+ if multipart_resp:
99
+ if multipart_resp.token:
100
+ return multipart_resp
101
+
102
+ if multipart_resp.photos:
103
+ first_photo = next(iter(multipart_resp.photos.values()), None)
104
+ if first_photo and first_photo.token:
105
+ multipart_resp.token = first_photo.token
106
+ return multipart_resp
107
+
108
+ if init_resp.token:
109
+ return UploadedInfo(token=init_resp.token)
110
+
111
+ raise Exception("Server did not return token after upload")
84
112
 
85
113
  async def get_upload_url_async(self, upload_type: UploadType) -> PhotoAttachmentRequestPayload:
86
- """Async version of get_upload_url."""
87
- return await asyncio.to_thread(self.get_upload_url, upload_type)
114
+ return await self.client.adecode("POST", Paths.UPLOADS, PhotoAttachmentRequestPayload, query={"type": upload_type.value})
88
115
 
89
116
  async def upload_multipart_async(self, upload_url: str, file_path: str) -> Optional[UploadedInfo]:
90
- """Async version of upload_multipart."""
91
117
  return await asyncio.to_thread(self.upload_multipart, upload_url, file_path)
@@ -87,7 +87,7 @@ class SenderAction(str, Enum):
87
87
 
88
88
  class ChatAdminPermission(str, Enum):
89
89
  READ_ALL_MESSAGES = "read_all_messages"
90
- ADD_REMOVE_USERS = "add_remove_members"
90
+ ADD_REMOVE_MEMBERS = "add_remove_members"
91
91
  ADD_ADMINS = "add_admins"
92
92
  CHANGE_CHAT_PHOTO = "change_chat_info"
93
93
  PIN_MESSAGE = "pin_message"
@@ -6,6 +6,14 @@ from maxbot_api_client_python.types.constants import (
6
6
  LinkedMessageType, SenderAction, ChatAdminPermission, ButtonType, UploadType
7
7
  )
8
8
 
9
+ class Config(BaseModel):
10
+ base_url: str
11
+ token: str
12
+ timeout: int = 35
13
+ ratelimiter: int = 25
14
+ max_retries: int = 3
15
+ retry_delay_sec: int = 3
16
+
9
17
  class MaxBotModel(BaseModel):
10
18
  model_config = ConfigDict(populate_by_name=True, arbitrary_types_allowed=True)
11
19
 
@@ -14,10 +22,9 @@ class APIError(MaxBotModel):
14
22
  message: str
15
23
 
16
24
  class SimpleQueryResult(MaxBotModel):
17
- success: bool = Field(..., alias="success")
25
+ success: bool = Field(True, alias="success")
18
26
  message: str | None = Field(None, alias="message")
19
27
 
20
-
21
28
  class User(MaxBotModel):
22
29
  user_id: int
23
30
  first_name: str
@@ -59,7 +66,6 @@ class Recipient(MaxBotModel):
59
66
  chat_type: ChatType
60
67
  user_id: int | None = None
61
68
 
62
-
63
69
  class Image(MaxBotModel):
64
70
  url: str
65
71
 
@@ -76,7 +82,6 @@ class StickerData(MaxBotModel):
76
82
  url: str | None = None
77
83
  code: str | None = None
78
84
 
79
-
80
85
  class MediaPayload(MaxBotModel):
81
86
  url: str | None = None
82
87
  token: str | None = None
@@ -142,7 +147,6 @@ class Attachment(MaxBotModel):
142
147
  latitude: float | None = None
143
148
  longitude: float | None = None
144
149
 
145
-
146
150
  class BotPatch(MaxBotModel):
147
151
  name: str | None = None
148
152
  username: str | None = None
@@ -150,7 +154,6 @@ class BotPatch(MaxBotModel):
150
154
  commands: list[BotCommand] | None = None
151
155
  photo: PhotoAttachmentRequestPayload | None = None
152
156
 
153
-
154
157
  class MarkupElement(MaxBotModel):
155
158
  type: MarkupType
156
159
  from_: int = Field(alias="from")
@@ -195,7 +198,6 @@ class Message(MaxBotModel):
195
198
  class MessagesList(MaxBotModel):
196
199
  messages: list[Message]
197
200
 
198
-
199
201
  class Chat(MaxBotModel):
200
202
  chat_id: int
201
203
  type: ChatType
@@ -226,10 +228,9 @@ class ChatInfo(MaxBotModel):
226
228
  is_public: bool
227
229
  link: str | None = None
228
230
  description: str | None = None
229
- dialog_with_user: list[DialogWithUser] | None = None
231
+ dialog_with_user: DialogWithUser | None = None
230
232
  chat_message_id: str | None = None
231
- pinned_message: list[Message] | None = None
232
-
233
+ pinned_message: Message | None = None
233
234
 
234
235
  class Callback(MaxBotModel):
235
236
  timestamp: int
@@ -258,73 +259,89 @@ class Subscription(MaxBotModel):
258
259
  time: int
259
260
  update_types: list[UpdateType] | None = None
260
261
 
261
-
262
262
  class GetChatsReq(MaxBotModel):
263
- count: int | None = Field(None, alias="count")
264
- marker: int | None = Field(None, alias="marker")
263
+ count: int | None = Field(None, alias="count", json_schema_extra={"in_query": True})
264
+ marker: int | None = Field(None, alias="marker", json_schema_extra={"in_query": True})
265
265
 
266
266
  class GetChatsResp(MaxBotModel):
267
267
  chats: list[Chat]
268
268
  marker: int | None = None
269
269
 
270
270
  class GetChatReq(MaxBotModel):
271
- chat_id: int = Field(..., alias="chatId")
271
+ # API Path: GET /chats/{chatId}
272
+ # Path parameters strictly use camelCase.
273
+ chat_id: int = Field(..., alias="chatId", json_schema_extra={"in_path": True})
272
274
 
273
275
  class EditChatReq(MaxBotModel):
274
- chat_id: int = Field(..., alias="chatId")
276
+ # API Path: PATCH /chats/{chatId}
277
+ # Path parameters strictly use camelCase.
278
+ chat_id: int = Field(..., alias="chatId", json_schema_extra={"in_path": True})
275
279
  icon: Image | None = None
276
280
  title: str | None = None
277
281
  pin: str | None = None
278
282
  notify: bool | None = None
279
283
 
280
284
  class DeleteChatReq(MaxBotModel):
281
- chat_id: int = Field(..., alias="chatId")
285
+ # API Path: DELETE /chats/{chatId}
286
+ # Path parameters strictly use camelCase.
287
+ chat_id: int = Field(..., alias="chatId", json_schema_extra={"in_path": True})
282
288
 
283
289
  class SendActionReq(MaxBotModel):
284
- chat_id: int = Field(..., alias="chatId")
290
+ # API Path: POST /chats/{chatId}/actions
291
+ # Path parameters strictly use camelCase.
292
+ chat_id: int = Field(..., alias="chatId", json_schema_extra={"in_path": True})
285
293
  action: SenderAction
286
294
 
287
295
  class PinMessageReq(MaxBotModel):
288
- chat_id: int = Field(..., alias="chatId")
296
+ chat_id: int = Field(..., alias="chatId", json_schema_extra={"in_path": True})
289
297
  message_id: str
290
298
  notify: bool | None = None
291
299
 
292
300
  class UnpinMessageReq(MaxBotModel):
293
- chat_id: int = Field(..., alias="chatId")
301
+ chat_id: int = Field(..., alias="chatId", json_schema_extra={"in_path": True})
294
302
 
295
303
  class GetPinnedMessageReq(MaxBotModel):
296
- chat_id: int = Field(..., alias="chatId")
304
+ chat_id: int = Field(..., alias="chatId", json_schema_extra={"in_path": True})
297
305
 
298
306
  class GetChatMembershipReq(MaxBotModel):
299
- chat_id: int = Field(..., alias="chatId")
307
+ chat_id: int = Field(..., alias="chatId", json_schema_extra={"in_path": True})
300
308
 
301
309
  class LeaveChatReq(MaxBotModel):
302
- chat_id: int = Field(..., alias="chatId")
310
+ chat_id: int = Field(..., alias="chatId", json_schema_extra={"in_path": True})
303
311
 
304
312
  class GetChatAdminsReq(MaxBotModel):
305
- chat_id: int = Field(..., alias="chatId")
313
+ chat_id: int = Field(..., alias="chatId", json_schema_extra={"in_path": True})
306
314
 
307
315
  class GetChatAdminsResp(MaxBotModel):
308
316
  members: list[ChatMember]
309
317
  marker: int | None = None
310
318
 
319
+ class GetChatMembersResp(MaxBotModel):
320
+ members: list[ChatMember]
321
+ marker: int | None = None
322
+
311
323
  class SetChatAdminsReq(MaxBotModel):
312
- chat_id: int = Field(..., alias="chatId")
324
+ chat_id: int = Field(..., alias="chatId", json_schema_extra={"in_path": True})
313
325
  admins: list[ChatAdmin]
314
326
  marker: int | None = None
315
327
 
316
328
  class DeleteAdminReq(MaxBotModel):
317
- chat_id: int = Field(..., alias="chatId")
318
- user_id: int = Field(..., alias="userId")
329
+ # API Path: DELETE /chats/{chatId}/members/admins/{userId}
330
+ # Both path parameters require camelCase according to the API specification.
331
+ chat_id: int = Field(..., alias="chatId", json_schema_extra={"in_path": True})
332
+ user_id: int = Field(..., alias="userId", json_schema_extra={"in_path": True})
319
333
 
320
334
  class GetChatMembersReq(MaxBotModel):
321
- chat_id: int = Field(..., alias="chatId")
322
- user_ids: list[int] | None = Field(None, alias="user_ids")
323
- marker: int | None = Field(None, alias="marker")
324
- count: int | None = Field(None, alias="count")
335
+ chat_id: int = Field(..., alias="chatId", json_schema_extra={"in_path": True})
336
+ user_ids: list[int] | None = Field(None, alias="user_ids", json_schema_extra={"in_query": True})
337
+ marker: int | None = Field(None, alias="marker", json_schema_extra={"in_query": True})
338
+ count: int | None = Field(None, alias="count", json_schema_extra={"in_query": True})
325
339
 
326
340
  class AddMembersReq(MaxBotModel):
327
- chat_id: int = Field(..., alias="chatId")
341
+ # API Path: POST /chats/{chatId}/members
342
+ # Path uses camelCase ('chatId').
343
+ # Payload array must be strictly serialized as 'user_ids' (snake_case).
344
+ chat_id: int = Field(..., alias="chatId", json_schema_extra={"in_path": True})
328
345
  user_ids: list[int] | None = None
329
346
 
330
347
  class FailedUserDetails(MaxBotModel):
@@ -336,33 +353,28 @@ class AddMembersResp(SimpleQueryResult):
336
353
  failed_user_details: list[FailedUserDetails] | None = Field(None, alias="failed_user_details")
337
354
 
338
355
  class DeleteMemberReq(MaxBotModel):
339
- chat_id: int = Field(..., alias="chatId")
340
- user_id: int = Field(..., alias="userId")
341
- block: bool | None = Field(None, alias="block")
342
-
343
-
344
- class GetMessagesReq(MaxBotModel):
345
- chat_id: int | None = Field(None, alias="chat_id")
346
- message_ids: list[str] | None = Field(None, alias="message_ids")
347
- from_: int | None = Field(None, alias="from")
348
- to: int | None = Field(None, alias="to")
349
- count: int | None = Field(None, alias="count")
356
+ # API Path: DELETE /chats/{chatId}/members?user_id={user_id}&block=true
357
+ # Mixed conventions: Path parameter is camelCase ('chatId'),
358
+ # but query parameters use snake_case ('user_id').
359
+ chat_id: int = Field(..., alias="chatId", json_schema_extra={"in_path": True})
360
+ user_id: int = Field(..., alias="user_id", json_schema_extra={"in_query": True})
361
+ block: bool | None = Field(None, alias="block", json_schema_extra={"in_query": True})
350
362
 
351
363
  class SendMessageReq(MaxBotModel):
352
- user_id: int | None = Field(None, alias="user_id")
353
- chat_id: int | None = Field(None, alias="chat_id")
364
+ user_id: int | None = Field(None, alias="user_id", json_schema_extra={"in_query": True})
365
+ chat_id: int | None = Field(None, alias="chat_id", json_schema_extra={"in_query": True})
354
366
  text: str | None = None
355
367
  format: Format | None = None
356
368
  notify: bool | None = None
357
369
  attachments: list[Attachment] | None = None
358
370
  link: NewMessageLink | None = None
359
- disable_link_preview: bool | None = Field(None, alias="disable_link_preview")
371
+ disable_link_preview: bool | None = Field(None, alias="disable_link_preview", json_schema_extra={"in_query": True})
360
372
 
361
373
  class SendMessageResp(MaxBotModel):
362
374
  message: Message
363
375
 
364
376
  class EditMessageReq(MaxBotModel):
365
- message_id: str = Field(..., alias="message_id")
377
+ message_id: str = Field(..., alias="message_id", json_schema_extra={"in_query": True})
366
378
  text: str | None = None
367
379
  attachments: list[Attachment] | None = None
368
380
  link: NewMessageLink | None = None
@@ -370,10 +382,17 @@ class EditMessageReq(MaxBotModel):
370
382
  format: Format | None = None
371
383
 
372
384
  class DeleteMessageReq(MaxBotModel):
373
- message_id: str = Field(..., alias="message_id")
385
+ message_id: str = Field(..., alias="message_id", json_schema_extra={"in_query": True})
374
386
 
375
387
  class GetMessageReq(MaxBotModel):
376
- message_id: str = Field(..., alias="message_id")
388
+ message_id: str = Field(..., alias="message_id", json_schema_extra={"in_path": True})
389
+
390
+ class GetMessagesReq(MaxBotModel):
391
+ chat_id: int | None = Field(None, alias="chat_id", json_schema_extra={"in_query": True})
392
+ message_ids: list[str] | None = Field(None, alias="message_ids", json_schema_extra={"in_query": True})
393
+ from_: int | None = Field(None, alias="from", json_schema_extra={"in_query": True})
394
+ to: int | None = Field(None, alias="to", json_schema_extra={"in_query": True})
395
+ count: int | None = Field(None, alias="count", json_schema_extra={"in_query": True})
377
396
 
378
397
  class VideoUrls(MaxBotModel):
379
398
  mp4_1080: str | None = None
@@ -391,7 +410,7 @@ class VideoInfo(MaxBotModel):
391
410
  url: str
392
411
 
393
412
  class GetVideoInfoReq(MaxBotModel):
394
- video_token: str = Field(..., alias="video_token")
413
+ video_token: str = Field(..., alias="video_token", json_schema_extra={"in_path": True})
395
414
 
396
415
  class GetVideoInfoResp(MaxBotModel):
397
416
  token: str
@@ -402,22 +421,21 @@ class GetVideoInfoResp(MaxBotModel):
402
421
  duration: int | None = None
403
422
 
404
423
  class AnswerCallbackReq(MaxBotModel):
405
- callback_id: str = Field(..., alias="callback_id")
424
+ callback_id: str = Field(..., alias="callback_id", json_schema_extra={"in_query": True})
406
425
  message: NewMessageBody | None = None
407
426
  notification: str | None = None
408
427
 
409
428
  class SendFileReq(MaxBotModel):
410
- user_id: int | None = Field(None, alias="user_id")
411
- chat_id: int | None = Field(None, alias="chat_id")
429
+ user_id: int | None = Field(None, alias="userId", json_schema_extra={"in_query": True})
430
+ chat_id: int | None = Field(None, alias="chatId", json_schema_extra={"in_query": True})
412
431
  text: str | None = None
413
432
  format: Format | None = None
414
433
  notify: bool | None = None
415
- file_source: str
434
+ file_source: str = Field(..., json_schema_extra={"in_path": True})
416
435
  link: NewMessageLink | None = None
417
- disable_link_preview: bool | None = Field(None, alias="disable_link_preview")
436
+ disable_link_preview: bool | None = Field(None, alias="disable_link_preview", json_schema_extra={"in_query": True})
418
437
  attachments: list[Attachment] | None = None
419
438
 
420
-
421
439
  class GetSubscriptionsResp(MaxBotModel):
422
440
  subscriptions: list[Subscription]
423
441
 
@@ -427,26 +445,25 @@ class SubscribeReq(MaxBotModel):
427
445
  secret: str | None = None
428
446
 
429
447
  class UnsubscribeReq(MaxBotModel):
430
- url: str = Field(..., alias="url")
448
+ url: str = Field(..., alias="url", json_schema_extra={"in_query": True})
431
449
 
432
450
  class GetUpdatesReq(MaxBotModel):
433
- limit: int | None = Field(None, alias="limit")
434
- timeout: int | None = Field(None, alias="timeout")
435
- marker: int | None = Field(None, alias="marker")
436
- types: list[UpdateType] | None = Field(None, alias="types")
451
+ limit: int | None = Field(None, alias="limit", json_schema_extra={"in_query": True})
452
+ timeout: int | None = Field(None, alias="timeout", json_schema_extra={"in_query": True})
453
+ marker: int | None = Field(None, alias="marker", json_schema_extra={"in_query": True})
454
+ types: list[UpdateType] | None = Field(None, alias="types", json_schema_extra={"in_query": True})
437
455
 
438
456
  class GetUpdatesResp(MaxBotModel):
439
457
  updates: list[Update]
440
458
  marker: int
441
459
 
442
-
443
460
  class UploadFileReq(MaxBotModel):
444
- type: UploadType = Field(..., alias="type")
445
- upload_url: str | None = None
446
- file_path: str | None = None
461
+ type: UploadType = Field(..., alias="type", json_schema_extra={"in_query": True})
462
+ upload_url: str | None = Field(None, json_schema_extra={"in_path": True})
463
+ file_path: str | None = Field(None, json_schema_extra={"in_path": True})
447
464
 
448
465
  class UploadTypeReq(MaxBotModel):
449
- type: UploadType = Field(..., alias="type")
466
+ type: UploadType = Field(..., alias="type", json_schema_extra={"in_query": True})
450
467
 
451
468
  class UploadFileMultipartReq(MaxBotModel):
452
469
  upload_url: str
@@ -37,7 +37,9 @@ def attach_sticker(url: str | None = None, code: str | None = None) -> Attachmen
37
37
  )
38
38
 
39
39
  def attach_contact(name: str, phone: str, contact_id: int | None = None) -> Attachment:
40
- vcf_info = f"BEGIN:VCARD\nVERSION:3.0\nFN:{name}\nTEL:{phone}\nEND:VCARD"
40
+ safe_name = name.replace("\n", " ").replace("\r", "")
41
+ safe_phone = phone.replace("\n", "").replace("\r", "")
42
+ vcf_info = f"BEGIN:VCARD\nVERSION:3.0\nFN:{safe_name}\nTEL:{safe_phone}\nEND:VCARD"
41
43
  return Attachment(
42
44
  type=AttachmentType.CONTACT,
43
45
  payload=ContactAttachmentPayload(