diff --git a/docs/source/index.rst b/docs/source/index.rst index f6961bc6..6a8ae4f5 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -40,7 +40,6 @@ Welcome to Pyrogram topics/more-on-updates topics/config-file topics/smart-plugins - topics/auto-auth topics/session-settings topics/tgcrypto topics/storage-engines diff --git a/docs/source/topics/auto-auth.rst b/docs/source/topics/auto-auth.rst deleted file mode 100644 index abeaf1fb..00000000 --- a/docs/source/topics/auto-auth.rst +++ /dev/null @@ -1,68 +0,0 @@ -Auto Authorization -================== - -Manually writing phone number, phone code and password on the terminal every time you want to login can be tedious. -Pyrogram is able to automate both **Log In** and **Sign Up** processes, all you need to do is pass the relevant -parameters when creating a new :class:`~pyrogram.Client`. - -.. note:: If you omit any of the optional parameter required for the authorization, Pyrogram will ask you to - manually write it. For instance, if you don't want to set a ``last_name`` when creating a new account you - have to explicitly pass an empty string ""; the default value (None) will trigger the input() call. - -Log In -------- - -To automate the **Log In** process, pass your ``phone_number`` and ``password`` (if you have one) in the Client parameters. -If you want to retrieve the phone code programmatically, pass a callback function in the ``phone_code`` field — this -function accepts a single positional argument (phone_number) and must return the correct phone code (e.g., "12345") -— otherwise, ignore this parameter, Pyrogram will ask you to input the phone code manually. - -Example: - -.. code-block:: python - - from pyrogram import Client - - def phone_code_callback(phone_number): - code = ... # Get your code programmatically - return code # e.g., "12345" - - - app = Client( - session_name="example", - phone_number="39**********", - phone_code=phone_code_callback, # Note the missing parentheses - password="password" # (if you have one) - ) - - with app: - print(app.get_me()) - -Sign Up -------- - -To automate the **Sign Up** process (i.e., automatically create a new Telegram account), simply fill **both** -``first_name`` and ``last_name`` fields alongside the other parameters; they will be used to automatically create a new -Telegram account in case the phone number you passed is not registered yet. - -Example: - -.. code-block:: python - - from pyrogram import Client - - def phone_code_callback(phone_number): - code = ... # Get your code programmatically - return code # e.g., "12345" - - - app = Client( - session_name="example", - phone_number="39**********", - phone_code=phone_code_callback, # Note the missing parentheses - first_name="Pyrogram", - last_name="" # Can be an empty string - ) - - with app: - print(app.get_me()) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index f259e54c..c8da0a60 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -266,13 +266,13 @@ class Client(Methods, BaseClient): self.load_config() await self.load_session() - self.session = Session(self, self.storage.dc_id, self.storage.auth_key) + self.session = Session(self, self.storage.dc_id(), self.storage.auth_key()) await self.session.start() self.is_connected = True - return bool(self.storage.user_id) + return bool(self.storage.user_id()) async def disconnect(self): """Disconnect the client from Telegram servers. @@ -402,9 +402,9 @@ class Client(Methods, BaseClient): except (PhoneMigrate, NetworkMigrate) as e: await self.session.stop() - self.storage.dc_id = e.x - self.storage.auth_key = await Auth(self, self.storage.dc_id).create() - self.session = Session(self, self.storage.dc_id, self.storage.auth_key) + self.storage.dc_id(e.x) + self.storage.auth_key(await Auth(self, self.storage.dc_id()).create()) + self.session = Session(self, self.storage.dc_id(), self.storage.auth_key()) await self.session.start() else: @@ -480,8 +480,8 @@ class Client(Methods, BaseClient): return False else: - self.storage.user_id = r.user.id - self.storage.is_bot = False + self.storage.user_id(r.user.id) + self.storage.is_bot(False) return User._parse(self, r.user) @@ -518,8 +518,8 @@ class Client(Methods, BaseClient): ) ) - self.storage.user_id = r.user.id - self.storage.is_bot = False + self.storage.user_id(r.user.id) + self.storage.is_bot(False) return User._parse(self, r.user) @@ -549,14 +549,14 @@ class Client(Methods, BaseClient): except UserMigrate as e: await self.session.stop() - self.storage.dc_id = e.x - self.storage.auth_key = await Auth(self, self.storage.dc_id).create() - self.session = Session(self, self.storage.dc_id, self.storage.auth_key) + self.storage.dc_id(e.x) + self.storage.auth_key(await Auth(self, self.storage.dc_id()).create()) + self.session = Session(self, self.storage.dc_id(), self.storage.auth_key()) await self.session.start() else: - self.storage.user_id = r.user.id - self.storage.is_bot = True + self.storage.user_id(r.user.id) + self.storage.is_bot(True) return User._parse(self, r.user) @@ -590,8 +590,8 @@ class Client(Methods, BaseClient): ) ) - self.storage.user_id = r.user.id - self.storage.is_bot = False + self.storage.user_id(r.user.id) + self.storage.is_bot(False) return User._parse(self, r.user) @@ -627,8 +627,8 @@ class Client(Methods, BaseClient): ) ) - self.storage.user_id = r.user.id - self.storage.is_bot = False + self.storage.user_id(r.user.id) + self.storage.is_bot(False) return User._parse(self, r.user) @@ -784,7 +784,7 @@ class Client(Methods, BaseClient): async def log_out(self): """Log out from Telegram and delete the *\\*.session* file. - When you log out, the current client is stopped and the storage session destroyed. + When you log out, the current client is stopped and the storage session deleted. No more API calls can be made until you start the client and re-authorize again. Returns: @@ -798,7 +798,7 @@ class Client(Methods, BaseClient): """ await self.send(functions.auth.LogOut()) await self.stop() - self.storage.destroy() + self.storage.delete() return True @@ -833,7 +833,7 @@ class Client(Methods, BaseClient): if not is_authorized: await self.authorize() - if not self.storage.is_bot and self.takeout: + if not self.storage.is_bot() and self.takeout: self.takeout_id = (await self.send(functions.account.InitTakeoutSession())).id log.warning("Takeout session {} initiated".format(self.takeout_id)) @@ -1176,41 +1176,24 @@ class Client(Methods, BaseClient): self.parse_mode = parse_mode - def fetch_peers( - self, - peers: List[ - Union[ - types.User, - types.Chat, types.ChatForbidden, - types.Channel, types.ChannelForbidden - ] - ] - ) -> bool: + def fetch_peers(self, peers: List[Union[types.User, types.Chat, types.Channel]]) -> bool: is_min = False parsed_peers = [] for peer in peers: + if getattr(peer, "min", False): + is_min = True + continue + username = None phone_number = None if isinstance(peer, types.User): peer_id = peer.id access_hash = peer.access_hash - - username = peer.username + username = (peer.username or "").lower() or None phone_number = peer.phone - - if peer.bot: - peer_type = "bot" - else: - peer_type = "user" - - if access_hash is None: - is_min = True - continue - - if username is not None: - username = username.lower() + peer_type = "bot" if peer.bot else "user" elif isinstance(peer, (types.Chat, types.ChatForbidden)): peer_id = -peer.id access_hash = 0 @@ -1218,20 +1201,8 @@ class Client(Methods, BaseClient): elif isinstance(peer, (types.Channel, types.ChannelForbidden)): peer_id = utils.get_channel_id(peer.id) access_hash = peer.access_hash - - username = getattr(peer, "username", None) - - if peer.broadcast: - peer_type = "channel" - else: - peer_type = "supergroup" - - if access_hash is None: - is_min = True - continue - - if username is not None: - username = username.lower() + username = (getattr(peer, "username", None) or "").lower() or None + peer_type = "channel" if peer.broadcast else "supergroup" else: continue @@ -1494,20 +1465,20 @@ class Client(Methods, BaseClient): self.storage.open() session_empty = any([ - self.storage.test_mode is None, - self.storage.auth_key is None, - self.storage.user_id is None, - self.storage.is_bot is None + self.storage.test_mode() is None, + self.storage.auth_key() is None, + self.storage.user_id() is None, + self.storage.is_bot() is None ]) if session_empty: - self.storage.dc_id = 2 - self.storage.date = 0 + self.storage.dc_id(2) + self.storage.date(0) - self.storage.test_mode = self.test_mode - self.storage.auth_key = await Auth(self, self.storage.dc_id).create() - self.storage.user_id = None - self.storage.is_bot = None + self.storage.test_mode(self.test_mode) + self.storage.auth_key(await Auth(self, self.storage.dc_id()).create()) + self.storage.user_id(None) + self.storage.is_bot(None) def load_plugins(self): if self.plugins: @@ -1715,7 +1686,7 @@ class Client(Methods, BaseClient): except KeyError: raise PeerIdInvalid - peer_type = utils.get_type(peer_id) + peer_type = utils.get_peer_type(peer_id) if peer_type == "user": self.fetch_peers( @@ -1836,7 +1807,7 @@ class Client(Methods, BaseClient): is_missing_part = file_id is not None file_id = file_id or self.rnd_id() md5_sum = md5() if not is_big and not is_missing_part else None - pool = [Session(self, self.storage.dc_id, self.storage.auth_key, is_media=True) for _ in range(pool_size)] + pool = [Session(self, self.storage.dc_id(), self.storage.auth_key(), is_media=True) for _ in range(pool_size)] workers = [asyncio.ensure_future(worker(session)) for session in pool for _ in range(workers_count)] queue = asyncio.Queue(16) @@ -1926,7 +1897,7 @@ class Client(Methods, BaseClient): session = self.media_sessions.get(dc_id, None) if session is None: - if dc_id != self.storage.dc_id: + if dc_id != self.storage.dc_id(): session = Session(self, dc_id, await Auth(self, dc_id).create(), is_media=True) await session.start() @@ -1952,7 +1923,7 @@ class Client(Methods, BaseClient): await session.stop() raise AuthBytesInvalid else: - session = Session(self, dc_id, self.storage.auth_key, is_media=True) + session = Session(self, dc_id, self.storage.auth_key(), is_media=True) await session.start() self.media_sessions[dc_id] = session diff --git a/pyrogram/client/ext/utils.py b/pyrogram/client/ext/utils.py index 47f2c240..ce708786 100644 --- a/pyrogram/client/ext/utils.py +++ b/pyrogram/client/ext/utils.py @@ -227,7 +227,7 @@ def get_peer_id(peer: Union[PeerUser, PeerChat, PeerChannel]) -> int: raise ValueError("Peer type invalid: {}".format(peer)) -def get_type(peer_id: int) -> str: +def get_peer_type(peer_id: int) -> str: if peer_id < 0: if MIN_CHAT_ID <= peer_id: return "chat" diff --git a/pyrogram/client/storage/file_storage.py b/pyrogram/client/storage/file_storage.py index b9529504..d0d18ea1 100644 --- a/pyrogram/client/storage/file_storage.py +++ b/pyrogram/client/storage/file_storage.py @@ -22,34 +22,29 @@ import logging import os import sqlite3 from pathlib import Path -from threading import Lock -from .memory_storage import MemoryStorage +from .sqlite_storage import SQLiteStorage log = logging.getLogger(__name__) -class FileStorage(MemoryStorage): +class FileStorage(SQLiteStorage): FILE_EXTENSION = ".session" def __init__(self, name: str, workdir: Path): super().__init__(name) - self.workdir = workdir self.database = workdir / (self.name + self.FILE_EXTENSION) - self.conn = None # type: sqlite3.Connection - self.lock = Lock() - # noinspection PyAttributeOutsideInit def migrate_from_json(self, session_json: dict): self.open() - self.dc_id = session_json["dc_id"] - self.test_mode = session_json["test_mode"] - self.auth_key = base64.b64decode("".join(session_json["auth_key"])) - self.user_id = session_json["user_id"] - self.date = session_json.get("date", 0) - self.is_bot = session_json.get("is_bot", False) + self.dc_id(session_json["dc_id"]) + self.test_mode(session_json["test_mode"]) + self.auth_key(base64.b64decode("".join(session_json["auth_key"]))) + self.user_id(session_json["user_id"]) + self.date(session_json.get("date", 0)) + self.is_bot(session_json.get("is_bot", False)) peers_by_id = session_json.get("peers_by_id", {}) peers_by_phone = session_json.get("peers_by_phone", {}) @@ -72,6 +67,17 @@ class FileStorage(MemoryStorage): # noinspection PyTypeChecker self.update_peers(peers.values()) + def update(self): + version = self.version() + + if version == 1: + with self.lock, self.conn: + self.conn.execute("DELETE FROM peers") + + version += 1 + + self.version(version) + def open(self): path = self.database file_exists = path.is_file() @@ -98,14 +104,12 @@ class FileStorage(MemoryStorage): if Path(path.name + ".OLD").is_file(): log.warning('Old session file detected: "{}.OLD". You can remove this file now'.format(path.name)) - self.conn = sqlite3.connect( - str(path), - timeout=1, - check_same_thread=False - ) + self.conn = sqlite3.connect(str(path), timeout=1, check_same_thread=False) if not file_exists: self.create() + else: + self.update() with self.conn: try: # Python 3.6.0 (exactly this version) is bugged and won't successfully execute the vacuum @@ -113,5 +117,5 @@ class FileStorage(MemoryStorage): except sqlite3.OperationalError: pass - def destroy(self): + def delete(self): os.remove(self.database) diff --git a/pyrogram/client/storage/memory_storage.py b/pyrogram/client/storage/memory_storage.py index b24fce38..00b81e7a 100644 --- a/pyrogram/client/storage/memory_storage.py +++ b/pyrogram/client/storage/memory_storage.py @@ -17,226 +17,37 @@ # along with Pyrogram. If not, see . import base64 -import inspect import logging import sqlite3 import struct -import time -from pathlib import Path -from threading import Lock -from typing import List, Tuple -from pyrogram.api import types -from pyrogram.client.storage.storage import Storage +from .sqlite_storage import SQLiteStorage log = logging.getLogger(__name__) -class MemoryStorage(Storage): - SCHEMA_VERSION = 1 - USERNAME_TTL = 8 * 60 * 60 - SESSION_STRING_FMT = ">B?256sI?" - SESSION_STRING_SIZE = 351 - +class MemoryStorage(SQLiteStorage): def __init__(self, name: str): super().__init__(name) - self.conn = None # type: sqlite3.Connection - self.lock = Lock() - - def create(self): - with self.lock, self.conn: - with open(str(Path(__file__).parent / "schema.sql"), "r") as schema: - self.conn.executescript(schema.read()) - - self.conn.execute( - "INSERT INTO version VALUES (?)", - (self.SCHEMA_VERSION,) - ) - - self.conn.execute( - "INSERT INTO sessions VALUES (?, ?, ?, ?, ?, ?)", - (1, None, None, 0, None, None) - ) - - def _import_session_string(self, session_string: str): - decoded = base64.urlsafe_b64decode(session_string + "=" * (-len(session_string) % 4)) - return struct.unpack(self.SESSION_STRING_FMT, decoded) - - def export_session_string(self): - packed = struct.pack( - self.SESSION_STRING_FMT, - self.dc_id, - self.test_mode, - self.auth_key, - self.user_id, - self.is_bot - ) - - return base64.urlsafe_b64encode(packed).decode().rstrip("=") - - # noinspection PyAttributeOutsideInit def open(self): self.conn = sqlite3.connect(":memory:", check_same_thread=False) self.create() if self.name != ":memory:": - imported_session_string = self._import_session_string(self.name) + dc_id, test_mode, auth_key, user_id, is_bot = struct.unpack( + self.SESSION_STRING_FORMAT, + base64.urlsafe_b64decode( + self.name + "=" * (-len(self.name) % 4) + ) + ) - self.dc_id, self.test_mode, self.auth_key, self.user_id, self.is_bot = imported_session_string - self.date = 0 + self.dc_id(dc_id) + self.test_mode(test_mode) + self.auth_key(auth_key) + self.user_id(user_id) + self.is_bot(is_bot) + self.date(0) - # noinspection PyAttributeOutsideInit - def save(self): - self.date = int(time.time()) - - with self.lock: - self.conn.commit() - - def close(self): - with self.lock: - self.conn.close() - - def destroy(self): + def delete(self): pass - - def update_peers(self, peers: List[Tuple[int, int, str, str, str]]): - with self.lock: - self.conn.executemany( - "REPLACE INTO peers (id, access_hash, type, username, phone_number)" - "VALUES (?, ?, ?, ?, ?)", - peers - ) - - def clear_peers(self): - with self.lock, self.conn: - self.conn.execute( - "DELETE FROM peers" - ) - - @staticmethod - def _get_input_peer(peer_id: int, access_hash: int, peer_type: str): - if peer_type in ["user", "bot"]: - return types.InputPeerUser( - user_id=peer_id, - access_hash=access_hash - ) - - if peer_type == "group": - return types.InputPeerChat( - chat_id=-peer_id - ) - - if peer_type in ["channel", "supergroup"]: - return types.InputPeerChannel( - channel_id=int(str(peer_id)[4:]), - access_hash=access_hash - ) - - raise ValueError("Invalid peer type: {}".format(peer_type)) - - def get_peer_by_id(self, peer_id: int): - r = self.conn.execute( - "SELECT id, access_hash, type FROM peers WHERE id = ?", - (peer_id,) - ).fetchone() - - if r is None: - raise KeyError("ID not found: {}".format(peer_id)) - - return self._get_input_peer(*r) - - def get_peer_by_username(self, username: str): - r = self.conn.execute( - "SELECT id, access_hash, type, last_update_on FROM peers WHERE username = ?", - (username,) - ).fetchone() - - if r is None: - raise KeyError("Username not found: {}".format(username)) - - if abs(time.time() - r[3]) > self.USERNAME_TTL: - raise KeyError("Username expired: {}".format(username)) - - return self._get_input_peer(*r[:3]) - - def get_peer_by_phone_number(self, phone_number: str): - r = self.conn.execute( - "SELECT id, access_hash, type FROM peers WHERE phone_number = ?", - (phone_number,) - ).fetchone() - - if r is None: - raise KeyError("Phone number not found: {}".format(phone_number)) - - return self._get_input_peer(*r) - - @property - def peers_count(self): - return self.conn.execute( - "SELECT COUNT(*) FROM peers" - ).fetchone()[0] - - def _get(self): - attr = inspect.stack()[1].function - - return self.conn.execute( - "SELECT {} FROM sessions".format(attr) - ).fetchone()[0] - - def _set(self, value): - attr = inspect.stack()[1].function - - with self.lock, self.conn: - self.conn.execute( - "UPDATE sessions SET {} = ?".format(attr), - (value,) - ) - - @property - def dc_id(self): - return self._get() - - @dc_id.setter - def dc_id(self, value): - self._set(value) - - @property - def test_mode(self): - return self._get() - - @test_mode.setter - def test_mode(self, value): - self._set(value) - - @property - def auth_key(self): - return self._get() - - @auth_key.setter - def auth_key(self, value): - self._set(value) - - @property - def date(self): - return self._get() - - @date.setter - def date(self, value): - self._set(value) - - @property - def user_id(self): - return self._get() - - @user_id.setter - def user_id(self, value): - self._set(value) - - @property - def is_bot(self): - return self._get() - - @is_bot.setter - def is_bot(self, value): - self._set(value) diff --git a/pyrogram/client/storage/schema.sql b/pyrogram/client/storage/schema.sql index 1f5af6d2..52dccee3 100644 --- a/pyrogram/client/storage/schema.sql +++ b/pyrogram/client/storage/schema.sql @@ -27,8 +27,8 @@ CREATE INDEX idx_peers_phone_number ON peers (phone_number); CREATE TRIGGER trg_peers_last_update_on AFTER UPDATE ON peers - BEGIN - UPDATE peers - SET last_update_on = CAST(STRFTIME('%s', 'now') AS INTEGER) - WHERE id = NEW.id; - END; \ No newline at end of file +BEGIN + UPDATE peers + SET last_update_on = CAST(STRFTIME('%s', 'now') AS INTEGER) + WHERE id = NEW.id; +END; \ No newline at end of file diff --git a/pyrogram/client/storage/sqlite_storage.py b/pyrogram/client/storage/sqlite_storage.py new file mode 100644 index 00000000..1c36c427 --- /dev/null +++ b/pyrogram/client/storage/sqlite_storage.py @@ -0,0 +1,184 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 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 . + +import inspect +import sqlite3 +import time +from pathlib import Path +from threading import Lock +from typing import List, Tuple, Any + +from pyrogram.api import types +from pyrogram.client.ext import utils +from .storage import Storage + + +def get_input_peer(peer_id: int, access_hash: int, peer_type: str): + if peer_type in ["user", "bot"]: + return types.InputPeerUser( + user_id=peer_id, + access_hash=access_hash + ) + + if peer_type == "group": + return types.InputPeerChat( + chat_id=-peer_id + ) + + if peer_type in ["channel", "supergroup"]: + return types.InputPeerChannel( + channel_id=utils.get_channel_id(peer_id), + access_hash=access_hash + ) + + raise ValueError("Invalid peer type: {}".format(peer_type)) + + +class SQLiteStorage(Storage): + VERSION = 2 + USERNAME_TTL = 8 * 60 * 60 + + def __init__(self, name: str): + super().__init__(name) + + self.conn = None # type: sqlite3.Connection + self.lock = Lock() + + def create(self): + with self.lock, self.conn: + with open(str(Path(__file__).parent / "schema.sql"), "r") as schema: + self.conn.executescript(schema.read()) + + self.conn.execute( + "INSERT INTO version VALUES (?)", + (self.VERSION,) + ) + + self.conn.execute( + "INSERT INTO sessions VALUES (?, ?, ?, ?, ?, ?)", + (2, None, None, 0, None, None) + ) + + def open(self): + raise NotImplementedError + + def save(self): + self.date(int(time.time())) + + with self.lock: + self.conn.commit() + + def close(self): + with self.lock: + self.conn.close() + + def delete(self): + raise NotImplementedError + + def update_peers(self, peers: List[Tuple[int, int, str, str, str]]): + with self.lock: + self.conn.executemany( + "REPLACE INTO peers (id, access_hash, type, username, phone_number)" + "VALUES (?, ?, ?, ?, ?)", + peers + ) + + def get_peer_by_id(self, peer_id: int): + r = self.conn.execute( + "SELECT id, access_hash, type FROM peers WHERE id = ?", + (peer_id,) + ).fetchone() + + if r is None: + raise KeyError("ID not found: {}".format(peer_id)) + + return get_input_peer(*r) + + def get_peer_by_username(self, username: str): + r = self.conn.execute( + "SELECT id, access_hash, type, last_update_on FROM peers WHERE username = ?", + (username,) + ).fetchone() + + if r is None: + raise KeyError("Username not found: {}".format(username)) + + if abs(time.time() - r[3]) > self.USERNAME_TTL: + raise KeyError("Username expired: {}".format(username)) + + return get_input_peer(*r[:3]) + + def get_peer_by_phone_number(self, phone_number: str): + r = self.conn.execute( + "SELECT id, access_hash, type FROM peers WHERE phone_number = ?", + (phone_number,) + ).fetchone() + + if r is None: + raise KeyError("Phone number not found: {}".format(phone_number)) + + return get_input_peer(*r) + + def _get(self): + attr = inspect.stack()[2].function + + return self.conn.execute( + "SELECT {} FROM sessions".format(attr) + ).fetchone()[0] + + def _set(self, value: Any): + attr = inspect.stack()[2].function + + with self.lock, self.conn: + self.conn.execute( + "UPDATE sessions SET {} = ?".format(attr), + (value,) + ) + + def _accessor(self, value: Any = object): + return self._get() if value == object else self._set(value) + + def dc_id(self, value: int = object): + return self._accessor(value) + + def test_mode(self, value: bool = object): + return self._accessor(value) + + def auth_key(self, value: bytes = object): + return self._accessor(value) + + def date(self, value: int = object): + return self._accessor(value) + + def user_id(self, value: int = object): + return self._accessor(value) + + def is_bot(self, value: bool = object): + return self._accessor(value) + + def version(self, value: int = object): + if value == object: + return self.conn.execute( + "SELECT number FROM version" + ).fetchone()[0] + else: + with self.lock, self.conn: + self.conn.execute( + "UPDATE version SET number = ?", + (value,) + ) diff --git a/pyrogram/client/storage/storage.py b/pyrogram/client/storage/storage.py index cd6438d4..49602750 100644 --- a/pyrogram/client/storage/storage.py +++ b/pyrogram/client/storage/storage.py @@ -16,8 +16,15 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import base64 +import struct +from typing import List, Tuple + class Storage: + SESSION_STRING_FORMAT = ">B?256sI?" + SESSION_STRING_SIZE = 351 + def __init__(self, name: str): self.name = name @@ -30,72 +37,47 @@ class Storage: def close(self): raise NotImplementedError - def destroy(self): + def delete(self): raise NotImplementedError - def update_peers(self, peers): + def update_peers(self, peers: List[Tuple[int, int, str, str, str]]): raise NotImplementedError - def get_peer_by_id(self, peer_id): + def get_peer_by_id(self, peer_id: int): raise NotImplementedError - def get_peer_by_username(self, username): + def get_peer_by_username(self, username: str): raise NotImplementedError - def get_peer_by_phone_number(self, phone_number): + def get_peer_by_phone_number(self, phone_number: str): + raise NotImplementedError + + def dc_id(self, value: int = object): + raise NotImplementedError + + def test_mode(self, value: bool = object): + raise NotImplementedError + + def auth_key(self, value: bytes = object): + raise NotImplementedError + + def date(self, value: int = object): + raise NotImplementedError + + def user_id(self, value: int = object): + raise NotImplementedError + + def is_bot(self, value: bool = object): raise NotImplementedError def export_session_string(self): - raise NotImplementedError - - @property - def peers_count(self): - raise NotImplementedError - - @property - def dc_id(self): - raise NotImplementedError - - @dc_id.setter - def dc_id(self, value): - raise NotImplementedError - - @property - def test_mode(self): - raise NotImplementedError - - @test_mode.setter - def test_mode(self, value): - raise NotImplementedError - - @property - def auth_key(self): - raise NotImplementedError - - @auth_key.setter - def auth_key(self, value): - raise NotImplementedError - - @property - def date(self): - raise NotImplementedError - - @date.setter - def date(self, value): - raise NotImplementedError - - @property - def user_id(self): - raise NotImplementedError - - @user_id.setter - def user_id(self, value): - raise NotImplementedError - - @property - def is_bot(self): - raise NotImplementedError - - @is_bot.setter - def is_bot(self, value): - raise NotImplementedError + return base64.urlsafe_b64encode( + struct.pack( + self.SESSION_STRING_FORMAT, + self.dc_id(), + self.test_mode(), + self.auth_key(), + self.user_id(), + self.is_bot() + ) + ).decode().rstrip("=") diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index 82a8a312..5413a12c 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -162,7 +162,7 @@ class Chat(Object): username=user.username, first_name=user.first_name, last_name=user.last_name, - photo=ChatPhoto._parse(client, user.photo, peer_id), + photo=ChatPhoto._parse(client, user.photo, peer_id, user.access_hash), restrictions=pyrogram.List([Restriction._parse(r) for r in user.restriction_reason]) or None, client=client ) @@ -175,7 +175,7 @@ class Chat(Object): id=peer_id, type="group", title=chat.title, - photo=ChatPhoto._parse(client, getattr(chat, "photo", None), peer_id), + photo=ChatPhoto._parse(client, getattr(chat, "photo", None), peer_id, 0), permissions=ChatPermissions._parse(getattr(chat, "default_banned_rights", None)), members_count=getattr(chat, "participants_count", None), client=client @@ -194,7 +194,7 @@ class Chat(Object): is_scam=getattr(channel, "scam", None), title=channel.title, username=getattr(channel, "username", None), - photo=ChatPhoto._parse(client, getattr(channel, "photo", None), peer_id), + photo=ChatPhoto._parse(client, getattr(channel, "photo", None), peer_id, channel.access_hash), restrictions=pyrogram.List([Restriction._parse(r) for r in restriction_reason]) or None, permissions=ChatPermissions._parse(getattr(channel, "default_banned_rights", None)), members_count=getattr(channel, "participants_count", None), diff --git a/pyrogram/client/types/user_and_chats/chat_photo.py b/pyrogram/client/types/user_and_chats/chat_photo.py index 914f8d59..498ac72c 100644 --- a/pyrogram/client/types/user_and_chats/chat_photo.py +++ b/pyrogram/client/types/user_and_chats/chat_photo.py @@ -20,6 +20,7 @@ from struct import pack import pyrogram from pyrogram.api import types +from pyrogram.client.ext import utils from ..object import Object from ...ext.utils import encode @@ -50,7 +51,7 @@ class ChatPhoto(Object): self.big_file_id = big_file_id @staticmethod - def _parse(client, chat_photo: types.UserProfilePhoto or types.ChatPhoto, peer_id: int): + def _parse(client, chat_photo: types.UserProfilePhoto or types.ChatPhoto, peer_id: int, peer_access_hash: int): if not isinstance(chat_photo, (types.UserProfilePhoto, types.ChatPhoto)): return None @@ -58,24 +59,14 @@ class ChatPhoto(Object): loc_small = chat_photo.photo_small loc_big = chat_photo.photo_big - try: - # We just want a local storage lookup by id, whose method is not async. - # Otherwise we have to turn this _parse method async and also all the other methods that use this one. - peer = client.storage.get_peer_by_id(peer_id) - except KeyError: - return None + peer_type = utils.get_peer_type(peer_id) - if isinstance(peer, types.InputPeerUser): - peer_id = peer.user_id - peer_access_hash = peer.access_hash + if peer_type == "user": x = 0 - elif isinstance(peer, types.InputPeerChat): - peer_id = -peer.chat_id - peer_access_hash = 0 + elif peer_type == "chat": x = -1 else: peer_id += 1000727379968 - peer_access_hash = peer.access_hash x = -234 return ChatPhoto( diff --git a/pyrogram/client/types/user_and_chats/user.py b/pyrogram/client/types/user_and_chats/user.py index 13068ff7..513eb757 100644 --- a/pyrogram/client/types/user_and_chats/user.py +++ b/pyrogram/client/types/user_and_chats/user.py @@ -187,7 +187,7 @@ class User(Object, Update): language_code=user.lang_code, dc_id=getattr(user.photo, "dc_id", None), phone_number=user.phone, - photo=ChatPhoto._parse(client, user.photo, user.id), + photo=ChatPhoto._parse(client, user.photo, user.id, user.access_hash), restrictions=pyrogram.List([Restriction._parse(r) for r in user.restriction_reason]) or None, client=client ) diff --git a/pyrogram/session/auth.py b/pyrogram/session/auth.py index ba0f5aed..74bbab50 100644 --- a/pyrogram/session/auth.py +++ b/pyrogram/session/auth.py @@ -38,7 +38,7 @@ class Auth: def __init__(self, client: "pyrogram.Client", dc_id: int): self.dc_id = dc_id - self.test_mode = client.storage.test_mode + self.test_mode = client.storage.test_mode() self.ipv6 = client.ipv6 self.proxy = client.proxy diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 858956e0..89c13f68 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -114,7 +114,7 @@ class Session: while True: self.connection = Connection( self.dc_id, - self.client.storage.test_mode, + self.client.storage.test_mode(), self.client.ipv6, self.client.proxy )