Stop auto-accepting ToS on sign_up, add get_tos instead

This commit is contained in:
Lonami Exo
2022-02-17 12:40:09 +01:00
parent 80d44cb75b
commit 0bc598c121
7 changed files with 240 additions and 18 deletions

View File

@@ -1,3 +1,4 @@
import asyncio
import getpass
import inspect
import os
@@ -235,7 +236,7 @@ async def sign_in(
if isinstance(result, _tl.auth.AuthorizationSignUpRequired):
# The method must return the User but we don't have it, so raise instead (matches pre-layer 104 behaviour)
self._tos = result.terms_of_service
self._tos = (result.terms_of_service, None)
raise errors.SignUpRequired()
return await _update_session_state(self, result.user)
@@ -258,15 +259,10 @@ async def sign_up(
# because the user already tried to sign in.
#
# We're emulating pre-layer 104 behaviour so except the right error:
if not self._tos:
try:
return await self.sign_in(code=code)
except errors.SignUpRequired:
pass # code is correct and was used, now need to sign in
if self._tos and self._tos.text:
sys.stderr.write("{}\n".format(self._tos.text))
sys.stderr.flush()
try:
return await self.sign_in(code=code)
except errors.SignUpRequired:
pass # code is correct and was used, now need to sign in
result = await self(_tl.fn.auth.SignUp(
phone_number=phone,
@@ -275,12 +271,23 @@ async def sign_up(
last_name=last_name
))
if self._tos:
await self(_tl.fn.help.AcceptTermsOfService(self._tos.id))
return await _update_session_state(self, result.user)
async def get_tos(self):
first_time = self._tos is None
no_tos = self._tos and self._tos[0] is None
tos_expired = self._tos and self._tos[1] is not None and asyncio.get_running_loop().time() >= self._tos[1]
if first_time or no_tos or tos_expired:
result = await self(_tl.fn.help.GetTermsOfServiceUpdate())
tos = getattr(result, 'terms_of_service', None)
self._tos = (tos, asyncio.get_running_loop().time() + result.expires)
# not stored in the client to prevent a cycle
return _custom.TermsOfService._new(self, *self._tos)
async def _update_session_state(self, user, save=True):
"""
Callback called whenever the login or sign up process completes.

View File

@@ -142,6 +142,7 @@ def init(
self.flood_sleep_threshold = flood_sleep_threshold
self._flood_waited_requests = {} # prevent calls that would floodwait entirely
self._phone_code_hash = None # used during login to prevent exposing the hash to end users
self._tos = None # used during signup and when fetching tos (tos/expiry)
# Update handling.
self._catch_up = catch_up

View File

@@ -455,10 +455,15 @@ class TelegramClient:
You must call `send_code_request` first.
**By using this method you're agreeing to Telegram's
Terms of Service. This is required and your account
will be banned otherwise.** See https://telegram.org/tos
and https://core.telegram.org/api/terms.
.. important::
When creating a new account, you must be sure to show the Terms of Service
to the user, and only after they approve, the code can accept the Terms of
Service. If not, they must be declined, in which case the account **will be
deleted**.
Make sure to use `client.get_tos` to fetch the Terms of Service, and to
use `tos.accept()` or `tos.decline()` after the user selects an option.
Arguments
first_name (`str`):
@@ -481,6 +486,16 @@ class TelegramClient:
code = input('enter code: ')
await client.sign_up('Anna', 'Banana', code=code)
# IMPORTANT: you MUST retrieve the Terms of Service and accept
# them, or Telegram has every right to delete the account.
tos = await client.get_tos()
print(tos.html)
if code('accept (y/n)?: ') == 'y':
await tos.accept()
else:
await tos.decline() # deletes the account!
"""
@forward_call(auth.send_code_request)
@@ -628,6 +643,42 @@ class TelegramClient:
await client.edit_2fa(current_password='I_<3_Telethon')
"""
@forward_call(auth.get_tos)
async def get_tos(self: 'TelegramClient') -> '_custom.TermsOfService':
"""
Fetch `Telegram's Terms of Service`_, which every user must accept in order to use
Telegram, or they must otherwise `delete their account`_.
This method **must** be called after sign up, and **should** be called again
after it expires (at the risk of having the account terminated otherwise).
See the documentation of `TermsOfService` for more information.
The library cannot automate this process because the user must read the Terms of Service.
Automating its usage without reading the terms would be done at the developer's own risk.
Example
.. code-block:: python
# Fetch the ToS, forever (this could be a separate task, for example)
while True:
tos = await client.get_tos()
if tos:
# There's an update or they must be accepted (you could show a popup)
print(tos.html)
if code('accept (y/n)?: ') == 'y':
await tos.accept()
else:
await tos.decline() # deletes the account!
# after tos.timeout expires, the method should be called again!
await asyncio.sleep(tos.timeout)
_Telegram's Terms of Service: https://telegram.org/tos
_delete their account: https://core.telegram.org/api/config#terms-of-service
"""
async def __aenter__(self):
await self.connect()
return self

View File

@@ -10,6 +10,7 @@ from ..errors._rpcbase import RpcError, ServerError, FloodError, InvalidDcError,
from .._misc import helpers, utils, hints
from .._sessions.types import Entity
from .. import errors, _tl
from ..types import _custom
from .account import ignore_takeout
_NOT_A_REQUEST = lambda: TypeError('You can only invoke requests, not types!')
@@ -134,7 +135,7 @@ async def _call(self: 'TelegramClient', sender, request, ordered=False, flood_sl
async def get_me(self: 'TelegramClient') \
-> 'typing.Union[_tl.User, _tl.InputPeerUser]':
try:
return (await self(_tl.fn.users.GetUsers([_tl.InputUserSelf()])))[0]
return _custom.User._new(self, (await self(_tl.fn.users.GetUsers([_tl.InputUserSelf()])))[0])
except UnauthorizedError:
return None