patch fae93b3a3f74e5858d8bf4f090f8ed245894e4c9
Author: E. Bosch <presidev@AT@gmail.com>
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 07:25:42.887736549 +0200
+++ new-irgramd/README.md 2026-07-25 07:25:42.887736549 +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 07:25:42.887736549 +0200
+++ new-irgramd/irgramd 2026-07-25 07:25:42.887736549 +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 07:25:42.887736549 +0200
+++ new-irgramd/service.py 2026-07-25 07:25:42.891736550 +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 <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>',
+ ' code <code> [<password>]',
'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 07:25:42.887736549 +0200
+++ new-irgramd/telegram.py 2026-07-25 07:25:42.891736550 +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()