patch 86ef89f9f9220ecde8477db2aa673f94f32656ab
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Oct 26 20:43:28 CEST 2024
* telegram: Fix op and founder detection in channels
patch 6d99fce9643fbdda1594f99331f4a4642ecc7f0f
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Oct 26 20:35:11 CEST 2024
* telegram: Fix handler for next reactions when the event is empty
patch efaf8df4b3fa314997615e2fc07a3decb3a7cfba
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Oct 26 13:01:02 CEST 2024
* exclam: Reorder handler list so commands are listed ordered in help
patch 0cfc7e59b24fb1a1b279fc593f8d04d0648e3880
Author: E. Bosch <presidev@AT@gmail.com>
Date: Mon Oct 21 00:54:16 CEST 2024
* README update
patch abf1d31ddcf3cddd55844900065a3c3dd6bf9c67
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Oct 20 02:32:44 CEST 2024
* exclam: Add "-" parameter to "!react" to remove a reaction
patch 5c1687a16a1e49af7b5f70e9449c447bbf7c9df9
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Oct 19 22:56:31 CEST 2024
* telegram: On emoticon->emoji conversions for reactions,
when several emoticons can be mapped to an emoji,
keep first elements that are more probable allowed for reactions
patch 7db9d9a07af099b00a32a6ee0197c400a52240e5
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Oct 13 23:00:24 CEST 2024
* telegram: Limit text length for reactions to twice as replies
patch 42fe2f72d41ed36616e6a19b9388eec356679e38
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Oct 12 23:17:45 CEST 2024
* README update
patch 873004141f83b60da113e8967a2148d9d33008be
Author: E. Bosch <presidev@AT@gmail.com>
Date: Thu Oct 10 23:11:40 CEST 2024
* exclam: Add reaction (!react) command to send emoticon->emoji as Telegram reaction
patch 69a63d9f5f49ece35c5d552ba8b3081c8277490d
Author: E. Bosch <presidev@AT@gmail.com>
Date: Mon Oct 7 00:07:54 CEST 2024
* telegram, service: Move initial help to service module
add a line for "equivalent command"
patch c1ffe716a42ea01ca345d7a756b685d7174f99c5
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Oct 6 23:59:23 CEST 2024
* telegram: Fix regression in delete reaction event
patch beacde93a685dd954f9823dc0a6fea4594b2c1e4
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Sep 28 02:53:06 CEST 2024
* telegram: Avoid duplicated reactions events in some cases
patch b2f8fe9251a26c43e16ba1aadff8d71e64a5a7e9
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Sep 27 11:05:53 CEST 2024
* telegram: Add handler for next reactions (2nd, 3rd, etc.) that don't come from
the same events as 1st (why?!)
patch 8770a66d55d4d1c34e009fe5d0078f77c3be4d34
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Sep 25 01:36:06 CEST 2024
* telegram: Fix in reaction handler
patch 7e550077a65e4737ca30a81e0decbcb4db0485a4
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Sep 20 23:50:13 CEST 2024
* telegram: Don't truncate text for reactions of replies
patch f9ff84bf789b6bd5109c5953590ca41c438fe123
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Sep 15 23:50:10 CEST 2024
* telegram: Minor improvement in debug of relay methods
patch 1d527812923bdf50653bb371185bec70c2abad40
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Sep 15 01:23:24 CEST 2024
* telegram: Improve a bit reactions handler
patch b43b2bc6a4e9dcf0eaddb66ea3fd5abf7c95082b
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Sep 7 23:20:27 CEST 2024
* Fix typo in a constant
patch 6aaf9a2af5898f8b6ec1027a3ccb7e85f6893f22
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Sep 1 01:01:19 CEST 2024
* Increase virtual version to 0.2
Remove alpha status
patch a39c65dc932ee95b44b5a759cad3e413177fc5aa
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Aug 30 21:53:13 CEST 2024
* telegram: Add a cache of "volatile" events (delete, edit, react) to be shown in history
patch 95e72ac9b26835162b8ba997c5ff99edfd5d464e
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Aug 30 19:00:53 CEST 2024
* utils: Small optimization in pretty()
patch 799dcf8a6a7c8346af93e7f17841baf08db70e7c
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Aug 30 01:58:06 CEST 2024
* utils: Add current_date() shortcut
patch 6c991a90a37dcc5a96906992b5c4df41e7f68991
Author: E. Bosch <presidev@AT@gmail.com>
Date: Thu Aug 29 23:06:59 CEST 2024
* Add trailing commas (and some spacing)
patch 6057cbb6c30094c80cba9f6326a5b513a9ab540c
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Aug 25 01:21:01 CEST 2024
* utils, telegram: Add pretty() function to print readable objects in debug
patch 0fd4392905932f144e14844f164d801a22b68467
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Aug 18 14:01:26 CEST 2024
* exclam: Add re-upload (!reupl) command to upload files/media as a reply to a message
patch 477b15fc239d17cccf626d042db2f323e1fa1b4b
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Aug 18 13:58:56 CEST 2024
* exclam: Check valid range for message IDs
patch 35206dbcb8c561df88e525160e5d6a50dad07481
Author: E. Bosch <presidev@AT@gmail.com>
Date: Thu Aug 15 01:30:54 CEST 2024
* Handle replies to deleted messages (maybe this case is only given from history)
patch ca113b48abe430eb11d500a5eb086edd15f23b0d
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Apr 28 19:45:06 CEST 2024
* Update copyright year in LICENSE
patch 3ad99bad13beb07c34a9a992b026ed923f39b047
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Apr 28 13:16:24 CEST 2024
* README update
patch c5d6314d751bbaca2ba4d24f4af0465af751ac40
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Apr 28 00:28:22 CEST 2024
* utils: Fix when a filename has no extension
patch 5d8ba95f7bbee459c4a9c7a0d524894ae8836c83
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Apr 27 20:32:49 CEST 2024
* exclam: Add command indicator to error messages
patch b287d9843e3a4854c7ab0dc15558546ab7fd4c86
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Apr 21 21:19:29 CEST 2024
* README update
patch d523591db91c8bf0ceb6ea65bac6358e5080f35a
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Apr 21 20:59:04 CEST 2024
* exclam: !upl: Add support for HTTP/HTTPS URL for file upload
patch 18c87eca10fbc53fd73e8adf6b4da64dd4f24109
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Apr 19 01:11:38 CEST 2024
* service: Disable by now the help for subcommands "archive" and "delete" from
command "dialog" as they are not really implemented yet
patch ca68ae9cfb315331dd08f8033ea2270e8a93e626
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Apr 14 22:48:30 CEST 2024
* exclam: Add upload (!upl) command to upload files/media to chats/channels
Add "upload_dir" option to define the local directory to pick the files up,
by default "~/.cache/irgramd/upload"
patch fa015f3a5b1ea9fe2b6c068491481d57837ecdc5
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Apr 14 02:13:34 CEST 2024
* telegram: Use directory ".cache/irgramd/media" instead of
".config/irgramd/media" by default (relative to home directory)
Added "cache_dir" option to override the default "~/.cache/irgramd"
patch 170328f9bde0160c50f56b98cbf51c8726ab4d69
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Apr 7 19:48:33 CEST 2024
* telegram: Fix a corner case in forward handler when saved_from_peer is not present
diff -rN -u old-irgramd/LICENSE new-irgramd/LICENSE
--- old-irgramd/LICENSE 2024-11-01 03:20:43.684623677 +0100
+++ new-irgramd/LICENSE 2024-11-01 03:20:43.692623664 +0100
@@ -1,7 +1,7 @@
MIT License
Copyright (c) 2019 Peter Bui <pbui@bx612.space>
-Copyright (c) 2020-2023 E. Bosch <presidev@AT@gmail.com>
+Copyright (c) 2020-2024 E. Bosch <presidev@AT@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff -rN -u old-irgramd/README.md new-irgramd/README.md
--- old-irgramd/README.md 2024-11-01 03:20:43.688623670 +0100
+++ new-irgramd/README.md 2024-11-01 03:20:43.692623664 +0100
@@ -13,7 +13,7 @@
**irgramd was forked from [pbui/irtelegramd], was heavily modified and
currently is a project on its own**
-**irgramd is under active development in alpha state, though usable, several
+**irgramd is under active development, though usable, several
planned features are not implemented yet**
## How it works
@@ -45,12 +45,12 @@
- Channels, groups and private chats
- Users and channels mapped in IRC
- Messages (receive, send)
-- Media in messages (receive, download)
+- Media in messages (receive, download, upload)
- Replies (receive, send)
- Forwards (receive, send)
- Deletions (receive, do)
- Editions (receive, do)
-- Reactions (receive)
+- Reactions (receive, send, remove)
- Polls (receive, show)
- Actions [pin message, channel photo] (receive)
- Dialogs management
@@ -116,6 +116,12 @@
(e.g. in Linux be in the shadow group or equivalent). The dependency is
totally optional, if not used, the module pyPAM is not needed.
+## Inspired by
+
+- [telegramircd]
+- [ibotg]
+- [bitlbee]
+
## License
Copyright (c) 2019 Peter Bui <pbui@bx612.space>
@@ -137,3 +143,6 @@
[aioconsole]: https://github.com/vxgmichel/aioconsole
[pyPAM]: https://packages.debian.org/bullseye/python3-pam
[BNC]: https://en.wikipedia.org/wiki/BNC_(software)
+[telegramircd]: https://github.com/prsai/telegramircd
+[ibotg]: https://github.com/prsai/ibotg
+[bitlbee]: https://www.bitlbee.org
diff -rN -u old-irgramd/emoji2emoticon.py new-irgramd/emoji2emoticon.py
--- old-irgramd/emoji2emoticon.py 2024-11-01 03:20:43.688623670 +0100
+++ new-irgramd/emoji2emoticon.py 2024-11-01 03:20:43.696623657 +0100
@@ -86,9 +86,13 @@
'\U0001f644': '"o o,"',
'\U0001f914': '":-L"',
'\U0001f92b': '":-o-m"',
- '\U0001f970': '":)e>"'
+ '\U0001f970': '":)e>"',
}
+emo_inv = { '-': None }
+for k in reversed(emo):
+ emo_inv[emo[k][1:-1]] = k
+
def replace_mult(line, emo):
for utf_emo in emo:
if utf_emo in line:
diff -rN -u old-irgramd/exclam.py new-irgramd/exclam.py
--- old-irgramd/exclam.py 2024-11-01 03:20:43.688623670 +0100
+++ new-irgramd/exclam.py 2024-11-01 03:20:43.696623657 +0100
@@ -6,18 +6,25 @@
# Use of this source code is governed by a MIT style license that
# can be found in the LICENSE file included in this project.
-from telethon.errors.rpcerrorlist import MessageNotModifiedError, MessageAuthorRequiredError
+import os
+from telethon.tl.functions.messages import SendReactionRequest
+from telethon import types as tgty
+from telethon.errors.rpcerrorlist import MessageNotModifiedError, MessageAuthorRequiredError, ReactionInvalidError
from utils import command, HELP
+from emoji2emoticon import emo_inv
class exclam(command):
def __init__(self, telegram):
self.commands = \
{ # Command Handler Arguments Min Max Maxsplit
- '!re': (self.handle_command_re, 2, 2, 2),
- '!ed': (self.handle_command_ed, 2, 2, 2),
'!del': (self.handle_command_del, 1, 1, -1),
+ '!ed': (self.handle_command_ed, 2, 2, 2),
'!fwd': (self.handle_command_fwd, 2, 2, -1),
+ '!re': (self.handle_command_re, 2, 2, 2),
+ '!react': (self.handle_command_react, 2, 2, -1),
+ '!reupl': (self.handle_command_reupl, 2, 3, 3),
+ '!upl': (self.handle_command_upl, 1, 2, 2),
}
self.tg = telegram
self.irc = telegram.irc
@@ -35,7 +42,7 @@
async def check_msg(self, cid):
id = self.tg.mid.id_to_num_offset(self.tmp_telegram_id, cid)
- if id is None:
+ if id is None or id < -2147483648 or id > 2147483647:
chk_msg = None
else:
chk_msg = await self.tg.telegram_client.get_messages(entity=self.tmp_telegram_id, ids=id)
@@ -48,7 +55,7 @@
self.tmp_tg_msg = await self.tg.telegram_client.send_message(self.tmp_telegram_id, msg, reply_to=id)
reply = True
else:
- reply = ('Unknown message to reply',)
+ reply = ('!re: Unknown message to reply',)
else: # HELP.brief or HELP.desc (first line)
reply = (' !re Reply to a message',)
if help == HELP.desc: # rest of HELP.desc
@@ -56,7 +63,7 @@
(
' !re <compact_id> <message>',
'Reply with <message> to a message with <compact_id> on current',
- 'channel/chat.'
+ 'channel/chat.',
)
return reply
@@ -70,7 +77,7 @@
self.tmp_tg_msg = ed_msg
reply = True
except MessageAuthorRequiredError:
- reply = ('Not the author of the message to edit',)
+ reply = ('!ed: Not the author of the message to edit',)
else:
reply = True
else:
@@ -82,7 +89,7 @@
(
' !ed <compact_id> <new_message>',
'Edit a message with <compact_id> on current channel/chat,',
- '<new_message> replaces the current message.'
+ '<new_message> replaces the current message.',
)
return reply
@@ -92,7 +99,7 @@
if del_msg is not None:
deleted = await self.tg.telegram_client.delete_messages(self.tmp_telegram_id, del_msg)
if deleted[0].pts_count == 0:
- reply = ('Not possible to delete',)
+ reply = ('!del: Not possible to delete',)
else:
self.tmp_tg_msg = None
reply = None
@@ -129,7 +136,7 @@
await send_fwd(tgt_ent, id)
reply = True
else:
- reply = ('Unknown chat to forward',)
+ reply = ('!fwd: Unknown chat to forward',)
else:
reply = ('Unknown message to forward',)
else: # HELP.brief or HELP.desc (first line)
@@ -141,3 +148,77 @@
'Forward a message with <compact_id> to <chat> channel/chat.'
)
return reply
+
+ async def handle_command_upl(self, file=None, caption=None, help=None, re_id=None):
+ if not help:
+ try:
+ if file[:8] == 'https://' or file[:7] == 'http://':
+ file_path = file
+ else:
+ file_path = os.path.join(self.tg.telegram_upload_dir, file)
+ self.tmp_tg_msg = await self.tg.telegram_client.send_file(self.tmp_telegram_id, file_path, caption=caption, reply_to=re_id)
+ reply = True
+ except:
+ cmd = '!reupl' if re_id else '!upl'
+ reply = ('{}: Error uploading'.format(cmd),)
+ else: # HELP.brief or HELP.desc (first line)
+ reply = (' !upl Upload a file to current channel/chat',)
+ if help == HELP.desc: # rest of HELP.desc
+ reply += \
+ (
+ ' !upl <file name/URL> [<optional caption>]',
+ 'Upload the file referenced by <file name/URL> to current',
+ 'channel/chat, the file must be present in "upload"',
+ 'irgramd local directory or be an external HTTP/HTTPS URL.',
+ )
+ return reply
+
+ async def handle_command_reupl(self, cid=None, file=None, caption=None, help=None):
+ if not help:
+ id, chk_msg = await self.check_msg(cid)
+ if chk_msg is not None:
+ reply = await self.handle_command_upl(file, caption, re_id=id)
+ else:
+ reply = ('!reupl: Unknown message to reply',)
+ else: # HELP.brief or HELP.desc (first line)
+ reply = (' !reupl Reply to a message with an upload',)
+ if help == HELP.desc: # rest of HELP.desc
+ reply += \
+ (
+ ' !reupl <compact_id> <file name/URL> [<optional caption>]',
+ 'Reply with the upload of <file name/URL> to a message with',
+ '<compact_id> on current channel/chat. The file must be',
+ 'present in "upload" irgramd local directory or be an external',
+ 'HTTP/HTTPS URL.',
+ )
+ return reply
+
+ async def handle_command_react(self, cid=None, act=None, help=None):
+ if not help:
+ id, chk_msg = await self.check_msg(cid)
+ if chk_msg is not None:
+ if act in emo_inv:
+ utf8_emo = emo_inv[act]
+ reaction = [ tgty.ReactionEmoji(emoticon=utf8_emo) ] if utf8_emo else None
+ try:
+ update = await self.tg.telegram_client(SendReactionRequest(self.tmp_telegram_id, id, reaction=reaction))
+ except ReactionInvalidError:
+ reply = ('!react: Reaction not allowed',)
+ else:
+ self.tmp_tg_msg = update.updates[0].message
+ reply = True
+ else:
+ reply = ('!react: Unknown reaction',)
+ else:
+ reply = ('!react: Unknown message to react',)
+ else: # HELP.brief or HELP.desc (first line)
+ reply = (' !react React to a message',)
+ if help == HELP.desc: # rest of HELP.desc
+ reply += \
+ (
+ ' !react <compact_id> <emoticon reaction>|-',
+ 'React with <emoticon reaction> to a message with <compact_id>,',
+ 'irgramd will translate emoticon to closest emoji.',
+ 'Use - to remove a previous reaction.',
+ )
+ return reply
diff -rN -u old-irgramd/include.py new-irgramd/include.py
--- old-irgramd/include.py 2024-11-01 03:20:43.688623670 +0100
+++ new-irgramd/include.py 2024-11-01 03:20:43.696623657 +0100
@@ -8,6 +8,6 @@
# Constants
-VERSION = '0.1'
+VERSION = '0.2'
NICK_MAX_LENGTH = 20
-CHAN_MAX_LENGHT = 50
+CHAN_MAX_LENGTH = 50
diff -rN -u old-irgramd/irc.py new-irgramd/irc.py
--- old-irgramd/irc.py 2024-11-01 03:20:43.688623670 +0100
+++ new-irgramd/irc.py 2024-11-01 03:20:43.696623657 +0100
@@ -18,7 +18,7 @@
# Local modules
-from include import VERSION, CHAN_MAX_LENGHT, NICK_MAX_LENGTH
+from include import VERSION, CHAN_MAX_LENGTH, NICK_MAX_LENGTH
from irc_replies import irc_codes
from utils import chunks, set_replace, split_lines
from service import service
@@ -529,14 +529,10 @@
await self.reply_code(user, 'RPL_ENDOFMOTD')
async def send_isupport(self, user):
- await self.reply_code(user, 'RPL_ISUPPORT', (CHAN_MAX_LENGHT, NICK_MAX_LENGTH))
+ await self.reply_code(user, 'RPL_ISUPPORT', (CHAN_MAX_LENGTH, NICK_MAX_LENGTH))
async def send_help(self, user):
- for line in (
- 'Welcome to irgramd service',
- 'use /msg {} help'.format(self.service_user.irc_nick),
- 'to get help',
- ):
+ for line in self.service.initial_help():
await self.send_msg(self.service_user, None, line, user)
async def check_telegram_auth(self, user):
diff -rN -u old-irgramd/irgramd new-irgramd/irgramd
--- old-irgramd/irgramd 2024-11-01 03:20:43.688623670 +0100
+++ new-irgramd/irgramd 2024-11-01 03:20:43.696623657 +0100
@@ -3,7 +3,7 @@
# irgramd: IRC-Telegram gateway - Main file
#
# Copyright (c) 2019 Peter Bui <pbui@bx612.space>
-# Copyright (c) 2020-2023 E. Bosch <presidev@AT@gmail.com>
+# Copyright (c) 2020-2024 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.
@@ -79,6 +79,7 @@
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('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')
tornado.options.define('config', default='irgramdrc', metavar='CONFIGFILE', help='Config file absolute or relative to `config_dir` (command line options override it)')
@@ -94,7 +95,7 @@
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_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 `config_dir`')
+ tornado.options.define('media_dir', default=None, metavar='PATH', help='Directory where Telegram media files are downloaded, default "media" in `cache_dir`')
tornado.options.define('media_url', default=None, metavar='BASE_URL', help='Base URL for media files, should be configured in the external (to irgramd) webserver')
tornado.options.define('pam', default=False, help='Use PAM for IRC authentication, if not set you should set `irc_password`')
tornado.options.define('pam_group', default=None, metavar='GROUP', help='Unix group allowed if `pam` enabled, if empty any user is allowed')
@@ -109,6 +110,7 @@
tornado.options.define('tls', default=False, help='Use TLS/SSL encrypted connection for IRC server')
tornado.options.define('tls_cert', default=None, metavar='CERTFILE', help='IRC server certificate chain for TLS/SSL, also can contain private key if not defined with `tls_key`')
tornado.options.define('tls_key', default=None, metavar='KEYFILE', help='IRC server private key for TLS/SSL')
+ tornado.options.define('upload_dir', default=None, metavar='PATH', help='Directory where files to upload are picked up, default "upload" in `cache_dir`')
try:
# parse cmd line first time to get --config and --config_dir
tornado.options.parse_command_line()
diff -rN -u old-irgramd/service.py new-irgramd/service.py
--- old-irgramd/service.py 2024-11-01 03:20:43.692623664 +0100
+++ new-irgramd/service.py 2024-11-01 03:20:43.696623657 +0100
@@ -25,6 +25,14 @@
self.irc = telegram.irc
self.tmp_ircnick = None
+ def initial_help(self):
+ return (
+ 'Welcome to irgramd service',
+ 'use /msg {} help'.format(self.irc.service_user.irc_nick),
+ 'or equivalent in your IRC client',
+ 'to get help',
+ )
+
async def handle_command_code(self, code=None, help=None):
if not help:
if self.ask_code:
@@ -91,8 +99,8 @@
' dialog <subcommand> [id]',
'Manage conversations (dialogs) established in Telegram, the',
'following subcommands are available:',
- ' archive <id> Archive the dialog specified by id',
- ' delete <id> Delete the dialog specified by id',
+# ' archive <id> Archive the dialog specified by id',
+# ' delete <id> Delete the dialog specified by id',
' list Show all dialogs',
)
return reply
@@ -249,7 +257,7 @@
(
' mark_read <peer>',
'Mark all messages on <peer> (channel or user) as read, this also will',
- 'reset the number of mentions to you on <peer>.'
+ 'reset the number of mentions to you on <peer>.',
)
return reply
diff -rN -u old-irgramd/telegram.py new-irgramd/telegram.py
--- old-irgramd/telegram.py 2024-11-01 03:20:43.692623664 +0100
+++ new-irgramd/telegram.py 2024-11-01 03:20:43.700623651 +0100
@@ -2,14 +2,13 @@
# telegram.py: Interface to Telethon Telegram library
#
# Copyright (c) 2019 Peter Bui <pbui@bx612.space>
-# Copyright (c) 2020-2023 E. Bosch <presidev@AT@gmail.com>
+# Copyright (c) 2020-2024 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.
import logging
import os
-import datetime
import re
import aioconsole
import asyncio
@@ -20,16 +19,17 @@
# Local modules
-from include import CHAN_MAX_LENGHT, NICK_MAX_LENGTH
+from include import CHAN_MAX_LENGTH, NICK_MAX_LENGTH
from irc import IRCUser
-from utils import sanitize_filename, add_filename, is_url_equiv, extract_url, get_human_size, get_human_duration, get_highlighted, fix_braces, format_timestamp
+from utils import sanitize_filename, add_filename, is_url_equiv, extract_url, get_human_size, get_human_duration
+from utils import get_highlighted, fix_braces, format_timestamp, pretty, current_date
import emoji2emoticon as e
# Test IP table
TEST_IPS = { 1: '149.154.175.10',
2: '149.154.167.40',
- 3: '149.154.175.117'
+ 3: '149.154.175.117',
}
# Telegram
@@ -38,10 +38,12 @@
def __init__(self, irc, settings):
self.logger = logging.getLogger()
self.config_dir = settings['config_dir']
+ self.cache_dir = settings['cache_dir']
self.download = settings['download_media']
self.notice_size = settings['download_notice'] * 1048576
self.media_dir = settings['media_dir']
self.media_url = settings['media_url']
+ self.upload_dir = settings['upload_dir']
self.api_id = settings['api_id']
self.api_hash = settings['api_hash']
self.phone = settings['phone']
@@ -66,16 +68,24 @@
self.webpending = {}
self.refwd_me = False
self.cache = collections.OrderedDict()
+ self.volatile_cache = collections.OrderedDict()
+ self.prev_id = {}
self.sorted_len_usernames = []
+ self.last_reaction = None
# Set event to be waited by irc.check_telegram_auth()
self.auth_checked = asyncio.Event()
async def initialize_telegram(self):
# Setup media folder
- self.telegram_media_dir = self.media_dir or os.path.join(self.config_dir, 'media')
+ self.telegram_media_dir = os.path.expanduser(self.media_dir or os.path.join(self.cache_dir, 'media'))
if not os.path.exists(self.telegram_media_dir):
os.makedirs(self.telegram_media_dir)
+ # Setup upload folder
+ self.telegram_upload_dir = os.path.expanduser(self.upload_dir or os.path.join(self.cache_dir, 'upload'))
+ if not os.path.exists(self.telegram_upload_dir):
+ os.makedirs(self.telegram_upload_dir)
+
# Setup session folder
self.telegram_session_dir = os.path.join(self.config_dir, 'session')
if not os.path.exists(self.telegram_session_dir):
@@ -95,10 +105,10 @@
# Register Telegram callbacks
callbacks = (
(self.handle_telegram_message , telethon.events.NewMessage),
- (self.handle_raw, telethon.events.Raw),
+ (self.handle_raw , telethon.events.Raw),
(self.handle_telegram_chat_action, telethon.events.ChatAction),
(self.handle_telegram_deleted , telethon.events.MessageDeleted),
- (self.handle_telegram_edited, telethon.events.MessageEdited),
+ (self.handle_telegram_edited , telethon.events.MessageEdited),
)
for handler, event in callbacks:
self.telegram_client.add_event_handler(handler, event)
@@ -171,10 +181,12 @@
if not user.is_self:
self.irc.irc_channels[chan].add(user_nick)
# Add admin users as ops in irc
- if isinstance(user.participant, tgty.ChatParticipantAdmin):
+ if isinstance(user.participant, tgty.ChatParticipantAdmin) or \
+ isinstance(user.participant, tgty.ChannelParticipantAdmin):
self.irc.irc_channels_ops[chan].add(user_nick)
# Add creator users as founders in irc
- elif isinstance(user.participant, tgty.ChatParticipantCreator):
+ elif isinstance(user.participant, tgty.ChatParticipantCreator) or \
+ isinstance(user.participant, tgty.ChannelParticipantCreator):
self.irc.irc_channels_founder[chan].add(user_nick)
def get_telegram_nick(self, user):
@@ -279,7 +291,7 @@
idle = 0
elif isinstance(user.status,tgty.UserStatusOffline):
last = user.status.was_online
- current = datetime.datetime.now(datetime.timezone.utc)
+ current = current_date()
idle = int((current - last).total_seconds())
elif isinstance(user.status,tgty.UserStatusLastWeek):
idle = 604800
@@ -380,7 +392,9 @@
)
async def get_reactions(m):
react = await self.telegram_client(GetMessagesReactionsRequest(m.peer_id, id=[m.id]))
- return react.updates[0].reactions.recent_reactions
+ updates = react.updates
+ r = next((x for x in updates if type(x) is tgty.UpdateMessageReactions), None)
+ return r.reactions.recent_reactions if r else None
react = None
if msg.reactions is None:
@@ -390,7 +404,7 @@
case = 'edition'
else:
case = 'react-del'
- elif react := next((x for x in reactions if x.date == msg.edit_date), None):
+ elif react := max(reactions, key=lambda y: y.date):
case = 'react-add'
else:
if msg_edited(msg):
@@ -401,17 +415,36 @@
return case, react
def to_cache(self, id, mid, message, proc_message, user, chan, media):
- if len(self.cache) >= 10000:
- self.cache.popitem(last=False)
+ self.limit_cache(self.cache)
self.cache[id] = {
'mid': mid,
'text': message,
'rendered_text': proc_message,
'user': user,
'channel': chan,
- 'media': media
+ 'media': media,
}
+ def to_volatile_cache(self, prev_id, id, ev, user, chan, date):
+ if chan in prev_id:
+ prid = prev_id[chan] if chan else prev_id[user]
+ self.limit_cache(self.volatile_cache)
+ elem = {
+ 'id': id,
+ 'rendered_event': ev,
+ 'user': user,
+ 'channel': chan,
+ 'date': date,
+ }
+ if prid not in self.volatile_cache:
+ self.volatile_cache[prid] = [elem]
+ else:
+ self.volatile_cache[prid].append(elem)
+
+ def limit_cache(self, cache):
+ if len(cache) >= 10000:
+ cache.popitem(last=False)
+
def replace_mentions(self, text, me_nick='', received=True):
# For received replace @mention to ~mention~
# For sent replace mention: to @mention
@@ -473,8 +506,27 @@
self.sorted_len_usernames.append(username)
self.sorted_len_usernames.sort(key=lambda k: len(k), reverse=True)
+ def format_reaction(self, msg, message_rendered, edition_case, reaction):
+ react_quote_len = self.quote_len * 2
+ if len(message_rendered) > react_quote_len:
+ text_old = '{}...'.format(message_rendered[:react_quote_len])
+ text_old = fix_braces(text_old)
+ else:
+ text_old = message_rendered
+
+ if edition_case == 'react-add':
+ user = self.get_irc_user_from_telegram(reaction.peer_id.user_id)
+ emoji = reaction.reaction.emoticon
+ react_action = '+'
+ react_icon = e.emo[emoji] if emoji in e.emo else emoji
+ elif edition_case == 'react-del':
+ user = self.get_irc_user_from_telegram(msg.sender_id)
+ react_action = '-'
+ react_icon = ''
+ return text_old, '{}{}'.format(react_action, react_icon), user
+
async def handle_telegram_edited(self, event):
- self.logger.debug('Handling Telegram Message Edited: %s', event)
+ self.logger.debug('Handling Telegram Message Edited: %s', pretty(event))
id = event.message.id
mid = self.mid.num_to_id_offset(event.message.peer_id, id)
@@ -506,32 +558,45 @@
# Reactions
else:
+ if reaction:
+ if self.last_reaction == reaction.date:
+ return
+ self.last_reaction = reaction.date
action = 'React'
- if len(message_rendered) > self.quote_len:
- text_old = '{}...'.format(message_rendered[:self.quote_len])
- text_old = fix_braces(text_old)
- else:
- text_old = message_rendered
-
- if edition_case == 'react-add':
- user = self.get_irc_user_from_telegram(reaction.peer_id.user_id)
- emoji = reaction.reaction.emoticon
- react_action = '+'
- react_icon = e.emo[emoji] if emoji in e.emo else emoji
- elif edition_case == 'react-del':
- user = self.get_irc_user_from_telegram(event.sender_id)
- react_action = '-'
- react_icon = ''
- edition_react = '{}{}'.format(react_action, react_icon)
+ text_old, edition_react, user = self.format_reaction(event.message, message_rendered, edition_case, reaction)
text = '|{} {}| {}'.format(action, text_old, edition_react)
chan = await self.relay_telegram_message(event, user, text)
self.to_cache(id, mid, message, message_rendered, user, chan, event.message.media)
+ self.to_volatile_cache(self.prev_id, id, text, user, chan, current_date())
+
+ async def handle_next_reaction(self, event):
+ self.logger.debug('Handling Telegram Next Reaction (2nd, 3rd, ...): %s', pretty(event))
+
+ reactions = event.reactions.recent_reactions
+ react = max(reactions, key=lambda y: y.date) if reactions else None
+
+ if react and self.last_reaction != react.date:
+ self.last_reaction = react.date
+ id = event.msg_id
+ msg = await self.telegram_client.get_messages(entity=event.peer, ids=id)
+ mid = self.mid.num_to_id_offset(msg.peer_id, id)
+ message = self.filters(msg.message)
+ message_rendered = await self.render_text(msg, mid, upd_to_webpend=None)
+
+ text_old, edition_react, user = self.format_reaction(msg, message_rendered, edition_case='react-add', reaction=react)
+
+ text = '|React {}| {}'.format(text_old, edition_react)
+
+ chan = await self.relay_telegram_message(msg, user, text)
+
+ self.to_cache(id, mid, message, message_rendered, user, chan, msg.media)
+ self.to_volatile_cache(self.prev_id, id, text, user, chan, current_date())
async def handle_telegram_deleted(self, event):
- self.logger.debug('Handling Telegram Message Deleted: %s', event)
+ self.logger.debug('Handling Telegram Message Deleted: %s', pretty(event))
for deleted_id in event.original_update.messages:
if deleted_id in self.cache:
@@ -540,20 +605,24 @@
user = self.cache[deleted_id]['user']
chan = self.cache[deleted_id]['channel']
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)
async def handle_raw(self, update):
- self.logger.debug('Handling Telegram Raw Event: %s', update)
+ self.logger.debug('Handling Telegram Raw Event: %s', pretty(update))
if isinstance(update, tgty.UpdateWebPage) and isinstance(update.webpage, tgty.WebPage):
message = self.webpending.pop(update.webpage.id, None)
if message:
await self.handle_telegram_message(event=None, message=message, upd_to_webpend=update.webpage)
+ elif isinstance(update, tgty.UpdateMessageReactions):
+ await self.handle_next_reaction(update)
+
async def handle_telegram_message(self, event, message=None, upd_to_webpend=None, history=False):
- self.logger.debug('Handling Telegram Message: %s', event or message)
+ self.logger.debug('Handling Telegram Message: %s', pretty(event or message))
msg = event.message if event else message
@@ -562,8 +631,11 @@
text = await self.render_text(msg, mid, upd_to_webpend, user)
text_send = self.set_history_timestamp(text, history, msg.date, msg.action)
chan = await self.relay_telegram_message(msg, user, text_send)
+ await self.history_search_volatile(history, msg.id)
self.to_cache(msg.id, mid, msg.message, text, user, chan, msg.media)
+ peer = chan if chan else user
+ self.prev_id[peer] = msg.id
self.refwd_me = False
@@ -602,6 +674,17 @@
res = text
return res
+ async def history_search_volatile(self, history, id):
+ if history:
+ if id in self.volatile_cache:
+ for item in self.volatile_cache[id]:
+ user = item['user']
+ text = item['rendered_event']
+ chan = item['channel']
+ date = item['date']
+ text_send = self.set_history_timestamp(text, history=True, date=date, action=False)
+ await self.relay_telegram_message(None, user, text_send, chan)
+
async def relay_telegram_message(self, message, user, text, channel=None):
private = (message and message.is_private) or (not message and not channel)
action = (message and message.action)
@@ -613,7 +696,7 @@
return chan
async def relay_telegram_private_message(self, user, message, action=None):
- self.logger.debug('Handling Telegram Private Message: %s, %s', user, message)
+ self.logger.debug('Relaying Telegram Private Message: %s, %s', user, message)
if action:
await self.irc.send_action(user, None, message)
@@ -621,14 +704,14 @@
await self.irc.send_msg(user, None, message)
async def relay_telegram_channel_message(self, message, user, text, channel, action):
- self.logger.debug('Handling Telegram Channel Message: %s', message or text)
-
if message:
entity = await message.get_chat()
chan = await self.get_irc_channel_from_telegram_id(message.chat_id, entity)
else:
chan = channel
+ self.logger.debug('Relaying Telegram Channel Message: %s, %s', chan, text)
+
if action:
await self.irc.send_action(user, chan, text)
else:
@@ -637,7 +720,7 @@
return chan
async def handle_telegram_chat_action(self, event):
- self.logger.debug('Handling Telegram Chat Action: %s', event)
+ self.logger.debug('Handling Telegram Chat Action: %s', pretty(event))
try:
tid = event.action_message.to_id.channel_id
@@ -686,18 +769,33 @@
space = ' '
trunc = ''
replied = await message.get_reply_message()
- replied_msg = replied.message
- cid = self.mid.num_to_id_offset(replied.peer_id, replied.id)
+ if replied:
+ replied_msg = replied.message
+ cid = self.mid.num_to_id_offset(replied.peer_id, replied.id)
+ replied_user = self.get_irc_user_from_telegram(replied.sender_id)
+ else:
+ replied_id = message.reply_to.reply_to_msg_id
+ cid = self.mid.num_to_id_offset(message.peer_id, replied_id)
+ if replied_id in self.cache:
+ text = self.cache[replied_id]['text']
+ replied_user = self.cache[replied_id]['user']
+ sp = ' '
+ else:
+ text = ''
+ replied_user = ''
+ sp = ''
+ replied_msg = '|Deleted|{}{}'.format(sp, text)
if not replied_msg:
replied_msg = ''
space = ''
elif len(replied_msg) > self.quote_len:
replied_msg = replied_msg[:self.quote_len]
trunc = '...'
- replied_user = self.get_irc_user_from_telegram(replied.sender_id)
if replied_user is None:
replied_nick = '{}'
self.refwd_me = True
+ elif replied_user == '':
+ replied_nick = ''
else:
replied_nick = replied_user.irc_nick
@@ -712,8 +810,8 @@
secondary_name = saved_peer_name
else:
# if it's from me I want to know who was the destination of a message (user)
- if self.refwd_me:
- secondary_name = self.get_irc_user_from_telegram(message.fwd_from.saved_from_peer.user_id).irc_nick
+ if self.refwd_me and (saved_from_peer := message.fwd_from.saved_from_peer) is not None:
+ secondary_name = self.get_irc_user_from_telegram(saved_from_peer.user_id).irc_nick
else:
secondary_name = ''
space2 = ''
diff -rN -u old-irgramd/utils.py new-irgramd/utils.py
--- old-irgramd/utils.py 2024-11-01 03:20:43.692623664 +0100
+++ new-irgramd/utils.py 2024-11-01 03:20:43.700623651 +0100
@@ -2,7 +2,7 @@
# utils.py: Helper functions
#
# Copyright (c) 2019 Peter Bui <pbui@bx612.space>
-# Copyright (c) 2020-2023 E. Bosch <presidev@AT@gmail.com>
+# Copyright (c) 2020-2024 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.
@@ -43,6 +43,9 @@
desc = 1
brief = 2
+class LOGL:
+ debug = False
+
def chunks(iterable, n, fillvalue=None):
''' Return iterable consisting of a sequence of n-length chunks '''
args = [iter(iterable)] * n
@@ -93,7 +96,10 @@
if add:
aux = filename.rsplit('.', 1)
name = aux[0]
- ext = aux[1]
+ try:
+ ext = aux[1]
+ except:
+ ext = ''
return '{}-{}.{}'.format(name, add, ext)
else:
return filename
@@ -149,7 +155,7 @@
return res
def compact_date(date, tz):
- delta = datetime.datetime.now(datetime.timezone.utc) - date
+ delta = current_date() - date
date_local = date.astimezone(zoneinfo.ZoneInfo(tz))
if delta.days < 1:
@@ -161,6 +167,9 @@
return compact_date
+def current_date():
+ return datetime.datetime.now(datetime.timezone.utc)
+
def get_highlighted(a, b):
awl = len(a.split())
bwl = len(b.split())
@@ -218,6 +227,8 @@
def parse_loglevel(level):
levelu = level.upper()
+ if levelu == 'DEBUG':
+ LOGL.debug = True
if levelu == 'NONE':
l = None
elif levelu in ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'):
@@ -225,3 +236,6 @@
else:
l = False
return l
+
+def pretty(object):
+ return object.stringify() if LOGL.debug and object else object