mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-07-22 13:59:46 +03:00
Merge branch 'master' into asyncio
This commit is contained in:
commit
7f5126c341
19
setup.py
19
setup.py
|
@ -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,
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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:
|
||||
y, c, m = randint(1, pq - 1), randint(1, pq - 1), randint(1, pq - 1)
|
||||
g = r = q = 1
|
||||
x = ys = 0
|
||||
|
||||
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
|
||||
|
||||
if (j & (j - 1)) == 0:
|
||||
y = x
|
||||
|
||||
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
|
||||
|
|
123
telethon/extensions/markdown.py
Normal file
123
telethon/extensions/markdown.py
Normal 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
|
|
@ -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,18 +439,13 @@ 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.
|
||||
if self._user_connected:
|
||||
# Server disconnected us, __call__ will try reconnecting.
|
||||
return None
|
||||
else:
|
||||
# User never called .connect(), so raise this error.
|
||||
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)
|
||||
return None
|
||||
|
||||
if init_connection:
|
||||
# We initialized the connection successfully, even if
|
||||
# a request had an RPC error we have invoked it fine.
|
||||
|
@ -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
|
||||
|
|
|
@ -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
|
||||
result = await self(SendCodeRequest(phone, self.api_id, self.api_hash))
|
||||
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_code_hash = result.phone_code_hash
|
||||
|
||||
self._phone = phone
|
||||
self._phone_code_hash = result.phone_code_hash
|
||||
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!
|
||||
"""
|
||||
result = await self(GetHistoryRequest(
|
||||
peer=await self.get_input_entity(entity),
|
||||
limit=limit,
|
||||
offset_date=offset_date,
|
||||
offset_id=offset_id,
|
||||
max_id=max_id,
|
||||
min_id=min_id,
|
||||
add_offset=add_offset
|
||||
))
|
||||
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=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)), [], []
|
||||
|
||||
# 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
|
||||
total_messages = 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
|
||||
))
|
||||
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,8 +1034,18 @@ class TelegramClient(TelegramBareClient):
|
|||
entity = phone
|
||||
await self(GetContactsRequest(0))
|
||||
else:
|
||||
entity = string.strip('@').lower()
|
||||
await self(ResolveUsernameRequest(entity))
|
||||
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
|
||||
|
||||
try:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,8 +325,13 @@ 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)):
|
||||
i = peer.channel_id
|
||||
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
|
||||
# IDs will be strictly positive -> log works
|
||||
|
|
3
telethon/version.py
Normal file
3
telethon/version.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Versions should comply with PEP440.
|
||||
# This line is parsed in setup.py:
|
||||
__version__ = '0.15.4'
|
|
@ -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
|
||||
))
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user