From 876506e038e9a93caa93edb745cce51fb9fab0a0 Mon Sep 17 00:00:00 2001 From: Daniil Isakov Date: Sat, 28 Feb 2026 04:14:08 +0300 Subject: [PATCH 1/5] chore(dependencies): update telethon to version 1.42.0 and add qrcode dependency --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b52dc17..c73c07c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,8 @@ dependencies = [ "nest-asyncio>=1.6.0", "python-dotenv>=1.1.0", "python-json-logger>=3.3.0", - "telethon>=1.39.0" + "qrcode>=8.2", + "telethon>=1.42.0", ] [project.urls] From 021298dae5f5458feedbc58f84907bdc467eec4a Mon Sep 17 00:00:00 2001 From: Daniil Isakov Date: Sat, 28 Feb 2026 04:14:15 +0300 Subject: [PATCH 2/5] chore(dependencies): update telethon to 1.42.0 and add qrcode dependency --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fca7260..f5906f8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,5 @@ mcp[cli]>=1.4.1 nest-asyncio>=1.6.0 python-dotenv>=1.1.0 python-json-logger>=3.3.0 -telethon>=1.39.0 \ No newline at end of file +qrcode>=8.2 +telethon>=1.42.0 \ No newline at end of file From fa11aaaf8817cb6a34d7499e0ba1f64a15799425 Mon Sep 17 00:00:00 2001 From: Daniil Isakov Date: Sat, 28 Feb 2026 04:14:22 +0300 Subject: [PATCH 3/5] feat(session_string_generator): implement QR code and phone number login methods for Telegram session generation --- session_string_generator.py | 157 ++++++++++++++++++++++++++---------- 1 file changed, 113 insertions(+), 44 deletions(-) diff --git a/session_string_generator.py b/session_string_generator.py index 21b15d9..9287e1f 100755 --- a/session_string_generator.py +++ b/session_string_generator.py @@ -19,26 +19,89 @@ parameters support integer IDs, string representations of IDs (e.g., "123456"), and usernames (e.g., "@mychannel"). """ +import asyncio +import io import os -from telethon.sync import TelegramClient -from telethon.sessions import StringSession -from dotenv import load_dotenv import sys -# Load environment variables from .env file +from dotenv import load_dotenv +from telethon import errors +from telethon.sessions import StringSession +from telethon.sync import TelegramClient + load_dotenv() +def _qr_login(client: TelegramClient) -> None: + import qrcode + + qr = client.qr_login() + + print("\n----- QR Code Login -----\n") + + qr_obj = qrcode.QRCode(border=1) + qr_obj.add_data(qr.url) + qr_obj.make(fit=True) + f = io.StringIO() + qr_obj.print_ascii(out=f, invert=True) + print(f.getvalue()) + + print("Scan the QR code above with your Telegram app:") + print(" Open Telegram > Settings > Devices > Link Desktop Device\n") + print(f"Or open this link on a device where you're logged in:\n {qr.url}\n") + print(f"Expires at: {qr.expires.strftime('%H:%M:%S')}") + print("Waiting for you to scan...") + + try: + client.loop.run_until_complete(qr.wait(timeout=120)) + except asyncio.TimeoutError: + print("\nQR code expired. Please try again.") + client.disconnect() + sys.exit(1) + except errors.SessionPasswordNeededError: + pw = input("\nTwo-factor authentication enabled. Please enter your password: ") + client.sign_in(password=pw) + + +def _phone_login(client: TelegramClient) -> None: + phone = input("Please enter your phone (or bot token): ") + + try: + client.send_code_request(phone) + except errors.FloodWaitError as e: + print( + f"\nFlood wait error; you must wait {e.seconds} seconds before trying again." + ) + client.disconnect() + sys.exit(1) + except errors.PhoneNumberInvalidError: + print("\nThe phone number is invalid.") + client.disconnect() + sys.exit(1) + except Exception as e: + print(f"\nError sending code: {e}") + client.disconnect() + sys.exit(1) + + code = input("\nPlease enter the code you received: ") + try: + client.sign_in(phone, code) + except errors.SessionPasswordNeededError: + pw = input("Two-factor authentication enabled. Please enter your password: ") + client.sign_in(password=pw) + + def main() -> None: API_ID = os.getenv("TELEGRAM_API_ID") API_HASH = os.getenv("TELEGRAM_API_HASH") if not API_ID or not API_HASH: print("Error: TELEGRAM_API_ID and TELEGRAM_API_HASH must be set in .env file") - print("Create an .env file with your credentials from https://my.telegram.org/apps") + print( + "Create an .env file with your credentials from https://my.telegram.org/apps" + ) sys.exit(1) - # Convert API_ID to integer try: API_ID = int(API_ID) except ValueError: @@ -47,56 +110,62 @@ def main() -> None: print("\n----- Telegram Session String Generator -----\n") print("This script will generate a session string for your Telegram account.") - print( - "You will be asked to enter your phone number and the verification code sent to your Telegram app." - ) print("The generated session string can be added to your .env file.") print( "\nYour credentials will NOT be stored on any server and are only used for local authentication.\n" ) + print("Choose login method:") + print(" 1) QR code login (recommended -- scan from your Telegram app)") + print(" 2) Phone number + verification code") + method = input("\nEnter 1 or 2 [default: 1]: ").strip() or "1" + try: - # Connect to Telegram and generate the session string - with TelegramClient(StringSession(), API_ID, API_HASH) as client: - # The client.session.save() function from StringSession returns the session string - session_string = StringSession.save(client.session) + client = TelegramClient(StringSession(), API_ID, API_HASH) + client.connect() - print("\nAuthentication successful!") - print("\n----- Your Session String -----") - print(f"\n{session_string}\n") - print("Add this to your .env file as:") - print(f"TELEGRAM_SESSION_STRING={session_string}") - print("\nIMPORTANT: Keep this string private and never share it with anyone!") + if not client.is_user_authorized(): + if method == "1": + _qr_login(client) + else: + _phone_login(client) - # Optional: auto-update the .env file - choice = input( - "\nWould you like to automatically update your .env file with this session string? (y/N): " - ) - if choice.lower() == "y": - try: - # Read the current .env file - with open(".env", "r") as file: - env_contents = file.readlines() + session_string = StringSession.save(client.session) - # Update or add the SESSION_STRING line - session_string_line_found = False - for i, line in enumerate(env_contents): - if line.startswith("TELEGRAM_SESSION_STRING="): - env_contents[i] = f"TELEGRAM_SESSION_STRING={session_string}\n" - session_string_line_found = True - break + print("\nAuthentication successful!") + print("\n----- Your Session String -----") + print(f"\n{session_string}\n") + print("Add this to your .env file as:") + print(f"TELEGRAM_SESSION_STRING={session_string}") + print("\nIMPORTANT: Keep this string private and never share it with anyone!") - if not session_string_line_found: - env_contents.append(f"TELEGRAM_SESSION_STRING={session_string}\n") + choice = input( + "\nWould you like to automatically update your .env file with this session string? (y/N): " + ) + if choice.lower() == "y": + try: + with open(".env", "r") as file: + env_contents = file.readlines() - # Write back to the .env file - with open(".env", "w") as file: - file.writelines(env_contents) + session_string_line_found = False + for i, line in enumerate(env_contents): + if line.startswith("TELEGRAM_SESSION_STRING="): + env_contents[i] = f"TELEGRAM_SESSION_STRING={session_string}\n" + session_string_line_found = True + break - print("\n.env file updated successfully!") - except Exception as e: - print(f"\nError updating .env file: {e}") - print("Please manually add the session string to your .env file.") + if not session_string_line_found: + env_contents.append(f"TELEGRAM_SESSION_STRING={session_string}\n") + + with open(".env", "w") as file: + file.writelines(env_contents) + + print("\n.env file updated successfully!") + except Exception as e: + print(f"\nError updating .env file: {e}") + print("Please manually add the session string to your .env file.") + + client.disconnect() except Exception as e: print(f"\nError: {e}") From 6d5e548937a7cdaa9bcc5bd35e9e4e3e24e84cbc Mon Sep 17 00:00:00 2001 From: Daniil Isakov Date: Sat, 28 Feb 2026 04:14:38 +0300 Subject: [PATCH 4/5] chore(dependencies): update uv.lock --- uv.lock | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/uv.lock b/uv.lock index d65eaea..98de4ec 100644 --- a/uv.lock +++ b/uv.lock @@ -825,6 +825,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, ] +[[package]] +name = "qrcode" +version = "8.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8f/b2/7fc2931bfae0af02d5f53b174e9cf701adbb35f39d69c2af63d4a39f81a9/qrcode-8.2.tar.gz", hash = "sha256:35c3f2a4172b33136ab9f6b3ef1c00260dd2f66f858f24d88418a015f446506c", size = 43317, upload-time = "2025-05-01T15:44:24.726Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/b8/d2d6d731733f51684bbf76bf34dab3b70a9148e8f2cef2bb544fccec681a/qrcode-8.2-py3-none-any.whl", hash = "sha256:16e64e0716c14960108e85d853062c9e8bba5ca8252c0b4d0231b9df4060ff4f", size = 45986, upload-time = "2025-05-01T15:44:22.781Z" }, +] + [[package]] name = "referencing" version = "0.37.0" @@ -1041,6 +1053,7 @@ dependencies = [ { name = "nest-asyncio" }, { name = "python-dotenv" }, { name = "python-json-logger" }, + { name = "qrcode" }, { name = "telethon" }, ] @@ -1060,7 +1073,8 @@ requires-dist = [ { name = "nest-asyncio", specifier = ">=1.6.0" }, { name = "python-dotenv", specifier = ">=1.1.0" }, { name = "python-json-logger", specifier = ">=3.3.0" }, - { name = "telethon", specifier = ">=1.39.0" }, + { name = "qrcode", specifier = ">=8.2" }, + { name = "telethon", specifier = ">=1.42.0" }, ] [package.metadata.requires-dev] @@ -1073,15 +1087,15 @@ dev = [ [[package]] name = "telethon" -version = "1.39.0" +version = "1.42.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyaes" }, { name = "rsa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/54/b2/6e48b593a0e4e678578a682332ef36e54648c78ef13a5d395e5cdd293c6d/telethon-1.39.0.tar.gz", hash = "sha256:35d4795d8c91deac515fb0bcb3723866b924de1c724e1d5c230460e96f284a63", size = 640634, upload-time = "2025-02-20T17:30:44.562Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/10/8c8c9476bfce767a856d8aaf9eae8ea1869df4e970da16f1c5b638fd1b0c/telethon-1.42.0.tar.gz", hash = "sha256:032e95511261d5ead719f75494c6c85ece2ce71816b54f3c65d6ccc371d6994d", size = 672734, upload-time = "2025-11-05T19:15:19.849Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/f2/4e66a59c6d0cb6a25c0c2a630b4b184d81cfe8cb982bf3b65ce32b020fc8/Telethon-1.39.0-py3-none-any.whl", hash = "sha256:aa9f394b94be144799a6f6a93ab463867bc7c63503ede9631751940a98f6c703", size = 715851, upload-time = "2025-02-20T17:30:41.955Z" }, + { url = "https://files.pythonhosted.org/packages/e4/e4/8ce0ff55251381966a7c3f88bd5b34abda79b225a8e7fb51ddef3b849c94/telethon-1.42.0-py3-none-any.whl", hash = "sha256:cf361c94586bcacd6d0fc8959a2bce509d1bb37007fe6476a80c4fb4a2decc29", size = 748466, upload-time = "2025-11-05T19:15:18.241Z" }, ] [[package]] From 03f332194258e0d72af4e4240f89c13296f1c7d1 Mon Sep 17 00:00:00 2001 From: Daniil Isakov Date: Thu, 5 Mar 2026 02:55:57 +0300 Subject: [PATCH 5/5] style: format session_string_generator.py with black --- session_string_generator.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/session_string_generator.py b/session_string_generator.py index 9287e1f..5a06d3b 100755 --- a/session_string_generator.py +++ b/session_string_generator.py @@ -69,9 +69,7 @@ def _phone_login(client: TelegramClient) -> None: try: client.send_code_request(phone) except errors.FloodWaitError as e: - print( - f"\nFlood wait error; you must wait {e.seconds} seconds before trying again." - ) + print(f"\nFlood wait error; you must wait {e.seconds} seconds before trying again.") client.disconnect() sys.exit(1) except errors.PhoneNumberInvalidError: @@ -97,9 +95,7 @@ def main() -> None: if not API_ID or not API_HASH: print("Error: TELEGRAM_API_ID and TELEGRAM_API_HASH must be set in .env file") - print( - "Create an .env file with your credentials from https://my.telegram.org/apps" - ) + print("Create an .env file with your credentials from https://my.telegram.org/apps") sys.exit(1) try: