Support clicking on buttons asking for phone/location

Closes #1492.
This commit is contained in:
Lonami Exo 2020-07-04 13:12:22 +02:00
parent 7b852206f1
commit 326f70b678
2 changed files with 90 additions and 22 deletions

View File

@ -764,7 +764,8 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC):
return await self._client.download_media(self, *args, **kwargs) return await self._client.download_media(self, *args, **kwargs)
async def click(self, i=None, j=None, async def click(self, i=None, j=None,
*, text=None, filter=None, data=None): *, text=None, filter=None, data=None, share_phone=None,
share_geo=None):
""" """
Calls `button.click <telethon.tl.custom.messagebutton.MessageButton.click>` Calls `button.click <telethon.tl.custom.messagebutton.MessageButton.click>`
on the specified button. on the specified button.
@ -813,6 +814,28 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC):
that if the message does not have this data, it will that if the message does not have this data, it will
``raise DataInvalidError``. ``raise DataInvalidError``.
share_phone (`bool` | `str` | tl:`InputMediaContact`):
When clicking on a keyboard button requesting a phone number
(:tl:`KeyboardButtonRequestPhone`), this argument must be
explicitly set to avoid accidentally sharing the number.
It can be `True` to automatically share the current user's
phone, a string to share a specific phone number, or a contact
media to specify all details.
If the button is pressed without this, `ValueError` is raised.
share_geo (`tuple` | `list` | tl:`InputMediaGeoPoint`):
When clicking on a keyboard button requesting a geo location
(:tl:`KeyboardButtonRequestGeoLocation`), this argument must
be explicitly set to avoid accidentally sharing the location.
It must be a `tuple` of `float` as ``(longitude, latitude)``,
or a :tl:`InputGeoPoint` instance to avoid accidentally using
the wrong roder.
If the button is pressed without this, `ValueError` is raised.
Example: Example:
.. code-block:: python .. code-block:: python
@ -828,6 +851,9 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC):
# Click by data # Click by data
await message.click(data=b'payload') await message.click(data=b'payload')
# Click on a button requesting a phone
await message.click(0, share_phone=True)
""" """
if not self._client: if not self._client:
return return
@ -853,29 +879,35 @@ class Message(ChatGetter, SenderGetter, TLObject, abc.ABC):
if not await self.get_buttons(): if not await self.get_buttons():
return # Accessing the property sets self._buttons[_flat] return # Accessing the property sets self._buttons[_flat]
def find_button():
nonlocal i
if text is not None: if text is not None:
if callable(text): if callable(text):
for button in self._buttons_flat: for button in self._buttons_flat:
if text(button.text): if text(button.text):
return await button.click() return button
else: else:
for button in self._buttons_flat: for button in self._buttons_flat:
if button.text == text: if button.text == text:
return await button.click() return button
return return
if filter is not None: if filter is not None:
for button in self._buttons_flat: for button in self._buttons_flat:
if filter(button): if filter(button):
return await button.click() return button
return return
if i is None: if i is None:
i = 0 i = 0
if j is None: if j is None:
return await self._buttons_flat[i].click() return self._buttons_flat[i]
else: else:
return await self._buttons[i][j].click() return self._buttons[i][j]
button = find_button()
if button:
return await button.click(share_phone=share_phone, share_geo=share_geo)
async def mark_read(self): async def mark_read(self):
""" """

View File

@ -59,7 +59,7 @@ class MessageButton:
if isinstance(self.button, types.KeyboardButtonUrl): if isinstance(self.button, types.KeyboardButtonUrl):
return self.button.url return self.button.url
async def click(self): async def click(self, share_phone=None, share_geo=None):
""" """
Emulates the behaviour of clicking this button. Emulates the behaviour of clicking this button.
@ -75,6 +75,19 @@ class MessageButton:
If it's a :tl:`KeyboardButtonUrl`, the URL of the button will If it's a :tl:`KeyboardButtonUrl`, the URL of the button will
be passed to ``webbrowser.open`` and return `True` on success. be passed to ``webbrowser.open`` and return `True` on success.
If it's a :tl:`KeyboardButtonRequestPhone`, you must indicate that you
want to ``share_phone=True`` in order to share it. Sharing it is not a
default because it is a privacy concern and could happen accidentally.
You may also use ``share_phone=phone`` to share a specific number, in
which case either `str` or :tl:`InputMediaContact` should be used.
If it's a :tl:`KeyboardButtonRequestGeoLocation`, you must pass a
tuple in ``share_geo=(longitude, latitude)``. Note that Telegram seems
to have some heuristics to determine impossible locations, so changing
this value a lot quickly may not work as expected. You may also pass a
:tl:`InputGeoPoint` if you find the order confusing.
""" """
if isinstance(self.button, types.KeyboardButton): if isinstance(self.button, types.KeyboardButton):
return await self._client.send_message( return await self._client.send_message(
@ -101,3 +114,26 @@ class MessageButton:
return await self._client(req) return await self._client(req)
except BotResponseTimeoutError: except BotResponseTimeoutError:
return None return None
elif isinstance(self.button, types.KeyboardButtonRequestPhone):
if not share_phone:
raise ValueError('cannot click on phone buttons unless share_phone=True')
if share_phone == True or isinstance(share_phone, str):
me = await self._client.get_me()
share_phone = types.InputMediaContact(
phone_number=me.phone if share_phone == True else share_phone,
first_name=me.first_name or '',
last_name=me.last_name or '',
vcard=''
)
return await self._client.send_file(self._chat, share_phone)
elif isinstance(self.button, types.KeyboardButtonRequestGeoLocation):
if not share_geo:
raise ValueError('cannot click on geo buttons unless share_geo=(longitude, latitude)')
if isinstance(share_geo, (tuple, list)):
long, lat = share_geo
share_geo = types.InputMediaGeoPoint(types.InputGeoPoint(lat=lat, long=long))
return await self._client.send_file(self._chat, share_geo)