mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-08-04 12:10:21 +03:00
Merge branch 'master' of github.com:lonamiwebs/Telethon
This commit is contained in:
commit
9c990330fd
12
setup.py
12
setup.py
|
@ -8,9 +8,12 @@ https://github.com/pypa/sampleproject
|
||||||
Extra supported commands are:
|
Extra supported commands are:
|
||||||
* gen_tl, to generate the classes required for Telethon to run
|
* gen_tl, to generate the classes required for Telethon to run
|
||||||
* clean_tl, to clean these generated classes
|
* clean_tl, to clean these generated classes
|
||||||
|
* pypi, to generate sdist, bdist_wheel, and push to PyPi
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# To use a consistent encoding
|
# To use a consistent encoding
|
||||||
|
from subprocess import run
|
||||||
|
from shutil import rmtree
|
||||||
from codecs import open
|
from codecs import open
|
||||||
from sys import argv
|
from sys import argv
|
||||||
from os import path
|
from os import path
|
||||||
|
@ -44,6 +47,15 @@ if __name__ == '__main__':
|
||||||
TLGenerator('telethon/tl').clean_tlobjects()
|
TLGenerator('telethon/tl').clean_tlobjects()
|
||||||
print('Done.')
|
print('Done.')
|
||||||
|
|
||||||
|
elif len(argv) >= 2 and argv[1] == 'pypi':
|
||||||
|
for x in ('build', 'dist', 'Telethon.egg-info'):
|
||||||
|
rmtree(x, ignore_errors=True)
|
||||||
|
run('python3 setup.py sdist', shell=True)
|
||||||
|
run('python3 setup.py bdist_wheel', shell=True)
|
||||||
|
run('twine upload dist/*', shell=True)
|
||||||
|
for x in ('build', 'dist', 'Telethon.egg-info'):
|
||||||
|
rmtree(x, ignore_errors=True)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if not TelegramClient:
|
if not TelegramClient:
|
||||||
print('Run `python3', argv[0], 'gen_tl` first.')
|
print('Run `python3', argv[0], 'gen_tl` first.')
|
||||||
|
|
|
@ -22,7 +22,9 @@ class ChannelInvalidError(BadRequestError):
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super(Exception, self).__init__(
|
super(Exception, self).__init__(
|
||||||
self,
|
self,
|
||||||
'Invalid channel object. Make sure to pass the right types.'
|
'Invalid channel object. Make sure to pass the right types,'
|
||||||
|
' for instance making sure that the request is designed for '
|
||||||
|
'channels or otherwise look for a different one more suited.'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,7 +42,12 @@ class ChatIdInvalidError(BadRequestError):
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super(Exception, self).__init__(
|
super(Exception, self).__init__(
|
||||||
self,
|
self,
|
||||||
'Invalid object ID for a chat. Make sure to pass the right types.'
|
'Invalid object ID for a chat. Make sure to pass the right types,'
|
||||||
|
' for instance making sure that the request is designed for chats'
|
||||||
|
' (not channels/megagroups) or otherwise look for a different one'
|
||||||
|
' more suited.\nAn example working with a megagroup and'
|
||||||
|
' AddChatUserRequest, it will fail because megagroups are channels'
|
||||||
|
'. Use InviteToChannelRequest instead.'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,7 +55,8 @@ class ConnectionLangPackInvalid(BadRequestError):
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super(Exception, self).__init__(
|
super(Exception, self).__init__(
|
||||||
self,
|
self,
|
||||||
'The specified language pack is not valid.'
|
'The specified language pack is not valid. This is meant to be '
|
||||||
|
'used by official applications only so far, leave it empty.'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -284,7 +292,7 @@ class UsernameInvalidError(BadRequestError):
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super(Exception, self).__init__(
|
super(Exception, self).__init__(
|
||||||
self,
|
self,
|
||||||
'Unacceptable username. Must match r"[a-zA-Z][\w\d]{4,31}"'
|
'Unacceptable username. Must match r"[a-zA-Z][\w\d]{4,31}".'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -292,7 +300,7 @@ class UsernameNotModifiedError(BadRequestError):
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super(Exception, self).__init__(
|
super(Exception, self).__init__(
|
||||||
self,
|
self,
|
||||||
'The username is not different from the current username'
|
'The username is not different from the current username.'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -300,7 +308,7 @@ class UsernameNotOccupiedError(BadRequestError):
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super(Exception, self).__init__(
|
super(Exception, self).__init__(
|
||||||
self,
|
self,
|
||||||
'See issue #96 for Telethon - try upgrading the library.'
|
'The username is not in use by anyone else yet.'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -333,7 +341,9 @@ class UserIdInvalidError(BadRequestError):
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super(Exception, self).__init__(
|
super(Exception, self).__init__(
|
||||||
self,
|
self,
|
||||||
'Invalid object ID for an user. Make sure to pass the right types.'
|
'Invalid object ID for an user. Make sure to pass the right types,'
|
||||||
|
'for instance making sure that the request is designed for users'
|
||||||
|
'or otherwise look for a different one more suited.'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ class BinaryReader:
|
||||||
'Either bytes or a stream must be provided')
|
'Either bytes or a stream must be provided')
|
||||||
|
|
||||||
self.reader = BufferedReader(self.stream)
|
self.reader = BufferedReader(self.stream)
|
||||||
|
self._last = None # Should come in handy to spot -404 errors
|
||||||
|
|
||||||
# region Reading
|
# region Reading
|
||||||
|
|
||||||
|
@ -57,8 +58,12 @@ class BinaryReader:
|
||||||
"""Read the given amount of bytes"""
|
"""Read the given amount of bytes"""
|
||||||
result = self.reader.read(length)
|
result = self.reader.read(length)
|
||||||
if len(result) != length:
|
if len(result) != length:
|
||||||
raise BufferError('No more data left to read')
|
raise BufferError(
|
||||||
|
'No more data left to read (need {}, got {}: {}); last read {}'
|
||||||
|
.format(length, len(result), repr(result), repr(self._last))
|
||||||
|
)
|
||||||
|
|
||||||
|
self._last = result
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_bytes(self):
|
def get_bytes(self):
|
||||||
|
|
|
@ -52,7 +52,7 @@ class TelegramBareClient:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Current TelegramClient version
|
# Current TelegramClient version
|
||||||
__version__ = '0.13.3'
|
__version__ = '0.13.4'
|
||||||
|
|
||||||
# TODO Make this thread-safe, all connections share the same DC
|
# TODO Make this thread-safe, all connections share the same DC
|
||||||
_dc_options = None
|
_dc_options = None
|
||||||
|
|
|
@ -41,7 +41,7 @@ from .tl.types import (
|
||||||
InputMediaUploadedDocument, InputMediaUploadedPhoto, InputPeerEmpty,
|
InputMediaUploadedDocument, InputMediaUploadedPhoto, InputPeerEmpty,
|
||||||
Message, MessageMediaContact, MessageMediaDocument, MessageMediaPhoto,
|
Message, MessageMediaContact, MessageMediaDocument, MessageMediaPhoto,
|
||||||
InputUserSelf, UserProfilePhoto, ChatPhoto, UpdateMessageID,
|
InputUserSelf, UserProfilePhoto, ChatPhoto, UpdateMessageID,
|
||||||
UpdateNewMessage
|
UpdateNewMessage, UpdateShortSentMessage
|
||||||
)
|
)
|
||||||
from .utils import find_user_or_chat, get_extension
|
from .utils import find_user_or_chat, get_extension
|
||||||
|
|
||||||
|
@ -136,18 +136,18 @@ class TelegramClient(TelegramBareClient):
|
||||||
|
|
||||||
# region Connecting
|
# region Connecting
|
||||||
|
|
||||||
def connect(self, *args):
|
def connect(self, exported_auth=None):
|
||||||
"""Connects to the Telegram servers, executing authentication if
|
"""Connects to the Telegram servers, executing authentication if
|
||||||
required. Note that authenticating to the Telegram servers is
|
required. Note that authenticating to the Telegram servers is
|
||||||
not the same as authenticating the desired user itself, which
|
not the same as authenticating the desired user itself, which
|
||||||
may require a call (or several) to 'sign_in' for the first time.
|
may require a call (or several) to 'sign_in' for the first time.
|
||||||
|
|
||||||
*args will be ignored.
|
exported_auth is meant for internal purposes and can be ignored.
|
||||||
"""
|
"""
|
||||||
if self._sender and self._sender.is_connected():
|
if self._sender and self._sender.is_connected():
|
||||||
return
|
return
|
||||||
|
|
||||||
ok = super().connect()
|
ok = super().connect(exported_auth=exported_auth)
|
||||||
# The main TelegramClient is the only one that will have
|
# The main TelegramClient is the only one that will have
|
||||||
# constant_read, since it's also the only one who receives
|
# constant_read, since it's also the only one who receives
|
||||||
# updates and need to be processed as soon as they occur.
|
# updates and need to be processed as soon as they occur.
|
||||||
|
@ -230,6 +230,8 @@ class TelegramClient(TelegramBareClient):
|
||||||
threading.get_ident() == self._recv_thread.ident:
|
threading.get_ident() == self._recv_thread.ident:
|
||||||
raise AssertionError('Cannot invoke requests from the ReadThread')
|
raise AssertionError('Cannot invoke requests from the ReadThread')
|
||||||
|
|
||||||
|
self.updates.check_error()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Users may call this method from within some update handler.
|
# Users may call this method from within some update handler.
|
||||||
# If this is the case, then the thread invoking the request
|
# If this is the case, then the thread invoking the request
|
||||||
|
@ -292,8 +294,7 @@ class TelegramClient(TelegramBareClient):
|
||||||
|
|
||||||
If no phone or code is provided, then the sole password will be used.
|
If no phone or code is provided, then the sole password will be used.
|
||||||
The password should be used after a normal authorization attempt
|
The password should be used after a normal authorization attempt
|
||||||
has happened and an RPCError with `.password_required = True` was
|
has happened and an SessionPasswordNeededError was raised.
|
||||||
raised.
|
|
||||||
|
|
||||||
To login as a bot, only `bot_token` should be provided.
|
To login as a bot, only `bot_token` should be provided.
|
||||||
This should equal to the bot access hash provided by
|
This should equal to the bot access hash provided by
|
||||||
|
@ -417,14 +418,26 @@ class TelegramClient(TelegramBareClient):
|
||||||
If 'reply_to' is set to either a message or a message ID,
|
If 'reply_to' is set to either a message or a message ID,
|
||||||
the sent message will be replying to such message.
|
the sent message will be replying to such message.
|
||||||
"""
|
"""
|
||||||
|
entity = self.get_entity(entity)
|
||||||
request = SendMessageRequest(
|
request = SendMessageRequest(
|
||||||
peer=self.get_entity(entity),
|
peer=entity,
|
||||||
message=message,
|
message=message,
|
||||||
entities=[],
|
entities=[],
|
||||||
no_webpage=not link_preview,
|
no_webpage=not link_preview,
|
||||||
reply_to_msg_id=self._get_reply_to(reply_to)
|
reply_to_msg_id=self._get_reply_to(reply_to)
|
||||||
)
|
)
|
||||||
result = self(request)
|
result = self(request)
|
||||||
|
if isinstance(request, UpdateShortSentMessage):
|
||||||
|
return Message(
|
||||||
|
id=result.id,
|
||||||
|
to_id=entity,
|
||||||
|
message=message,
|
||||||
|
date=result.date,
|
||||||
|
out=result.out,
|
||||||
|
media=result.media,
|
||||||
|
entities=result.entities
|
||||||
|
)
|
||||||
|
|
||||||
# Telegram seems to send updateMessageID first, then updateNewMessage,
|
# Telegram seems to send updateMessageID first, then updateNewMessage,
|
||||||
# however let's not rely on that just in case.
|
# however let's not rely on that just in case.
|
||||||
msg_id = None
|
msg_id = None
|
||||||
|
@ -1026,5 +1039,10 @@ class TelegramClient(TelegramBareClient):
|
||||||
self._recv_thread = None # Not running anymore
|
self._recv_thread = None # Not running anymore
|
||||||
self.reconnect()
|
self.reconnect()
|
||||||
return
|
return
|
||||||
|
except Exception as e:
|
||||||
|
# Unknown exception, pass it to the main thread
|
||||||
|
self.updates.set_error(e)
|
||||||
|
self._recv_thread = None
|
||||||
|
return
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
|
|
@ -34,7 +34,10 @@ class UpdateState:
|
||||||
if not self._updates:
|
if not self._updates:
|
||||||
self._updates_available.clear()
|
self._updates_available.clear()
|
||||||
|
|
||||||
return update
|
if isinstance(update, Exception):
|
||||||
|
raise update # Some error was set through .set_error()
|
||||||
|
|
||||||
|
return update
|
||||||
|
|
||||||
def get_polling(self):
|
def get_polling(self):
|
||||||
return self._polling
|
return self._polling
|
||||||
|
@ -47,6 +50,21 @@ class UpdateState:
|
||||||
|
|
||||||
polling = property(fget=get_polling, fset=set_polling)
|
polling = property(fget=get_polling, fset=set_polling)
|
||||||
|
|
||||||
|
def set_error(self, error):
|
||||||
|
"""Sets an error, so that the next call to .poll() will raise it.
|
||||||
|
Can be (and is) used to pass exceptions between threads.
|
||||||
|
"""
|
||||||
|
with self._updates_lock:
|
||||||
|
# Insert at the beginning so the very next poll causes an error
|
||||||
|
# TODO Should this reset the pts and such?
|
||||||
|
self._updates.insert(0, error)
|
||||||
|
self._updates_available.set()
|
||||||
|
|
||||||
|
def check_error(self):
|
||||||
|
with self._updates_lock:
|
||||||
|
if self._updates and isinstance(self._updates[0], Exception):
|
||||||
|
raise self._updates.pop()
|
||||||
|
|
||||||
def process(self, update):
|
def process(self, update):
|
||||||
"""Processes an update object. This method is normally called by
|
"""Processes an update object. This method is normally called by
|
||||||
the library itself.
|
the library itself.
|
||||||
|
@ -59,9 +77,10 @@ class UpdateState:
|
||||||
self._state = update
|
self._state = update
|
||||||
elif not hasattr(update, 'pts') or update.pts > self._state.pts:
|
elif not hasattr(update, 'pts') or update.pts > self._state.pts:
|
||||||
self._state.pts = getattr(update, 'pts', self._state.pts)
|
self._state.pts = getattr(update, 'pts', self._state.pts)
|
||||||
for handler in self.handlers:
|
|
||||||
handler(update)
|
|
||||||
|
|
||||||
if self._polling:
|
if self._polling:
|
||||||
self._updates.append(update)
|
self._updates.append(update)
|
||||||
self._updates_available.set()
|
self._updates_available.set()
|
||||||
|
|
||||||
|
for handler in self.handlers:
|
||||||
|
handler(update)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user