diff --git a/readthedocs/examples/users.rst b/readthedocs/examples/users.rst index d9c648ae..ea83871d 100644 --- a/readthedocs/examples/users.rst +++ b/readthedocs/examples/users.rst @@ -25,7 +25,7 @@ you should use :tl:`GetFullUser`: # or even full = await client(GetFullUserRequest('username')) - bio = full.about + bio = full.full_user.about See :tl:`UserFull` to know what other fields you can access. diff --git a/telethon/_client/chats.py b/telethon/_client/chats.py index 0759acc2..cd6d9726 100644 --- a/telethon/_client/chats.py +++ b/telethon/_client/chats.py @@ -634,13 +634,8 @@ async def get_permissions( entity = await self.get_entity(entity) if not user: - if isinstance(entity, _tl.Channel): - FullChat = await self(_tl.fn.channels.GetFullChannel(entity)) - elif isinstance(entity, _tl.Chat): - FullChat = await self(_tl.fn.messages.GetFullChat(entity)) - else: - return - return FullChat.chats[0].default_banned_rights + if helpers._entity_type(entity) != helpers._EntityType.USER: + return entity.default_banned_rights entity = await self.get_input_entity(entity) user = await self.get_input_entity(user) diff --git a/telethon/_client/messages.py b/telethon/_client/messages.py index e85921f3..250582d0 100644 --- a/telethon/_client/messages.py +++ b/telethon/_client/messages.py @@ -426,8 +426,10 @@ async def send_message( ttl: int = None, # - Send options reply_to: 'typing.Union[int, _tl.Message]' = None, + send_as: 'hints.EntityLike' = None, clear_draft: bool = False, background: bool = None, + noforwards: bool = None, schedule: 'hints.DateLike' = None, comment_to: 'typing.Union[int, _tl.Message]' = None, ) -> '_tl.Message': @@ -483,7 +485,7 @@ async def send_message( entity, message._file._media, reply_to_msg_id=reply_to, message=message._text, entities=message._fmt_entities, reply_markup=message._reply_markup, silent=message._silent, schedule_date=schedule, clear_draft=clear_draft, - background=background + background=background, noforwards=noforwards, send_as=send_as ) else: request = _tl.fn.messages.SendMessage( @@ -496,7 +498,9 @@ async def send_message( silent=silent, background=background, reply_markup=_custom.button.build_reply_markup(buttons), - schedule_date=schedule + schedule_date=schedule, + noforwards=noforwards, + send_as=send_as ) result = await self(request) @@ -525,7 +529,9 @@ async def forward_messages( with_my_score: bool = None, silent: bool = None, as_album: bool = None, - schedule: 'hints.DateLike' = None + schedule: 'hints.DateLike' = None, + noforwards: bool = None, + send_as: 'hints.EntityLike' = None ) -> 'typing.Sequence[_tl.Message]': if as_album is not None: warnings.warn('the as_album argument is deprecated and no longer has any effect') @@ -565,7 +571,9 @@ async def forward_messages( silent=silent, background=background, with_my_score=with_my_score, - schedule_date=schedule + schedule_date=schedule, + noforwards=noforwards, + send_as=send_as ) result = await self(req) sent.extend(self._get_response_message(req, result, entity)) diff --git a/telethon/_client/uploads.py b/telethon/_client/uploads.py index 92a2d36e..a92df8f4 100644 --- a/telethon/_client/uploads.py +++ b/telethon/_client/uploads.py @@ -113,6 +113,8 @@ async def send_file( reply_to: 'typing.Union[int, _tl.Message]' = None, clear_draft: bool = False, background: bool = None, + noforwards: bool = None, + send_as: 'hints.EntityLike' = None, schedule: 'hints.DateLike' = None, comment_to: 'typing.Union[int, _tl.Message]' = None, ) -> '_tl.Message': @@ -146,13 +148,16 @@ async def send_file( background=background, schedule=schedule, comment_to=comment_to, + noforwards=noforwards, + send_as=send_as ) async def _send_album(self: 'TelegramClient', entity, files, caption='', progress_callback=None, reply_to=None, parse_mode=(), silent=None, schedule=None, supports_streaming=None, clear_draft=None, - force_document=False, background=None, ttl=None): + force_document=False, background=None, ttl=None, + send_as=None, noforwards=None): """Specialized version of .send_file for albums""" # We don't care if the user wants to avoid cache, we will use it # anyway. Why? The cached version will be exactly the same thing @@ -212,7 +217,7 @@ async def _send_album(self: 'TelegramClient', entity, files, caption='', request = _tl.fn.messages.SendMultiMedia( entity, reply_to_msg_id=reply_to, multi_media=media, silent=silent, schedule_date=schedule, clear_draft=clear_draft, - background=background + background=background, noforwards=noforwards, send_as=send_as ) result = await self(request) diff --git a/telethon/_events/chataction.py b/telethon/_events/chataction.py index 671347c0..0bf83aa1 100644 --- a/telethon/_events/chataction.py +++ b/telethon/_events/chataction.py @@ -76,6 +76,11 @@ class ChatAction(EventBuilder): return cls.Event(msg, added_by=added_by, users=action.users) + elif isinstance(action, _tl.MessageActionChatJoinedByRequest): + # user joined from join request (after getting admin approval) + return cls.Event(msg, + from_approval=True, + users=msg.from_id) elif isinstance(action, _tl.MessageActionChatDeleteUser): return cls.Event(msg, kicked_by=utils.get_peer_id(msg.from_id) if msg.from_id else True, @@ -138,6 +143,10 @@ class ChatAction(EventBuilder): user_kicked (`bool`): `True` if the user was kicked by some other. + user_approved (`bool`): + `True` if the user's join request was approved. + along with `user_joined` will be also True. + created (`bool`, optional): `True` if this chat was just created. @@ -152,7 +161,7 @@ class ChatAction(EventBuilder): """ def __init__(self, where, new_photo=None, - added_by=None, kicked_by=None, created=None, + added_by=None, kicked_by=None, created=None, from_approval=None, users=None, new_title=None, pin_ids=None, pin=None, new_score=None): if isinstance(where, _tl.MessageService): self.action_message = where @@ -177,11 +186,12 @@ class ChatAction(EventBuilder): self.user_added = self.user_joined = self.user_left = \ self.user_kicked = self.unpin = False - if added_by is True: + if added_by is True or from_approval is True: self.user_joined = True elif added_by: self.user_added = True self._added_by = added_by + self.user_approved = from_approval # If `from_id` was not present (it's `True`) or the affected # user was "kicked by itself", then it left. Else it was kicked. diff --git a/telethon/_misc/html.py b/telethon/_misc/html.py index c17f6f7c..cdf4ced4 100644 --- a/telethon/_misc/html.py +++ b/telethon/_misc/html.py @@ -47,6 +47,8 @@ class HTMLToTelegramParser(HTMLParser): EntityType = _tl.MessageEntityUnderline elif tag == 'del' or tag == 's': EntityType = _tl.MessageEntityStrike + elif tag == 'tg-spoiler': + EntityType = _tl.MessageEntitySpoiler elif tag == 'blockquote': EntityType = _tl.MessageEntityBlockquote elif tag == 'code': diff --git a/telethon/_misc/markdown.py b/telethon/_misc/markdown.py index 8dc82701..3b62c995 100644 --- a/telethon/_misc/markdown.py +++ b/telethon/_misc/markdown.py @@ -19,6 +19,7 @@ DELIMITERS = { _tl.MessageEntityCode: ('`', '`'), _tl.MessageEntityItalic: ('_', '_'), _tl.MessageEntityStrike: ('~~', '~~'), + _tl.MessageEntitySpoiler: ('||', '||'), _tl.MessageEntityUnderline: ('# ', ''), } diff --git a/telethon/types/_custom/button.py b/telethon/types/_custom/button.py index ffac7c99..27f1aae9 100644 --- a/telethon/types/_custom/button.py +++ b/telethon/types/_custom/button.py @@ -1,6 +1,8 @@ +import typing + from .messagebutton import MessageButton from ... import _tl -from ..._misc import utils +from ..._misc import utils, hints class Button: @@ -54,6 +56,7 @@ class Button: _tl.KeyboardButtonCallback, _tl.KeyboardButtonGame, _tl.KeyboardButtonSwitchInline, + _tl.KeyboardButtonUserProfile, _tl.KeyboardButtonUrl, _tl.InputKeyboardButtonUrlAuth )) @@ -166,6 +169,29 @@ class Button: fwd_text=fwd_text ) + @staticmethod + def mention(text, input_entity): + """ + Creates a new inline button linked to the profile of user. + + Args: + input_entity: + Input entity of :tl:User to use for profile button. + By default, this is the logged in user (itself), although + you may pass a different input peer. + + .. note:: + + For now, you cannot use ID or username for this argument. + If you want to use different user, you must manually use + `client.get_input_entity() `. + """ + return _tl.InputKeyboardButtonUserProfile( + text, + utils.get_input_user(input_entity or _tl.InputUserSelf()) + ) + + @classmethod def text(cls, text, *, resize=None, single_use=None, selective=None): """ @@ -387,4 +413,4 @@ def build_reply_markup( return _tl.ReplyInlineMarkup(rows) # elif is_normal: return _tl.ReplyKeyboardMarkup( - rows, resize=resize, single_use=single_use, selective=selective) + rows, resize=resize, single_use=single_use, selective=selective) \ No newline at end of file diff --git a/telethon/types/_custom/inlineresult.py b/telethon/types/_custom/inlineresult.py index 052be4dd..45867edb 100644 --- a/telethon/types/_custom/inlineresult.py +++ b/telethon/types/_custom/inlineresult.py @@ -104,7 +104,7 @@ class InlineResult: async def click(self, entity=None, reply_to=None, comment_to=None, silent=False, clear_draft=False, hide_via=False, - background=None): + background=None, send_as=None): """ Clicks this result and sends the associated `message`. @@ -137,6 +137,9 @@ class InlineResult: background (`bool`, optional): Whether the message should be send in background. + send_as (`entity`, optional): + The channel entity on behalf of which, message should be send. + """ if entity: entity = await self._client.get_input_entity(entity) @@ -158,7 +161,8 @@ class InlineResult: background=background, clear_draft=clear_draft, hide_via=hide_via, - reply_to_msg_id=reply_id + reply_to_msg_id=reply_id, + send_as=send_as ) return self._client._get_response_message( req, await self._client(req), entity) diff --git a/telethon/types/_custom/message.py b/telethon/types/_custom/message.py index c1e213aa..e1bfff41 100644 --- a/telethon/types/_custom/message.py +++ b/telethon/types/_custom/message.py @@ -189,6 +189,10 @@ class Message(ChatGetter, SenderGetter): The number of times this message has been forwarded. """) + noforwards = _fwd('noforwards', """ + does the message was sent with noforwards restriction. + """) + replies = _fwd('replies', """ The number of times another message has replied to this message. """) @@ -205,13 +209,17 @@ class Message(ChatGetter, SenderGetter): grouped_id = _fwd('grouped_id', """ If this message belongs to a group of messages (photo albums or video albums), all of them will - have the same value here. + have the same value here.""") - restriction_reason (List[:tl:`RestrictionReason`]) + restriction_reason = _fwd('restriction_reason', """ An optional list of reasons why this message was restricted. If the list is `None`, this message has not been restricted. """) + reactions = _fwd('reactions', """ + emoji reactions attached to the message. + """) + ttl_period = _fwd('ttl_period', """ The Time To Live period configured for this message. The message should be erased from wherever it's stored (memory, a diff --git a/telethon/types/_custom/messagebutton.py b/telethon/types/_custom/messagebutton.py index 2d588727..eee4486e 100644 --- a/telethon/types/_custom/messagebutton.py +++ b/telethon/types/_custom/messagebutton.py @@ -71,6 +71,10 @@ class MessageButton: If it's an inline :tl:`KeyboardButtonCallback` with text and data, it will be "clicked" and the :tl:`BotCallbackAnswer` returned. + If it's an inline :tl:`KeyboardButtonUserProfile` button, the + `client.get_entity` will be called and the resulting :tl:User will be + returned. + If it's an inline :tl:`KeyboardButtonSwitchInline` button, the :tl:`StartBot` will be invoked and the resulting updates returned. @@ -107,6 +111,8 @@ class MessageButton: return await self._client(req) except BotResponseTimeoutError: return None + elif isinstance(self.button, _tl.KeyboardButtonUserProfile): + return await self._client.get_entity(self.button.user_id) elif isinstance(self.button, _tl.KeyboardButtonSwitchInline): return await self._client(_tl.fn.messages.StartBot( bot=self._bot, peer=self._chat, start_param=self.button.query @@ -143,4 +149,4 @@ class MessageButton: long, lat = share_geo share_geo = _tl.InputMediaGeoPoint(_tl.InputGeoPoint(lat=lat, long=long)) - return await self._client.send_file(self._chat, share_geo) + return await self._client.send_file(self._chat, share_geo) \ No newline at end of file