diff --git a/telebot/types.py b/telebot/types.py index 1089d111b..910c253b6 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -14679,7 +14679,7 @@ def de_json(cls, json_string): return cls(**obj) -class RichText(JsonDeserializable): +class RichText(JsonDeserializable, Dictionaryable): """ This object represents a rich formatted text. Currently, it can be either a String for plain text, an Array of :class:`RichText`, or any of the following types: @@ -14721,9 +14721,12 @@ def __init__(self, type: str, **kwargs): def de_json(cls, json_string): if json_string is None: return None if isinstance(json_string, str): + # "...can be either a String for plain text..." return json_string if isinstance(json_string, list): + # "...an Array of :class:`RichText`..." return [RichText.de_json(item) for item in json_string] + # "...or any of the following types..." obj = cls.check_json(json_string) type = obj.pop('type', None) if type == 'bold': @@ -14777,7 +14780,24 @@ def de_json(cls, json_string): elif type == 'reference_link': return RichTextReferenceLink.de_json(obj) return None - + + def to_dict(self): + data = { + 'type': self.type + } + return data + + @staticmethod + def richtext_to_dict(richtext: Union[str, List[RichText], RichText]): + if isinstance(richtext, str): + return richtext + elif isinstance(richtext, list): + return [RichText.richtext_to_dict(item) for item in richtext] + elif isinstance(richtext, RichText): + return richtext.to_dict() + return None + + class RichTextBold(RichText): """ A bold text. @@ -14804,6 +14824,12 @@ def de_json(cls, json_string): obj['text'] = RichText.de_json(obj['text']) return cls(**obj) + def to_dict(self): + data = super().to_dict() + data['text'] = RichText.richtext_to_dict(self.text) + return data + + class RichTextItalic(RichText): """ An italicized text. @@ -14830,6 +14856,12 @@ def de_json(cls, json_string): obj['text'] = RichText.de_json(obj['text']) return cls(**obj) + def to_dict(self): + data = super().to_dict() + data['text'] = RichText.richtext_to_dict(self.text) + return data + + class RichTextUnderline(RichText): """ An underlined text. @@ -14856,6 +14888,12 @@ def de_json(cls, json_string): obj['text'] = RichText.de_json(obj['text']) return cls(**obj) + def to_dict(self): + data = super().to_dict() + data['text'] = RichText.richtext_to_dict(self.text) + return data + + class RichTextStrikethrough(RichText): """ A strikethrough text. @@ -14882,6 +14920,12 @@ def de_json(cls, json_string): obj['text'] = RichText.de_json(obj['text']) return cls(**obj) + def to_dict(self): + data = super().to_dict() + data['text'] = RichText.richtext_to_dict(self.text) + return data + + class RichTextSpoiler(RichText): """ A text covered by a spoiler. @@ -14908,6 +14952,12 @@ def de_json(cls, json_string): obj['text'] = RichText.de_json(obj['text']) return cls(**obj) + def to_dict(self): + data = super().to_dict() + data['text'] = RichText.richtext_to_dict(self.text) + return data + + class RichTextDateTime(RichText): """ Formatted date and time. @@ -14942,6 +14992,14 @@ def de_json(cls, json_string): obj['text'] = RichText.de_json(obj['text']) return cls(**obj) + def to_dict(self): + data = super().to_dict() + data['text'] = RichText.richtext_to_dict(self.text) + data['unix_time'] = self.unix_time + data['date_time_format'] = self.date_time_format + return data + + class RichTextTextMention(RichText): """ A mention of a Telegram user by their identifier. @@ -14973,6 +15031,12 @@ def de_json(cls, json_string): obj['user'] = User.de_json(obj['user']) return cls(**obj) + def to_dict(self): + data = super().to_dict() + data['text'] = RichText.richtext_to_dict(self.text) + data['user'] = self.user.to_dict() + return data + class RichTextSubscript(RichText): """ @@ -15000,6 +15064,12 @@ def de_json(cls, json_string): obj['text'] = RichText.de_json(obj['text']) return cls(**obj) + def to_dict(self): + data = super().to_dict() + data['text'] = RichText.richtext_to_dict(self.text) + return data + + class RichTextSuperscript(RichText): """ A superscript text. @@ -15026,6 +15096,12 @@ def de_json(cls, json_string): obj['text'] = RichText.de_json(obj['text']) return cls(**obj) + def to_dict(self): + data = super().to_dict() + data['text'] = RichText.richtext_to_dict(self.text) + return data + + class RichTextMarked(RichText): """ A marked text. @@ -15052,6 +15128,12 @@ def de_json(cls, json_string): obj['text'] = RichText.de_json(obj['text']) return cls(**obj) + def to_dict(self): + data = super().to_dict() + data['text'] = RichText.richtext_to_dict(self.text) + return data + + class RichTextCode(RichText): """ A monowidth text. @@ -15078,6 +15160,12 @@ def de_json(cls, json_string): obj['text'] = RichText.de_json(obj['text']) return cls(**obj) + def to_dict(self): + data = super().to_dict() + data['text'] = RichText.richtext_to_dict(self.text) + return data + + class RichTextCustomEmoji(RichText): """ A custom emoji. @@ -15107,6 +15195,13 @@ def de_json(cls, json_string): obj = cls.check_json(json_string) return cls(**obj) + def to_dict(self): + data = super().to_dict() + data['custom_emoji_id'] = self.custom_emoji_id + data['alternative_text'] = self.alternative_text + return data + + class RichTextMathematicalExpression(RichText): """ A mathematical expression. @@ -15132,6 +15227,12 @@ def de_json(cls, json_string): obj = cls.check_json(json_string) return cls(**obj) + def to_dict(self): + data = super().to_dict() + data['expression'] = self.expression + return data + + class RichTextUrl(RichText): """ A text with a link. @@ -15162,6 +15263,13 @@ def de_json(cls, json_string): obj['text'] = RichText.de_json(obj['text']) return cls(**obj) + def to_dict(self): + data = super().to_dict() + data['text'] = RichText.richtext_to_dict(self.text) + data['url'] = self.url + return data + + class RichTextEmailAddress(RichText): """ A text with an email address. @@ -15192,6 +15300,13 @@ def de_json(cls, json_string): obj['text'] = RichText.de_json(obj['text']) return cls(**obj) + def to_dict(self): + data = super().to_dict() + data['text'] = RichText.richtext_to_dict(self.text) + data['email_address'] = self.email_address + return data + + class RichTextPhoneNumber(RichText): """ A text with a phone number. @@ -15222,6 +15337,13 @@ def de_json(cls, json_string): obj['text'] = RichText.de_json(obj['text']) return cls(**obj) + def to_dict(self): + data = super().to_dict() + data['text'] = RichText.richtext_to_dict(self.text) + data['phone_number'] = self.phone_number + return data + + class RichTextBankCardNumber(RichText): """ A text with a bank card number. @@ -15252,6 +15374,13 @@ def de_json(cls, json_string): obj['text'] = RichText.de_json(obj['text']) return cls(**obj) + def to_dict(self): + data = super().to_dict() + data['text'] = RichText.richtext_to_dict(self.text) + data['bank_card_number'] = self.bank_card_number + return data + + class RichTextMention(RichText): """ A mention by a username. @@ -15270,7 +15399,6 @@ class RichTextMention(RichText): :return: Instance of the class :rtype: :class:`RichTextMention` """ - def __init__(self, text: RichText, username: str, **kwargs): super().__init__(type='mention', **kwargs) self.text: RichText = text @@ -15283,6 +15411,12 @@ def de_json(cls, json_string): obj['text'] = RichText.de_json(obj['text']) return cls(**obj) + def to_dict(self): + data = super().to_dict() + data['text'] = RichText.richtext_to_dict(self.text) + data['username'] = self.username + return data + class RichTextHashtag(RichText): """ @@ -15314,6 +15448,12 @@ def de_json(cls, json_string): obj['text'] = RichText.de_json(obj['text']) return cls(**obj) + def to_dict(self): + data = super().to_dict() + data['text'] = RichText.richtext_to_dict(self.text) + data['hashtag'] = self.hashtag + return data + class RichTextCashtag(RichText): """ @@ -15345,6 +15485,12 @@ def de_json(cls, json_string): obj['text'] = RichText.de_json(obj['text']) return cls(**obj) + def to_dict(self): + data = super().to_dict() + data['text'] = RichText.richtext_to_dict(self.text) + data['cashtag'] = self.cashtag + return data + class RichTextBotCommand(RichText): """ @@ -15376,6 +15522,12 @@ def de_json(cls, json_string): obj['text'] = RichText.de_json(obj['text']) return cls(**obj) + def to_dict(self): + data = super().to_dict() + data['text'] = RichText.richtext_to_dict(self.text) + data['bot_command'] = self.bot_command + return data + class RichTextAnchor(RichText): """ @@ -15402,6 +15554,11 @@ def de_json(cls, json_string): obj = cls.check_json(json_string) return cls(**obj) + def to_dict(self): + data = super().to_dict() + data['name'] = self.name + return data + class RichTextAnchorLink(RichText): """ @@ -15432,7 +15589,13 @@ def de_json(cls, json_string): obj = cls.check_json(json_string) obj['text'] = RichText.de_json(obj['text']) return cls(**obj) - + + def to_dict(self): + data = super().to_dict() + data['text'] = RichText.richtext_to_dict(self.text) + data['anchor_name'] = self.anchor_name + return data + class RichTextReference(RichText): """ @@ -15452,7 +15615,6 @@ class RichTextReference(RichText): :return: Instance of the class :rtype: :class:`RichTextReference` """ - def __init__(self, text: RichText, name: str, **kwargs): super().__init__(type='reference', **kwargs) self.text: RichText = text @@ -15464,7 +15626,13 @@ def de_json(cls, json_string): obj = cls.check_json(json_string) obj['text'] = RichText.de_json(obj['text']) return cls(**obj) - + + def to_dict(self): + data = super().to_dict() + data['text'] = RichText.richtext_to_dict(self.text) + data['name'] = self.name + return data + class RichTextReferenceLink(RichText): """ @@ -15483,9 +15651,7 @@ class RichTextReferenceLink(RichText): :return: Instance of the class :rtype: :class:`RichTextReferenceLink` - """ - def __init__(self, text: RichText, reference_name: str, **kwargs): super().__init__(type='reference_link', **kwargs) self.text: RichText = text @@ -15498,6 +15664,12 @@ def de_json(cls, json_string): obj['text'] = RichText.de_json(obj['text']) return cls(**obj) + def to_dict(self): + data = super().to_dict() + data['text'] = RichText.richtext_to_dict(self.text) + data['reference_name'] = self.reference_name + return data + class RichBlockCaption(JsonDeserializable): """ @@ -16664,7 +16836,7 @@ def __init__(self, text: RichText, **kwargs): def to_dict(self) -> dict: data = super().to_dict() - data['text'] = self.text.to_dict() + data['text'] = RichText.richtext_to_dict(self.text) return data @@ -16693,7 +16865,7 @@ def __init__(self, text: RichText, size: int, **kwargs): def to_dict(self) -> dict: data = super().to_dict() - data['text'] = self.text.to_dict() + data['text'] = RichText.richtext_to_dict(self.text) data['size'] = self.size return data @@ -16723,7 +16895,7 @@ def __init__(self, text: RichText, language: Optional[str] = None, **kwargs): def to_dict(self) -> dict: data = super().to_dict() - data['text'] = self.text.to_dict() + data['text'] = RichText.richtext_to_dict(self.text) if self.language is not None: data['language'] = self.language return data @@ -16750,7 +16922,7 @@ def __init__(self, text: RichText, **kwargs): def to_dict(self) -> dict: data = super().to_dict() - data['text'] = self.text.to_dict() + data['text'] = RichText.richtext_to_dict(self.text) return data @@ -16873,7 +17045,7 @@ def to_dict(self) -> dict: data = super().to_dict() data['blocks'] = [block.to_dict() for block in self.blocks] if self.credit is not None: - data['credit'] = self.credit.to_dict() + data['credit'] = RichText.richtext_to_dict(self.credit) return data @@ -16902,9 +17074,9 @@ def __init__(self, text: RichText, credit: Optional[RichText] = None, **kwargs): def to_dict(self) -> dict: data = super().to_dict() - data['text'] = self.text.to_dict() + data['text'] = RichText.richtext_to_dict(self.text) if self.credit is not None: - data['credit'] = self.credit.to_dict() + data['credit'] = RichText.richtext_to_dict(self.credit) return data @@ -17016,7 +17188,7 @@ def to_dict(self) -> dict: if self.is_striped is not None: data['is_striped'] = self.is_striped if self.caption is not None: - data['caption'] = self.caption.to_dict() + data['caption'] = RichText.richtext_to_dict(self.caption) return data @@ -17055,7 +17227,7 @@ def __init__( def to_dict(self) -> dict: data = super().to_dict() - data['summary'] = self.summary.to_dict() + data['summary'] = RichText.richtext_to_dict(self.summary) data['blocks'] = [block.to_dict() for block in self.blocks] if self.is_open is not None: data['is_open'] = self.is_open @@ -17292,7 +17464,7 @@ def __init__(self, text: RichText, **kwargs): def to_dict(self) -> dict: data = super().to_dict() - data['text'] = self.text.to_dict() + data['text'] = RichText.richtext_to_dict(self.text) return data diff --git a/tests/test_types.py b/tests/test_types.py index ba6211af7..398100e83 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -387,3 +387,46 @@ def test_chat_administrator_rights_can_manage_tags(): can_manage_tags=True, ) assert rights.to_dict()['can_manage_tags'] is True + + +def test_rich_text_plain_string_serialization(): + # RichText as a plain string serialized through InputRichMessage/InputRichBlock + block = types.InputRichBlockParagraph(text='plain text') + msg = types.InputRichMessage(blocks=[block]) + d = msg.to_dict() + assert d['blocks'][0]['type'] == 'paragraph' + assert d['blocks'][0]['text'] == 'plain text' + + +def test_rich_text_nested_list_serialization(): + # RichText as a nested list serialized through InputRichMessage/InputRichBlock + block = types.InputRichBlockParagraph(text=['hello ', types.RichTextBold(text='world')]) + msg = types.InputRichMessage(blocks=[block]) + d = msg.to_dict() + assert d['blocks'][0]['type'] == 'paragraph' + text_list = d['blocks'][0]['text'] + assert isinstance(text_list, list) + assert text_list[0] == 'hello ' + assert text_list[1] == {'type': 'bold', 'text': 'world'} + + +def test_rich_text_nested_richtext_serialization(): + # RichText as a nested RichText object serialized through InputRichMessage/InputRichBlock + bold = types.RichTextBold(text='bold text') + block = types.InputRichBlockParagraph(text=bold) + msg = types.InputRichMessage(blocks=[block]) + d = msg.to_dict() + assert d['blocks'][0]['type'] == 'paragraph' + assert d['blocks'][0]['text'] == {'type': 'bold', 'text': 'bold text'} + + +def test_rich_text_datetime_serialization(): + # RichTextDateTime must include all required fields (unix_time, date_time_format) + dt = types.RichTextDateTime(text='Jan 1', unix_time=1740000000, date_time_format='short') + block = types.InputRichBlockParagraph(text=dt) + msg = types.InputRichMessage(blocks=[block]) + d = msg.to_dict() + text_dict = d['blocks'][0]['text'] + assert text_dict['type'] == 'date_time' + assert text_dict['unix_time'] == 1740000000 + assert text_dict['date_time_format'] == 'short'