mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-08-05 20:50:22 +03:00
Merge branch 'master' of https://github.com/LonamiWebs/Telethon into stop-propagation-of-updates
This commit is contained in:
commit
ebcfd1ed99
|
@ -1,2 +1,3 @@
|
||||||
cryptg
|
cryptg
|
||||||
|
pysocks
|
||||||
hachoir3
|
hachoir3
|
||||||
|
|
|
@ -14,6 +14,45 @@ it can take advantage of new goodies!
|
||||||
.. contents:: List of All Versions
|
.. contents:: List of All Versions
|
||||||
|
|
||||||
|
|
||||||
|
Further easing library usage (v0.17.4)
|
||||||
|
======================================
|
||||||
|
|
||||||
|
*Published at 2018/02/24*
|
||||||
|
|
||||||
|
Some new things and patches that already deserved their own release.
|
||||||
|
|
||||||
|
|
||||||
|
Additions
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
- New ``pattern`` argument to ``NewMessage`` to easily filter messages.
|
||||||
|
- New ``.get_participants()`` convenience method to get members from chats.
|
||||||
|
- ``.send_message()`` now accepts a ``Message`` as the ``message`` parameter.
|
||||||
|
- You can now ``.get_entity()`` through exact name match instead username.
|
||||||
|
- Raise ``ProxyConnectionError`` instead looping forever so you can
|
||||||
|
``except`` it on your own code and behave accordingly.
|
||||||
|
|
||||||
|
Bug fixes
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
- ``.parse_username`` would fail with ``www.`` or a trailing slash.
|
||||||
|
- ``events.MessageChanged`` would fail with ``UpdateDeleteMessages``.
|
||||||
|
- You can now send ``b'byte strings'`` directly as files again.
|
||||||
|
- ``.send_file()`` was not respecting the original captions when passing
|
||||||
|
another message (or media) as the file.
|
||||||
|
- Downloading media from a different data center would always log a warning
|
||||||
|
for the first time.
|
||||||
|
|
||||||
|
Internal changes
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- Use ``req_pq_multi`` instead ``req_pq`` when generating ``auth_key``.
|
||||||
|
- You can use ``.get_me(input_peer=True)`` if all you need is your self ID.
|
||||||
|
- New addition to the interactive client example to show peer information.
|
||||||
|
- Avoid special casing ``InputPeerSelf`` on some ``NewMessage`` events, so
|
||||||
|
you can always safely rely on ``.sender`` to get the right ID.
|
||||||
|
|
||||||
|
|
||||||
New small convenience functions (v0.17.3)
|
New small convenience functions (v0.17.3)
|
||||||
=========================================
|
=========================================
|
||||||
|
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -110,7 +110,7 @@ def main():
|
||||||
long_description = f.read()
|
long_description = f.read()
|
||||||
|
|
||||||
with open('telethon/version.py', encoding='utf-8') as f:
|
with open('telethon/version.py', encoding='utf-8') as f:
|
||||||
version = re.search(r"^__version__\s+=\s+'(.*)'$",
|
version = re.search(r"^__version__\s*=\s*'(.*)'.*$",
|
||||||
f.read(), flags=re.MULTILINE).group(1)
|
f.read(), flags=re.MULTILINE).group(1)
|
||||||
setup(
|
setup(
|
||||||
name='Telethon',
|
name='Telethon',
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import abc
|
import abc
|
||||||
import datetime
|
import datetime
|
||||||
import itertools
|
import itertools
|
||||||
|
import re
|
||||||
|
|
||||||
from .. import utils
|
from .. import utils
|
||||||
from ..errors import RPCError
|
from ..errors import RPCError
|
||||||
|
@ -13,15 +14,14 @@ def _into_id_set(client, chats):
|
||||||
if chats is None:
|
if chats is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if not hasattr(chats, '__iter__') or isinstance(chats, str):
|
if not utils.is_list_like(chats):
|
||||||
chats = (chats,)
|
chats = (chats,)
|
||||||
|
|
||||||
result = set()
|
result = set()
|
||||||
for chat in chats:
|
for chat in chats:
|
||||||
chat = client.get_input_entity(chat)
|
chat = client.get_input_entity(chat)
|
||||||
if isinstance(chat, types.InputPeerSelf):
|
if isinstance(chat, types.InputPeerSelf):
|
||||||
chat = getattr(_into_id_set, 'me', None) or client.get_me()
|
chat = client.get_me(input_peer=True)
|
||||||
_into_id_set.me = chat
|
|
||||||
result.add(utils.get_peer_id(chat))
|
result.add(utils.get_peer_id(chat))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@ class _EventBuilder(abc.ABC):
|
||||||
def __init__(self, chats=None, blacklist_chats=False):
|
def __init__(self, chats=None, blacklist_chats=False):
|
||||||
self.chats = chats
|
self.chats = chats
|
||||||
self.blacklist_chats = blacklist_chats
|
self.blacklist_chats = blacklist_chats
|
||||||
|
self._self_id = None
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def build(self, update):
|
def build(self, update):
|
||||||
|
@ -50,6 +51,7 @@ class _EventBuilder(abc.ABC):
|
||||||
def resolve(self, client):
|
def resolve(self, client):
|
||||||
"""Helper method to allow event builders to be resolved before usage"""
|
"""Helper method to allow event builders to be resolved before usage"""
|
||||||
self.chats = _into_id_set(client, self.chats)
|
self.chats = _into_id_set(client, self.chats)
|
||||||
|
self._self_id = client.get_me(input_peer=True).user_id
|
||||||
|
|
||||||
def _filter_event(self, event):
|
def _filter_event(self, event):
|
||||||
"""
|
"""
|
||||||
|
@ -153,6 +155,9 @@ class Raw(_EventBuilder):
|
||||||
"""
|
"""
|
||||||
Represents a raw event. The event is the update itself.
|
Represents a raw event. The event is the update itself.
|
||||||
"""
|
"""
|
||||||
|
def resolve(self, client):
|
||||||
|
pass
|
||||||
|
|
||||||
def build(self, update):
|
def build(self, update):
|
||||||
return update
|
return update
|
||||||
|
|
||||||
|
@ -173,19 +178,28 @@ class NewMessage(_EventBuilder):
|
||||||
If set to ``True``, only **outgoing** messages will be handled.
|
If set to ``True``, only **outgoing** messages will be handled.
|
||||||
Mutually exclusive with ``incoming`` (can only set one of either).
|
Mutually exclusive with ``incoming`` (can only set one of either).
|
||||||
|
|
||||||
Notes:
|
pattern (:obj:`str`, :obj:`callable`, :obj:`Pattern`, optional):
|
||||||
The ``message.from_id`` might not only be an integer or ``None``,
|
If set, only messages matching this pattern will be handled.
|
||||||
but also ``InputPeerSelf()`` for short private messages (the API
|
You can specify a regex-like string which will be matched
|
||||||
would not return such thing, this is a custom modification).
|
against the message, a callable function that returns ``True``
|
||||||
|
if a message is acceptable, or a compiled regex pattern.
|
||||||
"""
|
"""
|
||||||
def __init__(self, incoming=None, outgoing=None,
|
def __init__(self, incoming=None, outgoing=None,
|
||||||
chats=None, blacklist_chats=False):
|
chats=None, blacklist_chats=False, pattern=None):
|
||||||
if incoming and outgoing:
|
if incoming and outgoing:
|
||||||
raise ValueError('Can only set either incoming or outgoing')
|
raise ValueError('Can only set either incoming or outgoing')
|
||||||
|
|
||||||
super().__init__(chats=chats, blacklist_chats=blacklist_chats)
|
super().__init__(chats=chats, blacklist_chats=blacklist_chats)
|
||||||
self.incoming = incoming
|
self.incoming = incoming
|
||||||
self.outgoing = outgoing
|
self.outgoing = outgoing
|
||||||
|
if isinstance(pattern, str):
|
||||||
|
self.pattern = re.compile(pattern).match
|
||||||
|
elif not pattern or callable(pattern):
|
||||||
|
self.pattern = pattern
|
||||||
|
elif hasattr(pattern, 'match') and callable(pattern.match):
|
||||||
|
self.pattern = pattern.match
|
||||||
|
else:
|
||||||
|
raise TypeError('Invalid pattern type given')
|
||||||
|
|
||||||
def build(self, update):
|
def build(self, update):
|
||||||
if isinstance(update,
|
if isinstance(update,
|
||||||
|
@ -201,7 +215,7 @@ class NewMessage(_EventBuilder):
|
||||||
silent=update.silent,
|
silent=update.silent,
|
||||||
id=update.id,
|
id=update.id,
|
||||||
to_id=types.PeerUser(update.user_id),
|
to_id=types.PeerUser(update.user_id),
|
||||||
from_id=types.InputPeerSelf() if update.out else update.user_id,
|
from_id=self._self_id if update.out else update.user_id,
|
||||||
message=update.message,
|
message=update.message,
|
||||||
date=update.date,
|
date=update.date,
|
||||||
fwd_from=update.fwd_from,
|
fwd_from=update.fwd_from,
|
||||||
|
@ -229,13 +243,16 @@ class NewMessage(_EventBuilder):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Short-circuit if we let pass all events
|
# Short-circuit if we let pass all events
|
||||||
if all(x is None for x in (self.incoming, self.outgoing, self.chats)):
|
if all(x is None for x in (self.incoming, self.outgoing, self.chats,
|
||||||
|
self.pattern)):
|
||||||
return event
|
return event
|
||||||
|
|
||||||
if self.incoming and event.message.out:
|
if self.incoming and event.message.out:
|
||||||
return
|
return
|
||||||
if self.outgoing and not event.message.out:
|
if self.outgoing and not event.message.out:
|
||||||
return
|
return
|
||||||
|
if self.pattern and not self.pattern(event.message.message or ''):
|
||||||
|
return
|
||||||
|
|
||||||
return self._filter_event(event)
|
return self._filter_event(event)
|
||||||
|
|
||||||
|
@ -260,7 +277,14 @@ class NewMessage(_EventBuilder):
|
||||||
Whether the message is a reply to some other or not.
|
Whether the message is a reply to some other or not.
|
||||||
"""
|
"""
|
||||||
def __init__(self, message):
|
def __init__(self, message):
|
||||||
super().__init__(chat_peer=message.to_id,
|
if not message.out and isinstance(message.to_id, types.PeerUser):
|
||||||
|
# Incoming message (e.g. from a bot) has to_id=us, and
|
||||||
|
# from_id=bot (the actual "chat" from an user's perspective).
|
||||||
|
chat_peer = types.PeerUser(message.from_id)
|
||||||
|
else:
|
||||||
|
chat_peer = message.to_id
|
||||||
|
|
||||||
|
super().__init__(chat_peer=chat_peer,
|
||||||
msg_id=message.id, broadcast=bool(message.post))
|
msg_id=message.id, broadcast=bool(message.post))
|
||||||
|
|
||||||
self.message = message
|
self.message = message
|
||||||
|
@ -299,7 +323,11 @@ class NewMessage(_EventBuilder):
|
||||||
or the edited message otherwise.
|
or the edited message otherwise.
|
||||||
"""
|
"""
|
||||||
if not self.message.out:
|
if not self.message.out:
|
||||||
return None
|
if not isinstance(self.message.to_id, types.PeerUser):
|
||||||
|
return None
|
||||||
|
me = self._client.get_me(input_peer=True)
|
||||||
|
if self.message.to_id.user_id != me.user_id:
|
||||||
|
return None
|
||||||
|
|
||||||
return self._client.edit_message(self.input_chat,
|
return self._client.edit_message(self.input_chat,
|
||||||
self.message,
|
self.message,
|
||||||
|
|
|
@ -2,18 +2,26 @@
|
||||||
This module holds a rough implementation of the C# TCP client.
|
This module holds a rough implementation of the C# TCP client.
|
||||||
"""
|
"""
|
||||||
import errno
|
import errno
|
||||||
|
import logging
|
||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from io import BytesIO, BufferedWriter
|
from io import BytesIO, BufferedWriter
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
|
|
||||||
|
try:
|
||||||
|
import socks
|
||||||
|
except ImportError:
|
||||||
|
socks = None
|
||||||
|
|
||||||
MAX_TIMEOUT = 15 # in seconds
|
MAX_TIMEOUT = 15 # in seconds
|
||||||
CONN_RESET_ERRNOS = {
|
CONN_RESET_ERRNOS = {
|
||||||
errno.EBADF, errno.ENOTSOCK, errno.ENETUNREACH,
|
errno.EBADF, errno.ENOTSOCK, errno.ENETUNREACH,
|
||||||
errno.EINVAL, errno.ENOTCONN
|
errno.EINVAL, errno.ENOTCONN
|
||||||
}
|
}
|
||||||
|
|
||||||
|
__log__ = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TcpClient:
|
class TcpClient:
|
||||||
"""A simple TCP client to ease the work with sockets and proxies."""
|
"""A simple TCP client to ease the work with sockets and proxies."""
|
||||||
|
@ -70,6 +78,10 @@ class TcpClient:
|
||||||
self._socket.connect(address)
|
self._socket.connect(address)
|
||||||
break # Successful connection, stop retrying to connect
|
break # Successful connection, stop retrying to connect
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
|
__log__.info('OSError "%s" raised while connecting', e)
|
||||||
|
# Stop retrying to connect if proxy connection error occurred
|
||||||
|
if socks and isinstance(e, socks.ProxyConnectionError):
|
||||||
|
raise
|
||||||
# There are some errors that we know how to handle, and
|
# There are some errors that we know how to handle, and
|
||||||
# the loop will allow us to retry
|
# the loop will allow us to retry
|
||||||
if e.errno in (errno.EBADF, errno.ENOTSOCK, errno.EINVAL,
|
if e.errno in (errno.EBADF, errno.ENOTSOCK, errno.EINVAL,
|
||||||
|
@ -112,19 +124,22 @@ class TcpClient:
|
||||||
:param data: the data to send.
|
:param data: the data to send.
|
||||||
"""
|
"""
|
||||||
if self._socket is None:
|
if self._socket is None:
|
||||||
self._raise_connection_reset()
|
self._raise_connection_reset(None)
|
||||||
|
|
||||||
# TODO Timeout may be an issue when sending the data, Changed in v3.5:
|
# TODO Timeout may be an issue when sending the data, Changed in v3.5:
|
||||||
# The socket timeout is now the maximum total duration to send all data.
|
# The socket timeout is now the maximum total duration to send all data.
|
||||||
try:
|
try:
|
||||||
self._socket.sendall(data)
|
self._socket.sendall(data)
|
||||||
except socket.timeout as e:
|
except socket.timeout as e:
|
||||||
|
__log__.debug('socket.timeout "%s" while writing data', e)
|
||||||
raise TimeoutError() from e
|
raise TimeoutError() from e
|
||||||
except ConnectionError:
|
except ConnectionError as e:
|
||||||
self._raise_connection_reset()
|
__log__.info('ConnectionError "%s" while writing data', e)
|
||||||
|
self._raise_connection_reset(e)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
|
__log__.info('OSError "%s" while writing data', e)
|
||||||
if e.errno in CONN_RESET_ERRNOS:
|
if e.errno in CONN_RESET_ERRNOS:
|
||||||
self._raise_connection_reset()
|
self._raise_connection_reset(e)
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
@ -136,7 +151,7 @@ class TcpClient:
|
||||||
:return: the read data with len(data) == size.
|
:return: the read data with len(data) == size.
|
||||||
"""
|
"""
|
||||||
if self._socket is None:
|
if self._socket is None:
|
||||||
self._raise_connection_reset()
|
self._raise_connection_reset(None)
|
||||||
|
|
||||||
# TODO Remove the timeout from this method, always use previous one
|
# TODO Remove the timeout from this method, always use previous one
|
||||||
with BufferedWriter(BytesIO(), buffer_size=size) as buffer:
|
with BufferedWriter(BytesIO(), buffer_size=size) as buffer:
|
||||||
|
@ -145,17 +160,22 @@ class TcpClient:
|
||||||
try:
|
try:
|
||||||
partial = self._socket.recv(bytes_left)
|
partial = self._socket.recv(bytes_left)
|
||||||
except socket.timeout as e:
|
except socket.timeout as e:
|
||||||
|
# These are somewhat common if the server has nothing
|
||||||
|
# to send to us, so use a lower logging priority.
|
||||||
|
__log__.debug('socket.timeout "%s" while reading data', e)
|
||||||
raise TimeoutError() from e
|
raise TimeoutError() from e
|
||||||
except ConnectionError:
|
except ConnectionError as e:
|
||||||
self._raise_connection_reset()
|
__log__.info('ConnectionError "%s" while reading data', e)
|
||||||
|
self._raise_connection_reset(e)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
|
__log__.info('OSError "%s" while reading data', e)
|
||||||
if e.errno in CONN_RESET_ERRNOS:
|
if e.errno in CONN_RESET_ERRNOS:
|
||||||
self._raise_connection_reset()
|
self._raise_connection_reset(e)
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
if len(partial) == 0:
|
if len(partial) == 0:
|
||||||
self._raise_connection_reset()
|
self._raise_connection_reset(None)
|
||||||
|
|
||||||
buffer.write(partial)
|
buffer.write(partial)
|
||||||
bytes_left -= len(partial)
|
bytes_left -= len(partial)
|
||||||
|
@ -164,7 +184,8 @@ class TcpClient:
|
||||||
buffer.flush()
|
buffer.flush()
|
||||||
return buffer.raw.getvalue()
|
return buffer.raw.getvalue()
|
||||||
|
|
||||||
def _raise_connection_reset(self):
|
def _raise_connection_reset(self, original):
|
||||||
"""Disconnects the client and raises ConnectionResetError."""
|
"""Disconnects the client and raises ConnectionResetError."""
|
||||||
self.close() # Connection reset -> flag as socket closed
|
self.close() # Connection reset -> flag as socket closed
|
||||||
raise ConnectionResetError('The server has closed the connection.')
|
raise ConnectionResetError('The server has closed the connection.')\
|
||||||
|
from original
|
||||||
|
|
|
@ -227,7 +227,7 @@ class Session:
|
||||||
c = self._cursor()
|
c = self._cursor()
|
||||||
c.execute('select auth_key from sessions')
|
c.execute('select auth_key from sessions')
|
||||||
tuple_ = c.fetchone()
|
tuple_ = c.fetchone()
|
||||||
if tuple_:
|
if tuple_ and tuple_[0]:
|
||||||
self._auth_key = AuthKey(data=tuple_[0])
|
self._auth_key = AuthKey(data=tuple_[0])
|
||||||
else:
|
else:
|
||||||
self._auth_key = None
|
self._auth_key = None
|
||||||
|
@ -355,14 +355,14 @@ class Session:
|
||||||
if not self.save_entities:
|
if not self.save_entities:
|
||||||
return
|
return
|
||||||
|
|
||||||
if not isinstance(tlo, TLObject) and hasattr(tlo, '__iter__'):
|
if not isinstance(tlo, TLObject) and utils.is_list_like(tlo):
|
||||||
# This may be a list of users already for instance
|
# This may be a list of users already for instance
|
||||||
entities = tlo
|
entities = tlo
|
||||||
else:
|
else:
|
||||||
entities = []
|
entities = []
|
||||||
if hasattr(tlo, 'chats') and hasattr(tlo.chats, '__iter__'):
|
if hasattr(tlo, 'chats') and utils.is_list_like(tlo.chats):
|
||||||
entities.extend(tlo.chats)
|
entities.extend(tlo.chats)
|
||||||
if hasattr(tlo, 'users') and hasattr(tlo.users, '__iter__'):
|
if hasattr(tlo, 'users') and utils.is_list_like(tlo.users):
|
||||||
entities.extend(tlo.users)
|
entities.extend(tlo.users)
|
||||||
if not entities:
|
if not entities:
|
||||||
return
|
return
|
||||||
|
|
|
@ -56,7 +56,7 @@ from .tl.functions.messages import (
|
||||||
GetDialogsRequest, GetHistoryRequest, SendMediaRequest,
|
GetDialogsRequest, GetHistoryRequest, SendMediaRequest,
|
||||||
SendMessageRequest, GetChatsRequest, GetAllDraftsRequest,
|
SendMessageRequest, GetChatsRequest, GetAllDraftsRequest,
|
||||||
CheckChatInviteRequest, ReadMentionsRequest, SendMultiMediaRequest,
|
CheckChatInviteRequest, ReadMentionsRequest, SendMultiMediaRequest,
|
||||||
UploadMediaRequest, EditMessageRequest
|
UploadMediaRequest, EditMessageRequest, GetFullChatRequest
|
||||||
)
|
)
|
||||||
|
|
||||||
from .tl.functions import channels
|
from .tl.functions import channels
|
||||||
|
@ -66,7 +66,7 @@ from .tl.functions.users import (
|
||||||
GetUsersRequest
|
GetUsersRequest
|
||||||
)
|
)
|
||||||
from .tl.functions.channels import (
|
from .tl.functions.channels import (
|
||||||
GetChannelsRequest, GetFullChannelRequest
|
GetChannelsRequest, GetFullChannelRequest, GetParticipantsRequest
|
||||||
)
|
)
|
||||||
from .tl.types import (
|
from .tl.types import (
|
||||||
DocumentAttributeAudio, DocumentAttributeFilename,
|
DocumentAttributeAudio, DocumentAttributeFilename,
|
||||||
|
@ -80,7 +80,8 @@ from .tl.types import (
|
||||||
InputSingleMedia, InputMediaPhoto, InputPhoto, InputFile, InputFileBig,
|
InputSingleMedia, InputMediaPhoto, InputPhoto, InputFile, InputFileBig,
|
||||||
InputDocument, InputMediaDocument, Document, MessageEntityTextUrl,
|
InputDocument, InputMediaDocument, Document, MessageEntityTextUrl,
|
||||||
InputMessageEntityMentionName, DocumentAttributeVideo,
|
InputMessageEntityMentionName, DocumentAttributeVideo,
|
||||||
UpdateEditMessage, UpdateEditChannelMessage, UpdateShort, Updates
|
UpdateEditMessage, UpdateEditChannelMessage, UpdateShort, Updates,
|
||||||
|
MessageMediaWebPage, ChannelParticipantsSearch
|
||||||
)
|
)
|
||||||
from .tl.types.messages import DialogsSlice
|
from .tl.types.messages import DialogsSlice
|
||||||
from .extensions import markdown, html
|
from .extensions import markdown, html
|
||||||
|
@ -179,6 +180,9 @@ class TelegramClient(TelegramBareClient):
|
||||||
self._phone_code_hash = {}
|
self._phone_code_hash = {}
|
||||||
self._phone = None
|
self._phone = None
|
||||||
|
|
||||||
|
# Sometimes we need to know who we are, cache the self peer
|
||||||
|
self._self_input_peer = None
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
|
||||||
# region Telegram requests functions
|
# region Telegram requests functions
|
||||||
|
@ -407,6 +411,9 @@ class TelegramClient(TelegramBareClient):
|
||||||
'and a password only if an RPCError was raised before.'
|
'and a password only if an RPCError was raised before.'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self._self_input_peer = utils.get_input_peer(
|
||||||
|
result.user, allow_self=False
|
||||||
|
)
|
||||||
self._set_connected_and_authorized()
|
self._set_connected_and_authorized()
|
||||||
return result.user
|
return result.user
|
||||||
|
|
||||||
|
@ -436,6 +443,9 @@ class TelegramClient(TelegramBareClient):
|
||||||
last_name=last_name
|
last_name=last_name
|
||||||
))
|
))
|
||||||
|
|
||||||
|
self._self_input_peer = utils.get_input_peer(
|
||||||
|
result.user, allow_self=False
|
||||||
|
)
|
||||||
self._set_connected_and_authorized()
|
self._set_connected_and_authorized()
|
||||||
return result.user
|
return result.user
|
||||||
|
|
||||||
|
@ -455,16 +465,31 @@ class TelegramClient(TelegramBareClient):
|
||||||
self.session.delete()
|
self.session.delete()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get_me(self):
|
def get_me(self, input_peer=False):
|
||||||
"""
|
"""
|
||||||
Gets "me" (the self user) which is currently authenticated,
|
Gets "me" (the self user) which is currently authenticated,
|
||||||
or None if the request fails (hence, not authenticated).
|
or None if the request fails (hence, not authenticated).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
input_peer (:obj:`bool`, optional):
|
||||||
|
Whether to return the ``InputPeerUser`` version or the normal
|
||||||
|
``User``. This can be useful if you just need to know the ID
|
||||||
|
of yourself.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
:obj:`User`: Your own user.
|
:obj:`User`: Your own user.
|
||||||
"""
|
"""
|
||||||
|
if input_peer and self._self_input_peer:
|
||||||
|
return self._self_input_peer
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return self(GetUsersRequest([InputUserSelf()]))[0]
|
me = self(GetUsersRequest([InputUserSelf()]))[0]
|
||||||
|
if not self._self_input_peer:
|
||||||
|
self._self_input_peer = utils.get_input_peer(
|
||||||
|
me, allow_self=False
|
||||||
|
)
|
||||||
|
|
||||||
|
return self._self_input_peer if input_peer else me
|
||||||
except UnauthorizedError:
|
except UnauthorizedError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -641,8 +666,8 @@ class TelegramClient(TelegramBareClient):
|
||||||
entity (:obj:`entity`):
|
entity (:obj:`entity`):
|
||||||
To who will it be sent.
|
To who will it be sent.
|
||||||
|
|
||||||
message (:obj:`str`):
|
message (:obj:`str` | :obj:`Message`):
|
||||||
The message to be sent.
|
The message to be sent, or another message object to resend.
|
||||||
|
|
||||||
reply_to (:obj:`int` | :obj:`Message`, optional):
|
reply_to (:obj:`int` | :obj:`Message`, optional):
|
||||||
Whether to reply to a message or not. If an integer is provided,
|
Whether to reply to a message or not. If an integer is provided,
|
||||||
|
@ -661,15 +686,35 @@ class TelegramClient(TelegramBareClient):
|
||||||
the sent message
|
the sent message
|
||||||
"""
|
"""
|
||||||
entity = self.get_input_entity(entity)
|
entity = self.get_input_entity(entity)
|
||||||
message, msg_entities = self._parse_message_text(message, parse_mode)
|
if isinstance(message, Message):
|
||||||
|
if (message.media
|
||||||
|
and not isinstance(message.media, MessageMediaWebPage)):
|
||||||
|
return self.send_file(entity, message.media)
|
||||||
|
|
||||||
|
if utils.get_peer_id(entity) == utils.get_peer_id(message.to_id):
|
||||||
|
reply_id = message.reply_to_msg_id
|
||||||
|
else:
|
||||||
|
reply_id = None
|
||||||
|
request = SendMessageRequest(
|
||||||
|
peer=entity,
|
||||||
|
message=message.message or '',
|
||||||
|
silent=message.silent,
|
||||||
|
reply_to_msg_id=reply_id,
|
||||||
|
reply_markup=message.reply_markup,
|
||||||
|
entities=message.entities,
|
||||||
|
no_webpage=not isinstance(message.media, MessageMediaWebPage)
|
||||||
|
)
|
||||||
|
message = message.message
|
||||||
|
else:
|
||||||
|
message, msg_ent = self._parse_message_text(message, parse_mode)
|
||||||
|
request = SendMessageRequest(
|
||||||
|
peer=entity,
|
||||||
|
message=message,
|
||||||
|
entities=msg_ent,
|
||||||
|
no_webpage=not link_preview,
|
||||||
|
reply_to_msg_id=self._get_message_id(reply_to)
|
||||||
|
)
|
||||||
|
|
||||||
request = SendMessageRequest(
|
|
||||||
peer=entity,
|
|
||||||
message=message,
|
|
||||||
entities=msg_entities,
|
|
||||||
no_webpage=not link_preview,
|
|
||||||
reply_to_msg_id=self._get_message_id(reply_to)
|
|
||||||
)
|
|
||||||
result = self(request)
|
result = self(request)
|
||||||
if isinstance(result, UpdateShortSentMessage):
|
if isinstance(result, UpdateShortSentMessage):
|
||||||
return Message(
|
return Message(
|
||||||
|
@ -930,7 +975,7 @@ class TelegramClient(TelegramBareClient):
|
||||||
"""
|
"""
|
||||||
if max_id is None:
|
if max_id is None:
|
||||||
if message:
|
if message:
|
||||||
if hasattr(message, '__iter__'):
|
if utils.is_list_like(message):
|
||||||
max_id = max(msg.id for msg in message)
|
max_id = max(msg.id for msg in message)
|
||||||
else:
|
else:
|
||||||
max_id = message.id
|
max_id = message.id
|
||||||
|
@ -970,11 +1015,64 @@ class TelegramClient(TelegramBareClient):
|
||||||
|
|
||||||
raise TypeError('Invalid message type: {}'.format(type(message)))
|
raise TypeError('Invalid message type: {}'.format(type(message)))
|
||||||
|
|
||||||
|
def get_participants(self, entity, limit=None, search=''):
|
||||||
|
"""
|
||||||
|
Gets the list of participants from the specified entity
|
||||||
|
|
||||||
|
Args:
|
||||||
|
entity (:obj:`entity`):
|
||||||
|
The entity from which to retrieve the participants list.
|
||||||
|
|
||||||
|
limit (:obj: `int`):
|
||||||
|
Limits amount of participants fetched.
|
||||||
|
|
||||||
|
search (:obj: `str`, optional):
|
||||||
|
Look for participants with this string in name/username.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A list of participants with an additional .total variable on the list
|
||||||
|
indicating the total amount of members in this group/channel.
|
||||||
|
"""
|
||||||
|
entity = self.get_input_entity(entity)
|
||||||
|
limit = float('inf') if limit is None else int(limit)
|
||||||
|
if isinstance(entity, InputPeerChannel):
|
||||||
|
offset = 0
|
||||||
|
all_participants = {}
|
||||||
|
search = ChannelParticipantsSearch(search)
|
||||||
|
while True:
|
||||||
|
loop_limit = min(limit - offset, 200)
|
||||||
|
participants = self(GetParticipantsRequest(
|
||||||
|
entity, search, offset, loop_limit, hash=0
|
||||||
|
))
|
||||||
|
if not participants.users:
|
||||||
|
break
|
||||||
|
for user in participants.users:
|
||||||
|
if len(all_participants) < limit:
|
||||||
|
all_participants[user.id] = user
|
||||||
|
offset += len(participants.users)
|
||||||
|
if offset > limit:
|
||||||
|
break
|
||||||
|
|
||||||
|
users = UserList(all_participants.values())
|
||||||
|
users.total = self(GetFullChannelRequest(
|
||||||
|
entity)).full_chat.participants_count
|
||||||
|
|
||||||
|
elif isinstance(entity, InputPeerChat):
|
||||||
|
users = self(GetFullChatRequest(entity.chat_id)).users
|
||||||
|
if len(users) > limit:
|
||||||
|
users = users[:limit]
|
||||||
|
users = UserList(users)
|
||||||
|
users.total = len(users)
|
||||||
|
else:
|
||||||
|
users = UserList([entity])
|
||||||
|
users.total = 1
|
||||||
|
return users
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
|
||||||
# region Uploading files
|
# region Uploading files
|
||||||
|
|
||||||
def send_file(self, entity, file, caption='',
|
def send_file(self, entity, file, caption=None,
|
||||||
force_document=False, progress_callback=None,
|
force_document=False, progress_callback=None,
|
||||||
reply_to=None,
|
reply_to=None,
|
||||||
attributes=None,
|
attributes=None,
|
||||||
|
@ -1042,7 +1140,7 @@ class TelegramClient(TelegramBareClient):
|
||||||
"""
|
"""
|
||||||
# First check if the user passed an iterable, in which case
|
# First check if the user passed an iterable, in which case
|
||||||
# we may want to send as an album if all are photo files.
|
# we may want to send as an album if all are photo files.
|
||||||
if hasattr(file, '__iter__') and not isinstance(file, (str, bytes)):
|
if utils.is_list_like(file):
|
||||||
# Convert to tuple so we can iterate several times
|
# Convert to tuple so we can iterate several times
|
||||||
file = tuple(x for x in file)
|
file = tuple(x for x in file)
|
||||||
if all(utils.is_image(x) for x in file):
|
if all(utils.is_image(x) for x in file):
|
||||||
|
@ -1086,11 +1184,11 @@ class TelegramClient(TelegramBareClient):
|
||||||
if isinstance(file_handle, use_cache):
|
if isinstance(file_handle, use_cache):
|
||||||
# File was cached, so an instance of use_cache was returned
|
# File was cached, so an instance of use_cache was returned
|
||||||
if as_image:
|
if as_image:
|
||||||
media = InputMediaPhoto(file_handle, caption)
|
media = InputMediaPhoto(file_handle, caption or '')
|
||||||
else:
|
else:
|
||||||
media = InputMediaDocument(file_handle, caption)
|
media = InputMediaDocument(file_handle, caption or '')
|
||||||
elif as_image:
|
elif as_image:
|
||||||
media = InputMediaUploadedPhoto(file_handle, caption)
|
media = InputMediaUploadedPhoto(file_handle, caption or '')
|
||||||
else:
|
else:
|
||||||
mime_type = None
|
mime_type = None
|
||||||
if isinstance(file, str):
|
if isinstance(file, str):
|
||||||
|
@ -1128,8 +1226,9 @@ class TelegramClient(TelegramBareClient):
|
||||||
attr_dict[DocumentAttributeVideo] = doc
|
attr_dict[DocumentAttributeVideo] = doc
|
||||||
else:
|
else:
|
||||||
attr_dict = {
|
attr_dict = {
|
||||||
DocumentAttributeFilename:
|
DocumentAttributeFilename: DocumentAttributeFilename(
|
||||||
DocumentAttributeFilename('unnamed')
|
os.path.basename(
|
||||||
|
getattr(file, 'name', None) or 'unnamed'))
|
||||||
}
|
}
|
||||||
|
|
||||||
if 'is_voice_note' in kwargs:
|
if 'is_voice_note' in kwargs:
|
||||||
|
@ -1160,7 +1259,7 @@ class TelegramClient(TelegramBareClient):
|
||||||
file=file_handle,
|
file=file_handle,
|
||||||
mime_type=mime_type,
|
mime_type=mime_type,
|
||||||
attributes=list(attr_dict.values()),
|
attributes=list(attr_dict.values()),
|
||||||
caption=caption,
|
caption=caption or '',
|
||||||
**input_kw
|
**input_kw
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1180,15 +1279,11 @@ class TelegramClient(TelegramBareClient):
|
||||||
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
def send_voice_note(self, entity, file, caption='', progress_callback=None,
|
def send_voice_note(self, *args, **kwargs):
|
||||||
reply_to=None):
|
"""Wrapper method around .send_file() with is_voice_note=True"""
|
||||||
"""Wrapper method around .send_file() with is_voice_note=()"""
|
return self.send_file(*args, **kwargs, is_voice_note=True)
|
||||||
return self.send_file(entity, file, caption,
|
|
||||||
progress_callback=progress_callback,
|
|
||||||
reply_to=reply_to,
|
|
||||||
is_voice_note=()) # empty tuple is enough
|
|
||||||
|
|
||||||
def _send_album(self, entity, files, caption='',
|
def _send_album(self, entity, files, caption=None,
|
||||||
progress_callback=None, reply_to=None):
|
progress_callback=None, reply_to=None):
|
||||||
"""Specialized version of .send_file for albums"""
|
"""Specialized version of .send_file for albums"""
|
||||||
# We don't care if the user wants to avoid cache, we will use it
|
# We don't care if the user wants to avoid cache, we will use it
|
||||||
|
@ -1197,6 +1292,7 @@ class TelegramClient(TelegramBareClient):
|
||||||
# cache only makes a difference for documents where the user may
|
# cache only makes a difference for documents where the user may
|
||||||
# want the attributes used on them to change. Caption's ignored.
|
# want the attributes used on them to change. Caption's ignored.
|
||||||
entity = self.get_input_entity(entity)
|
entity = self.get_input_entity(entity)
|
||||||
|
caption = caption or ''
|
||||||
reply_to = self._get_message_id(reply_to)
|
reply_to = self._get_message_id(reply_to)
|
||||||
|
|
||||||
# Need to upload the media first, but only if they're not cached yet
|
# Need to upload the media first, but only if they're not cached yet
|
||||||
|
@ -1524,18 +1620,27 @@ class TelegramClient(TelegramBareClient):
|
||||||
|
|
||||||
file_size = document.size
|
file_size = document.size
|
||||||
|
|
||||||
|
kind = 'document'
|
||||||
possible_names = []
|
possible_names = []
|
||||||
for attr in document.attributes:
|
for attr in document.attributes:
|
||||||
if isinstance(attr, DocumentAttributeFilename):
|
if isinstance(attr, DocumentAttributeFilename):
|
||||||
possible_names.insert(0, attr.file_name)
|
possible_names.insert(0, attr.file_name)
|
||||||
|
|
||||||
elif isinstance(attr, DocumentAttributeAudio):
|
elif isinstance(attr, DocumentAttributeAudio):
|
||||||
possible_names.append('{} - {}'.format(
|
kind = 'audio'
|
||||||
attr.performer, attr.title
|
if attr.performer and attr.title:
|
||||||
))
|
possible_names.append('{} - {}'.format(
|
||||||
|
attr.performer, attr.title
|
||||||
|
))
|
||||||
|
elif attr.performer:
|
||||||
|
possible_names.append(attr.performer)
|
||||||
|
elif attr.title:
|
||||||
|
possible_names.append(attr.title)
|
||||||
|
elif attr.voice:
|
||||||
|
kind = 'voice'
|
||||||
|
|
||||||
file = self._get_proper_filename(
|
file = self._get_proper_filename(
|
||||||
file, 'document', utils.get_extension(document),
|
file, kind, utils.get_extension(document),
|
||||||
date=date, possible_names=possible_names
|
date=date, possible_names=possible_names
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1787,7 +1892,7 @@ class TelegramClient(TelegramBareClient):
|
||||||
callback.__name__, type(update).__name__))
|
callback.__name__, type(update).__name__))
|
||||||
break
|
break
|
||||||
|
|
||||||
def add_event_handler(self, callback, event):
|
def add_event_handler(self, callback, event=None):
|
||||||
"""
|
"""
|
||||||
Registers the given callback to be called on the specified event.
|
Registers the given callback to be called on the specified event.
|
||||||
|
|
||||||
|
@ -1795,9 +1900,12 @@ class TelegramClient(TelegramBareClient):
|
||||||
callback (:obj:`callable`):
|
callback (:obj:`callable`):
|
||||||
The callable function accepting one parameter to be used.
|
The callable function accepting one parameter to be used.
|
||||||
|
|
||||||
event (:obj:`_EventBuilder` | :obj:`type`):
|
event (:obj:`_EventBuilder` | :obj:`type`, optional):
|
||||||
The event builder class or instance to be used,
|
The event builder class or instance to be used,
|
||||||
for instance ``events.NewMessage``.
|
for instance ``events.NewMessage``.
|
||||||
|
|
||||||
|
If left unspecified, ``events.Raw`` (the ``Update`` objects
|
||||||
|
with no further processing) will be passed instead.
|
||||||
"""
|
"""
|
||||||
if self.updates.workers is None:
|
if self.updates.workers is None:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
|
@ -1809,6 +1917,8 @@ class TelegramClient(TelegramBareClient):
|
||||||
self.updates.handler = self._on_handler
|
self.updates.handler = self._on_handler
|
||||||
if isinstance(event, type):
|
if isinstance(event, type):
|
||||||
event = event()
|
event = event()
|
||||||
|
elif not event:
|
||||||
|
event = events.Raw()
|
||||||
|
|
||||||
event.resolve(self)
|
event.resolve(self)
|
||||||
self._event_builders.append((event, callback))
|
self._event_builders.append((event, callback))
|
||||||
|
@ -1857,7 +1967,7 @@ class TelegramClient(TelegramBareClient):
|
||||||
``User``, ``Chat`` or ``Channel`` corresponding to the input
|
``User``, ``Chat`` or ``Channel`` corresponding to the input
|
||||||
entity.
|
entity.
|
||||||
"""
|
"""
|
||||||
if hasattr(entity, '__iter__') and not isinstance(entity, str):
|
if utils.is_list_like(entity):
|
||||||
single = False
|
single = False
|
||||||
else:
|
else:
|
||||||
single = True
|
single = True
|
||||||
|
@ -1940,8 +2050,8 @@ class TelegramClient(TelegramBareClient):
|
||||||
return entity
|
return entity
|
||||||
try:
|
try:
|
||||||
# Nobody with this username, maybe it's an exact name/title
|
# Nobody with this username, maybe it's an exact name/title
|
||||||
return self.get_entity(self.get_input_entity(string))
|
return self.get_entity(self.session.get_input_entity(string))
|
||||||
except (ValueError, TypeError):
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
|
|
|
@ -5,6 +5,7 @@ to convert between an entity like an User, Chat, etc. into its Input version)
|
||||||
import math
|
import math
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import re
|
import re
|
||||||
|
import types
|
||||||
from mimetypes import add_type, guess_extension
|
from mimetypes import add_type, guess_extension
|
||||||
|
|
||||||
from .tl.types import (
|
from .tl.types import (
|
||||||
|
@ -27,7 +28,7 @@ from .tl.types import (
|
||||||
from .tl.types.contacts import ResolvedPeer
|
from .tl.types.contacts import ResolvedPeer
|
||||||
|
|
||||||
USERNAME_RE = re.compile(
|
USERNAME_RE = re.compile(
|
||||||
r'@|(?:https?://)?(?:telegram\.(?:me|dog)|t\.me)/(joinchat/)?'
|
r'@|(?:https?://)?(?:www\.)?(?:telegram\.(?:me|dog)|t\.me)/(joinchat/)?'
|
||||||
)
|
)
|
||||||
|
|
||||||
VALID_USERNAME_RE = re.compile(r'^[a-zA-Z][\w\d]{3,30}[a-zA-Z\d]$')
|
VALID_USERNAME_RE = re.compile(r'^[a-zA-Z][\w\d]{3,30}[a-zA-Z\d]$')
|
||||||
|
@ -341,6 +342,17 @@ def is_video(file):
|
||||||
(mimetypes.guess_type(file)[0] or '').startswith('video/'))
|
(mimetypes.guess_type(file)[0] or '').startswith('video/'))
|
||||||
|
|
||||||
|
|
||||||
|
def is_list_like(obj):
|
||||||
|
"""
|
||||||
|
Returns True if the given object looks like a list.
|
||||||
|
|
||||||
|
Checking if hasattr(obj, '__iter__') and ignoring str/bytes is not
|
||||||
|
enough. Things like open() are also iterable (and probably many
|
||||||
|
other things), so just support the commonly known list-like objects.
|
||||||
|
"""
|
||||||
|
return isinstance(obj, (list, tuple, set, dict, types.GeneratorType))
|
||||||
|
|
||||||
|
|
||||||
def parse_phone(phone):
|
def parse_phone(phone):
|
||||||
"""Parses the given phone, or returns None if it's invalid"""
|
"""Parses the given phone, or returns None if it's invalid"""
|
||||||
if isinstance(phone, int):
|
if isinstance(phone, int):
|
||||||
|
@ -366,6 +378,8 @@ def parse_username(username):
|
||||||
is_invite = bool(m.group(1))
|
is_invite = bool(m.group(1))
|
||||||
if is_invite:
|
if is_invite:
|
||||||
return username, True
|
return username, True
|
||||||
|
else:
|
||||||
|
username = username.rstrip('/')
|
||||||
|
|
||||||
if VALID_USERNAME_RE.match(username):
|
if VALID_USERNAME_RE.match(username):
|
||||||
return username.lower(), False
|
return username.lower(), False
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
# Versions should comply with PEP440.
|
# Versions should comply with PEP440.
|
||||||
# This line is parsed in setup.py:
|
# This line is parsed in setup.py:
|
||||||
__version__ = '0.17.3'
|
__version__ = '0.17.4'
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import os
|
import os
|
||||||
from getpass import getpass
|
from getpass import getpass
|
||||||
|
|
||||||
from telethon import TelegramClient, ConnectionMode
|
from telethon.utils import get_display_name
|
||||||
|
|
||||||
|
from telethon import ConnectionMode, TelegramClient
|
||||||
from telethon.errors import SessionPasswordNeededError
|
from telethon.errors import SessionPasswordNeededError
|
||||||
from telethon.tl.types import (
|
from telethon.tl.types import (
|
||||||
UpdateShortChatMessage, UpdateShortMessage, PeerChat
|
PeerChat, UpdateShortChatMessage, UpdateShortMessage
|
||||||
)
|
)
|
||||||
from telethon.utils import get_display_name
|
|
||||||
|
|
||||||
|
|
||||||
def sprint(string, *args, **kwargs):
|
def sprint(string, *args, **kwargs):
|
||||||
|
@ -47,6 +48,7 @@ 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,
|
def __init__(self, session_user_id, user_phone, api_id, api_hash,
|
||||||
proxy=None):
|
proxy=None):
|
||||||
"""
|
"""
|
||||||
|
@ -182,14 +184,15 @@ class InteractiveTelegramClient(TelegramClient):
|
||||||
# Show some information
|
# Show some information
|
||||||
print_title('Chat with "{}"'.format(get_display_name(entity)))
|
print_title('Chat with "{}"'.format(get_display_name(entity)))
|
||||||
print('Available commands:')
|
print('Available commands:')
|
||||||
print(' !q: Quits the current chat.')
|
print(' !q: Quits the current chat.')
|
||||||
print(' !Q: Quits the current chat and exits.')
|
print(' !Q: Quits the current chat and exits.')
|
||||||
print(' !h: prints the latest messages (message History).')
|
print(' !h: prints the latest messages (message History).')
|
||||||
print(' !up <path>: Uploads and sends the Photo from path.')
|
print(' !up <path>: Uploads and sends the Photo from path.')
|
||||||
print(' !uf <path>: Uploads and sends the File from path.')
|
print(' !uf <path>: Uploads and sends the File from path.')
|
||||||
print(' !d <msg-id>: Deletes a message by its id')
|
print(' !d <msg-id>: Deletes a message by its id')
|
||||||
print(' !dm <msg-id>: Downloads the given message Media (if any).')
|
print(' !dm <msg-id>: Downloads the given message Media (if any).')
|
||||||
print(' !dp: Downloads the current dialog Profile picture.')
|
print(' !dp: Downloads the current dialog Profile picture.')
|
||||||
|
print(' !i: Prints information about this chat..')
|
||||||
print()
|
print()
|
||||||
|
|
||||||
# And start a while loop to chat
|
# And start a while loop to chat
|
||||||
|
@ -234,8 +237,7 @@ class InteractiveTelegramClient(TelegramClient):
|
||||||
|
|
||||||
# And print it to the user
|
# And print it to the user
|
||||||
sprint('[{}:{}] (ID={}) {}: {}'.format(
|
sprint('[{}:{}] (ID={}) {}: {}'.format(
|
||||||
msg.date.hour, msg.date.minute, msg.id, name,
|
msg.date.hour, msg.date.minute, msg.id, name, content))
|
||||||
content))
|
|
||||||
|
|
||||||
# Send photo
|
# Send photo
|
||||||
elif msg.startswith('!up '):
|
elif msg.startswith('!up '):
|
||||||
|
@ -264,12 +266,16 @@ class InteractiveTelegramClient(TelegramClient):
|
||||||
os.makedirs('usermedia', exist_ok=True)
|
os.makedirs('usermedia', exist_ok=True)
|
||||||
output = self.download_profile_photo(entity, 'usermedia')
|
output = self.download_profile_photo(entity, 'usermedia')
|
||||||
if output:
|
if output:
|
||||||
print(
|
print('Profile picture downloaded to', output)
|
||||||
'Profile picture downloaded to {}'.format(output)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
print('No profile picture found for this user!')
|
print('No profile picture found for this user!')
|
||||||
|
|
||||||
|
elif msg == '!i':
|
||||||
|
attributes = list(entity.to_dict().items())
|
||||||
|
pad = max(len(x) for x, _ in attributes)
|
||||||
|
for name, val in attributes:
|
||||||
|
print("{:<{width}} : {}".format(name, val, width=pad))
|
||||||
|
|
||||||
# Send chat message (if any)
|
# Send chat message (if any)
|
||||||
elif msg:
|
elif msg:
|
||||||
self.send_message(entity, msg, link_preview=False)
|
self.send_message(entity, msg, link_preview=False)
|
||||||
|
@ -356,6 +362,5 @@ class InteractiveTelegramClient(TelegramClient):
|
||||||
else:
|
else:
|
||||||
who = self.get_entity(update.from_id)
|
who = self.get_entity(update.from_id)
|
||||||
sprint('<< {} @ {} sent "{}"'.format(
|
sprint('<< {} @ {} sent "{}"'.format(
|
||||||
get_display_name(which), get_display_name(who),
|
get_display_name(which), get_display_name(who), update.message
|
||||||
update.message
|
|
||||||
))
|
))
|
||||||
|
|
Loading…
Reference in New Issue
Block a user