From 6bc1c36251c83f0cfa606eb396a7bf7ad40fc6a2 Mon Sep 17 00:00:00 2001 From: Badiboy Date: Sat, 26 Jul 2025 14:42:41 +0300 Subject: [PATCH 1/4] Some deprecation warnings, formal and typo fixes --- telebot/apihelper.py | 7 ++- telebot/types.py | 146 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 124 insertions(+), 29 deletions(-) diff --git a/telebot/apihelper.py b/telebot/apihelper.py index 9318d926d..cba38bbdd 100644 --- a/telebot/apihelper.py +++ b/telebot/apihelper.py @@ -168,6 +168,8 @@ def _make_request(token, method_name, method='get', params=None, files=None): json_result = _check_result(method_name, result) if json_result: return json_result['result'] + else: + return None def _check_result(method_name, result): @@ -1139,8 +1141,10 @@ def send_data(token, chat_id, data, data_type, reply_markup=None, parse_mode=Non def get_method_by_type(data_type): if data_type == 'document': return r'sendDocument' - if data_type == 'sticker': + elif data_type == 'sticker': return r'sendSticker' + else: + raise ValueError(f"Unsupported data type: {data_type}.") def ban_chat_member(token, chat_id, user_id, until_date=None, revoke_messages=None): @@ -2524,6 +2528,7 @@ def convert_input_media_array(array): for input_media in array: if isinstance(input_media, types.InputMedia) or isinstance(input_media, types.InputPaidMedia): media_dict = input_media.to_dict() + key = "x" # stub if media_dict['media'].startswith('attach://'): key = media_dict['media'].replace('attach://', '') files[key] = input_media.media diff --git a/telebot/types.py b/telebot/types.py index 971f5824c..4a58c2529 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -276,7 +276,6 @@ def __init__(self, update_id, message, edited_message, channel_post, edited_chan self.purchased_paid_media: Optional[PaidMediaPurchased] = purchased_paid_media - class ChatMemberUpdated(JsonDeserializable): """ This object represents changes in the status of a chat member. @@ -403,6 +402,7 @@ def __init__(self, chat, from_user, user_chat_id, date, bio=None, invite_link=No self.invite_link: Optional[ChatInviteLink] = invite_link self.user_chat_id: int = user_chat_id + class WebhookInfo(JsonDeserializable): """ Describes the current status of a webhook. @@ -6790,6 +6790,10 @@ def __init__(self, type, media, caption=None, parse_mode=None, caption_entities= self._media_name = service_utils.generate_random_token() self._media_dic = 'attach://{0}'.format(self._media_name) + if self.__class__ is InputMedia: + # Make InputMedia as ABC some time... + log_deprecation_warning('The InputMedia class should not be instantiated directly. Use particular InputMediaXXX class instead') + def to_json(self): return json.dumps(self.to_dict()) @@ -8547,9 +8551,16 @@ def de_json(cls, json_string): elif obj['type'] == 'custom_emoji': del obj['type'] return ReactionTypeCustomEmoji(**obj) + elif obj['type'] == 'paid': + del obj['type'] + return ReactionTypePaid(**obj) + else: + raise ValueError(f"Unknown reaction type: {obj['type']}.") def __init__(self, type: str) -> None: self.type: str = type + if self.__class__ is ReactionType: + log_deprecation_warning('The ReactionType class should not be instantiated directly. Use particular ReactionTypeXXX class instead') def to_dict(self) -> dict: json_dict = { @@ -8935,7 +8946,7 @@ def __init__( # noinspection PyUnresolvedReferences,PyShadowingBuiltins -class MessageOrigin(JsonDeserializable): +class MessageOrigin(JsonDeserializable, ABC): """ This object describes the origin of a message. @@ -8979,6 +8990,8 @@ def de_json(cls, json_string): elif message_type == 'channel': chat = Chat.de_json(obj['chat']) return MessageOriginChannel(date=obj['date'], chat=chat, message_id=obj['message_id'], author_signature=obj.get('author_signature')) + else: + raise ValueError(f"Unknown message origin type: {message_type}.") def __init__(self, type: str, date: int) -> None: self.type: str = type @@ -10513,13 +10526,17 @@ def de_json(cls, json_string): return cls(**obj) -class TransactionPartner(JsonDeserializable): +class TransactionPartner(JsonDeserializable, ABC): # noinspection PyUnresolvedReferences """ This object describes the source of a transaction, or its recipient for outgoing transactions. Currently, it can be one of TransactionPartnerFragment TransactionPartnerUser TransactionPartnerOther + TransactionPartnerTelegramAds + TransactionPartnerTelegramApi + TransactionPartnerAffiliateProgram + TransactionPartnerChat Telegram documentation: https://core.telegram.org/bots/api#transactionpartner @@ -10548,6 +10565,8 @@ def de_json(cls, json_string): return TransactionPartnerOther.de_json(obj) elif obj["type"] == "chat": return TransactionPartnerChat.de_json(obj) + else: + raise ValueError(f"Unknown transaction partner type: {obj['type']}") # noinspection PyShadowingBuiltins @@ -10581,6 +10600,7 @@ def de_json(cls, json_string): return cls(**obj) +# noinspection PyShadowingBuiltins class TransactionPartnerTelegramApi(TransactionPartner): """ Describes a transaction with payment for paid broadcasting. @@ -10796,7 +10816,7 @@ def __init__(self, transactions, **kwargs): self.transactions: List[StarTransaction] = transactions -class PaidMedia(JsonDeserializable): +class PaidMedia(JsonDeserializable, ABC): """ This object describes paid media. Currently, it can be one of @@ -10820,6 +10840,8 @@ def de_json(cls, json_string): return PaidMediaPhoto.de_json(obj) elif obj["type"] == "video": return PaidMediaVideo.de_json(obj) + else: + raise ValueError("Unknown type of PaidMedia: {0}".format(obj["type"])) # noinspection PyShadowingBuiltins @@ -10947,7 +10969,7 @@ def __init__(self, star_count, paid_media, **kwargs): # noinspection PyShadowingBuiltins -class InputPaidMedia(JsonSerializable): +class InputPaidMedia(Dictionaryable, JsonSerializable): """ This object describes the paid media to be sent. Currently, it can be one of InputPaidMediaPhoto @@ -10970,6 +10992,10 @@ def __init__(self, type: str, media: Union[str, InputFile], **kwargs): self._media_name = service_utils.generate_random_token() self._media_dic = 'attach://{0}'.format(self._media_name) + if self.__class__ is InputPaidMedia: + # Make InputPaidMedia as ABC some time... + log_deprecation_warning('The InputPaidMedia class should not be instantiated directly. Use particular InputPaidMediaXXX class instead') + def to_json(self): return json.dumps(self.to_dict()) @@ -10979,7 +11005,8 @@ def to_dict(self): 'media': self._media_dic } return data - + + class InputPaidMediaPhoto(InputPaidMedia): """ The paid media to send is a photo. @@ -11000,7 +11027,8 @@ class InputPaidMediaPhoto(InputPaidMedia): def __init__(self, media: Union[str, InputFile], **kwargs): super().__init__(type='photo', media=media) - + + class InputPaidMediaVideo(InputPaidMedia): """ The paid media to send is a video. @@ -11058,8 +11086,6 @@ def __init__(self, media: Union[str, InputFile], thumbnail: Optional[InputFile] self.cover: Optional[Union[str,InputFile]] = cover self.start_timestamp: Optional[int] = start_timestamp - - def to_dict(self): data = super().to_dict() if self.thumbnail: @@ -11078,6 +11104,7 @@ def to_dict(self): data['start_timestamp'] = self.start_timestamp return data + class RefundedPayment(JsonDeserializable): """ This object contains basic information about a refunded payment. @@ -11176,6 +11203,7 @@ def de_json(cls, json_string): return cls(**obj) +# noinspection PyShadowingBuiltins class PreparedInlineMessage(JsonDeserializable): """ Describes an inline message to be sent by a user of a Mini App. @@ -11203,6 +11231,7 @@ def de_json(cls, json_string): return cls(**obj) +# noinspection PyShadowingBuiltins class Gift(JsonDeserializable): """ This object represents a gift that can be sent by the bot. @@ -11245,7 +11274,8 @@ def de_json(cls, json_string): obj = cls.check_json(json_string) obj['sticker'] = Sticker.de_json(obj['sticker']) return cls(**obj) - + + class Gifts(JsonDeserializable): """ This object represent a list of gifts. @@ -11270,6 +11300,7 @@ def de_json(cls, json_string): return cls(**obj) +# noinspection PyShadowingBuiltins class TransactionPartnerAffiliateProgram(TransactionPartner): """ Describes the affiliate program that issued the affiliate commission received via this transaction. @@ -11347,6 +11378,7 @@ def de_json(cls, json_string): return cls(**obj) +# noinspection PyShadowingBuiltins class TransactionPartnerChat(TransactionPartner): """ Describes a transaction with a chat. @@ -11497,6 +11529,7 @@ def to_dict(self): 'premium_subscription': self.premium_subscription } return data + @classmethod def de_json(cls, json_string): if json_string is None: return None @@ -11522,13 +11555,15 @@ class StarAmount(JsonDeserializable): def __init__(self, amount, nanostar_amount=None, **kwargs): self.amount: int = amount self.nanostar_amount: Optional[int] = nanostar_amount + @classmethod def de_json(cls, json_string): if json_string is None: return None obj = cls.check_json(json_string) return cls(**obj) - + +# noinspection PyShadowingBuiltins class OwnedGift(JsonDeserializable, ABC): """ This object describes a gift received and owned by a user or a chat. Currently, it can be one of @@ -11540,9 +11575,8 @@ class OwnedGift(JsonDeserializable, ABC): def __init__(self, type, **kwargs): self.type: str = type - self.gift: Union[Gift, UniqueGift] = None - - + self.gift: Optional[Union[Gift, UniqueGift]] = None + @classmethod def de_json(cls, json_string): if json_string is None: return None @@ -11551,7 +11585,11 @@ def de_json(cls, json_string): return OwnedGiftRegular.de_json(obj) elif obj["type"] == "unique": return OwnedGiftUnique.de_json(obj) - + else: + raise ValueError(f"Unknown gift type: {obj['type']}.") + + +# noinspection PyShadowingBuiltins class OwnedGiftRegular(OwnedGift): """ This object describes a regular gift owned by a user or a chat. @@ -11616,6 +11654,7 @@ def __init__(self, type, gift, owned_gift_id=None, sender_user=None, send_date=N self.was_refunded: Optional[bool] = was_refunded self.convert_star_count: Optional[int] = convert_star_count self.prepaid_upgrade_star_count: Optional[int] = prepaid_upgrade_star_count + @classmethod def de_json(cls, json_string): if json_string is None: return None @@ -11626,7 +11665,9 @@ def de_json(cls, json_string): if 'entities' in obj: obj['entities'] = [MessageEntity.de_json(entity) for entity in obj['entities']] return cls(**obj) - + + +# noinspection PyShadowingBuiltins class OwnedGiftUnique(OwnedGift): """ This object describes a unique gift owned by a user or a chat. @@ -11674,6 +11715,7 @@ def __init__(self, type, gift, owned_gift_id=None, sender_user=None, send_date=N self.can_be_transferred: Optional[bool] = can_be_transferred self.transfer_star_count: Optional[int] = transfer_star_count self.next_transfer_date: Optional[int] = next_transfer_date + @classmethod def de_json(cls, json_string): if json_string is None: return None @@ -11707,6 +11749,7 @@ def __init__(self, total_count, gifts, next_offset=None, **kwargs): self.total_count: int = total_count self.gifts: List[OwnedGift] = gifts self.next_offset: Optional[str] = next_offset + @classmethod def de_json(cls, json_string): if json_string is None: return None @@ -11715,7 +11758,6 @@ def de_json(cls, json_string): return cls(**obj) - class UniqueGift(JsonDeserializable): """ This object describes a unique gift that was upgraded from a regular gift. @@ -11750,6 +11792,7 @@ def __init__(self, base_name, name, number, model, symbol, backdrop, **kwargs): self.model: UniqueGiftModel = model self.symbol: UniqueGiftSymbol = symbol self.backdrop: UniqueGiftBackdrop = backdrop + @classmethod def de_json(cls, json_string): if json_string is None: return None @@ -11783,13 +11826,15 @@ def __init__(self, name, sticker, rarity_per_mille, **kwargs): self.name: str = name self.sticker: Sticker = sticker self.rarity_per_mille: int = rarity_per_mille + @classmethod def de_json(cls, json_string): if json_string is None: return None obj = cls.check_json(json_string) obj['sticker'] = Sticker.de_json(obj['sticker']) return cls(**obj) - + + class UniqueGiftSymbol(JsonDeserializable): """ This object describes the symbol shown on the pattern of a unique gift. @@ -11813,13 +11858,15 @@ def __init__(self, name, sticker, rarity_per_mille, **kwargs): self.name: str = name self.sticker: Sticker = sticker self.rarity_per_mille: int = rarity_per_mille + @classmethod def de_json(cls, json_string): if json_string is None: return None obj = cls.check_json(json_string) obj['sticker'] = Sticker.de_json(obj['sticker']) return cls(**obj) - + + class UniqueGiftBackdropColors(JsonDeserializable): """ This object describes the colors of the backdrop of a unique gift. @@ -11846,12 +11893,14 @@ def __init__(self, center_color, edge_color, symbol_color, text_color, **kwargs) self.edge_color: int = edge_color self.symbol_color: int = symbol_color self.text_color: int = text_color + @classmethod def de_json(cls, json_string): if json_string is None: return None obj = cls.check_json(json_string) return cls(**obj) - + + class UniqueGiftBackdrop(JsonDeserializable): """ This object describes the backdrop of a unique gift. @@ -11874,6 +11923,7 @@ def __init__(self, name, colors, rarity_per_mille, **kwargs): self.name: str = name self.colors: UniqueGiftBackdropColors = colors self.rarity_per_mille: int = rarity_per_mille + @classmethod def de_json(cls, json_string): if json_string is None: return None @@ -11881,6 +11931,8 @@ def de_json(cls, json_string): obj['colors'] = UniqueGiftBackdropColors.de_json(obj['colors']) return cls(**obj) + +# noinspection PyShadowingBuiltins class InputStoryContent(JsonSerializable, ABC): """ This object describes the content of a story to post. Currently, it can be one of @@ -11888,7 +11940,6 @@ class InputStoryContent(JsonSerializable, ABC): InputStoryContentVideo Telegram documentation: https://core.telegram.org/bots/api#inputstorycontent - """ def __init__(self, type: str, **kwargs): self.type: str = type @@ -11956,6 +12007,7 @@ def __init__(self, video: InputFile, duration: Optional[float] = None, cover_fra self.duration: Optional[float] = duration self.cover_frame_timestamp: Optional[float] = cover_frame_timestamp self.is_animation: Optional[bool] = is_animation + def to_json(self): return json.dumps(self.to_dict()) @@ -11971,6 +12023,7 @@ def to_dict(self): if self.is_animation is not None: data['is_animation'] = self.is_animation return data + def convert_input_story(self): return self.to_json(), {self._video_name: self.video} @@ -12010,8 +12063,10 @@ def __init__(self, x_percentage: float, y_percentage: float, width_percentage: f self.height_percentage: float = height_percentage self.rotation_angle: float = rotation_angle self.corner_radius_percentage: float = corner_radius_percentage + def to_json(self): return json.dumps(self.to_dict()) + def to_dict(self): data = { 'x_percentage': self.x_percentage, @@ -12051,8 +12106,10 @@ def __init__(self, country_code: str, state: Optional[str] = None, city: Optiona self.state: Optional[str] = state self.city: Optional[str] = city self.street: Optional[str] = street + def to_json(self): return json.dumps(self.to_dict()) + def to_dict(self): data = { 'country_code': self.country_code @@ -12064,7 +12121,9 @@ def to_dict(self): if self.street is not None: data['street'] = self.street return data + +# noinspection PyShadowingBuiltins class StoryAreaType(JsonSerializable, ABC): """ Describes the type of a clickable area on a story. Currently, it can be one of @@ -12109,8 +12168,10 @@ def __init__(self,latitude: float, longitude: float, address: LocationAddress = self.latitude: float = latitude self.longitude: float = longitude self.address: Optional[LocationAddress] = address + def to_json(self): return json.dumps(self.to_dict()) + def to_dict(self): data = { 'type': self.type, @@ -12148,8 +12209,10 @@ def __init__(self, reaction_type: ReactionType, is_dark: Optional[bool] = None, self.reaction_type: ReactionType = reaction_type self.is_dark: Optional[bool] = is_dark self.is_flipped: Optional[bool] = is_flipped + def to_json(self): return json.dumps(self.to_dict()) + def to_dict(self): data = { 'type': self.type, @@ -12160,7 +12223,8 @@ def to_dict(self): if self.is_flipped is not None: data['is_flipped'] = self.is_flipped return data - + + class StoryAreaTypeLink(StoryAreaType): """ Describes a story area pointing to an HTTP or tg:// link. Currently, a story can have up to 3 link areas. @@ -12179,15 +12243,18 @@ class StoryAreaTypeLink(StoryAreaType): def __init__(self, url: str, **kwargs): super().__init__(type="link") self.url: str = url + def to_json(self): return json.dumps(self.to_dict()) + def to_dict(self): data = { 'type': self.type, 'url': self.url } return data - + + class StoryAreaTypeWeather(StoryAreaType): """ Describes a story area containing weather information. Currently, a story can have up to 3 weather areas. @@ -12214,8 +12281,10 @@ def __init__(self, temperature: float, emoji: str, background_color: int, **kwar self.temperature: float = temperature self.emoji: str = emoji self.background_color: int = background_color + def to_json(self): return json.dumps(self.to_dict()) + def to_dict(self): data = { 'type': self.type, @@ -12224,7 +12293,8 @@ def to_dict(self): 'background_color': self.background_color } return data - + + class StoryAreaTypeUniqueGift(StoryAreaType): """ Describes a story area pointing to a unique gift. Currently, a story can have at most 1 unique gift area. @@ -12243,8 +12313,10 @@ class StoryAreaTypeUniqueGift(StoryAreaType): def __init__(self, name: str, **kwargs): super().__init__(type="unique_gift") self.name: str = name + def to_json(self): return json.dumps(self.to_dict()) + def to_dict(self): data = { 'type': self.type, @@ -12254,6 +12326,7 @@ def to_dict(self): return data +# noinspection PyShadowingBuiltins class StoryArea(JsonSerializable): """ Describes a clickable area on a story media. @@ -12272,8 +12345,10 @@ class StoryArea(JsonSerializable): def __init__(self, position: StoryAreaPosition, type: StoryAreaType, **kwargs): self.position: StoryAreaPosition = position self.type: StoryAreaType = type + def to_json(self): return json.dumps(self.to_dict()) + def to_dict(self): data = { 'position': self.position.to_dict(), @@ -12327,6 +12402,7 @@ def __init__(self, gift: Gift, owned_gift_id: Optional[str] = None, convert_star self.text: Optional[str] = text self.entities: Optional[List[MessageEntity]] = entities self.is_private: Optional[bool] = is_private + @classmethod def de_json(cls, json_string): if json_string is None: return None @@ -12335,7 +12411,8 @@ def de_json(cls, json_string): if 'entities' in obj: obj['entities'] = [MessageEntity.de_json(entity) for entity in obj['entities']] return cls(**obj) - + + class UniqueGiftInfo(JsonDeserializable): """ This object describes a service message about a unique gift that was sent or received. @@ -12396,6 +12473,7 @@ class PaidMessagePriceChanged(JsonDeserializable): """ def __init__(self, paid_message_star_count: int, **kwargs): self.paid_message_star_count: int = paid_message_star_count + @classmethod def de_json(cls, json_string): if json_string is None: return None @@ -12403,7 +12481,7 @@ def de_json(cls, json_string): return cls(**obj) -class InputProfilePhoto(JsonSerializable): +class InputProfilePhoto(JsonSerializable, ABC): """ This object describes a profile photo to set. Currently, it can be one of InputProfilePhotoStatic @@ -12415,6 +12493,7 @@ class InputProfilePhoto(JsonSerializable): :rtype: :class:`InputProfilePhoto` """ + class InputProfilePhotoStatic(InputProfilePhoto): """ This object describes a static profile photo to set. @@ -12434,9 +12513,9 @@ class InputProfilePhotoStatic(InputProfilePhoto): def __init__(self, photo: InputFile, **kwargs): self.type: str = "static" self.photo: InputFile = photo - self._photo_name = service_utils.generate_random_token() self._photo_dic = "attach://{}".format(self._photo_name) + def to_json(self): return json.dumps(self.to_dict()) @@ -12446,6 +12525,7 @@ def to_dict(self): 'photo': self._photo_dic } return data + def convert_input_profile_photo(self): return self.to_json(), {self._photo_name: self.photo} @@ -12475,8 +12555,10 @@ def __init__(self, animation: InputFile, main_frame_timestamp: Optional[float] = self._animation_name = service_utils.generate_random_token() self._animation_dic = "attach://{}".format(self._animation_name) self.main_frame_timestamp: Optional[float] = main_frame_timestamp + def to_json(self): return json.dumps(self.to_dict()) + def to_dict(self): data = { 'type': self.type, @@ -12485,10 +12567,12 @@ def to_dict(self): if self.main_frame_timestamp is not None: data['main_frame_timestamp'] = self.main_frame_timestamp return data + def convert_input_profile_photo(self): return self.to_json(), {self._animation_name: self.animation} +# noinspection PyShadowingBuiltins class ChecklistTask(JsonDeserializable): """ Describes a task in a checklist. @@ -12531,6 +12615,7 @@ def de_json(cls, json_string): if 'completed_by_user' in obj: obj['completed_by_user'] = User.de_json(obj['completed_by_user']) return cls(**obj) + class Checklist(JsonDeserializable): """ @@ -12575,6 +12660,8 @@ def de_json(cls, json_string): obj['tasks'] = [ChecklistTask.de_json(task) for task in obj['tasks']] return cls(**obj) + +# noinspection PyShadowingBuiltins class InputChecklistTask(JsonSerializable): """ Describes a task to add to a checklist. @@ -12616,7 +12703,8 @@ def to_dict(self): if self.text_entities: data['text_entities'] = [entity.to_dict() for entity in self.text_entities] return data - + + class InputChecklist(JsonSerializable): """ Describes a checklist to create. @@ -12726,6 +12814,7 @@ class ChecklistTasksAdded(JsonDeserializable): def __init__(self, tasks: List[ChecklistTask], checklist_message: Optional[Message] = None, **kwargs): self.checklist_message: Optional[Message] = checklist_message self.tasks: List[ChecklistTask] = tasks + @classmethod def de_json(cls, json_string): if json_string is None: return None @@ -12735,6 +12824,7 @@ def de_json(cls, json_string): obj['tasks'] = [ChecklistTask.de_json(task) for task in obj['tasks']] return cls(**obj) + class DirectMessagePriceChanged(JsonDeserializable): """ Describes a service message about a change in the price of direct messages sent to a channel chat. From 18c524f1fb8d0919fe98654e26d68c3a10807c46 Mon Sep 17 00:00:00 2001 From: Badiboy Date: Sat, 26 Jul 2025 15:35:32 +0300 Subject: [PATCH 2/4] ChatMemberXXX classes redesigned --- telebot/types.py | 200 ++++++++++++++++++++++++----------------------- 1 file changed, 102 insertions(+), 98 deletions(-) diff --git a/telebot/types.py b/telebot/types.py index 4a58c2529..a707b72ba 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -3251,7 +3251,7 @@ def __init__(self, small_file_id, small_file_unique_id, big_file_id, big_file_un self.big_file_unique_id: str = big_file_unique_id -class ChatMember(JsonDeserializable): +class ChatMember(JsonDeserializable, ABC): """ This object contains information about one member of a chat. Currently, the following 6 types of chat members are supported: @@ -3266,78 +3266,31 @@ class ChatMember(JsonDeserializable): Telegram Documentation: https://core.telegram.org/bots/api#chatmember """ + def __init__(self, user, status, **kwargs): + self.user: User = user + self.status: str = status + @classmethod def de_json(cls, json_string): if json_string is None: return None obj = cls.check_json(json_string) obj['user'] = User.de_json(obj['user']) - member_type = obj['status'] + status = obj['status'] # Ordered according to estimated appearance frequency. - if member_type == "member": + if status == "member": return ChatMemberMember(**obj) - elif member_type == "left": + elif status == "left": return ChatMemberLeft(**obj) - elif member_type == "kicked": + elif status == "kicked": return ChatMemberBanned(**obj) - elif member_type == "restricted": + elif status == "restricted": return ChatMemberRestricted(**obj) - elif member_type == "administrator": + elif status == "administrator": return ChatMemberAdministrator(**obj) - elif member_type == "creator": + elif status == "creator": return ChatMemberOwner(**obj) else: - # Should not be here. For "if something happen" compatibility - return cls(**obj) - - def __init__(self, user, status, custom_title=None, is_anonymous=None, can_be_edited=None, - can_post_messages=None, can_edit_messages=None, can_delete_messages=None, - can_restrict_members=None, can_promote_members=None, can_change_info=None, - can_invite_users=None, can_pin_messages=None, is_member=None, - can_send_messages=None, can_send_audios=None, can_send_documents=None, - can_send_photos=None, can_send_videos=None, can_send_video_notes=None, - can_send_voice_notes=None, - can_send_polls=None, - can_send_other_messages=None, can_add_web_page_previews=None, - can_manage_chat=None, can_manage_video_chats=None, - until_date=None, can_manage_topics=None, - can_post_stories=None, can_edit_stories=None, can_delete_stories=None, - **kwargs): - self.user: User = user - self.status: str = status - self.custom_title: str = custom_title - self.is_anonymous: bool = is_anonymous - self.can_be_edited: bool = can_be_edited - self.can_post_messages: bool = can_post_messages - self.can_edit_messages: bool = can_edit_messages - self.can_delete_messages: bool = can_delete_messages - self.can_restrict_members: bool = can_restrict_members - self.can_promote_members: bool = can_promote_members - self.can_change_info: bool = can_change_info - self.can_invite_users: bool = can_invite_users - self.can_pin_messages: bool = can_pin_messages - self.is_member: bool = is_member - self.can_send_messages: bool = can_send_messages - self.can_send_polls: bool = can_send_polls - self.can_send_other_messages: bool = can_send_other_messages - self.can_add_web_page_previews: bool = can_add_web_page_previews - self.can_manage_chat: bool = can_manage_chat - self.can_manage_video_chats: bool = can_manage_video_chats - self.until_date: int = until_date - self.can_manage_topics: bool = can_manage_topics - self.can_send_audios: bool = can_send_audios - self.can_send_documents: bool = can_send_documents - self.can_send_photos: bool = can_send_photos - self.can_send_videos: bool = can_send_videos - self.can_send_video_notes: bool = can_send_video_notes - self.can_send_voice_notes: bool = can_send_voice_notes - self.can_post_stories: bool = can_post_stories - self.can_edit_stories: bool = can_edit_stories - self.can_delete_stories: bool = can_delete_stories - - @property - def can_manage_voice_chats(self): - log_deprecation_warning('The parameter "can_manage_voice_chats" is deprecated. Use "can_manage_video_chats" instead.') - return self.can_manage_video_chats + raise ValueError(f"Unknown chat member type: {status}.") # noinspection PyUnresolvedReferences @@ -3362,7 +3315,10 @@ class ChatMemberOwner(ChatMember): :return: Instance of the class :rtype: :class:`telebot.types.ChatMemberOwner` """ - pass + def __init__(self, user, status, is_anonymous, custom_title=None, **kwargs): + super().__init__(user, status, **kwargs) + self.is_anonymous: bool = is_anonymous + self.custom_title: Optional[str] = custom_title # noinspection PyUnresolvedReferences @@ -3409,36 +3365,60 @@ class ChatMemberAdministrator(ChatMember): :param can_invite_users: True, if the user is allowed to invite new users to the chat :type can_invite_users: :obj:`bool` + :param can_post_stories: True, if the administrator can post channel stories + :type can_post_stories: :obj:`bool` + + :param can_edit_stories: True, if the administrator can edit stories + :type can_edit_stories: :obj:`bool` + + :param can_delete_stories: True, if the administrator can delete stories of other users + :type can_delete_stories: :obj:`bool` + :param can_post_messages: Optional. True, if the administrator can post in the channel; channels only :type can_post_messages: :obj:`bool` - :param can_edit_messages: Optional. True, if the administrator can edit messages of other users and can pin - messages; channels only + :param can_edit_messages: Optional. True, if the administrator can edit messages of other users and can pin messages; channels only :type can_edit_messages: :obj:`bool` :param can_pin_messages: Optional. True, if the user is allowed to pin messages; groups and supergroups only :type can_pin_messages: :obj:`bool` - :param can_manage_topics: Optional. True, if the user is allowed to create, rename, close, and reopen forum topics; - supergroups only + :param can_manage_topics: Optional. True, if the user is allowed to create, rename, close, and reopen forum topics; supergroups only :type can_manage_topics: :obj:`bool` :param custom_title: Optional. Custom title for this user :type custom_title: :obj:`str` - :param can_post_stories: Optional. True, if the administrator can post channel stories - :type can_post_stories: :obj:`bool` - - :param can_edit_stories: Optional. True, if the administrator can edit stories - :type can_edit_stories: :obj:`bool` - - :param can_delete_stories: Optional. True, if the administrator can delete stories of other users - :type can_delete_stories: :obj:`bool` - :return: Instance of the class :rtype: :class:`telebot.types.ChatMemberAdministrator` """ - pass + def __init__(self, user, status, can_be_edited, is_anonymous, can_manage_chat, can_delete_messages, + can_manage_video_chats, can_restrict_members, can_promote_members, can_change_info, can_invite_users, + can_post_stories, can_edit_stories, can_delete_stories, can_post_messages=None, can_edit_messages=None, + can_pin_messages=None, can_manage_topics=None, custom_title=None, **kwargs): + super().__init__(user, status, **kwargs) + self.can_be_edited: bool = can_be_edited + self.is_anonymous: bool = is_anonymous + self.can_manage_chat: bool = can_manage_chat + self.can_delete_messages: bool = can_delete_messages + self.can_manage_video_chats: bool = can_manage_video_chats + self.can_restrict_members: bool = can_restrict_members + self.can_promote_members: bool = can_promote_members + self.can_change_info: bool = can_change_info + self.can_invite_users: bool = can_invite_users + self.can_post_stories: bool = can_post_stories + self.can_edit_stories: bool = can_edit_stories + self.can_delete_stories: bool = can_delete_stories + self.can_post_messages: Optional[bool] = can_post_messages + self.can_edit_messages: Optional[bool] = can_edit_messages + self.can_pin_messages: Optional[bool] = can_pin_messages + self.can_manage_topics: Optional[bool] = can_manage_topics + self.custom_title: Optional[str] = custom_title + + @property + def can_manage_voice_chats(self): + log_deprecation_warning('The parameter "can_manage_voice_chats" is deprecated. Use "can_manage_video_chats" instead.') + return self.can_manage_video_chats # noinspection PyUnresolvedReferences @@ -3454,10 +3434,15 @@ class ChatMemberMember(ChatMember): :param user: Information about the user :type user: :class:`telebot.types.User` + :param until_date: Optional. Date when the user's subscription will expire; Unix time. If 0, then the user is a member forever + :type until_date: :obj:`int` + :return: Instance of the class :rtype: :class:`telebot.types.ChatMemberMember` """ - pass + def __init__(self, user, status, until_date=None, **kwargs): + super().__init__(user, status, **kwargs) + self.until_date: Optional[int] = until_date # noinspection PyUnresolvedReferences @@ -3476,18 +3461,6 @@ class ChatMemberRestricted(ChatMember): :param is_member: True, if the user is a member of the chat at the moment of the request :type is_member: :obj:`bool` - :param can_change_info: True, if the user is allowed to change the chat title, photo and other settings - :type can_change_info: :obj:`bool` - - :param can_invite_users: True, if the user is allowed to invite new users to the chat - :type can_invite_users: :obj:`bool` - - :param can_pin_messages: True, if the user is allowed to pin messages - :type can_pin_messages: :obj:`bool` - - :param can_manage_topics: True, if the user is allowed to create forum topics - :type can_manage_topics: :obj:`bool` - :param can_send_messages: True, if the user is allowed to send text messages, contacts, locations and venues :type can_send_messages: :obj:`bool` @@ -3512,21 +3485,52 @@ class ChatMemberRestricted(ChatMember): :param can_send_polls: True, if the user is allowed to send polls :type can_send_polls: :obj:`bool` - :param can_send_other_messages: True, if the user is allowed to send animations, games, stickers and use inline - bots + :param can_send_other_messages: True, if the user is allowed to send animations, games, stickers and use inline bots :type can_send_other_messages: :obj:`bool` :param can_add_web_page_previews: True, if the user is allowed to add web page previews to their messages :type can_add_web_page_previews: :obj:`bool` - :param until_date: Date when restrictions will be lifted for this user; unix time. If 0, then the user is restricted - forever + :param can_change_info: True, if the user is allowed to change the chat title, photo and other settings + :type can_change_info: :obj:`bool` + + :param can_invite_users: True, if the user is allowed to invite new users to the chat + :type can_invite_users: :obj:`bool` + + :param can_pin_messages: True, if the user is allowed to pin messages + :type can_pin_messages: :obj:`bool` + + :param can_manage_topics: True, if the user is allowed to create forum topics + :type can_manage_topics: :obj:`bool` + + :param until_date: Date when restrictions will be lifted for this user; unix time. If 0, then the user is restricted forever :type until_date: :obj:`int` :return: Instance of the class :rtype: :class:`telebot.types.ChatMemberRestricted` """ - pass + def __init__(self, user, status, is_member, can_send_messages, can_send_audios, can_send_documents, + can_send_photos, can_send_videos, can_send_video_notes, can_send_voice_notes, can_send_polls, + can_send_other_messages, can_add_web_page_previews, + can_change_info, can_invite_users, can_pin_messages, can_manage_topics, + until_date=None, **kwargs): + super().__init__(user, status, **kwargs) + self.is_member: bool = is_member + self.can_send_messages: bool = can_send_messages + self.can_send_audios: bool = can_send_audios + self.can_send_documents: bool = can_send_documents + self.can_send_photos: bool = can_send_photos + self.can_send_videos: bool = can_send_videos + self.can_send_video_notes: bool = can_send_video_notes + self.can_send_voice_notes: bool = can_send_voice_notes + self.can_send_polls: bool = can_send_polls + self.can_send_other_messages: bool = can_send_other_messages + self.can_add_web_page_previews: bool = can_add_web_page_previews + self.can_change_info: bool = can_change_info + self.can_invite_users: bool = can_invite_users + self.can_pin_messages: bool = can_pin_messages + self.can_manage_topics: bool = can_manage_topics + self.until_date: Optional[int] = until_date # noinspection PyUnresolvedReferences @@ -3561,14 +3565,15 @@ class ChatMemberBanned(ChatMember): :param user: Information about the user :type user: :class:`telebot.types.User` - :param until_date: Date when restrictions will be lifted for this user; unix time. If 0, then the user is banned - forever + :param until_date: Date when restrictions will be lifted for this user; unix time. If 0, then the user is banned forever :type until_date: :obj:`int` :return: Instance of the class :rtype: :class:`telebot.types.ChatMemberBanned` """ - pass + def __init__(self, user, status, until_date=None, **kwargs): + super().__init__(user, status, **kwargs) + self.until_date: Optional[int] = until_date class ChatPermissions(JsonDeserializable, JsonSerializable, Dictionaryable): @@ -3577,8 +3582,7 @@ class ChatPermissions(JsonDeserializable, JsonSerializable, Dictionaryable): Telegram Documentation: https://core.telegram.org/bots/api#chatpermissions - :param can_send_messages: Optional. True, if the user is allowed to send text messages, contacts, locations and - venues + :param can_send_messages: Optional. True, if the user is allowed to send text messages, contacts, locations and venues :type can_send_messages: :obj:`bool` :param can_send_audios: Optional. True, if the user is allowed to send audios From 9dfb79661f735ca757f1d11bc2435e94ef581595 Mon Sep 17 00:00:00 2001 From: Badiboy Date: Sat, 26 Jul 2025 15:41:20 +0300 Subject: [PATCH 3/4] Update test_chat_member_updated with appropriate params --- tests/test_types.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_types.py b/tests/test_types.py index 197834748..de01b0e5c 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -242,7 +242,7 @@ def test_json_chat_invite_link(): def test_chat_member_updated(): - json_string = r'{"chat": {"id": -1234567890123, "type": "supergroup", "title": "No Real Group", "username": "NoRealGroup"}, "from": {"id": 133869498, "is_bot": false, "first_name": "Vincent"}, "date": 1624119999, "old_chat_member": {"user": {"id": 77777777, "is_bot": false, "first_name": "Pepe"}, "status": "member"}, "new_chat_member": {"user": {"id": 77777777, "is_bot": false, "first_name": "Pepe"}, "status": "administrator"}}' + json_string = r'{"chat": {"id": -1234567890123, "type": "supergroup", "title": "No Real Group", "username": "NoRealGroup"}, "from": {"id": 133869498, "is_bot": false, "first_name": "Vincent"}, "date": 1624119999, "old_chat_member": {"user": {"id": 77777777, "is_bot": false, "first_name": "Pepe"}, "status": "member"}, "new_chat_member": {"user": {"id": 77777777, "is_bot": false, "first_name": "Pepe"}, "status": "left"}}' cm_updated = types.ChatMemberUpdated.de_json(json_string) assert cm_updated.chat.id == -1234567890123 assert cm_updated.from_user.id == 133869498 @@ -309,5 +309,3 @@ def test_message_entity(): sample_string_7 = r'{"update_id":934522167,"message":{"message_id":1374526,"from":{"id":927266710,"is_bot":false,"first_name":">_run","username":"coder2020","language_code":"en","is_premium":true},"chat":{"id":927266710,"first_name":">_run","username":"coder2020","type":"private"},"date":1682179716,"reply_to_message":{"message_id":1374510,"from":{"id":927266710,"is_bot":false,"first_name":">_run","username":"coder2020","language_code":"en"},"chat":{"id":927266710,"first_name":">_run","username":"coder2020","type":"private"},"date":1712765863,"text":"text @UserName b i s u c p #hashtag https://example.com","entities":[{"offset":5,"length":9,"type":"mention"},{"offset":15,"length":1,"type":"bold"},{"offset":17,"length":1,"type":"italic"},{"offset":19,"length":1,"type":"strikethrough"},{"offset":21,"length":1,"type":"underline"},{"offset":23,"length":1,"type":"code"},{"offset":25,"length":1,"type":"spoiler"},{"offset":27,"length":8,"type":"hashtag"},{"offset":36,"length":19,"type":"url"}],"link_preview_options":{"is_disabled":true}},"quote":{"text":"text @UserName b i s u c p #hashtag https://example.com","entities":[{"offset":15,"length":1,"type":"bold"},{"offset":17,"length":1,"type":"italic"},{"offset":19,"length":1,"type":"strikethrough"},{"offset":21,"length":1,"type":"underline"},{"offset":25,"length":1,"type":"spoiler"}],"position":0,"is_manual":true},"text":"quote reply"}}' message_7 = types.Update.de_json(sample_string_7).message assert message_7.quote.html_text == 'text @UserName b i s u c p #hashtag https://example.com' - - From c01b90452b9fa1df554709e6efad116db41ef9c2 Mon Sep 17 00:00:00 2001 From: Badiboy Date: Sat, 26 Jul 2025 15:42:29 +0300 Subject: [PATCH 4/4] Update test_chat_member_updated with appropriate params --- tests/test_types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_types.py b/tests/test_types.py index de01b0e5c..630ab658c 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -248,7 +248,7 @@ def test_chat_member_updated(): assert cm_updated.from_user.id == 133869498 assert cm_updated.date == 1624119999 assert cm_updated.old_chat_member.status == "member" - assert cm_updated.new_chat_member.status == "administrator" + assert cm_updated.new_chat_member.status == "left" def test_webhook_info():