botapi-py 1.3.2__tar.gz → 1.4.0__tar.gz

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,7 +1,7 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: botapi-py
3
- Version: 1.3.2
4
- Summary: A simple and flexible Python library to conveniently build Telegram bots using the Bot API.
3
+ Version: 1.4.0
4
+ Summary: A simple and flexible Python library to conveniently build fast Telegram bots using the Bot API.
5
5
  Requires-Dist: httpx>=0.28.1
6
6
  Requires-Dist: pydantic>=2.12.0
7
7
  Requires-Dist: redis>=6.4.0 ; extra == 'redis'
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "botapi-py"
3
- version = "1.3.2"
4
- description = "A simple and flexible Python library to conveniently build Telegram bots using the Bot API."
3
+ version = "1.4.0"
4
+ description = "A simple and flexible Python library to conveniently build fast Telegram bots using the Bot API."
5
5
  # readme = "README.md"
6
6
  # authors = [
7
7
  # { name = "CalledLuca", email = "me@calledluca.it" }
@@ -0,0 +1,4 @@
1
+ from . import enums, errors, types, utils, dispatchers
2
+ from .methods import Bot
3
+
4
+ __all__ = ['Bot', 'types', 'enums', 'utils', 'errors', 'dispatchers']
@@ -0,0 +1,4 @@
1
+ from . import enums, errors, types, utils, dispatchers
2
+ from .methods import Bot
3
+
4
+ __all__ = ['Bot', 'types', 'enums', 'utils', 'errors', 'dispatchers']
@@ -20,7 +20,7 @@ MultipartFile: TypeAlias = tuple[
20
20
  MultipartFiles: TypeAlias = dict[str, MultipartFile]
21
21
 
22
22
 
23
- class BaseBotAPI:
23
+ class BaseBot:
24
24
  def __init__(
25
25
  self,
26
26
  token: str,
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import asyncio
2
4
  import logging
3
5
  import signal
@@ -17,7 +19,7 @@ class GetUpdatesAsyncDispatcher:
17
19
  update_handler: UpdateHandler
18
20
  tasks_shutdown_timeout: float
19
21
  tasks: set[asyncio.Task] # prevent gc from collecting tasks before they finish
20
- bot: botapi.api.BotAPI
22
+ bot: botapi.methods.Bot
21
23
  reconnect_delay: float
22
24
  allowed_updates: list[str]
23
25
  offset: int
@@ -25,7 +27,7 @@ class GetUpdatesAsyncDispatcher:
25
27
  def __init__(
26
28
  self,
27
29
  update_handler: UpdateHandler,
28
- bot: botapi.api.BotAPI,
30
+ bot: botapi.methods.Bot,
29
31
  allowed_updates: list[str], # botapi.enums.UpdateType
30
32
  reconnect_delay: float = 5,
31
33
  shutdown_wait_timeout: float = 3
@@ -37,7 +39,7 @@ class GetUpdatesAsyncDispatcher:
37
39
  Args:
38
40
  update_handler (UpdateHandler):
39
41
  Async function to handle received messages.
40
- bot (botapi.api.BotAPI):
42
+ bot (botapi.Bot):
41
43
  A `Bot` instance.
42
44
  allowed_updates (list[botapi.enums.UpdateType]):
43
45
  List of allowed update types.
@@ -85,7 +85,7 @@ class RedisPubSubAsyncDispatcher:
85
85
  then validated it and loads it into an Update object with Pydantic.
86
86
  '''
87
87
  data = json.loads(message)
88
- return Update.de_json(data)
88
+ return Update.from_dict(data)
89
89
 
90
90
  async def _dispatch(self, message: str) -> None:
91
91
  '''
@@ -1,4 +1,6 @@
1
1
  import enum
2
+ from typing import Union
3
+
2
4
 
3
5
  class ChatType(enum.StrEnum):
4
6
  PRIVATE = 'private'
@@ -100,11 +102,9 @@ class ServiceMessage(enum.StrEnum):
100
102
  WEB_APP_DATA = 'web_app_data'
101
103
  SUGGESTED_POST = 'suggested_post_info'
102
104
 
103
- # @boilerplate.extend_enum(MediaType, ServiceMessage)
104
- class MessageType(enum.StrEnum):
105
+ class OtherMessageType(enum.StrEnum):
105
106
  '''
106
- The content of this message.
107
- All of these fields should be mutually exclusive.
107
+ The type of this message (that does NOT contain multimedia or service content).
108
108
  '''
109
109
  TEXT = 'text'
110
110
  CHECKLIST = 'checklist'
@@ -116,68 +116,17 @@ class MessageType(enum.StrEnum):
116
116
  LOCATION = 'location'
117
117
  CONTACT = 'contact'
118
118
 
119
- # MediaType - Python sucks.
120
- ANIMATION = 'animation'
121
- AUDIO = 'audio'
122
- DOCUMENT = 'document'
123
- PHOTO = 'photo'
124
- STICKER = 'sticker'
125
- VIDEO = 'video'
126
- VIDEO_NOTE = 'video_note'
127
- VOICE = 'voice'
128
- STORY = 'story'
129
- PAID_MEDIA = 'paid_media'
130
-
131
- # ServiceMessage - Python sucks.
132
- NEW_CHAT_MEMBERS = 'new_chat_members'
133
- LEFT_CHAT_MEMBER = 'left_chat_member'
134
- NEW_CHAT_TITLE = 'new_chat_title'
135
- NEW_CHAT_PHOTO = 'new_chat_photo'
136
- DELETE_CHAT_PHOTO = 'delete_chat_photo'
137
- GROUP_CHAT_CREATED = 'group_chat_created'
138
- SUPERGROUP_CHAT_CREATED = 'supergroup_chat_created'
139
- CHANNEL_CHAT_CREATED = 'channel_chat_created'
140
- MESSAGE_AUTO_DELETE_TIMER_CHANGED = 'message_auto_delete_timer_changed'
141
- MIGRATE_TO_CHAT_ID = 'migrate_to_chat_id'
142
- MIGRATE_FROM_CHAT_ID = 'migrate_from_chat_id'
143
- PINNED_MESSAGE = 'pinned_message'
144
- SUCCESSFUL_PAYMENT = 'successful_payment'
145
- REFUNDED_PAYMENT = 'refunded_payment'
146
- USERS_SHARED = 'users_shared'
147
- CHAT_SHARED = 'chat_shared'
148
- GIFT = 'gift'
149
- UNIQUE_GIFT = 'unique_gift'
150
- CONNECTED_WEBSITE = 'connected_website'
151
- WRITE_ACCESS_ALLOWED = 'write_access_allowed'
152
- PASSPORT_DATA = 'passport_data'
153
- PROXIMITY_ALERT_TRIGGERED = 'proximity_alert_triggered'
154
- BOOST_ADDED = 'boost_added'
155
- CHAT_BACKGROUND_SET = 'chat_background_set'
156
- CHECKLIST_TASKS_DONE = 'checklist_tasks_done'
157
- CHECKLIST_TASKS_ADDED = 'checklist_tasks_added'
158
- DIRECT_MESSAGE_PRICE_CHANGED = 'direct_message_price_changed'
159
- FORUM_TOPIC_CREATED = 'forum_topic_created'
160
- FORUM_TOPIC_EDITED = 'forum_topic_edited'
161
- FORUM_TOPIC_CLOSED = 'forum_topic_closed'
162
- FORUM_TOPIC_REOPENED = 'forum_topic_reopened'
163
- GENERAL_FORUM_TOPIC_HIDDEN = 'general_forum_topic_hidden'
164
- GENERAL_FORUM_TOPIC_UNHIDDEN = 'general_forum_topic_unhidden'
165
- GIVEAWAY = 'giveaway'
166
- GIVEAWAY_CREATED = 'giveaway_created'
167
- GIVEAWAY_WINNERS = 'giveaway_winners'
168
- GIVEAWAY_COMPLETED = 'giveaway_completed'
169
- PAID_MESSAGE_PRICE_CHANGED = 'paid_message_price_changed'
170
- SUGGESTED_POST_APPROVED = 'suggested_post_approved'
171
- SUGGESTED_POST_APPROVAL_FAILED = 'suggested_post_approval_failed'
172
- SUGGESTED_POST_DECLINED = 'suggested_post_declined'
173
- SUGGESTED_POST_PAID = 'suggested_post_paid'
174
- SUGGESTED_POST_REFUNDED = 'suggested_post_refunded'
175
- VIDEO_CHAT_SCHEDULED = 'video_chat_scheduled'
176
- VIDEO_CHAT_STARTED = 'video_chat_started'
177
- VIDEO_CHAT_ENDED = 'video_chat_ended'
178
- VIDEO_CHAT_PARTICIPANTS_INVITED = 'video_chat_participants_invited'
179
- WEB_APP_DATA = 'web_app_data'
180
- SUGGESTED_POST = 'suggested_post_info'
119
+ MessageType = Union[
120
+ MediaType,
121
+ ServiceMessage,
122
+ OtherMessageType
123
+ ]
124
+ '''
125
+ The type of the **actual** message content.
126
+
127
+ All of the fields are mutually exclusive, since a message
128
+ can only be of one 'type' at a time.
129
+ '''
181
130
 
182
131
  class ChatMemberStatus(enum.StrEnum):
183
132
  '''
@@ -314,7 +314,7 @@ from .types import (
314
314
  BoolAdapter,
315
315
  )
316
316
 
317
- class BotAPI(core.BaseBotAPI):
317
+ class Bot(core.BaseBot):
318
318
  '''
319
319
  Creates an instance of a new Bot API client.
320
320
 
@@ -10,7 +10,7 @@ from typing import TYPE_CHECKING, Union, Optional, Literal, List, Any
10
10
  from . import enums
11
11
 
12
12
  if TYPE_CHECKING:
13
- from . import api
13
+ from . import methods
14
14
 
15
15
  class MaybeInaccessibleMessage(BaseModel, frozen=True):
16
16
  message_id: int
@@ -127,15 +127,118 @@ class Message(MaybeInaccessibleMessage, frozen=True):
127
127
  web_app_data: Optional[WebAppData] = Field(default=None)
128
128
  reply_markup: Optional[InlineKeyboardMarkup] = Field(default=None)
129
129
 
130
- async def delete(self, bot: api.BotAPI) -> bool:
130
+ async def delete(
131
+ self,
132
+ bot: methods.Bot
133
+ ) -> bool:
134
+ '''
135
+ Deletes this message.
136
+ Shortcut for `bot.delete_message`.
137
+ '''
131
138
  return await bot.delete_message(
132
139
  chat_id=self.chat.id,
133
140
  message_id=self.message_id
134
141
  )
135
142
 
143
+ async def pin(
144
+ self,
145
+ bot: methods.Bot,
146
+ disable_notification: bool | None = None,
147
+ ) -> bool:
148
+ '''
149
+ Pins this message in the chat.
150
+ Shortcut for `bot.pin_chat_message`.
151
+ '''
152
+ return await bot.pin_chat_message(
153
+ chat_id=self.chat.id,
154
+ message_id=self.message_id,
155
+ disable_notification=disable_notification
156
+ )
157
+
158
+ async def unpin(
159
+ self,
160
+ bot: methods.Bot
161
+ ) -> bool:
162
+ '''
163
+ Unpins this message in the chat.
164
+ Shortcut for `bot.unpin_chat_message`.
165
+ '''
166
+ return await bot.unpin_chat_message(
167
+ chat_id=self.chat.id,
168
+ message_id=self.message_id
169
+ )
170
+
171
+ async def forward(
172
+ self,
173
+ bot: methods.Bot,
174
+ chat_id: int | str,
175
+ disable_notification: bool | None = None,
176
+ protect_content: bool | None = None,
177
+ message_thread_id: int | None = None,
178
+ direct_messages_topic_id: int | None = None,
179
+ video_start_timestamp: int | None = None,
180
+ # suggested_post_parameters: SuggestedPostParameters | None = None,
181
+ ) -> Message:
182
+ '''
183
+ Forwards this message to another chat.
184
+ Shortcut for `bot.forward_message`.
185
+ '''
186
+ return await bot.forward_message(
187
+ chat_id=chat_id,
188
+ from_chat_id=self.chat.id,
189
+ message_id=self.message_id,
190
+ message_thread_id=message_thread_id,
191
+ direct_messages_topic_id=direct_messages_topic_id,
192
+ video_start_timestamp=video_start_timestamp,
193
+ disable_notification=disable_notification,
194
+ protect_content=protect_content,
195
+ # suggested_post_parameters=suggested_post_parameters
196
+ )
197
+
198
+ async def copy_to(
199
+ self,
200
+ bot: methods.Bot,
201
+ chat_id: int | str,
202
+ message_thread_id: int | None = None,
203
+ direct_messages_topic_id: int | None = None,
204
+ video_start_timestamp: int | None = None,
205
+ # caption: str | None = None,
206
+ # parse_mode: str | None = 'HTML',
207
+ # caption_entities: List[MessageEntity] | None = None,
208
+ # show_caption_above_media: bool | None = None,
209
+ disable_notification: bool | None = None,
210
+ protect_content: bool | None = None,
211
+ # allow_paid_broadcast: bool | None = None,
212
+ # suggested_post_parameters: SuggestedPostParameters | None = None,
213
+ # reply_parameters: ReplyParameters | None = None,
214
+ # reply_markup: InlineKeyboardMarkup | ReplyKeyboardMarkup | ReplyKeyboardRemove | ForceReply | None = None
215
+ ) -> MessageId:
216
+ '''
217
+ Copies this message to another chat.
218
+ Shortcut for `bot.copy_message`.
219
+ '''
220
+ return await bot.copy_message(
221
+ chat_id=chat_id,
222
+ from_chat_id=self.chat.id,
223
+ message_id=self.message_id,
224
+ message_thread_id=message_thread_id,
225
+ direct_messages_topic_id=direct_messages_topic_id,
226
+ video_start_timestamp=video_start_timestamp,
227
+ # caption=caption,
228
+ # parse_mode=parse_mode,
229
+ # caption_entities=caption_entities,
230
+ # show_caption_above_media=show_caption_above_media,
231
+ disable_notification=disable_notification,
232
+ protect_content=protect_content,
233
+ # allow_paid_broadcast=allow_paid_broadcast,
234
+ # suggested_post_parameters=suggested_post_parameters,
235
+ # reply_parameters=reply_parameters,
236
+ # reply_markup=reply_markup
237
+ )
238
+
136
239
  async def edit_text(
137
240
  self,
138
- bot: api.BotAPI,
241
+ bot: methods.Bot,
139
242
  text: str,
140
243
  # inline_message_id: Optional[str] = None,
141
244
  parse_mode: str | None = 'HTML',
@@ -143,6 +246,11 @@ class Message(MaybeInaccessibleMessage, frozen=True):
143
246
  link_preview_options: LinkPreviewOptions | None = None,
144
247
  reply_markup: InlineKeyboardMarkup | None = None,
145
248
  ) -> Message | bool:
249
+ '''
250
+ Edits the text of this message.
251
+ Shortcut for `bot.edit_message_text`.
252
+ You can pass **either** `parse_mode` or `entities` to format the message text.
253
+ '''
146
254
  return await bot.edit_message_text(
147
255
  chat_id=self.chat.id,
148
256
  message_id=self.message_id,
@@ -157,10 +265,14 @@ class Message(MaybeInaccessibleMessage, frozen=True):
157
265
 
158
266
  async def edit_reply_markup(
159
267
  self,
160
- bot: api.BotAPI,
268
+ bot: methods.Bot,
161
269
  # inline_message_id: Optional[str] = None,
162
270
  reply_markup: InlineKeyboardMarkup | None = None,
163
271
  ) -> Message | bool:
272
+ '''
273
+ Edits the reply markup (inline keyboard) of this message.
274
+ Shortcut for `bot.edit_message_reply_markup`.
275
+ '''
164
276
  return await bot.edit_message_reply_markup(
165
277
  chat_id=self.chat.id,
166
278
  message_id=self.message_id,
@@ -171,7 +283,7 @@ class Message(MaybeInaccessibleMessage, frozen=True):
171
283
 
172
284
  async def reply_text(
173
285
  self,
174
- bot: api.BotAPI,
286
+ bot: methods.Bot,
175
287
  text: str,
176
288
  parse_mode: str | None = 'HTML',
177
289
  entities: list[MessageEntity] | None = None,
@@ -194,7 +306,7 @@ class Message(MaybeInaccessibleMessage, frozen=True):
194
306
 
195
307
  You can pass **either** `reply_to_original_message` or `reply_parameters` to reply
196
308
  to this message. If both are passed, `reply_parameters` will be used.
197
-
309
+
198
310
  '''
199
311
  if not reply_parameters and reply_to_original_message:
200
312
  reply_parameters = ReplyParameters(
@@ -2093,9 +2205,12 @@ class Update(BaseModel, frozen=True):
2093
2205
  removed_chat_boost: Optional[ChatBoostRemoved] = Field(default=None)
2094
2206
 
2095
2207
  @staticmethod
2096
- def de_json(
2208
+ def from_dict(
2097
2209
  json_data: dict[str, Any]
2098
2210
  ) -> Update:
2211
+ '''
2212
+ Converts a JSON dictionary to an `Update` object.
2213
+ '''
2099
2214
  return Update.model_validate(json_data)
2100
2215
 
2101
2216
 
@@ -2237,6 +2352,9 @@ class MessageEntity(BaseModel, frozen=True):
2237
2352
  def from_dict(
2238
2353
  json_data: list[dict[str, Any]]
2239
2354
  ) -> list[MessageEntity]:
2355
+ '''
2356
+ Converts a list of JSON dictionaries to a list of `MessageEntity` objects.
2357
+ '''
2240
2358
  return [MessageEntity.model_validate(item) for item in json_data]
2241
2359
 
2242
2360
 
@@ -3205,6 +3323,9 @@ class InlineKeyboardMarkup(BaseModel, frozen=True):
3205
3323
  def from_dict(
3206
3324
  json_data: list[list[dict[str, Any]]]
3207
3325
  ) -> InlineKeyboardMarkup:
3326
+ '''
3327
+ Converts a JSON dictionary to an `InlineKeyboardMarkup` object.
3328
+ '''
3208
3329
  keyboard = [
3209
3330
  [
3210
3331
  InlineKeyboardButton(**button) for button in row
@@ -3407,8 +3528,11 @@ class ChatJoinRequest(BaseModel, frozen=True):
3407
3528
 
3408
3529
  async def approve(
3409
3530
  self,
3410
- bot: api.BotAPI
3531
+ bot: methods.Bot
3411
3532
  ) -> bool:
3533
+ '''
3534
+ Approves this chat join request.
3535
+ '''
3412
3536
  return await bot.approve_chat_join_request(
3413
3537
  chat_id=self.chat.id,
3414
3538
  user_id=self.from_user.id
@@ -3416,8 +3540,11 @@ class ChatJoinRequest(BaseModel, frozen=True):
3416
3540
 
3417
3541
  async def decline(
3418
3542
  self,
3419
- bot: api.BotAPI
3543
+ bot: methods.Bot
3420
3544
  ) -> bool:
3545
+ '''
3546
+ Declines this chat join request.
3547
+ '''
3421
3548
  return await bot.decline_chat_join_request(
3422
3549
  chat_id=self.chat.id,
3423
3550
  user_id=self.from_user.id
@@ -1,4 +0,0 @@
1
- from . import core, enums, errors, types, utils, dispatchers
2
- from .api import BotAPI
3
-
4
- __all__ = ['BotAPI', 'types', 'enums', 'utils', 'errors', 'core', 'dispatchers']
@@ -1,4 +0,0 @@
1
- from . import core, enums, errors, types, utils, dispatchers
2
- from .api import BotAPI
3
-
4
- __all__ = ['BotAPI', 'types', 'enums', 'utils', 'errors', 'core', 'dispatchers']
File without changes