patch fae93b3a3f74e5858d8bf4f090f8ed245894e4c9 Author: E. Bosch Date: Sun Jul 19 22:35:25 CEST 2026 * telegram: Add support for 2nd factor authentication (2FA) password for Telegram accounts that have it enabled. This will work consistenly with "ask_code" option whether True or False. Refactored authentication code to not produce too many login code requests to avoid bans. Improve error logging. Remove the use of aioconsole, use to_thread with standard functions instead. diff -rN -u old-irgramd/README.md new-irgramd/README.md --- old-irgramd/README.md 2026-07-25 09:08:06.237022345 +0200 +++ new-irgramd/README.md 2026-07-25 09:08:06.237022345 +0200 @@ -55,6 +55,7 @@ - Actions [pin message, channel photo] (receive) - Dialogs management - History +- Login code and 2FA password support for Telegram - Authentication and TLS for IRC - Multiple connections from IRC @@ -63,7 +64,6 @@ - [python] (>= v3.9) - [telethon] (tested with v1.28.5) - [tornado] (tested with v6.1.0) -- [aioconsole] (tested with v0.6.1) - [pyPAM] (optional, tested with v0.4.2-13.4 from deb, [legacy web](https://web.archive.org/web/20110316070059/http://www.pangalactic.org/PyPAM/)) ## Instalation diff -rN -u old-irgramd/irgramd new-irgramd/irgramd --- old-irgramd/irgramd 2026-07-25 09:08:06.237022345 +0200 +++ new-irgramd/irgramd 2026-07-25 09:08:06.237022345 +0200 @@ -78,7 +78,7 @@ # Define irgramd options tornado.options.define('api_hash', default=None, metavar='HASH', help='Telegram API Hash for your account (obtained from https://my.telegram.org/apps)') tornado.options.define('api_id', type=int, default=None, metavar='ID', help='Telegram API ID for your account (obtained from https://my.telegram.org/apps)') - tornado.options.define('ask_code', default=False, help='Ask authentication code (sent by Telegram) in console instead of "code" service command in IRC') + tornado.options.define('ask_code', default=False, help='Ask authentication code (sent by Telegram) and 2FA password (if enabled) in console instead of "code" service command in IRC') tornado.options.define('cache_dir', default='~/.cache/irgramd', metavar='PATH', help='Cache directory where telegram media is saved by default') tornado.options.define('char_in_encoding', default='utf-8', metavar='ENCODING', help='Character input encoding for IRC') tornado.options.define('char_out_encoding', default='utf-8', metavar='ENCODING', help='Character output encoding for IRC') diff -rN -u old-irgramd/service.py new-irgramd/service.py --- old-irgramd/service.py 2026-07-25 09:08:06.237022345 +0200 +++ new-irgramd/service.py 2026-07-25 09:08:06.237022345 +0200 @@ -8,12 +8,13 @@ from utils import compact_date, command, HELP from telethon import utils as tgutils +from telethon.errors.rpcerrorlist import SessionPasswordNeededError class service(command): def __init__(self, settings, telegram): self.commands = \ { # Command Handler Arguments Min Max Maxsplit - 'code': (self.handle_command_code, 1, 1, -1), + 'code': (self.handle_command_code, 1, 2, -1), 'dialog': (self.handle_command_dialog, 1, 2, -1), 'get': (self.handle_command_get, 2, 2, -1), 'help': (self.handle_command_help, 0, 1, -1), @@ -41,34 +42,46 @@ 'Your Telegram account is not authorized yet,', 'you must supply the code that Telegram sent to your phone', 'or another client that is currently connected', - 'use /msg {} code '.format(self.irc.service_user.irc_nick), - 'e.g. /msg {} code 12345'.format(self.irc.service_user.irc_nick) + 'use /msg or equivalent in your IRC client', + 'e.g. /msg {} code 12345'.format(self.irc.service_user.irc_nick), + 'If 2nd authentication factor (2FA) password is enabled in', + 'your account, you must provide it as well', + 'e.g. /msg {} code 12345 password'.format(self.irc.service_user.irc_nick), ) - async def handle_command_code(self, code=None, help=None): + async def handle_command_code(self, code=None, passw=None, help=None): if not help: if self.ask_code: reply = ('Code will be asked on console',) elif code.isdigit(): + valid_auth = True try: await self.tg.telegram_client.sign_in(code=code) + except SessionPasswordNeededError: + try: + await self.tg.telegram_client.sign_in(password=passw) + except: + reply = ('Invalid 2FA password',) + valid_auth = False except: reply = ('Invalid code',) - else: - reply = ('Valid code', 'Telegram account authorized') + valid_auth = False + if valid_auth: + reply = ('Valid authentication', 'Telegram account authorized') await self.tg.continue_auth() else: # not isdigit reply = ('Code must be numeric',) else: # HELP.brief or HELP.desc (first line) - reply = (' code Enter authorization code',) + reply = (' code Enter authorization code and 2FA password',) if help == HELP.desc: # rest of HELP.desc reply += \ ( - ' code ', + ' code []', 'Enter authorization code sent by Telegram to the phone or to', - 'another client connected.', - 'This authorization code usually is only needed the first time', + 'another client connected. If 2nd factor authentication (2FA) password', + 'is enabled in your account, must be provided too.', + 'This authentication usually is only needed the first time', 'that irgramd connects to Telegram with a given account.', ) return reply diff -rN -u old-irgramd/telegram.py new-irgramd/telegram.py --- old-irgramd/telegram.py 2026-07-25 09:08:06.237022345 +0200 +++ new-irgramd/telegram.py 2026-07-25 09:08:06.237022345 +0200 @@ -10,13 +10,14 @@ import logging import os import re -import aioconsole import asyncio import collections import telethon +from getpass import getpass from telethon import types as tgty, utils as tgutils from telethon.tl.functions.messages import GetMessagesReactionsRequest, GetFullChatRequest from telethon.tl.functions.channels import GetFullChannelRequest +from telethon.errors.rpcerrorlist import SessionPasswordNeededError # Local modules @@ -123,19 +124,41 @@ else: await self.telegram_client.connect() - while not await self.telegram_client.is_user_authorized(): + if not await self.telegram_client.is_user_authorized(): self.logger.info('Telegram account not authorized') await self.telegram_client.send_code_request(self.phone) self.auth_checked.set() if not self.ask_code: return - self.logger.info('You must provide the Login code that Telegram will ' - 'sent you via SMS or another connected client') - code = await aioconsole.ainput('Login code: ') + attempts_msg = ('After 3 failed authentication attempts, aborted. ' + 'Please restart irgramd to try again') + count = 1 + while not await self.telegram_client.is_user_authorized(): + if count > 3: + self.logger.error(attempts_msg) + return + await asyncio.to_thread(print, 'You must provide the Login code that Telegram will ' + 'sent you via SMS or another connected client') + code = await asyncio.to_thread(input, 'Login code: ') try: await self.telegram_client.sign_in(code=code) - except: - pass + except SessionPasswordNeededError: + count = 1 + while not await self.telegram_client.is_user_authorized(): + if count > 3: + self.logger.error(attempts_msg) + return + await asyncio.to_thread(print, '2nd factor authentication (2FA) password is enabled ' + 'in your account, you must provide it') + passw = await asyncio.to_thread(getpass, 'Password (not shown): ') + try: + await self.telegram_client.sign_in(password=passw) + except BaseException as err: + self.logger.error(repr(err)) + count += 1 + except BaseException as err: + self.logger.error(repr(err)) + count += 1 await self.continue_auth()