Merge branch 'master' into asyncio

This commit is contained in:
Lonami Exo 2017-11-16 13:20:20 +01:00
commit 7f5126c341
12 changed files with 446 additions and 185 deletions

View File

@ -15,16 +15,11 @@ Extra supported commands are:
from codecs import open
from sys import argv
import os
import re
# Always prefer setuptools over distutils
from setuptools import find_packages, setup
try:
from telethon import TelegramClient
except Exception as e:
print('Failed to import TelegramClient due to', e)
TelegramClient = None
class TempWorkDir:
"""Switches the working directory to be the one on which this file lives,
@ -94,21 +89,15 @@ def main():
fetch_errors(ERRORS_JSON)
else:
if not TelegramClient:
gen_tl()
from telethon import TelegramClient as TgClient
version = TgClient.__version__
else:
version = TelegramClient.__version__
# Get the long description from the README file
with open('README.rst', encoding='utf-8') as f:
long_description = f.read()
with open('telethon/version.py', encoding='utf-8') as f:
version = re.search(r"^__version__\s+=\s+'(.*)'$",
f.read(), flags=re.MULTILINE).group(1)
setup(
name='Telethon',
# Versions should comply with PEP440.
version=version,
description="Full-featured Telegram client library for Python 3",
long_description=long_description,

View File

@ -1,4 +1,9 @@
import logging
from .telegram_bare_client import TelegramBareClient
from .telegram_client import TelegramClient
from .network import ConnectionMode
from . import tl
from . import tl, version
__version__ = version.__version__
logging.getLogger(__name__).addHandler(logging.NullHandler())

View File

@ -1,71 +1,45 @@
from random import randint
try:
import sympy.ntheory
except ImportError:
sympy = None
class Factorization:
@staticmethod
def find_small_multiplier_lopatin(what):
"""Finds the small multiplier by using Lopatin's method"""
g = 0
for i in range(3):
q = (randint(0, 127) & 15) + 17
x = randint(0, 1000000000) + 1
y = x
lim = 1 << (i + 18)
for j in range(1, lim):
a, b, c = x, x, q
while b != 0:
if (b & 1) != 0:
c += a
if c >= what:
c -= what
a += a
if a >= what:
a -= what
b >>= 1
@classmethod
def factorize(cls, pq):
if pq % 2 == 0:
return 2, pq // 2
x = c
z = y - x if x < y else x - y
g = Factorization.gcd(z, what)
if g != 1:
break
y, c, m = randint(1, pq - 1), randint(1, pq - 1), randint(1, pq - 1)
g = r = q = 1
x = ys = 0
if (j & (j - 1)) == 0:
y = x
while g == 1:
x = y
for i in range(r):
y = (pow(y, 2, pq) + c) % pq
k = 0
while k < r and g == 1:
ys = y
for i in range(min(m, r - k)):
y = (pow(y, 2, pq) + c) % pq
q = q * (abs(x - y)) % pq
g = cls.gcd(q, pq)
k += m
r *= 2
if g == pq:
while True:
ys = (pow(ys, 2, pq) + c) % pq
g = cls.gcd(abs(x - ys), pq)
if g > 1:
break
p = what // g
return min(p, g)
return g, pq // g
@staticmethod
def gcd(a, b):
"""Calculates the greatest common divisor"""
while a != 0 and b != 0:
while b & 1 == 0:
b >>= 1
while b:
a, b = b, a % b
while a & 1 == 0:
a >>= 1
if a > b:
a -= b
else:
b -= a
return a if b == 0 else b
@staticmethod
def factorize(pq):
"""Factorizes the given number and returns both
the divisor and the number divided by the divisor
"""
if sympy:
return tuple(sympy.ntheory.factorint(pq).keys())
else:
divisor = Factorization.find_small_multiplier_lopatin(pq)
return divisor, pq // divisor
return a

View File

@ -0,0 +1,123 @@
"""
Simple markdown parser which does not support nesting. Intended primarily
for use within the library, which attempts to handle emojies correctly,
since they seem to count as two characters and it's a bit strange.
"""
import re
from ..tl.types import (
MessageEntityBold, MessageEntityItalic, MessageEntityCode,
MessageEntityPre, MessageEntityTextUrl
)
DEFAULT_DELIMITERS = {
'**': MessageEntityBold,
'__': MessageEntityItalic,
'`': MessageEntityCode,
'```': MessageEntityPre
}
# Regex used to match utf-16le encoded r'\[(.+?)\]\((.+?)\)',
# reason why there's '\0' after every match-literal character.
DEFAULT_URL_RE = re.compile(b'\\[\0(.+?)\\]\0\\(\0(.+?)\\)\0')
def parse(message, delimiters=None, url_re=None):
"""
Parses the given message and returns the stripped message and a list
of MessageEntity* using the specified delimiters dictionary (or default
if None). The dictionary should be a mapping {delimiter: entity class}.
The url_re(gex) must contain two matching groups: the text to be
clickable and the URL itself, and be utf-16le encoded.
"""
if url_re is None:
url_re = DEFAULT_URL_RE
elif url_re:
if isinstance(url_re, bytes):
url_re = re.compile(url_re)
if not delimiters:
if delimiters is not None:
return message, []
delimiters = DEFAULT_DELIMITERS
delimiters = {k.encode('utf-16le'): v for k, v in delimiters.items()}
# Cannot use a for loop because we need to skip some indices
i = 0
result = []
current = None
end_delimiter = None
# Work on byte level with the utf-16le encoding to get the offsets right.
# The offset will just be half the index we're at.
message = message.encode('utf-16le')
while i < len(message):
if url_re and current is None:
# If we're not inside a previous match since Telegram doesn't allow
# nested message entities, try matching the URL from the i'th pos.
url_match = url_re.match(message, pos=i)
if url_match:
# Replace the whole match with only the inline URL text.
message = b''.join((
message[:url_match.start()],
url_match.group(1),
message[url_match.end():]
))
result.append(MessageEntityTextUrl(
offset=i // 2, length=len(url_match.group(1)) // 2,
url=url_match.group(2).decode('utf-16le')
))
i += len(url_match.group(1))
# Next loop iteration, don't check delimiters, since
# a new inline URL might be right after this one.
continue
if end_delimiter is None:
# We're not expecting any delimiter, so check them all
for d, m in delimiters.items():
# Slice the string at the current i'th position to see if
# it matches the current delimiter d, otherwise skip it.
if message[i:i + len(d)] != d:
continue
if message[i + len(d):i + 2 * len(d)] == d:
# The same delimiter can't be right afterwards, if
# this were the case we would match empty strings
# like `` which we don't want to.
continue
# Get rid of the delimiter by slicing it away
message = message[:i] + message[i + len(d):]
if m == MessageEntityPre:
# Special case, also has 'lang'
current = m(i // 2, None, '')
else:
current = m(i // 2, None)
end_delimiter = d # We expect the same delimiter.
break
elif message[i:i + len(end_delimiter)] == end_delimiter:
message = message[:i] + message[i + len(end_delimiter):]
current.length = (i // 2) - current.offset
result.append(current)
current, end_delimiter = None, None
# Don't increment i here as we matched a delimiter,
# and there may be a new one right after. This is
# different than when encountering the first delimiter,
# as we already know there won't be the same right after.
continue
# Next iteration, utf-16 encoded characters need 2 bytes.
i += 2
# We may have found some a delimiter but not its ending pair.
# If this is the case, we want to insert the delimiter character back.
if current is not None:
message = \
message[:current.offset] + end_delimiter + message[current.offset:]
return message.decode('utf-16le'), result

View File

@ -1,12 +1,12 @@
import logging
import os
import asyncio
from datetime import timedelta, datetime
from datetime import timedelta
from hashlib import md5
from io import BytesIO
from asyncio import Lock
from . import helpers as utils
from . import helpers as utils, version
from .crypto import rsa, CdnDecrypter
from .errors import (
RPCError, BrokenAuthKeyError, ServerError,
@ -57,7 +57,7 @@ class TelegramBareClient:
"""
# Current TelegramClient version
__version__ = '0.15.3'
__version__ = version.__version__
# TODO Make this thread-safe, all connections share the same DC
_config = None # Server configuration (with .dc_options)
@ -188,12 +188,10 @@ class TelegramBareClient:
self.disconnect()
return await self.connect(_sync_updates=_sync_updates)
except (RPCError, ConnectionError) as error:
except (RPCError, ConnectionError):
# Probably errors from the previous session, ignore them
self.disconnect()
self._logger.debug(
'Could not stabilise initial connection: {}'.format(error)
)
self._logger.exception('Could not stabilise initial connection.')
return False
def is_connected(self):
@ -232,7 +230,6 @@ class TelegramBareClient:
# Assume we are disconnected due to some error, so connect again
try:
await self._reconnect_lock.acquire()
# Another thread may have connected again, so check that first
if self.is_connected():
return True
@ -383,6 +380,12 @@ class TelegramBareClient:
if result is not None:
return result
await asyncio.sleep(retry + 1, loop=self._loop)
self._logger.debug('RPC failed. Attempting reconnection.')
if not self._reconnect_lock.locked():
with await self._reconnect_lock:
self._reconnect()
raise ValueError('Number of retries reached 0.')
# Let people use client.invoke(SomeRequest()) instead client(...)
@ -436,17 +439,12 @@ class TelegramBareClient:
pass # We will just retry
except ConnectionResetError:
if not self._user_connected or self._reconnect_lock.locked():
# Only attempt reconnecting if the user called connect and not
# reconnecting already.
raise
self._logger.debug('Server disconnected us. Reconnecting and '
'resending request... (%d)' % retry)
await self._reconnect()
if not self.is_connected():
await asyncio.sleep(retry + 1, loop=self._loop)
if self._user_connected:
# Server disconnected us, __call__ will try reconnecting.
return None
else:
# User never called .connect(), so raise this error.
raise
if init_connection:
# We initialized the connection successfully, even if
@ -740,11 +738,10 @@ class TelegramBareClient:
self._logger.debug(error)
need_reconnect = True
await asyncio.sleep(1, loop=self._loop)
except Exception as error:
except Exception:
# Unknown exception, pass it to the main thread
self._logger.debug(
'[ERROR] Unknown error on the read loop, please report',
error
self._logger.exception(
'Unknown error on the read loop, please report.'
)
try:
@ -762,10 +759,6 @@ class TelegramBareClient:
except ImportError:
"Not using PySocks, so it can't be a socket error"
# If something strange happens we don't want to enter an
# infinite loop where all we do is raise an exception, so
# add a little sleep to avoid the CPU usage going mad.
await asyncio.sleep(0.1, loop=self._loop)
break
self._recv_loop = None

View File

@ -1,7 +1,10 @@
import os
import time
from datetime import datetime, timedelta
from mimetypes import guess_type
import asyncio
try:
import socks
except ImportError:
@ -22,15 +25,16 @@ from .tl.functions.account import (
)
from .tl.functions.auth import (
CheckPasswordRequest, LogOutRequest, SendCodeRequest, SignInRequest,
SignUpRequest, ImportBotAuthorizationRequest
SignUpRequest, ResendCodeRequest, ImportBotAuthorizationRequest
)
from .tl.functions.contacts import (
GetContactsRequest, ResolveUsernameRequest
)
from .tl.functions.messages import (
GetDialogsRequest, GetHistoryRequest, ReadHistoryRequest, SendMediaRequest,
SendMessageRequest, GetChatsRequest,
GetAllDraftsRequest)
SendMessageRequest, GetChatsRequest, GetAllDraftsRequest,
CheckChatInviteRequest
)
from .tl.functions import channels
from .tl.functions import messages
@ -48,8 +52,12 @@ from .tl.types import (
Message, MessageMediaContact, MessageMediaDocument, MessageMediaPhoto,
InputUserSelf, UserProfilePhoto, ChatPhoto, UpdateMessageID,
UpdateNewChannelMessage, UpdateNewMessage, UpdateShortSentMessage,
PeerUser, InputPeerUser, InputPeerChat, InputPeerChannel)
PeerUser, InputPeerUser, InputPeerChat, InputPeerChannel, MessageEmpty,
ChatInvite, ChatInviteAlready, PeerChannel
)
from .tl.types.messages import DialogsSlice
from .extensions import markdown
class TelegramClient(TelegramBareClient):
"""Full featured TelegramClient meant to extend the basic functionality"""
@ -101,16 +109,29 @@ class TelegramClient(TelegramBareClient):
# region Authorization requests
async def send_code_request(self, phone):
async def send_code_request(self, phone, force_sms=False):
"""Sends a code request to the specified phone number.
:param str | int phone: The phone to which the code will be sent.
:return auth.SentCode: Information about the result of the request.
:param str | int phone:
The phone to which the code will be sent.
:param bool force_sms:
Whether to force sending as SMS. You should call it at least
once before without this set to True first.
:return auth.SentCode:
Information about the result of the request.
"""
phone = EntityDatabase.parse_phone(phone) or self._phone
if force_sms:
if not self._phone_code_hash:
raise ValueError(
'You must call this method without force_sms at least once.'
)
result = await self(ResendCodeRequest(phone, self._phone_code_hash))
else:
result = await self(SendCodeRequest(phone, self.api_id, self.api_hash))
self._phone = phone
self._phone_code_hash = result.phone_code_hash
self._phone = phone
return result
async def sign_in(self, phone=None, code=None,
@ -251,22 +272,21 @@ class TelegramClient(TelegramBareClient):
The peer to be used as an offset.
:return: A tuple of lists ([dialogs], [entities]).
"""
if limit is None:
limit = float('inf')
limit = float('inf') if limit is None else int(limit)
if limit == 0:
return [], []
dialogs = {} # Use peer id as identifier to avoid dupes
messages = {} # Used later for sorting TODO also return these?
entities = {}
while len(dialogs) < limit:
need = limit - len(dialogs)
real_limit = min(limit - len(dialogs), 100)
r = await self(GetDialogsRequest(
offset_date=offset_date,
offset_id=offset_id,
offset_peer=offset_peer,
limit=need if need < float('inf') else 0
limit=real_limit
))
if not r.dialogs:
break
for d in r.dialogs:
dialogs[utils.get_peer_id(d.peer, True)] = d
@ -279,8 +299,9 @@ class TelegramClient(TelegramBareClient):
for c in r.chats:
entities[c.id] = c
if not isinstance(r, DialogsSlice):
# Don't enter next iteration if we already got all
if len(r.dialogs) < real_limit or not isinstance(r, DialogsSlice):
# Less than we requested means we reached the end, or
# we didn't get a DialogsSlice which means we got all.
break
offset_date = r.messages[-1].date
@ -324,21 +345,39 @@ class TelegramClient(TelegramBareClient):
entity,
message,
reply_to=None,
parse_mode=None,
link_preview=True):
"""
Sends the given message to the specified entity (user/chat/channel).
:param str | int | User | Chat | Channel entity: To who will it be sent.
:param str message: The message to be sent.
:param int | Message reply_to: Whether to reply to a message or not.
:param link_preview: Should the link preview be shown?
:param str | int | User | Chat | Channel entity:
To who will it be sent.
:param str message:
The message to be sent.
:param int | Message reply_to:
Whether to reply to a message or not.
:param str parse_mode:
Can be 'md' or 'markdown' for markdown-like parsing, in a similar
fashion how official clients work.
:param link_preview:
Should the link preview be shown?
:return Message: the sent message
"""
entity = await self.get_input_entity(entity)
if parse_mode:
parse_mode = parse_mode.lower()
if parse_mode in {'md', 'markdown'}:
message, msg_entities = markdown.parse(message)
else:
raise ValueError('Unknown parsing mode', parse_mode)
else:
msg_entities = []
request = SendMessageRequest(
peer=entity,
message=message,
entities=[],
entities=msg_entities,
no_webpage=not link_preview,
reply_to_msg_id=self._get_reply_to(reply_to)
)
@ -415,43 +454,100 @@ class TelegramClient(TelegramBareClient):
"""
Gets the message history for the specified entity
:param entity: The entity from whom to retrieve the message history
:param limit: Number of messages to be retrieved
:param offset_date: Offset date (messages *previous* to this date will be retrieved)
:param offset_id: Offset message ID (only messages *previous* to the given ID will be retrieved)
:param max_id: All the messages with a higher (newer) ID or equal to this will be excluded
:param min_id: All the messages with a lower (older) ID or equal to this will be excluded
:param add_offset: Additional message offset (all of the specified offsets + this offset = older messages)
:param entity:
The entity from whom to retrieve the message history.
:param limit:
Number of messages to be retrieved. Due to limitations with the API
retrieving more than 3000 messages will take longer than half a
minute (or even more based on previous calls). The limit may also
be None, which would eventually return the whole history.
:param offset_date:
Offset date (messages *previous* to this date will be retrieved).
:param offset_id:
Offset message ID (only messages *previous* to the given ID will
be retrieved).
:param max_id:
All the messages with a higher (newer) ID or equal to this will
be excluded
:param min_id:
All the messages with a lower (older) ID or equal to this will
be excluded.
:param add_offset:
Additional message offset
(all of the specified offsets + this offset = older messages).
:return: A tuple containing total message count and two more lists ([messages], [senders]).
Note that the sender can be null if it was not found!
"""
entity = await self.get_input_entity(entity)
limit = float('inf') if limit is None else int(limit)
if limit == 0:
# No messages, but we still need to know the total message count
result = await self(GetHistoryRequest(
peer=await self.get_input_entity(entity),
limit=limit,
peer=entity, limit=1,
offset_date=None, offset_id=0, max_id=0, min_id=0, add_offset=0
))
return getattr(result, 'count', len(result.messages)), [], []
total_messages = 0
messages = []
entities = {}
while len(messages) < limit:
# Telegram has a hard limit of 100
real_limit = min(limit - len(messages), 100)
result = 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
))
# The result may be a messages slice (not all messages were retrieved)
# or simply a messages TLObject. In the later case, no "count"
# attribute is specified, so the total messages count is simply
# the count of retrieved messages
messages.extend(
m for m in result.messages if not isinstance(m, MessageEmpty)
)
total_messages = getattr(result, 'count', len(result.messages))
# Iterate over all the messages and find the sender User
entities = [
utils.find_user_or_chat(m.from_id, result.users, result.chats)
if m.from_id is not None else
utils.find_user_or_chat(m.to_id, result.users, result.chats)
# TODO We can potentially use self.session.database, but since
# it might be disabled, use a local dictionary.
for u in result.users:
entities[utils.get_peer_id(u, add_mark=True)] = u
for c in result.chats:
entities[utils.get_peer_id(c, add_mark=True)] = c
for m in result.messages
]
if len(result.messages) < real_limit:
break
return total_messages, result.messages, entities
offset_id = result.messages[-1].id
offset_date = result.messages[-1].date
# Telegram limit seems to be 3000 messages within 30 seconds in
# batches of 100 messages each request (since the FloodWait was
# of 30 seconds). If the limit is greater than that, we will
# sleep 1s between each request.
if limit > 3000:
await asyncio.sleep(1, loop=self._loop)
# In a new list with the same length as the messages append
# their senders, so people can zip(messages, senders).
senders = []
for m in messages:
if m.from_id:
who = entities[utils.get_peer_id(m.from_id, add_mark=True)]
elif getattr(m, 'fwd_from', None):
# .from_id is optional, so this is the sanest fallback.
who = entities[utils.get_peer_id(
m.fwd_from.from_id or PeerChannel(m.fwd_from.channel_id),
add_mark=True
)]
else:
# If there's not even a FwdHeader, fallback to the sender
# being where the message was sent.
who = entities[utils.get_peer_id(m.to_id, add_mark=True)]
senders.append(who)
return total_messages, messages, senders
async def send_read_acknowledge(self, entity, messages=None, max_id=None):
"""
@ -938,7 +1034,17 @@ class TelegramClient(TelegramBareClient):
entity = phone
await self(GetContactsRequest(0))
else:
entity = string.strip('@').lower()
entity, is_join_chat = EntityDatabase.parse_username(string)
if is_join_chat:
invite = await self(CheckChatInviteRequest(entity))
if isinstance(invite, ChatInvite):
# If it's an invite to a chat, the user must join before
# for the link to be resolved and work, otherwise raise.
if invite.channel:
return invite.channel
elif isinstance(invite, ChatInviteAlready):
return invite.chat
else:
await self(ResolveUsernameRequest(entity))
# MtProtoSender will call .process_entities on the requests made

View File

@ -8,6 +8,11 @@ from ..tl.types import (
from .. import utils # Keep this line the last to maybe fix #357
USERNAME_RE = re.compile(
r'@|(?:https?://)?(?:telegram\.(?:me|dog)|t\.me)/(joinchat/)?'
)
class EntityDatabase:
def __init__(self, input_list=None, enabled=True, enabled_full=True):
"""Creates a new entity database with an initial load of "Input"
@ -21,7 +26,12 @@ class EntityDatabase:
self._entities = {} # marked_id: user|chat|channel
if input_list:
self._input_entities = {k: v for k, v in input_list}
# TODO For compatibility reasons some sessions were saved with
# 'access_hash': null in the JSON session file. Drop these, as
# it means we don't have access to such InputPeers. Issue #354.
self._input_entities = {
k: v for k, v in input_list if v is not None
}
else:
self._input_entities = {} # marked_id: hash
@ -66,10 +76,22 @@ class EntityDatabase:
try:
p = utils.get_input_peer(e, allow_self=False)
new_input[utils.get_peer_id(p, add_mark=True)] = \
getattr(p, 'access_hash', 0) # chats won't have hash
marked_id = utils.get_peer_id(p, add_mark=True)
if self.enabled_full:
has_hash = False
if isinstance(p, InputPeerChat):
# Chats don't have a hash
new_input[marked_id] = 0
has_hash = True
elif p.access_hash:
# Some users and channels seem to be returned without
# an 'access_hash', meaning Telegram doesn't want you
# to access them. This is the reason behind ensuring
# that the 'access_hash' is non-zero. See issue #354.
new_input[marked_id] = p.access_hash
has_hash = True
if self.enabled_full and has_hash:
if isinstance(e, (User, Chat, Channel)):
new.append(e)
except ValueError:
@ -114,7 +136,7 @@ class EntityDatabase:
phone = getattr(entity, 'phone', None)
if phone:
self._username_id[phone] = marked_id
self._phone_id[phone] = marked_id
def _parse_key(self, key):
"""Parses the given string, integer or TLObject key into a
@ -132,7 +154,8 @@ class EntityDatabase:
if phone:
return self._phone_id[phone]
else:
return self._username_id[key.lstrip('@').lower()]
username, _ = EntityDatabase.parse_username(key)
return self._username_id[username.lower()]
except KeyError as e:
raise ValueError() from e
@ -185,6 +208,19 @@ class EntityDatabase:
if phone.isdigit():
return phone
@staticmethod
def parse_username(username):
"""Parses the given username or channel access hash, given
a string, username or URL. Returns a tuple consisting of
both the stripped username and whether it is a joinchat/ hash.
"""
username = username.strip()
m = USERNAME_RE.match(username)
if m:
return username[m.end():], bool(m.group(1))
else:
return username, False
def get_input_entity(self, peer):
try:
i = utils.get_peer_id(peer, add_mark=True)

View File

@ -19,7 +19,7 @@ from .tl.types import (
DocumentEmpty, InputDocumentEmpty, Message, GeoPoint, InputGeoPoint,
GeoPointEmpty, InputGeoPointEmpty, Photo, InputPhoto, PhotoEmpty,
InputPhotoEmpty, FileLocation, ChatPhotoEmpty, UserProfilePhotoEmpty,
FileLocationUnavailable, InputMediaUploadedDocument,
FileLocationUnavailable, InputMediaUploadedDocument, ChannelFull,
InputMediaUploadedPhoto, DocumentAttributeFilename, photos
)
@ -84,13 +84,13 @@ def get_input_peer(entity, allow_self=True):
if entity.is_self and allow_self:
return InputPeerSelf()
else:
return InputPeerUser(entity.id, entity.access_hash)
return InputPeerUser(entity.id, entity.access_hash or 0)
if isinstance(entity, (Chat, ChatEmpty, ChatForbidden)):
return InputPeerChat(entity.id)
if isinstance(entity, (Channel, ChannelForbidden)):
return InputPeerChannel(entity.id, entity.access_hash)
return InputPeerChannel(entity.id, entity.access_hash or 0)
# Less common cases
if isinstance(entity, UserEmpty):
@ -99,6 +99,9 @@ def get_input_peer(entity, allow_self=True):
if isinstance(entity, InputUser):
return InputPeerUser(entity.user_id, entity.access_hash)
if isinstance(entity, InputUserSelf):
return InputPeerSelf()
if isinstance(entity, UserFull):
return get_input_peer(entity.user)
@ -120,7 +123,7 @@ def get_input_channel(entity):
return entity
if isinstance(entity, (Channel, ChannelForbidden)):
return InputChannel(entity.id, entity.access_hash)
return InputChannel(entity.id, entity.access_hash or 0)
if isinstance(entity, InputPeerChannel):
return InputChannel(entity.channel_id, entity.access_hash)
@ -140,7 +143,7 @@ def get_input_user(entity):
if entity.is_self:
return InputUserSelf()
else:
return InputUser(entity.id, entity.access_hash)
return InputUser(entity.id, entity.access_hash or 0)
if isinstance(entity, InputPeerSelf):
return InputUserSelf()
@ -322,7 +325,12 @@ def get_peer_id(peer, add_mark=False):
return peer.user_id
elif isinstance(peer, (PeerChat, InputPeerChat)):
return -peer.chat_id if add_mark else peer.chat_id
elif isinstance(peer, (PeerChannel, InputPeerChannel)):
elif isinstance(peer, (PeerChannel, InputPeerChannel, ChannelFull)):
if isinstance(peer, ChannelFull):
# Special case: .get_input_peer can't return InputChannel from
# ChannelFull since it doesn't have an .access_hash attribute.
i = peer.id
else:
i = peer.channel_id
if add_mark:
# Concat -100 through math tricks, .to_supergroup() on Madeline

3
telethon/version.py Normal file
View File

@ -0,0 +1,3 @@
# Versions should comply with PEP440.
# This line is parsed in setup.py:
__version__ = '0.15.4'

View File

@ -354,7 +354,7 @@ class InteractiveTelegramClient(TelegramClient):
update.message, get_display_name(who)
))
else:
sprint('<< {} sent "{}"]'.format(
sprint('<< {} sent "{}"'.format(
get_display_name(who), update.message
))

View File

@ -45,7 +45,7 @@ PHONE_NUMBER_OCCUPIED=The phone number is already in use
PHONE_NUMBER_UNOCCUPIED=The phone number is not yet being used
PHOTO_INVALID_DIMENSIONS=The photo dimensions are invalid
TYPE_CONSTRUCTOR_INVALID=The type constructor is invalid
USERNAME_INVALID=Unacceptable username. Must match r"[a-zA-Z][\w\d]{3,30}[a-zA-Z\d]"
USERNAME_INVALID=Nobody is using this username, or the username is unacceptable. If the latter, it must match r"[a-zA-Z][\w\d]{3,30}[a-zA-Z\d]"
USERNAME_NOT_MODIFIED=The username is not different from the current username
USERNAME_NOT_OCCUPIED=The username is not in use by anyone else yet
USERNAME_OCCUPIED=The username is already taken

View File

@ -159,14 +159,17 @@ inputMediaUploadedPhoto#2f37e231 flags:# file:InputFile caption:string stickers:
inputMediaPhoto#81fa373a flags:# id:InputPhoto caption:string ttl_seconds:flags.0?int = InputMedia;
inputMediaGeoPoint#f9c44144 geo_point:InputGeoPoint = InputMedia;
inputMediaContact#a6e45987 phone_number:string first_name:string last_name:string = InputMedia;
inputMediaUploadedDocument#e39621fd flags:# file:InputFile thumb:flags.2?InputFile mime_type:string attributes:Vector<DocumentAttribute> caption:string stickers:flags.0?Vector<InputDocument> ttl_seconds:flags.1?int = InputMedia;
inputMediaUploadedDocument#e39621fd flags:# nosound_video:flags.3?true file:InputFile thumb:flags.2?InputFile mime_type:string attributes:Vector<DocumentAttribute> caption:string stickers:flags.0?Vector<InputDocument> ttl_seconds:flags.1?int = InputMedia;
inputMediaDocument#5acb668e flags:# id:InputDocument caption:string ttl_seconds:flags.0?int = InputMedia;
inputMediaVenue#2827a81a geo_point:InputGeoPoint title:string address:string provider:string venue_id:string = InputMedia;
inputMediaVenue#c13d1c11 geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string = InputMedia;
inputMediaGifExternal#4843b0fd url:string q:string = InputMedia;
inputMediaPhotoExternal#922aec1 flags:# url:string caption:string ttl_seconds:flags.0?int = InputMedia;
inputMediaDocumentExternal#b6f74335 flags:# url:string caption:string ttl_seconds:flags.0?int = InputMedia;
inputMediaGame#d33f43f3 id:InputGame = InputMedia;
inputMediaInvoice#92153685 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string start_param:string = InputMedia;
inputMediaGeoLive#7b1a118f geo_point:InputGeoPoint period:int = InputMedia;
inputSingleMedia#5eaa7809 media:InputMedia random_id:long = InputSingleMedia;
inputChatPhotoEmpty#1ca48f57 = InputChatPhoto;
inputChatUploadedPhoto#927c55b4 file:InputFile = InputChatPhoto;
@ -218,11 +221,11 @@ userStatusLastMonth#77ebc742 = UserStatus;
chatEmpty#9ba2d800 id:int = Chat;
chat#d91cdd54 flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true admins_enabled:flags.3?true admin:flags.4?true deactivated:flags.5?true id:int title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel = Chat;
chatForbidden#7328bdb id:int title:string = Chat;
channel#cb44b1c flags:# creator:flags.0?true left:flags.2?true editor:flags.3?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true democracy:flags.10?true signatures:flags.11?true min:flags.12?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?string admin_rights:flags.14?ChannelAdminRights banned_rights:flags.15?ChannelBannedRights = Chat;
channel#450b7115 flags:# creator:flags.0?true left:flags.2?true editor:flags.3?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true democracy:flags.10?true signatures:flags.11?true min:flags.12?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?string admin_rights:flags.14?ChannelAdminRights banned_rights:flags.15?ChannelBannedRights participants_count:flags.17?int = Chat;
channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat;
chatFull#2e02a614 id:int participants:ChatParticipants chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector<BotInfo> = ChatFull;
channelFull#17f45fcf flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet = ChatFull;
channelFull#76af5481 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int = ChatFull;
chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant;
chatParticipantCreator#da13538a user_id:int = ChatParticipant;
@ -235,7 +238,7 @@ chatPhotoEmpty#37c1011c = ChatPhoto;
chatPhoto#6153276a photo_small:FileLocation photo_big:FileLocation = ChatPhoto;
messageEmpty#83e5de54 id:int = Message;
message#90dddc11 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true id:int from_id:flags.8?int to_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int edit_date:flags.15?int post_author:flags.16?string = Message;
message#44f9b43d flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true id:int from_id:flags.8?int to_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long = Message;
messageService#9e19a1f6 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true id:int from_id:flags.8?int to_id:Peer reply_to_msg_id:flags.3?int date:int action:MessageAction = Message;
messageMediaEmpty#3ded6320 = MessageMedia;
@ -245,9 +248,10 @@ messageMediaContact#5e7d2f39 phone_number:string first_name:string last_name:str
messageMediaUnsupported#9f84f49e = MessageMedia;
messageMediaDocument#7c4414d3 flags:# document:flags.0?Document caption:flags.1?string ttl_seconds:flags.2?int = MessageMedia;
messageMediaWebPage#a32dd600 webpage:WebPage = MessageMedia;
messageMediaVenue#7912b71f geo:GeoPoint title:string address:string provider:string venue_id:string = MessageMedia;
messageMediaVenue#2ec0533f geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string = MessageMedia;
messageMediaGame#fdb19008 game:Game = MessageMedia;
messageMediaInvoice#84551347 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string = MessageMedia;
messageMediaGeoLive#7c3c2609 geo:GeoPoint period:int = MessageMedia;
messageActionEmpty#b6aef7b0 = MessageAction;
messageActionChatCreate#a6638b9a title:string users:Vector<int> = MessageAction;
@ -267,6 +271,7 @@ messageActionPaymentSentMe#8f31b327 flags:# currency:string total_amount:long pa
messageActionPaymentSent#40699cd0 currency:string total_amount:long = MessageAction;
messageActionPhoneCall#80e11a7f flags:# call_id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = MessageAction;
messageActionScreenshotTaken#4792929b = MessageAction;
messageActionCustomAction#fae69f56 message:string = MessageAction;
dialog#e4def5db flags:# pinned:flags.2?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage = Dialog;
@ -363,6 +368,8 @@ inputMessagesFilterPhoneCalls#80c99768 flags:# missed:flags.0?true = MessagesFil
inputMessagesFilterRoundVoice#7a7c17a4 = MessagesFilter;
inputMessagesFilterRoundVideo#b549da53 = MessagesFilter;
inputMessagesFilterMyMentions#c1f8e69a = MessagesFilter;
inputMessagesFilterContacts#e062db83 = MessagesFilter;
inputMessagesFilterGeo#e7026d0d = MessagesFilter;
updateNewMessage#1f2b0afd message:Message pts:int pts_count:int = Update;
updateMessageID#4e90bfd6 id:int random_id:long = Update;
@ -429,6 +436,7 @@ updateLangPack#56022f4d difference:LangPackDifference = Update;
updateFavedStickers#e511996d = Update;
updateChannelReadMessagesContents#89893b45 channel_id:int messages:Vector<int> = Update;
updateContactsReset#7084a7be = Update;
updateChannelAvailableMessages#70db6837 channel_id:int available_min_id:int = Update;
updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
@ -455,7 +463,7 @@ upload.fileCdnRedirect#ea52fe5a dc_id:int file_token:bytes encryption_key:bytes
dcOption#5d8c6cc flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true cdn:flags.3?true static:flags.4?true id:int ip_address:string port:int = DcOption;
config#8df376a4 flags:# phonecalls_enabled:flags.1?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector<DcOption> chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int chat_big_size:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string suggested_lang_code:flags.2?string lang_pack_version:flags.2?int disabled_features:Vector<DisabledFeature> = Config;
config#9c840964 flags:# phonecalls_enabled:flags.1?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector<DcOption> chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int chat_big_size:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string suggested_lang_code:flags.2?string lang_pack_version:flags.2?int disabled_features:Vector<DisabledFeature> = Config;
nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc;
@ -665,6 +673,7 @@ channelParticipantsBanned#1427a5e1 q:string = ChannelParticipantsFilter;
channelParticipantsSearch#656ac4b q:string = ChannelParticipantsFilter;
channels.channelParticipants#f56ee2a8 count:int participants:Vector<ChannelParticipant> users:Vector<User> = channels.ChannelParticipants;
channels.channelParticipantsNotModified#f0173fe9 = channels.ChannelParticipants;
channels.channelParticipant#d0d9b163 participant:ChannelParticipant users:Vector<User> = channels.ChannelParticipant;
@ -680,7 +689,7 @@ messages.savedGifs#2e0709a5 hash:int gifs:Vector<Document> = messages.SavedGifs;
inputBotInlineMessageMediaAuto#292fed13 flags:# caption:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
inputBotInlineMessageText#3dcd7a87 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
inputBotInlineMessageMediaGeo#f4a59de1 flags:# geo_point:InputGeoPoint reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
inputBotInlineMessageMediaGeo#c1b15d65 flags:# geo_point:InputGeoPoint period:int reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
inputBotInlineMessageMediaVenue#aaafadc8 flags:# geo_point:InputGeoPoint title:string address:string provider:string venue_id:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
inputBotInlineMessageMediaContact#2daf01a7 flags:# phone_number:string first_name:string last_name:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
inputBotInlineMessageGame#4b425864 flags:# reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
@ -692,18 +701,18 @@ inputBotInlineResultGame#4fa417f2 id:string short_name:string send_message:Input
botInlineMessageMediaAuto#a74b15b flags:# caption:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
botInlineMessageText#8c7f65e2 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
botInlineMessageMediaGeo#3a8fd8b8 flags:# geo:GeoPoint reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
botInlineMessageMediaGeo#b722de65 flags:# geo:GeoPoint period:int reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
botInlineMessageMediaVenue#4366232e flags:# geo:GeoPoint title:string address:string provider:string venue_id:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
botInlineMessageMediaContact#35edb4d4 flags:# phone_number:string first_name:string last_name:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
botInlineResult#9bebaeb9 flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb_url:flags.4?string content_url:flags.5?string content_type:flags.5?string w:flags.6?int h:flags.6?int duration:flags.7?int send_message:BotInlineMessage = BotInlineResult;
botInlineMediaResult#17db940b flags:# id:string type:string photo:flags.0?Photo document:flags.1?Document title:flags.2?string description:flags.3?string send_message:BotInlineMessage = BotInlineResult;
messages.botResults#ccd3563d flags:# gallery:flags.0?true query_id:long next_offset:flags.1?string switch_pm:flags.2?InlineBotSwitchPM results:Vector<BotInlineResult> cache_time:int = messages.BotResults;
messages.botResults#947ca848 flags:# gallery:flags.0?true query_id:long next_offset:flags.1?string switch_pm:flags.2?InlineBotSwitchPM results:Vector<BotInlineResult> cache_time:int users:Vector<User> = messages.BotResults;
exportedMessageLink#1f486803 link:string = ExportedMessageLink;
messageFwdHeader#fadff4ac flags:# from_id:flags.0?int date:int channel_id:flags.1?int channel_post:flags.2?int post_author:flags.3?string = MessageFwdHeader;
messageFwdHeader#559ebe6d flags:# from_id:flags.0?int date:int channel_id:flags.1?int channel_post:flags.2?int post_author:flags.3?string saved_from_peer:flags.4?Peer saved_from_msg_id:flags.4?int = MessageFwdHeader;
auth.codeTypeSms#72a3158c = auth.CodeType;
auth.codeTypeCall#741cd3e3 = auth.CodeType;
@ -903,6 +912,7 @@ channelAdminLogEventActionParticipantInvite#e31c34d8 participant:ChannelParticip
channelAdminLogEventActionParticipantToggleBan#e6d83d7e prev_participant:ChannelParticipant new_participant:ChannelParticipant = ChannelAdminLogEventAction;
channelAdminLogEventActionParticipantToggleAdmin#d5676710 prev_participant:ChannelParticipant new_participant:ChannelParticipant = ChannelAdminLogEventAction;
channelAdminLogEventActionChangeStickerSet#b1c3caa7 prev_stickerset:InputStickerSet new_stickerset:InputStickerSet = ChannelAdminLogEventAction;
channelAdminLogEventActionTogglePreHistoryHidden#5f5c95f1 new_value:Bool = ChannelAdminLogEventAction;
channelAdminLogEvent#3b5a3e40 id:long date:int user_id:int action:ChannelAdminLogEventAction = ChannelAdminLogEvent;
@ -917,6 +927,14 @@ cdnFileHash#77eec38f offset:int limit:int hash:bytes = CdnFileHash;
messages.favedStickersNotModified#9e8fa6d3 = messages.FavedStickers;
messages.favedStickers#f37f2f16 hash:int packs:Vector<StickerPack> stickers:Vector<Document> = messages.FavedStickers;
help.recentMeUrls#e0310d7 urls:Vector<RecentMeUrl> chats:Vector<Chat> users:Vector<User> = help.RecentMeUrls;
recentMeUrlUser#8dbc3336 url:string user_id:int = RecentMeUrl;
recentMeUrlChat#a01b22f9 url:string chat_id:int = RecentMeUrl;
recentMeUrlStickerSet#bc0a57dc url:string set:StickerSetCovered = RecentMeUrl;
recentMeUrlChatInvite#eb49081d url:string chat_invite:ChatInvite = RecentMeUrl;
recentMeUrlUnknown#46e1d13d url:string = RecentMeUrl;
---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@ -1001,7 +1019,7 @@ messages.receivedMessages#5a954c0 max_id:int = Vector<ReceivedNotifyMessage>;
messages.setTyping#a3825e50 peer:InputPeer action:SendMessageAction = Bool;
messages.sendMessage#fa88427a flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> = Updates;
messages.sendMedia#c8f16791 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia random_id:long reply_markup:flags.2?ReplyMarkup = Updates;
messages.forwardMessages#708e0195 flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true from_peer:InputPeer id:Vector<int> random_id:Vector<long> to_peer:InputPeer = Updates;
messages.forwardMessages#708e0195 flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true grouped:flags.9?true from_peer:InputPeer id:Vector<int> random_id:Vector<long> to_peer:InputPeer = Updates;
messages.reportSpam#cf1592db peer:InputPeer = Bool;
messages.hideReportSpam#a8f1709b peer:InputPeer = Bool;
messages.getPeerSettings#3672e09c peer:InputPeer = PeerSettings;
@ -1048,7 +1066,7 @@ messages.getInlineBotResults#514e999d flags:# bot:InputUser peer:InputPeer geo_p
messages.setInlineBotResults#eb5ea206 flags:# gallery:flags.0?true private:flags.1?true query_id:long results:Vector<InputBotInlineResult> cache_time:int next_offset:flags.2?string switch_pm:flags.3?InlineBotSwitchPM = Bool;
messages.sendInlineBotResult#b16e06fe flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int random_id:long query_id:long id:string = Updates;
messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEditData;
messages.editMessage#ce91e4ca flags:# no_webpage:flags.1?true peer:InputPeer id:int message:flags.11?string reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> = Updates;
messages.editMessage#5d1b8dd flags:# no_webpage:flags.1?true stop_geo_live:flags.12?true peer:InputPeer id:int message:flags.11?string reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> geo_point:flags.13?InputGeoPoint = Updates;
messages.editInlineBotMessage#130c2c85 flags:# no_webpage:flags.1?true id:InputBotInlineMessageID message:flags.11?string reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> = Bool;
messages.getBotCallbackAnswer#810a9fec flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes = messages.BotCallbackAnswer;
messages.setBotCallbackAnswer#d58f130a flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.2?string cache_time:int = Bool;
@ -1080,6 +1098,9 @@ messages.sendScreenshotNotification#c97df020 peer:InputPeer reply_to_msg_id:int
messages.getFavedStickers#21ce0b0e hash:int = messages.FavedStickers;
messages.faveSticker#b9ffc55b id:InputDocument unfave:Bool = Bool;
messages.getUnreadMentions#46578472 peer:InputPeer offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
messages.getRecentLocations#249431e2 peer:InputPeer limit:int = messages.Messages;
messages.readMentions#f0189d3 peer:InputPeer = messages.AffectedHistory;
messages.sendMultiMedia#2095512f flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int multi_media:Vector<InputSingleMedia> = Updates;
updates.getState#edd4882a = updates.State;
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
@ -1108,13 +1129,14 @@ help.getAppChangelog#9010ef6f prev_app_version:string = Updates;
help.getTermsOfService#350170f3 = help.TermsOfService;
help.setBotUpdatesStatus#ec22cfcd pending_updates_count:int message:string = Bool;
help.getCdnConfig#52029342 = CdnConfig;
help.getRecentMeUrls#3dc0f114 referer:string = help.RecentMeUrls;
channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool;
channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector<int> = messages.AffectedMessages;
channels.deleteUserHistory#d10dd71b channel:InputChannel user_id:InputUser = messages.AffectedHistory;
channels.reportSpam#fe087810 channel:InputChannel user_id:InputUser id:Vector<int> = Bool;
channels.getMessages#93d7b347 channel:InputChannel id:Vector<int> = messages.Messages;
channels.getParticipants#24d98f92 channel:InputChannel filter:ChannelParticipantsFilter offset:int limit:int = channels.ChannelParticipants;
channels.getParticipants#123e05e9 channel:InputChannel filter:ChannelParticipantsFilter offset:int limit:int hash:int = channels.ChannelParticipants;
channels.getParticipant#546dd7a6 channel:InputChannel user_id:InputUser = channels.ChannelParticipant;
channels.getChannels#a7f6bbb id:Vector<InputChannel> = messages.Chats;
channels.getFullChannel#8736a09 channel:InputChannel = messages.ChatFull;
@ -1139,6 +1161,8 @@ channels.editBanned#bfd915cd channel:InputChannel user_id:InputUser banned_right
channels.getAdminLog#33ddf480 flags:# channel:InputChannel q:string events_filter:flags.0?ChannelAdminLogEventsFilter admins:flags.1?Vector<InputUser> max_id:long min_id:long limit:int = channels.AdminLogResults;
channels.setStickers#ea8ca4f9 channel:InputChannel stickerset:InputStickerSet = Bool;
channels.readMessageContents#eab5dc38 channel:InputChannel id:Vector<int> = Bool;
channels.deleteHistory#af369d42 channel:InputChannel max_id:int = Bool;
channels.togglePreHistoryHidden#eabbb94c channel:InputChannel enabled:Bool = Updates;
bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON;
bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool;
@ -1169,4 +1193,4 @@ langpack.getStrings#2e1ee318 lang_code:string keys:Vector<string> = Vector<LangP
langpack.getDifference#b2e4d7d from_version:int = LangPackDifference;
langpack.getLanguages#800fd57d = Vector<LangPackLanguage>;
// LAYER 71
// LAYER 73