patch 9c530196db77dfde94d09e8dfbee848665084731
Author: Lucas de Sena <lucas@seninha.org>
Date: Sat Jul 25 00:27:23 CEST 2026
* Merge pull request #16 on github from phillbush/show-reactions
add "show_reactions" option
Set it to false if you do not want to be annoyed with reactions and emojis.
patch b785776c7df3016fb9c83aa7f7b9e51815435bab
Author: E. Bosch <presidev@AT@gmail.com>
Date: Thu Jul 23 18:06:45 CEST 2026
* Merge pull request #18 on github from phillbush/message-deleted-not-in-cache along with an option to configure the new behaviour
move "Message id ... deleted not in cache" into logger
TelegramServ can get filled with warnings like
Message id 012345 deleted not in cache
if you are in a channel/group that automatically deletes messages after
a certain time, and the deleted message is not in cache.
Warn that in the logger.
With "info" severity, because that's not actually an issue, just
something that can happen.
Add option "log_deleted" to enable this, by default false (use TelegramServ)
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 09:21:49.229206024 +0200
+++ new-irgramd/README.md 2026-07-25 09:21:49.229206024 +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:21:49.229206024 +0200
+++ new-irgramd/irgramd 2026-07-25 09:21:49.233206025 +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')
@@ -94,6 +94,7 @@
tornado.options.define('irc_nicks', type=str, multiple=True, metavar='nick,..', help='List of nicks allowed for IRC, if `pam` and optionally `pam_group` are set, PAM authentication will be used instead')
tornado.options.define('irc_password', default='', metavar='PASSWORD', help='Password for IRC authentication, if `pam` is set, PAM authentication will be used instead')
tornado.options.define('irc_port', type=int, default=None, metavar='PORT', help='Port to listen on for IRC. (default 6667, default with TLS 6697)')
+ tornado.options.define('log_deleted', default=False, help='Deleted messages not in cache are logged, if disabled service user [TelegramServ] will notify them')
tornado.options.define('log_file', default=None, metavar='PATH', help='File where logs are appended, if not set will be stderr')
tornado.options.define('log_level', default='INFO', metavar='DEBUG|INFO|WARNING|ERROR|CRITICAL|NONE', help='The log level (and any higher to it) that will be logged')
tornado.options.define('media_dir', default=None, metavar='PATH', help='Directory where Telegram media files are downloaded, default "media" in `cache_dir`')
@@ -103,6 +104,7 @@
tornado.options.define('phone', default=None, metavar='PHONE_NUMBER', help='Phone number associated with the Telegram account to receive the authorization codes if necessary')
tornado.options.define('quote_length', default=50, metavar='LENGTH', help='Max length of the text quoted in replies and reactions, if longer is truncated')
tornado.options.define('service_user', default='TelegramServ', metavar='SERVICE_NICK', help='Nick of the service/control user, must be a nick not used by a real Telegram user')
+ tornado.options.define('show_reactions', default=True, help='Show reactions to messages')
tornado.options.define('test', default=False, help='Connect to Telegram test environment')
tornado.options.define('test_datacenter', default=2, metavar='DATACENTER_NUMBER', help='Datacenter to connect to Telegram test environment')
tornado.options.define('test_host', default=None, metavar='HOST_IP', help='Host to connect to Telegram test environment (default: use a internal table depending on datacenter)')
diff -rN -u old-irgramd/service.py new-irgramd/service.py
--- old-irgramd/service.py 2026-07-25 09:21:49.229206024 +0200
+++ new-irgramd/service.py 2026-07-25 09:21:49.233206025 +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 09:21:49.229206024 +0200
+++ new-irgramd/telegram.py 2026-07-25 09:21:49.233206025 +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
@@ -56,9 +57,11 @@
self.test_port = settings['test_port']
self.ask_code = settings['ask_code']
self.quote_len = settings['quote_length']
+ self.show_react = settings['show_reactions']
self.hist_fmt = settings['hist_timestamp_format']
self.timezone = settings['timezone']
self.geo_url = settings['geo_url']
+ self.log_del = settings['log_deleted']
if not settings['emoji_ascii']:
e.emo = {}
self.token = token('+0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz')
@@ -123,19 +126,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()
@@ -573,6 +598,8 @@
# Reactions
else:
+ if not self.show_react:
+ return
if reaction:
if self.last_reaction == reaction.date:
return
@@ -588,6 +615,8 @@
self.to_volatile_cache(self.prev_id, id, text, user, chan, current_date())
async def handle_next_reaction(self, event):
+ if not self.show_react:
+ return
self.logger.debug('Handling Telegram Next Reaction (2nd, 3rd, ...): %s', pretty(event))
reactions = event.reactions.recent_reactions
@@ -622,8 +651,11 @@
await self.relay_telegram_message(message=None, user=user, text=text, channel=chan)
self.to_volatile_cache(self.prev_id, deleted_id, text, user, chan, current_date())
else:
- text = 'Message id {} deleted not in cache'.format(deleted_id)
- await self.relay_telegram_private_message(self.irc.service_user, text)
+ if self.log_del:
+ self.logger.info('Message id {} deleted not in cache'.format(deleted_id))
+ else:
+ text = 'Message id {} deleted not in cache'.format(deleted_id)
+ await self.relay_telegram_private_message(self.irc.service_user, text)
async def handle_raw(self, update):
self.logger.debug('Handling Telegram Raw Event: %s', pretty(update))