Merge branch 'master' into asyncio

This commit is contained in:
Lonami Exo 2018-04-23 15:53:28 +02:00
commit 04a68f12cc
9 changed files with 142 additions and 60 deletions

View File

@ -204,9 +204,7 @@ class ChatAction(EventBuilder):
if isinstance(self._pinned_message, int) and await self.input_chat:
r = await self._client(functions.channels.GetMessagesRequest(
self._input_chat, [
types.InputMessageID(self._pinned_message)
]
self._input_chat, [self._pinned_message]
))
try:
self._pinned_message = next(

View File

@ -118,15 +118,11 @@ class EventCommon(abc.ABC):
try:
if isinstance(chat, types.InputPeerChannel):
result = await self._client(
functions.channels.GetMessagesRequest(chat, [
types.InputMessageID(msg_id)
])
functions.channels.GetMessagesRequest(chat, [msg_id])
)
else:
result = await self._client(
functions.messages.GetMessagesRequest([
types.InputMessageID(msg_id)
])
functions.messages.GetMessagesRequest([msg_id])
)
except RPCError:
return None, None

View File

@ -101,16 +101,14 @@ class MessageRead(EventBuilder):
if not chat:
self._messages = []
elif isinstance(chat, types.InputPeerChannel):
ids = [types.InputMessageID(x) for x in self._message_ids]
self._messages =\
await self._client(functions.channels.GetMessagesRequest(
chat, ids
chat, self._message_ids
)).messages
else:
ids = [types.InputMessageID(x) for x in self._message_ids]
self._messages = \
await self._client(functions.messages.GetMessagesRequest(
ids
self._message_ids
)).messages
return self._messages

View File

@ -297,13 +297,11 @@ class NewMessage(EventBuilder):
if self._reply_message is None:
if isinstance(await self.input_chat, types.InputPeerChannel):
r = await self._client(functions.channels.GetMessagesRequest(
await self.input_chat, [
types.InputMessageID(self.message.reply_to_msg_id)
]
await self.input_chat, [self.message.reply_to_msg_id]
))
else:
r = await self._client(functions.messages.GetMessagesRequest(
[types.InputMessageID(self.message.reply_to_msg_id)]
[self.message.reply_to_msg_id]
))
if not isinstance(r, types.messages.MessagesNotModified):
self._reply_message = r.messages[0]

View File

@ -17,7 +17,7 @@ CURRENT_VERSION = 3 # database version
class SQLiteSession(MemorySession):
"""This session contains the required information to login into your
Telegram account. NEVER give the saved JSON file to anyone, since
Telegram account. NEVER give the saved session file to anyone, since
they would gain instant access to all your messages and contacts.
If you think the session has been compromised, close all the sessions

View File

@ -9,7 +9,8 @@ from .crypto import rsa
from .errors import (
RPCError, BrokenAuthKeyError, ServerError, FloodWaitError,
FloodTestPhoneWaitError, TypeNotFoundError, UnauthorizedError,
PhoneMigrateError, NetworkMigrateError, UserMigrateError, AuthKeyError
PhoneMigrateError, NetworkMigrateError, UserMigrateError, AuthKeyError,
RpcCallFailError
)
from .network import authenticator, MtProtoSender, Connection, ConnectionMode
from .sessions import Session, SQLiteSession
@ -544,9 +545,9 @@ class TelegramBareClient:
await self._reconnect(new_dc=e.new_dc)
return await self._invoke(call_receive, retry, *requests)
except ServerError as e:
except (ServerError, RpcCallFailError) as e:
# Telegram is having some issues, just retry
__log__.error('Telegram servers are having internal errors %s', e)
__log__.warning('Telegram is having internal issues: %s', e)
except (FloodWaitError, FloodTestPhoneWaitError) as e:
__log__.warning('Request invoked too often, wait %ds', e.seconds)

View File

@ -58,7 +58,7 @@ from .tl.functions.messages import (
SendMessageRequest, GetChatsRequest, GetAllDraftsRequest,
CheckChatInviteRequest, ReadMentionsRequest, SendMultiMediaRequest,
UploadMediaRequest, EditMessageRequest, GetFullChatRequest,
ForwardMessagesRequest
ForwardMessagesRequest, SearchRequest
)
from .tl.functions import channels
@ -780,9 +780,13 @@ class TelegramClient(TelegramBareClient):
if isinstance(message, Message):
if (message.media
and not isinstance(message.media, MessageMediaWebPage)):
return await self.send_file(entity, message.media)
return await self.send_file(entity, message.media,
caption=message.message,
entities=message.entities)
if utils.get_peer_id(entity) == utils.get_peer_id(message.to_id):
if reply_to is not None:
reply_id = self._get_message_id(reply_to)
elif utils.get_peer_id(entity) == utils.get_peer_id(message.to_id):
reply_id = message.reply_to_msg_id
else:
reply_id = None
@ -879,20 +883,26 @@ class TelegramClient(TelegramBareClient):
result = [id_to_message[random_to_id[rnd]] for rnd in req.random_id]
return result[0] if single else result
async def edit_message(self, entity, message_id, message=None,
async def edit_message(self, entity, message=None, text=None,
parse_mode='md', link_preview=True):
"""
Edits the given message ID (to change its contents or disable preview).
Args:
entity (`entity`):
From which chat to edit the message.
entity (`entity` | :tl:`Message`):
From which chat to edit the message. This can also be
the message to be edited, and the entity will be inferred
from it, so the next parameter will be assumed to be the
message text.
message_id (`str`):
The ID of the message (or ``Message`` itself) to be edited.
message (`int` | :tl:`Message` | `str`):
The ID of the message (or :tl:`Message` itself) to be edited.
If the `entity` was a :tl:`Message`, then this message will be
treated as the new text.
message (`str`, optional):
The new text of the message.
text (`str`, optional):
The new text of the message. Does nothing if the `entity`
was a :tl:`Message`.
parse_mode (`str`, optional):
Can be 'md' or 'markdown' for markdown-like parsing (default),
@ -903,6 +913,21 @@ class TelegramClient(TelegramBareClient):
link_preview (`bool`, optional):
Should the link preview be shown?
Examples:
>>> async def main():
... client = await TelegramClient(...).start()
... message = await client.send_message('username', 'hello')
...
... await client.edit_message('username', message, 'hello!')
... # or
... await client.edit_message('username', message.id, 'Hello')
... # or
... await client.edit_message(message, 'Hello!')
...
>>> loop = ...
>>> loop.run_until_complete(main())
Raises:
``MessageAuthorRequiredError`` if you're not the author of the
message but try editing it anyway.
@ -913,12 +938,16 @@ class TelegramClient(TelegramBareClient):
Returns:
The edited :tl:`Message`.
"""
message, msg_entities =\
await self._parse_message_text(message, parse_mode)
if isinstance(entity, Message):
text = message # Shift the parameters to the right
message = entity
entity = entity.to_id
text, msg_entities = await self._parse_message_text(text, parse_mode)
request = EditMessageRequest(
peer=await self.get_input_entity(entity),
id=self._get_message_id(message_id),
message=message,
id=self._get_message_id(message),
message=text,
no_webpage=not link_preview,
entities=msg_entities
)
@ -967,10 +996,14 @@ class TelegramClient(TelegramBareClient):
async def iter_messages(self, entity, limit=20, offset_date=None,
offset_id=0, max_id=0, min_id=0, add_offset=0,
search=None, filter=None, from_user=None,
batch_size=100, wait_time=None, _total=None):
"""
Iterator over the message history for the specified entity.
If either `search`, `filter` or `from_user` are provided,
:tl:`messages.Search` will be used instead of :tl:`messages.getHistory`.
Args:
entity (`entity`):
The entity from whom to retrieve the message history.
@ -1002,6 +1035,17 @@ class TelegramClient(TelegramBareClient):
Additional message offset (all of the specified offsets +
this offset = older messages).
search (`str`):
The string to be used as a search query.
filter (:tl:`MessagesFilter` | `type`):
The filter to use when returning messages. For instance,
:tl:`InputMessagesFilterPhotos` would yield only messages
containing photos.
from_user (`entity`):
Only messages from this user will be returned.
batch_size (`int`):
Messages will be returned in chunks of this size (100 is
the maximum). While it makes no sense to modify this value,
@ -1033,15 +1077,37 @@ class TelegramClient(TelegramBareClient):
"""
entity = await self.get_input_entity(entity)
limit = float('inf') if limit is None else int(limit)
if search is not None or filter or from_user:
request = SearchRequest(
peer=entity,
q=search or '',
filter=filter() if isinstance(filter, type) else filter,
min_date=None,
max_date=offset_date,
offset_id=offset_id,
add_offset=add_offset,
limit=1,
max_id=max_id,
min_id=min_id,
from_id=self.get_input_entity(from_user) if from_user else None
)
else:
request = GetHistoryRequest(
peer=entity,
limit=1,
offset_date=offset_date,
offset_id=offset_id,
min_id=min_id,
max_id=max_id,
add_offset=add_offset,
hash=0
)
if limit == 0:
if not _total:
return
# No messages, but we still need to know the total message count
result = await self(GetHistoryRequest(
peer=entity, limit=1,
offset_date=None, offset_id=0, max_id=0, min_id=0,
add_offset=0, hash=0
))
result = await self(request)
_total[0] = getattr(result, 'count', len(result.messages))
return
@ -1052,17 +1118,8 @@ class TelegramClient(TelegramBareClient):
batch_size = min(max(batch_size, 1), 100)
while have < limit:
# Telegram has a hard limit of 100
real_limit = min(limit - have, batch_size)
r = await self(GetHistoryRequest(
peer=entity,
limit=real_limit,
offset_date=offset_date,
offset_id=offset_id,
max_id=max_id,
min_id=min_id,
add_offset=add_offset,
hash=0
))
request.limit = min(limit - have, batch_size)
r = await self(request)
if _total:
_total[0] = getattr(r, 'count', len(r.messages))
@ -1097,11 +1154,15 @@ class TelegramClient(TelegramBareClient):
yield message
have += 1
if len(r.messages) < real_limit:
if len(r.messages) < request.limit:
break
offset_id = r.messages[-1].id
offset_date = r.messages[-1].date
request.offset_id = r.messages[-1].id
if isinstance(request, GetHistoryRequest):
request.offset_date = r.messages[-1].date
else:
request.max_date = r.messages[-1].date
await asyncio.sleep(wait_time)
async def get_messages(self, *args, **kwargs):
@ -1479,8 +1540,14 @@ class TelegramClient(TelegramBareClient):
entity = await self.get_input_entity(entity)
reply_to = self._get_message_id(reply_to)
caption, msg_entities =\
await self._parse_message_text(caption, parse_mode)
# Not document since it's subject to change.
# Needed when a Message is passed to send_message and it has media.
if 'entities' in kwargs:
msg_entities = kwargs['entities']
else:
caption, msg_entities =\
await self._parse_message_text(caption, parse_mode)
if not isinstance(file, (str, bytes, io.IOBase)):
# The user may pass a Message containing media (or the media,
@ -2481,7 +2548,11 @@ class TelegramClient(TelegramBareClient):
If in the end the access hash required for the peer was not found,
a ValueError will be raised.
Returns:
:tl:`InputPeerUser`, :tl:`InputPeerChat` or :tl:`InputPeerChannel`.
:tl:`InputPeerUser`, :tl:`InputPeerChat` or :tl:`InputPeerChannel`
or :tl:`InputPeerSelf` if the parameter is ``'me'`` or ``'self'``.
If you need to get the ID of yourself, you should use
`get_me` with ``input_peer=True``) instead.
"""
if peer in ('me', 'self'):
return InputPeerSelf()

View File

@ -25,7 +25,7 @@ from .tl.types import (
InputPhotoEmpty, FileLocation, ChatPhotoEmpty, UserProfilePhotoEmpty,
FileLocationUnavailable, InputMediaUploadedDocument, ChannelFull,
InputMediaUploadedPhoto, DocumentAttributeFilename, photos,
TopPeer, InputNotifyPeer
TopPeer, InputNotifyPeer, InputMessageID
)
from .tl.types.contacts import ResolvedPeer
@ -255,8 +255,12 @@ def get_input_media(media, is_photo=False):
it will be treated as an :tl:`InputMediaUploadedPhoto`.
"""
try:
if media.SUBCLASS_OF_ID == 0xfaf846f4: # crc32(b'InputMedia'):
if media.SUBCLASS_OF_ID == 0xfaf846f4: # crc32(b'InputMedia')
return media
elif media.SUBCLASS_OF_ID == 0x846363e0: # crc32(b'InputPhoto')
return InputMediaPhoto(media)
elif media.SUBCLASS_OF_ID == 0xf33fdb68: # crc32(b'InputDocument')
return InputMediaDocument(media)
except AttributeError:
_raise_cast_fail(media, 'InputMedia')
@ -333,6 +337,21 @@ def get_input_media(media, is_photo=False):
_raise_cast_fail(media, 'InputMedia')
def get_input_message(message):
"""Similar to :meth:`get_input_peer`, but for input messages."""
try:
if isinstance(message, int): # This case is really common too
return InputMessageID(message)
elif message.SUBCLASS_OF_ID == 0x54b6bcc5: # crc32(b'InputMessage'):
return message
elif message.SUBCLASS_OF_ID == 0x790009e3: # crc32(b'Message'):
return InputMessageID(message.id)
except AttributeError:
pass
_raise_cast_fail(message, 'InputMedia')
def is_image(file):
"""
Returns ``True`` if the file extension looks like an image file to Telegram.

View File

@ -18,7 +18,8 @@ AUTO_CASTS = {
'InputChannel': 'utils.get_input_channel(await client.get_input_entity({}))',
'InputUser': 'utils.get_input_user(await client.get_input_entity({}))',
'InputMedia': 'utils.get_input_media({})',
'InputPhoto': 'utils.get_input_photo({})'
'InputPhoto': 'utils.get_input_photo({})',
'InputMessage': 'utils.get_input_message({})'
}
BASE_TYPES = ('string', 'bytes', 'int', 'long', 'int128',