patch 89bc0957a4fb31bf8066e338bc4548cd2d52c821 Author: E. Bosch Date: Sat Nov 2 20:20:45 CET 2024 * telegram, irc: Set topic in IRC with Telegram channel/chat description patch 86ef89f9f9220ecde8477db2aa673f94f32656ab Author: E. Bosch Date: Sat Oct 26 20:43:28 CEST 2024 * telegram: Fix op and founder detection in channels patch 6d99fce9643fbdda1594f99331f4a4642ecc7f0f Author: E. Bosch 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 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 Date: Mon Oct 21 00:54:16 CEST 2024 * README update patch abf1d31ddcf3cddd55844900065a3c3dd6bf9c67 Author: E. Bosch Date: Sun Oct 20 02:32:44 CEST 2024 * exclam: Add "-" parameter to "!react" to remove a reaction patch 5c1687a16a1e49af7b5f70e9449c447bbf7c9df9 Author: E. Bosch 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 Date: Sun Oct 13 23:00:24 CEST 2024 * telegram: Limit text length for reactions to twice as replies patch 42fe2f72d41ed36616e6a19b9388eec356679e38 Author: E. Bosch Date: Sat Oct 12 23:17:45 CEST 2024 * README update patch 873004141f83b60da113e8967a2148d9d33008be Author: E. Bosch 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 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 Date: Sun Oct 6 23:59:23 CEST 2024 * telegram: Fix regression in delete reaction event patch beacde93a685dd954f9823dc0a6fea4594b2c1e4 Author: E. Bosch Date: Sat Sep 28 02:53:06 CEST 2024 * telegram: Avoid duplicated reactions events in some cases patch b2f8fe9251a26c43e16ba1aadff8d71e64a5a7e9 Author: E. Bosch 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 Date: Wed Sep 25 01:36:06 CEST 2024 * telegram: Fix in reaction handler patch 7e550077a65e4737ca30a81e0decbcb4db0485a4 Author: E. Bosch Date: Fri Sep 20 23:50:13 CEST 2024 * telegram: Don't truncate text for reactions of replies patch f9ff84bf789b6bd5109c5953590ca41c438fe123 Author: E. Bosch Date: Sun Sep 15 23:50:10 CEST 2024 * telegram: Minor improvement in debug of relay methods patch 1d527812923bdf50653bb371185bec70c2abad40 Author: E. Bosch Date: Sun Sep 15 01:23:24 CEST 2024 * telegram: Improve a bit reactions handler patch b43b2bc6a4e9dcf0eaddb66ea3fd5abf7c95082b Author: E. Bosch Date: Sat Sep 7 23:20:27 CEST 2024 * Fix typo in a constant diff -rN -u old-irgramd/README.md new-irgramd/README.md --- old-irgramd/README.md 2024-11-26 08:58:32.361375030 +0100 +++ new-irgramd/README.md 2024-11-26 08:58:32.365375024 +0100 @@ -50,7 +50,7 @@ - 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 diff -rN -u old-irgramd/emoji2emoticon.py new-irgramd/emoji2emoticon.py --- old-irgramd/emoji2emoticon.py 2024-11-26 08:58:32.361375030 +0100 +++ new-irgramd/emoji2emoticon.py 2024-11-26 08:58:32.365375024 +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-26 08:58:32.361375030 +0100 +++ new-irgramd/exclam.py 2024-11-26 08:58:32.369375018 +0100 @@ -7,20 +7,24 @@ # can be found in the LICENSE file included in this project. import os -from telethon.errors.rpcerrorlist import MessageNotModifiedError, MessageAuthorRequiredError +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), - '!upl': (self.handle_command_upl, 1, 2, 2), + '!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 @@ -188,3 +192,33 @@ '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 |-', + 'React with to a message with ,', + '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-26 08:58:32.361375030 +0100 +++ new-irgramd/include.py 2024-11-26 08:58:32.369375018 +0100 @@ -10,4 +10,5 @@ VERSION = '0.2' NICK_MAX_LENGTH = 20 -CHAN_MAX_LENGHT = 50 +CHAN_MAX_LENGTH = 50 +MAX_LINE = 400 diff -rN -u old-irgramd/irc.py new-irgramd/irc.py --- old-irgramd/irc.py 2024-11-26 08:58:32.361375030 +0100 +++ new-irgramd/irc.py 2024-11-26 08:58:32.369375018 +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, MAX_LINE from irc_replies import irc_codes from utils import chunks, set_replace, split_lines from service import service @@ -259,7 +259,7 @@ real_chan = self.get_realcaps_name(chan) users_count = len(self.irc_channels[chan]) topic = await self.tg.get_channel_topic(chan, [None]) - await self.reply_code(user, 'RPL_LIST', (real_chan, users_count, topic)) + await self.reply_code(user, 'RPL_LIST', (real_chan, users_count, topic[:MAX_LINE])) await self.reply_code(user, 'RPL_LISTEND') async def handle_irc_names(self, user, channels): @@ -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): @@ -616,7 +612,7 @@ founder = list(self.irc_channels_founder[chan])[0] else: founder = self.service_user.irc_nick - await self.reply_code(user, 'RPL_TOPIC', (channel, topic)) + await self.reply_code(user, 'RPL_TOPIC', (channel, topic[:MAX_LINE])) await self.reply_code(user, 'RPL_TOPICWHOTIME', (channel, founder, timestamp)) async def irc_namelist(self, user, channel): diff -rN -u old-irgramd/service.py new-irgramd/service.py --- old-irgramd/service.py 2024-11-26 08:58:32.365375024 +0100 +++ new-irgramd/service.py 2024-11-26 08:58:32.369375018 +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: diff -rN -u old-irgramd/telegram.py new-irgramd/telegram.py --- old-irgramd/telegram.py 2024-11-26 08:58:32.365375024 +0100 +++ new-irgramd/telegram.py 2024-11-26 08:58:32.369375018 +0100 @@ -15,11 +15,12 @@ import collections import telethon from telethon import types as tgty, utils as tgutils -from telethon.tl.functions.messages import GetMessagesReactionsRequest +from telethon.tl.functions.messages import GetMessagesReactionsRequest, GetFullChatRequest +from telethon.tl.functions.channels import GetFullChannelRequest # 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 from utils import get_highlighted, fix_braces, format_timestamp, pretty, current_date @@ -71,6 +72,7 @@ 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() @@ -180,10 +182,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): @@ -306,8 +310,16 @@ else: entity = await self.telegram_client.get_entity(tid) entity_cache[0] = entity + if isinstance(entity, tgty.Channel): + full = await self.telegram_client(GetFullChannelRequest(channel=entity)) + elif isinstance(entity, tgty.Chat): + full = await self.telegram_client(GetFullChatRequest(chat_id=tid)) + else: + return '' entity_type = self.get_entity_type(entity, format='long') - return 'Telegram ' + entity_type + ' ' + str(tid) + ': ' + entity.title + topic = full.full_chat.about + sep = ': ' if topic else '' + return entity_type + sep + topic async def get_channel_creation(self, channel, entity_cache): tid = self.get_tid(channel) @@ -389,7 +401,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: @@ -399,7 +413,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): @@ -501,6 +515,25 @@ 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', pretty(event)) @@ -534,23 +567,12 @@ # 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) @@ -559,6 +581,29 @@ 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', pretty(event)) @@ -582,6 +627,9 @@ 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', pretty(event or message)) @@ -657,7 +705,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) @@ -665,14 +713,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', pretty(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: diff -rN -u old-irgramd/utils.py new-irgramd/utils.py --- old-irgramd/utils.py 2024-11-26 08:58:32.365375024 +0100 +++ new-irgramd/utils.py 2024-11-26 08:58:32.373375011 +0100 @@ -20,6 +20,8 @@ FILENAME_INVALID_CHARS = re.compile('[/{}<>()"\'\\|&#%?]') SIMPLE_URL = re.compile('http(|s)://[^ ]+') +from include import MAX_LINE + # Utilities class command: @@ -61,9 +63,8 @@ return (x + mark if n != length else x for n, x in enumerate(items, start=1)) def split_lines(message): - MAX = 400 messages_limited = [] - wr = textwrap.TextWrapper(width=MAX) + wr = textwrap.TextWrapper(width=MAX_LINE) # Split when Telegram original message has breaks messages = message.splitlines()