mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-02-18 04:20:57 +03:00
Stop auto-accepting ToS on sign_up, add get_tos instead
This commit is contained in:
parent
80d44cb75b
commit
0bc598c121
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -17,4 +17,5 @@ from ._custom import (
|
|||
ParticipantPermissions,
|
||||
Chat,
|
||||
User,
|
||||
TermsOfService,
|
||||
)
|
||||
|
|
|
@ -14,3 +14,4 @@ from .qrlogin import QRLogin
|
|||
from .participantpermissions import ParticipantPermissions
|
||||
from .chat import Chat
|
||||
from .user import User
|
||||
from .tos import TermsOfService
|
||||
|
|
160
telethon/types/_custom/tos.py
Normal file
160
telethon/types/_custom/tos.py
Normal file
|
@ -0,0 +1,160 @@
|
|||
import sys
|
||||
|
||||
from typing import Optional, List, TYPE_CHECKING
|
||||
from datetime import datetime
|
||||
from dataclasses import dataclass
|
||||
import mimetypes
|
||||
from .chatgetter import ChatGetter
|
||||
from .sendergetter import SenderGetter
|
||||
from .messagebutton import MessageButton
|
||||
from .forward import Forward
|
||||
from .file import File
|
||||
from .inputfile import InputFile
|
||||
from .inputmessage import InputMessage
|
||||
from .button import build_reply_markup
|
||||
from ..._misc import utils, helpers, tlobject, markdown, html
|
||||
from ... import _tl, _misc
|
||||
|
||||
|
||||
_DEFAULT_TIMEOUT = 24 * 60 * 60
|
||||
|
||||
|
||||
class TermsOfService:
|
||||
"""
|
||||
Represents `Telegram's Terms of Service`_, which every user must accept in order to use
|
||||
Telegram, or they must otherwise `delete their account`_.
|
||||
|
||||
This is not the same as the `API's Terms of Service`_, which every developer must accept
|
||||
before creating applications for Telegram.
|
||||
|
||||
You must make sure to check for the terms text (or markdown, or HTML), as well as confirm
|
||||
the user's age if required.
|
||||
|
||||
This class implements `__bool__`, meaning it will be truthy if there are terms to display,
|
||||
and falsey otherwise.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
tos = await client.get_tos()
|
||||
if tos:
|
||||
print(tos.html) # there's something to read and accept or decline
|
||||
...
|
||||
else:
|
||||
await asyncio.sleep(tos.timeout) # nothing to read, but still has tos.timeout
|
||||
|
||||
_Telegram's Terms of Service: https://telegram.org/tos
|
||||
_delete their account: https://core.telegram.org/api/config#terms-of-service
|
||||
_API's Terms of Service: https://core.telegram.org/api/terms
|
||||
"""
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
"""Plain-text version of the Terms of Service, or `None` if there is no ToS update."""
|
||||
return self._tos and self._tos.text
|
||||
|
||||
@property
|
||||
def markdown(self):
|
||||
"""Markdown-formatted version of the Terms of Service, or `None` if there is no ToS update."""
|
||||
return self._tos and markdown.unparse(self._tos.text, self._tos.entities)
|
||||
|
||||
@property
|
||||
def html(self):
|
||||
"""HTML-formatted version of the Terms of Service, or `None` if there is no ToS update."""
|
||||
return self._tos and html.unparse(self._tos.text, self._tos.entities)
|
||||
|
||||
@property
|
||||
def popup(self):
|
||||
"""`True` a popup should be shown to the user."""
|
||||
return self._tos and self._tos.popup
|
||||
|
||||
@property
|
||||
def minimum_age(self):
|
||||
"""The minimum age the user must be to accept the terms, or `None` if there's no requirement."""
|
||||
return self._tos and self._tos.min_age_confirm
|
||||
|
||||
@property
|
||||
def timeout(self):
|
||||
"""
|
||||
How many seconds are left before `client.get_tos` should be used again.
|
||||
|
||||
This value is a positive floating point number, and is monotically decreasing.
|
||||
The value will reach zero after enough seconds have elapsed. This lets you do some work
|
||||
and call sleep on the value and still wait just long enough.
|
||||
"""
|
||||
return max(0.0, self._expiry - asyncio.get_running_loop().time())
|
||||
|
||||
@property
|
||||
def expired(self):
|
||||
"""
|
||||
Returns `True` if this instance of the Terms of Service has expired and should be re-fetched.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
if tos.expired:
|
||||
tos = await client.get_tos()
|
||||
"""
|
||||
return asyncio.get_running_loop() >= self._expiry
|
||||
|
||||
def __init__(self):
|
||||
raise TypeError('You cannot create TermsOfService instances by hand!')
|
||||
|
||||
@classmethod
|
||||
def _new(cls, client, tos, expiry):
|
||||
self = cls.__new__(cls)
|
||||
self._client = client
|
||||
self._tos = tos
|
||||
self._expiry = expiry or asyncio.get_running_loop().time() + _DEFAULT_TIMEOUT
|
||||
return self
|
||||
|
||||
async def accept(self, *, age=None):
|
||||
"""
|
||||
Accept the Terms of Service.
|
||||
|
||||
Does nothing if there is nothing to accept.
|
||||
|
||||
If `minimum_age` is not `None`, the `age` parameter must be provided,
|
||||
and be greater than or equal to `minimum_age`. Otherwise, the function will fail.
|
||||
|
||||
.. code-example:
|
||||
|
||||
if tos.minimum_age:
|
||||
age = int(input('age: '))
|
||||
else:
|
||||
age = None
|
||||
|
||||
print(tos.html)
|
||||
if input('accept (y/n)?: ') == 'y':
|
||||
await tos.accept(age=age)
|
||||
"""
|
||||
if not self._tos:
|
||||
return
|
||||
|
||||
if age < (self.minimum_age or 0):
|
||||
raise ValueError('User is not old enough to accept the Terms of Service')
|
||||
|
||||
if age > 122:
|
||||
# This easter egg may be out of date by 2025
|
||||
print('Lying is done at your own risk!', file=sys.stderr)
|
||||
|
||||
await self._client(_tl.fn.help.AcceptTermsOfService(self._tos.id))
|
||||
|
||||
async def decline(self):
|
||||
"""
|
||||
Decline the Terms of Service.
|
||||
|
||||
Does nothing if there is nothing to decline.
|
||||
|
||||
.. danger::
|
||||
|
||||
Declining the Terms of Service will result in the `termination of your account`_.
|
||||
**Your account will be deleted**.
|
||||
|
||||
_termination of your account: https://core.telegram.org/api/config#terms-of-service
|
||||
"""
|
||||
if not self._tos:
|
||||
return
|
||||
|
||||
await self._client(_tl.fn.account.DeleteAccount('Decline ToS update'))
|
||||
|
||||
def __bool__(self):
|
||||
return self._tos is not None
|
Loading…
Reference in New Issue
Block a user