├── .gitignore ├── LICENSE ├── README.md ├── __init__.py ├── __main__.py ├── config.ini.sample ├── requirements.txt └── src ├── Colored.py ├── __init__.py ├── args.py ├── client.py ├── config.py ├── updater.py └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .nox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | .hypothesis/ 49 | .pytest_cache/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | db.sqlite3 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Jupyter Notebook 74 | .ipynb_checkpoints 75 | 76 | # IPython 77 | profile_default/ 78 | ipython_config.py 79 | 80 | # pyenv 81 | .python-version 82 | 83 | # celery beat schedule file 84 | celerybeat-schedule 85 | 86 | # SageMath parsed files 87 | *.sage.py 88 | 89 | # Environments 90 | .env 91 | .venv 92 | env/ 93 | venv/ 94 | ENV/ 95 | env.bak/ 96 | venv.bak/ 97 | 98 | # Spyder project settings 99 | .spyderproject 100 | .spyproject 101 | 102 | # Rope project settings 103 | .ropeproject 104 | 105 | # mkdocs documentation 106 | /site 107 | 108 | # mypy 109 | .mypy_cache/ 110 | .dmypy.json 111 | dmypy.json 112 | 113 | # Pyre type checker 114 | .pyre/ 115 | *.session 116 | *.session-journal 117 | *.ini -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 pokurt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Generate Pyrogram session via QRlogin 2 | 3 | ### instructions 4 | - run git clone into a pyrogram project: `git clone https://github.com/pokurt/qr-Pyrogram-session session` 5 | - rename `config.ini.sample` to `config.ini` with your favourite text editor and fill in configs: 6 | - `mv session/config.ini.sample config.ini && nano config.ini` 7 | - run `pip install -r session/requirements.txt` 8 | - finally run `python -m session -s [session_name]` - session_name: str = 'any name you like to give your Client' 9 | - to get help menu `python -m session -h` 10 | 11 | ### Features 12 | - modular 13 | - supports bot session and user (qr code) session generation 14 | - can be used as a submodule with git in different pyrogram projects -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | from pyrogram import Client, errors 4 | from .src import args, app, APP_ID, APP_HASH 5 | from rich.logging import RichHandler 6 | 7 | FORMAT = "%(message)s" 8 | logging.basicConfig( 9 | level=logging.INFO, format=FORMAT, datefmt="[%X]", handlers=[RichHandler()] 10 | ) 11 | 12 | 13 | if args.token: 14 | app = Client( 15 | args.session_name or "pyrogram", 16 | api_id=APP_ID, 17 | api_hash=APP_HASH, 18 | bot_token=args.token 19 | ) 20 | try: 21 | app.start() 22 | except ( 23 | errors.AccessTokenInvalid, 24 | errors.AccessTokenExpired, 25 | errors.AuthKeyUnregistered 26 | ) as err: 27 | sys.exit(print(err)) 28 | me = app.get_me().first_name 29 | session_string = app.export_session_string() 30 | app.stop() 31 | logging.info( 32 | f"Generated session for {me}" 33 | ) 34 | sys.exit( 35 | print(f'SessionString:\n{session_string}\n\nquitting...') 36 | ) 37 | 38 | -------------------------------------------------------------------------------- /__main__.py: -------------------------------------------------------------------------------- 1 | from .src import app, check_session, create_qrcodes, nearest, raw_handler 2 | from pyrogram import idle, handlers 3 | import asyncio 4 | 5 | async def main(): 6 | await check_session(app, nearest.nearest_dc) 7 | await create_qrcodes() 8 | await idle() 9 | 10 | app.add_handler( 11 | handlers.RawUpdateHandler( 12 | raw_handler 13 | ) 14 | ) 15 | 16 | loop = asyncio.get_event_loop() 17 | loop.run_until_complete(main()) 18 | -------------------------------------------------------------------------------- /config.ini.sample: -------------------------------------------------------------------------------- 1 | [pyrogram] 2 | api_id = 123456 3 | api_hash = abcdefghijklmnop -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | qrcode>=7.3.1 2 | Pyrogram>=2.0.51 3 | TgCrypto>=1.2.3 4 | rich>=12.5.1 5 | -------------------------------------------------------------------------------- /src/Colored.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | from gettext import gettext 4 | 5 | class ColoredArgParser(argparse.ArgumentParser): 6 | 7 | # color_dict is a class attribute, here we avoid compatibility 8 | # issues by attempting to override the __init__ method 9 | # RED : Error, GREEN : Okay, YELLOW : Warning, Blue: Help/Info 10 | color_dict = {'RED' : '1;31', 'GREEN' : '1;32', 11 | 'YELLOW' : '1;33', 'BLUE' : '1;36'} 12 | 13 | def print_usage(self, file = None): 14 | if file is None: 15 | file = sys.stdout 16 | self._print_message(self.format_usage()[0].upper() + 17 | self.format_usage()[1:], 18 | file, self.color_dict['YELLOW']) 19 | 20 | def print_help(self, file = None): 21 | if file is None: 22 | file = sys.stdout 23 | self._print_message(self.format_help()[0].upper() + 24 | self.format_help()[1:], 25 | file, self.color_dict['BLUE']) 26 | 27 | def _print_message(self, message, file = None, color = None): 28 | if message: 29 | if file is None: 30 | file = sys.stderr 31 | # Print messages in bold, colored text if color is given. 32 | if color is None: 33 | file.write(message) 34 | else: 35 | # \x1b[ is the ANSI Control Sequence Introducer (CSI) 36 | file.write('\x1b[' + color + 'm' + message.strip() + '\x1b[0m\n') 37 | 38 | def exit(self, status = 0, message = None): 39 | if message: 40 | self._print_message(message, sys.stderr, self.color_dict['RED']) 41 | sys.exit(status) 42 | 43 | def error(self, message): 44 | self.print_usage(sys.stderr) 45 | args = {'prog' : self.prog, 'message': message} 46 | self.exit(2, gettext('%(prog)s: Error: %(message)s\n') % args) 47 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | from .updater import raw_handler 2 | from .client import app, args 3 | from .config import APP_ID, APP_HASH 4 | from .utils import check_session, create_qrcodes, nearest -------------------------------------------------------------------------------- /src/args.py: -------------------------------------------------------------------------------- 1 | args = [ 2 | { 3 | 'short_name': '-s', 4 | 'long_name': '--session_name', 5 | 'help': 'any name you want to give to your pyrogram.Client', 6 | 'type': str 7 | }, 8 | { 9 | 'short_name': '-v', 10 | 'long_name': '--app_version', 11 | 'help': 'a string classify as version to give to your pyrogram.Client', 12 | 'type': str 13 | }, 14 | { 15 | 'short_name': '-t', 16 | 'long_name': '--token', 17 | 'help': 'if generating session is for a bot to pass bot_token', 18 | 'type': str 19 | }, 20 | 21 | ] -------------------------------------------------------------------------------- /src/client.py: -------------------------------------------------------------------------------- 1 | from .config import APP_HASH, APP_ID 2 | from pyrogram import Client 3 | from .Colored import ColoredArgParser 4 | from .args import args as Args 5 | parser = ColoredArgParser() 6 | for arg in Args: 7 | parser.add_argument( 8 | arg['short_name'], 9 | arg['long_name'], 10 | help=arg['help'], 11 | type=arg['type'] 12 | ) 13 | args = parser.parse_args() 14 | 15 | app = Client(args.session_name or "pyrogram", api_id=APP_ID, api_hash=APP_HASH) -------------------------------------------------------------------------------- /src/config.py: -------------------------------------------------------------------------------- 1 | from configparser import ConfigParser, NoOptionError, NoSectionError 2 | import sys 3 | 4 | cfg = ConfigParser() 5 | cfg.read("config.ini") 6 | 7 | try: 8 | APP_ID = cfg.getint("pyrogram", "api_id") 9 | APP_HASH = cfg.get("pyrogram", "api_hash") 10 | except (NoOptionError, NoSectionError): 11 | # sys.exit(print('fill in configs before making the session.')) 12 | print("Find your App configs in https://my.telegram.org") 13 | APP_ID = int(input("Enter your api_id: ")) 14 | APP_HASH = input("Enter your api_hash: ") 15 | -------------------------------------------------------------------------------- /src/updater.py: -------------------------------------------------------------------------------- 1 | from .config import APP_ID, APP_HASH 2 | from pyrogram import Client, raw, errors, utils 3 | import asyncio 4 | from .utils import check_session, clear_screen, _gen_qr, nearest 5 | import sys 6 | 7 | ACCEPTED = False 8 | 9 | 10 | async def raw_handler(client: Client, update: raw.base.Update, users: list, chats: list): 11 | if isinstance(update, raw.types.auth.LoginToken) and nearest.nearest_dc != await client.storage.dc_id(): 12 | await check_session(client, dc_id=nearest.nearest_dc) 13 | if isinstance(update, raw.types.UpdateLoginToken): 14 | try: 15 | r = await client.invoke( 16 | raw.functions.auth.ExportLoginToken( 17 | api_id=APP_ID, api_hash=APP_HASH, except_ids=[] 18 | ) 19 | ) 20 | except errors.exceptions.unauthorized_401.SessionPasswordNeeded as err: 21 | await client.check_password(await utils.ainput("2FA Password: ", hide=True)) 22 | r = await client.invoke( 23 | raw.functions.auth.ExportLoginToken( 24 | api_id=APP_ID, api_hash=APP_HASH, except_ids=[] 25 | ) 26 | ) 27 | if isinstance(r, raw.types.auth.LoginTokenSuccess): 28 | me = (await client.get_me()).username 29 | session_string = await client.export_session_string() 30 | sys.exit( 31 | print( 32 | f"Generated session for {me}\n\nSessionString:\n{session_string}\n\nquitting..." 33 | ) 34 | ) 35 | elif isinstance(r, raw.types.auth.LoginTokenMigrateTo): 36 | await check_session(client, dc_id=r.dc_id) 37 | r = await client.invoke( 38 | raw.functions.auth.ExportLoginToken( 39 | api_id=APP_ID, api_hash=APP_HASH, except_ids=[] 40 | ) 41 | ) 42 | if isinstance(r, raw.types.auth.LoginToken): 43 | clear_screen() 44 | print('Auth DC Mismatched! please delete the last session and try again') 45 | await _gen_qr(r.token) 46 | await asyncio.sleep(30) 47 | -------------------------------------------------------------------------------- /src/utils.py: -------------------------------------------------------------------------------- 1 | from pyrogram.session import Session, Auth 2 | from pyrogram import Client, raw 3 | import asyncio 4 | import os 5 | from qrcode import QRCode 6 | from base64 import urlsafe_b64encode as base64url 7 | from subprocess import call 8 | from .config import APP_ID, APP_HASH 9 | from .client import app 10 | 11 | qr = QRCode() 12 | 13 | 14 | async def check_session( 15 | client: Client, 16 | dc_id:int 17 | ): 18 | await client.session.stop() 19 | await client.storage.dc_id(dc_id) 20 | await client.storage.auth_key( 21 | await Auth( 22 | client, await client.storage.dc_id(), 23 | await client.storage.test_mode() 24 | ).create() 25 | ) 26 | client.session = Session( 27 | client, await client.storage.dc_id(), 28 | await client.storage.auth_key(), await client.storage.test_mode() 29 | ) 30 | return await client.session.start() 31 | 32 | 33 | async def clear_screen(): 34 | call(['cls' if os.name == 'nt' else 'clear'], shell=True) 35 | 36 | 37 | async def create_qrcodes(): 38 | if not app.is_initialized: 39 | await app.dispatcher.start() 40 | app.is_initialized = True 41 | while True: 42 | await clear_screen() 43 | print( 44 | 'Scan the QR code below:' 45 | ) 46 | print( 47 | 'Settings > Privacy and Security > Active Sessions > Scan QR Code' 48 | ) 49 | r = await app.invoke( 50 | raw.functions.auth.ExportLoginToken( 51 | api_id=APP_ID, api_hash=APP_HASH, except_ids=[] 52 | ) 53 | ) 54 | if isinstance(r, raw.types.auth.LoginToken): 55 | await _gen_qr(r.token) 56 | await asyncio.sleep(30) 57 | 58 | 59 | async def _gen_qr(token: bytes): 60 | token = base64url(token).decode("utf8") 61 | login_url = f"tg://login?token={token}" 62 | qr.clear() 63 | qr.add_data(login_url) 64 | qr.print_ascii() 65 | 66 | app.connect() 67 | nearest = app.invoke(raw.functions.help.GetNearestDc()) --------------------------------------------------------------------------------