Update year in copyright notices --> to head
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.
patch 2922f89ba9e3b2a0983b0e564ce29105e670c3f2
Author: E. Bosch <presidev@AT@gmail.com>
Date: Tue Jul 14 11:30:59 CEST 2026
* service: In auth help add separator if there is init help only
patch 7f3352bbc9fe55c1c399495c13e36549390d9b07
Author: E. Bosch <presidev@AT@gmail.com>
Date: Tue Jul 14 01:15:16 CEST 2026
* Move auth help from irc.py to service.py
patch abf2b7ea17e019e9d6933da27c16e614eddbb8f7
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Jul 12 00:09:26 CEST 2026
* Update Telethon URL in README
patch 26fc7b94f1d10c6528a0de866eed28a552c31a36
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Jul 10 23:47:21 CEST 2026
* Fix line break in README
patch 96f3cc24071c7b58c1647b1996dac3fee46eb367
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Jul 10 22:56:40 CEST 2026
* Update year in copyright notices
diff -rN -u old-irgramd/README.md new-irgramd/README.md
--- old-irgramd/README.md 2026-07-25 09:15:23.681119071 +0200
+++ new-irgramd/README.md 2026-07-25 09:15:23.689119073 +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
@@ -125,7 +125,7 @@
## License
Copyright (c) 2019 Peter Bui <pbui@bx612.space>
-Copyright (c) 2020-2026 E. Bosch <presidev@AT@gmail.com>
+Copyright (c) 2020-2026 E. Bosch <presidev@AT@gmail.com>
Copyright (c) 2026 Miro (Mirochill on github.com)
Use of this source code is governed by a MIT style license that
@@ -134,7 +134,7 @@
[IRC]: https://en.wikipedia.org/wiki/Internet_Relay_Chat
[Telegram]: https://telegram.org/
[python]: https://www.python.org/
-[Telethon]: https://github.com/LonamiWebs/Telethon
+[Telethon]: https://codeberg.org/Lonami/Telethon
[irgramd primary repository]: https://src.presi.org/darcs/irgramd
[darcs]: http://darcs.net
[project management and secondary repository]: https://github.com/prsai/irgramd
diff -rN -u old-irgramd/irc.py new-irgramd/irc.py
--- old-irgramd/irc.py 2026-07-25 09:15:23.681119071 +0200
+++ new-irgramd/irc.py 2026-07-25 09:15:23.689119073 +0200
@@ -2,7 +2,7 @@
# irc.py: IRC server side implementation
#
# Copyright (c) 2019 Peter Bui <pbui@bx612.space>
-# Copyright (c) 2020-2024 E. Bosch <presidev@AT@gmail.com>
+# Copyright (c) 2020-2024,2026 E. Bosch <presidev@AT@gmail.com>
# Copyright (c) 2026 Miro (Mirochill on github.com)
#
# Use of this source code is governed by a MIT style license that
@@ -540,14 +540,7 @@
async def check_telegram_auth(self, user):
await self.tg.auth_checked.wait()
if not self.tg.authorized and not self.tg.ask_code:
- for line in (
- '----',
- '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.service_user.irc_nick),
- 'e.g. /msg {} code 12345'.format(self.service_user.irc_nick),
- ):
+ for line in self.service.auth_help():
await self.send_msg(self.service_user, user.irc_nick, line)
async def send_users_irc(self, prfx, command, params):
diff -rN -u old-irgramd/irgramd new-irgramd/irgramd
--- old-irgramd/irgramd 2026-07-25 09:15:23.681119071 +0200
+++ new-irgramd/irgramd 2026-07-25 09:15:23.689119073 +0200
@@ -3,7 +3,7 @@
# irgramd: IRC-Telegram gateway - Main file
#
# Copyright (c) 2019 Peter Bui <pbui@bx612.space>
-# Copyright (c) 2020-2024 E. Bosch <presidev@AT@gmail.com>
+# Copyright (c) 2020-2024,2026 E. Bosch <presidev@AT@gmail.com>
#
# Use of this source code is governed by a MIT style license that
# can be found in the LICENSE file included in this project.
@@ -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:15:23.685119072 +0200
+++ new-irgramd/service.py 2026-07-25 09:15:23.689119073 +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),
@@ -21,6 +22,7 @@
'mark_read': (self.handle_command_mark_read, 1, 1, -1),
}
self.ask_code = settings['ask_code']
+ self.init_help = settings['initial_help']
self.tg = telegram
self.irc = telegram.irc
self.tmp_ircnick = None
@@ -33,30 +35,53 @@
'to get help',
)
- async def handle_command_code(self, code=None, help=None):
+ def auth_help(self):
+ sep = '----' if self.init_help else ''
+ return (
+ sep,
+ '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 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, 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:15:23.689119073 +0200
+++ new-irgramd/telegram.py 2026-07-25 09:15:23.689119073 +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))