mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-08-05 04:30:22 +03:00
Merge branch 'master' of git://github.com/LonamiWebs/Telethon
This commit is contained in:
commit
8ada0e176d
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -8,6 +8,9 @@ telethon/tl/all_tlobjects.py
|
||||||
usermedia/
|
usermedia/
|
||||||
api/settings
|
api/settings
|
||||||
|
|
||||||
|
# Quick tests should live in this file
|
||||||
|
example.py
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
|
|
1
setup.py
1
setup.py
|
@ -60,6 +60,7 @@ setup(
|
||||||
'Programming Language :: Python :: 3.3',
|
'Programming Language :: Python :: 3.3',
|
||||||
'Programming Language :: Python :: 3.4',
|
'Programming Language :: Python :: 3.4',
|
||||||
'Programming Language :: Python :: 3.5',
|
'Programming Language :: Python :: 3.5',
|
||||||
|
'Programming Language :: Python :: 3.6'
|
||||||
],
|
],
|
||||||
|
|
||||||
# What does your project relate to?
|
# What does your project relate to?
|
||||||
|
|
|
@ -32,12 +32,12 @@ def rpc_message_to_error(code, message):
|
||||||
return cls(extra=extra)
|
return cls(extra=extra)
|
||||||
|
|
||||||
elif code == 403:
|
elif code == 403:
|
||||||
return ForbiddenError()
|
return ForbiddenError(message)
|
||||||
|
|
||||||
elif code == 404:
|
elif code == 404:
|
||||||
return NotFoundError()
|
return NotFoundError(message)
|
||||||
|
|
||||||
elif code == 500:
|
elif code == 500:
|
||||||
return ServerError()
|
return ServerError(message)
|
||||||
|
|
||||||
return RPCError('{} (code {})'.format(message, code))
|
return RPCError('{} (code {})'.format(message, code))
|
||||||
|
|
|
@ -38,6 +38,10 @@ class ForbiddenError(RPCError):
|
||||||
code = 403
|
code = 403
|
||||||
message = 'FORBIDDEN'
|
message = 'FORBIDDEN'
|
||||||
|
|
||||||
|
def __init__(self, message):
|
||||||
|
super().__init__(self, message)
|
||||||
|
self.message = message
|
||||||
|
|
||||||
|
|
||||||
class NotFoundError(RPCError):
|
class NotFoundError(RPCError):
|
||||||
"""
|
"""
|
||||||
|
@ -46,6 +50,10 @@ class NotFoundError(RPCError):
|
||||||
code = 404
|
code = 404
|
||||||
message = 'NOT_FOUND'
|
message = 'NOT_FOUND'
|
||||||
|
|
||||||
|
def __init__(self, message):
|
||||||
|
super().__init__(self, message)
|
||||||
|
self.message = message
|
||||||
|
|
||||||
|
|
||||||
class FloodError(RPCError):
|
class FloodError(RPCError):
|
||||||
"""
|
"""
|
||||||
|
@ -67,6 +75,10 @@ class ServerError(RPCError):
|
||||||
code = 500
|
code = 500
|
||||||
message = 'INTERNAL'
|
message = 'INTERNAL'
|
||||||
|
|
||||||
|
def __init__(self, message):
|
||||||
|
super().__init__(self, message)
|
||||||
|
self.message = message
|
||||||
|
|
||||||
|
|
||||||
class BadMessageError(Exception):
|
class BadMessageError(Exception):
|
||||||
"""Occurs when handling a bad_message_notification"""
|
"""Occurs when handling a bad_message_notification"""
|
||||||
|
|
|
@ -148,7 +148,7 @@ def do_authentication(transport):
|
||||||
server_time = dh_inner_data_reader.read_int()
|
server_time = dh_inner_data_reader.read_int()
|
||||||
time_offset = server_time - int(time.time())
|
time_offset = server_time - int(time.time())
|
||||||
|
|
||||||
b = get_int(os.urandom(2048), signed=False)
|
b = get_int(os.urandom(256), signed=False)
|
||||||
gb = pow(g, b, dh_prime)
|
gb = pow(g, b, dh_prime)
|
||||||
gab = pow(ga, b, dh_prime)
|
gab = pow(ga, b, dh_prime)
|
||||||
|
|
||||||
|
|
|
@ -113,11 +113,13 @@ class MtProtoSender:
|
||||||
self._logger.info('Request result received')
|
self._logger.info('Request result received')
|
||||||
self._logger.debug('receive() released the lock')
|
self._logger.debug('receive() released the lock')
|
||||||
|
|
||||||
def receive_update(self, timeout=timedelta(seconds=5)):
|
def receive_updates(self, timeout=timedelta(seconds=5)):
|
||||||
"""Receives an update object and returns its result"""
|
"""Receives one or more update objects
|
||||||
|
and returns them as a list
|
||||||
|
"""
|
||||||
updates = []
|
updates = []
|
||||||
self.receive(timeout=timeout, updates=updates)
|
self.receive(timeout=timeout, updates=updates)
|
||||||
return updates[0]
|
return updates
|
||||||
|
|
||||||
def cancel_receive(self):
|
def cancel_receive(self):
|
||||||
"""Cancels any pending receive operation
|
"""Cancels any pending receive operation
|
||||||
|
@ -131,13 +133,13 @@ class MtProtoSender:
|
||||||
def _send_packet(self, packet, request):
|
def _send_packet(self, packet, request):
|
||||||
"""Sends the given packet bytes with the additional
|
"""Sends the given packet bytes with the additional
|
||||||
information of the original request. This does NOT lock the threads!"""
|
information of the original request. This does NOT lock the threads!"""
|
||||||
request.msg_id = self.session.get_new_msg_id()
|
request.request_msg_id = self.session.get_new_msg_id()
|
||||||
|
|
||||||
# First calculate plain_text to encrypt it
|
# First calculate plain_text to encrypt it
|
||||||
with BinaryWriter() as plain_writer:
|
with BinaryWriter() as plain_writer:
|
||||||
plain_writer.write_long(self.session.salt, signed=False)
|
plain_writer.write_long(self.session.salt, signed=False)
|
||||||
plain_writer.write_long(self.session.id, signed=False)
|
plain_writer.write_long(self.session.id, signed=False)
|
||||||
plain_writer.write_long(request.msg_id)
|
plain_writer.write_long(request.request_msg_id)
|
||||||
plain_writer.write_int(
|
plain_writer.write_int(
|
||||||
self.session.generate_sequence(request.confirmed))
|
self.session.generate_sequence(request.confirmed))
|
||||||
|
|
||||||
|
@ -221,7 +223,7 @@ class MtProtoSender:
|
||||||
if code == 0x62d6b459:
|
if code == 0x62d6b459:
|
||||||
ack = reader.tgread_object()
|
ack = reader.tgread_object()
|
||||||
for r in self._pending_receive:
|
for r in self._pending_receive:
|
||||||
if r.msg_id in ack.msg_ids:
|
if r.request_msg_id in ack.msg_ids:
|
||||||
self._logger.warning('Ack found for the a request')
|
self._logger.warning('Ack found for the a request')
|
||||||
|
|
||||||
if self.logging_out:
|
if self.logging_out:
|
||||||
|
@ -257,7 +259,7 @@ class MtProtoSender:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
request = next(r for r in self._pending_receive
|
request = next(r for r in self._pending_receive
|
||||||
if r.msg_id == received_msg_id)
|
if r.request_msg_id == received_msg_id)
|
||||||
|
|
||||||
self._logger.warning('Pong confirmed a request')
|
self._logger.warning('Pong confirmed a request')
|
||||||
request.confirm_received = True
|
request.confirm_received = True
|
||||||
|
@ -294,7 +296,7 @@ class MtProtoSender:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
request = next(r for r in self._pending_receive
|
request = next(r for r in self._pending_receive
|
||||||
if r.msg_id == bad_msg_id)
|
if r.request_msg_id == bad_msg_id)
|
||||||
|
|
||||||
self.send(request)
|
self.send(request)
|
||||||
except StopIteration: pass
|
except StopIteration: pass
|
||||||
|
@ -328,7 +330,7 @@ class MtProtoSender:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
request = next(r for r in self._pending_receive
|
request = next(r for r in self._pending_receive
|
||||||
if r.msg_id == request_id)
|
if r.request_msg_id == request_id)
|
||||||
|
|
||||||
request.confirm_received = True
|
request.confirm_received = True
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
|
|
|
@ -47,7 +47,7 @@ class TelegramBareClient:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Current TelegramClient version
|
# Current TelegramClient version
|
||||||
__version__ = '0.10.1'
|
__version__ = '0.11'
|
||||||
|
|
||||||
# region Initialization
|
# region Initialization
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ from .errors import (RPCError, UnauthorizedError, InvalidParameterError,
|
||||||
ReadCancelledError, FileMigrateError, PhoneMigrateError,
|
ReadCancelledError, FileMigrateError, PhoneMigrateError,
|
||||||
NetworkMigrateError, UserMigrateError, PhoneCodeEmptyError,
|
NetworkMigrateError, UserMigrateError, PhoneCodeEmptyError,
|
||||||
PhoneCodeExpiredError, PhoneCodeHashEmptyError,
|
PhoneCodeExpiredError, PhoneCodeHashEmptyError,
|
||||||
PhoneCodeInvalidError)
|
PhoneCodeInvalidError, InvalidChecksumError)
|
||||||
|
|
||||||
# For sending and receiving requests
|
# For sending and receiving requests
|
||||||
from .tl import MTProtoRequest, Session, JsonSession
|
from .tl import MTProtoRequest, Session, JsonSession
|
||||||
|
@ -145,21 +145,13 @@ class TelegramClient(TelegramBareClient):
|
||||||
|
|
||||||
self._cached_clients.clear()
|
self._cached_clients.clear()
|
||||||
|
|
||||||
def reconnect(self, new_dc=None):
|
|
||||||
"""Disconnects and connects again (effectively reconnecting).
|
|
||||||
|
|
||||||
If 'new_dc' is not None, the current authorization key is
|
|
||||||
removed, the DC used is switched, and a new connection is made.
|
|
||||||
|
|
||||||
*args will be ignored.
|
|
||||||
"""
|
|
||||||
super(TelegramClient, self).reconnect(new_dc=new_dc)
|
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
|
||||||
# region Working with different Data Centers
|
# region Working with different connections
|
||||||
|
|
||||||
def _get_exported_client(self, dc_id, init_connection=False):
|
def _get_exported_client(self, dc_id,
|
||||||
|
init_connection=False,
|
||||||
|
bypass_cache=False):
|
||||||
"""Gets a cached exported TelegramBareClient for the desired DC.
|
"""Gets a cached exported TelegramBareClient for the desired DC.
|
||||||
|
|
||||||
If it's the first time retrieving the TelegramBareClient, the
|
If it's the first time retrieving the TelegramBareClient, the
|
||||||
|
@ -168,12 +160,16 @@ class TelegramClient(TelegramBareClient):
|
||||||
|
|
||||||
If after using the sender a ConnectionResetError is raised,
|
If after using the sender a ConnectionResetError is raised,
|
||||||
this method should be called again with init_connection=True
|
this method should be called again with init_connection=True
|
||||||
in order to perform the reconnection."""
|
in order to perform the reconnection.
|
||||||
|
|
||||||
|
If bypass_cache is True, a new client will be exported and
|
||||||
|
it will not be cached.
|
||||||
|
"""
|
||||||
# Thanks badoualy/kotlogram on /telegram/api/DefaultTelegramClient.kt
|
# Thanks badoualy/kotlogram on /telegram/api/DefaultTelegramClient.kt
|
||||||
# for clearly showing how to export the authorization! ^^
|
# for clearly showing how to export the authorization! ^^
|
||||||
|
|
||||||
client = self._cached_clients.get(dc_id)
|
client = self._cached_clients.get(dc_id)
|
||||||
if client:
|
if client and not bypass_cache:
|
||||||
if init_connection:
|
if init_connection:
|
||||||
client.reconnect()
|
client.reconnect()
|
||||||
return client
|
return client
|
||||||
|
@ -185,16 +181,46 @@ class TelegramClient(TelegramBareClient):
|
||||||
|
|
||||||
# Create a temporary session for this IP address, which needs
|
# Create a temporary session for this IP address, which needs
|
||||||
# to be different because each auth_key is unique per DC.
|
# to be different because each auth_key is unique per DC.
|
||||||
session = JsonSession(None)
|
#
|
||||||
|
# Construct this session with the connection parameters
|
||||||
|
# (system version, device model...) from the current one.
|
||||||
|
session = JsonSession(self.session)
|
||||||
session.server_address = dc.ip_address
|
session.server_address = dc.ip_address
|
||||||
session.port = dc.port
|
session.port = dc.port
|
||||||
client = TelegramBareClient(session, self.api_id, self.api_hash)
|
client = TelegramBareClient(session, self.api_id, self.api_hash)
|
||||||
client.connect(exported_auth=export_auth)
|
client.connect(exported_auth=export_auth)
|
||||||
|
|
||||||
# Don't go through this expensive process every time.
|
if not bypass_cache:
|
||||||
self._cached_clients[dc_id] = client
|
# Don't go through this expensive process every time.
|
||||||
|
self._cached_clients[dc_id] = client
|
||||||
return client
|
return client
|
||||||
|
|
||||||
|
def create_new_connection(self, on_dc=None):
|
||||||
|
"""Creates a new connection which can be used in parallel
|
||||||
|
with the original TelegramClient. A TelegramBareClient
|
||||||
|
will be returned already connected, and the caller is
|
||||||
|
responsible to disconnect it.
|
||||||
|
|
||||||
|
If 'on_dc' is None, the new client will run on the same
|
||||||
|
data center as the current client (most common case).
|
||||||
|
|
||||||
|
If the client is meant to be used on a different data
|
||||||
|
center, the data center ID should be specified instead.
|
||||||
|
|
||||||
|
Note that TelegramBareClients will not handle automatic
|
||||||
|
reconnection (i.e. switching to another data center to
|
||||||
|
download media), and InvalidDCError will be raised in
|
||||||
|
such case.
|
||||||
|
"""
|
||||||
|
if on_dc is None:
|
||||||
|
client = TelegramBareClient(self.session, self.api_id, self.api_hash,
|
||||||
|
proxy=self.proxy)
|
||||||
|
client.connect()
|
||||||
|
else:
|
||||||
|
client = self._get_exported_client(on_dc, bypass_cache=True)
|
||||||
|
|
||||||
|
return client
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
|
||||||
# region Telegram requests functions
|
# region Telegram requests functions
|
||||||
|
@ -447,8 +473,10 @@ class TelegramClient(TelegramBareClient):
|
||||||
total_messages = getattr(result, 'count', len(result.messages))
|
total_messages = getattr(result, 'count', len(result.messages))
|
||||||
|
|
||||||
# Iterate over all the messages and find the sender User
|
# Iterate over all the messages and find the sender User
|
||||||
entities = [find_user_or_chat(msg.from_id, result.users, result.chats)
|
entities = [find_user_or_chat(m.from_id, result.users, result.chats)
|
||||||
for msg in result.messages]
|
if m.from_id is not None else
|
||||||
|
find_user_or_chat(m.to_id, result.users, result.chats)
|
||||||
|
for m in result.messages]
|
||||||
|
|
||||||
return total_messages, result.messages, entities
|
return total_messages, result.messages, entities
|
||||||
|
|
||||||
|
@ -474,6 +502,8 @@ class TelegramClient(TelegramBareClient):
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
|
||||||
|
# region Uploading files
|
||||||
|
|
||||||
def send_photo_file(self, input_file, entity, caption=''):
|
def send_photo_file(self, input_file, entity, caption=''):
|
||||||
"""Sends a previously uploaded input_file
|
"""Sends a previously uploaded input_file
|
||||||
(which should be a photo) to the given entity (or input peer)"""
|
(which should be a photo) to the given entity (or input peer)"""
|
||||||
|
@ -771,14 +801,20 @@ class TelegramClient(TelegramBareClient):
|
||||||
self._logger.debug('Updates thread acquired the lock')
|
self._logger.debug('Updates thread acquired the lock')
|
||||||
try:
|
try:
|
||||||
self._updates_thread_receiving.set()
|
self._updates_thread_receiving.set()
|
||||||
self._logger.debug('Trying to receive updates from the updates thread')
|
self._logger.debug(
|
||||||
|
'Trying to receive updates from the updates thread'
|
||||||
|
)
|
||||||
|
|
||||||
result = self.sender.receive_update(timeout=timeout)
|
updates = self.sender.receive_updates(timeout=timeout)
|
||||||
|
|
||||||
self._updates_thread_receiving.clear()
|
self._updates_thread_receiving.clear()
|
||||||
self._logger.info('Received update from the updates thread')
|
self._logger.info(
|
||||||
for handler in self._update_handlers:
|
'Received {} update(s) from the updates thread'
|
||||||
handler(result)
|
.format(len(updates))
|
||||||
|
)
|
||||||
|
for update in updates:
|
||||||
|
for handler in self._update_handlers:
|
||||||
|
handler(update)
|
||||||
|
|
||||||
except ConnectionResetError:
|
except ConnectionResetError:
|
||||||
self._logger.info('Server disconnected us. Reconnecting...')
|
self._logger.info('Server disconnected us. Reconnecting...')
|
||||||
|
@ -790,6 +826,14 @@ class TelegramClient(TelegramBareClient):
|
||||||
except ReadCancelledError:
|
except ReadCancelledError:
|
||||||
self._logger.info('Receiving updates cancelled')
|
self._logger.info('Receiving updates cancelled')
|
||||||
|
|
||||||
|
except BrokenPipeError:
|
||||||
|
self._logger.info('Tcp session is broken. Reconnecting...')
|
||||||
|
self.reconnect()
|
||||||
|
|
||||||
|
except InvalidChecksumError:
|
||||||
|
self._logger.info('MTProto session is broken. Reconnecting...')
|
||||||
|
self.reconnect()
|
||||||
|
|
||||||
except OSError:
|
except OSError:
|
||||||
self._logger.warning('OSError on updates thread, %s logging out',
|
self._logger.warning('OSError on updates thread, %s logging out',
|
||||||
'was' if self.sender.logging_out else 'was not')
|
'was' if self.sender.logging_out else 'was not')
|
||||||
|
|
|
@ -5,7 +5,7 @@ class MTProtoRequest:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.sent = False
|
self.sent = False
|
||||||
|
|
||||||
self.msg_id = 0 # Long
|
self.request_msg_id = 0 # Long
|
||||||
self.sequence = 0
|
self.sequence = 0
|
||||||
|
|
||||||
self.dirty = False
|
self.dirty = False
|
||||||
|
|
|
@ -99,14 +99,28 @@ class JsonSession:
|
||||||
through an official Telegram client to revoke the authorization.
|
through an official Telegram client to revoke the authorization.
|
||||||
"""
|
"""
|
||||||
def __init__(self, session_user_id):
|
def __init__(self, session_user_id):
|
||||||
|
"""session_user_id should either be a string or another Session.
|
||||||
|
Note that if another session is given, only parameters like
|
||||||
|
those required to init a connection will be copied.
|
||||||
|
"""
|
||||||
# These values will NOT be saved
|
# These values will NOT be saved
|
||||||
self.session_user_id = session_user_id
|
if isinstance(session_user_id, JsonSession):
|
||||||
|
self.session_user_id = None
|
||||||
|
|
||||||
# For connection purposes
|
# For connection purposes
|
||||||
self.device_model = platform.node()
|
session = session_user_id
|
||||||
self.system_version = platform.system()
|
self.device_model = session.device_model
|
||||||
self.app_version = '0'
|
self.system_version = session.system_version
|
||||||
self.lang_code = 'en'
|
self.app_version = session.app_version
|
||||||
|
self.lang_code = session.lang_code
|
||||||
|
|
||||||
|
else: # str / None
|
||||||
|
self.session_user_id = session_user_id
|
||||||
|
|
||||||
|
self.device_model = platform.node()
|
||||||
|
self.system_version = platform.system()
|
||||||
|
self.app_version = '1.0' # note: '0' will provoke error
|
||||||
|
self.lang_code = 'en'
|
||||||
|
|
||||||
# Cross-thread safety
|
# Cross-thread safety
|
||||||
self._lock = Lock()
|
self._lock = Lock()
|
||||||
|
|
|
@ -5,9 +5,10 @@ to convert between an entity like an User, Chat, etc. into its Input version)
|
||||||
from mimetypes import add_type, guess_extension
|
from mimetypes import add_type, guess_extension
|
||||||
|
|
||||||
from .tl.types import (
|
from .tl.types import (
|
||||||
Channel, Chat, ChatPhoto, InputPeerChannel, InputPeerChat, InputPeerUser,
|
Channel, ChannelForbidden, Chat, ChatEmpty, ChatForbidden, ChatFull,
|
||||||
MessageMediaDocument, MessageMediaPhoto, PeerChannel, PeerChat, PeerUser,
|
ChatPhoto, InputPeerChannel, InputPeerChat, InputPeerUser, InputPeerEmpty,
|
||||||
User, UserProfilePhoto)
|
InputPeerSelf, MessageMediaDocument, MessageMediaPhoto, PeerChannel,
|
||||||
|
PeerChat, PeerUser, User, UserFull, UserProfilePhoto)
|
||||||
|
|
||||||
|
|
||||||
def get_display_name(entity):
|
def get_display_name(entity):
|
||||||
|
@ -51,18 +52,27 @@ def get_extension(media):
|
||||||
def get_input_peer(entity):
|
def get_input_peer(entity):
|
||||||
"""Gets the input peer for the given "entity" (user, chat or channel).
|
"""Gets the input peer for the given "entity" (user, chat or channel).
|
||||||
A ValueError is raised if the given entity isn't a supported type."""
|
A ValueError is raised if the given entity isn't a supported type."""
|
||||||
if (isinstance(entity, InputPeerUser) or
|
if type(entity).subclass_of_id == 0xc91c90b6: # crc32('InputUser')
|
||||||
isinstance(entity, InputPeerChat) or
|
|
||||||
isinstance(entity, InputPeerChannel)):
|
|
||||||
return entity
|
return entity
|
||||||
|
|
||||||
if isinstance(entity, User):
|
if isinstance(entity, User):
|
||||||
return InputPeerUser(entity.id, entity.access_hash)
|
return InputPeerUser(entity.id, entity.access_hash)
|
||||||
if isinstance(entity, Chat):
|
|
||||||
|
if any(isinstance(entity, c) for c in (
|
||||||
|
Chat, ChatEmpty, ChatForbidden)):
|
||||||
return InputPeerChat(entity.id)
|
return InputPeerChat(entity.id)
|
||||||
if isinstance(entity, Channel):
|
|
||||||
|
if any(isinstance(entity, c) for c in (
|
||||||
|
Channel, ChannelForbidden)):
|
||||||
return InputPeerChannel(entity.id, entity.access_hash)
|
return InputPeerChannel(entity.id, entity.access_hash)
|
||||||
|
|
||||||
|
# Less common cases
|
||||||
|
if isinstance(entity, UserFull):
|
||||||
|
return InputPeerUser(entity.user.id, entity.user.access_hash)
|
||||||
|
|
||||||
|
if isinstance(entity, ChatFull):
|
||||||
|
return InputPeerChat(entity.id)
|
||||||
|
|
||||||
raise ValueError('Cannot cast {} to any kind of InputPeer.'
|
raise ValueError('Cannot cast {} to any kind of InputPeer.'
|
||||||
.format(type(entity).__name__))
|
.format(type(entity).__name__))
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import shutil
|
|
||||||
from getpass import getpass
|
from getpass import getpass
|
||||||
|
|
||||||
from telethon import TelegramClient
|
from telethon import TelegramClient
|
||||||
|
@ -6,9 +5,6 @@ from telethon.errors import SessionPasswordNeededError
|
||||||
from telethon.tl.types import UpdateShortChatMessage, UpdateShortMessage
|
from telethon.tl.types import UpdateShortChatMessage, UpdateShortMessage
|
||||||
from telethon.utils import get_display_name
|
from telethon.utils import get_display_name
|
||||||
|
|
||||||
# Get the (current) number of lines in the terminal
|
|
||||||
cols, rows = shutil.get_terminal_size()
|
|
||||||
|
|
||||||
|
|
||||||
def sprint(string, *args, **kwargs):
|
def sprint(string, *args, **kwargs):
|
||||||
"""Safe Print (handle UnicodeEncodeErrors on some terminals)"""
|
"""Safe Print (handle UnicodeEncodeErrors on some terminals)"""
|
||||||
|
@ -47,7 +43,8 @@ class InteractiveTelegramClient(TelegramClient):
|
||||||
Telegram through Telethon, such as listing dialogs (open chats),
|
Telegram through Telethon, such as listing dialogs (open chats),
|
||||||
talking to people, downloading media, and receiving updates.
|
talking to people, downloading media, and receiving updates.
|
||||||
"""
|
"""
|
||||||
def __init__(self, session_user_id, user_phone, api_id, api_hash, proxy=None):
|
def __init__(self, session_user_id, user_phone, api_id, api_hash,
|
||||||
|
proxy=None):
|
||||||
print_title('Initialization')
|
print_title('Initialization')
|
||||||
|
|
||||||
print('Initializing interactive example...')
|
print('Initializing interactive example...')
|
||||||
|
@ -76,7 +73,7 @@ class InteractiveTelegramClient(TelegramClient):
|
||||||
self_user = self.sign_in(user_phone, code)
|
self_user = self.sign_in(user_phone, code)
|
||||||
|
|
||||||
# Two-step verification may be enabled
|
# Two-step verification may be enabled
|
||||||
except SessionPasswordNeededError as e:
|
except SessionPasswordNeededError:
|
||||||
pw = getpass('Two step verification is enabled. '
|
pw = getpass('Two step verification is enabled. '
|
||||||
'Please enter your password: ')
|
'Please enter your password: ')
|
||||||
|
|
||||||
|
@ -100,8 +97,7 @@ class InteractiveTelegramClient(TelegramClient):
|
||||||
print_title('Dialogs window')
|
print_title('Dialogs window')
|
||||||
|
|
||||||
# Display them so the user can choose
|
# Display them so the user can choose
|
||||||
for i, entity in enumerate(entities):
|
for i, entity in enumerate(entities, start=1):
|
||||||
i += 1 # 1-based index
|
|
||||||
sprint('{}. {}'.format(i, get_display_name(entity)))
|
sprint('{}. {}'.format(i, get_display_name(entity)))
|
||||||
|
|
||||||
# Let the user decide who they want to talk to
|
# Let the user decide who they want to talk to
|
||||||
|
@ -120,7 +116,7 @@ class InteractiveTelegramClient(TelegramClient):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
i = int(i if i else 0) - 1
|
i = int(i if i else 0) - 1
|
||||||
# Ensure it is inside the bounds, otherwise set to None and retry
|
# Ensure it is inside the bounds, otherwise retry
|
||||||
if not 0 <= i < dialog_count:
|
if not 0 <= i < dialog_count:
|
||||||
i = None
|
i = None
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
@ -162,7 +158,14 @@ class InteractiveTelegramClient(TelegramClient):
|
||||||
for msg, sender in zip(
|
for msg, sender in zip(
|
||||||
reversed(messages), reversed(senders)):
|
reversed(messages), reversed(senders)):
|
||||||
# Get the name of the sender if any
|
# Get the name of the sender if any
|
||||||
name = sender.first_name if sender else '???'
|
if sender:
|
||||||
|
name = getattr(sender, 'first_name', None)
|
||||||
|
if not name:
|
||||||
|
name = getattr(sender, 'title')
|
||||||
|
if not name:
|
||||||
|
name = '???'
|
||||||
|
else:
|
||||||
|
name = '???'
|
||||||
|
|
||||||
# Format the message content
|
# Format the message content
|
||||||
if getattr(msg, 'media', None):
|
if getattr(msg, 'media', None):
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
|
from zlib import crc32
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -186,6 +187,13 @@ class TLGenerator:
|
||||||
builder.writeln('from {}.tl.mtproto_request import MTProtoRequest'
|
builder.writeln('from {}.tl.mtproto_request import MTProtoRequest'
|
||||||
.format('.' * depth))
|
.format('.' * depth))
|
||||||
|
|
||||||
|
if tlobject.is_function and \
|
||||||
|
any(a for a in tlobject.args if a.type == 'InputPeer'):
|
||||||
|
# We can automatically convert a normal peer to an InputPeer,
|
||||||
|
# it will make invoking a lot of requests a lot simpler.
|
||||||
|
builder.writeln('from {}.utils import get_input_peer'
|
||||||
|
.format('.' * depth))
|
||||||
|
|
||||||
if any(a for a in tlobject.args if a.can_be_inferred):
|
if any(a for a in tlobject.args if a.can_be_inferred):
|
||||||
# Currently only 'random_id' needs 'os' to be imported
|
# Currently only 'random_id' needs 'os' to be imported
|
||||||
builder.writeln('import os')
|
builder.writeln('import os')
|
||||||
|
@ -207,6 +215,9 @@ class TLGenerator:
|
||||||
# Class-level variable to store its constructor ID
|
# Class-level variable to store its constructor ID
|
||||||
builder.writeln("# Telegram's constructor (U)ID for this class")
|
builder.writeln("# Telegram's constructor (U)ID for this class")
|
||||||
builder.writeln('constructor_id = {}'.format(hex(tlobject.id)))
|
builder.writeln('constructor_id = {}'.format(hex(tlobject.id)))
|
||||||
|
builder.writeln("# Also the ID of its resulting type for fast checks")
|
||||||
|
builder.writeln('subclass_of_id = {}'.format(
|
||||||
|
hex(crc32(tlobject.result.encode('ascii')))))
|
||||||
builder.writeln()
|
builder.writeln()
|
||||||
|
|
||||||
# Flag arguments must go last
|
# Flag arguments must go last
|
||||||
|
@ -306,6 +317,10 @@ class TLGenerator:
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise ValueError('Cannot infer a value for ', arg)
|
raise ValueError('Cannot infer a value for ', arg)
|
||||||
|
elif arg.type == 'InputPeer' and tlobject.is_function:
|
||||||
|
# Well-known case, auto-cast it to the right type
|
||||||
|
builder.writeln(
|
||||||
|
'self.{0} = get_input_peer({0})'.format(arg.name))
|
||||||
else:
|
else:
|
||||||
builder.writeln('self.{0} = {0}'.format(arg.name))
|
builder.writeln('self.{0} = {0}'.format(arg.name))
|
||||||
|
|
||||||
|
|
|
@ -24,11 +24,18 @@ def load_settings(path='api/settings'):
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# Load the settings and initialize the client
|
# Load the settings and initialize the client
|
||||||
settings = load_settings()
|
settings = load_settings()
|
||||||
|
kwargs = {}
|
||||||
|
if settings.get('socks_proxy'):
|
||||||
|
import socks # $ pip install pysocks
|
||||||
|
host, port = settings['socks_proxy'].split(':')
|
||||||
|
kwargs = dict(proxy=(socks.SOCKS5, host, int(port)))
|
||||||
|
|
||||||
client = InteractiveTelegramClient(
|
client = InteractiveTelegramClient(
|
||||||
session_user_id=str(settings.get('session_name', 'anonymous')),
|
session_user_id=str(settings.get('session_name', 'anonymous')),
|
||||||
user_phone=str(settings['user_phone']),
|
user_phone=str(settings['user_phone']),
|
||||||
api_id=settings['api_id'],
|
api_id=settings['api_id'],
|
||||||
api_hash=str(settings['api_hash']))
|
api_hash=str(settings['api_hash']),
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
print('Initialization done!')
|
print('Initialization done!')
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user