From 807a50b82cbbef974883e2f2e122ad67a48b09f4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 31 Jan 2018 21:03:26 +0100 Subject: [PATCH 01/21] Pass the client to the update handler as parameter --- pyrogram/client/client.py | 2 +- pyrogram/session/session.py | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 5fd75cc0..dd037bcc 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -170,7 +170,7 @@ class Client: self.rnd_id = self.session.msg_id self.get_dialogs() - self.session.update_handler = self.update_handler + self.session.set_update_handler(self, self.update_handler) mimetypes.init() diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 23d686a4..9fb109ab 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -26,6 +26,7 @@ from os import urandom from queue import Queue from threading import Event, Thread +import pyrogram from pyrogram import __copyright__, __license__, __version__ from pyrogram.api import functions, types, core from pyrogram.api.all import layer @@ -109,6 +110,7 @@ class Session: self.is_connected = Event() + self.client = None self.update_handler = None self.total_connections = 0 @@ -247,6 +249,10 @@ class Session: log.debug("{} stopped".format(name)) + def set_update_handler(self, client: pyrogram, update_handler: callable): + self.client = client + self.update_handler = update_handler + def unpack_dispatch_and_ack(self, packet: bytes): # TODO: A better dispatcher data = self.unpack(BytesIO(packet)) @@ -288,7 +294,7 @@ class Session: msg_id = i.body.msg_id else: if self.update_handler: - self.update_handler(i.body) + self.update_handler(self.client, i.body) if msg_id in self.results: self.results[msg_id].value = getattr(i.body, "result", i.body) From 2a0a3881a4261cd5e5cde28a59bb34830a624b62 Mon Sep 17 00:00:00 2001 From: 1pyxa1 <1pyxa1@gmail.com> Date: Wed, 7 Feb 2018 16:05:35 +0300 Subject: [PATCH 02/21] Add ABOUT_TOO_LONG error (#17) --- compiler/error/source/400_BAD_REQUEST.tsv | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index 0b92aba5..1c6a155a 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -44,3 +44,4 @@ FILE_ID_INVALID The file id is invalid LOCATION_INVALID The file location is invalid CHAT_ADMIN_REQUIRED The method requires admin privileges PHONE_NUMBER_BANNED The phone number is banned +ABOUT_TOO_LONG The about text is too long From 49ad245edc3d8cc911b9a66fa4e9ed2fff547171 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 7 Feb 2018 14:46:53 +0100 Subject: [PATCH 03/21] Add missing notice --- pyrogram/client/input_media.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/pyrogram/client/input_media.py b/pyrogram/client/input_media.py index d7612121..74da22b8 100644 --- a/pyrogram/client/input_media.py +++ b/pyrogram/client/input_media.py @@ -1,3 +1,22 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + + class InputMedia: class Photo: """This object represents a photo to be sent inside an album. From 5927e3ae8b62564f019221bcd32fecb49faafcfa Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 7 Feb 2018 19:35:52 +0100 Subject: [PATCH 04/21] Add MULTI_MEDIA_TOO_LONG error --- compiler/error/source/400_BAD_REQUEST.tsv | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index 1c6a155a..762424b9 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -45,3 +45,4 @@ LOCATION_INVALID The file location is invalid CHAT_ADMIN_REQUIRED The method requires admin privileges PHONE_NUMBER_BANNED The phone number is banned ABOUT_TOO_LONG The about text is too long +MULTI_MEDIA_TOO_LONG The album contains more than 10 items From ed449ece9b942a2cee054318e2eb6e4a122c882e Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 8 Feb 2018 18:17:23 +0300 Subject: [PATCH 05/21] Add filename attribute to video files in albums (#19) --- pyrogram/client/client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 37570786..b1fa0cb5 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -2016,7 +2016,8 @@ class Client: duration=i.duration, w=i.width, h=i.height - ) + ), + types.DocumentAttributeFilename(os.path.basename(i.media)) ] ) ) From a2d435b25f7603c53085c7082f64fd5597fe1152 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 8 Feb 2018 16:20:19 +0100 Subject: [PATCH 06/21] Add filename attribute for single videos --- pyrogram/client/client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index b1fa0cb5..cb1e52df 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -985,7 +985,8 @@ class Client: duration=duration, w=width, h=height - ) + ), + types.DocumentAttributeFilename(os.path.basename(video)) ] ), silent=disable_notification or None, From 3097cadd8a042a1943797214bb0e30a2ac90a53e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 8 Feb 2018 17:23:37 +0100 Subject: [PATCH 07/21] Clean --- pyrogram/session/session.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 9fb109ab..8f5798bd 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -254,7 +254,6 @@ class Session: self.update_handler = update_handler def unpack_dispatch_and_ack(self, packet: bytes): - # TODO: A better dispatcher data = self.unpack(BytesIO(packet)) messages = ( @@ -265,9 +264,6 @@ class Session: log.debug(data) - self.total_bytes += len(packet) - self.total_messages += len(messages) - for i in messages: if i.seq_no % 2 != 0: if i.msg_id in self.pending_acks: @@ -275,8 +271,6 @@ class Session: else: self.pending_acks.add(i.msg_id) - # log.debug("{}".format(type(i.body))) - if isinstance(i.body, (types.MsgDetailedInfo, types.MsgNewDetailedInfo)): self.pending_acks.add(i.body.answer_msg_id) continue @@ -300,14 +294,6 @@ class Session: self.results[msg_id].value = getattr(i.body, "result", i.body) self.results[msg_id].event.set() - # print( - # "This packet bytes: ({}) | Total bytes: ({})\n" - # "This packet messages: ({}) | Total messages: ({})\n" - # "Total connections: ({})".format( - # len(packet), self.total_bytes, len(messages), self.total_messages, self.total_connections - # ) - # ) - if len(self.pending_acks) >= self.ACKS_THRESHOLD: log.info("Send {} acks".format(len(self.pending_acks))) From 00631fcc332bc40c8cd92c2df55348e8a2be3916 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 8 Feb 2018 17:34:00 +0100 Subject: [PATCH 08/21] Use a better variable name --- pyrogram/session/session.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 8f5798bd..b117a3fc 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -264,34 +264,34 @@ class Session: log.debug(data) - for i in messages: - if i.seq_no % 2 != 0: - if i.msg_id in self.pending_acks: + for msg in messages: + if msg.seq_no % 2 != 0: + if msg.msg_id in self.pending_acks: continue else: - self.pending_acks.add(i.msg_id) + self.pending_acks.add(msg.msg_id) - if isinstance(i.body, (types.MsgDetailedInfo, types.MsgNewDetailedInfo)): - self.pending_acks.add(i.body.answer_msg_id) + if isinstance(msg.body, (types.MsgDetailedInfo, types.MsgNewDetailedInfo)): + self.pending_acks.add(msg.body.answer_msg_id) continue - if isinstance(i.body, types.NewSessionCreated): + if isinstance(msg.body, types.NewSessionCreated): continue msg_id = None - if isinstance(i.body, (types.BadMsgNotification, types.BadServerSalt)): - msg_id = i.body.bad_msg_id - elif isinstance(i.body, (core.FutureSalts, types.RpcResult)): - msg_id = i.body.req_msg_id - elif isinstance(i.body, types.Pong): - msg_id = i.body.msg_id + if isinstance(msg.body, (types.BadMsgNotification, types.BadServerSalt)): + msg_id = msg.body.bad_msg_id + elif isinstance(msg.body, (core.FutureSalts, types.RpcResult)): + msg_id = msg.body.req_msg_id + elif isinstance(msg.body, types.Pong): + msg_id = msg.body.msg_id else: if self.update_handler: - self.update_handler(self.client, i.body) + self.update_handler(self.client, msg.body) if msg_id in self.results: - self.results[msg_id].value = getattr(i.body, "result", i.body) + self.results[msg_id].value = getattr(msg.body, "result", msg.body) self.results[msg_id].event.set() if len(self.pending_acks) >= self.ACKS_THRESHOLD: From f8688cd2607b7d29a812843e38725c10513c1dfa Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 8 Feb 2018 18:56:40 +0100 Subject: [PATCH 09/21] Rename worker to net_worker --- pyrogram/client/client.py | 4 ++-- pyrogram/session/session.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index cb1e52df..d61e37d6 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -157,7 +157,7 @@ class Client: self.proxy, self.auth_key, self.config.api_id, - workers=self.workers + net_workers=self.workers ) terms = self.session.start() @@ -263,7 +263,7 @@ class Client: self.proxy, self.auth_key, self.config.api_id, - workers=self.workers + net_workers=self.workers ) self.session.start() diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index b117a3fc..ffd768e5 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -75,14 +75,14 @@ class Session: auth_key: bytes, api_id: str, is_cdn: bool = False, - workers: int = 2): + net_workers: int = 2): if not Session.notice_displayed: print("Pyrogram v{}, {}".format(__version__, __copyright__)) print("Licensed under the terms of the " + __license__, end="\n\n") Session.notice_displayed = True self.is_cdn = is_cdn - self.workers = workers + self.net_workers = net_workers self.connection = Connection(DataCenter(dc_id, test_mode), proxy) @@ -124,8 +124,8 @@ class Session: try: self.connection.connect() - for i in range(self.workers): - Thread(target=self.worker, name="Worker#{}".format(i + 1)).start() + for i in range(self.net_workers): + Thread(target=self.net_worker, name="NetWorker#{}".format(i + 1)).start() Thread(target=self.recv, name="RecvThread").start() @@ -184,7 +184,7 @@ class Session: self.connection.close() - for i in range(self.workers): + for i in range(self.net_workers): self.recv_queue.put(None) log.debug("Session stopped") @@ -232,7 +232,7 @@ class Session: return message - def worker(self): + def net_worker(self): name = threading.current_thread().name log.debug("{} started".format(name)) From f36bb2678c31dd6f4e00531406bd8ba54d368340 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 8 Feb 2018 19:02:25 +0100 Subject: [PATCH 10/21] Remove unused --- pyrogram/session/session.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index ffd768e5..6a57d5db 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -113,10 +113,6 @@ class Session: self.client = None self.update_handler = None - self.total_connections = 0 - self.total_messages = 0 - self.total_bytes = 0 - def start(self): terms = None @@ -161,7 +157,6 @@ class Session: break self.is_connected.set() - self.total_connections += 1 log.debug("Session started") From 5885f382454d008ee6b265207c74292a7f504db5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 8 Feb 2018 19:03:44 +0100 Subject: [PATCH 11/21] Remove TODO --- pyrogram/session/session.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 6a57d5db..a57d1717 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -190,10 +190,6 @@ class Session: def pack(self, message: Message): data = Long(self.current_salt.salt) + self.session_id + message.write() - # MTProto 2.0 requires a minimum of 12 padding bytes. - # I don't get why it says up to 1024 when what it actually needs after the - # required 12 bytes is just extra 0..15 padding bytes for aes - # TODO: It works, but recheck this. What's the meaning of 12..1024 padding bytes? padding = urandom(-(len(data) + 12) % 16 + 12) # 88 = 88 + 0 (outgoing message) From 7cee6b079fafb725e36a2431e468af85cebe85fe Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 8 Feb 2018 19:48:01 +0100 Subject: [PATCH 12/21] Revert to fixed NET_WORKERS count --- pyrogram/client/client.py | 6 ++---- pyrogram/session/session.py | 14 +++++--------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index d61e37d6..7c17701f 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -156,8 +156,7 @@ class Client: self.test_mode, self.proxy, self.auth_key, - self.config.api_id, - net_workers=self.workers + self.config.api_id ) terms = self.session.start() @@ -262,8 +261,7 @@ class Client: self.test_mode, self.proxy, self.auth_key, - self.config.api_id, - net_workers=self.workers + self.config.api_id ) self.session.start() diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index a57d1717..db03f740 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -60,7 +60,7 @@ class Session: ) INITIAL_SALT = 0x616e67656c696361 - + NET_WORKERS = 4 WAIT_TIMEOUT = 10 MAX_RETRIES = 5 ACKS_THRESHOLD = 8 @@ -74,19 +74,15 @@ class Session: proxy: type, auth_key: bytes, api_id: str, - is_cdn: bool = False, - net_workers: int = 2): + is_cdn: bool = False): if not Session.notice_displayed: print("Pyrogram v{}, {}".format(__version__, __copyright__)) print("Licensed under the terms of the " + __license__, end="\n\n") Session.notice_displayed = True - self.is_cdn = is_cdn - self.net_workers = net_workers - self.connection = Connection(DataCenter(dc_id, test_mode), proxy) - self.api_id = api_id + self.is_cdn = is_cdn self.auth_key = auth_key self.auth_key_id = sha1(auth_key).digest()[-8:] @@ -120,7 +116,7 @@ class Session: try: self.connection.connect() - for i in range(self.net_workers): + for i in range(self.NET_WORKERS): Thread(target=self.net_worker, name="NetWorker#{}".format(i + 1)).start() Thread(target=self.recv, name="RecvThread").start() @@ -179,7 +175,7 @@ class Session: self.connection.close() - for i in range(self.net_workers): + for i in range(self.NET_WORKERS): self.recv_queue.put(None) log.debug("Session stopped") From d8edfb38bf8ada12e24633b37168431600a7fa9c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 8 Feb 2018 20:46:47 +0100 Subject: [PATCH 13/21] Move update handler into Client --- pyrogram/client/client.py | 36 ++++++++++++++++++++++++++++++++---- pyrogram/session/session.py | 15 +++++---------- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 7c17701f..863e83bb 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -23,12 +23,14 @@ import math import mimetypes import os import re +import threading import time from collections import namedtuple from configparser import ConfigParser from hashlib import sha256, md5 +from queue import Queue from signal import signal, SIGINT, SIGTERM, SIGABRT -from threading import Event +from threading import Event, Thread from pyrogram.api import functions, types from pyrogram.api.core import Object @@ -141,6 +143,8 @@ class Client: self.update_handler = None self.is_idle = Event() + self.update_queue = Queue() + def start(self): """Use this method to start the Client after creating it. Requires no parameters. @@ -156,7 +160,8 @@ class Client: self.test_mode, self.proxy, self.auth_key, - self.config.api_id + self.config.api_id, + client=self ) terms = self.session.start() @@ -170,7 +175,9 @@ class Client: self.rnd_id = self.session.msg_id self.get_dialogs() - self.session.set_update_handler(self, self.update_handler) + + for i in range(self.workers): + Thread(target=self.update_worker, name="UpdateWorker#{}".format(i + 1)).start() mimetypes.init() @@ -180,6 +187,26 @@ class Client: """ self.session.stop() + for i in range(self.workers): + self.update_queue.put(None) + + def update_worker(self): + name = threading.current_thread().name + log.debug("{} started".format(name)) + + while True: + update = self.update_queue.get() + + if update is None: + break + + try: + self.update_handler(self, update) + except Exception as e: + log.error(e, exc_info=True) + + log.debug("{} stopped".format(name)) + def signal_handler(self, *args): self.stop() self.is_idle.set() @@ -261,7 +288,8 @@ class Client: self.test_mode, self.proxy, self.auth_key, - self.config.api_id + self.config.api_id, + client=self ) self.session.start() diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index db03f740..d438de85 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -74,7 +74,8 @@ class Session: proxy: type, auth_key: bytes, api_id: str, - is_cdn: bool = False): + is_cdn: bool = False, + client: pyrogram = None): if not Session.notice_displayed: print("Pyrogram v{}, {}".format(__version__, __copyright__)) print("Licensed under the terms of the " + __license__, end="\n\n") @@ -83,6 +84,7 @@ class Session: self.connection = Connection(DataCenter(dc_id, test_mode), proxy) self.api_id = api_id self.is_cdn = is_cdn + self.client = client self.auth_key = auth_key self.auth_key_id = sha1(auth_key).digest()[-8:] @@ -106,9 +108,6 @@ class Session: self.is_connected = Event() - self.client = None - self.update_handler = None - def start(self): terms = None @@ -236,10 +235,6 @@ class Session: log.debug("{} stopped".format(name)) - def set_update_handler(self, client: pyrogram, update_handler: callable): - self.client = client - self.update_handler = update_handler - def unpack_dispatch_and_ack(self, packet: bytes): data = self.unpack(BytesIO(packet)) @@ -274,8 +269,8 @@ class Session: elif isinstance(msg.body, types.Pong): msg_id = msg.body.msg_id else: - if self.update_handler: - self.update_handler(self.client, msg.body) + if self.client is not None: + self.client.update_queue.put(msg.body) if msg_id in self.results: self.results[msg_id].value = getattr(msg.body, "result", msg.body) From bd75dc808284c598a4186e9967bff35e57fdccb4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 8 Feb 2018 20:47:56 +0100 Subject: [PATCH 14/21] Set NET_WORKERS to 2 --- pyrogram/session/session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index d438de85..e3e236b6 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -60,7 +60,7 @@ class Session: ) INITIAL_SALT = 0x616e67656c696361 - NET_WORKERS = 4 + NET_WORKERS = 2 WAIT_TIMEOUT = 10 MAX_RETRIES = 5 ACKS_THRESHOLD = 8 From 07c3d96d821f2c29c9b4a7e75634ada79deaa7bb Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 8 Feb 2018 21:59:08 +0100 Subject: [PATCH 15/21] Add Event Handler (for single updates) --- pyrogram/client/client.py | 40 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 863e83bb..fed9607b 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -103,6 +103,7 @@ class Client: INVITE_LINK_RE = re.compile(r"^(?:https?://)?t\.me/joinchat/(.+)$") DIALOGS_AT_ONCE = 100 + UPDATE_WORKERS = 2 def __init__(self, session_name: str, @@ -144,6 +145,7 @@ class Client: self.is_idle = Event() self.update_queue = Queue() + self.event_queue = Queue() def start(self): """Use this method to start the Client after creating it. @@ -176,9 +178,12 @@ class Client: self.rnd_id = self.session.msg_id self.get_dialogs() - for i in range(self.workers): + for i in range(self.UPDATE_WORKERS): Thread(target=self.update_worker, name="UpdateWorker#{}".format(i + 1)).start() + for i in range(self.workers): + Thread(target=self.event_worker, name="EventWorker#{}".format(i + 1)).start() + mimetypes.init() def stop(self): @@ -187,9 +192,12 @@ class Client: """ self.session.stop() - for i in range(self.workers): + for i in range(self.UPDATE_WORKERS): self.update_queue.put(None) + for i in range(self.workers): + self.event_queue.put(None) + def update_worker(self): name = threading.current_thread().name log.debug("{} started".format(name)) @@ -201,7 +209,33 @@ class Client: break try: - self.update_handler(self, update) + # TODO: Fetch users and chats + if isinstance(update, (types.Update, types.UpdatesCombined)): + for i in update.updates: + self.event_queue.put(i) + elif isinstance(update, (types.UpdateShortMessage, types.UpdateShortChatMessage)): + self.event_queue.put(update) + elif isinstance(update, types.UpdateShort): + self.event_queue.put(update.update) + else: + print(">>>>>", type(update)) + except Exception as e: + log.error(e, exc_info=True) + + log.debug("{} stopped".format(name)) + + def event_worker(self): + name = threading.current_thread().name + log.debug("{} started".format(name)) + + while True: + event = self.event_queue.get() + + if event is None: + break + + try: + self.update_handler(self, event) except Exception as e: log.error(e, exc_info=True) From 646c821706f89cfffecd70a526d2116cf12d2924 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 8 Feb 2018 22:00:13 +0100 Subject: [PATCH 16/21] Rename update to event --- pyrogram/client/client.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index fed9607b..987348e4 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -141,9 +141,9 @@ class Client: self.proxy = None self.session = None - self.update_handler = None self.is_idle = Event() + self.event_handler = None self.update_queue = Queue() self.event_queue = Queue() @@ -235,7 +235,7 @@ class Client: break try: - self.update_handler(self, event) + self.event_handler(self, event) except Exception as e: log.error(e, exc_info=True) @@ -259,15 +259,14 @@ class Client: self.is_idle.wait() - # TODO: Better update handler - def set_update_handler(self, callback: callable): + def set_event_handler(self, callback: callable): """Use this method to set the update handler. Args: callback (:obj:`callable`): A callback function that accepts a single argument: the update object. """ - self.update_handler = callback + self.event_handler = callback def send(self, data: Object): """Use this method to send :ref:`Raw Function ` queries. From 0f45cacefa919de7625cc59207f29cfa9097c9c6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 8 Feb 2018 22:01:14 +0100 Subject: [PATCH 17/21] Update doctrings --- pyrogram/client/client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 987348e4..b60754a5 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -98,7 +98,7 @@ class Client: be an empty string: "" workers (:obj:`int`, optional): - Thread pool size for handling incoming messages (updates). Defaults to 4. + Thread pool size for handling incoming events (updates). Defaults to 4. """ INVITE_LINK_RE = re.compile(r"^(?:https?://)?t\.me/joinchat/(.+)$") @@ -260,11 +260,11 @@ class Client: self.is_idle.wait() def set_event_handler(self, callback: callable): - """Use this method to set the update handler. + """Use this method to set the event handler. Args: callback (:obj:`callable`): - A callback function that accepts a single argument: the update object. + A callback function that accepts a single argument: the event (update) object. """ self.event_handler = callback From 9a912192f05eb3d30a28a544901683c26792da95 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 9 Feb 2018 01:52:40 +0100 Subject: [PATCH 18/21] Add fetch_peers method --- pyrogram/client/client.py | 99 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 93 insertions(+), 6 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index b60754a5..b50ac87d 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -198,6 +198,67 @@ class Client: for i in range(self.workers): self.event_queue.put(None) + def fetch_peers(self, entities: list): + for entity in entities: + if isinstance(entity, User): + user_id = entity.id + + if user_id in self.peers_by_id: + continue + + access_hash = entity.access_hash + + if access_hash is None: + continue + + username = entity.username + + input_peer = InputPeerUser( + user_id=user_id, + access_hash=access_hash + ) + + self.peers_by_id[user_id] = input_peer + + if username is not None: + self.peers_by_id[username] = input_peer + + if isinstance(entity, Chat): + chat_id = entity.id + + if chat_id in self.peers_by_id: + continue + + input_peer = InputPeerChat( + chat_id=chat_id + ) + + self.peers_by_id[chat_id] = input_peer + + if isinstance(entity, Channel): + channel_id = entity.id + peer_id = int("-100" + str(channel_id)) + + if peer_id in self.peers_by_id: + continue + + access_hash = entity.access_hash + + if access_hash is None: + continue + + username = entity.username + + input_peer = InputPeerChannel( + channel_id=channel_id, + access_hash=access_hash + ) + + self.peers_by_id[peer_id] = input_peer + + if username is not None: + self.peers_by_username[username] = input_peer + def update_worker(self): name = threading.current_thread().name log.debug("{} started".format(name)) @@ -209,16 +270,41 @@ class Client: break try: - # TODO: Fetch users and chats if isinstance(update, (types.Update, types.UpdatesCombined)): + self.fetch_peers(update.users) + self.fetch_peers(update.chats) + for i in update.updates: self.event_queue.put(i) - elif isinstance(update, (types.UpdateShortMessage, types.UpdateShortChatMessage)): + elif isinstance(update, types.UpdateShortMessage): + if update.user_id not in self.peers_by_id: + diff = self.send( + functions.updates.GetDifference( + pts=update.pts - 1, + date=update.date, + qts=-1 + ) + ) + + self.fetch_peers(diff.users) + + self.event_queue.put(update) + elif isinstance(update, types.UpdateShortChatMessage): + if update.chat_id not in self.peers_by_id: + diff = self.send( + functions.updates.GetDifference( + pts=update.pts - 1, + date=update.date, + qts=-1 + ) + ) + + self.fetch_peers(diff.users) + self.fetch_peers(diff.chats) + self.event_queue.put(update) elif isinstance(update, types.UpdateShort): self.event_queue.put(update.update) - else: - print(">>>>>", type(update)) except Exception as e: log.error(e, exc_info=True) @@ -235,7 +321,8 @@ class Client: break try: - self.event_handler(self, event) + if self.event_handler: + self.event_handler(self, event) except Exception as e: log.error(e, exc_info=True) @@ -264,7 +351,7 @@ class Client: Args: callback (:obj:`callable`): - A callback function that accepts a single argument: the event (update) object. + A callback function that accepts two arguments: the Client itself and the event (update) object. """ self.event_handler = callback From b1064015f6c68479a86cac71596b9db2b823c72d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 9 Feb 2018 01:54:47 +0100 Subject: [PATCH 19/21] Update set_event_handler docstring --- pyrogram/client/client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index b50ac87d..1c6a8653 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -351,7 +351,8 @@ class Client: Args: callback (:obj:`callable`): - A callback function that accepts two arguments: the Client itself and the event (update) object. + A function that takes ``client, event`` as positional arguments. + It will be called when a new event is generated on your account. """ self.event_handler = callback From a0bd47fee44b8d0daeb9e56bd27b0ebc1be4b26e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 9 Feb 2018 02:57:08 +0100 Subject: [PATCH 20/21] Revamp get_dialogs. Fixes #13 --- pyrogram/client/client.py | 91 ++++++--------------------------------- 1 file changed, 12 insertions(+), 79 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 1c6a8653..990ccede 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -43,8 +43,7 @@ from pyrogram.api.errors import ( ) from pyrogram.api.types import ( User, Chat, Channel, - PeerUser, PeerChat, PeerChannel, - Dialog, Message, + PeerUser, PeerChannel, InputPeerEmpty, InputPeerSelf, InputPeerUser, InputPeerChat, InputPeerChannel ) @@ -221,7 +220,7 @@ class Client: self.peers_by_id[user_id] = input_peer if username is not None: - self.peers_by_id[username] = input_peer + self.peers_by_username[username] = input_peer if isinstance(entity, Chat): chat_id = entity.id @@ -580,53 +579,17 @@ class Client: ) def get_dialogs(self): - peers = [] + def parse_dialogs(d): + self.fetch_peers(d.chats) + self.fetch_peers(d.users) - def parse_dialogs(d) -> int: - oldest_date = 1 << 32 - - for dialog in d.dialogs: # type: Dialog - # Only search for Users, Chats and Channels - if not isinstance(dialog.peer, (PeerUser, PeerChat, PeerChannel)): + for m in reversed(d.messages): + if isinstance(m, types.MessageEmpty): continue - - if isinstance(dialog.peer, PeerUser): - peer_type = "user" - peer_id = dialog.peer.user_id - elif isinstance(dialog.peer, PeerChat): - peer_type = "chat" - peer_id = dialog.peer.chat_id - elif isinstance(dialog.peer, PeerChannel): - peer_type = "channel" - peer_id = dialog.peer.channel_id else: - continue - - for message in d.messages: # type: Message - is_this = peer_id == message.from_id or dialog.peer == message.to_id - - if is_this: - for entity in (d.users if peer_type == "user" else d.chats): # type: User or Chat or Channel - if entity.id == peer_id: - peers.append( - dict( - id=peer_id, - access_hash=getattr(entity, "access_hash", None), - type=peer_type, - first_name=getattr(entity, "first_name", None), - last_name=getattr(entity, "last_name", None), - title=getattr(entity, "title", None), - username=getattr(entity, "username", None), - ) - ) - - if message.date < oldest_date: - oldest_date = message.date - - break - break - - return oldest_date + return m.date + else: + return 0 pinned_dialogs = self.send(functions.messages.GetPinnedDialogs()) parse_dialogs(pinned_dialogs) @@ -639,7 +602,7 @@ class Client: ) offset_date = parse_dialogs(dialogs) - log.info("Dialogs count: {}".format(len(peers))) + log.info("Entities count: {}".format(len(self.peers_by_id))) while len(dialogs.dialogs) == self.DIALOGS_AT_ONCE: try: @@ -655,37 +618,7 @@ class Client: continue offset_date = parse_dialogs(dialogs) - log.info("Dialogs count: {}".format(len(peers))) - - for i in peers: - peer_id = i["id"] - peer_type = i["type"] - peer_username = i["username"] - peer_access_hash = i["access_hash"] - - if peer_type == "user": - input_peer = InputPeerUser( - peer_id, - peer_access_hash - ) - elif peer_type == "chat": - input_peer = InputPeerChat( - peer_id - ) - elif peer_type == "channel": - input_peer = InputPeerChannel( - peer_id, - peer_access_hash - ) - peer_id = int("-100" + str(peer_id)) - else: - continue - - self.peers_by_id[peer_id] = input_peer - - if peer_username: - peer_username = peer_username.lower() - self.peers_by_username[peer_username] = input_peer + log.info("Entities count: {}".format(len(self.peers_by_id))) def resolve_username(self, username: str): username = username.lower().strip("@") From 0b15cb98672a8d4f21ef04d0803cd5f46415d1e7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 9 Feb 2018 03:02:27 +0100 Subject: [PATCH 21/21] Patch Layer 75 --- compiler/api/source/main_api.tl | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index ca2ea52f..b4d0e7a8 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -431,7 +431,7 @@ accountDaysTTL#b8d0afdf days:int = AccountDaysTTL; documentAttributeImageSize#6c37c15c w:int h:int = DocumentAttribute; documentAttributeAnimated#11b58939 = DocumentAttribute; documentAttributeSticker#6319d612 flags:# mask:flags.1?true alt:string stickerset:InputStickerSet mask_coords:flags.0?MaskCoords = DocumentAttribute; -documentAttributeVideo#ef02ce6 flags:# round_message:flags.0?true duration:int w:int h:int = DocumentAttribute; +documentAttributeVideo#ef02ce6 flags:# round_message:flags.0?true supports_streaming:flags.1?true duration:int w:int h:int = DocumentAttribute; documentAttributeAudio#9852f9c6 flags:# voice:flags.10?true duration:int title:flags.0?string performer:flags.1?string waveform:flags.2?bytes = DocumentAttribute; documentAttributeFilename#15590068 file_name:string = DocumentAttribute; documentAttributeHasStickers#9801d2f7 = DocumentAttribute; @@ -815,6 +815,14 @@ help.recentMeUrls#e0310d7 urls:Vector chats:Vector users:Vect inputSingleMedia#1cc6e91f flags:# media:InputMedia random_id:long message:string entities:flags.0?Vector = InputSingleMedia; +webAuthorization#cac943f2 hash:long bot_id:int domain:string browser:string platform:string date_created:int date_active:int ip:string region:string = WebAuthorization; + +account.webAuthorizations#ed56c9fc authorizations:Vector users:Vector = account.WebAuthorizations; + +inputMessageID#a676a322 id:int = InputMessage; +inputMessageReplyTo#bad88395 id:int = InputMessage; +inputMessagePinned#86872538 = InputMessage; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -868,6 +876,9 @@ account.updatePasswordSettings#fa7c4b86 current_password_hash:bytes new_settings account.sendConfirmPhoneCode#1516d7bd flags:# allow_flashcall:flags.0?true hash:string current_number:flags.0?Bool = auth.SentCode; account.confirmPhone#5f2178c3 phone_code_hash:string phone_code:string = Bool; account.getTmpPassword#4a82327e password_hash:bytes period:int = account.TmpPassword; +account.getWebAuthorizations#182e6d6f = account.WebAuthorizations; +account.resetWebAuthorization#2d01b9ef hash:long = Bool; +account.resetWebAuthorizations#682d2594 = Bool; users.getUsers#d91a548 id:Vector = Vector; users.getFullUser#ca30a5b1 id:InputUser = UserFull; @@ -888,7 +899,7 @@ contacts.getTopPeers#d4982db5 flags:# correspondents:flags.0?true bots_pm:flags. contacts.resetTopPeerRating#1ae373ac category:TopPeerCategory peer:InputPeer = Bool; contacts.resetSaved#879537f1 = Bool; -messages.getMessages#4222fa74 id:Vector = messages.Messages; +messages.getMessages#63c66506 id:Vector = messages.Messages; messages.getDialogs#191ba9c5 flags:# exclude_pinned:flags.0?true offset_date:int offset_id:int offset_peer:InputPeer limit:int = messages.Dialogs; messages.getHistory#dcbb8260 peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages; messages.search#39e9ea0 flags:# peer:InputPeer q:string from_id:flags.0?InputUser filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages; @@ -1016,7 +1027,7 @@ channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool; channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector = messages.AffectedMessages; channels.deleteUserHistory#d10dd71b channel:InputChannel user_id:InputUser = messages.AffectedHistory; channels.reportSpam#fe087810 channel:InputChannel user_id:InputUser id:Vector = Bool; -channels.getMessages#93d7b347 channel:InputChannel id:Vector = messages.Messages; +channels.getMessages#ad8c9a23 channel:InputChannel id:Vector = messages.Messages; 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 = messages.Chats;