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
hunk ./telegram.py 184
- if isinstance(user.participant, tgty.ChatParticipantAdmin):
+ if isinstance(user.participant, tgty.ChatParticipantAdmin) or \
+ isinstance(user.participant, tgty.ChannelParticipantAdmin):
hunk ./telegram.py 188
- elif isinstance(user.participant, tgty.ChatParticipantCreator):
+ elif isinstance(user.participant, tgty.ChatParticipantCreator) or \
+ isinstance(user.participant, tgty.ChannelParticipantCreator):
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
hunk ./telegram.py 577
- react = max(reactions, key=lambda y: y.date)
- [_$_]
- if self.last_reaction != react.date:
+ react = max(reactions, key=lambda y: y.date) if reactions else None
+
+ if react and self.last_reaction != react.date:
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
hunk ./exclam.py 21
- '!re': (self.handle_command_re, 2, 2, 2),
- '!ed': (self.handle_command_ed, 2, 2, 2),
hunk ./exclam.py 22
+ '!ed': (self.handle_command_ed, 2, 2, 2),
hunk ./exclam.py 24
- '!upl': (self.handle_command_upl, 1, 2, 2),
- '!reupl': (self.handle_command_reupl, 2, 3, 3),
+ '!re': (self.handle_command_re, 2, 2, 2),
hunk ./exclam.py 26
+ '!reupl': (self.handle_command_reupl, 2, 3, 3),
+ '!upl': (self.handle_command_upl, 1, 2, 2),
patch 0cfc7e59b24fb1a1b279fc593f8d04d0648e3880
Author: E. Bosch <presidev@AT@gmail.com>
Date: Mon Oct 21 00:54:16 CEST 2024
* README update
hunk ./README.md 53
-- Reactions (receive, send)
+- Reactions (receive, send, remove)
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
hunk ./emoji2emoticon.py 92
-emo_inv = {}
+emo_inv = { '-': None }
hunk ./exclam.py 202
- reaction = [ tgty.ReactionEmoji(emoticon=utf8_emo) ]
+ reaction = [ tgty.ReactionEmoji(emoticon=utf8_emo) ] if utf8_emo else None
hunk ./exclam.py 219
- ' !react <compact_id> <emoticon reaction>',
+ ' !react <compact_id> <emoticon reaction>|-',
hunk ./exclam.py 222
+ 'Use - to remove a previous 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
hunk ./emoji2emoticon.py 93
-for k in emo:
+for k in reversed(emo):
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
hunk ./telegram.py 508
- if len(message_rendered) > self.quote_len and not msg.is_reply:
- text_old = '{}...'.format(message_rendered[:self.quote_len])
+ react_quote_len = self.quote_len * 2
+ if len(message_rendered) > react_quote_len:
+ text_old = '{}...'.format(message_rendered[:react_quote_len])
patch 42fe2f72d41ed36616e6a19b9388eec356679e38
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Oct 12 23:17:45 CEST 2024
* README update
hunk ./README.md 53
-- Reactions (receive)
+- Reactions (receive, send)
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
hunk ./emoji2emoticon.py 89
- '\U0001f970': '":)e>"'
+ '\U0001f970': '":)e>"',
hunk ./emoji2emoticon.py 92
+emo_inv = {}
+for k in emo:
+ emo_inv[emo[k][1:-1]] = k
+
hunk ./exclam.py 10
-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
hunk ./exclam.py 15
+from emoji2emoticon import emo_inv
hunk ./exclam.py 27
+ '!react': (self.handle_command_react, 2, 2, -1),
hunk ./exclam.py 193
+ )
+ 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) ]
+ 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.',
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"
hunk ./irc.py 535
- 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():
hunk ./service.py 28
+ 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',
+ )
+
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
hunk ./telegram.py 520
- user = self.get_irc_user_from_telegram(event.sender_id)
+ user = self.get_irc_user_from_telegram(msg.sender_id)
hunk ./telegram.py 558
- if self.last_reaction == reaction.date:
- return
- self.last_reaction = reaction.date
+ if reaction:
+ if self.last_reaction == reaction.date:
+ return
+ self.last_reaction = reaction.date
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
hunk ./telegram.py 74
+ self.last_reaction = None
hunk ./telegram.py 558
+ if self.last_reaction == reaction.date:
+ return
+ self.last_reaction = reaction.date
hunk ./telegram.py 576
- 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())
+ [_$_]
+ if 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())
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?!)
hunk ./telegram.py 506
+ def format_reaction(self, msg, message_rendered, edition_case, reaction):
+ if len(message_rendered) > self.quote_len and not msg.is_reply:
+ 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 = ''
+ return text_old, '{}{}'.format(react_action, react_icon), user
+
hunk ./telegram.py 558
- if len(message_rendered) > self.quote_len and not event.message.is_reply:
- 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)
hunk ./telegram.py 567
+ 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)
+ 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())
+
hunk ./telegram.py 610
+ elif isinstance(update, tgty.UpdateMessageReactions):
+ await self.handle_next_reaction(update)
+
patch 8770a66d55d4d1c34e009fe5d0078f77c3be4d34
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Sep 25 01:36:06 CEST 2024
* telegram: Fix in reaction handler
hunk ./telegram.py 404
- elif react := next((x for x in reactions if x.date == msg.edit_date), None):
+ elif react := max(reactions, key=lambda y: y.date):
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
hunk ./telegram.py 540
- if len(message_rendered) > self.quote_len:
+ if len(message_rendered) > self.quote_len and not event.message.is_reply:
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
hunk ./telegram.py 662
- self.logger.debug('Handling Telegram Private Message: %s, %s', user, message)
+ self.logger.debug('Relaying Telegram Private Message: %s, %s', user, message)
hunk ./telegram.py 670
- self.logger.debug('Handling Telegram Channel Message: %s', pretty(message) or text)
-
hunk ./telegram.py 676
+ self.logger.debug('Relaying Telegram Channel Message: %s, %s', chan, text)
+
patch 1d527812923bdf50653bb371185bec70c2abad40
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Sep 15 01:23:24 CEST 2024
* telegram: Improve a bit reactions handler
hunk ./telegram.py 392
- 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
patch b43b2bc6a4e9dcf0eaddb66ea3fd5abf7c95082b
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Sep 7 23:20:27 CEST 2024
* Fix typo in a constant
hunk ./include.py 13
-CHAN_MAX_LENGHT = 50
+CHAN_MAX_LENGTH = 50
hunk ./irc.py 21
-from include import VERSION, CHAN_MAX_LENGHT, NICK_MAX_LENGTH
+from include import VERSION, CHAN_MAX_LENGTH, NICK_MAX_LENGTH
hunk ./irc.py 532
- 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))
hunk ./telegram.py 22
-from include import CHAN_MAX_LENGHT, NICK_MAX_LENGTH
+from include import CHAN_MAX_LENGTH, NICK_MAX_LENGTH
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
hunk ./README.md 16
-**irgramd is under active development in alpha state, though usable, several
+**irgramd is under active development, though usable, several
hunk ./include.py 11
-VERSION = '0.1'
+VERSION = '0.2'
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
hunk ./telegram.py 71
+ self.volatile_cache = collections.OrderedDict()
+ self.prev_id = {}
hunk ./telegram.py 413
- if len(self.cache) >= 10000:
- self.cache.popitem(last=False)
+ self.limit_cache(self.cache)
hunk ./telegram.py 423
+ 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)
+
hunk ./telegram.py 560
+ self.to_volatile_cache(self.prev_id, id, text, user, chan, current_date())
hunk ./telegram.py 572
+ self.to_volatile_cache(self.prev_id, deleted_id, text, user, chan, current_date())
hunk ./telegram.py 595
+ await self.history_search_volatile(history, msg.id)
hunk ./telegram.py 598
+ peer = chan if chan else user
+ self.prev_id[peer] = msg.id
hunk ./telegram.py 638
+ 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)
+
patch 95e72ac9b26835162b8ba997c5ff99edfd5d464e
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Aug 30 19:00:53 CEST 2024
* utils: Small optimization in pretty()
hunk ./utils.py 46
+class LOGL:
+ debug = False
+
hunk ./utils.py 230
+ if levelu == 'DEBUG':
+ LOGL.debug = True
hunk ./utils.py 241
- return object.stringify() if object else object
+ return object.stringify() if LOGL.debug and object else object
patch 799dcf8a6a7c8346af93e7f17841baf08db70e7c
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Aug 30 01:58:06 CEST 2024
* utils: Add current_date() shortcut
hunk ./telegram.py 12
-import datetime
hunk ./telegram.py 24
-from utils import sanitize_filename, add_filename, is_url_equiv, extract_url, get_human_size, get_human_duration, get_highlighted, fix_braces, format_timestamp, pretty
+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
hunk ./telegram.py 289
- current = datetime.datetime.now(datetime.timezone.utc)
+ current = current_date()
hunk ./utils.py 155
- delta = datetime.datetime.now(datetime.timezone.utc) - date
+ delta = current_date() - date
hunk ./utils.py 167
+def current_date():
+ return datetime.datetime.now(datetime.timezone.utc)
+
patch 6c991a90a37dcc5a96906992b5c4df41e7f68991
Author: E. Bosch <presidev@AT@gmail.com>
Date: Thu Aug 29 23:06:59 CEST 2024
* Add trailing commas (and some spacing)
hunk ./exclam.py 62
- 'channel/chat.'
+ 'channel/chat.',
hunk ./exclam.py 88
- '<new_message> replaces the current message.'
+ '<new_message> replaces the current message.',
hunk ./exclam.py 168
- 'irgramd local directory or be an external HTTP/HTTPS URL.'
+ 'irgramd local directory or be an external HTTP/HTTPS URL.',
hunk ./exclam.py 188
- 'HTTP/HTTPS URL.'
+ 'HTTP/HTTPS URL.',
hunk ./service.py 252
- 'reset the number of mentions to you on <peer>.'
+ 'reset the number of mentions to you on <peer>.',
hunk ./telegram.py 32
- 3: '149.154.175.117'
+ 3: '149.154.175.117',
hunk ./telegram.py 105
- (self.handle_raw, telethon.events.Raw),
+ (self.handle_raw , telethon.events.Raw),
hunk ./telegram.py 108
- (self.handle_telegram_edited, telethon.events.MessageEdited),
+ (self.handle_telegram_edited , telethon.events.MessageEdited),
hunk ./telegram.py 419
- 'media': media
+ 'media': media,
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
hunk ./telegram.py 25
-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, get_highlighted, fix_braces, format_timestamp, pretty
hunk ./telegram.py 484
- self.logger.debug('Handling Telegram Message Edited: %s', event)
+ self.logger.debug('Handling Telegram Message Edited: %s', pretty(event))
hunk ./telegram.py 541
- self.logger.debug('Handling Telegram Message Deleted: %s', event)
+ self.logger.debug('Handling Telegram Message Deleted: %s', pretty(event))
hunk ./telegram.py 555
- self.logger.debug('Handling Telegram Raw Event: %s', update)
+ self.logger.debug('Handling Telegram Raw Event: %s', pretty(update))
hunk ./telegram.py 563
- self.logger.debug('Handling Telegram Message: %s', event or message)
+ self.logger.debug('Handling Telegram Message: %s', pretty(event or message))
hunk ./telegram.py 631
- self.logger.debug('Handling Telegram Channel Message: %s', message or text)
+ self.logger.debug('Handling Telegram Channel Message: %s', pretty(message) or text)
hunk ./telegram.py 647
- self.logger.debug('Handling Telegram Chat Action: %s', event)
+ self.logger.debug('Handling Telegram Chat Action: %s', pretty(event))
hunk ./utils.py 231
+
+def pretty(object):
+ return object.stringify() if object else object
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
hunk ./exclam.py 23
+ '!reupl': (self.handle_command_reupl, 2, 3, 3),
hunk ./exclam.py 148
- async def handle_command_upl(self, file=None, caption=None, help=None):
+ async def handle_command_upl(self, file=None, caption=None, help=None, re_id=None):
hunk ./exclam.py 155
- self.tmp_tg_msg = await self.tg.telegram_client.send_file(self.tmp_telegram_id, file_path, caption=caption)
+ self.tmp_tg_msg = await self.tg.telegram_client.send_file(self.tmp_telegram_id, file_path, caption=caption, reply_to=re_id)
hunk ./exclam.py 158
- reply = ('!upl: Error uploading',)
+ cmd = '!reupl' if re_id else '!upl'
+ reply = ('{}: Error uploading'.format(cmd),)
hunk ./exclam.py 169
+ )
+ 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.'
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
hunk ./exclam.py 40
- if id is None:
+ if id is None or id < -2147483648 or id > 2147483647:
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)
hunk ./telegram.py 696
- 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)
hunk ./telegram.py 718
- replied_user = self.get_irc_user_from_telegram(replied.sender_id)
hunk ./telegram.py 721
+ elif replied_user == '':
+ replied_nick = ''
patch ca113b48abe430eb11d500a5eb086edd15f23b0d
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Apr 28 19:45:06 CEST 2024
* Update copyright year in LICENSE
hunk ./LICENSE 4
-Copyright (c) 2020-2023 E. Bosch <presidev@AT@gmail.com>
+Copyright (c) 2020-2024 E. Bosch <presidev@AT@gmail.com>
patch 3ad99bad13beb07c34a9a992b026ed923f39b047
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Apr 28 13:16:24 CEST 2024
* README update
hunk ./README.md 119
+## Inspired by
+
+- [telegramircd]
+- [ibotg]
+- [bitlbee]
+
hunk ./README.md 146
+[telegramircd]: https://github.com/prsai/telegramircd
+[ibotg]: https://github.com/prsai/ibotg
+[bitlbee]: https://www.bitlbee.org
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
hunk ./utils.py 5
-# Copyright (c) 2020-2023 E. Bosch <presidev@AT@gmail.com>
+# Copyright (c) 2020-2024 E. Bosch <presidev@AT@gmail.com>
hunk ./utils.py 96
- ext = aux[1]
+ try:
+ ext = aux[1]
+ except:
+ ext = ''
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
hunk ./exclam.py 53
- reply = ('Unknown message to reply',)
+ reply = ('!re: Unknown message to reply',)
hunk ./exclam.py 75
- reply = ('Not the author of the message to edit',)
+ reply = ('!ed: Not the author of the message to edit',)
hunk ./exclam.py 97
- reply = ('Not possible to delete',)
+ reply = ('!del: Not possible to delete',)
hunk ./exclam.py 134
- reply = ('Unknown chat to forward',)
+ reply = ('!fwd: Unknown chat to forward',)
hunk ./exclam.py 157
- reply = ('Error uploading',)
+ reply = ('!upl: Error uploading',)
patch b287d9843e3a4854c7ab0dc15558546ab7fd4c86
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Apr 21 21:19:29 CEST 2024
* README update
hunk ./README.md 48
-- Media in messages (receive, download)
+- Media in messages (receive, download, upload)
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
hunk ./exclam.py 150
- file_path = os.path.join(self.tg.telegram_upload_dir, file)
+ if file[:8] == 'https://' or file[:7] == 'http://':
+ file_path = file
+ else:
+ file_path = os.path.join(self.tg.telegram_upload_dir, file)
hunk ./exclam.py 163
- ' !upl <file name> [<optional caption>]',
- 'Upload the file referenced by <file name> to current ',
+ ' !upl <file name/URL> [<optional caption>]',
+ 'Upload the file referenced by <file name/URL> to current',
hunk ./exclam.py 166
- 'local directory.'
+ 'irgramd local directory or be an external HTTP/HTTPS URL.'
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
hunk ./service.py 94
- ' 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',
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"
hunk ./exclam.py 9
+import os
hunk ./exclam.py 22
+ '!upl': (self.handle_command_upl, 1, 2, 2),
hunk ./exclam.py 144
+ )
+ return reply
+
+ async def handle_command_upl(self, file=None, caption=None, help=None):
+ if not help:
+ try:
+ 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 = True
+ except:
+ reply = ('Error uploading',)
+ 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> [<optional caption>]',
+ 'Upload the file referenced by <file name> to current ',
+ 'channel/chat, the file must be present in "upload"',
+ 'local directory.'
hunk ./irgramd 113
+ tornado.options.define('upload_dir', default=None, metavar='PATH', help='Directory where files to upload are picked up, default "upload" in `cache_dir`')
hunk ./telegram.py 46
+ self.upload_dir = settings['upload_dir']
hunk ./telegram.py 81
+ # 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)
+
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"
hunk ./irgramd 6
-# Copyright (c) 2020-2023 E. Bosch <presidev@AT@gmail.com>
+# Copyright (c) 2020-2024 E. Bosch <presidev@AT@gmail.com>
hunk ./irgramd 82
+ tornado.options.define('cache_dir', default='~/.cache/irgramd', metavar='PATH', help='Cache directory where telegram media is saved by default')
hunk ./irgramd 98
- 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`')
hunk ./telegram.py 41
+ self.cache_dir = settings['cache_dir']
hunk ./telegram.py 76
- 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'))
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
hunk ./telegram.py 5
-# Copyright (c) 2020-2023 E. Bosch <presidev@AT@gmail.com>
+# Copyright (c) 2020-2024 E. Bosch <presidev@AT@gmail.com>
hunk ./telegram.py 715
- 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
patch 734e8c9f78627a6536e3ff52bd2db4879737830d
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Apr 7 19:08:52 CEST 2024
* README update
hunk ./README.md 50
-- Forwards (receive)
+- Forwards (receive, send)
hunk ./README.md 122
-Copyright (c) 2020-2023 E. Bosch <presidev@AT@gmail.com>
+Copyright (c) 2020-2024 E. Bosch <presidev@AT@gmail.com>
patch 4bb5866f7a3278949670e153444b9b1db74344ad
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Apr 7 19:07:04 CEST 2024
* exclam: Add forward (!fwd) command to forward messages to other channels or chats
hunk ./exclam.py 4
-# Copyright (c) 2023 E. Bosch <presidev@AT@gmail.com>
+# Copyright (c) 2023, 2024 E. Bosch <presidev@AT@gmail.com>
hunk ./exclam.py 20
+ '!fwd': (self.handle_command_fwd, 2, 2, -1),
hunk ./exclam.py 108
+ )
+ return reply
+
+ async def handle_command_fwd(self, cid=None, chat=None, help=None):
+ if not help:
+ id, chk_msg = await self.check_msg(cid)
+ if chk_msg is not None:
+ async def send_fwd(tgt_ent, id):
+ from_ent = await self.tg.telegram_client.get_entity(self.tmp_telegram_id)
+ self.tmp_tg_msg = await self.tg.telegram_client.forward_messages(tgt_ent, id, from_ent)
+ return self.tmp_tg_msg
+
+ tgt = chat.lower()
+ if tgt in self.irc.iid_to_tid:
+ tgt_ent = await self.tg.telegram_client.get_entity(self.irc.iid_to_tid[tgt])
+ msg = await send_fwd(tgt_ent, id)
+ # echo fwded message
+ await self.tg.handle_telegram_message(event=None, message=msg)
+ reply = True
+ elif tgt in (u.irc_nick.lower() for u in self.irc.users.values() if u.stream):
+ tgt_ent = await self.tg.telegram_client.get_me()
+ await send_fwd(tgt_ent, id)
+ reply = True
+ else:
+ reply = ('Unknown chat to forward',)
+ else:
+ reply = ('Unknown message to forward',)
+ else: # HELP.brief or HELP.desc (first line)
+ reply = (' !fwd Forward a message',)
+ if help == HELP.desc: # rest of HELP.desc
+ reply += \
+ (
+ ' !fwd <compact_id> <chat>',
+ 'Forward a message with <compact_id> to <chat> channel/chat.'
patch aa90a8fae2fb0ed855e80f146dc88cbf7069a2dd
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Dec 31 01:26:30 CET 2023
* README update
hunk ./README.md 108
-In background (without logs):
-
- ./irgramd --logging=none &
+In background (with logs):
+
+ ./irgramd --log-file=irgramd.log &
patch f7068578a6b2806e38c5c5bfc1c03b8e59a14455
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Dec 20 01:50:56 CET 2023
* Fix logging system.
Remove logging options from tornado.log that were not working correctly in this setup
and use the new options from irgramd ("log_file" and "log_level").
Defer first logs to be included in log file opened later.
Improve option error handling.
hunk ./irgramd 23
+from utils import parse_loglevel
hunk ./irgramd 69
- logger = logging.getLogger()
+ # Remove tornado.log options (ugly hacks but these must not be defined)
+ tornado.options.options.logging = None
+ tornado_log_options = tuple(x for x in tornado.options.options._options.keys() if x != 'help' and x != 'logging')
+ for opt in tornado_log_options:
+ del tornado.options.options._options[opt]
+ # and reuse "--logging" to document empty "--" ;)
+ tornado.options.options._options['logging'].help = 'Stop parsing options'
+ for att in ('name', 'metavar', 'group_name', 'default'):
+ setattr(tornado.options.options._options['logging'], att, '')
+ # Define irgramd options
hunk ./irgramd 95
+ 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')
hunk ./irgramd 112
- # parse cmd line first time to get --config and --config_dir
- tornado.options.parse_command_line()
+ try:
+ # parse cmd line first time to get --config and --config_dir
+ tornado.options.parse_command_line()
+ except Exception as exc:
+ print(exc)
+ exit(1)
hunk ./irgramd 122
- logger.info('Configuration Directory: %s', config_dir)
+ defered_logs = [(logging.INFO, 'Configuration Directory: %s', config_dir)]
hunk ./irgramd 127
- logger.info('Using configuration file: %s', config_file)
- tornado.options.parse_config_file(config_file)
+ defered_logs.append((logging.INFO, 'Using configuration file: %s', config_file))
+ try:
+ tornado.options.parse_config_file(config_file)
+ except Exception as exc:
+ print(exc)
+ exit(1)
hunk ./irgramd 134
- logger.warning('Configuration file not present, using only command line options and defaults')
+ defered_logs.append((logging.WARNING, 'Configuration file not present, using only command line options and defaults'))
hunk ./irgramd 141
+ # configure logging
+ loglevel = parse_loglevel(options['log_level'])
+ if loglevel == False:
+ print("Option 'log_level' requires one of these values: {}".format(tornado.options.options._options['log-level'].metavar))
+ exit(1)
+ logger_formats = { 'datefmt':'%Y-%m-%d %H:%M:%S', 'format':'[%(levelname).1s %(asctime)s %(module)s:%(lineno)d] %(message)s' }
+ logger = logging.getLogger()
+ if options['log_file']:
+ logging.basicConfig(filename=options['log_file'], level=loglevel, **logger_formats)
+ else:
+ logging.basicConfig(level=loglevel, **logger_formats)
+
+ for log in defered_logs:
+ logger.log(*log)
+
+ # main loop
hunk ./utils.py 16
+import logging
hunk ./utils.py 218
+
+def parse_loglevel(level):
+ levelu = level.upper()
+ if levelu == 'NONE':
+ l = None
+ elif levelu in ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'):
+ l = getattr(logging, levelu)
+ else:
+ l = False
+ return l
patch 987c7436cd18763a950f5799ef5fac6e7cb127e4
Author: E. Bosch <presidev@AT@gmail.com>
Date: Mon Dec 18 21:18:42 CET 2023
* telegram, utils: Replace invalid characters in filenames with number sequences instead of just removing.
This will prevent some filename collisions in corner cases.
hunk ./utils.py 84
- return FILENAME_INVALID_CHARS.sub('', fn).strip('-').replace(' ','_')
+ cn = str(sanitize_filename.cn)
+ new_fn, ns = FILENAME_INVALID_CHARS.subn(cn, fn)
+ if ns:
+ sanitize_filename.cn += 1
+ return new_fn.strip('-').replace(' ','_')
+sanitize_filename.cn = 0
patch 3ba3cb2a3d628290c5d421637e779869c00fba7d
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Dec 17 03:45:05 CET 2023
* telegram: Remove characters '#', '%' and '?' from filenames,
are not valid for static files in HTTP URLs.
hunk ./utils.py 19
-FILENAME_INVALID_CHARS = re.compile('[/{}<>()"\'\\|&]')
+FILENAME_INVALID_CHARS = re.compile('[/{}<>()"\'\\|&#%?]')
patch 59776df1810cf643a8b7d70ab0502afde6a5c47f
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Dec 17 02:49:18 CET 2023
* telegram: Add compact message IDs to filenames of media,
this will prevent most of the possible collisions of media with the same filename
hunk ./telegram.py 25
-from utils import sanitize_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, get_highlighted, fix_braces, format_timestamp
hunk ./telegram.py 572
- text = await self.handle_webpage(upd_to_webpend, message)
+ text = await self.handle_webpage(upd_to_webpend, message, mid)
hunk ./telegram.py 579
- final_text = await self.handle_telegram_action(message)
+ final_text = await self.handle_telegram_action(message, mid)
hunk ./telegram.py 672
- async def handle_telegram_action(self, message):
+ async def handle_telegram_action(self, message, mid):
hunk ./telegram.py 679
- photo_url = await self.download_telegram_media(message)
+ photo_url = await self.download_telegram_media(message, mid)
hunk ./telegram.py 747
- return await self.handle_webpage(message.media.webpage, message)
+ return await self.handle_webpage(message.media.webpage, message, mid)
hunk ./telegram.py 834
- media_url_or_data = await self.download_telegram_media(message, filename, size, relay_attr)
+ media_url_or_data = await self.download_telegram_media(message, mid, filename, size, relay_attr)
hunk ./telegram.py 857
- async def handle_webpage(self, webpage, message):
+ async def handle_webpage(self, webpage, message, mid):
hunk ./telegram.py 859
- logo = await self.download_telegram_media(message)
+ logo = await self.download_telegram_media(message, mid)
hunk ./telegram.py 917
- async def download_telegram_media(self, message, filename=None, size=0, relay_attr=None):
+ async def download_telegram_media(self, message, mid, filename=None, size=0, relay_attr=None):
hunk ./telegram.py 921
- new_file = sanitize_filename(filename)
+ idd_file = add_filename(filename, mid)
+ new_file = sanitize_filename(idd_file)
hunk ./telegram.py 935
- new_file = str(self.media_cn) + filetype
+ gen_file = str(self.media_cn) + filetype
+ idd_file = add_filename(gen_file, mid)
+ new_file = sanitize_filename(idd_file)
hunk ./utils.py 86
+def add_filename(filename, add):
+ if add:
+ aux = filename.rsplit('.', 1)
+ name = aux[0]
+ ext = aux[1]
+ return '{}-{}.{}'.format(name, add, ext)
+ else:
+ return filename
+
patch 50155e1ef1b508f47640d4f570da84407efac9cb
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Dec 15 00:00:44 CET 2023
* telegram: Improve metadata shown for audio and recording/voice media,
and get filename from them
hunk ./telegram.py 731
- attrib_file = attrib_video = filename = None
+ attrib_file = attrib_av = filename = None
hunk ./telegram.py 735
- if isinstance(x, tgty.DocumentAttributeVideo):
- attrib_video = x
+ if isinstance(x, tgty.DocumentAttributeVideo) or isinstance(x, tgty.DocumentAttributeAudio):
+ attrib_av = x
hunk ./telegram.py 741
- return size, h_size, attrib_video, filename
+ return size, h_size, attrib_av, filename
hunk ./telegram.py 759
- elif message.audio: media_type = 'audio'
- elif message.voice: media_type = 'rec'
+ elif message.audio:
+ size, h_size, attrib_audio, filename = scan_doc_attributes(message.media.document)
+ dur = get_human_duration(attrib_audio.duration) if attrib_audio else ''
+ per = attrib_audio.performer or ''
+ tit = attrib_audio.title or ''
+ theme = ',{}/{}'.format(per, tit) if per or tit else ''
+ media_type = 'audio:{},{}{}'.format(h_size, dur, theme)
+ elif message.voice:
+ size, _, attrib_audio, filename = scan_doc_attributes(message.media.document)
+ dur = get_human_duration(attrib_audio.duration) if attrib_audio else ''
+ media_type = 'rec:{}'.format(dur)
hunk ./utils.py 133
- if s > 0: res += str(s) + 's'
+ if s > 0 or duration < 60: res += str(s) + 's'
patch 17b3e7fb0ee1d0153c40740e2934a2030bd9b0ba
Author: E. Bosch <presidev@AT@gmail.com>
Date: Thu Dec 7 21:16:12 CET 2023
* README update
hunk ./README.md 35
-several Telegram accounts you will need to run several irgramd instances.
+several Telegram accounts you will need to run several irgramd instances. If
+all IRC clients are disconnected, irgramd will remain connected to Telegram.
+
+irgramd can also be seen as a kind of bouncer ([BNC]), with the difference
+that instead of talking IRC protocol on the client side, it talks Telegram
+protocol (MTProto), and can hide the IP and location of the IRC client (if
+executed in a different host).
hunk ./README.md 139
+[BNC]: https://en.wikipedia.org/wiki/BNC_(software)
patch ec320fd2c408d1239ba7e980e42318e1ab50bb21
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Dec 3 00:12:34 CET 2023
* Correct OpenStreetMap URL in irgramdrc.sample
hunk ./irgramdrc.sample 19
-#geo_url='https://osm.org/search?query={lat}%20{long}#map=15'
+#geo_url='https://osm.org/?mlat={lat}&mlon={long}&zoom=15'
patch 3976770a9424b2085ba1260328b3adb5ce6b1ebe
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Dec 2 20:41:44 CET 2023
* telegram: Add target nick in private messages sent from the self user
from another Telegram client
hunk ./service.py 80
- if id in self.tg.tid_to_iid.keys():
- name_in_irc = self.tg.tid_to_iid[id]
- else:
- name_in_irc = '<Unknown>'
+ name_in_irc = self.tg.get_irc_name_from_telegram_id(id)
+
hunk ./telegram.py 209
+ def get_irc_name_from_telegram_id(self, tid):
+ if tid in self.tid_to_iid.keys():
+ name_in_irc = self.tid_to_iid[tid]
+ else:
+ name_in_irc = '<Unknown>'
+ return name_in_irc
+
hunk ./telegram.py 588
- final_text = '[{}] {}{}'.format(mid, refwd_text, text)
+ target_mine = self.handle_target_mine(message.peer_id, user)
+
+ final_text = '[{}] {}{}{}'.format(mid, target_mine, refwd_text, text)
hunk ./telegram.py 835
+ def handle_target_mine(self, target, user):
+ # Add the target of messages sent by self user (me)
+ # received in other clients
+ target_id, target_type = self.get_peer_id_and_type(target)
+ if user is None and target_type == 'user' and target_id != self.id:
+ # self user^
+ # as sender
+ irc_id = self.get_irc_name_from_telegram_id(target_id)
+ target_mine = '[T: {}] '.format(irc_id)
+ else:
+ target_mine = ''
+ return target_mine
+
patch d9d472cb672a8172ee1d54241f20899754fb0640
Author: E. Bosch <presidev@AT@gmail.com>
Date: Tue Nov 28 23:53:52 CET 2023
* README update
hunk ./README.md 49
+- Actions [pin message, channel photo] (receive)
patch 9c73d6fcff003a7d9b4f5f90df4f6dc71d84ae9d
Author: E. Bosch <presidev@AT@gmail.com>
Date: Tue Nov 28 22:38:01 CET 2023
* service: Add absolute ID as argument to get command
hunk ./service.py 109
- id = self.tg.mid.id_to_num_offset(peer_id, mid)
+ # If the ID starts with '=' is absolute ID, not compact ID
+ # character '=' is not used by compact IDs
+ if mid[0] == '=':
+ id = int(mid[1:])
+ else:
+ id = self.tg.mid.id_to_num_offset(peer_id, mid)
hunk ./service.py 128
- ' get <peer> <compact_id>',
- 'Get one message from peer with the compact ID',
+ ' get <peer> <compact_id|=ID>',
+ 'Get one message from peer with the compact or absolute ID',
patch c36c361baf23612e31780ba510278ceef611b7ec
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Nov 26 23:13:52 CET 2023
* telegram: Add support for the change of channel photo action
including download of the new photo
mapped to CTCP action on IRC
hunk ./telegram.py 668
+ elif isinstance(message.action, tgty.MessageActionChatEditPhoto):
+ _, media_type = self.scan_photo_attributes(message.action.photo)
+ photo_url = await self.download_telegram_media(message)
+ action_text = 'has changed chat [{}] {}'.format(media_type, photo_url)
hunk ./telegram.py 749
- ph_size = message.media.photo.sizes[-1]
- if isinstance(ph_size, tgty.PhotoSizeProgressive):
- size = ph_size.sizes[-1]
- else:
- size = ph_size.size
- if hasattr(ph_size, 'w') and hasattr(ph_size, 'h'):
- media_type = 'photo:{}x{}'.format(ph_size.w, ph_size.h)
- else:
- media_type = 'photo'
+ size, media_type = self.scan_photo_attributes(message.media.photo)
hunk ./telegram.py 867
+ def scan_photo_attributes(self, photo):
+ size = 0
+ sizes = photo.sizes
+ ph_size = sizes[-1]
+ if isinstance(ph_size, tgty.PhotoSizeProgressive):
+ size = ph_size.sizes[-1]
+ else:
+ for x in sizes:
+ if isinstance(x, tgty.PhotoSize):
+ if x.size > size:
+ size = x.size
+ ph_size = x
+ if hasattr(ph_size, 'w') and hasattr(ph_size, 'h'):
+ media_type = 'photo:{}x{}'.format(ph_size.w, ph_size.h)
+ else:
+ media_type = 'photo'
+
+ return size, media_type
+
patch 9c00648eaf7c048826dc4fe6bdbd509eabe57f47
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Nov 26 20:07:44 CET 2023
* telegram: Add support for pin message action
irc: Add support for sending CTCP actions (me) to map actions from Telegram
hunk ./irc.py 473
+ async def send_action(self, source, target, message):
+ action_message = '\x01ACTION {}\x01'.format(message)
+ await self.send_msg(source, target, action_message)
+
hunk ./telegram.py 556
- text_send = self.set_history_timestamp(text, history, msg.date)
+ text_send = self.set_history_timestamp(text, history, msg.date, msg.action)
hunk ./telegram.py 571
- if message.is_reply:
+ if message.action:
+ final_text = await self.handle_telegram_action(message)
+ return final_text
+ elif message.is_reply:
hunk ./telegram.py 585
- def set_history_timestamp(self, text, history, date):
+ def set_history_timestamp(self, text, history, date, action):
hunk ./telegram.py 588
- res = '{} {}'.format(timestamp, text)
+ if action:
+ res = '{} {}'.format(text, timestamp)
+ else:
+ res = '{} {}'.format(timestamp, text)
hunk ./telegram.py 598
+ action = (message and message.action)
hunk ./telegram.py 600
- await self.relay_telegram_private_message(user, text)
+ await self.relay_telegram_private_message(user, text, action)
hunk ./telegram.py 603
- chan = await self.relay_telegram_channel_message(message, user, text, channel)
+ chan = await self.relay_telegram_channel_message(message, user, text, channel, action)
hunk ./telegram.py 606
- async def relay_telegram_private_message(self, user, message):
+ async def relay_telegram_private_message(self, user, message, action=None):
hunk ./telegram.py 609
- await self.irc.send_msg(user, None, message)
-
- async def relay_telegram_channel_message(self, message, user, text, channel=None):
+ if action:
+ await self.irc.send_action(user, None, message)
+ else:
+ await self.irc.send_msg(user, None, message)
+
+ async def relay_telegram_channel_message(self, message, user, text, channel, action):
hunk ./telegram.py 622
- await self.irc.send_msg(user, chan, text)
+
+ if action:
+ await self.irc.send_action(user, chan, text)
+ else:
+ await self.irc.send_msg(user, chan, text)
+
hunk ./telegram.py 663
+ async def handle_telegram_action(self, message):
+ if isinstance(message.action, tgty.MessageActionPinMessage):
+ replied = await message.get_reply_message()
+ cid = self.mid.num_to_id_offset(replied.peer_id, replied.id)
+ action_text = 'has pinned message [{}]'.format(cid)
+ else:
+ action_text = ''
+ return action_text
+
patch 583b2fd653aa6786aba38cb9a00c6e990f3a1d11
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Nov 19 00:54:47 CET 2023
* telegram: Change add symbol in editions from "_" to "+"
hunk ./utils.py 180
- res += '_{}_ '.format(i[2:])
+ res += '+{}+ '.format(i[2:])
patch 8167f7dfd2712a9a103504fe7763e2a497adf723
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Nov 19 00:43:04 CET 2023
* telegram: Refactor download media code.
If named media files already downloaded, don't try to download again
hunk ./telegram.py 691
+ filename = None
+
+ def scan_doc_attributes(document):
+ attrib_file = attrib_video = filename = None
+ size = document.size
+ h_size = get_human_size(size)
+ for x in document.attributes:
+ if isinstance(x, tgty.DocumentAttributeVideo):
+ attrib_video = x
+ if isinstance(x, tgty.DocumentAttributeFilename):
+ attrib_file = x
+ filename = attrib_file.file_name if attrib_file else None
+
+ return size, h_size, attrib_video, filename
hunk ./telegram.py 733
- size = message.media.document.size
- h_size = get_human_size(size)
- attrib = next(x for x in message.media.document.attributes if isinstance(x, tgty.DocumentAttributeVideo))
- dur = get_human_duration(attrib.duration)
+ size, h_size, attrib_video, filename = scan_doc_attributes(message.media.document)
+ dur = get_human_duration(attrib_video.duration) if attrib_video else ''
hunk ./telegram.py 740
- size = message.media.document.size
- h_size = get_human_size(size)
+ size, h_size, _, filename = scan_doc_attributes(message.media.document)
hunk ./telegram.py 795
- if self.download and size > self.notice_size:
- await self.relay_telegram_message(message, user, '[{}] [{}] [Downloading]'.format(mid, media_type))
-
- media_url_or_data = await self.download_telegram_media(message)
+ relay_attr = (message, user, mid, media_type)
+ media_url_or_data = await self.download_telegram_media(message, filename, size, relay_attr)
hunk ./telegram.py 847
- async def download_telegram_media(self, message):
- local_path = None
- if self.download:
+ async def download_telegram_media(self, message, filename=None, size=0, relay_attr=None):
+ if not self.download:
+ return ''
+ if filename:
+ new_file = sanitize_filename(filename)
+ new_path = os.path.join(self.telegram_media_dir, new_file)
+ if os.path.exists(new_path):
+ local_path = new_path
+ else:
+ await self.notice_downloading(size, relay_attr)
+ local_path = await message.download_media(new_path)
+ if not local_path: return ''
+ else:
+ await self.notice_downloading(size, relay_attr)
hunk ./telegram.py 862
- if not local_path: return ''
-
- if message.document:
- new_file = sanitize_filename(os.path.basename(local_path))
- else:
+ if not local_path: return ''
hunk ./telegram.py 866
-
- new_path = os.path.join(self.telegram_media_dir, new_file)
+ new_path = os.path.join(self.telegram_media_dir, new_file)
+
hunk ./telegram.py 874
+ async def notice_downloading(self, size, relay_attr):
+ if relay_attr and size > self.notice_size:
+ message, user, mid, media_type = relay_attr
+ await self.relay_telegram_message(message, user, '[{}] [{}] [Downloading]'.format(mid, media_type))
+
patch 24a50b886f4cef2e1da003b74921982f5da0941c
Author: E. Bosch <presidev@AT@gmail.com>
Date: Mon Oct 16 00:24:53 CEST 2023
* telegram: Add option "download_notice" to show the start of a big download
hunk ./irgramd 77
+ tornado.options.define('download_notice', default=10, metavar='SIZE (MiB)', help='Enable a notice when a download starts if its size is greater than SIZE, this is useful when a download takes some time to be completed')
hunk ./telegram.py 42
+ self.notice_size = settings['download_notice'] * 1048576
hunk ./telegram.py 555
- text = await self.render_text(msg, mid, upd_to_webpend)
+ text = await self.render_text(msg, mid, upd_to_webpend, user)
hunk ./telegram.py 563
- async def render_text(self, message, mid, upd_to_webpend, history=False):
+ async def render_text(self, message, mid, upd_to_webpend, user=None):
hunk ./telegram.py 567
- text = await self.handle_telegram_media(message)
+ text = await self.handle_telegram_media(message, user, mid)
hunk ./telegram.py 686
- async def handle_telegram_media(self, message):
+ async def handle_telegram_media(self, message, user, mid):
hunk ./telegram.py 690
+ size = 0
hunk ./telegram.py 707
- size = message.media.photo.sizes[-1]
- if hasattr(size, 'w') and hasattr(size, 'h'):
- media_type = 'photo:{}x{}'.format(size.w, size.h)
+ ph_size = message.media.photo.sizes[-1]
+ if isinstance(ph_size, tgty.PhotoSizeProgressive):
+ size = ph_size.sizes[-1]
hunk ./telegram.py 711
+ size = ph_size.size
+ if hasattr(ph_size, 'w') and hasattr(ph_size, 'h'):
+ media_type = 'photo:{}x{}'.format(ph_size.w, ph_size.h)
+ else:
hunk ./telegram.py 719
- size = get_human_size(message.media.document.size)
+ size = message.media.document.size
+ h_size = get_human_size(size)
hunk ./telegram.py 723
- media_type = 'video:{},{}'.format(size, dur)
+ media_type = 'video:{},{}'.format(h_size, dur)
hunk ./telegram.py 728
- size = get_human_size(message.media.document.size)
- media_type = 'file:{}'.format(size)
+ size = message.media.document.size
+ h_size = get_human_size(size)
+ media_type = 'file:{}'.format(h_size)
hunk ./telegram.py 784
+ if self.download and size > self.notice_size:
+ await self.relay_telegram_message(message, user, '[{}] [{}] [Downloading]'.format(mid, media_type))
+
patch 1fb88a2962a05a4a46ccec54dc3ae3b65f73ab78
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Oct 15 22:06:49 CEST 2023
* telegram: Add option "download_media" to control if media files must be downloaded
hunk ./irgramd 76
+ tornado.options.define('download_media', default=True, help='Enable download of any media (photos, documents, etc.), if not set only a message of media will be shown')
hunk ./telegram.py 41
+ self.download = settings['download_media']
hunk ./telegram.py 828
- local_path = await message.download_media(self.telegram_media_dir)
+ local_path = None
+ if self.download:
+ local_path = await message.download_media(self.telegram_media_dir)
patch a62e1a9973921a97198519dd5a74e1a4472b9364
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Oct 11 00:35:28 CEST 2023
* telegram: Support for showing question and options of polls
hunk ./README.md 48
+- Polls (receive, show)
hunk ./telegram.py 761
- media_url_or_data = ''
+ media_url_or_data = self.handle_poll(message.media.poll)
hunk ./telegram.py 779
+ def handle_poll(self, poll):
+ text = poll.question
+ for ans in poll.answers:
+ text += '\n* ' + ans.text
+ return text
+
patch 400207de8aa3ca0b2dfc8ff1eeaafeddc8221070
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Sep 17 20:57:11 CEST 2023
* README update
hunk ./README.md 49
+- History
patch 2ab08fbae8ec48e123e86a498c287e7afcd173e1
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Jul 22 20:32:19 CEST 2023
* telegram: Add compact ID to all replies
hunk ./telegram.py 647
+ space = ' '
hunk ./telegram.py 651
+ cid = self.mid.num_to_id_offset(replied.peer_id, replied.id)
hunk ./telegram.py 653
- replied_msg = '[{}]'.format(self.mid.num_to_id_offset(replied.peer_id, replied.id))
+ replied_msg = ''
+ space = ''
hunk ./telegram.py 665
- return '|Re {}: {}{}| '.format(replied_nick, replied_msg, trunc)
+ return '|Re {}: [{}]{}{}{}| '.format(replied_nick, cid, space, replied_msg, trunc)
patch 505cd255e7a1cea83385a48467c6d1d099b3bc5d
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Jul 21 01:12:18 CEST 2023
* telegram: Add option for geo custom URL
hunk ./irgramd 77
+ tornado.options.define('geo_url', type=str, default=None, metavar='TEMPLATE_URL', help='Use custom URL for showing geo latitude/longitude location, eg. OpenStreetMap')
hunk ./irgramdrc.sample 18
+#geo url OpenStreetMap
+#geo_url='https://osm.org/search?query={lat}%20{long}#map=15'
+#geo url Google Maps
+#geo_url='https://maps.google.com/?q={lat},{long}'
hunk ./telegram.py 54
+ self.geo_url = settings['geo_url']
hunk ./telegram.py 741
- media_url_or_data = 'lat: {}, long: {}'.format(message.media.geo.lat, message.media.geo.long)
+ if self.geo_url:
+ geo_url = ' | ' + self.geo_url
+ else:
+ geo_url = ''
+ lat_long_template = 'lat: {lat}, long: {long}' + geo_url
+ media_url_or_data = lat_long_template.format(lat=message.media.geo.lat, long=message.media.geo.long)
patch f83be7c1c04d7dcb5ebd0119895eafbc6d784ecb
Author: E. Bosch <presidev@AT@gmail.com>
Date: Thu Jul 20 04:46:06 CEST 2023
* README update
hunk ./README.md 45
-- Deletions (receive)
+- Deletions (receive, do)
patch ee2b02319b5759d67df9fcc08fdafbd57a8fcc80
Author: E. Bosch <presidev@AT@gmail.com>
Date: Mon Jul 17 02:37:21 CEST 2023
* exclam: Add delete (!del) command to delete messages
hunk ./exclam.py 19
+ '!del': (self.handle_command_del, 1, 1, -1),
hunk ./exclam.py 37
- chk_msg = await self.tg.telegram_client.get_messages(entity=self.tmp_telegram_id, ids=id)
+ if id is None:
+ chk_msg = None
+ else:
+ chk_msg = await self.tg.telegram_client.get_messages(entity=self.tmp_telegram_id, ids=id)
hunk ./exclam.py 85
+ )
+ return reply
+
+ async def handle_command_del(self, cid=None, help=None):
+ if not help:
+ id, del_msg = await self.check_msg(cid)
+ 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',)
+ else:
+ self.tmp_tg_msg = None
+ reply = None
+ else:
+ reply = ('Unknown message to delete',)
+ else: # HELP.brief or HELP.desc (first line)
+ reply = (' !del Delete a message',)
+ if help == HELP.desc: # rest of HELP.desc
+ reply += \
+ (
+ ' !del <compact_id>',
+ 'Delete a message with <compact_id> on current channel/chat'
patch 1de628ed1063db8129e0c142d5fe08907b27930a
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Jul 16 20:09:53 CEST 2023
* README update
hunk ./README.md 112
-Copyright (c) 2019 Peter Bui <pbui@bx612.space> [_$_]
+Copyright (c) 2019 Peter Bui <pbui@bx612.space> [_$_]
patch cbe1c8b9a949b8eb5b466496eb7eab3b2f3874bd
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Jul 16 20:02:03 CEST 2023
* README update
hunk ./README.md 46
-- Editions (receive)
+- Editions (receive, do)
hunk ./README.md 112
-Copyright (c) 2019 Peter Bui <pbui@bx612.space>
+Copyright (c) 2019 Peter Bui <pbui@bx612.space> [_$_]
patch fe0c125aa83d95b5472c0bc599e883c9b47213f6
Author: E. Bosch <presidev@AT@gmail.com>
Date: Tue Jul 11 23:54:41 CEST 2023
* exclam: Fix indentation
hunk ./exclam.py 43
- self.tmp_tg_msg = await self.tg.telegram_client.send_message(self.tmp_telegram_id, msg, reply_to=id)
- reply = True
+ self.tmp_tg_msg = await self.tg.telegram_client.send_message(self.tmp_telegram_id, msg, reply_to=id)
+ reply = True
hunk ./exclam.py 46
- reply = ('Unknown message to reply',)
+ reply = ('Unknown message to reply',)
hunk ./exclam.py 62
- try:
- self.tmp_tg_msg = await self.tg.telegram_client.edit_message(ed_msg, new_msg)
- except MessageNotModifiedError:
- self.tmp_tg_msg = ed_msg
- reply = True
- except MessageAuthorRequiredError:
- reply = ('Not the author of the message to edit',)
- else:
- reply = True
+ try:
+ self.tmp_tg_msg = await self.tg.telegram_client.edit_message(ed_msg, new_msg)
+ except MessageNotModifiedError:
+ self.tmp_tg_msg = ed_msg
+ reply = True
+ except MessageAuthorRequiredError:
+ reply = ('Not the author of the message to edit',)
+ else:
+ reply = True
hunk ./exclam.py 72
- reply = ('Unknown message to edit',)
+ reply = ('Unknown message to edit',)
patch 74e097435eeb72086c6ed3f43a98ce3f5463d1c6
Author: E. Bosch <presidev@AT@gmail.com>
Date: Tue Jul 11 01:48:40 CEST 2023
* exclam: Add edit (!ed) command to modify already sent messages
hunk ./exclam.py 9
+from telethon.errors.rpcerrorlist import MessageNotModifiedError, MessageAuthorRequiredError
+
hunk ./exclam.py 18
+ '!ed': (self.handle_command_ed, 2, 2, 2),
hunk ./exclam.py 33
+
+ async def check_msg(self, cid):
+ id = self.tg.mid.id_to_num_offset(self.tmp_telegram_id, cid)
+ chk_msg = await self.tg.telegram_client.get_messages(entity=self.tmp_telegram_id, ids=id)
+ return id, chk_msg
hunk ./exclam.py 41
- id = self.tg.mid.id_to_num_offset(self.tmp_telegram_id, cid)
- chk_msg = await self.tg.telegram_client.get_messages(entity=self.tmp_telegram_id, ids=id)
+ id, chk_msg = await self.check_msg(cid)
hunk ./exclam.py 55
+ )
+ return reply
+
+ async def handle_command_ed(self, cid=None, new_msg=None, help=None):
+ if not help:
+ id, ed_msg = await self.check_msg(cid)
+ if ed_msg is not None:
+ try:
+ self.tmp_tg_msg = await self.tg.telegram_client.edit_message(ed_msg, new_msg)
+ except MessageNotModifiedError:
+ self.tmp_tg_msg = ed_msg
+ reply = True
+ except MessageAuthorRequiredError:
+ reply = ('Not the author of the message to edit',)
+ else:
+ reply = True
+ else:
+ reply = ('Unknown message to edit',)
+ else: # HELP.brief or HELP.desc (first line)
+ reply = (' !ed Edit a message',)
+ if help == HELP.desc: # rest of HELP.desc
+ reply += \
+ (
+ ' !ed <compact_id> <new_message>',
+ 'Edit a message with <compact_id> on current channel/chat,',
+ '<new_message> replaces the current message.'
patch a8d4d79f7ef62a0938f42c2895c99e5d9341d50c
Author: E. Bosch <presidev@AT@gmail.com>
Date: Mon Jul 10 00:56:15 CEST 2023
* README update
hunk ./README.md 60
+## Instalation
+
+### From darcs
+
+ darcs clone https://src.presi.org/repos/darcs/irgramd
+ chmod +x irgramd/irgramd
+
+### From git
+
+ git clone https://github.com/prsai/irgramd.git
+ chmod +x irgramd/irgramd
+
+## Configuration
+
+From irgramd directory `./irgramd --help` will show all configuration
+options available, these options can be used directy in the command line or
+in a file.
+
+When used in command line the separator is `-` (dash) with two leading
+dashes, example: `--api-hash`.
+
+When used in a file the separator is `_` (underscore) without two leading
+dashes nor underscores, example: `api_hash`. The syntax of this file is just
+Python so strings are surrounded by quotes (`'`) and lists by brackets (`[]`).
+
+A sample of the configuration file is provided, copy it to the default
+configuration location:
+
+ mkdir -p ~/.config/irgramd
+ cp irgramd/irgramdrc.sample ~/.config/irgramd/irgramdrc
+
+And modified it with your API IDs and preferences.
+
+## Usage
+
+From irgramd directory, in foreground:
+
+ ./irgramd
+
+In background (without logs):
+
+ ./irgramd --logging=none &
+
hunk ./README.md 107
-(e.g. in Linux be in the shadow group or equivalent). The dependency is
+(e.g. in Linux be in the shadow group or equivalent). The dependency is
hunk ./README.md 112
-Copyright (c) 2019 Peter Bui <pbui@bx612.space> [_$_]
+Copyright (c) 2019 Peter Bui <pbui@bx612.space>
patch 93ee900b41a7ba1e912d3897d242f714e866de09
Author: E. Bosch <presidev@AT@gmail.com>
Date: Mon Jul 10 00:52:02 CEST 2023
* Add configuration file sample (irgramdrc.sample)
addfile ./irgramdrc.sample
hunk ./irgramdrc.sample 1
+api_id=XXXXXX
+api_hash='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
+phone='XXXXXXXXXXX'
+#ask_code=True
+#irc_address='0.0.0.0'
+#tls=True
+#tls_cert='/path/to/certificate'
+#tls_key='/path/to/key'
+irc_nicks=['XXXXX', 'XXXXXX']
+media_url='https://server/token/'
+#pam=True
+#pam_group='XXXXX'
+#char_in_encoding='iso-8859-1'
+#char_out_encoding='iso-8859-1'
+#emoji_ascii=True
+hist_timestamp_format='[%m-%d %H:%M]'
+#timezone='Europe/Madrid'
patch 274ec21ecc7d6c159237df2648668f54b65d5332
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Jul 8 01:58:26 CEST 2023
* telegram, irc: Refactor and improve routine for conversion of mentions
hunk ./telegram.py 66
+ self.sorted_len_usernames = []
hunk ./telegram.py 136
+ self.add_sorted_len_usernames(self.tg_username)
hunk ./telegram.py 152
+ self.add_sorted_len_usernames(tg_ni)
hunk ./telegram.py 409
- def repl_mentioned(text, me_nick, received, mark, index, rargs):
- if text and text[index] == mark:
+ def repl_mentioned(text, me_nick, received, mark, repl_pref, repl_suff):
+ new_text = text
+
+ for user in self.sorted_len_usernames:
+ if user == self.tg_username:
+ if me_nick:
+ username = me_nick
+ else:
+ continue
+ else:
+ username = self.irc.users[user].irc_nick
+
hunk ./telegram.py 422
- subtext = text[1:]
- else:
- subtext = text[:-1]
- part = subtext.lower()
- if me_nick and part == self.tg_username:
- return replacement(me_nick, **rargs)
- if part in self.irc.users:
- return replacement(self.irc.users[part].irc_nick, **rargs)
- return text
-
- def replacement(nick, repl_pref, repl_suff):
- return '{}{}{}'.format(repl_pref, nick, repl_suff)
+ mention = mark + user
+ mention_case = mark + username
+ else: # sent
+ mention = user + mark
+ mention_case = username + mark
+ replcmnt = repl_pref + username + repl_suff
+
+ # Start of the text
+ for ment in (mention, mention_case):
+ if new_text.startswith(ment):
+ new_text = new_text.replace(ment, replcmnt, 1)
+
+ # Next words (with space as separator)
+ mention = ' ' + mention
+ mention_case = ' ' + mention_case
+ replcmnt = ' ' + replcmnt
+ new_text = new_text.replace(mention, replcmnt).replace(mention_case, replcmnt)
+
+ return new_text
hunk ./telegram.py 444
- index = 0
hunk ./telegram.py 446
- else:
+ else: # sent
hunk ./telegram.py 448
- index = -1
hunk ./telegram.py 452
- words = text.split(' ')
- words_replaced = [repl_mentioned(elem, me_nick, received, mark, index, rargs) for elem in words]
- text_replaced = ' '.join(words_replaced)
+ text_replaced = repl_mentioned(text, me_nick, received, mark, **rargs)
hunk ./telegram.py 462
+ def add_sorted_len_usernames(self, username):
+ self.sorted_len_usernames.append(username)
+ self.sorted_len_usernames.sort(key=lambda k: len(k), reverse=True)
+
patch 153aba3773e6ca3c7c505212ac1aeac19f15e68d
Author: E. Bosch <presidev@AT@gmail.com>
Date: Tue Jun 27 03:00:57 CEST 2023
* Fix typos in README
hunk ./README.md 64
-(e.g. in Linux be in the shadow group or equivalent).
-The dependency is totally optional, if not use, the module pyPAM is no
-needed.
+(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.
patch 7225855530de2f42820f61b5a8f1083269aab749
Author: E. Bosch <presidev@AT@gmail.com>
Date: Mon Jun 26 22:55:35 CEST 2023
* README update
hunk ./README.md 58
+- [pyPAM] (optional, tested with v0.4.2-13.4 from deb, [legacy web](https://web.archive.org/web/20110316070059/http://www.pangalactic.org/PyPAM/))
+
+## Notes
+
+PAM authentication: it allows to authenticate IRC users from the system in
+Unix/Linux. The user that executes irgramd must have permissions to use PAM
+(e.g. in Linux be in the shadow group or equivalent).
+The dependency is totally optional, if not use, the module pyPAM is no
+needed.
hunk ./README.md 87
+[pyPAM]: https://packages.debian.org/bullseye/python3-pam
patch 1b816235b1b9baeae5f394f01c379bbb2e0136ce
Author: E. Bosch <presidev@AT@gmail.com>
Date: Mon Jun 26 22:23:59 CEST 2023
* README update
hunk ./README.md 43
-- Replies (receive)
+- Replies (receive, send)
patch ae837b8af1788904bfa4ed7430d331028475e8e3
Author: E. Bosch <presidev@AT@gmail.com>
Date: Mon Jun 26 00:29:50 CEST 2023
* Remove trailing spaces
hunk ./service.py 115
- reply = ('Message not found',) [_$_]
+ reply = ('Message not found',)
patch 5b2c938f7967a3345435fe21b2127999d0975f50
Author: E. Bosch <presidev@AT@gmail.com>
Date: Mon Jun 26 00:17:22 CEST 2023
* Add exclam module to handle channel/chat commands begining with exclamation mark (!)
Implement reply (!re) as first exclam command
addfile ./exclam.py
hunk ./exclam.py 1
+# irgramd: IRC-Telegram gateway
+# exclam.py: IRC exclamation command handlers
+#
+# Copyright (c) 2023 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.
+
+from utils import command, HELP
+
+class exclam(command):
+ def __init__(self, telegram):
+ self.commands = \
+ { # Command Handler Arguments Min Max Maxsplit
+ '!re': (self.handle_command_re, 2, 2, 2),
+ }
+ self.tg = telegram
+ self.irc = telegram.irc
+ self.tmp_ircnick = None
+ self.tmp_telegram_id = None
+ self.tmp_tg_msg = None
+
+ async def command(self, message, telegram_id, user):
+ self.tmp_telegram_id = telegram_id
+ res = await self.parse_command(message, nick=None)
+ if isinstance(res, tuple):
+ await self.irc.send_msg(self.irc.service_user, None, res[0], user)
+ res = False
+ return res, self.tmp_tg_msg
+
+ async def handle_command_re(self, cid=None, msg=None, help=None):
+ if not help:
+ id = self.tg.mid.id_to_num_offset(self.tmp_telegram_id, cid)
+ chk_msg = await self.tg.telegram_client.get_messages(entity=self.tmp_telegram_id, ids=id)
+ if chk_msg is not None:
+ 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',)
+ else: # HELP.brief or HELP.desc (first line)
+ reply = (' !re Reply to a message',)
+ if help == HELP.desc: # rest of HELP.desc
+ reply += \
+ (
+ ' !re <compact_id> <message>',
+ 'Reply with <message> to a message with <compact_id> on current',
+ 'channel/chat.'
+ )
+ return reply
hunk ./irc.py 25
+from exclam import exclam
hunk ./irc.py 113
+ self.exclam = exclam(self.tg)
hunk ./irc.py 413
- tg_msg = await self.tg.telegram_client.send_message(telegram_id, message)
-
- mid = self.tg.mid.num_to_id_offset(telegram_id, tg_msg.id)
- text = '[{}] {}'.format(mid, message)
- self.tg.to_cache(tg_msg.id, mid, text, message, user, chan, media=None)
-
- if defered_send:
- await defered_send(user, defered_target, text)
+ if message[0] == '!':
+ cont, tg_msg = await self.exclam.command(message, telegram_id, user)
+ else:
+ tg_msg = await self.tg.telegram_client.send_message(telegram_id, message)
+ cont = True
+ if cont:
+ mid = self.tg.mid.num_to_id_offset(telegram_id, tg_msg.id)
+ text = '[{}] {}'.format(mid, message)
+ self.tg.to_cache(tg_msg.id, mid, text, message, user, chan, media=None)
+
+ if defered_send:
+ await defered_send(user, defered_target, text)
hunk ./service.py 9
-from utils import compact_date, command
+from utils import compact_date, command, HELP
hunk ./service.py 15
- { # Command Handler Arguments Min Max
- 'code': (self.handle_command_code, 1, 1),
- 'dialog': (self.handle_command_dialog, 1, 2),
- 'get': (self.handle_command_get, 2, 2),
- 'help': (self.handle_command_help, 0, 1),
- 'history': (self.handle_command_history, 1, 3),
- 'mark_read': (self.handle_command_mark_read, 1, 1),
+ { # Command Handler Arguments Min Max Maxsplit
+ 'code': (self.handle_command_code, 1, 1, -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),
+ 'history': (self.handle_command_history, 1, 3, -1),
+ 'mark_read': (self.handle_command_mark_read, 1, 1, -1),
hunk ./service.py 147
+ 'The commands begining with ! (exclamation) must be used directly',
+ 'in channels or chats. The following ! commands are available:',
+ )
+ for command in self.irc.exclam.commands.values():
+ handler = command[0]
+ help_text += await handler(help=HELP.brief)
+ help_text += \
+ (
hunk ./service.py 159
- elif help_command in self.commands.keys():
- handler = self.commands[help_command][0]
+ elif help_command in (all_commands := dict(**self.commands, **self.irc.exclam.commands)).keys():
+ handler = all_commands[help_command][0]
hunk ./service.py 261
-
-class HELP:
- desc = 1
- brief = 2
hunk ./utils.py 26
- words = line.split()
- command = words.pop(0).lower()
+ command = line.partition(' ')[0].lower()
hunk ./utils.py 29
- handler, min_args, max_args = self.commands[command]
+ handler, min_args, max_args, maxsplit = self.commands[command]
+ words = line.split(maxsplit=maxsplit)[1:]
hunk ./utils.py 41
+class HELP:
+ desc = 1
+ brief = 2
+
patch cbec6bc5a68bcadaaea6113355f12056d10cf577
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Jun 23 23:49:58 CEST 2023
* telegram: Fix: in forwards when the original user is unknown use the ID
hunk ./telegram.py 212
- user = self.get_irc_user_from_telegram(peer_id)
- if user is None:
- name = '{}'
- self.refwd_me = True
+ try:
+ user = self.get_irc_user_from_telegram(peer_id)
+ except:
+ name = str(peer_id)
hunk ./telegram.py 217
- name = user.irc_nick
+ if user is None:
+ name = '{}'
+ self.refwd_me = True
+ else:
+ name = user.irc_nick
patch dd182f24b1eb76b357ab4ab84363ca55669f7b97
Author: E. Bosch <presidev@AT@gmail.com>
Date: Thu Jun 22 21:57:16 CEST 2023
* Move parse_command() to a new class, the code will be reused for the future
exclam module
hunk ./service.py 9
-from utils import compact_date
+from utils import compact_date, command
hunk ./service.py 12
-class service:
+class service(command):
hunk ./service.py 28
- async def parse_command(self, line, nick):
-
- words = line.split()
- command = words.pop(0).lower()
- self.tmp_ircnick = nick
- if command in self.commands.keys():
- handler, min_args, max_args = self.commands[command]
- num_words = len(words)
- if num_words < min_args or num_words > max_args:
- reply = ('Wrong number of arguments',)
- else:
- reply = await handler(*words)
- else:
- reply = ('Unknown command',)
-
- return reply
-
hunk ./utils.py 24
+class command:
+ async def parse_command(self, line, nick):
+ words = line.split()
+ command = words.pop(0).lower()
+ self.tmp_ircnick = nick
+ if command in self.commands.keys():
+ handler, min_args, max_args = self.commands[command]
+ num_words = len(words)
+ if num_words < min_args or num_words > max_args:
+ reply = ('Wrong number of arguments',)
+ else:
+ reply = await handler(*words)
+ else:
+ reply = ('Unknown command',)
+
+ return reply
+
patch 3ec451218b80bdd3c409b4aec91ae94ef4fa5c38
Author: E. Bosch <presidev@AT@gmail.com>
Date: Thu Jun 15 20:08:03 CEST 2023
* telegram, irc: Add conversion of "mention:" (IRC style) to "@mention" in sent messages
hunk ./irc.py 409
+ message = self.tg.replace_mentions(message, me_nick='', received=False)
hunk ./telegram.py 398
- def replace_mentions(self, text, me_nick=''):
- def repl_mentioned(text, me_nick):
- if text and text[0] == '@':
- part = text[1:].lower()
+ def replace_mentions(self, text, me_nick='', received=True):
+ # For received replace @mention to ~mention~
+ # For sent replace mention: to @mention
+ rargs = {}
+ def repl_mentioned(text, me_nick, received, mark, index, rargs):
+ if text and text[index] == mark:
+ if received:
+ subtext = text[1:]
+ else:
+ subtext = text[:-1]
+ part = subtext.lower()
hunk ./telegram.py 410
- return replacement(me_nick)
+ return replacement(me_nick, **rargs)
hunk ./telegram.py 412
- return replacement(self.irc.users[part].irc_nick)
+ return replacement(self.irc.users[part].irc_nick, **rargs)
hunk ./telegram.py 415
- def replacement(nick):
- return '{}{}{}'.format('~',nick, '~')
-
- if text.find('@') != -1:
+ def replacement(nick, repl_pref, repl_suff):
+ return '{}{}{}'.format(repl_pref, nick, repl_suff)
+
+ if received:
+ mark = '@'
+ index = 0
+ rargs['repl_pref'] = '~'
+ rargs['repl_suff'] = '~'
+ else:
+ mark = ':'
+ index = -1
+ rargs['repl_pref'] = '@'
+ rargs['repl_suff'] = ''
+
+ if text.find(mark) != -1:
hunk ./telegram.py 431
- words_replaced = [repl_mentioned(elem, me_nick) for elem in words]
+ words_replaced = [repl_mentioned(elem, me_nick, received, mark, index, rargs) for elem in words]
patch eb5b15eba474680cab8764d0242229617d960e38
Author: E. Bosch <presidev@AT@gmail.com>
Date: Thu Jun 15 01:15:43 CEST 2023
* irc: Add log for user registered (authorized in IRC)
hunk ./irc.py 143
- self.service_user = IRCUser(None, ('Services',), self.conf['service_user'],
+ self.service_user = IRCUser(None, ('Services',''), self.conf['service_user'],
hunk ./irc.py 430
+ self.logger.info('Registered IRC user "%s" from %s:%s', user.irc_nick, user.address, user.port)
+
hunk ./irc.py 645
+ self.port = str(address[1])
hunk ./telegram.py 148
- irc_user = IRCUser(None, ('Telegram',), tg_nick, user.id, self.get_telegram_display_name(user))
+ irc_user = IRCUser(None, ('Telegram',''), tg_nick, user.id, self.get_telegram_display_name(user))
patch 5da8c71bd90c689771ee2afddbb6f16a663c7c35
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Jun 14 00:43:05 CEST 2023
* emoji2emoticon: Add emoji "thinking face" to convert to ASCII
hunk ./emoji2emoticon.py 87
+ '\U0001f914': '":-L"',
patch 38a5f5ab57d1d819c40ed65f00e86c2cf6a9042f
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Jun 11 22:27:57 CEST 2023
* emoji2emoticon: Add emoji "shushing face" to convert to ASCII
hunk ./emoji2emoticon.py 87
+ '\U0001f92b': '":-o-m"',
patch 746b8dec8a7b53ef2750501c320aaa627d5ae7fe
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Jun 11 00:44:55 CEST 2023
* irc: Separate character encoding options as input and output
hunk ./irc.py 85
- message = message.decode(self.conf['char_encoding'], errors='replace').replace('\r','\n')
+ message = message.decode(self.conf['char_in_encoding'], errors='replace').replace('\r','\n')
hunk ./irc.py 150
- user.stream.write(command.encode(self.conf['char_encoding'], errors='replace'))
+ user.stream.write(command.encode(self.conf['char_out_encoding'], errors='replace'))
hunk ./irgramd 72
- tornado.options.define('char_encoding', default='utf-8', metavar='ENCODING', help='Character encoding for IRC')
+ 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')
patch 7300fb2b6fde17386971ea497343afae8526fb4c
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Jun 10 22:31:22 CEST 2023
* telegram, irc: Add conversion of mentions for self @username as well as
other mentions in self messages [saved messages]
hunk ./irc.py 471
+ # replace self @username and other mentions for self messages sent by this instance of irgramd
+ msg = self.tg.replace_mentions(msg, user.irc_nick)
+
hunk ./telegram.py 398
- def replace_mentions(self, text):
- def repl_mentioned(text):
+ def replace_mentions(self, text, me_nick=''):
+ def repl_mentioned(text, me_nick):
hunk ./telegram.py 402
+ if me_nick and part == self.tg_username:
+ return replacement(me_nick)
hunk ./telegram.py 405
- return '{}{}{}'.format('~', self.irc.users[part].irc_nick, '~')
+ return replacement(self.irc.users[part].irc_nick)
hunk ./telegram.py 408
+ def replacement(nick):
+ return '{}{}{}'.format('~',nick, '~')
+
hunk ./telegram.py 413
- words_replaced = [repl_mentioned(elem) for elem in words]
+ words_replaced = [repl_mentioned(elem, me_nick) for elem in words]
patch da6b06a9a974df34a72286bd8b93194e712490d5
Author: E. Bosch <presidev@AT@gmail.com>
Date: Thu Jun 8 00:26:21 CEST 2023
* telegram: Refactor forward handle and related functions
Fix the use of saved_from_peer attribute and other improvements
hunk ./telegram.py 203
- def get_irc_nick_from_telegram_forward(self, fwd):
- if fwd.from_id is None:
+ async def get_irc_name_from_telegram_forward(self, fwd, saved):
+ from_id = fwd.saved_from_peer if saved else fwd.from_id
+ if from_id is None:
hunk ./telegram.py 207
- nick = fwd.from_name
+ # or was a broadcast from a channel (no user)
+ name = fwd.from_name
hunk ./telegram.py 210
- user = self.get_irc_user_from_telegram(fwd.from_id.user_id)
- if user is None:
- nick = '{}'
- self.refwd_me = True
+ peer_id, type = self.get_peer_id_and_type(from_id)
+ if type == 'user':
+ user = self.get_irc_user_from_telegram(peer_id)
+ if user is None:
+ name = '{}'
+ self.refwd_me = True
+ else:
+ name = user.irc_nick
hunk ./telegram.py 219
- nick = user.irc_nick
- return nick
+ try:
+ name = await self.get_irc_channel_from_telegram_id(peer_id)
+ except:
+ name = ''
+ return name
hunk ./telegram.py 330
+ def get_peer_id_and_type(self, peer):
+ if isinstance(peer, tgty.PeerChannel):
+ id = peer.channel_id
+ type = 'chan'
+ elif isinstance(peer, tgty.PeerChat):
+ id = peer.chat_id
+ type = 'chan'
+ elif isinstance(peer, tgty.PeerUser):
+ id = peer.user_id
+ type = 'user'
+ else:
+ id = peer
+ type = ''
+ return id, type
+
hunk ./telegram.py 617
- forwarded_nick = self.get_irc_nick_from_telegram_forward(message.fwd_from)
- forwarded_peer = message.fwd_from.saved_from_peer
- if isinstance(forwarded_peer, tgty.PeerChannel):
- dest = ' ' + await self.get_irc_channel_from_telegram_id(forwarded_peer.channel_id)
- elif isinstance(forwarded_peer, tgty.PeerChat):
- dest = ' ' + await self.get_irc_channel_from_telegram_id(forwarded_peer.chat_id)
+ space = space2 = ' '
+ if not (forwarded_peer_name := await self.get_irc_name_from_telegram_forward(message.fwd_from, saved=False)):
+ space = ''
+ saved_peer_name = await self.get_irc_name_from_telegram_forward(message.fwd_from, saved=True)
+ if saved_peer_name and saved_peer_name != forwarded_peer_name:
+ secondary_name = saved_peer_name
hunk ./telegram.py 626
- dest = ' ' + self.get_irc_user_from_telegram(forwarded_peer.user_id).irc_nick
+ secondary_name = self.get_irc_user_from_telegram(message.fwd_from.saved_from_peer.user_id).irc_nick
hunk ./telegram.py 628
- dest = ''
-
- return '|Fwd {}{}| '.format(forwarded_nick, dest)
+ secondary_name = ''
+ space2 = ''
+
+ return '|Fwd{}{}{}{}| '.format(space, forwarded_peer_name, space2, secondary_name)
patch d6b83b6ed9dfd2c1692e79da8b54046f96e059f5
Author: E. Bosch <presidev@AT@gmail.com>
Date: Tue Jun 6 00:09:40 CEST 2023
* telegram: Add "media_dir" option to set the download media directory outside
of "config_dir" that didn't make sense, but keep it for compatibility if
"media_dir" is not set
hunk ./irgramd 81
+ tornado.options.define('media_dir', default=None, metavar='PATH', help='Directory where Telegram media files are downloaded, default "media" in `config_dir`')
hunk ./telegram.py 41
+ self.media_dir = settings['media_dir']
hunk ./telegram.py 71
- self.telegram_media_dir = os.path.join(self.config_dir, 'media')
+ self.telegram_media_dir = self.media_dir or os.path.join(self.config_dir, 'media')
patch 4c771fc4851d84b5ae451e1cd9280223c3c5b014
Author: E. Bosch <presidev@AT@gmail.com>
Date: Thu Jun 1 19:55:14 CEST 2023
* telegram: Fix media contact attributes
hunk ./telegram.py 649
- if message.media.contact.first_name:
- media_url_or_data += message.media.contact.first_name + ' '
- if message.media.contact.last_name:
- media_url_or_data += message.media.contact.last_name + ' '
- if message.media.contact.phone_number:
- media_url_or_data += message.media.contact.phone_number
+ if message.media.first_name:
+ media_url_or_data += message.media.first_name + ' '
+ if message.media.last_name:
+ media_url_or_data += message.media.last_name + ' '
+ if message.media.phone_number:
+ media_url_or_data += message.media.phone_number
patch d147fb4ac2eb91490933ee7ba89ecffcde0255c1
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat May 20 23:39:03 CEST 2023
* telegram: Add filters for received messages, in these filters:
Include existing emoji to ASCII function
Add conversion of "@mention" to "~mention~" as "@user" is used to denote
channel operator in most IRC clients
hunk ./telegram.py 373
+ def replace_mentions(self, text):
+ def repl_mentioned(text):
+ if text and text[0] == '@':
+ part = text[1:].lower()
+ if part in self.irc.users:
+ return '{}{}{}'.format('~', self.irc.users[part].irc_nick, '~')
+ return text
+
+ if text.find('@') != -1:
+ words = text.split(' ')
+ words_replaced = [repl_mentioned(elem) for elem in words]
+ text_replaced = ' '.join(words_replaced)
+ else:
+ text_replaced = text
+ return text_replaced
+
+ def filters(self, text):
+ filtered = e.replace_mult(text, e.emo)
+ filtered = self.replace_mentions(filtered)
+ return filtered
+
hunk ./telegram.py 400
- message = e.replace_mult(event.message.message, e.emo)
+ message = self.filters(event.message.message)
hunk ./telegram.py 408
- t = e.replace_mult(self.cache[id]['text'], e.emo)
+ t = self.filters(self.cache[id]['text'])
hunk ./telegram.py 504
- final_text = e.replace_mult(final_text, e.emo)
+ final_text = self.filters(final_text)
patch 00e75e93c744727f1f30cb8e409d53b495c500ed
Author: E. Bosch <presidev@AT@gmail.com>
Date: Tue May 16 23:13:51 CEST 2023
* service: Refactor "get" command handle, make it robust when compact id has
not been initialized yet
hunk ./service.py 121
+ msg = None
hunk ./service.py 124
+ else: reply = ()
hunk ./service.py 127
- msg = await self.tg.telegram_client.get_messages(entity=peer_id, ids=id)
- if msg is None:
- reply = ('Message not found',)
- return reply
- await self.tg.handle_telegram_message(event=None, message=msg, history=True)
- reply = ()
+ if id is not None:
+ msg = await self.tg.telegram_client.get_messages(entity=peer_id, ids=id)
+ if msg is not None:
+ await self.tg.handle_telegram_message(event=None, message=msg, history=True)
+ else:
+ reply = ('Message not found',) [_$_]
patch 72bb23b64e2392ff01f7e060670e98093cfe0839
Author: E. Bosch <presidev@AT@gmail.com>
Date: Mon May 15 21:27:05 CEST 2023
* telegram: As messages are referenced by peer and id (in "get" and future
commands), make the compact IDs more compact with offsets relative to peers
(users or channels)
hunk ./irc.py 412
- mid = self.tg.mid.num_to_id_offset(tg_msg.id)
+ mid = self.tg.mid.num_to_id_offset(telegram_id, tg_msg.id)
hunk ./service.py 121
- id = self.tg.mid.id_to_num_offset(mid)
hunk ./service.py 124
+ id = self.tg.mid.id_to_num_offset(peer_id, mid)
hunk ./telegram.py 377
- mid = self.mid.num_to_id_offset(id)
+ mid = self.mid.num_to_id_offset(event.message.peer_id, id)
hunk ./telegram.py 441
- mid = self.mid.num_to_id_offset(deleted_id)
- text = 'Message id {} deleted not in cache'.format(mid)
+ text = 'Message id {} deleted not in cache'.format(deleted_id)
hunk ./telegram.py 458
- mid = self.mid.num_to_id_offset(msg.id)
+ mid = self.mid.num_to_id_offset(msg.peer_id, msg.id)
hunk ./telegram.py 557
- replied_msg = '[{}]'.format(self.mid.num_to_id_offset(replied.id))
+ replied_msg = '[{}]'.format(self.mid.num_to_id_offset(replied.peer_id, replied.id))
hunk ./telegram.py 740
- self.mesg_base = None
+ self.mesg_base = {}
hunk ./telegram.py 751
- def num_to_id_offset(self, num):
- if self.mesg_base is None:
- self.mesg_base = num
- return self.num_to_id(num - self.mesg_base)
+ def num_to_id_offset(self, peer, num):
+ peer_id = self.get_peer_id(peer)
+ if peer_id not in self.mesg_base:
+ self.mesg_base[peer_id] = num
+ return self.num_to_id(num - self.mesg_base[peer_id])
hunk ./telegram.py 766
- def id_to_num_offset(self, mid):
- if self.mesg_base is not None:
+ def id_to_num_offset(self, peer, mid):
+ peer_id = self.get_peer_id(peer)
+ if peer_id in self.mesg_base:
hunk ./telegram.py 770
- id = id_rel + self.mesg_base
+ id = id_rel + self.mesg_base[peer_id]
hunk ./telegram.py 773
+ return id
+
+ def get_peer_id(self, peer):
+ if isinstance(peer, tgty.PeerChannel):
+ id = peer.channel_id
+ elif isinstance(peer, tgty.PeerChat):
+ id = peer.chat_id
+ elif isinstance(peer, tgty.PeerUser):
+ id = peer.user_id
+ else:
+ id = peer
patch 3fe74b2337a9d66bddd47052220c413836cb05f9
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun May 14 00:39:42 CEST 2023
* service: Add "get" command to retrieve a specific message
hunk ./service.py 18
+ 'get': (self.handle_command_get, 2, 2),
hunk ./service.py 116
+ )
+ return reply
+
+ async def handle_command_get(self, peer=None, mid=None, help=None):
+ if not help:
+ id = self.tg.mid.id_to_num_offset(mid)
+ peer_id, reply = self.get_peer_id(peer.lower())
+ if reply: return reply
+
+ msg = await self.tg.telegram_client.get_messages(entity=peer_id, ids=id)
+ if msg is None:
+ reply = ('Message not found',)
+ return reply
+ await self.tg.handle_telegram_message(event=None, message=msg, history=True)
+ reply = ()
+ return reply
+
+ else: # HELP.brief or HELP.desc (first line)
+ reply = (' get Get a message by id and peer',)
+ if help == HELP.desc: # rest of HELP.desc
+ reply += \
+ (
+ ' get <peer> <compact_id>',
+ 'Get one message from peer with the compact ID',
hunk ./telegram.py 765
+
+ def id_to_num_offset(self, mid):
+ if self.mesg_base is not None:
+ id_rel = self.id_to_num(mid)
+ id = id_rel + self.mesg_base
+ else:
+ id = None
+ return id
patch 720468dce764675132030772fcb93f4bb5b00724
Author: E. Bosch <presidev@AT@gmail.com>
Date: Mon May 8 01:12:09 CEST 2023
* service: Add more indentation in brief help descriptions
hunk ./service.py 60
- reply = (' code Enter authorization code',)
+ reply = (' code Enter authorization code',)
hunk ./service.py 105
- reply = (' dialog Manage conversations (dialogs)',)
+ reply = (' dialog Manage conversations (dialogs)',)
hunk ./service.py 124
- help_text = (' help This help',)
+ help_text = (' help This help',)
hunk ./service.py 203
- reply = (' history Get messages from history',)
+ reply = (' history Get messages from history',)
hunk ./service.py 225
- reply = (' mark_read Mark messages as read',)
+ reply = (' mark_read Mark messages as read',)
patch 3ddecf1fe8d1534ab6af10d2b3602245541d4234
Author: E. Bosch <presidev@AT@gmail.com>
Date: Mon May 8 01:04:52 CEST 2023
* service: Add "mark_read" command to mark chats as read and reset mentions
hunk ./service.py 20
+ 'mark_read': (self.handle_command_mark_read, 1, 1),
hunk ./service.py 152
- def get_peer_id(tgt):
- if tgt in self.irc.users or tgt in self.irc.irc_channels:
- peer_id = self.tg.get_tid(tgt)
- reply = None
- else:
- peer_id = None
- reply = ('Unknown user or channel',)
- return peer_id, reply
-
hunk ./service.py 176
- peer_id, reply = get_peer_id(tgt)
+ peer_id, reply = self.get_peer_id(tgt)
hunk ./service.py 217
+ async def handle_command_mark_read(self, peer=None, help=None):
+ if not help:
+ peer_id, reply = self.get_peer_id(peer.lower())
+ if reply: return reply
+
+ await self.tg.telegram_client.send_read_acknowledge(peer_id, clear_mentions=True)
+ reply = ('',)
+ else: # HELP.brief or HELP.desc (first line)
+ reply = (' mark_read Mark messages as read',)
+ if help == HELP.desc: # rest of HELP.desc
+ reply += \
+ (
+ ' 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>.'
+ )
+ return reply
+
+ def get_peer_id(self, tgt):
+ if tgt in self.irc.users or tgt in self.irc.irc_channels:
+ peer_id = self.tg.get_tid(tgt)
+ reply = None
+ else:
+ peer_id = None
+ reply = ('Unknown user or channel',)
+ return peer_id, reply
+
patch 940936aea4bf440b777cf726b9c88f21fc54f131
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun May 7 11:49:05 CEST 2023
* service: In dialog list: Use timezone option to convert the date for "last" field
hunk ./service.py 91
- last = compact_date(dialog.date)
+ last = compact_date(dialog.date, self.tg.timezone)
hunk ./utils.py 115
-def compact_date(date):
+def compact_date(date, tz):
hunk ./utils.py 117
+ date_local = date.astimezone(zoneinfo.ZoneInfo(tz))
hunk ./utils.py 120
- compact_date = date.strftime('%H:%M')
+ compact_date = date_local.strftime('%H:%M')
hunk ./utils.py 122
- compact_date = date.strftime('%d-%b')
+ compact_date = date_local.strftime('%d-%b')
hunk ./utils.py 124
- compact_date = date.strftime('%Y')
+ compact_date = date_local.strftime('%Y')
patch ecba573e04bccde171413d8064bd44c957952ad2
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun May 7 02:24:08 CEST 2023
* telegram: In highlight of editions, use dash (-) instead of dot (.) for
marking words deleted, that represents strikethrough.
Add some comments to the code
hunk ./utils.py 153
+ # deletion of words
hunk ./utils.py 155
- res += '.{}. '.format(i[2:])
+ res += '-{}- '.format(i[2:])
+ # addition of words
patch 5c25f44f55e071966feef397617ebfeea52dd88c
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun May 7 01:31:34 CEST 2023
* telegram: Add an option to enable and control format of timestamps for history messages
Add timezone option to convert timestamps
hunk ./README.md 54
-- [python] (>= v3.8)
+- [python] (>= v3.9)
hunk ./irgramd 76
+ tornado.options.define('hist_timestamp_format', metavar='DATETIME_FORMAT', help='Format string for timestamps in history, see https://www.strfti.me')
hunk ./irgramd 91
+ tornado.options.define('timezone', default='UTC', metavar='TIMEZONE', help='Timezone to use for dates (timestamps in history, last in dialogs, etc.)')
hunk ./service.py 206
- await self.tg.handle_telegram_message(event=None, message=msg)
+ await self.tg.handle_telegram_message(event=None, message=msg, history=True)
hunk ./telegram.py 25
-from utils import sanitize_filename, is_url_equiv, extract_url, get_human_size, get_human_duration, get_highlighted, fix_braces
+from utils import sanitize_filename, is_url_equiv, extract_url, get_human_size, get_human_duration, get_highlighted, fix_braces, format_timestamp
hunk ./telegram.py 51
+ self.hist_fmt = settings['hist_timestamp_format']
+ self.timezone = settings['timezone']
hunk ./telegram.py 453
- async def handle_telegram_message(self, event, message=None, upd_to_webpend=None):
+ async def handle_telegram_message(self, event, message=None, upd_to_webpend=None, history=False):
hunk ./telegram.py 461
- chan = await self.relay_telegram_message(msg, user, text)
+ text_send = self.set_history_timestamp(text, history, msg.date)
+ chan = await self.relay_telegram_message(msg, user, text_send)
hunk ./telegram.py 468
- async def render_text(self, message, mid, upd_to_webpend):
+ async def render_text(self, message, mid, upd_to_webpend, history=False):
hunk ./telegram.py 487
+ def set_history_timestamp(self, text, history, date):
+ if history and self.hist_fmt:
+ timestamp = format_timestamp(self.hist_fmt, self.timezone, date)
+ res = '{} {}'.format(timestamp, text)
+ else:
+ res = text
+ return res
+
hunk ./utils.py 14
+import zoneinfo
hunk ./utils.py 175
+
+def format_timestamp(format, tz, date):
+ date_local = date.astimezone(zoneinfo.ZoneInfo(tz))
+ return date_local.strftime(format)
patch 2f799847198dd8bd7070520f56803c063d74d524
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat May 6 21:04:28 CEST 2023
* telegram: Fix arguments in call to relay_telegram_message() in deleted handle
This should have been changed in patch "telegram: Change interface for received messages ..."
hunk ./telegram.py 437
- await self.relay_telegram_message(event=None, user=user, message=text, channel=chan)
+ await self.relay_telegram_message(message=None, user=user, text=text, channel=chan)
patch 18bfe4820f2f732b9af4d882c9e60191cf435256
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri May 5 00:23:04 CEST 2023
* telegram: Add support for retrieving history of messages, this is
implemented by service command "history"
hunk ./service.py 19
+ 'history': (self.handle_command_history, 1, 3),
hunk ./service.py 23
+ self.irc = telegram.irc
hunk ./service.py 149
+ async def handle_command_history(self, peer=None, limit='10', add_unread=None, help=None):
+ if not help:
+ def get_peer_id(tgt):
+ if tgt in self.irc.users or tgt in self.irc.irc_channels:
+ peer_id = self.tg.get_tid(tgt)
+ reply = None
+ else:
+ peer_id = None
+ reply = ('Unknown user or channel',)
+ return peer_id, reply
+
+ async def get_unread(tgt):
+ async for dialog in self.tg.telegram_client.iter_dialogs():
+ id, type = tgutils.resolve_id(dialog.id)
+ if id in self.tg.tid_to_iid.keys():
+ name = self.tg.tid_to_iid[id]
+ if tgt == name.lower():
+ count = dialog.unread_count
+ reply = None
+ break
+ else:
+ count = None
+ reply = ('Unknown unread',)
+ return count, reply
+
+ def conv_int(num_str):
+ if num_str.isdigit():
+ n = int(num_str)
+ err = None
+ else:
+ n = None
+ err = ('Invalid argument',)
+ return n, err
+
+ tgt = peer.lower()
+ peer_id, reply = get_peer_id(tgt)
+ if reply: return reply
+
+ if limit == 'unread':
+ add_unread = '0' if add_unread is None else add_unread
+ add_unread_int, reply = conv_int(add_unread)
+ if reply: return reply
+
+ li, reply = await get_unread(tgt)
+ if reply: return reply
+ li += add_unread_int
+ elif add_unread is not None:
+ reply = ('Wrong number of arguments',)
+ return reply
+ elif limit == 'all':
+ li = None
+ else:
+ li, reply = conv_int(limit)
+ if reply: return reply
+
+ his = await self.tg.telegram_client.get_messages(peer_id, limit=li)
+ for msg in reversed(his):
+ await self.tg.handle_telegram_message(event=None, message=msg)
+ reply = ()
+ return reply
+
+ else: # HELP.brief or HELP.desc (first line)
+ reply = (' history Get messages from history',)
+ if help == HELP.desc: # rest of HELP.desc
+ reply += \
+ (
+ ' history <peer> [<limit>|all|unread [<plusN>]]',
+ 'Get last <limit> number of messages already sent to <peer>',
+ '(channel or user). If not set <limit> is 10.',
+ 'Instead of <limit>, "unread" is for messages not marked as read,',
+ 'optionally <plusN> number of previous messages to the first unread.',
+ 'Instead of <limit>, "all" is for retrieving all available messages',
+ 'in <peer>.',
+ )
+ return reply
patch 5588befb5af1bef24d1309002c728e1cd2799fe6
Author: E. Bosch <presidev@AT@gmail.com>
Date: Thu May 4 22:04:15 CEST 2023
* telegram: Change event/message if statement by an expression
hunk ./telegram.py 454
- if event:
- msg = event.message
- else:
- msg = message
+ msg = event.message if event else message
patch 7fbbd73b77d52696b7d3ebdee564c07cea280f8a
Author: E. Bosch <presidev@AT@gmail.com>
Date: Tue May 2 23:37:17 CEST 2023
* telegram: Change interface for received messages, use message instead event
object if possible, this will be necessary to inject messages from future
history functionality
hunk ./telegram.py 378
- message_rendered = await self.render_text(event, mid, upd_to_webpend=None)
+ message_rendered = await self.render_text(event.message, mid, upd_to_webpend=None)
hunk ./telegram.py 447
- event = self.webpending.pop(update.webpage.id, None)
- if event:
- await self.handle_telegram_message(event, update.webpage)
-
- async def handle_telegram_message(self, event, upd_to_webpend=None):
- self.logger.debug('Handling Telegram Message: %s', event)
-
- user = self.get_irc_user_from_telegram(event.sender_id)
- mid = self.mid.num_to_id_offset(event.message.id)
-
- message = await self.render_text(event, mid, upd_to_webpend)
-
- chan = await self.relay_telegram_message(event, user, message)
-
- self.to_cache(event.message.id, mid, event.message.message, message, user, chan, event.message.media)
+ message = self.webpending.pop(update.webpage.id, None)
+ if message:
+ await self.handle_telegram_message(event=None, message=message, upd_to_webpend=update.webpage)
+
+ async def handle_telegram_message(self, event, message=None, upd_to_webpend=None):
+ self.logger.debug('Handling Telegram Message: %s', event or message)
+
+ if event:
+ msg = event.message
+ else:
+ msg = message
+
+ user = self.get_irc_user_from_telegram(msg.sender_id)
+ mid = self.mid.num_to_id_offset(msg.id)
+ text = await self.render_text(msg, mid, upd_to_webpend)
+ chan = await self.relay_telegram_message(msg, user, text)
+
+ self.to_cache(msg.id, mid, msg.message, text, user, chan, msg.media)
hunk ./telegram.py 468
- async def render_text(self, event, mid, upd_to_webpend):
+ async def render_text(self, message, mid, upd_to_webpend):
hunk ./telegram.py 470
- text = await self.handle_webpage(upd_to_webpend, event.message)
- elif event.message.media:
- text = await self.handle_telegram_media(event)
+ text = await self.handle_webpage(upd_to_webpend, message)
+ elif message.media:
+ text = await self.handle_telegram_media(message)
hunk ./telegram.py 474
- text = event.message.message
-
- if event.message.is_reply:
- refwd_text = await self.handle_telegram_reply(event)
- elif event.message.forward:
- refwd_text = await self.handle_telegram_forward(event)
+ text = message.message
+
+ if message.is_reply:
+ refwd_text = await self.handle_telegram_reply(message)
+ elif message.forward:
+ refwd_text = await self.handle_telegram_forward(message)
hunk ./telegram.py 483
- message = '[{}] {}{}'.format(mid, refwd_text, text)
- message = e.replace_mult(message, e.emo)
- return message
-
- async def relay_telegram_message(self, event, user, message, channel=None):
- private = (event and event.message.is_private) or (not event and not channel)
+ final_text = '[{}] {}{}'.format(mid, refwd_text, text)
+ final_text = e.replace_mult(final_text, e.emo)
+ return final_text
+
+ async def relay_telegram_message(self, message, user, text, channel=None):
+ private = (message and message.is_private) or (not message and not channel)
hunk ./telegram.py 490
- await self.relay_telegram_private_message(user, message)
+ await self.relay_telegram_private_message(user, text)
hunk ./telegram.py 493
- chan = await self.relay_telegram_channel_message(event, user, message, channel)
+ chan = await self.relay_telegram_channel_message(message, user, text, channel)
hunk ./telegram.py 501
- async def relay_telegram_channel_message(self, event, user, message, channel=None):
- self.logger.debug('Handling Telegram Channel Message: %s', event)
-
- if event:
- entity = await event.message.get_chat()
- chan = await self.get_irc_channel_from_telegram_id(event.message.chat_id, entity)
+ async def relay_telegram_channel_message(self, message, user, text, channel=None):
+ 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)
hunk ./telegram.py 509
- await self.irc.send_msg(user, chan, message)
+ await self.irc.send_msg(user, chan, text)
hunk ./telegram.py 545
- async def handle_telegram_reply(self, event):
+ async def handle_telegram_reply(self, message):
hunk ./telegram.py 547
- replied = await event.message.get_reply_message()
- message = replied.message
- if not message:
- message = '[{}]'.format(self.mid.num_to_id_offset(replied.id))
- elif len(message) > self.quote_len:
- message = message[:self.quote_len]
+ replied = await message.get_reply_message()
+ replied_msg = replied.message
+ if not replied_msg:
+ replied_msg = '[{}]'.format(self.mid.num_to_id_offset(replied.id))
+ elif len(replied_msg) > self.quote_len:
+ replied_msg = replied_msg[:self.quote_len]
hunk ./telegram.py 561
- return '|Re {}: {}{}| '.format(replied_nick, message, trunc)
-
- async def handle_telegram_forward(self, event):
- forwarded_nick = self.get_irc_nick_from_telegram_forward(event.message.fwd_from)
- forwarded_peer = event.forward.saved_from_peer
+ return '|Re {}: {}{}| '.format(replied_nick, replied_msg, trunc)
+
+ async def handle_telegram_forward(self, message):
+ forwarded_nick = self.get_irc_nick_from_telegram_forward(message.fwd_from)
+ forwarded_peer = message.fwd_from.saved_from_peer
hunk ./telegram.py 579
- async def handle_telegram_media(self, event):
- message = event.message
+ async def handle_telegram_media(self, message):
hunk ./telegram.py 593
- self.webpending[message.media.webpage.id] = event
+ self.webpending[message.media.webpage.id] = message
patch 0d2a17b54534226b14e45db9a6f5116bd2c3c41b
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Apr 30 00:53:29 CEST 2023
* telegram: Make more robust the forward handler, take into account if the
user has a privacy option to only show the name
hunk ./telegram.py 200
+ def get_irc_nick_from_telegram_forward(self, fwd):
+ if fwd.from_id is None:
+ # telegram user has privacy options to show only the name
+ nick = fwd.from_name
+ else:
+ user = self.get_irc_user_from_telegram(fwd.from_id.user_id)
+ if user is None:
+ nick = '{}'
+ self.refwd_me = True
+ else:
+ nick = user.irc_nick
+ return nick
+
hunk ./telegram.py 560
- async def handle_telegram_forward(self, event): [_$_]
- forwarded_user = self.get_irc_user_from_telegram(event.forward.from_id.user_id)
- if forwarded_user is None:
- forwarded_nick = '{}'
- self.refwd_me = True
- else:
- forwarded_nick = forwarded_user.irc_nick
+ async def handle_telegram_forward(self, event):
+ forwarded_nick = self.get_irc_nick_from_telegram_forward(event.message.fwd_from)
patch 4f4de75d90e58e3985a7cbd8f6aad6ace683a406
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Apr 29 23:02:02 CEST 2023
* telegram: Add conversion of UTF-8 emojis to ASCII emoticons for all messages (not only reactions)
hunk ./emoji2emoticon.py 89
+
+def replace_mult(line, emo):
+ for utf_emo in emo:
+ if utf_emo in line:
+ line = line.replace(utf_emo, emo[utf_emo])
+ return line
hunk ./telegram.py 364
- message = event.message.message
+ message = e.replace_mult(event.message.message, e.emo)
hunk ./telegram.py 372
- t = self.cache[id]['text']
+ t = e.replace_mult(self.cache[id]['text'], e.emo)
hunk ./telegram.py 468
+ message = e.replace_mult(message, e.emo)
patch 0bfa5e100cf36ddf987dd40df98b4fdbaa90728f
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Apr 29 20:47:52 CEST 2023
* Add "emoji_ascii" option to control if emojis are converted to ASCII emoticons
hunk ./irgramd 75
+ tornado.options.define('emoji_ascii', default=False, help='Replace emoji with ASCII emoticons')
hunk ./telegram.py 26
-from emoji2emoticon import emo
+import emoji2emoticon as e
hunk ./telegram.py 51
+ if not settings['emoji_ascii']:
+ e.emo = {}
hunk ./telegram.py 402
- react_icon = emo[emoji] if emoji in emo else emoji
+ react_icon = e.emo[emoji] if emoji in e.emo else emoji
patch 22812451b9c8e65dc2d08c04bd4cbde65d8c9b23
Author: E. Bosch <presidev@AT@gmail.com>
Date: Thu Apr 27 23:01:01 CEST 2023
* README update
hunk ./README.md 45
-- Deletes (receive)
+- Deletions (receive)
hunk ./README.md 52
+## Requirements
+
+- [python] (>= v3.8)
+- [telethon] (tested with v1.28.5)
+- [tornado] (tested with v6.1.0)
+- [aioconsole] (tested with v0.6.1)
+
hunk ./README.md 75
+[python]: https://www.python.org
+[tornado]: https://www.tornadoweb.org
+[aioconsole]: https://github.com/vxgmichel/aioconsole
patch e9b83df5a08d4ddb130b53ef26f389438e3dc75d
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Apr 26 21:20:04 CEST 2023
* README update
hunk ./README.md 10
-**[irgramd primary repository] is in [darcs] version control system, [github
-repository] is synched as secondary**
-
-**irgramd is a fork from [pbui/irtelegramd] to resume the development**
-
-**irgramd is under active development in alpha state, several planned features
-are not implemented yet**
+**[irgramd primary repository] is in [darcs] version control system, github
+is used as [project management and secondary repository]**
+
+**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
+planned features are not implemented yet**
hunk ./README.md 37
+## Features
+
+- Channels, groups and private chats
+- Users and channels mapped in IRC
+- Messages (receive, send)
+- Media in messages (receive, download)
+- Replies (receive)
+- Forwards (receive)
+- Deletes (receive)
+- Editions (receive)
+- Reactions (receive)
+- Dialogs management
+- Authentication and TLS for IRC
+- Multiple connections from IRC
+
hunk ./README.md 66
-[github repository]: https://github.com/prsai/irgramd
+[project management and secondary repository]: https://github.com/prsai/irgramd
patch 9fdc513df921e5f956b93feb4f061add2c55c548
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Apr 26 20:45:17 CEST 2023
* telegram: Rename option 'reply_length' as 'quote_length' and use it for quotes in reactions as well (not only for replies)
hunk ./irgramd 83
- tornado.options.define('reply_length', default=50, metavar='LENGTH', help='Max length of the text refered in replies, if longer is truncated')
+ tornado.options.define('quote_length', default=50, metavar='LENGTH', help='Max length of the text quoted in replies and reactions, if longer is truncated')
hunk ./telegram.py 25
-from utils import sanitize_filename, is_url_equiv, extract_url, get_human_size, get_human_duration, get_highlighted
+from utils import sanitize_filename, is_url_equiv, extract_url, get_human_size, get_human_duration, get_highlighted, fix_braces
hunk ./telegram.py 50
- self.reply_len = settings['reply_length']
+ self.quote_len = settings['quote_length']
hunk ./telegram.py 390
- if len(message_rendered) > 50:
- text_old = '{}...'.format(message_rendered[:50])
+ if len(message_rendered) > self.quote_len:
+ text_old = '{}...'.format(message_rendered[:self.quote_len])
+ text_old = fix_braces(text_old)
hunk ./telegram.py 532
- elif len(message) > self.reply_len:
- message = message[:self.reply_len]
+ elif len(message) > self.quote_len:
+ message = message[:self.quote_len]
hunk ./utils.py 166
+
+def fix_braces(text):
+ # Remove braces not closed, if the text was truncated
+ if text.endswith(' {...'):
+ subtext = text[:-5]
+ if not '{}' in subtext:
+ return '{}...'.format(subtext)
+ return text
patch 1d3ba8cce2f25104ebe89e202143ac27bfec87fa
Author: E. Bosch <presidev@AT@gmail.com>
Date: Tue Apr 25 22:34:42 CEST 2023
* telegram: Add support for showing reactions to messages
Add conversion of UTF-8 emojis to ASCII emoticons
addfile ./emoji2emoticon.py
hunk ./emoji2emoticon.py 1
+# irgramd: IRC-Telegram gateway
+# emoji2emoticon.py: UTF-8 Emoji to ASCII emoticon replacement
+#
+# (C) Copyright 2019,2023 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.
+
+emo = {
+ '\U0000270c': '"V"',
+ '\U00002764': '"<3"',
+ '\U0001f389': '"<*``"',
+ '\U0001f44c': '"ok"',
+ '\U0001f44d': '"+1"',
+ '\U0001f44e': '"-1"',
+ '\U0001f44f': '"m_m"',
+ '\U0001f525': '"\^^^/"',
+ '\U0001f600': '":D"',
+ '\U0001f601': '"xD"',
+ '\U0001f602': '"x_D"',
+ '\U0001f603': '":D"',
+ '\U0001f604': '"xD"',
+ '\U0001f605': '"x`D"',
+ '\U0001f606': '"xD"',
+ '\U0001f607': '"O:)"',
+ '\U0001f608': '"}:)"',
+ '\U0001f609': '";)"',
+ '\U0001f60a': '"x)"',
+ '\U0001f60b': '"xP"',
+ '\U0001f60c': '":)"',
+ '\U0001f60d': '"E>)"',
+ '\U0001f60e': '"B)"',
+ '\U0001f60f': '"- -,"',
+ '\U0001f610': '":|"',
+ '\U0001f611': '":|"',
+ '\U0001f612': '"-. -."',
+ '\U0001f613': '":`|"',
+ '\U0001f614': '":|"',
+ '\U0001f615': '":/"',
+ '\U0001f616': '":S"',
+ '\U0001f617': '":*"',
+ '\U0001f618': '":**"',
+ '\U0001f619': '"x*"',
+ '\U0001f61a': '"x*"',
+ '\U0001f61b': '":P"',
+ '\U0001f61c': '";P"',
+ '\U0001f61d': '"xP"',
+ '\U0001f61e': '":("',
+ '\U0001f61f': '":(("',
+ '\U0001f620': '":("',
+ '\U0001f621': '":("',
+ '\U0001f622': '":_("',
+ '\U0001f623': '"x("',
+ '\U0001f624': '":<("',
+ '\U0001f625': '":`("',
+ '\U0001f626': '":(|"',
+ '\U0001f627': '":(||"',
+ '\U0001f628': '"||:("',
+ '\U0001f629': '"::("',
+ '\U0001f62a': '":`("',
+ '\U0001f62b': '"x("',
+ '\U0001f62c': '":E"',
+ '\U0001f62d': '":__(|"',
+ '\U0001f62e': '":O"',
+ '\U0001f62f': '":o"',
+ '\U0001f630': '":`O"',
+ '\U0001f631': '":O>"',
+ '\U0001f632': '"8-O"',
+ '\U0001f633': '":8|"',
+ '\U0001f634': '":.zz"',
+ '\U0001f635': '"6)"',
+ '\U0001f636': '":"',
+ '\U0001f637': '":W"',
+ '\U0001f638': '">:D"',
+ '\U0001f639': '":_D"',
+ '\U0001f63a': '">:D"',
+ '\U0001f63b': '">E>D"',
+ '\U0001f63c': '">- -,"',
+ '\U0001f63d': '">:*"',
+ '\U0001f63e': '">:("',
+ '\U0001f63f': '">:_("',
+ '\U0001f640': '">:(|"',
+ '\U0001f641': '":("',
+ '\U0001f642': '":)"',
+ '\U0001f643': '"(:"',
+ '\U0001f644': '"o o,"',
+ '\U0001f970': '":)e>"'
+ }
hunk ./irc.py 414
- self.tg.add_to_cache(tg_msg.id, mid, text, message, user, chan)
+ self.tg.to_cache(tg_msg.id, mid, text, message, user, chan, media=None)
hunk ./telegram.py 19
+from telethon.tl.functions.messages import GetMessagesReactionsRequest
hunk ./telegram.py 26
+from emoji2emoticon import emo
hunk ./telegram.py 316
- def add_to_cache(self, id, mid, message, proc_message, user, chan):
+ async def edition_case(self, msg):
+ def msg_edited(m):
+ return m.id in self.cache and \
+ ( m.message != self.cache[m.id]['text']
+ or m.media != self.cache[m.id]['media']
+ )
+ async def get_reactions(m):
+ react = await self.telegram_client(GetMessagesReactionsRequest(m.peer_id, id=[m.id]))
+ return react.updates[0].reactions.recent_reactions
+
+ react = None
+ if msg.reactions is None:
+ case = 'edition'
+ elif (reactions := await get_reactions(msg)) is None:
+ if msg_edited(msg):
+ case = 'edition'
+ else:
+ case = 'react-del'
+ elif react := next((x for x in reactions if x.date == msg.edit_date), None):
+ case = 'react-add'
+ else:
+ if msg_edited(msg):
+ case = 'edition'
+ else:
+ case = 'react-del'
+ react = None
+ return case, react
+
+ def to_cache(self, id, mid, message, proc_message, user, chan, media):
hunk ./telegram.py 352
- 'channel': chan
+ 'channel': chan,
+ 'media': media
hunk ./telegram.py 360
- user = self.get_irc_user_from_telegram(event.sender_id)
hunk ./telegram.py 365
- if id in self.cache:
- t = self.cache[id]['text']
- rt = self.cache[id]['rendered_text']
-
- ht, is_ht = get_highlighted(t, message)
-
- self.cache[id]['text'] = message
- self.cache[id]['rendered_text'] = message_rendered
+ edition_case, reaction = await self.edition_case(event.message)
+ if edition_case == 'edition':
+ action = 'Edited'
+ user = self.get_irc_user_from_telegram(event.sender_id)
+ if id in self.cache:
+ t = self.cache[id]['text']
+ rt = self.cache[id]['rendered_text']
+
+ ht, is_ht = get_highlighted(t, message)
+ else:
+ rt = fmid
+ is_ht = False
+
+ if is_ht:
+ edition_react = ht
+ text_old = fmid
+ else:
+ edition_react = message
+ text_old = rt
+ if user is None:
+ self.refwd_me = True
+
+ # Reactions
hunk ./telegram.py 389
- rt = fmid
- is_ht = False
-
- if is_ht:
- text_edited = ht
- text_old = fmid
- else:
- text_edited = message
- text_old = rt
- if user is None:
- self.refwd_me = True
-
- text = '|Edited {}| {}'.format(text_old, text_edited)
+ action = 'React'
+ if len(message_rendered) > 50:
+ text_old = '{}...'.format(message_rendered[:50])
+ 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 = emo[emoji] if emoji in 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 = '|{} {}| {}'.format(action, text_old, edition_react)
+
hunk ./telegram.py 410
- if id not in self.cache:
- self.add_to_cache(id, mid, message, message_rendered, user, chan)
+ self.to_cache(id, mid, message, message_rendered, user, chan, event.message.media)
hunk ./telegram.py 445
- self.add_to_cache(event.message.id, mid, event.message.message, message, user, chan)
+ self.to_cache(event.message.id, mid, event.message.message, message, user, chan, event.message.media)
patch 5b3071c8f389341108b84aad6b91351a118cddb0
Author: E. Bosch <presidev@AT@gmail.com>
Date: Thu Apr 20 01:49:15 CEST 2023
* telegram: Relocate is_bot() method, to have get_* methods together
hunk ./telegram.py 246
- async def is_bot(self, irc_nick, tid=None):
- user = self.irc.users[irc_nick]
- if user.stream or user.is_service:
- bot = False
- else:
- bot = user.bot
- if bot == None:
- tid = self.get_tid(irc_nick, tid)
- tg_user = await self.telegram_client.get_entity(tid)
- bot = tg_user.bot
- user.bot = bot
- return bot
-
hunk ./telegram.py 301
+ async def is_bot(self, irc_nick, tid=None):
+ user = self.irc.users[irc_nick]
+ if user.stream or user.is_service:
+ bot = False
+ else:
+ bot = user.bot
+ if bot == None:
+ tid = self.get_tid(irc_nick, tid)
+ tg_user = await self.telegram_client.get_entity(tid)
+ bot = tg_user.bot
+ user.bot = bot
+ return bot
+
patch 515b30d8c49e9ec8619b0c0e3249d093e88a2a74
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Apr 16 01:13:07 CEST 2023
* telegram: Add support for showing editions of messages, including highlight of differences
hunk ./irc.py 412
- text = '[{}] {}'.format(self.tg.mid.num_to_id_offset(tg_msg.id), message)
- self.tg.add_to_cache(tg_msg.id, None, text, user, chan)
+ mid = self.tg.mid.num_to_id_offset(tg_msg.id)
+ text = '[{}] {}'.format(mid, message)
+ self.tg.add_to_cache(tg_msg.id, mid, text, message, user, chan)
hunk ./telegram.py 24
-from utils import sanitize_filename, is_url_equiv, extract_url, get_human_size, get_human_duration
+from utils import sanitize_filename, is_url_equiv, extract_url, get_human_size, get_human_duration, get_highlighted
hunk ./telegram.py 90
+ (self.handle_telegram_edited, telethon.events.MessageEdited),
hunk ./telegram.py 314
- def add_to_cache(self, id, mid, message, user, chan):
+ def add_to_cache(self, id, mid, message, proc_message, user, chan):
hunk ./telegram.py 319
- 'rendered_text': message,
+ 'text': message,
+ 'rendered_text': proc_message,
hunk ./telegram.py 325
+ async def handle_telegram_edited(self, event):
+ self.logger.debug('Handling Telegram Message Edited: %s', event)
+
+ id = event.message.id
+ user = self.get_irc_user_from_telegram(event.sender_id)
+ mid = self.mid.num_to_id_offset(id)
+ fmid = '[{}]'.format(mid)
+ message = event.message.message
+ message_rendered = await self.render_text(event, mid, upd_to_webpend=None)
+
+ if id in self.cache:
+ t = self.cache[id]['text']
+ rt = self.cache[id]['rendered_text']
+
+ ht, is_ht = get_highlighted(t, message)
+
+ self.cache[id]['text'] = message
+ self.cache[id]['rendered_text'] = message_rendered
+ else:
+ rt = fmid
+ is_ht = False
+
+ if is_ht:
+ text_edited = ht
+ text_old = fmid
+ else:
+ text_edited = message
+ text_old = rt
+ if user is None:
+ self.refwd_me = True
+
+ text = '|Edited {}| {}'.format(text_old, text_edited)
+ chan = await self.relay_telegram_message(event, user, text)
+
+ if id not in self.cache:
+ self.add_to_cache(id, mid, message, message_rendered, user, chan)
+
hunk ./telegram.py 391
+ message = await self.render_text(event, mid, upd_to_webpend)
+
+ chan = await self.relay_telegram_message(event, user, message)
+
+ self.add_to_cache(event.message.id, mid, event.message.message, message, user, chan)
+
+ self.refwd_me = False
+
+ async def render_text(self, event, mid, upd_to_webpend):
hunk ./telegram.py 415
-
- chan = await self.relay_telegram_message(event, user, message)
-
- self.add_to_cache(event.message.id, mid, message, user, chan)
-
- self.refwd_me = False
+ return message
hunk ./utils.py 14
+import difflib
hunk ./utils.py 125
+
+def get_highlighted(a, b):
+ awl = len(a.split())
+ bwl = len(b.split())
+ delta_size = abs(awl - bwl)
+ highlighted = True
+
+ if not a:
+ res = '> {}'.format(b)
+ elif delta_size > 5:
+ res = b
+ highlighted = False
+ else:
+ al = a.split(' ')
+ bl = b.split(' ')
+ diff = difflib.ndiff(al, bl)
+ ld = list(diff)
+ res = ''
+ d = ''
+ eq = 0
+
+ for i in ld:
+ if i == '- ' or i[0] == '?':
+ continue
+ elif i == ' ' or i == '+ ':
+ res += ' '
+ continue
+ elif i[0] == '-':
+ res += '.{}. '.format(i[2:])
+ elif i[0] == '+':
+ res += '_{}_ '.format(i[2:])
+ else:
+ res += '{} '.format(i[2:])
+ eq += 1
+
+ delta_eq = bwl - eq
+ if delta_eq > 3:
+ res = b
+ highlighted = False
+
+ return res, highlighted
patch 7f549d8b7544443878875c310fea6f1fb6385364
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Apr 12 00:43:22 CEST 2023
* telegram: Fix recognition of Telegram user, group, channel types
hunk ./service.py 79
- ' {:<11} {:<9} {:<9} {:4} {:<3} {:<4} {:<6} {}'.format(
+ ' {:<11} {:<9} {:<9} {:5} {:<3} {:<4} {:<6} {}'.format(
hunk ./service.py 86
- ty = 'User' if dialog.is_user else 'Chat' if dialog.is_group else 'Chan'
+ ty = self.tg.get_entity_type(dialog.entity, format='short')
hunk ./service.py 97
- reply += (' {:<11d} {:<9d} {:<9d} {:4} {:<3} {:<4} {:<6} {}'.format(
+ reply += (' {:<11d} {:<9d} {:<9d} {:5} {:<3} {:<4} {:<6} {}'.format(
hunk ./telegram.py 266
- entity_type = self.get_entity_type(entity)
+ entity_type = self.get_entity_type(entity, format='long')
hunk ./telegram.py 294
- def get_entity_type(self, entity):
- return type(entity).__name__
+ def get_entity_type(self, entity, format):
+ if isinstance(entity, tgty.User):
+ short = long = 'User'
+ elif isinstance(entity, tgty.Chat):
+ short = 'Chat'
+ long = 'Chat/Basic Group'
+ elif isinstance(entity, tgty.Channel):
+ if entity.broadcast:
+ short = 'Broad'
+ long = 'Broadcast Channel'
+ elif entity.megagroup:
+ short = 'Mega'
+ long = 'Super/Megagroup Channel'
+ elif entity.gigagroup:
+ short = 'Giga'
+ long = 'Broadcast Gigagroup Channel'
+
+ return short if format == 'short' else long
patch e664cfbb6157d6f9eab1db5d836213317fe1c2e5
Author: E. Bosch <presidev@AT@gmail.com>
Date: Tue Apr 11 09:45:58 CEST 2023
* Add compact message ids in messages sent from IRC that are echoed to IRC,
add those messages to cache
hunk ./irc.py 387
+ defered_send = None
hunk ./irc.py 390
+ # used defered_send function when id is known
hunk ./irc.py 392
- await self.send_msg_others(user, tgl, message)
+ chan = tgl
+ defered_send = self.send_msg_others
+ defered_target = chan
+ else:
+ chan = None
hunk ./irc.py 402
- await self.send_msg(user, None, message)
+ # used defered_send function when id is known
+ defered_send = self.send_msg
+ defered_target = None
hunk ./irc.py 410
- await self.tg.telegram_client.send_message(telegram_id, message)
+ tg_msg = await self.tg.telegram_client.send_message(telegram_id, message)
+
+ text = '[{}] {}'.format(self.tg.mid.num_to_id_offset(tg_msg.id), message)
+ self.tg.add_to_cache(tg_msg.id, None, text, user, chan)
+
+ if defered_send:
+ await defered_send(user, defered_target, text)
patch cee19ae7322cdc3235ad93d1666e9d44669ed16d
Author: E. Bosch <presidev@AT@gmail.com>
Date: Mon Apr 10 23:31:19 CEST 2023
* telegram: Add support for showing deleted messages
hunk ./telegram.py 89
+ (self.handle_telegram_deleted , telethon.events.MessageDeleted),
hunk ./telegram.py 307
+ async def handle_telegram_deleted(self, event):
+ self.logger.debug('Handling Telegram Message Deleted: %s', event)
+
+ for deleted_id in event.original_update.messages:
+ if deleted_id in self.cache:
+ recovered_text = self.cache[deleted_id]['rendered_text']
+ text = '|Deleted| {}'.format(recovered_text)
+ user = self.cache[deleted_id]['user']
+ chan = self.cache[deleted_id]['channel']
+ await self.relay_telegram_message(event=None, user=user, message=text, channel=chan)
+ else:
+ mid = self.mid.num_to_id_offset(deleted_id)
+ text = 'Message id {} deleted not in cache'.format(mid)
+ await self.relay_telegram_private_message(self.irc.service_user, text)
+
hunk ./telegram.py 333
- if self.mid.mesg_base is None:
- self.mid.mesg_base = event.message.id
-
hunk ./telegram.py 334
- mid = self.mid.num_to_id(event.message.id - self.mid.mesg_base)
+ mid = self.mid.num_to_id_offset(event.message.id)
hunk ./telegram.py 421
- message = '[{}]'.format(self.mid.num_to_id(replied.id - self.mid.mesg_base))
+ message = '[{}]'.format(self.mid.num_to_id_offset(replied.id))
hunk ./telegram.py 621
+ def num_to_id_offset(self, num):
+ if self.mesg_base is None:
+ self.mesg_base = num
+ return self.num_to_id(num - self.mesg_base)
+
patch d4f07b39c63d6cfefbf3f3c7bd91fac6969ce39d
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Apr 9 01:07:35 CEST 2023
* telegram: Refactorize relay message functions, this will be necessary for deleted message support
hunk ./telegram.py 339
- if event.message.is_private:
+ chan = await self.relay_telegram_message(event, user, message)
+
+ self.add_to_cache(event.message.id, mid, message, user, chan)
+
+ self.refwd_me = False
+
+ async def relay_telegram_message(self, event, user, message, channel=None):
+ private = (event and event.message.is_private) or (not event and not channel)
+ if private:
hunk ./telegram.py 351
- chan = await self.relay_telegram_channel_message(event, user, message)
-
- self.add_to_cache(event.message.id, mid, message, user, chan)
-
- self.refwd_me = False
+ chan = await self.relay_telegram_channel_message(event, user, message, channel)
+ return chan
hunk ./telegram.py 359
- async def relay_telegram_channel_message(self, event, user, message):
+ async def relay_telegram_channel_message(self, event, user, message, channel=None):
hunk ./telegram.py 362
- entity = await event.message.get_chat()
- channel = await self.get_irc_channel_from_telegram_id(event.message.chat_id, entity)
- await self.irc.send_msg(user, channel, message)
- return channel
+ if event:
+ entity = await event.message.get_chat()
+ chan = await self.get_irc_channel_from_telegram_id(event.message.chat_id, entity)
+ else:
+ chan = channel
+ await self.irc.send_msg(user, chan, message)
+ return chan
patch 1a0d9f40779a40ea3c7e9967b71d8142ceba0081
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Apr 8 20:56:09 CEST 2023
* telegram: Rename some functions: use "relay" instead of "handle", to clarify
hunk ./telegram.py 340
- await self.handle_telegram_private_message(user, message)
+ await self.relay_telegram_private_message(user, message)
hunk ./telegram.py 343
- chan = await self.handle_telegram_channel_message(event, user, message)
+ chan = await self.relay_telegram_channel_message(event, user, message)
hunk ./telegram.py 349
- async def handle_telegram_private_message(self, user, message):
+ async def relay_telegram_private_message(self, user, message):
hunk ./telegram.py 354
- async def handle_telegram_channel_message(self, event, user, message):
+ async def relay_telegram_channel_message(self, event, user, message):
patch 677c32d4755b0de6d30000202b99de435ca742d3
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Apr 8 12:30:10 CEST 2023
* telegram: Add message cache
hunk ./telegram.py 16
+import collections
hunk ./telegram.py 58
+ self.cache = collections.OrderedDict()
hunk ./telegram.py 296
+ def add_to_cache(self, id, mid, message, user, chan):
+ if len(self.cache) >= 10000:
+ self.cache.popitem(last=False)
+ self.cache[id] = {
+ 'mid': mid,
+ 'rendered_text': message,
+ 'user': user,
+ 'channel': chan
+ }
+
hunk ./telegram.py 341
+ chan = None
hunk ./telegram.py 343
- await self.handle_telegram_channel_message(event, user, message)
+ chan = await self.handle_telegram_channel_message(event, user, message)
+
+ self.add_to_cache(event.message.id, mid, message, user, chan)
hunk ./telegram.py 360
+ return channel
patch 69fd83158f7bf48b9ab8ffc3cf18d7938cbe2967
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Mar 31 22:46:58 CEST 2023
* telegram: Add logging in handle_raw()
hunk ./telegram.py 295
+ self.logger.debug('Handling Telegram Raw Event: %s', update)
+
patch d7fd113735674f001a113114fd8562b160458b86
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Mar 31 00:32:55 CEST 2023
* Remove obsolete dockerfile
hunk ./Dockerfile 1
-FROM alpine:latest
-MAINTAINER Peter Bui <pbui@yld.bx612.space>
-
-RUN apk update && \
- apk add python3 py3-pip
-
-RUN pip3 install telethon tornado==5.1.1
-
-RUN wget -O - https://gitlab.com/pbui/irtelegramd/-/archive/master/irtelegramd-master.tar.gz | tar xzvf -
-
-COPY irtelegramd.py /irtelegramd-master
-
-EXPOSE 6667
-ENTRYPOINT ["/irtelegramd-master/irtelegramd.py", "--address=0.0.0.0", "--config_dir=/var/lib/irtelegramd"]
rmfile ./Dockerfile
patch e1aef39b9e3f2cbe017b3c1ea324ccc9c330a73a
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Mar 31 00:31:39 CEST 2023
* Update copyright year in license
hunk ./LICENSE 4
-Copyright (c) 2020-2022 E. Bosch <presidev@AT@gmail.com>
+Copyright (c) 2020-2023 E. Bosch <presidev@AT@gmail.com>
hunk ./README.md 39
-Copyright (c) 2020-2022 E. Bosch <presidev@AT@gmail.com>
+Copyright (c) 2020-2023 E. Bosch <presidev@AT@gmail.com>
patch 945c376d09b4d456a8da3a5cc9b3ef17a0735abb
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Mar 29 21:50:59 CEST 2023
* Update year in copyright notices
hunk ./irc.py 5
-# Copyright (c) 2020-2022 E. Bosch <presidev@AT@gmail.com>
+# Copyright (c) 2020-2023 E. Bosch <presidev@AT@gmail.com>
hunk ./irgramd 6
-# Copyright (c) 2020-2022 E. Bosch <presidev@AT@gmail.com>
+# Copyright (c) 2020-2023 E. Bosch <presidev@AT@gmail.com>
hunk ./service.py 4
-# Copyright (c) 2022 E. Bosch <presidev@AT@gmail.com>
+# Copyright (c) 2022,2023 E. Bosch <presidev@AT@gmail.com>
hunk ./telegram.py 5
-# Copyright (c) 2020-2022 E. Bosch <presidev@AT@gmail.com>
+# Copyright (c) 2020-2023 E. Bosch <presidev@AT@gmail.com>
hunk ./utils.py 5
-# Copyright (c) 2020-2022 E. Bosch <presidev@AT@gmail.com>
+# Copyright (c) 2020-2023 E. Bosch <presidev@AT@gmail.com>
patch aedc28b24f83d3fb3266bb7effe4cb7dcb3f7414
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Mar 29 21:07:06 CEST 2023
* telegram: Add 'reply_length' option to limit the length of the text refered in replies
hunk ./irgramd 83
+ tornado.options.define('reply_length', default=50, metavar='LENGTH', help='Max length of the text refered in replies, if longer is truncated')
hunk ./telegram.py 47
+ self.reply_len = settings['reply_length']
hunk ./telegram.py 383
- elif len(message) > 30:
- message = message[:30]
+ elif len(message) > self.reply_len:
+ message = message[:self.reply_len]
patch 0f6013389cd9efdee9025e5483d1f2d4cde460a8
Author: E. Bosch <presidev@AT@gmail.com>
Date: Tue Mar 28 23:32:36 CEST 2023
* telegram: Add support for showing replies and forwards of messages
hunk ./irc.py 454
+ if self.tg.refwd_me:
+ msg = msg.format(user.irc_nick)
hunk ./telegram.py 55
+ self.refwd_me = False
hunk ./telegram.py 315
- message = '[{}] {}'.format(mid, text)
+ if event.message.is_reply:
+ refwd_text = await self.handle_telegram_reply(event)
+ elif event.message.forward:
+ refwd_text = await self.handle_telegram_forward(event)
+ else:
+ refwd_text = ''
+
+ message = '[{}] {}{}'.format(mid, refwd_text, text)
hunk ./telegram.py 329
+ self.refwd_me = False
+
hunk ./telegram.py 376
+ async def handle_telegram_reply(self, event):
+ trunc = ''
+ replied = await event.message.get_reply_message()
+ message = replied.message
+ if not message:
+ message = '[{}]'.format(self.mid.num_to_id(replied.id - self.mid.mesg_base))
+ elif len(message) > 30:
+ message = message[:30]
+ trunc = '...'
+ replied_user = self.get_irc_user_from_telegram(replied.sender_id)
+ if replied_user is None:
+ replied_nick = '{}'
+ self.refwd_me = True
+ else:
+ replied_nick = replied_user.irc_nick
+
+ return '|Re {}: {}{}| '.format(replied_nick, message, trunc)
+
+ async def handle_telegram_forward(self, event): [_$_]
+ forwarded_user = self.get_irc_user_from_telegram(event.forward.from_id.user_id)
+ if forwarded_user is None:
+ forwarded_nick = '{}'
+ self.refwd_me = True
+ else:
+ forwarded_nick = forwarded_user.irc_nick
+ forwarded_peer = event.forward.saved_from_peer
+ if isinstance(forwarded_peer, tgty.PeerChannel):
+ dest = ' ' + await self.get_irc_channel_from_telegram_id(forwarded_peer.channel_id)
+ elif isinstance(forwarded_peer, tgty.PeerChat):
+ dest = ' ' + await self.get_irc_channel_from_telegram_id(forwarded_peer.chat_id)
+ else:
+ # if it's from me I want to know who was the destination of a message (user)
+ if self.refwd_me:
+ dest = ' ' + self.get_irc_user_from_telegram(forwarded_peer.user_id).irc_nick
+ else:
+ dest = ''
+
+ return '|Fwd {}{}| '.format(forwarded_nick, dest)
+
patch 5f288223526f5888b98269a37b8c36df0be8f38d
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Mar 24 23:45:37 CET 2023
* Use named parameter full_join in join_irc_channel(), remove default value False not needed
hunk ./irc.py 228
- await self.join_irc_channel(user, channel, True)
+ await self.join_irc_channel(user, channel, full_join=True)
hunk ./irc.py 543
- async def join_irc_channel(self, user, channel, full_join=False):
+ async def join_irc_channel(self, user, channel, full_join):
hunk ./telegram.py 353
- await self.irc.join_irc_channel(irc_nick, irc_channel, False)
+ await self.irc.join_irc_channel(irc_nick, irc_channel, full_join=False)
hunk ./telegram.py 364
- await self.irc.join_irc_channel(self.irc.irc_nick, channel, True)
+ await self.irc.join_irc_channel(self.irc.irc_nick, channel, full_join=True)
patch 830950349c796059b0bbe9759c277846252d6db6
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Mar 22 22:15:41 CET 2023
* telegram: Use positive IDs when checking if a channel already exists when a new message is received
hunk ./telegram.py 17
-from telethon import types as tgty
+from telethon import types as tgty, utils as tgutils
hunk ./telegram.py 200
- if tid not in self.tid_to_iid:
+ rtid, type = tgutils.resolve_id(tid)
+ if rtid not in self.tid_to_iid:
hunk ./telegram.py 204
- self.tid_to_iid[tid] = channel
- self.irc.iid_to_tid[channel] = tid
-
- return self.tid_to_iid[tid]
+ self.tid_to_iid[rtid] = channel
+ self.irc.iid_to_tid[channel] = rtid
+
+ return self.tid_to_iid[rtid]
patch 99ff6d5f15836e4bd53dfba9c49b1bcaf8a41b4b
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Mar 19 02:40:30 CET 2023
* Fix deprecation warning about the use of asyncio.get_event_loop(), use asyncio.new_event_loop() instead
hunk ./irgramd 113
- asyncio.get_event_loop().run_until_complete(irc_server.run(options))
- asyncio.get_event_loop().run_forever()
+ loop = asyncio.new_event_loop()
+ loop.run_until_complete(irc_server.run(options))
+ loop.run_forever()
patch a032f79bcbacc71d08e813af1e4b8b513111f34e
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Mar 19 02:15:39 CET 2023
* service: Fix compact date/time for more than 1 year, in dialog list
hunk ./utils.py 118
- elif delta.days < 366:
+ elif delta.days < 365:
patch 3c474f69222f523a7b93853ead954940374688ad
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Mar 18 05:25:42 CET 2023
* service: Use positive ids in dialog list, if not, channel names are not shown correctly
hunk ./service.py 10
+from telethon import utils as tgutils
hunk ./service.py 83
- id = dialog.id
+ id, type = tgutils.resolve_id(dialog.id)
patch 039674332424c9792bcc0ef23d43f47dd7189644
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Mar 18 05:12:31 CET 2023
* service: In dialog list: support entries with unknown names (shouldn't happen)
hunk ./service.py 89
- name_in_irc = self.tmp_ircnick if id == self.tg.id else self.tg.tid_to_iid[id]
+ if id == self.tg.id:
+ name_in_irc = self.tmp_ircnick
+ else:
+ if id in self.tg.tid_to_iid.keys():
+ name_in_irc = self.tg.tid_to_iid[id]
+ else:
+ name_in_irc = '<Unknown>'
patch 6ee9aa156a4ffb387c5d160d53ba72cc34d6e1bb
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Mar 18 04:43:35 CET 2023
* telegram: Support (different) channels with same name
hunk ./telegram.py 180
- return '#' + chat.title.replace(' ', '-').replace(',', '-')
+ chan = '#' + chat.title.replace(' ', '-').replace(',', '-')
+ while chan.lower() in self.irc.iid_to_tid:
+ chan += '_'
+ return chan
patch d168c6b0635df9185312cd63f8332647d85bac4b
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Mar 18 04:42:34 CET 2023
* telegram: Support empty (only containing self user) chats
hunk ./telegram.py 148
+ self.irc.irc_channels[chan] = set()
patch 08302dbf9ee8df43f79b27308551499d73115a72
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Mar 18 00:08:10 CET 2023
* telegram: Fix: use positive chat ids, it seems necessary at least in Telethon v1.24.0
hunk ./telegram.py 145
- self.tid_to_iid[-chat.id] = channel
+ self.tid_to_iid[chat.id] = channel
hunk ./telegram.py 147
- self.irc.iid_to_tid[chan] = -chat.id
+ self.irc.iid_to_tid[chan] = chat.id
patch 24d0a1e56c3957fbf4257882a52f2b98706b9fcd
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Mar 17 23:03:05 CET 2023
* telegram: Minor optimization in set_irc_channel_from_telegram()
hunk ./telegram.py 146
- self.irc.iid_to_tid[channel.lower()] = -chat.id
hunk ./telegram.py 147
+ self.irc.iid_to_tid[chan] = -chat.id
patch ca97990016d86bdb491bbead36be23125a3548a9
Author: E. Bosch <presidev@AT@gmail.com>
Date: Thu Mar 16 01:06:33 CET 2023
* irc: If there is no owner of a group in Telegram, in IRC show the service user as owner/founder
hunk ./irc.py 582
- founder = list(self.irc_channels_founder[chan])[0]
+ if self.irc_channels_founder[chan]:
+ founder = list(self.irc_channels_founder[chan])[0]
+ else:
+ founder = self.service_user.irc_nick
patch 2e88501e2cdbf503df49404f4b4fffeed574658b
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Mar 19 23:26:56 CET 2022
* service: Add dialog command (only with list subcommand by now)
hunk ./irc.py 383
- reply = await self.service.parse_command(message)
+ reply = await self.service.parse_command(message, user.irc_nick)
hunk ./service.py 9
+from utils import compact_date
+
hunk ./service.py 16
+ 'dialog': (self.handle_command_dialog, 1, 2),
hunk ./service.py 21
-
- async def parse_command(self, line):
+ self.tmp_ircnick = None
+
+ async def parse_command(self, line, nick):
hunk ./service.py 27
+ self.tmp_ircnick = nick
hunk ./service.py 65
+ )
+ return reply
+
+ async def handle_command_dialog(self, command=None, id=None, help=None):
+ if not help:
+ if command == 'archive':
+ pass
+ elif command == 'delete':
+ pass
+ elif command == 'list':
+ reply = \
+ (
+ 'Dialogs:',
+ ' {:<11} {:<9} {:<9} {:4} {:<3} {:<4} {:<6} {}'.format(
+ 'Id', 'Unread', 'Mentions', 'Type', 'Pin', 'Arch', 'Last', 'Name'),
+ )
+ async for dialog in self.tg.telegram_client.iter_dialogs():
+ id = dialog.id
+ unr = dialog.unread_count
+ men = dialog.unread_mentions_count
+ ty = 'User' if dialog.is_user else 'Chat' if dialog.is_group else 'Chan'
+ pin = 'Yes' if dialog.pinned else 'No'
+ arch = 'Yes' if dialog.archived else 'No'
+ last = compact_date(dialog.date)
+ name_in_irc = self.tmp_ircnick if id == self.tg.id else self.tg.tid_to_iid[id]
+ reply += (' {:<11d} {:<9d} {:<9d} {:4} {:<3} {:<4} {:<6} {}'.format(
+ id, unr, men, ty, pin, arch, last, name_in_irc),
+ )
+
+ else: # HELP.brief or HELP.desc (first line)
+ reply = (' dialog Manage conversations (dialogs)',)
+ if help == HELP.desc: # rest of HELP.desc
+ reply += \
+ (
+ ' 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',
+ ' list Show all dialogs',
hunk ./telegram.py 145
- self.tid_to_iid[chat.id] = channel
- self.irc.iid_to_tid[channel.lower()] = chat.id
+ self.tid_to_iid[-chat.id] = channel
+ self.irc.iid_to_tid[channel.lower()] = -chat.id
hunk ./utils.py 13
+import datetime
hunk ./utils.py 112
+
+def compact_date(date):
+ delta = datetime.datetime.now(datetime.timezone.utc) - date
+
+ if delta.days < 1:
+ compact_date = date.strftime('%H:%M')
+ elif delta.days < 366:
+ compact_date = date.strftime('%d-%b')
+ else:
+ compact_date = date.strftime('%Y')
+
+ return compact_date
patch 60471321f48dd5fba8292ee22e9ee6a38f469508
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Mar 19 22:26:45 CET 2022
* service: Remove hostname from TelegramServ for compactness
hunk ./irc.py 143
- self.service_user = IRCUser(None, ('Services.{}'.format(self.hostname),), self.conf['service_user'],
+ self.service_user = IRCUser(None, ('Services',), self.conf['service_user'],
patch 062bc6053a000038e9edcc0af8974851f9fe753d
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Mar 19 06:11:26 CET 2022
* Wait for Telegram authentication is checked before the "not authorized yet"
message is given on IRC (by TelegramServ).
This is convenient if an IRC client connects faster than Telegram connection
is established when irgramd is started, so it won't give a fake "not authorized
yet" message.
hunk ./irc.py 510
+ await self.tg.auth_checked.wait()
hunk ./telegram.py 15
+import asyncio
hunk ./telegram.py 55
+ # Set event to be waited by irc.check_telegram_auth()
+ self.auth_checked = asyncio.Event()
hunk ./telegram.py 98
+ self.auth_checked.set()
hunk ./telegram.py 114
+ self.auth_checked.set()
patch 232723b9dcfbfc20de95b31bc610a8de971a95c3
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Mar 18 22:08:29 CET 2022
* service: Send reply messages from service only to the IRC connected user
that sent the command
hunk ./irc.py 385
- await self.send_msg(self.service_user, user.irc_nick, reply_line)
+ await self.send_msg(self.service_user, None, reply_line, user)
hunk ./irc.py 420
- async def send_msg(self, source, target, message):
+ async def send_msg(self, source, target, message, selfuser=None):
hunk ./irc.py 427
- if is_chan:
+ if selfuser:
+ irc_users = (selfuser,)
+ elif is_chan:
hunk ./irc.py 507
- await self.send_msg(self.service_user, user.irc_nick, line)
+ await self.send_msg(self.service_user, None, line, user)
patch 712de2f8a9af0ab75b8b3f05f7564e1987fe0453
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Mar 18 20:57:45 CET 2022
* irc: Add character encoding selection
hunk ./irc.py 85
- message = message.decode().replace('\r','\n')
+ message = message.decode(self.conf['char_encoding'], errors='replace').replace('\r','\n')
hunk ./irc.py 150
- user.stream.write(command.encode())
+ user.stream.write(command.encode(self.conf['char_encoding'], errors='replace'))
hunk ./irgramd 72
+ tornado.options.define('char_encoding', default='utf-8', metavar='ENCODING', help='Character encoding for IRC')
patch a9bce1e704e2e0a7c711e4b984e87b2919384554
Author: E. Bosch <presidev@AT@gmail.com>
Date: Tue Mar 8 22:58:55 CET 2022
* service: Add code command, add ask_code option
hunk ./irc.py 111
+ self.service = service(self.conf, self.tg)
hunk ./irc.py 146
- self.service = service()
hunk ./irc.py 383
- reply = self.service.parse_command(message)
+ reply = await self.service.parse_command(message)
hunk ./irc.py 418
+ await self.check_telegram_auth(user)
hunk ./irc.py 507
+ async def check_telegram_auth(self, user):
+ 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),
+ ):
+ await self.send_msg(self.service_user, user.irc_nick, line)
+
hunk ./irgramd 71
+ tornado.options.define('ask_code', default=False, help='Ask authentication code (sent by Telegram) in console instead of "code" service command in IRC')
hunk ./service.py 10
- def __init__(self):
+ def __init__(self, settings, telegram):
hunk ./service.py 13
+ 'code': (self.handle_command_code, 1, 1),
hunk ./service.py 16
-
- def parse_command(self, line):
+ self.ask_code = settings['ask_code']
+ self.tg = telegram
+
+ async def parse_command(self, line):
hunk ./service.py 29
- reply = handler(*words)
+ reply = await handler(*words)
hunk ./service.py 35
- def handle_command_help(self, help_command=None, help=None):
+ async def handle_command_code(self, code=None, help=None):
+ if not help:
+ if self.ask_code:
+ reply = ('Code will be asked on console',)
+ elif code.isdigit():
+ try:
+ await self.tg.telegram_client.sign_in(code=code)
+ except:
+ reply = ('Invalid code',)
+ else:
+ reply = ('Valid code', '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',)
+ if help == HELP.desc: # rest of HELP.desc
+ reply += \
+ (
+ ' code <code>',
+ 'Enter authorization code sent by Telegram to the phone or to',
+ 'another client connected.',
+ 'This authorization code usually is only needed the first time',
+ 'that irgramd connects to Telegram with a given account.',
+ )
+ return reply
+
+ async def handle_command_help(self, help_command=None, help=None):
hunk ./service.py 79
- help_text += handler(help=HELP.brief)
+ help_text += await handler(help=HELP.brief)
hunk ./service.py 89
- help_text += handler(help=HELP.desc)
+ help_text += await handler(help=HELP.desc)
hunk ./telegram.py 45
+ self.ask_code = settings['ask_code']
hunk ./telegram.py 93
- self.logger.info('Telegram account not authorized, you must provide the Login code '
- 'that Telegram will sent you via SMS or another connected client')
+ self.logger.info('Telegram account not authorized')
hunk ./telegram.py 95
+ 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')
hunk ./telegram.py 105
+ await self.continue_auth()
+
+ async def continue_auth(self):
patch c659a2d6a4a69276aaa838c05e8fd6f3988cefdc
Author: E. Bosch <presidev@AT@gmail.com>
Date: Tue Mar 8 21:57:51 CET 2022
* irc: Add initial help message from service
hunk ./irc.py 186
- user.registered = True
- await self.send_greeting(user)
+ await self.register(user)
hunk ./irc.py 216
- user.registered = True
- await self.send_greeting(user)
+ await self.register(user)
hunk ./irc.py 414
+ async def register(self, user):
+ user.registered = True
+ await self.send_greeting(user)
+ await self.send_help(user)
hunk ./irc.py 498
+ 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',
+ ):
+ await self.send_msg(self.service_user, user.irc_nick, line)
+
patch 2d85c1f72cbe23f45c514c3100ebe57bee0d576b
Author: E. Bosch <presidev@AT@gmail.com>
Date: Tue Mar 8 21:28:02 CET 2022
* service: Include brief description for help command itself
hunk ./service.py 37
- if not help_command or help_command == 'help':
+ if help == HELP.brief:
+ help_text = (' help This help',)
+ elif not help_command or help_command == 'help':
hunk ./service.py 48
- if handler == self.handle_command_help: break
patch 093705f786069ded6ba780f3dc2b53c928b6abbe
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Mar 6 02:36:51 CET 2022
* irc: Add help functionality for service commands.
Use tuples for single or multiple lines (output) from commands.
hunk ./irc.py 386
- await self.send_msg(self.service_user, user.irc_nick, reply)
+ for reply_line in reply:
+ await self.send_msg(self.service_user, user.irc_nick, reply_line)
hunk ./service.py 12
- {
- # Command Handler Arguments Min Max
- 'help': (self.handle_command_help, 0, 0),
+ { # Command Handler Arguments Min Max
+ 'help': (self.handle_command_help, 0, 1),
hunk ./service.py 24
- reply = 'Wrong number of arguments'
+ reply = ('Wrong number of arguments',)
hunk ./service.py 28
- reply = 'Unknown command'
+ reply = ('Unknown command',)
hunk ./service.py 32
- def handle_command_help(self):
- return 'help'
+ def handle_command_help(self, help_command=None, help=None):
+
+ start_help = ('*** Telegram Service Help ***',)
+ end_help = ('*** End of Help ***',)
+
+ if not help_command or help_command == 'help':
+ help_text = start_help
+ help_text += \
+ (
+ 'This service contains specific Telegram commands that irgramd',
+ 'cannot map to IRC commands. The following commands are available:',
+ )
+ for command in self.commands.values():
+ handler = command[0]
+ if handler == self.handle_command_help: break
+ help_text += handler(help=HELP.brief)
+ help_text += \
+ (
+ 'If you need more information about a specific command you can use',
+ 'help <command>',
+ )
+ help_text += end_help
+ elif help_command in self.commands.keys():
+ handler = self.commands[help_command][0]
+ help_text = start_help
+ help_text += handler(help=HELP.desc)
+ help_text += end_help
+ else:
+ help_text = ('help: Unknown command',)
+ return help_text
+
+
+class HELP:
+ desc = 1
+ brief = 2
patch 42b71525bc8af2e7a875fa0e091e5d62220a5937
Author: E. Bosch <presidev@AT@gmail.com>
Date: Thu Mar 3 23:55:06 CET 2022
* irc: Fix and rename options for IRC address and port
hunk ./irgramd 29
- effective_port = settings['port']
+ effective_port = settings['irc_port']
hunk ./irgramd 47
- self.address = settings['address'] or '127.0.0.1'
+ self.address = settings['irc_address']
hunk ./irgramd 69
- tornado.options.define('address', default=None, metavar='ADDRESS', help='Address to listen on.')
hunk ./irgramd 73
+ tornado.options.define('irc_address', default='127.0.0.1', metavar='ADDRESS', help='Address to listen on for IRC')
hunk ./irgramd 76
+ tornado.options.define('irc_port', type=int, default=None, metavar='PORT', help='Port to listen on for IRC. (default 6667, default with TLS 6697)')
hunk ./irgramd 81
- tornado.options.define('port', default=None, metavar='PORT', help='Port to listen on for IRC. (default 6667, default with TLS 6697)')
patch ad20db76c932d5cec9aa18df25d91abeabcb7f51
Author: E. Bosch <presidev@AT@gmail.com>
Date: Thu Mar 3 23:07:36 CET 2022
* Fix translation of channel name from Telegram to IRC
hunk ./telegram.py 167
- return '#' + chat.title.replace(' ', '-')
+ return '#' + chat.title.replace(' ', '-').replace(',', '-')
patch 2f1d894e6e9861771f4330aadef00d20279d4525
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Mar 2 20:18:03 CET 2022
* irc: Add class for service/control command parsing and handlers, by now with stub help
command
hunk ./irc.py 24
+from service import service
hunk ./irc.py 145
+ self.service = service()
hunk ./irc.py 385
- # TODO: handle serivce command
+ reply = self.service.parse_command(message)
+ await self.send_msg(self.service_user, user.irc_nick, reply)
addfile ./service.py
hunk ./service.py 1
+# irgramd: IRC-Telegram gateway
+# service.py: IRC service/control command handlers
+#
+# Copyright (c) 2022 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.
+
+class service:
+ def __init__(self):
+ self.commands = \
+ {
+ # Command Handler Arguments Min Max
+ 'help': (self.handle_command_help, 0, 0),
+ }
+
+ def parse_command(self, line):
+
+ words = line.split()
+ command = words.pop(0).lower()
+ if command in self.commands.keys():
+ handler, min_args, max_args = self.commands[command]
+ num_words = len(words)
+ if num_words < min_args or num_words > max_args:
+ reply = 'Wrong number of arguments'
+ else:
+ reply = handler(*words)
+ else:
+ reply = 'Unknown command'
+
+ return reply
+
+ def handle_command_help(self):
+ return 'help'
patch 7312031fd0700656a913ba6662c0c84d7a08ebc9
Author: E. Bosch <presidev@AT@gmail.com>
Date: Tue Mar 1 20:02:36 CET 2022
* irc: Reorder handlers from most to least probability of
use (small optimization)
hunk ./irc.py 114
- self.irc_handlers = (
+ self.irc_handlers = \
+ (
hunk ./irc.py 117
+ (IRC_PRIVMSG_RX, self.handle_irc_privmsg, True, ALL_PARAMS),
+ (IRC_PING_RX, self.handle_irc_ping, True, ALL_PARAMS),
hunk ./irc.py 120
- (IRC_LIST_RX, self.handle_irc_list, True, 0),
hunk ./irc.py 121
- (IRC_MOTD_RX, self.handle_irc_motd, True, 0),
hunk ./irc.py 122
- (IRC_NICK_RX, self.handle_irc_nick, False, ALL_PARAMS),
- (IRC_PART_RX, self.handle_irc_part, True, 1),
- (IRC_PASS_RX, self.handle_irc_pass, False, ALL_PARAMS),
- (IRC_PING_RX, self.handle_irc_ping, True, ALL_PARAMS),
- (IRC_PRIVMSG_RX, self.handle_irc_privmsg, True, ALL_PARAMS),
- (IRC_QUIT_RX, self.handle_irc_quit, False, 0),
hunk ./irc.py 123
- (IRC_USER_RX, self.handle_irc_user, False, ALL_PARAMS),
hunk ./irc.py 124
- (IRC_VERSION_RX, self.handle_irc_version, True, 0),
+ (IRC_PART_RX, self.handle_irc_part, True, 1),
hunk ./irc.py 127
+ (IRC_LIST_RX, self.handle_irc_list, True, 0),
+ (IRC_NICK_RX, self.handle_irc_nick, False, ALL_PARAMS),
+ (IRC_MOTD_RX, self.handle_irc_motd, True, 0),
+ (IRC_USER_RX, self.handle_irc_user, False, ALL_PARAMS),
+ (IRC_QUIT_RX, self.handle_irc_quit, False, 0),
+ (IRC_VERSION_RX, self.handle_irc_version, True, 0),
+ (IRC_PASS_RX, self.handle_irc_pass, False, ALL_PARAMS),
patch f8db901e130865d95c9a0a50ed1e0abe85a5a67a
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Feb 27 01:05:16 CET 2022
* irc: Fix whois handler when user doesn't exist
hunk ./irc.py 341
- real_ni = self.users[ni].irc_nick
hunk ./irc.py 343
+ real_ni = usr.irc_nick
patch 6017f51ba8b0e51288cb9ed128f5caa6aa304088
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Feb 27 00:47:48 CET 2022
* irc: Add service/control user (TelegramServ), by now without functions
hunk ./irc.py 140
+ self.service_user = IRCUser(None, ('Services.{}'.format(self.hostname),), self.conf['service_user'],
+ 'Control', 'Telegram Service', is_service=True)
+ self.users[self.conf['service_user'].lower()] = self.service_user
hunk ./irc.py 357
- elif usr.tls or not usr.stream:
+ elif usr.tls or (not usr.stream and not usr.is_service):
hunk ./irc.py 361
+ if usr.is_service:
+ await self.reply_code(user, 'RPL_WHOISSERVICE', (real_ni,))
hunk ./irc.py 381
+ if self.service_user.irc_nick.lower() == tgl:
+ # TODO: handle serivce command
+ return
hunk ./irc.py 586
- def __init__(self, stream, address, irc_nick=None, username='', realname=None):
+ def __init__(self, stream, address, irc_nick=None, username='', realname=None, is_service=False):
hunk ./irc.py 599
+ self.is_service = is_service
hunk ./irc_replies.py 18
+ 'RPL_WHOISSERVICE': ('310', '{} :is an irgramd service'),
hunk ./irgramd 81
+ 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')
hunk ./telegram.py 204
+ if self.irc.users[irc_nick].is_service:
+ return None
hunk ./telegram.py 224
- if self.irc.users[irc_nick].stream:
+ user = self.irc.users[irc_nick]
+ if user.stream or user.is_service:
hunk ./telegram.py 228
- bot = self.irc.users[irc_nick].bot
+ bot = user.bot
hunk ./telegram.py 231
- user = await self.telegram_client.get_entity(tid)
- bot = user.bot
- self.irc.users[irc_nick].bot = bot
+ tg_user = await self.telegram_client.get_entity(tid)
+ bot = tg_user.bot
+ user.bot = bot
patch e1a17b912b5984786b1fe73f14faa785efe8920e
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Feb 23 20:32:39 CET 2022
* Update README
hunk ./README.md 4
-[Telegram] as a regular user (not bot)
+[Telegram] as a regular user (not bot).
hunk ./README.md 8
-using the [Telethon] library
-
-**[ This is a fork from [pbui/irtelegramd] to resume the development ]**
+using the [Telethon] library.
+
+**[irgramd primary repository] is in [darcs] version control system, [github
+repository] is synched as secondary**
+
+**irgramd is a fork from [pbui/irtelegramd] to resume the development**
+
+**irgramd is under active development in alpha state, several planned features
+are not implemented yet**
+
+## How it works
+
+Configure your IRC client to connect to irgramd (running on the same host or
+on a remote host) then you will see in your IRC client the Telegram groups
+as IRC channels and Telegram users as IRC users, if you send a message to a
+user or channel in IRC it will go to the corresponding user or group in
+Telegram, and the same from Telegram to IRC.
+
+The users on Telegram using the official or other clients will see you with
+your regular Telegram user account and will be indistinguishable for them
+whether you are using irgramd or another Telegram client.
+
+Several IRC clients can connect to irgramd but they will see the same
+Telegram account, this allows connecting to the same Telegram account from
+different IRC clients on different locations or devices, so one irgramd
+instance only connects to one Telegram account, if you want to connect to
+several Telegram accounts you will need to run several irgramd instances.
hunk ./README.md 48
+[irgramd primary repository]: https://src.presi.org/darcs/irgramd
+[darcs]: http://darcs.net
+[github repository]: https://github.com/prsai/irgramd
patch 219dba6a312f4a8d40668f28f81baa8a8a535bfe
Author: E. Bosch <presidev@AT@gmail.com>
Date: Tue Feb 22 02:37:33 CET 2022
* telegram: Support to connect to test environment
Add options to configure this
hunk ./irgramd 80
- tornado.options.define('port', default=None, metavar='PORT', help='Port to listen on. (default 6667, default with TLS 6697)')
+ tornado.options.define('port', default=None, metavar='PORT', help='Port to listen on for IRC. (default 6667, default with TLS 6697)')
+ 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)')
+ tornado.options.define('test_port', default=443, metavar='PORT', help='Port to connect to Telegram test environment')
hunk ./telegram.py 24
+# Test IP table
+
+TEST_IPS = { 1: '149.154.175.10',
+ 2: '149.154.167.40',
+ 3: '149.154.175.117'
+ }
+
hunk ./telegram.py 41
+ self.test = settings['test']
+ self.test_dc = settings['test_datacenter']
+ self.test_ip = settings['test_host'] if settings['test_host'] else TEST_IPS[self.test_dc]
+ self.test_port = settings['test_port']
hunk ./telegram.py 66
- telegram_session = os.path.join(self.telegram_session_dir, 'telegram')
- self.telegram_client = telethon.TelegramClient(telegram_session, self.api_id, self.api_hash)
+ if self.test:
+ self.telegram_client = telethon.TelegramClient(None, self.api_id, self.api_hash)
+ self.telegram_client.session.set_dc(self.test_dc, self.test_ip, self.test_port)
+ else:
+ telegram_session = os.path.join(self.telegram_session_dir, 'telegram')
+ self.telegram_client = telethon.TelegramClient(telegram_session, self.api_id, self.api_hash)
hunk ./telegram.py 86
- await self.telegram_client.connect()
+ if self.test:
+ await self.telegram_client.start(self.phone, code_callback=lambda: str(self.test_dc) * 5)
+ else:
+ await self.telegram_client.connect()
patch a5b9bc679af09407eae949907dde1f04548d899b
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Feb 20 02:49:33 CET 2022
* Fix copyright lines in README
hunk ./README.md 14
-Copyright (c) 2019 Peter Bui <pbui@bx612.space>
+Copyright (c) 2019 Peter Bui <pbui@bx612.space> [_$_]
patch dcead852a97cd7cc87af9a757725173dcdf112ee
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Feb 20 02:25:27 CET 2022
* Add copyright headers/notices
hunk ./LICENSE 4
+Copyright (c) 2020-2022 E. Bosch <presidev@AT@gmail.com>
hunk ./README.md 12
+## License
+
+Copyright (c) 2019 Peter Bui <pbui@bx612.space>
+Copyright (c) 2020-2022 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.
+
hunk ./include.py 1
+# irgramd: IRC-Telegram gateway
+# include.py: Constants and other definitions to be included in other files
+#
+# Copyright (c) 2020-2022 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.
hunk ./irc.py 1
+# irgramd: IRC-Telegram gateway
+# irc.py: IRC server side implementation
+#
+# Copyright (c) 2019 Peter Bui <pbui@bx612.space>
+# Copyright (c) 2020-2022 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.
hunk ./irc_replies.py 1
+# irgramd: IRC-Telegram gateway
+# irc_replies.py: IRC reply and error codes
+#
+# Copyright (c) 2020-2022 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.
hunk ./irgramd 2
+#
+# irgramd: IRC-Telegram gateway - Main file
+#
+# Copyright (c) 2019 Peter Bui <pbui@bx612.space>
+# Copyright (c) 2020-2022 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.
hunk ./telegram.py 1
+# irgramd: IRC-Telegram gateway
+# telegram.py: Interface to Telethon Telegram library
+#
+# Copyright (c) 2019 Peter Bui <pbui@bx612.space>
+# Copyright (c) 2020-2022 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.
hunk ./utils.py 1
+# irgramd: IRC-Telegram gateway
+# utils.py: Helper functions
+#
+# Copyright (c) 2019 Peter Bui <pbui@bx612.space>
+# Copyright (c) 2020-2022 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.
patch fb97160021d62fa832132bda177b4d15df393334
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Feb 20 00:01:27 CET 2022
* telegram: In initial mapping: force the addition of self user just in case
is not in dialogs (never sent self messages [saved messages])
hunk ./telegram.py 84
+ self.set_ircuser_from_telegram(tg_user)
patch 3a9740db19a230cfa53758a4d89dbe82ba024ff5
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Feb 19 02:10:00 CET 2022
* telegram: Improve web media handling
hunk ./telegram.py 14
-from utils import sanitize_filename, remove_slash, remove_http_s, get_human_size, get_human_duration
+from utils import sanitize_filename, is_url_equiv, extract_url, get_human_size, get_human_duration
hunk ./telegram.py 413
- if webpage.url != webpage.display_url \
- and remove_slash(webpage.url) != webpage.display_url \
- and remove_http_s(webpage.url) != webpage.display_url:
- media_url_or_data = '{} | {}'.format(webpage.url, webpage.display_url)
+ if is_url_equiv(webpage.url, webpage.display_url):
+ url_data = webpage.url
hunk ./telegram.py 416
- media_url_or_data = webpage.url
+ url_data = '{} | {}'.format(webpage.url, webpage.display_url)
hunk ./telegram.py 424
+ # extract the URL in the message, don't repeat it
+ message_url = extract_url(message.message)
+ if is_url_equiv(message_url, webpage.url):
+ if is_url_equiv(message_url, webpage.display_url):
+ media_url_or_data = message.message
+ else:
+ media_url_or_data = '{} | {}'.format(message.message, webpage.display_url)
+ else:
+ media_url_or_data = '{} | {}'.format(message.message, url_data)
hunk ./telegram.py 435
+ media_url_or_data = url_data
hunk ./utils.py 9
+SIMPLE_URL = re.compile('http(|s)://[^ ]+')
hunk ./utils.py 66
+def is_url_equiv(url1, url2):
+ if url1 and url2:
+ return url1 == url2 or remove_slash(remove_http_s(url1)) == remove_slash(remove_http_s(url2))
+ else:
+ return False
+
+def extract_url(text):
+ url = SIMPLE_URL.search(text)
+ return url.group() if url else None
+
patch 2637f19f7173e59977cd377994b39816084d3b84
Author: E. Bosch <presidev@AT@gmail.com>
Date: Thu Feb 17 00:34:56 CET 2022
* irc: Define IRC connection log as info level, add disconnection log as info
as well
hunk ./irc.py 61
- self.logger.debug('Running client connection from %s:%s', address[0], address[1])
+ self.logger.info('Running IRC client connection from %s:%s', address[0], address[1])
hunk ./irc.py 70
+ self.logger.info('Closing IRC client connection from %s:%s', address[0], address[1])
patch 8d22777b1339caf300a5b2e9b76f7d791ff0ec76
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Feb 16 23:22:04 CET 2022
* telegram: Add login code support entered by console during authorization
check, add phone number option in config, required by login code
hunk ./irgramd 71
+ tornado.options.define('phone', default=None, metavar='PHONE_NUMBER', help='Phone number associated with the Telegram account to receive the authorization codes if necessary')
hunk ./telegram.py 6
+import aioconsole
hunk ./telegram.py 25
+ self.phone = settings['phone']
hunk ./telegram.py 65
- if await self.telegram_client.is_user_authorized():
- self.authorized = True
- await self.init_mapping()
+ while not await self.telegram_client.is_user_authorized():
+ self.logger.info('Telegram account not authorized, you must provide the Login code '
+ 'that Telegram will sent you via SMS or another connected client')
+ await self.telegram_client.send_code_request(self.phone)
+ code = await aioconsole.ainput('Login code: ')
+ try:
+ await self.telegram_client.sign_in(code=code)
+ except:
+ pass
+
+ self.logger.info('Telegram account authorized')
+ self.authorized = True
+ await self.init_mapping()
patch cd15d391cfdd3f1db483a5235ac7233eb912e03e
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Feb 16 02:18:10 CET 2022
* telegram: Add options for API_ID and API_HASH
hunk ./irgramd 62
+ 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)')
hunk ./telegram.py 14
-
-# Configuration
-
-# GET API_ID and API_HASH from https://my.telegram.org/apps
-# AND PUT HERE BEFORE RUNNING irgramd
-
-TELEGRAM_API_ID =
-TELEGRAM_API_HASH = ''
-
hunk ./telegram.py 22
+ self.api_id = settings['api_id']
+ self.api_hash = settings['api_hash']
hunk ./telegram.py 46
- self.telegram_client = telethon.TelegramClient(telegram_session,
- TELEGRAM_API_ID, TELEGRAM_API_HASH
- )
+ self.telegram_client = telethon.TelegramClient(telegram_session, self.api_id, self.api_hash)
patch cbc19fde23bb1d3fff242dae13a40ab86514c3de
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Feb 16 01:58:19 CET 2022
* Order config options
hunk ./irgramd 61
- tornado.options.define('config', default='irgramdrc', metavar='CONFIGFILE', help='Config file absolute or relative to `config_dir` (command line options override it)')
hunk ./irgramd 62
- tornado.options.define('port', default=None, metavar='PORT', help='Port to listen on. (default 6667, default with TLS 6697)')
+ tornado.options.define('config', default='irgramdrc', metavar='CONFIGFILE', help='Config file absolute or relative to `config_dir` (command line options override it)')
hunk ./irgramd 64
- 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('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')
hunk ./irgramd 69
- 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('port', default=None, metavar='PORT', help='Port to listen on. (default 6667, default with TLS 6697)')
+ 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')
patch 7dacfafb25c3114f77d91fb0a790658a144a6664
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Feb 13 02:52:08 CET 2022
* Remove unused tornado.httpclient import and ioloop initialization
hunk ./irc.py 9
-import tornado.httpclient
hunk ./irc.py 50
- self.ioloop = tornado.ioloop.IOLoop.current()
patch 6a0451271e91ed7c081a631b49780c0f3ed761e7
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Feb 12 03:02:43 CET 2022
* irc: Add special case localhost for hostname
use FQDN if possible instead of simple hostname
hunk ./irc.py 52
- self.hostname = socket.gethostname()
+ self.hostname = socket.getfqdn()
hunk ./irc.py 289
- if not target or target == self.hostname:
+ if not target or target == self.gethostname(user):
hunk ./irc.py 304
- await self.reply_command(user, SRV, 'PONG', (self.hostname, payload))
+ await self.reply_command(user, SRV, 'PONG', (self.gethostname(user), payload))
hunk ./irc.py 323
- usr.address, self.hostname, usr.irc_nick, op, usr.irc_realname
+ usr.address, self.gethostname(user), usr.irc_nick, op, usr.irc_realname
hunk ./irc.py 335
- await self.reply_code(user, 'RPL_WHOISSERVER', (real_ni, self.hostname))
+ await self.reply_code(user, 'RPL_WHOISSERVER', (real_ni, self.gethostname(user)))
hunk ./irc.py 349
- server = self.hostname if usr.stream else 'Telegram'
+ server = self.gethostname(user) if usr.stream else 'Telegram'
hunk ./irc.py 359
- if not tgt or tgt == self.hostname or tgt in self.users.keys():
- await self.reply_code(user, 'RPL_VERSION', (VERSION, self.hostname))
+ if not tgt or tgt == self.gethostname(user) or tgt in self.users.keys():
+ await self.reply_code(user, 'RPL_VERSION', (VERSION, self.gethostname(user)))
hunk ./irc.py 432
- prefix = self.hostname if prfx == SRV else prfx.get_irc_mask()
+ prefix = self.gethostname(user) if prfx == SRV else prfx.get_irc_mask()
hunk ./irc.py 445
- stri = ':{} {} {} {}'.format(self.hostname, num, nick, rest)
+ stri = ':{} {} {} {}'.format(self.gethostname(user), num, nick, rest)
hunk ./irc.py 447
- stri = ':{} {} {} :{}'.format(self.hostname, num, user.irc_nick, tail)
+ stri = ':{} {} {} :{}'.format(self.gethostname(user), num, user.irc_nick, tail)
hunk ./irc.py 452
- await self.reply_code(user, 'RPL_YOURHOST', (self.hostname, VERSION))
+ await self.reply_code(user, 'RPL_YOURHOST', (self.gethostname(user), VERSION))
hunk ./irc.py 454
- await self.reply_code(user, 'RPL_MYINFO', (self.hostname, VERSION))
+ await self.reply_code(user, 'RPL_MYINFO', (self.gethostname(user), VERSION))
hunk ./irc.py 460
- await self.reply_code(user, 'RPL_MOTDSTART', (self.hostname,))
+ await self.reply_code(user, 'RPL_MOTDSTART', (self.gethostname(user),))
hunk ./irc.py 567
+ def gethostname(self, user):
+ return 'localhost' if user.from_localhost else self.hostname
+
hunk ./irc.py 574
+ self.from_localhost = True if address[0].split('.')[0] == '127' else False
patch 6acc2599b3961c177cac87db79969190e17ec6ee
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Feb 12 01:29:36 CET 2022
* irc: Improve MODE command hanlder
Add support for empty banlist response
hunk ./irc.py 261
+ is_user = False
+ is_channel = False
+
+ tgt = target.lower()
+ if tgt in self.users.keys():
+ if tgt == user.irc_nick:
+ is_user = True
+ else:
+ await self.reply_code(user, 'ERR_USERSDONTMATCH')
+ elif tgt[0] == '#':
+ if tgt in self.irc_channels.keys():
+ is_channel = True
+ else:
+ await self.reply_code(user, 'ERR_NOSUCHCHANNEL', (target,))
+ else:
+ await self.reply_code(user, 'ERR_NOSUCHNICK', (target,))
+
hunk ./irc.py 279
- tgt = target.lower()
- if tgt in self.users.keys():
- if tgt == user.irc_nick:
- await self.mode_user(user, user, False)
- else:
- await self.reply_code(user, 'ERR_USERSDONTMATCH')
- elif tgt[0] == '#':
- if tgt in self.irc_channels.keys():
- await self.mode_channel(user, target, False)
- else:
- await self.reply_code(user, 'ERR_NOSUCHCHANNEL', (target,))
- else:
- await self.reply_code(user, 'ERR_NOSUCHNICK', (target,))
+ if is_user:
+ await self.mode_user(user, user, False)
+ if is_channel:
+ await self.mode_channel(user, target, False)
+ elif mode == 'b' and is_channel:
+ await self.reply_code(user, 'RPL_ENDOFBANLIST', (target,))
hunk ./irc_replies.py 31
+ 'RPL_ENDOFBANLIST': ('368', '{} :End of channel ban list'),
patch 3c6a7e6e9f4c7cae2781b33d0a23e1ea92c0e060
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Feb 9 23:52:06 CET 2022
* telegram: Improve metadata for documents: files and videos
hunk ./telegram.py 13
-from utils import sanitize_filename, remove_slash, remove_http_s
+from utils import sanitize_filename, remove_slash, remove_http_s, get_human_size, get_human_duration
hunk ./telegram.py 344
- elif message.video: media_type = 'video'
+ elif message.video:
+ size = get_human_size(message.media.document.size)
+ attrib = next(x for x in message.media.document.attributes if isinstance(x, tgty.DocumentAttributeVideo))
+ dur = get_human_duration(attrib.duration)
+ media_type = 'video:{},{}'.format(size, dur)
hunk ./telegram.py 352
- elif message.document: media_type = 'file'
-
+ elif message.document:
+ size = get_human_size(message.media.document.size)
+ media_type = 'file:{}'.format(size)
hunk ./utils.py 64
+
+def get_human_size(size):
+ human_units = ('', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
+
+ def get_human_size_values(size, unit_pos=0):
+ aux = size / 1024.0
+ if aux > 1: return get_human_size_values(aux, unit_pos + 1)
+ else: return size, human_units[unit_pos]
+
+ if size <= 1237940039285380274899124224: # 1024Y
+ num, unit = get_human_size_values(size)
+ else:
+ num = size / 1208925819614629174706176 # 1Y
+ unit = 'Y'
+
+ fs = '{:.1f}{}' if num < 10 else '{:.0f}{}'
+
+ return fs.format(num, unit)
+
+def get_human_duration(duration):
+ res = ''
+ x, s = divmod(duration, 60)
+ h, m = divmod(x, 60)
+
+ if h > 0: res = str(h) + 'h'
+ if m > 0: res += str(m) + 'm'
+ if s > 0: res += str(s) + 's'
+ return res
patch c0d8386222d332e03fabbcb60c93cd19bb77a14e
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Feb 9 22:58:31 CET 2022
* telegram: Fix: move FILENAME_INVALID_CHARS to the file that uses it ;)
hunk ./telegram.py 14
-
-# Constants
-
-FILENAME_INVALID_CHARS = re.compile('[/{}<>()"\'\\|&]')
hunk ./utils.py 6
+# Constants
+
+FILENAME_INVALID_CHARS = re.compile('[/{}<>()"\'\\|&]')
+
patch 0015ec4a3cf416b0e597b5f97681b4f443977e37
Author: E. Bosch <presidev@AT@gmail.com>
Date: Tue Feb 8 02:04:55 CET 2022
* telegram: Improve webpage (media) handler
Add suport for webpage update after webpending
hunk ./telegram.py 13
-from utils import sanitize_filename
+from utils import sanitize_filename, remove_slash, remove_http_s
hunk ./telegram.py 42
+ self.webpending = {}
hunk ./telegram.py 67
+ (self.handle_raw, telethon.events.Raw),
hunk ./telegram.py 246
- async def handle_telegram_message(self, event):
+ async def handle_raw(self, update):
+ if isinstance(update, tgty.UpdateWebPage) and isinstance(update.webpage, tgty.WebPage):
+ event = self.webpending.pop(update.webpage.id, None)
+ if event:
+ await self.handle_telegram_message(event, update.webpage)
+
+ async def handle_telegram_message(self, event, upd_to_webpend=None):
hunk ./telegram.py 261
- if event.message.media:
- text = await self.handle_telegram_media(event.message)
+ if upd_to_webpend:
+ text = await self.handle_webpage(upd_to_webpend, event.message)
+ elif event.message.media:
+ text = await self.handle_telegram_media(event)
hunk ./telegram.py 320
- async def handle_telegram_media(self, message):
+ async def handle_telegram_media(self, event):
+ message = event.message
hunk ./telegram.py 326
- if message.web_preview:
- media_type = 'web'
- logo = await self.download_telegram_media(message)
+ if isinstance(message.media, tgty.MessageMediaWebPage):
hunk ./telegram.py 328
- media_url_or_data = message.message
- if message.media.webpage.title and logo:
- caption = ' | {} | {}'.format(message.media.webpage.title, logo)
- elif message.media.webpage.title:
- caption = ' | {}'.format(message.media.webpage.title)
- elif logo:
- caption = ' | {}'.format(logo)
+ if isinstance(message.media.webpage, tgty.WebPage):
+ # web
+ return await self.handle_webpage(message.media.webpage, message)
+ elif isinstance(message.media.webpage, tgty.WebPagePending):
+ media_type = 'webpending'
+ media_url_or_data = message.message
+ caption = ''
+ self.webpending[message.media.webpage.id] = event
hunk ./telegram.py 337
+ media_type = 'webunknown'
+ media_url_or_data = message.message
hunk ./telegram.py 340
-
hunk ./telegram.py 395
+ else:
+ media_type = 'unknown'
+ caption = ''
+ to_download = False
+ media_url_or_data = message.message
hunk ./telegram.py 404
+ return self.format_media(media_type, media_url_or_data, caption)
+
+ async def handle_webpage(self, webpage, message):
+ media_type = 'web'
+ logo = await self.download_telegram_media(message)
+ if webpage.url != webpage.display_url \
+ and remove_slash(webpage.url) != webpage.display_url \
+ and remove_http_s(webpage.url) != webpage.display_url:
+ media_url_or_data = '{} | {}'.format(webpage.url, webpage.display_url)
+ else:
+ media_url_or_data = webpage.url
+ if message:
+ # sometimes the 1st line of message contains the title, don't repeat it
+ message_line = message.message.splitlines()[0]
+ if message_line != webpage.title:
+ title = webpage.title
+ else:
+ title = ''
+ else:
+ title = webpage.title
+
+ if title and logo:
+ caption = ' | {} | {}'.format(title, logo)
+ elif title:
+ caption = ' | {}'.format(title)
+ elif logo:
+ caption = ' | {}'.format(logo)
+ else:
+ caption = ''
+
+ return self.format_media(media_type, media_url_or_data, caption)
+
+ def format_media(self, media_type, media_url_or_data, caption):
hunk ./utils.py 48
+
+def remove_slash(url):
+ return url[:-1] if url[-1:] == '/' else url
+
+def remove_http_s(url):
+ if url[:8] == 'https://':
+ surl = url[8:]
+ elif url[:7] == 'http://':
+ surl = url[7:]
+ else:
+ surl = url
+ return remove_slash(surl)
patch 8729250774e56024723bc882c6da35402f602906
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Feb 6 17:38:26 CET 2022
* telegram: Simplify get entity type name routine
hunk ./telegram.py 17
-TL_TYPES_IDENT = re.compile(r"<class 'telethon.tl.types.([^']+)'>")
hunk ./telegram.py 242
- return TL_TYPES_IDENT.match(str(type(entity))).groups()[0]
+ return type(entity).__name__
patch 6bc0aa52a10f9031c5334801deaeba2b6ac0d2b0
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Feb 5 18:21:05 CET 2022
* telegram: optimize regular expression for sanitize filenames
hunk ./telegram.py 18
+FILENAME_INVALID_CHARS = re.compile('[/{}<>()"\'\\|&]')
hunk ./utils.py 47
- return re.sub('[/{}<>()"\'\\|&]', '', fn).strip('-').replace(' ','_')
+ return FILENAME_INVALID_CHARS.sub('', fn).strip('-').replace(' ','_')
patch eb8bab16f3d5ca071956c1b4eb0565befd57805a
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Feb 5 17:21:17 CET 2022
* telegram: Replace spaces with underscores in downloaded file names
hunk ./utils.py 47
- return re.sub('[/{}<>()"\'\\|&]', '', fn).strip('-')
+ return re.sub('[/{}<>()"\'\\|&]', '', fn).strip('-').replace(' ','_')
patch bee27c3c443c6fbca835bf662f253d2270c6d76a
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Feb 5 03:30:31 CET 2022
* Add Telegram display name to WHOIS for self user (me)
hunk ./irc.py 332
- '{}!{}@Telegram'.format(self.tg.tg_username, self.tg.id
+ '{}|{}!{}@Telegram'.format(self.tg.tg_username,
+ await self.tg.get_telegram_display_name_me(), self.tg.id
hunk ./irc_replies.py 23
- 'RPL_WHOISACCOUNT': ('330', '{} {} :Telegram name'),
+ 'RPL_WHOISACCOUNT': ('330', '{} {} :has Telegram account'),
hunk ./telegram.py 134
+ async def get_telegram_display_name_me(self):
+ tg_user = await self.telegram_client.get_me()
+ return self.get_telegram_display_name(tg_user)
+
patch 497bfd6efd884d00e2d958e9ad8ac0b4009ad780
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Feb 4 22:08:05 CET 2022
* telegram: Fix size info of photo media
hunk ./telegram.py 326
- size = [x for x in message.media.photo.sizes if x.type == 'x'][0]
- media_type = 'photo:{}x{}'.format(size.w, size.h)
+ size = message.media.photo.sizes[-1]
+ if hasattr(size, 'w') and hasattr(size, 'h'):
+ media_type = 'photo:{}x{}'.format(size.w, size.h)
+ else:
+ media_type = 'photo'
patch e06c0b7e00071bd2c3fc3b822d3997652358b53f
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Feb 4 21:32:40 CET 2022
* irc: In MOTD change the order of the repositories since darcs is the primary
hunk ./irc.py 458
+ await self.reply_code(user, 'RPL_MOTD', ('darcs repository: https://src.presi.org/darcs/irgramd',))
hunk ./irc.py 460
- await self.reply_code(user, 'RPL_MOTD', ('darcs repository: https://src.presi.org/darcs/irgramd',))
patch 26503ca5b65894059c3fff76cc5462c45f357428
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Feb 4 19:31:39 CET 2022
* irc: Fix when nick is not allowed (when the user is already registered) in
NICK command
hunk ./irc.py 176
- await self.reply_code(user, 'ERR_PASSWDMISMATCH')
+ if user.registered:
+ await self.reply_code(user, 'ERR_ERRONEUSNICKNAME', (nick,))
+ else:
+ await self.reply_code(user, 'ERR_PASSWDMISMATCH')
patch 1e24fbe344b94c6bd9a5d5cc75250227c80da5ee
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Feb 4 19:18:34 CET 2022
* irc: Fix when nick is the current in NICK command
hunk ./irc.py 153
+ current = user.irc_nick.lower() if user.irc_nick else None
+ if ni == current:
+ return
hunk ./irc.py 164
- current = user.irc_nick.lower()
patch 34113fbedae0bddb28721fc1d5a879ef9df9ca89
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Feb 4 18:21:49 CET 2022
* irc: Handle messages to inexistent users and to users in other IRC
connections (these messages won't be sent by design)
hunk ./irc.py 366
- if tgt not in self.iid_to_tid:
- print('TODO: handle error')
-
- telegram_id = self.iid_to_tid[tgt]
- await self.tg.telegram_client.send_message(telegram_id, message)
+ if tgt in self.iid_to_tid:
+ telegram_id = self.iid_to_tid[tgt]
+ await self.tg.telegram_client.send_message(telegram_id, message)
+ else:
+ await self.reply_code(user, 'ERR_NOSUCHNICK', (target,))
hunk ./irc_replies.py 34
- 'ERR_NOSUCHNICK': ('401', '{} :Nick not found'),
+ 'ERR_NOSUCHNICK': ('401', '{} :Nick not found or available'),
patch d8ede54b5d94a3c78869abbc53736362686d1a64
Author: E. Bosch <presidev@AT@gmail.com>
Date: Thu Feb 3 21:11:10 CET 2022
* irc: Refactorize PRIVMSG handle and routines to send messages to IRC
Echo channel messages from IRC to other IRC connections
Change 'nick' argument with 'target' in PRIVMSG handle
Fix: send channel messages only to IRC connections that are in that channel
hunk ./irc.py 37
-IRC_PRIVMSG_RX = re.compile(PREFIX + r'PRIVMSG( +|\n)(?P<nick>[^ ]+)( +:| +|\n)(?P<message>[^\n]+|)')
+IRC_PRIVMSG_RX = re.compile(PREFIX + r'PRIVMSG( +|\n)(?P<target>[^ ]+)( +:| +|\n)(?P<message>[^\n]+|)')
hunk ./irc.py 349
- async def handle_irc_privmsg(self, user, nick, message):
- self.logger.debug('Handling PRIVMSG: %s, %s', nick, message)
-
- if nick == user.irc_nick:
- target = self.tg.tg_username
+ async def handle_irc_privmsg(self, user, target, message):
+ self.logger.debug('Handling PRIVMSG: %s, %s', target, message)
+
+ tgl = target.lower()
+ # Echo channel messages from IRC to other IRC connections
+ # because they won't receive event from Telegram
+ if tgl in self.irc_channels.keys():
+ await self.send_msg_others(user, tgl, message)
+
+ if tgl == user.irc_nick:
+ tgt = self.tg.tg_username.lower()
hunk ./irc.py 364
- target = nick
- tgt = target.lower()
+ tgt = tgl
hunk ./irc.py 383
+ tgt = target.lower() if target else ''
+ is_chan = tgt in self.irc_channels.keys()
+ # source None (False): it's self Telegram user, see [1]
hunk ./irc.py 388
- for irc_user in (x for x in self.users.values() if x.stream):
- src_mask = source_mask if source_mask else irc_user.get_irc_mask()
- tgt = target if target else irc_user.irc_nick
- await self.send_irc_command(irc_user, ':{} PRIVMSG {} :{}'.format(src_mask, tgt, msg))
+ if is_chan:
+ irc_users = (u for u in self.users.values() if u.stream and u.irc_nick in self.irc_channels[tgt])
+ else:
+ irc_users = (u for u in self.users.values() if u.stream)
+
+ for irc_user in irc_users:
+ await self.send_privmsg(irc_user, source_mask, target, msg)
+
+ async def send_msg_others(self, source, target, message):
+ source_mask = source.get_irc_mask()
+ is_chan = target in self.irc_channels.keys()
+ if is_chan:
+ irc_users = (u for u in self.users.values() if u.stream and u.irc_nick != source.irc_nick
+ and u.irc_nick in self.irc_channels[target])
+ else:
+ irc_users = (u for u in self.users.values() if u.stream and u.irc_nick != source.irc_nick)
+
+ for irc_user in irc_users:
+ await self.send_privmsg(irc_user, source_mask, target, message)
+
+ async def send_privmsg(self, user, source_mask, target, msg):
+ # reference [1]
+ src_mask = source_mask if source_mask else user.get_irc_mask()
+ # target None (False): it's private, not a channel
+ tgt = target if target else user.irc_nick
+ await self.send_irc_command(user, ':{} PRIVMSG {} :{}'.format(src_mask, tgt, msg))
patch 2c875a55dfbf11c2895bdfb365b75ac8198dc74c
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Jan 30 22:06:02 CET 2022
* telegram: Remove implicit value for entity_cache in function declarations,
if entity_cache already existed was not assigning the default value so was
confusing. Add comments to explain that entity_cache should be a list.
hunk ./telegram.py 201
- async def get_channel_topic(self, channel, entity_cache=[None]):
+ async def get_channel_topic(self, channel, entity_cache):
hunk ./telegram.py 203
+ # entity_cache should be a list to be a persistent and by reference value
hunk ./telegram.py 212
- async def get_channel_creation(self, channel, entity_cache=[None]):
+ async def get_channel_creation(self, channel, entity_cache):
hunk ./telegram.py 217
+ # entity_cache should be a list to be a persistent and by reference value
patch 2de0ad1de382e090f44ded877cd0f66bcf257165
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Jan 30 21:50:58 CET 2022
* irc: In TOPIC and JOIN commands fix bad use of entity cache, was producing
wrong topic result
hunk ./irc.py 284
- await self.irc_channel_topic(user, real_chan)
+ await self.irc_channel_topic(user, real_chan, [None])
hunk ./irc.py 486
- async def irc_channel_topic(self, user, channel, entity_cache=[None]):
+ async def irc_channel_topic(self, user, channel, entity_cache):
patch 9a24eaf9120521752a95f20b03135a0fbba756c7
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Jan 30 21:28:27 CET 2022
* irc: In LIST command fix wrong topic info (bad use of entity cache)
hunk ./irc.py 231
- entity_cache = [None]
hunk ./irc.py 243
- topic = await self.tg.get_channel_topic(chan, entity_cache)
+ topic = await self.tg.get_channel_topic(chan, [None])
patch 40a72fbbea1b081086b4743201fffcb71e513e5f
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Jan 30 02:28:02 CET 2022
* telegram: Add size info to photo (media)
hunk ./telegram.py 323
- elif message.photo: media_type = 'photo'
+ elif message.photo:
+ size = [x for x in message.media.photo.sizes if x.type == 'x'][0]
+ media_type = 'photo:{}x{}'.format(size.w, size.h)
patch 3576c9bc94562bf539d31f44a3ac08d9ed4a7d05
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Jan 30 01:29:08 CET 2022
* telegram: Support media in messages for using a webserver (not included in
irgramd) to get the downloaded media from Telegram, also support
non-downloadable media (geo, contact, etc.)
hunk ./irgramd 68
+ 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')
hunk ./telegram.py 13
+from utils import sanitize_filename
hunk ./telegram.py 34
+ self.media_url = settings['media_url']
+ self.media_cn = 0
hunk ./telegram.py 246
- message = '[{}] {}'.format(mid, event.message.message)
+
+ if event.message.media:
+ text = await self.handle_telegram_media(event.message)
+ else:
+ text = event.message.message
+
+ message = '[{}] {}'.format(mid, text)
hunk ./telegram.py 269
-
- # Format messages with media
-# if event.message.media and (event.message.photo or event.message.gif):
-# message = await self.download_telegram_media(event.message, 'Image')
-# if message:
-# messages.insert(0, message)
-# elif event.message.media and (event.message.sticker):
-# messages.insert(0, 'Sticker: {}'.format(event.message.sticker.id))
-
hunk ./telegram.py 304
- async def download_telegram_media(self, message, tag):
+ async def handle_telegram_media(self, message):
+ caption = ' | {}'.format(message.message) if message.message else ''
+ to_download = True
+ media_url_or_data = ''
+
+ if message.web_preview:
+ media_type = 'web'
+ logo = await self.download_telegram_media(message)
+ to_download = False
+ media_url_or_data = message.message
+ if message.media.webpage.title and logo:
+ caption = ' | {} | {}'.format(message.media.webpage.title, logo)
+ elif message.media.webpage.title:
+ caption = ' | {}'.format(message.media.webpage.title)
+ elif logo:
+ caption = ' | {}'.format(logo)
+ else:
+ caption = ''
+
+ elif message.photo: media_type = 'photo'
+ elif message.audio: media_type = 'audio'
+ elif message.voice: media_type = 'rec'
+ elif message.video: media_type = 'video'
+ elif message.video_note: media_type = 'videorec'
+ elif message.gif: media_type = 'anim'
+ elif message.sticker: media_type = 'sticker'
+ elif message.document: media_type = 'file'
+
+ elif message.contact:
+ media_type = 'contact'
+ caption = ''
+ to_download = False
+ if message.media.contact.first_name:
+ media_url_or_data += message.media.contact.first_name + ' '
+ if message.media.contact.last_name:
+ media_url_or_data += message.media.contact.last_name + ' '
+ if message.media.contact.phone_number:
+ media_url_or_data += message.media.contact.phone_number
+
+ elif message.game:
+ media_type = 'game'
+ caption = ''
+ to_download = False
+ if message.media.game.title:
+ media_url_or_data = message.media.game.title
+
+ elif message.geo:
+ media_type = 'geo'
+ caption = ''
+ to_download = False
+ media_url_or_data = 'lat: {}, long: {}'.format(message.media.geo.lat, message.media.geo.long)
+
+ elif message.invoice:
+ media_type = 'invoice'
+ caption = ''
+ to_download = False
+ media_url_or_data = ''
+
+ elif message.poll:
+ media_type = 'poll'
+ caption = ''
+ to_download = False
+ media_url_or_data = ''
+
+ elif message.venue:
+ media_type = 'venue'
+ caption = ''
+ to_download = False
+ media_url_or_data = ''
+
+ if to_download:
+ media_url_or_data = await self.download_telegram_media(message)
+
+ return '[{}] {}{}'.format(media_type, media_url_or_data, caption)
+
+ async def download_telegram_media(self, message):
hunk ./telegram.py 381
- return
- if not local_path:
- return
-
- request = tornado.httpclient.HTTPRequest(
- url = 'https://yld.me/paste',
- method = 'POST',
- body = open(local_path, 'rb').read(),
- )
- response = await tornado.httpclient.AsyncHTTPClient().fetch(request)
-
- os.unlink(local_path)
- return tag + ': ' + response.body.decode().strip()
+ if not local_path: return ''
+
+ if message.document:
+ new_file = sanitize_filename(os.path.basename(local_path))
+ else:
+ filetype = os.path.splitext(local_path)[1]
+ new_file = str(self.media_cn) + filetype
+ self.media_cn += 1
+
+ new_path = os.path.join(self.telegram_media_dir, new_file)
+ if local_path != new_path:
+ os.replace(local_path, new_path)
+ if self.media_url[-1:] != '/':
+ self.media_url += '/'
+ return self.media_url + new_file
hunk ./utils.py 4
+import re
hunk ./utils.py 45
+
+def sanitize_filename(fn):
+ return re.sub('[/{}<>()"\'\\|&]', '', fn).strip('-')
patch 75f57f811bc8cabaaeeb6998f39fdbf9f34fe000
Author: E. Bosch <presidev@AT@gmail.com>
Date: Thu Jan 27 23:40:06 CET 2022
* irc: When the user sends a message for him/herself it will be stored in the
"saved messages" space in Telegram, but no event will be returned to
irgramd, so irgramd will echo the message to the user in IRC.
hunk ./irc.py 353
- target = self.tg.tg_username if nick == user.irc_nick else nick
+ if nick == user.irc_nick:
+ target = self.tg.tg_username
+ # Echo message to the user him/herself in IRC
+ # because no event will be received from Telegram
+ await self.send_msg(user, None, message)
+ else:
+ target = nick
patch b77c17d9bb8b484231cb1d28ca9c9fdc865d4612
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Jan 26 22:30:58 CET 2022
* telegram: Add compact ids to messages
hunk ./telegram.py 38
+ self.mid = mesg_id('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%+./_~')
hunk ./telegram.py 237
+
+ if self.mid.mesg_base is None:
+ self.mid.mesg_base = event.message.id
+
+ user = self.get_irc_user_from_telegram(event.sender_id)
+ mid = self.mid.num_to_id(event.message.id - self.mid.mesg_base)
+ message = '[{}] {}'.format(mid, event.message.message)
hunk ./telegram.py 246
- await self.handle_telegram_private_message(event)
+ await self.handle_telegram_private_message(user, message)
hunk ./telegram.py 248
- await self.handle_telegram_channel_message(event)
-
- async def handle_telegram_private_message(self, event):
- self.logger.debug('Handling Telegram Private Message: %s', event)
-
- user = self.get_irc_user_from_telegram(event.sender_id)
- message = event.message.message if event.message.message else ''
+ await self.handle_telegram_channel_message(event, user, message)
+
+ async def handle_telegram_private_message(self, user, message):
+ self.logger.debug('Handling Telegram Private Message: %s, %s', user, message)
hunk ./telegram.py 255
- async def handle_telegram_channel_message(self, event):
+ async def handle_telegram_channel_message(self, event, user, message):
hunk ./telegram.py 261
- user = self.get_irc_user_from_telegram(event.sender_id)
-
hunk ./telegram.py 262
- message = event.message.message if event.message.message else ''
hunk ./telegram.py 319
+
+class mesg_id:
+ def __init__(self, alpha):
+ self.alpha = alpha
+ self.base = len(alpha)
+ self.alphaval = { i:v for v, i in enumerate(alpha) }
+ self.mesg_base = None
+
+ def num_to_id(self, num, neg=''):
+ if num < 0: return self.num_to_id(-num, '-')
+ (high, low) = divmod(num, self.base)
+ if high >= self.base:
+ aux = self.num_to_id(high)
+ return neg + aux + self.alpha[low]
+ else:
+ return neg + self.alpha[high] + self.alpha[low]
+
+ def id_to_num(self, id, n=1):
+ if id:
+ if id[0] == '-': return self.id_to_num(id[1:], -1)
+ aux = self.alphaval[id[-1:]] * n
+ sum = self.id_to_num(id[:-1], n * self.base)
+ return sum + aux
+ else:
+ return 0
patch 5a82675ae695a750607f1347e3cb01e180005ffb
Author: E. Bosch <presidev@AT@gmail.com>
Date: Mon Jan 24 22:35:55 CET 2022
* Improve and cleanup send (to IRC) routines, add breaks and "continued line" marks
hunk ./irc.py 16
-from utils import chunks, set_replace
+from utils import chunks, set_replace, split_lines
hunk ./irc.py 371
+ async def send_msg(self, source, target, message):
+ messages = split_lines(message)
+ source_mask = source.get_irc_mask() if source else ''
+ for msg in messages:
+ for irc_user in (x for x in self.users.values() if x.stream):
+ src_mask = source_mask if source_mask else irc_user.get_irc_mask()
+ tgt = target if target else irc_user.irc_nick
+ await self.send_irc_command(irc_user, ':{} PRIVMSG {} :{}'.format(src_mask, tgt, msg))
+
hunk ./telegram.py 246
- for message in event.message.message.splitlines():
- for irc_user in [x for x in self.irc.users.values() if x.stream]:
- usr = user if user else irc_user
- await self.irc.send_irc_command(irc_user, ':{} PRIVMSG {} :{}'.format(
- usr.get_irc_mask(), irc_user.irc_nick, message
- ))
+ message = event.message.message if event.message.message else ''
+
+ await self.irc.send_msg(user, None, message)
hunk ./telegram.py 259
- messages = event.message.message.splitlines() if event.message.message else []
- if event.message.media and (event.message.photo or event.message.gif):
- message = await self.download_telegram_media(event.message, 'Image')
- if message:
- messages.insert(0, message)
- elif event.message.media and (event.message.sticker):
- messages.insert(0, 'Sticker: {}'.format(event.message.sticker.id))
-
- # Send all messages to IRC
- for message in messages:
- for irc_user in [x for x in self.irc.users.values() if x.stream]:
- usr = user if user else irc_user
- await self.irc.send_irc_command(irc_user, ':{} PRIVMSG {} :{}'.format(
- usr.get_irc_mask(), channel, message
- ))
+ message = event.message.message if event.message.message else ''
+# if event.message.media and (event.message.photo or event.message.gif):
+# message = await self.download_telegram_media(event.message, 'Image')
+# if message:
+# messages.insert(0, message)
+# elif event.message.media and (event.message.sticker):
+# messages.insert(0, 'Sticker: {}'.format(event.message.sticker.id))
+
+ await self.irc.send_msg(user, channel, message)
hunk ./utils.py 3
+import textwrap
hunk ./utils.py 16
+
+def get_continued(items, mark, length):
+ # Add "continued" mark to lines, except last one
+ 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)
+
+ # Split when Telegram original message has breaks
+ messages = message.splitlines()
+ lm = len(messages)
+ if lm > 1:
+ # Add "continued line" mark (\) for lines that belong to the same message
+ # (split previously)
+ messages = get_continued(messages, ' \\', lm)
+ for m in messages:
+ wrapped = wr.wrap(text=m)
+ lw = len(wrapped)
+ if lw > 1:
+ # Add double "continued line" mark (\\) for lines that belong to the same message
+ # and have been wrapped to not exceed IRC limits
+ messages_limited += get_continued(wrapped, ' \\\\', lw)
+ else:
+ messages_limited += wrapped
+ del wr
+ return messages_limited
patch 7adb8b6b7deac877dd19a610e031c5577ec39349
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Jan 22 22:51:40 CET 2022
* telegram: Remove unused code for autojoin when message received
hunk ./telegram.py 256
- # Join IRC channel if not already in it
hunk ./telegram.py 258
-# if channel not in self.irc.irc_channels:
-# await self.irc.join_irc_channel(self.irc.irc_nick, channel, True)
hunk ./telegram.py 261
-# if nick not in self.irc.irc_channels[channel]:
-# await self.irc.join_irc_channel(nick, channel, False)
-
patch eabdad37e130b538e49cd13fb3efd211db457e18
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Jan 22 22:08:56 CET 2022
* irc: Fix authentication issues
hunk ./irc.py 158
- or user.local_auth(nick, self.conf['irc_nicks'], user.recv_pass, self.conf['irc_password']):
+ or user.local_auth(nick, self.conf['irc_nicks'], user.recv_pass, self.conf['irc_password'], self.conf['pam']):
hunk ./irc.py 563
- else:
- check_group = True
hunk ./irc.py 582
- def local_auth(self, nick, nicks, recv_pass, irc_pass):
- return ( nick in nicks
+ def local_auth(self, nick, nicks, recv_pass, irc_pass, pam):
+ return ( not pam
+ and nick in nicks
patch 014152c41803dc37a9c8e8151cad5ea37b3f4281
Author: E. Bosch <presidev@AT@gmail.com>
Date: Mon Dec 13 21:31:17 CET 2021
* irc: Add authentication (including users allowed) via local configuration and PAM
hunk ./irc.py 53
+ self.conf = settings
hunk ./irc.py 157
- elif user.password == user.recv_pass:
+ elif user.pam_auth(nick, self.conf['pam'], self.conf['pam_group'], user.recv_pass) \
+ or user.local_auth(nick, self.conf['irc_nicks'], user.recv_pass, self.conf['irc_password']):
hunk ./irc.py 545
+
+ def pam_auth(self, nick, pam, pam_group, recv_pass):
+ if not pam: return False
+
+ # Check if user is in groups (main or others)
+ if pam_group:
+ import pwd
+ import grp
+ try:
+ user_group_id = pwd.getpwnam(nick).pw_gid
+ group_data = grp.getgrnam(pam_group)
+ pam_group_id = group_data.gr_gid
+ group_members = group_data.gr_mem
+ check_group = user_group_id == pam_group_id \
+ or nick in group_members
+ except:
+ check_group = False
+ if not check_group: return False
+ else:
+ check_group = True
+
+ # Check user authentication (via PAM)
+ import PAM
+ def pam_conv(auth, query_list, userData):
+ resp = []
+ resp.append((recv_pass, 0))
+ return resp
+ p = PAM.pam()
+ p.start('passwd')
+ p.set_item(PAM.PAM_USER, nick)
+ p.set_item(PAM.PAM_CONV, pam_conv)
+ try:
+ p.authenticate()
+ p.acct_mgmt()
+ except:
+ return False
+ else:
+ return True
+
+ def local_auth(self, nick, nicks, recv_pass, irc_pass):
+ return ( nick in nicks
+ and recv_pass == irc_pass
+ )
hunk ./irgramd 68
+ 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')
+ 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')
patch b07534112c075b3b057cf9d514264cd5283cae33
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Dec 12 02:15:52 CET 2021
* Propagate 'settings' to IRCHandler() and TelegramHandler()
hunk ./irc.py 49
- def __init__(self, config_dir):
+ def __init__(self, settings):
hunk ./irc.py 53
- self.config_dir = config_dir
hunk ./irgramd 19
- def __init__(self, logger, config_direc, settings):
+ def __init__(self, logger, settings):
hunk ./irgramd 41
- self.config_dir = config_direc
hunk ./irgramd 48
- async def run(self):
+ async def run(self, settings):
hunk ./irgramd 51
- self.irc_handler = IRCHandler(self.config_dir)
- self.tg_handler = TelegramHandler(self.irc_handler, self.config_dir)
+ self.irc_handler = IRCHandler(settings)
+ self.tg_handler = TelegramHandler(self.irc_handler, settings)
hunk ./irgramd 87
-
- irc_server = IRCTelegramd(logger, config_dir, options)
- asyncio.get_event_loop().run_until_complete(irc_server.run())
+ options['config_dir'] = config_dir
+
+ irc_server = IRCTelegramd(logger, options)
+ asyncio.get_event_loop().run_until_complete(irc_server.run(options))
hunk ./telegram.py 30
- def __init__(self, irc, config_dir):
+ def __init__(self, irc, settings):
hunk ./telegram.py 32
- self.config_dir = config_dir
+ self.config_dir = settings['config_dir']
patch 7c308e8fae032971c2bf0598b0c9cb12102c2960
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Dec 12 01:06:55 CET 2021
* Remove keyword arguments for IRCTelegramd(), use 'settings' dictionary instead
hunk ./irgramd 19
- def __init__(self, logger, config_direc, address=None, port=None, **settings):
+ def __init__(self, logger, config_direc, settings):
hunk ./irgramd 21
- effective_port = port
+ effective_port = settings['port']
hunk ./irgramd 39
- self.address = address or '127.0.0.1'
+ self.address = settings['address'] or '127.0.0.1'
hunk ./irgramd 89
- irc_server = IRCTelegramd(logger, config_dir, **options)
+ irc_server = IRCTelegramd(logger, config_dir, options)
patch 41f19aaddb6d10537b87620bf88715276dc7f234
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Dec 11 22:32:43 CET 2021
* Add configuration file support
hunk ./irgramd 19
- def __init__(self, address=None, port=None, config_dir=None, **settings):
- self.logger = logging.getLogger()
+ def __init__(self, logger, config_direc, address=None, port=None, **settings):
+ self.logger = logger
hunk ./irgramd 41
- self.config_dir = config_dir or os.path.expanduser('~/.config/irgramd')
+ self.config_dir = config_direc
hunk ./irgramd 45
- if not os.path.exists(self.config_dir):
- os.makedirs(self.config_dir)
hunk ./irgramd 52
- self.logger.info('Configuration Directory: %s', self.config_dir)
-
hunk ./irgramd 61
+ logger = logging.getLogger()
+ tornado.options.define('config', default='irgramdrc', metavar='CONFIGFILE', help='Config file absolute or relative to `config_dir` (command line options override it)')
hunk ./irgramd 65
- tornado.options.define('config_dir', default=None, metavar='PATH', help='Configuration directory')
+ tornado.options.define('config_dir', default='~/.config/irgramd', metavar='PATH', help='Configuration directory where telegram session info is saved')
hunk ./irgramd 69
+ # parse cmd line first time to get --config and --config_dir
hunk ./irgramd 71
+ config_file = os.path.expanduser(tornado.options.options.config)
+ config_dir = os.path.expanduser(tornado.options.options.config_dir)
+ if not os.path.exists(config_dir):
+ os.makedirs(config_dir)
+ logger.info('Configuration Directory: %s', config_dir)
+
+ if not os.path.isabs(config_file):
+ config_file = os.path.join(config_dir, config_file)
+ if os.path.isfile(config_file):
+ logger.info('Using configuration file: %s', config_file)
+ tornado.options.parse_config_file(config_file)
+ else:
+ logger.warning('Configuration file not present, using only command line options and defaults')
+ # parse cmd line second time to override file options
+ tornado.options.parse_command_line()
hunk ./irgramd 88
- irc_server = IRCTelegramd(**options)
+
+ irc_server = IRCTelegramd(logger, config_dir, **options)
patch b5d9701390240c147a7a994db9fc806da6fc9d54
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Dec 10 01:06:11 CET 2021
* irc: add relative user path support (e.g. ~/ on Unix) for TLS/SSL certificates and key
hunk ./irgramd 28
- tls_context.load_cert_chain(settings['tls_cert'], settings['tls_key'])
+ tls_context.load_cert_chain(os.path.expanduser(settings['tls_cert']), os.path.expanduser(settings['tls_key']))
patch 8c55b3a0ef5dec2bb3fb16c8947a03b43554c09d
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Dec 8 21:20:09 CET 2021
* Add metavar to options
hunk ./irgramd 65
- tornado.options.define('address', default=None, help='Address to listen on.')
- tornado.options.define('port', default=None, help='Port to listen on. (default 6667, default with TLS 6697)')
- tornado.options.define('config_dir', default=None, help='Configuration directory')
+ tornado.options.define('address', default=None, metavar='ADDRESS', help='Address to listen on.')
+ tornado.options.define('port', default=None, metavar='PORT', help='Port to listen on. (default 6667, default with TLS 6697)')
+ tornado.options.define('config_dir', default=None, metavar='PATH', help='Configuration directory')
hunk ./irgramd 69
- tornado.options.define('tls_cert', default=None, 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, help='IRC server private key for TLS/SSL')
+ 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')
patch b720e14a8bf28c93658226f9f94140364906bf23
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Dec 8 01:39:32 CET 2021
* irc: Add TLS/SSL support
hunk ./irgramd 9
+import ssl
hunk ./irgramd 19
- def __init__(self, address=None, port=6667, config_dir=None, **settings):
- tornado.tcpserver.TCPServer.__init__(self)
-
+ def __init__(self, address=None, port=None, config_dir=None, **settings):
hunk ./irgramd 21
+ effective_port = port
+
+ if settings['tls']:
+ if not settings['tls_cert']: # error
+ self.logger.error('TLS configured but certificate not present')
+ exit(1)
+ tls_context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH)
+ tls_context.load_cert_chain(settings['tls_cert'], settings['tls_key'])
+ if not effective_port:
+ effective_port = 6697
+ self.logger.info('TLS configured')
+ else:
+ tls_context = None
+ if not effective_port:
+ effective_port = 6667
+
+ tornado.tcpserver.TCPServer.__init__(self, ssl_options=tls_context)
+
hunk ./irgramd 40
- self.port = port
+ self.port = effective_port
hunk ./irgramd 66
- tornado.options.define('port', default=6667, help='Port to listen on.')
+ tornado.options.define('port', default=None, help='Port to listen on. (default 6667, default with TLS 6697)')
hunk ./irgramd 68
+ tornado.options.define('tls', default=False, help='Use TLS/SSL encrypted connection for IRC server')
+ tornado.options.define('tls_cert', default=None, 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, help='IRC server private key for TLS/SSL')
patch d4fcf2fabedea7d242e06b126876f63646faf1e7
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Nov 24 00:27:12 CET 2021
* irc: Add LIST command
hunk ./irc.py 29
+IRC_LIST_RX = re.compile(PREFIX + r'LIST( +:| +|\n)(?P<channels>[^\n ]+|)')
hunk ./irc.py 110
+ (IRC_LIST_RX, self.handle_irc_list, True, 0),
hunk ./irc.py 228
+ async def handle_irc_list(self, user, channels):
+ self.logger.debug('Handling LIST: %s', channels)
+ entity_cache = [None]
+
+ if channels:
+ chans = channels.split(',')
+ else:
+ chans = self.irc_channels.keys()
+
+ await self.reply_code(user, 'RPL_LISTSTART')
+ for channel in chans:
+ chan = channel.lower()
+ if chan in self.irc_channels.keys():
+ real_chan = self.get_realcaps_name(chan)
+ users_count = len(self.irc_channels[chan])
+ topic = await self.tg.get_channel_topic(chan, entity_cache)
+ await self.reply_code(user, 'RPL_LIST', (real_chan, users_count, topic))
+ await self.reply_code(user, 'RPL_LISTEND')
+
hunk ./irc_replies.py 18
+ 'RPL_LISTSTART': ('321', 'Channel :Users Name'),
+ 'RPL_LIST': ('322', '{} {} :{}'),
+ 'RPL_LISTEND': ('323', ':End of /LIST'),
patch ea8cab114202439713c9d3d5040a91bb376f1d8f
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Nov 17 21:49:05 CET 2021
* irc: add USERHOST command
hunk ./irc.py 40
+IRC_USERHOST_RX = re.compile(PREFIX + r'USERHOST( +|\n)(?P<nick1>[^ ]+( +|\n)|)(?P<nick2>[^ ]+( +|\n)|)(?P<nick3>[^ ]+( +|\n)|)(?P<nick4>[^ ]+( +|\n)|)(?P<nick5>[^\n]+|)')
hunk ./irc.py 120
+ (IRC_USERHOST_RX, self.handle_irc_userhost, True, 1),
hunk ./irc.py 173
+ async def handle_irc_userhost(self, user, **nicks):
+ niv = nicks.values()
+ self.logger.debug('Handling USERHOST: %s', str(tuple(niv)))
+
+ reply = ''
+ sep = ''
+ away = '+'
+ for ni in niv:
+ n = ni.lower()
+ if n in self.users.keys():
+ usr = self.users[n]
+ oper = '*' if usr.oper else ''
+ reply += '{}{}{}={}{}@{}'.format(sep, usr.irc_nick, oper, away, usr.irc_username, usr.address)
+ if not sep: sep = ' '
+ if reply:
+ await self.reply_code(user, 'RPL_USERHOST', (reply,))
+
hunk ./irc_replies.py 10
+ 'RPL_USERHOST': ('302', ':{}'),
patch 524dd6f7c12b54f938923345e56a313df81213f4
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Mar 31 03:21:58 CEST 2021
* irc: Add PART command
hunk ./irc.py 33
+IRC_PART_RX = re.compile(PREFIX + r'PART( +|\n)(?P<channels>[^ ]+|)( +:| +|\n|)(?P<reason>[^\n]+|)')
hunk ./irc.py 112
+ (IRC_PART_RX, self.handle_irc_part, True, 1),
hunk ./irc.py 184
- #part all
- pass
+ for channel in self.irc_channels.keys():
+ if user.irc_nick in self.irc_channels[channel]:
+ await self.part_irc_channel(user, channel, '')
hunk ./irc.py 194
+ async def handle_irc_part(self, user, channels, reason):
+ self.logger.debug('Handling PART: %s, %s', channels, reason)
+
+ for channel in channels.split(','):
+ chan = channel.lower()
+ if chan in self.irc_channels.keys():
+ if user.irc_nick in self.irc_channels[chan]:
+ await self.part_irc_channel(user, channel, reason)
+ else:
+ await self.reply_code(user, 'ERR_NOTONCHANNEL', (channel,))
+ else:
+ await self.reply_code(user, 'ERR_NOSUCHCHANNEL', (channel,))
+
hunk ./irc.py 419
+ async def part_irc_channel(self, user, channel, reason):
+ chan = channel.lower()
+ real_chan = self.get_realcaps_name(chan)
+
+ # Notify IRC users in this channel
+ for usr in [self.users[x.lower()] for x in self.irc_channels[chan] if self.users[x.lower()].stream]:
+ await self.reply_command(usr, user, 'PART', (real_chan, reason))
+
+ self.irc_channels[chan].remove(user.irc_nick)
+ self.irc_channels_ops[chan].discard(user.irc_nick)
+ self.irc_channels_founder[chan].discard(user.irc_nick)
+
hunk ./irc.py 446
- async def part_irc_channel(self, user, channel):
- self.irc_channels[channel].remove(user.irc_nick)
- await self.send_irc_command(user, ':{} PART {} :'.format(
- user.get_irc_mask(), channel
- ))
-
hunk ./irc_replies.py 51
- 'ERR_NOTONCHANNEL': ('442', 'Not on channel'),
+ 'ERR_NOTONCHANNEL': ('442', '{} :Not on channel'),
patch 8f98a1d6a3608b31de2c9251491c1a51e5c64124
Author: E. Bosch <presidev@AT@gmail.com>
Date: Thu Mar 4 01:08:27 CET 2021
* irc: Add MODE command
Convert params_required from boolean to min number of parameters required
(num_params_required).
hunk ./irc.py 21
+ALL_PARAMS = 16
hunk ./irc.py 29
+IRC_MODE_RX = re.compile(PREFIX + r'MODE( +|\n)(?P<target>[^ ]+( +|\n)|)(?P<mode>[^ ]+( +|\n)|)(?P<arguments>[^\n]+|)')
hunk ./irc.py 77
- for pattern, handler, register_required, params_required in self.irc_handlers:
+ for pattern, handler, register_required, num_params_required in self.irc_handlers:
hunk ./irc.py 86
- if num_params >= num_params_expected or not params_required:
+ if num_params >= self.num_params_necessary(num_params_required,
+ num_params_expected):
hunk ./irc.py 105
- # pattern handle register_required params_required
- (IRC_JOIN_RX, self.handle_irc_join, True, True),
- (IRC_MOTD_RX, self.handle_irc_motd, True, False),
- (IRC_NAMES_RX, self.handle_irc_names, True, True),
- (IRC_NICK_RX, self.handle_irc_nick, False, True),
- (IRC_PASS_RX, self.handle_irc_pass, False, True),
- (IRC_PING_RX, self.handle_irc_ping, True, True),
- (IRC_PRIVMSG_RX, self.handle_irc_privmsg, True, True),
- (IRC_QUIT_RX, self.handle_irc_quit, False, False),
- (IRC_TOPIC_RX, self.handle_irc_topic, True, True),
- (IRC_USER_RX, self.handle_irc_user, False, True),
- (IRC_VERSION_RX, self.handle_irc_version, True, False),
- (IRC_WHO_RX, self.handle_irc_who, True, True),
- (IRC_WHOIS_RX, self.handle_irc_whois, True, True),
+ # pattern handle register_required num_params_required
+ (IRC_JOIN_RX, self.handle_irc_join, True, ALL_PARAMS),
+ (IRC_MODE_RX, self.handle_irc_mode, True, 1),
+ (IRC_MOTD_RX, self.handle_irc_motd, True, 0),
+ (IRC_NAMES_RX, self.handle_irc_names, True, ALL_PARAMS),
+ (IRC_NICK_RX, self.handle_irc_nick, False, ALL_PARAMS),
+ (IRC_PASS_RX, self.handle_irc_pass, False, ALL_PARAMS),
+ (IRC_PING_RX, self.handle_irc_ping, True, ALL_PARAMS),
+ (IRC_PRIVMSG_RX, self.handle_irc_privmsg, True, ALL_PARAMS),
+ (IRC_QUIT_RX, self.handle_irc_quit, False, 0),
+ (IRC_TOPIC_RX, self.handle_irc_topic, True, ALL_PARAMS),
+ (IRC_USER_RX, self.handle_irc_user, False, ALL_PARAMS),
+ (IRC_VERSION_RX, self.handle_irc_version, True, 0),
+ (IRC_WHO_RX, self.handle_irc_who, True, ALL_PARAMS),
+ (IRC_WHOIS_RX, self.handle_irc_whois, True, ALL_PARAMS),
hunk ./irc.py 197
+ async def handle_irc_mode(self, user, target, mode, arguments):
+ self.logger.debug('Handling MODE: %s, %s, %s', target, mode, arguments)
+
+ if not mode:
+ tgt = target.lower()
+ if tgt in self.users.keys():
+ if tgt == user.irc_nick:
+ await self.mode_user(user, user, False)
+ else:
+ await self.reply_code(user, 'ERR_USERSDONTMATCH')
+ elif tgt[0] == '#':
+ if tgt in self.irc_channels.keys():
+ await self.mode_channel(user, target, False)
+ else:
+ await self.reply_code(user, 'ERR_NOSUCHCHANNEL', (target,))
+ else:
+ await self.reply_code(user, 'ERR_NOSUCHNICK', (target,))
+
hunk ./irc.py 340
+ await self.mode_user(user, user, True)
hunk ./irc.py 363
+ async def mode_user(self, user, usr, com):
+ modes = ''
+ if usr.oper: modes += 'o'
+ if usr.tls: modes += 'S'
+ if modes: modes = '+' + modes
+ if com:
+ await self.reply_command(user, usr, 'MODE', (usr.irc_nick, modes))
+ else:
+ await self.reply_code(user, 'RPL_UMODEIS', (modes,))
+
+ async def mode_channel(self, user, channel, com):
+ modes = '+nt'
+ if com:
+ await self.reply_command(user, user, 'MODE', (channel, modes))
+ else:
+ await self.reply_code(user, 'RPL_CHANNELMODEIS', (channel, modes,''))
+
hunk ./irc.py 437
+ def num_params_necessary(self, num_params_required, num_params_expected):
+ if num_params_required == ALL_PARAMS:
+ npn = num_params_expected
+ else:
+ npn = num_params_required
+ return npn
+
hunk ./irc_replies.py 7
- 'RPL_MYINFO': ('004', '{} irgramd-{} o nt'),
+ 'RPL_MYINFO': ('004', '{} irgramd-{} oS nt'),
hunk ./irc_replies.py 9
+ 'RPL_UMODEIS': ('221', ':{}'),
hunk ./irc_replies.py 17
+ 'RPL_CHANNELMODEIS': ('324', '{} {} {}'),
patch 88dc8263a1e44bbacb642ba34ec7ddcfa6ec0719
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Feb 21 23:35:32 CET 2021
* irc: Add VERSION command
hunk ./irc.py 37
+IRC_VERSION_RX = re.compile(PREFIX + r'VERSION( +:| +|\n)(?P<target>[^\n ]+|)')
hunk ./irc.py 113
+ (IRC_VERSION_RX, self.handle_irc_version, True, False),
hunk ./irc.py 261
+ async def handle_irc_version(self, user, target):
+ self.logger.debug('Handling VERSION: %s', target)
+
+ tgt = target.lower()
+ if not tgt or tgt == self.hostname or tgt in self.users.keys():
+ await self.reply_code(user, 'RPL_VERSION', (VERSION, self.hostname))
+ await self.send_isupport(user)
+ else:
+ await self.reply_code(user, 'ERR_NOSUCHSERVER', (target,))
+
hunk ./irc.py 316
- await self.reply_code(user, 'RPL_ISUPPORT', (str(CHAN_MAX_LENGHT), str(NICK_MAX_LENGTH)))
+ await self.send_isupport(user)
hunk ./irc.py 333
+ async def send_isupport(self, user):
+ await self.reply_code(user, 'RPL_ISUPPORT', (CHAN_MAX_LENGHT, NICK_MAX_LENGTH))
+
hunk ./irc_replies.py 21
+ 'RPL_VERSION': ('351', 'irgramd-{} {} :IRC to Telegram gateway'),
patch cc0339ea68f91d508f96a56b575c0a587bd869d0
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Feb 21 23:32:20 CET 2021
* irc: Fix debug message in WHOIS handler
hunk ./irc.py 233
- self.logger.debug('Handling WHO: %s', nicks)
+ self.logger.debug('Handling WHOIS: %s', nicks)
patch 595793eb543ce778275da1ad086c675444e04c34
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Feb 21 01:12:58 CET 2021
* irc: Fix nick rename, was not updating channel structures
hunk ./irc.py 16
-from utils import chunks
+from utils import chunks, set_replace
hunk ./irc.py 148
+ current = user.irc_nick.lower()
hunk ./irc.py 150
- del self.users[user.irc_nick.lower()]
+ del self.users[current]
+ for ch in self.irc_channels.keys():
+ set_replace(self.irc_channels[ch], current, ni)
+ set_replace(self.irc_channels_ops[ch], current, ni)
+ set_replace(self.irc_channels_founder[ch], current, ni)
hunk ./utils.py 10
+
+def set_replace(set, item, new_item):
+ if item in set:
+ set.remove(item)
+ set.add(new_item)
patch f0d8dcc0208b2d27059174c3e23ef2e9fb370971
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Feb 21 01:01:32 CET 2021
* irc: Fix initialization of username
hunk ./irc.py 378
- def __init__(self, stream, address, irc_nick=None, username=None, realname=None):
+ def __init__(self, stream, address, irc_nick=None, username='', realname=None):
patch 00c6b4424dec6eb5c2f2d2e993084814b673461c
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Feb 20 23:52:38 CET 2021
* irc: Add QUIT command
hunk ./irc.py 34
+IRC_QUIT_RX = re.compile(PREFIX + r'QUIT( +:| +|\n)(?P<reason>[^\n]+|)')
hunk ./irc.py 63
+ user.stream = None
+ reason = user.close_reason if user.close_reason else ':Client disconnect'
+ await self.send_users_irc(user, 'QUIT', (reason,))
hunk ./irc.py 109
+ (IRC_QUIT_RX, self.handle_irc_quit, False, False),
hunk ./irc.py 148
- for usr in [x for x in self.users.values() if x.stream]:
- await self.reply_command(usr, user, 'NICK', (nick,))
+ await self.send_users_irc(user, 'NICK', (nick,))
hunk ./irc.py 266
+ async def handle_irc_quit(self, user, reason):
+ self.logger.debug('Handling TOPIC: %s', reason)
+
+ await self.reply_command(user, SRV, 'ERROR', (':Client disconnect',))
+ user.close_reason = ':' + reason
+ user.stream.close()
+
hunk ./irc.py 316
+ async def send_users_irc(self, prfx, command, params):
+ for usr in [x for x in self.users.values() if x.stream]:
+ await self.reply_command(usr, prfx, command, params)
+
hunk ./irc.py 390
+ self.close_reason = ''
patch cb76ecce75bd80a41fdab176f9dacf94801e5e23
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Feb 19 01:05:45 CET 2021
* irc: Add TOPIC command
hunk ./irc.py 34
+IRC_TOPIC_RX = re.compile(PREFIX + r'TOPIC( +:| +|\n)(?P<channel>[^\n ]+|)')
hunk ./irc.py 105
+ (IRC_TOPIC_RX, self.handle_irc_topic, True, True),
hunk ./irc.py 190
+ async def handle_irc_topic(self, user, channel):
+ self.logger.debug('Handling TOPIC: %s', channel)
+
+ chan = channel.lower()
+ real_chan = self.get_realcaps_name(chan)
+ await self.irc_channel_topic(user, real_chan)
+
hunk ./irc.py 328
- async def irc_channel_topic(self, user, channel, entity_cache):
- topic = await self.tg.get_channel_topic(channel, entity_cache)
- timestamp = await self.tg.get_channel_creation(channel, entity_cache)
- founder = list(self.irc_channels_founder[channel.lower()])[0]
+ async def irc_channel_topic(self, user, channel, entity_cache=[None]):
+ chan = channel.lower()
+ topic = await self.tg.get_channel_topic(chan, entity_cache)
+ timestamp = await self.tg.get_channel_creation(chan, entity_cache)
+ founder = list(self.irc_channels_founder[chan])[0]
patch d02af43b83f57ff7b57f61c691a2399615383be6
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Feb 19 00:48:08 CET 2021
* irc: Fix regular expressions
hunk ./irc.py 27
-IRC_JOIN_RX = re.compile(PREFIX + r'JOIN( +:| +|\n)(?P<channels>[^\n]+|)')
-IRC_MOTD_RX = re.compile(PREFIX + r'MOTD( +:| +|\n)(?P<target>[^\n]+|)')
-IRC_NAMES_RX = re.compile(PREFIX + r'NAMES( +:| +|\n)(?P<channels>[^\n]+|)')
-IRC_NICK_RX = re.compile(PREFIX + r'NICK( +:| +|\n)(?P<nick>[^\n]+|)')
-IRC_PASS_RX = re.compile(PREFIX + r'PASS( +:| +|\n)(?P<password>[^\n]+|)')
+IRC_JOIN_RX = re.compile(PREFIX + r'JOIN( +:| +|\n)(?P<channels>[^\n ]+|)')
+IRC_MOTD_RX = re.compile(PREFIX + r'MOTD( +:| +|\n)(?P<target>[^\n ]+|)')
+IRC_NAMES_RX = re.compile(PREFIX + r'NAMES( +:| +|\n)(?P<channels>[^\n ]+|)')
+IRC_NICK_RX = re.compile(PREFIX + r'NICK( +:| +|\n)(?P<nick>[^\n ]+|)')
+IRC_PASS_RX = re.compile(PREFIX + r'PASS( +:| +|\n)(?P<password>[^\n ]+|)')
hunk ./irc.py 35
-IRC_WHO_RX = re.compile(PREFIX + r'WHO( +:| +|\n)(?P<target>[^\n]+|)')
-IRC_WHOIS_RX = re.compile(PREFIX + r'WHOIS( +:| +|\n)(?P<nicks>[^\n]+|)')
+IRC_WHO_RX = re.compile(PREFIX + r'WHO( +:| +|\n)(?P<target>[^\n ]+|)')
+IRC_WHOIS_RX = re.compile(PREFIX + r'WHOIS( +:| +|\n)(?P<nicks>[^\n ]+|)')
patch 96d42ed534ad22a1d5ef012cb679b3ab6550151c
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Feb 17 21:24:00 CET 2021
* irc: Add MOTD command
Add support for commands without parameters
hunk ./irc.py 28
+IRC_MOTD_RX = re.compile(PREFIX + r'MOTD( +:| +|\n)(?P<target>[^\n]+|)')
hunk ./irc.py 69
- for pattern, handler, register_required in self.irc_handlers:
+ for pattern, handler, register_required, params_required in self.irc_handlers:
hunk ./irc.py 78
- if num_params >= num_params_expected:
+ if num_params >= num_params_expected or not params_required:
hunk ./irc.py 96
- # pattern handle register_required
- (IRC_JOIN_RX, self.handle_irc_join, True),
- (IRC_NAMES_RX, self.handle_irc_names, True),
- (IRC_NICK_RX, self.handle_irc_nick, False),
- (IRC_PASS_RX, self.handle_irc_pass, False),
- (IRC_PING_RX, self.handle_irc_ping, True),
- (IRC_PRIVMSG_RX, self.handle_irc_privmsg, True),
- (IRC_USER_RX, self.handle_irc_user, False),
- (IRC_WHO_RX, self.handle_irc_who, True),
- (IRC_WHOIS_RX, self.handle_irc_whois, True),
+ # pattern handle register_required params_required
+ (IRC_JOIN_RX, self.handle_irc_join, True, True),
+ (IRC_MOTD_RX, self.handle_irc_motd, True, False),
+ (IRC_NAMES_RX, self.handle_irc_names, True, True),
+ (IRC_NICK_RX, self.handle_irc_nick, False, True),
+ (IRC_PASS_RX, self.handle_irc_pass, False, True),
+ (IRC_PING_RX, self.handle_irc_ping, True, True),
+ (IRC_PRIVMSG_RX, self.handle_irc_privmsg, True, True),
+ (IRC_USER_RX, self.handle_irc_user, False, True),
+ (IRC_WHO_RX, self.handle_irc_who, True, True),
+ (IRC_WHOIS_RX, self.handle_irc_whois, True, True),
hunk ./irc.py 180
+ async def handle_irc_motd(self, user, target):
+ self.logger.debug('Handling MOTD: %s', target)
+
+ if not target or target == self.hostname:
+ await self.send_motd(user)
+ else:
+ await self.reply_code(user, 'ERR_NOSUCHSERVER', (target,))
+
patch b2ed84c15eb61138a3ed6f39254b54a2e9536345
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Feb 17 02:53:21 CET 2021
* irc: Improve the parsing
Recognize commands case insensitive: the RFCs are not very concise about
this and I don't know about clients sending commands in lower case but
just in case and it won't hurt
Line terminator: according RFCs should be \r\n but some clients only
send \n, I was confused about this but the parser must be robust and
now any combination of \r and \n is recognized
hunk ./irc.py 26
-PREFIX = r'(:[^ ]+ +|)'
+PREFIX = r'(?ai)(:[^ ]+ +|)'
hunk ./irc.py 65
- message = message.decode()
+ message = message.decode().replace('\r','\n')
patch a9a417e6056eebbd3a05bfb65fc1c2a79808c12d
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Feb 17 02:48:48 CET 2021
* irc: Fix JOIN and NAMES command regular expressions
hunk ./irc.py 27
-IRC_JOIN_RX = re.compile(PREFIX + r'JOIN( +|\n)(?P<channels>[^ ]+)')
-IRC_NAMES_RX = re.compile(PREFIX + r'NAMES( +|\n)(?P<channels>[^ ]+)')
+IRC_JOIN_RX = re.compile(PREFIX + r'JOIN( +:| +|\n)(?P<channels>[^\n]+|)')
+IRC_NAMES_RX = re.compile(PREFIX + r'NAMES( +:| +|\n)(?P<channels>[^\n]+|)')
patch 161f49073844cf414ebd6bd7fdd9fd47ffd36d76
Author: E. Bosch <presidev@AT@gmail.com>
Date: Tue Feb 16 19:44:01 CET 2021
* irc: Return True/False instead of 1/0 in valid_nick()
hunk ./irc.py 370
- return 0
- return 1
- else: return 0
+ return False
+ return True
+ else: return False
patch 8a4fa2a2053e3c2bceac8f793f0f3c77e3213fd8
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Feb 14 01:55:27 CET 2021
* irc: Add NAMES command
hunk ./irc.py 28
+IRC_NAMES_RX = re.compile(PREFIX + r'NAMES( +|\n)(?P<channels>[^ ]+)')
hunk ./irc.py 97
+ (IRC_NAMES_RX, self.handle_irc_names, True),
hunk ./irc.py 172
+ async def handle_irc_names(self, user, channels):
+ self.logger.debug('Handling NAMES: %s', channels)
+
+ for channel in channels.split(','):
+ await self.irc_namelist(user, channel)
+
patch 5b00b840b5eb306f65353707ae3d726ea5f010e4
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Feb 14 01:47:13 CET 2021
* irc: Show channel names with real caps in WHO response
hunk ./irc.py 180
- chan = target
+ chan = self.get_realcaps_name(tgt)
patch ec512178f7ed832b1c27969506ffe07b1a6eb2ac
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Feb 14 01:37:19 CET 2021
* irc: Fix not channel found reply call in JOIN command
hunk ./irc.py 168
- await reply_code(user, 'ERR_NOSUCHCHANNEL', (channel,))
+ await self.reply_code(user, 'ERR_NOSUCHCHANNEL', (channel,))
patch 96012123940ebb5a2671eb761cfd6540e18761a2
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Feb 14 01:02:09 CET 2021
* irc: Minor optimization in command parser
hunk ./irc.py 66
- matched = False
hunk ./irc.py 70
- matched = True
hunk ./irc.py 82
-
- if not matched and user.registered:
+ break
+
+ if not matches and user.registered:
patch 116330830a0e51f1298aa6577eabdaacb8158d03
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Feb 14 00:38:08 CET 2021
* irc: Add error reply when there is a command not allowed when not registered
Fix PING command: not allowed if not registered
hunk ./irc.py 82
+ else:
+ await self.reply_code(user, 'ERR_NOTREGISTERED', ('',), '*')
hunk ./irc.py 99
- (IRC_PING_RX, self.handle_irc_ping, False),
+ (IRC_PING_RX, self.handle_irc_ping, True),
hunk ./irc_replies.py 53
- 'ERR_NOTREGISTERED': ('451', 'Not registered'),
+ 'ERR_NOTREGISTERED': ('451', ':Not registered'),
patch 8b7eaea88f73d7a7a2020a04fbb593cac51a9236
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Feb 13 23:56:18 CET 2021
* irc: reorder JOIN definitions
hunk ./irc.py 27
+IRC_JOIN_RX = re.compile(PREFIX + r'JOIN( +|\n)(?P<channels>[^ ]+)')
hunk ./irc.py 33
-IRC_JOIN_RX = re.compile(PREFIX + r'JOIN( +|\n)(?P<channels>[^ ]+)')
hunk ./irc.py 94
+ (IRC_JOIN_RX, self.handle_irc_join, True),
hunk ./irc.py 100
- (IRC_JOIN_RX, self.handle_irc_join, True),
patch 4b63ac15d2b61f2c0da0686ba1936c79d7136444
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Feb 12 03:09:58 CET 2021
* irc: Fix JOIN command
hunk ./irc.py 32
-IRC_JOIN_RX = re.compile(PREFIX + r'JOIN( +|\n)(?P<channel>[^ ]+)')
+IRC_JOIN_RX = re.compile(PREFIX + r'JOIN( +|\n)(?P<channels>[^ ]+)')
hunk ./irc.py 156
- async def handle_irc_join(self, user, channel):
- self.logger.debug('Handling JOIN: %s', channel)
-
- await self.join_irc_channel(user, channel, True)
+ async def handle_irc_join(self, user, channels):
+ self.logger.debug('Handling JOIN: %s', channels)
+
+ if channels == '0':
+ #part all
+ pass
+ else:
+ for channel in channels.split(','):
+ if channel.lower() in self.irc_channels.keys():
+ await self.join_irc_channel(user, channel, True)
+ else:
+ await reply_code(user, 'ERR_NOSUCHCHANNEL', (channel,))
hunk ./irc.py 278
+ entity_cache = [None]
hunk ./irc.py 280
- self.irc_channels[chan].add(user.irc_nick)
+ real_chan = self.get_realcaps_name(chan)
+
+ if full_join: self.irc_channels[chan].add(user.irc_nick)
+
+ # Notify IRC users in this channel
+ for usr in [self.users[x.lower()] for x in self.irc_channels[chan] if self.users[x.lower()].stream]:
+ await self.reply_command(usr, user, 'JOIN', (real_chan,))
+
+ if not full_join:
+ return
+
hunk ./irc.py 295
- # Join Channel
- await self.send_irc_command(user, ':{} JOIN :{}'.format(
- user.get_irc_mask(), channel
- ))
-
- if not full_join:
- return
-
- # Get all users from channel
- tid = self.iid_to_tid[channel.lower()]
- nicks = self.irc_channels[channel.lower()]
-
- # Set channel topic
- topic = (await self.tg.telegram_client.get_entity(tid)).title
- await self.send_irc_command(user, ':{} TOPIC {} :{}'.format(
- user.get_irc_mask(), channel, topic
- ))
-
- # Send NAMESLIST
+ date = await self.tg.get_channel_creation(channel, entity_cache)
+ await self.reply_code(user, 'RPL_CREATIONTIME', (real_chan, date))
+ await self.irc_channel_topic(user, real_chan, entity_cache)
+ await self.irc_namelist(user, real_chan)
+
+ async def irc_channel_topic(self, user, channel, entity_cache):
+ topic = await self.tg.get_channel_topic(channel, entity_cache)
+ timestamp = await self.tg.get_channel_creation(channel, entity_cache)
+ founder = list(self.irc_channels_founder[channel.lower()])[0]
+ await self.reply_code(user, 'RPL_TOPIC', (channel, topic))
+ await self.reply_code(user, 'RPL_TOPICWHOTIME', (channel, founder, timestamp))
+
+ async def irc_namelist(self, user, channel):
+ nicks = [self.get_irc_op(x, channel) + x for x in self.irc_channels[channel.lower()]]
+ status = '='
hunk ./irc.py 311
- await self.send_irc_command(user, ':{} 353 {} = {} :{}'.format(
- self.hostname, user.irc_nick, channel, ' '.join(chunk)
- ))
+ await self.reply_code(user, 'RPL_NAMREPLY', (status, channel, ' '.join(chunk)))
+ await self.reply_code(user, 'RPL_ENDOFNAMES', (channel,))
hunk ./irc.py 329
+ def get_realcaps_name(self, name):
+ # name must be in lower
+ return self.tg.tid_to_iid[self.iid_to_tid[name]]
+
hunk ./irc_replies.py 16
+ 'RPL_CREATIONTIME': ('329', '{} {}'),
hunk ./irc_replies.py 18
+ 'RPL_TOPIC': ('332', '{} :{}'),
+ 'RPL_TOPICWHOTIME': ('333', '{} {} {}'),
hunk ./irc_replies.py 22
+ 'RPL_NAMREPLY': ('353', '{} {} :{}'),
+ 'RPL_ENDOFNAMES': ('366', '{} :End of NAME reply'),
hunk ./irc_replies.py 29
- 'ERR_NOSUCHCHANNEL': ('403', 'No such channel'),
+ 'ERR_NOSUCHCHANNEL': ('403', '{} :Channel not found'),
hunk ./telegram.py 5
+import re
hunk ./telegram.py 14
+# Constants
+
+TL_TYPES_IDENT = re.compile(r"<class 'telethon.tl.types.([^']+)'>")
+
hunk ./telegram.py 37
+ self.channels_date = {}
hunk ./telegram.py 197
- def get_tid(self, irc_nick, tid=None):
+ async def get_channel_topic(self, channel, entity_cache=[None]):
+ tid = self.get_tid(channel)
+ if entity_cache[0]:
+ entity = entity_cache[0]
+ else:
+ entity = await self.telegram_client.get_entity(tid)
+ entity_cache[0] = entity
+ entity_type = self.get_entity_type(entity)
+ return 'Telegram ' + entity_type + ' ' + str(tid) + ': ' + entity.title
+
+ async def get_channel_creation(self, channel, entity_cache=[None]):
+ tid = self.get_tid(channel)
+ if tid in self.channels_date.keys():
+ timestamp = self.channels_date[tid]
+ else:
+ if entity_cache[0]:
+ entity = entity_cache[0]
+ else:
+ entity = await self.telegram_client.get_entity(tid)
+ entity_cache[0] = entity
+ timestamp = entity.date.timestamp()
+ self.channels_date[tid] = timestamp
+ return int(timestamp)
+
+ def get_tid(self, irc_item, tid=None):
+ it = irc_item.lower()
hunk ./telegram.py 225
- elif irc_nick in self.irc.iid_to_tid:
- tid = self.irc.iid_to_tid[irc_nick.lower()]
+ elif it in self.irc.iid_to_tid:
+ tid = self.irc.iid_to_tid[it]
hunk ./telegram.py 231
+ def get_entity_type(self, entity):
+ return TL_TYPES_IDENT.match(str(type(entity))).groups()[0]
+
patch b97c6c1dbe715d083b40ae6c287e0de614af7f7a
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Feb 10 19:56:07 CET 2021
* irc: simplify prefix selection in reply command code
hunk ./irc.py 229
- if prfx == SRV:
- prefix = self.hostname
- else:
- prefix = prfx.get_irc_mask()
+ prefix = self.hostname if prfx == SRV else prfx.get_irc_mask()
patch bde05b3fbfa6558d64cd0c534e3a5c9dbc83785f
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Feb 10 19:55:02 CET 2021
* irc: simplify IRC username conversion
hunk ./irc.py 323
- self.irc_username = str(username) if type(username) is int else username
+ self.irc_username = str(username)
patch 6176f009d1ee06533bca9b17d829baac7989692a
Author: E. Bosch <presidev@AT@gmail.com>
Date: Tue Feb 9 00:58:21 CET 2021
* irc: ping handler (PONG response) now uses reply_command()
hunk ./irc.py 163
- await self.send_irc_command(user, ':{} PONG {} :{}'.format(
- self.hostname, self.hostname, payload
- ))
+
+ await self.reply_command(user, SRV, 'PONG', (self.hostname, payload))
patch d5ab5ead428630cada7cd85c04d2a91afdd00c28
Author: E. Bosch <presidev@AT@gmail.com>
Date: Tue Feb 9 00:04:54 CET 2021
* irc: Add rename function to NICK command (and cleanup a bit)
add reply_command() for covering all replies to commands
hunk ./irc.py 20
+SRV = None
hunk ./irc.py 128
+ ni = nick.lower()
hunk ./irc.py 130
- await self.reply_code(user, 'ERR_ERRONEUSNICKNAME', (nick,), '*')
- elif nick.lower() in self.users.keys():
+ await self.reply_code(user, 'ERR_ERRONEUSNICKNAME', (nick,), '*')
+ elif ni in self.users.keys():
hunk ./irc.py 134
+ if user.registered:
+ # rename
+ for usr in [x for x in self.users.values() if x.stream]:
+ await self.reply_command(usr, user, 'NICK', (nick,))
+ del self.users[user.irc_nick.lower()]
hunk ./irc.py 140
- self.users[nick.lower()] = user
-
- if user.irc_nick in self.iid_to_tid:
- tid = self.iid_to_tid[user.irc_nick]
- self.tg.tid_to_iid[tid] = nick
- self.iid_to_tid[nick] = tid
-
+ self.users[ni] = user
hunk ./irc.py 229
+ async def reply_command(self, user, prfx, comm, params):
+ if prfx == SRV:
+ prefix = self.hostname
+ else:
+ prefix = prfx.get_irc_mask()
+ p = len(params)
+ if p == 1:
+ fstri = ':{} {} {}'
+ else:
+ fstri = ':{} {}' + ((p - 1) * ' {}') + ' :{}'
+ await self.send_irc_command(user, fstri.format(prefix, comm, *params))
+
patch 8e120e3fd8592748b35ae91c4acbf189036704d9
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Feb 7 03:14:36 CET 2021
* irc: Add WHOIS command
hunk ./irc.py 33
+IRC_WHOIS_RX = re.compile(PREFIX + r'WHOIS( +:| +|\n)(?P<nicks>[^\n]+|)')
hunk ./irc.py 100
+ (IRC_WHOIS_RX, self.handle_irc_whois, True),
hunk ./irc.py 187
+ async def handle_irc_whois(self, user, nicks):
+ self.logger.debug('Handling WHO: %s', nicks)
+ for nick in nicks.split(','):
+ ni = nick.lower()
+ real_ni = self.users[ni].irc_nick
+ if ni in self.users.keys():
+ usr = self.users[ni]
+ await self.reply_code(user, 'RPL_WHOISUSER', (real_ni, usr.irc_username, usr.address, usr.irc_realname))
+ await self.reply_code(user, 'RPL_WHOISSERVER', (real_ni, self.hostname))
+ chans = usr.get_channels(self)
+ if chans: await self.reply_code(user, 'RPL_WHOISCHANNELS', (real_ni, chans))
+ idle = await self.tg.get_telegram_idle(ni)
+ if idle != None: await self.reply_code(user, 'RPL_WHOISIDLE', (real_ni, idle))
+ if usr.oper: await self.reply_code(user, 'RPL_WHOISOPERATOR', (real_ni,))
+ if usr.stream: await self.reply_code(user, 'RPL_WHOISACCOUNT', (real_ni,
+ '{}!{}@Telegram'.format(self.tg.tg_username, self.tg.id
+ )))
+ if await self.tg.is_bot(ni):
+ await self.reply_code(user, 'RPL_WHOISBOT', (real_ni,))
+ elif usr.tls or not usr.stream:
+ proto = 'TLS' if usr.tls else 'MTProto'
+ server = self.hostname if usr.stream else 'Telegram'
+ await self.reply_code(user, 'RPL_WHOISSECURE', (real_ni, proto, server))
+ await self.reply_code(user, 'RPL_ENDOFWHOIS', (real_ni,))
+ else:
+ await self.reply_code(user, 'ERR_NOSUCHNICK', (nick,))
+
hunk ./irc.py 316
+ self.oper = False
+ self.tls = False
+ self.bot = None
hunk ./irc.py 323
+ def get_channels(self, irc):
+ res = ''
+ for chan in irc.irc_channels.keys():
+ if self.irc_nick in irc.irc_channels[chan]:
+ res += irc.get_irc_op(self.irc_nick, chan) + chan + ' '
+ return res
+
hunk ./irc_replies.py 9
+ 'RPL_WHOISUSER': ('311', '{} {} {} * :{}'),
+ 'RPL_WHOISSERVER': ('312', '{} {} :irgramd gateway'),
+ 'RPL_WHOISOPERATOR': ('313', '{} :is an irgramd operator'),
hunk ./irc_replies.py 13
+ 'RPL_WHOISIDLE': ('317', '{} {} :seconds idle'),
+ 'RPL_ENDOFWHOIS': ('318', '{} :End of WHOIS command'),
+ 'RPL_WHOISCHANNELS': ('319', '{} :{}'),
+ 'RPL_WHOISACCOUNT': ('330', '{} {} :Telegram name'),
+ 'RPL_WHOISBOT': ('335', '{} :is a Telegram bot'),
hunk ./irc_replies.py 22
- 'ERR_NOSUCHNICK': ('401', 'No such nick'),
+ 'ERR_NOSUCHNICK': ('401', '{} :Nick not found'),
hunk ./irc_replies.py 66
+ 'RPL_WHOISSECURE': ('671', '{} :is using a secure {} connection with {}'),
hunk ./telegram.py 4
+import datetime
hunk ./telegram.py 161
+ async def get_telegram_idle(self, irc_nick, tid=None):
+ tid = self.get_tid(irc_nick, tid)
+ user = await self.telegram_client.get_entity(tid)
+ if isinstance(user.status,tgty.UserStatusRecently) or \
+ isinstance(user.status,tgty.UserStatusOnline):
+ idle = 0
+ elif isinstance(user.status,tgty.UserStatusOffline):
+ last = user.status.was_online
+ current = datetime.datetime.now(datetime.timezone.utc)
+ idle = int((current - last).total_seconds())
+ elif isinstance(user.status,tgty.UserStatusLastWeek):
+ idle = 604800
+ elif isinstance(user.status,tgty.UserStatusLastMonth):
+ idle = 2678400
+ else:
+ idle = None
+ return idle
+
+ async def is_bot(self, irc_nick, tid=None):
+ if self.irc.users[irc_nick].stream:
+ bot = False
+ else:
+ bot = self.irc.users[irc_nick].bot
+ if bot == None:
+ tid = self.get_tid(irc_nick, tid)
+ user = await self.telegram_client.get_entity(tid)
+ bot = user.bot
+ self.irc.users[irc_nick].bot = bot
+ return bot
+
+ def get_tid(self, irc_nick, tid=None):
+ if tid:
+ pass
+ elif irc_nick in self.irc.iid_to_tid:
+ tid = self.irc.iid_to_tid[irc_nick.lower()]
+ else:
+ tid = self.id
+ return tid
+
patch f4cc7576a468f3c1edcd37d3f1992cb86fc1c1c8
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Feb 6 01:07:48 CET 2021
* irc: Fix some reply types of codes from int to str
hunk ./irc_replies.py 10
- 'RPL_WHOREPLY': (352, '{} {} {} {} {} H{} :0 {}'),
+ 'RPL_WHOREPLY': ('352', '{} {} {} {} {} H{} :0 {}'),
hunk ./irc_replies.py 13
- 'RPL_ENDOFMOTD': (376, 'End of MOTD command'),
+ 'RPL_ENDOFMOTD': ('376', 'End of MOTD command'),
patch a632ad5590ca682a3409d25be72768761aff0b95
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Feb 5 00:38:22 CET 2021
* irc: Add WHO command
hunk ./irc.py 32
+IRC_WHO_RX = re.compile(PREFIX + r'WHO( +:| +|\n)(?P<target>[^\n]+|)')
hunk ./irc.py 98
+ (IRC_WHO_RX, self.handle_irc_who, True),
hunk ./irc.py 164
+ async def handle_irc_who(self, user, target):
+ self.logger.debug('Handling WHO: %s', target)
+ tgt = target.lower()
+ if tgt in self.irc_channels.keys():
+ users = self.irc_channels[tgt]
+ chan = target
+ elif tgt in self.users.keys():
+ users = (self.users[tgt],)
+ chan = '*'
+ else:
+ await self.reply_code(user, 'ERR_NOSUCHSERVER', (target,))
+ return
+ for usr in users:
+ if not isinstance(usr,IRCUser):
+ usr = self.users[usr.lower()]
+ op = self.get_irc_op(usr.irc_nick, chan)
+ await self.reply_code(user, 'RPL_WHOREPLY', (chan, usr.irc_username,
+ usr.address, self.hostname, usr.irc_nick, op, usr.irc_realname
+ ))
+ await self.reply_code(user, 'RPL_ENDOFWHO', (chan,))
+
hunk ./irc_replies.py 9
+ 'RPL_ENDOFWHO': ('315', '{} :End of WHO list'),
+ 'RPL_WHOREPLY': (352, '{} {} {} {} {} H{} :0 {}'),
hunk ./irc_replies.py 15
- 'ERR_NOSUCHSERVER': ('402', 'No such server'),
+ 'ERR_NOSUCHSERVER': ('402', '{} :Target not found'),
patch fb9e0599871ca17db6fe28ef3d2a53c5354f264a
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Feb 5 00:26:44 CET 2021
* irc: Fix channel handling for self user
hunk ./irc.py 58
+ user.del_from_channels(self)
hunk ./irc.py 101
- self.irc_channels_founder = {}
+ self.irc_channels_founder = collections.defaultdict(set)
hunk ./irc.py 211
+ op = self.get_irc_op(self.tg.tg_username, channel)
+ if op == '@': self.irc_channels_ops[chan].add(user.irc_nick)
+ elif op == '~': self.irc_channels_founder[chan].add(user.irc_nick)
hunk ./irc.py 245
+ def get_irc_op(self, nick, channel):
+ chan = channel.lower()
+ if chan in self.irc_channels.keys():
+ if nick in self.irc_channels_ops[chan]:
+ return '@'
+ if nick in self.irc_channels_founder[chan]:
+ return '~'
+ return ''
+
hunk ./irc.py 275
+
+ def del_from_channels(self, irc, channels=None):
+ for chan in channels if channels else irc.irc_channels.keys():
+ irc.irc_channels[chan].discard(self.irc_nick)
+ irc.irc_channels_ops[chan].discard(self.irc_nick)
+ irc.irc_channels_founder[chan].discard(self.irc_nick)
hunk ./telegram.py 107
- self.irc.irc_channels_founder[chan] = user_nick
+ self.irc.irc_channels_founder[chan].add(user_nick)
patch bb3b1764cd938721fa5ff776c4e6a2352b6ef105
Author: E. Bosch <presidev@AT@gmail.com>
Date: Thu Feb 4 15:37:25 CET 2021
* irc: Strip unwanted characters from command parameters
hunk ./irc.py 70
+ # Remove possible extra characters in parameters
+ params = {x:y.strip() for x,y in params.items()}
patch 6161cf3d257e2a0b1497f9dd283b0b8d13903835
Author: E. Bosch <presidev@AT@gmail.com>
Date: Tue Feb 2 23:52:41 CET 2021
* Fix mapping Telegram<->IRC for self user
hunk ./irc.py 162
- if nick not in self.iid_to_tid:
+ target = self.tg.tg_username if nick == user.irc_nick else nick
+ tgt = target.lower()
+
+ if tgt not in self.iid_to_tid:
hunk ./irc.py 168
- telegram_id = self.iid_to_tid[nick]
+ telegram_id = self.iid_to_tid[tgt]
hunk ./telegram.py 29
+ self.id = None
+ self.tg_username = None
hunk ./telegram.py 69
+ tg_user = await self.telegram_client.get_me()
+ self.id = tg_user.id
+ self.tg_username = self.get_telegram_nick(tg_user)
hunk ./telegram.py 75
- if not chat.is_self:
- self.set_ircuser_from_telegram(chat)
+ self.set_ircuser_from_telegram(chat)
hunk ./telegram.py 82
- irc_user = IRCUser(None, ('Telegram',), tg_nick, user.id, self.get_telegram_display_name(user))
- self.irc.users[tg_nick.lower()] = irc_user
+ tg_ni = tg_nick.lower()
+ if not user.is_self:
+ irc_user = IRCUser(None, ('Telegram',), tg_nick, user.id, self.get_telegram_display_name(user))
+ self.irc.users[tg_ni] = irc_user
hunk ./telegram.py 87
- self.irc.iid_to_tid[tg_nick.lower()] = user.id
+ self.irc.iid_to_tid[tg_ni] = user.id
hunk ./telegram.py 128
+ if nick == self.tg_username: return None
hunk ./telegram.py 174
+ usr = user if user else irc_user
hunk ./telegram.py 176
- user.get_irc_mask(), irc_user.irc_nick, message
+ usr.get_irc_mask(), irc_user.irc_nick, message
hunk ./telegram.py 205
+ usr = user if user else irc_user
hunk ./telegram.py 207
- user.get_irc_mask(), channel, message
+ usr.get_irc_mask(), channel, message
patch 5c06638983343c88cd73232a723f5efa09fed082
Author: E. Bosch <presidev@AT@gmail.com>
Date: Mon Feb 1 23:18:25 CET 2021
* Record channel admins in Telegram as ops in IRC, and channel creators in Telegram as creators/founders in IRC
hunk ./irc.py 97
+ self.irc_channels_ops = collections.defaultdict(set)
+ self.irc_channels_founder = {}
hunk ./irc.py 203
- self.irc_channels[channel.lower()].add(user.irc_nick)
+ chan = channel.lower()
+ self.irc_channels[chan].add(user.irc_nick)
hunk ./telegram.py 5
+from telethon import types as tgty
hunk ./telegram.py 69
- if isinstance(chat, telethon.types.User):
+ if isinstance(chat, tgty.User):
hunk ./telegram.py 90
+ chan = channel.lower()
hunk ./telegram.py 92
- for user in [x async for x in self.telegram_client.iter_participants(chat.id) if not x.is_self]:
+ async for user in self.telegram_client.iter_participants(chat.id):
hunk ./telegram.py 94
- self.irc.irc_channels[channel.lower()].add(user_nick)
+ 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):
+ self.irc.irc_channels_ops[chan].add(user_nick)
+ # Add creator users as founders in irc
+ elif isinstance(user.participant, tgty.ChatParticipantCreator):
+ self.irc.irc_channels_founder[chan] = user_nick
hunk ./telegram.py 228
- if not isinstance(chat, telethon.types.User):
+ if not isinstance(chat, tgty.User):
patch 056e5b5a8b6235ebdca9d0524b1d8a5d50e41837
Author: E. Bosch <presidev@AT@gmail.com>
Date: Mon Feb 1 19:48:42 CET 2021
* irc: update user mask generation
hunk ./irc.py 99
- def get_irc_user_mask(self, nick):
- return '{}!{}@{}'.format(nick, nick, self.hostname)
hunk ./irc.py 205
- self.get_irc_user_mask(user.irc_nick), channel
+ user.get_irc_mask(), channel
hunk ./irc.py 218
- self.get_irc_user_mask(user.irc_nick), channel, topic
+ user.get_irc_mask(), channel, topic
hunk ./irc.py 230
- self.get_irc_user_mask(user.irc_nick), channel
+ user.get_irc_mask(), channel
hunk ./irc.py 244
+ def get_irc_mask(self):
+ return '{}!{}@{}'.format(self.irc_nick, self.irc_username, self.address)
+
hunk ./telegram.py 111
+ def get_irc_user_from_telegram(self, tid):
+ nick = self.tid_to_iid[tid]
+ return self.irc.users[nick.lower()]
+
hunk ./telegram.py 155
- nick = await self.get_irc_nick_from_telegram_id(event.sender_id)
+ user = self.get_irc_user_from_telegram(event.sender_id)
hunk ./telegram.py 157
- for user in [x for x in self.irc.users.values() if x.stream]:
- await self.irc.send_irc_command(user, ':{} PRIVMSG {} :{}'.format(
- self.irc.get_irc_user_mask(nick), user.irc_nick, message
+ for irc_user in [x for x in self.irc.users.values() if x.stream]:
+ await self.irc.send_irc_command(irc_user, ':{} PRIVMSG {} :{}'.format(
+ user.get_irc_mask(), irc_user.irc_nick, message
hunk ./telegram.py 171
- nick = await self.get_irc_nick_from_telegram_id(event.sender_id)
+ user = self.get_irc_user_from_telegram(event.sender_id)
+
hunk ./telegram.py 187
- for user in [x for x in self.irc.users.values() if x.stream]:
- await self.irc.send_irc_command(user, ':{} PRIVMSG {} :{}'.format(
- self.irc.get_irc_user_mask(nick), channel, message
+ for irc_user in [x for x in self.irc.users.values() if x.stream]:
+ await self.irc.send_irc_command(irc_user, ':{} PRIVMSG {} :{}'.format(
+ user.get_irc_mask(), channel, message
patch 4cff2e00c20989a0f7cf0bdcebd126a71967bf3a
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Jan 31 20:23:45 CET 2021
* Improve mapping between Telegram and IRC: users and channels
hunk ./irc.py 50
- self.logger.debug('Running client connection from %s', user.address)
+ self.logger.debug('Running client connection from %s:%s', address[0], address[1])
hunk ./irc.py 203
- self.irc_channels[channel].add(user.irc_nick)
+ self.irc_channels[channel.lower()].add(user.irc_nick)
hunk ./irc.py 213
- # Add all users to channel
- tid = self.iid_to_tid[channel]
- nicks = await self.tg.get_telegram_channel_participants(tid)
+ # Get all users from channel
+ tid = self.iid_to_tid[channel.lower()]
+ nicks = self.irc_channels[channel.lower()]
hunk ./irc.py 236
- def __init__(self, stream, address):
+ def __init__(self, stream, address, irc_nick=None, username=None, realname=None):
hunk ./irc.py 238
- self.address = '{}:{}'.format(address[0], address[1])
- self.irc_nick = None
- self.irc_username = None
- self.irc_realname = None
+ self.address = address[0]
+ self.irc_nick = irc_nick
+ self.irc_username = str(username) if type(username) is int else username
+ self.irc_realname = realname
hunk ./telegram.py 9
+from irc import IRCUser
hunk ./telegram.py 69
- user = self.get_telegram_nick(chat)
- self.tid_to_iid[chat.id] = user
- self.irc.iid_to_tid[user] = chat.id
+ if not chat.is_self:
+ self.set_ircuser_from_telegram(chat)
hunk ./telegram.py 72
- channel = self.get_telegram_channel(chat)
- self.tid_to_iid[chat.id] = channel
- self.irc.iid_to_tid[channel] = chat.id
+ await self.set_irc_channel_from_telegram(chat)
+
+ def set_ircuser_from_telegram(self, user):
+ if user.id not in self.tid_to_iid:
+ tg_nick = self.get_telegram_nick(user)
+ irc_user = IRCUser(None, ('Telegram',), tg_nick, user.id, self.get_telegram_display_name(user))
+ self.irc.users[tg_nick.lower()] = irc_user
+ self.tid_to_iid[user.id] = tg_nick
+ self.irc.iid_to_tid[tg_nick.lower()] = user.id
+ else:
+ tg_nick = self.tid_to_iid[user.id]
+ return tg_nick
+
+ async def set_irc_channel_from_telegram(self, chat):
+ channel = self.get_telegram_channel(chat)
+ self.tid_to_iid[chat.id] = channel
+ self.irc.iid_to_tid[channel.lower()] = chat.id
+ # Add users from the channel
+ for user in [x async for x in self.telegram_client.iter_participants(chat.id) if not x.is_self]:
+ user_nick = self.set_ircuser_from_telegram(user)
+ self.irc.irc_channels[channel.lower()].add(user_nick)
hunk ./telegram.py 96
- or telethon.utils.get_display_name(user)
+ or self.get_telegram_display_name(user)
hunk ./telegram.py 98
- nick = nick.replace(' ', '')[:NICK_MAX_LENGTH]
+ nick = nick[:NICK_MAX_LENGTH]
hunk ./telegram.py 102
+
+ def get_telegram_display_name(self, user):
+ name = telethon.utils.get_display_name(user)
+ name = name.replace(' ', '_')
+ return name
hunk ./telegram.py 109
- return '#' + chat.title.lower().replace(' ', '-')
+ return '#' + chat.title.replace(' ', '-')
hunk ./telegram.py 153
- for user in self.irc.users.values():
+ for user in [x for x in self.irc.users.values() if x.stream]:
hunk ./telegram.py 164
- if channel not in self.irc.irc_channels:
- await self.irc.join_irc_channel(self.irc.irc_nick, channel, True)
+# if channel not in self.irc.irc_channels:
+# await self.irc.join_irc_channel(self.irc.irc_nick, channel, True)
hunk ./telegram.py 168
- if nick not in self.irc.irc_channels[channel]:
- await self.irc.join_irc_channel(nick, channel, False)
+# if nick not in self.irc.irc_channels[channel]:
+# await self.irc.join_irc_channel(nick, channel, False)
hunk ./telegram.py 182
- for user in self.irc.users.values():
+ for user in [x for x in self.irc.users.values() if x.stream]:
patch ff5ce787fa94beaa0cb7d3dff7849ba4386da021
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Jan 31 01:39:07 CET 2021
* irc: Improve reply function
Fix replies for errors in nick command
hunk ./irc.py 75
- await self.reply(user, 'ERR_NEEDMOREPARAMS')
+ await self.reply_code(user, 'ERR_NEEDMOREPARAMS')
hunk ./irc.py 78
- await self.reply(user, 'ERR_UNKNOWNCOMMAND')
+ await self.reply_code(user, 'ERR_UNKNOWNCOMMAND')
hunk ./irc.py 113
- await self.reply(user, 'ERR_ALREADYREGISTRED')
+ await self.reply_code(user, 'ERR_ALREADYREGISTRED')
hunk ./irc.py 121
- await self.reply(user, 'ERR_ERRONEUSNICKNAME')
+ await self.reply_code(user, 'ERR_ERRONEUSNICKNAME', (nick,), '*')
hunk ./irc.py 123
- await self.reply(user, 'ERR_NICKNAMEINUSE')
+ await self.reply_code(user, 'ERR_NICKNAMEINUSE', (nick,), '*')
hunk ./irc.py 137
- await self.reply(user, 'ERR_PASSWDMISMATCH')
+ await self.reply_code(user, 'ERR_PASSWDMISMATCH')
hunk ./irc.py 170
- async def reply(self, user, code):
+ async def reply_code(self, user, code, params=None, client=None):
hunk ./irc.py 172
- await self.send_irc_command(user, ':{} {} {} :{}'.format(
- self.hostname, num, user.irc_nick, tail
- ))
-
- async def reply_param(self, user, num, rest):
- await self.send_irc_command(user, ':{} {} {} {}'.format(
- self.hostname, num, user.irc_nick, rest
- ))
+ if params:
+ nick = client if client else user.irc_nick
+ rest = tail.format(*params)
+ stri = ':{} {} {} {}'.format(self.hostname, num, nick, rest)
+ else:
+ stri = ':{} {} {} :{}'.format(self.hostname, num, user.irc_nick, tail)
+ await self.send_irc_command(user, stri)
hunk ./irc.py 181
- num, rest = irc_codes['RPL_WELCOME']
- await self.reply_param(user, num, rest.format(user.irc_nick))
- num, rest = irc_codes['RPL_YOURHOST']
- await self.reply_param(user, num, rest.format(self.hostname, VERSION))
- num, rest = irc_codes['RPL_CREATED']
- await self.reply_param(user, num, rest.format(self.start_time))
- num, rest = irc_codes['RPL_MYINFO']
- await self.reply_param(user, num, rest.format(self.hostname, VERSION))
- num, rest = irc_codes['RPL_ISUPPORT']
- await self.reply_param(user, num, rest.format(str(CHAN_MAX_LENGHT), str(NICK_MAX_LENGTH)))
+ await self.reply_code(user, 'RPL_WELCOME', (user.irc_nick,))
+ await self.reply_code(user, 'RPL_YOURHOST', (self.hostname, VERSION))
+ await self.reply_code(user, 'RPL_CREATED', (self.start_time,))
+ await self.reply_code(user, 'RPL_MYINFO', (self.hostname, VERSION))
+ await self.reply_code(user, 'RPL_ISUPPORT', (str(CHAN_MAX_LENGHT), str(NICK_MAX_LENGTH)))
hunk ./irc.py 189
- num, rest = irc_codes['RPL_MOTDSTART']
- await self.reply_param(user, num, rest.format(self.hostname))
- num, rest = irc_codes['RPL_MOTD']
- await self.reply_param(user, num, rest.format('Welcome to the irgramd server'))
- await self.reply_param(user, num, rest.format(''))
- await self.reply_param(user, num, rest.format('This is not a normal IRC server, it\'s a gateway that'))
- await self.reply_param(user, num, rest.format('allows connecting from an IRC client (the program that'))
- await self.reply_param(user, num, rest.format('you are [probably] using right now) to the Telegram instant'))
- await self.reply_param(user, num, rest.format('messaging network as a regular user account (not bot)'))
- await self.reply_param(user, num, rest.format(''))
- await self.reply_param(user, num, rest.format('irgramd is an open source project that you can find on'))
- await self.reply_param(user, num, rest.format('git repository: https://github.com/prsai/irgramd'))
- await self.reply_param(user, num, rest.format('darcs repository: https://src.presi.org/darcs/irgramd'))
- await self.reply(user, 'RPL_ENDOFMOTD')
+ await self.reply_code(user, 'RPL_MOTDSTART', (self.hostname,))
+ await self.reply_code(user, 'RPL_MOTD', ('Welcome to the irgramd server',))
+ await self.reply_code(user, 'RPL_MOTD', ('',))
+ await self.reply_code(user, 'RPL_MOTD', ('This is not a normal IRC server, it\'s a gateway that',))
+ await self.reply_code(user, 'RPL_MOTD', ('allows connecting from an IRC client (the program that',))
+ await self.reply_code(user, 'RPL_MOTD', ('you are [probably] using right now) to the Telegram instant',))
+ await self.reply_code(user, 'RPL_MOTD', ('messaging network as a regular user account (not bot)',))
+ await self.reply_code(user, 'RPL_MOTD', ('',))
+ await self.reply_code(user, 'RPL_MOTD', ('irgramd is an open source project that you can find on',))
+ await self.reply_code(user, 'RPL_MOTD', ('git repository: https://github.com/prsai/irgramd',))
+ await self.reply_code(user, 'RPL_MOTD', ('darcs repository: https://src.presi.org/darcs/irgramd',))
+ await self.reply_code(user, 'RPL_ENDOFMOTD')
hunk ./irc_replies.py 29
- 'ERR_ERRONEUSNICKNAME': ('432', 'Erroneus nickname'),
- 'ERR_NICKNAMEINUSE': ('433', 'Nickname in use'),
+ 'ERR_ERRONEUSNICKNAME': ('432', '{} :Erroneus nickname'),
+ 'ERR_NICKNAMEINUSE': ('433', '{} :Nickname in use'),
patch 86f9714b9d1e6bfcc23baf0d7518d1d433ae9b8a
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Jan 29 01:44:15 CET 2021
* irc: Implement CASEMAPPING=ascii for nicks
hunk ./irc.py 57
- del self.users[user.irc_nick]
+ del self.users[user.irc_nick.lower()]
hunk ./irc.py 122
- elif nick in self.users.keys():
+ elif nick.lower() in self.users.keys():
hunk ./irc.py 126
- self.users[nick] = user
+ self.users[nick.lower()] = user
patch fd56f8eea3d9bba22d6597d934449795fb156101
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Jan 29 01:24:25 CET 2021
* irc: Handle user disconnection
hunk ./irc.py 53
- message = await user.stream.read_until(b'\n')
+ try:
+ message = await user.stream.read_until(b'\n')
+ except tornado.iostream.StreamClosedError:
+ if user in self.users.values():
+ del self.users[user.irc_nick]
+ del user
+ break
hunk ./irc.py 250
- self.registered = True
+ self.registered = False
patch 45b2ab79cbf0c21382ec472fd8726cfeacdb9bc1
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Jan 29 00:28:55 CET 2021
* irc: Improve users data structure in IRCHandler
hunk ./irc.py 41
- self.users = []
+ self.users = {}
hunk ./irc.py 49
- self.users.append(user)
hunk ./irc.py 116
- elif nick in [x.irc_nick for x in self.users if x is not user]:
+ elif nick in self.users.keys():
hunk ./irc.py 120
+ self.users[nick] = user
hunk ./telegram.py 130
- for user in self.irc.users:
+ for user in self.irc.users.values():
hunk ./telegram.py 159
- for user in self.irc.users:
+ for user in self.irc.users.values():
patch 7ae0aa39e474aa8974b0da88ffe14125da15ec66
Author: E. Bosch <presidev@AT@gmail.com>
Date: Thu Jan 28 14:07:44 CET 2021
* irc: Move valid_nick() to IRCUser class
hunk ./irc.py 115
- if not self.valid_nick(nick):
+ if not user.valid_nick(nick):
hunk ./irc.py 237
- def valid_nick(self, nick):
- if len(nick) <= NICK_MAX_LENGTH and nick[0] in VALID_IRC_NICK_FIRST_CHARS:
- for x in nick[1:]:
- if x not in VALID_IRC_NICK_CHARS:
- return 0
- return 1
- else: return 0
-
hunk ./irc.py 247
+
+ def valid_nick(self, nick):
+ if len(nick) <= NICK_MAX_LENGTH and nick[0] in VALID_IRC_NICK_FIRST_CHARS:
+ for x in nick[1:]:
+ if x not in VALID_IRC_NICK_CHARS:
+ return 0
+ return 1
+ else: return 0
patch 2dc409973eb2934cb273a0707b662b85959cf03d
Author: E. Bosch <presidev@AT@gmail.com>
Date: Thu Jan 28 00:13:48 CET 2021
* telegram: Replace telegram_client.start() with telegram_client.connect()
This will allow to set the verification code (if necessary) from IRC
hunk ./irgramd 5
-
-import tornado.ioloop
+import asyncio
+
hunk ./irgramd 34
- def run(self):
+ async def run(self):
hunk ./irgramd 42
- self.tg_handler.initialize_telegram()
-
- tornado.ioloop.IOLoop.current().start()
+ await self.tg_handler.initialize_telegram()
hunk ./irgramd 55
- irc_server.run()
+ asyncio.get_event_loop().run_until_complete(irc_server.run())
+ asyncio.get_event_loop().run_forever()
hunk ./telegram.py 26
-
- def initialize_telegram(self):
+ self.authorized = False
+
+ async def initialize_telegram(self):
hunk ./telegram.py 57
- self.telegram_client.start()
+ await self.telegram_client.connect()
+
+ if await self.telegram_client.is_user_authorized():
+ self.authorized = True
+ await self.init_mapping()
+
+ async def init_mapping(self):
hunk ./telegram.py 65
- for dialog in self.telegram_client.iter_dialogs():
+ async for dialog in self.telegram_client.iter_dialogs():
patch f6adfec3b3a67fe89d7f7528ea918372f8070ace
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Jan 27 20:51:33 CET 2021
* irc: Add registration handling of new connections
Improve replies, error responses, motd, nick validation, etc.
addfile ./include.py
hunk ./include.py 1
+
+# Constants
+
+VERSION = '0.1'
+NICK_MAX_LENGTH = 20
+CHAN_MAX_LENGHT = 50
hunk ./irc.py 6
+import string
+import time
hunk ./irc.py 14
+from include import VERSION, CHAN_MAX_LENGHT, NICK_MAX_LENGTH
+from irc_replies import irc_codes
hunk ./irc.py 17
-from irc_replies import irc_codes
+
+# Constants
+
+VALID_IRC_NICK_FIRST_CHARS = string.ascii_letters + '[]\`_^{|}'
+VALID_IRC_NICK_CHARS = VALID_IRC_NICK_FIRST_CHARS + string.digits + '-'
hunk ./irc.py 92
+ self.start_time = time.strftime('%a %d %b %Y %H:%M:%S %z')
hunk ./irc.py 102
+ # IRC handlers
+
+ async def handle_irc_pass(self, user, password):
+ self.logger.debug('Handling PASS: %s %s', password)
+
+ if user.registered:
+ await self.reply(user, 'ERR_ALREADYREGISTRED')
+ else:
+ user.recv_pass = password
+
hunk ./irc.py 115
- if user.irc_nick in self.iid_to_tid:
- tid = self.iid_to_tid[user.irc_nick]
- self.tg.tid_to_iid[tid] = nick
- self.iid_to_tid[nick] = tid
-
- user.irc_nick = nick
+ if not self.valid_nick(nick):
+ await self.reply(user, 'ERR_ERRONEUSNICKNAME')
+ elif nick in [x.irc_nick for x in self.users if x is not user]:
+ await self.reply(user, 'ERR_NICKNAMEINUSE')
+ elif user.password == user.recv_pass:
+ user.irc_nick = nick
+
+ if user.irc_nick in self.iid_to_tid:
+ tid = self.iid_to_tid[user.irc_nick]
+ self.tg.tid_to_iid[tid] = nick
+ self.iid_to_tid[nick] = tid
+
+ if not user.registered and user.irc_username:
+ user.registered = True
+ await self.send_greeting(user)
+ else:
+ await self.reply(user, 'ERR_PASSWDMISMATCH')
hunk ./irc.py 138
-
- await self.send_irc_command(user, ':{} 001 {} :{}'.format(
- self.hostname, user.irc_nick, 'Welcome to irgramd'
- ))
- await self.send_irc_command(user, ':{} 376 {} :{}'.format(
- self.hostname, user.irc_nick, 'End of MOTD command'
- ))
+ if user.irc_nick:
+ user.registered = True
+ await self.send_greeting(user)
hunk ./irc.py 147
- async def handle_irc_pass(self, user, app_id, app_hash):
- self.logger.debug('Handling PASS: %s %s', app_id, app_hash)
-
hunk ./irc.py 170
+ async def reply_param(self, user, num, rest):
+ await self.send_irc_command(user, ':{} {} {} {}'.format(
+ self.hostname, num, user.irc_nick, rest
+ ))
+
+ async def send_greeting(self, user):
+ num, rest = irc_codes['RPL_WELCOME']
+ await self.reply_param(user, num, rest.format(user.irc_nick))
+ num, rest = irc_codes['RPL_YOURHOST']
+ await self.reply_param(user, num, rest.format(self.hostname, VERSION))
+ num, rest = irc_codes['RPL_CREATED']
+ await self.reply_param(user, num, rest.format(self.start_time))
+ num, rest = irc_codes['RPL_MYINFO']
+ await self.reply_param(user, num, rest.format(self.hostname, VERSION))
+ num, rest = irc_codes['RPL_ISUPPORT']
+ await self.reply_param(user, num, rest.format(str(CHAN_MAX_LENGHT), str(NICK_MAX_LENGTH)))
+ await self.send_motd(user)
+
+ async def send_motd(self, user):
+ num, rest = irc_codes['RPL_MOTDSTART']
+ await self.reply_param(user, num, rest.format(self.hostname))
+ num, rest = irc_codes['RPL_MOTD']
+ await self.reply_param(user, num, rest.format('Welcome to the irgramd server'))
+ await self.reply_param(user, num, rest.format(''))
+ await self.reply_param(user, num, rest.format('This is not a normal IRC server, it\'s a gateway that'))
+ await self.reply_param(user, num, rest.format('allows connecting from an IRC client (the program that'))
+ await self.reply_param(user, num, rest.format('you are [probably] using right now) to the Telegram instant'))
+ await self.reply_param(user, num, rest.format('messaging network as a regular user account (not bot)'))
+ await self.reply_param(user, num, rest.format(''))
+ await self.reply_param(user, num, rest.format('irgramd is an open source project that you can find on'))
+ await self.reply_param(user, num, rest.format('git repository: https://github.com/prsai/irgramd'))
+ await self.reply_param(user, num, rest.format('darcs repository: https://src.presi.org/darcs/irgramd'))
+ await self.reply(user, 'RPL_ENDOFMOTD')
+
hunk ./irc.py 237
+ def valid_nick(self, nick):
+ if len(nick) <= NICK_MAX_LENGTH and nick[0] in VALID_IRC_NICK_FIRST_CHARS:
+ for x in nick[1:]:
+ if x not in VALID_IRC_NICK_CHARS:
+ return 0
+ return 1
+ else: return 0
+
hunk ./irc.py 253
+ self.password = ''
hunk ./irc_replies.py 4
- 'RPL_WELCOME': ('001', 'Welcome to the irgramd gateway, {}'),
- 'RPL_YOURHOST': ('002', 'Your host is {}, running version irgramd-{}'),
- 'RPL_CREATED': ('003', 'This server was created {}'),
+ 'RPL_WELCOME': ('001', ':Welcome to the irgramd gateway, {}'),
+ 'RPL_YOURHOST': ('002', ':Your host is {}, running version irgramd-{}'),
+ 'RPL_CREATED': ('003', ':This server was created {}'),
hunk ./irc_replies.py 8
- 'RPL_ISUPPORT': ('005', 'CASEMAPPING=ascii CHANLIMIT=#&+: CHANTYPES=&#+ CHANMODES=,,,nt CHANNELLEN={} NICKLEN={} SAFELIST'),
- 'RPL_MOTDSTART': ('375', '- {} Message of the day - '),
- 'RPL_MOTD': ('372', '{}'),
+ 'RPL_ISUPPORT': ('005', 'CASEMAPPING=ascii CHANLIMIT=#&+: CHANTYPES=&#+ CHANMODES=,,,nt CHANNELLEN={} NICKLEN={} SAFELIST :are supported by this server'),
+ 'RPL_MOTDSTART': ('375', ':- {} Message of the day - '),
+ 'RPL_MOTD': ('372', ':- {}'),
hunk ./telegram.py 6
+# Local modules
+
+from include import CHAN_MAX_LENGHT, NICK_MAX_LENGTH
+
hunk ./telegram.py 18
-NICK_MAX_LENGTH = 20
patch c95caa1ca215824f0ccf22028210a8da84283df7
Author: E. Bosch <presidev@AT@gmail.com>
Date: Mon Jan 25 00:46:43 CET 2021
* irc: Correct RPL_MYINFO message
hunk ./irc_replies.py 7
- 'RPL_MYINFO': ('004', '{} ircgramd {} o nt'),
+ 'RPL_MYINFO': ('004', '{} irgramd-{} o nt'),
patch 9ab303525274bdb6251d6f8f4612f9ef7d222c37
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Jan 24 23:41:00 CET 2021
* irc: Correct RPL_YOURHOST message
hunk ./irc_replies.py 5
- 'RPL_YOURHOST': ('002', 'Host {} is running irgramd version {}'),
+ 'RPL_YOURHOST': ('002', 'Your host is {}, running version irgramd-{}'),
patch 1e0f59717da4cd1b659c91ef4b75715620d33325
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Jan 24 03:31:52 CET 2021
* irc: Improve parsing of IRC commands
Add IRC replies/errors numerics
hunk ./irc.py 13
+from irc_replies import irc_codes
hunk ./irc.py 17
-PREFIX = r'(:[^ ]+ +|)'
-IRC_NICK_RX = re.compile(PREFIX + r'NICK +(:|)(?P<nick>[^\n\r]+)')
-IRC_PASS_RX = re.compile(PREFIX + r'PASS +(:|)(?P<password>[^\n\r]+)')
-IRC_PING_RX = re.compile(PREFIX + r'PING +(:|)(?P<payload>[^\n\r]+)')
-IRC_PRIVMSG_RX = re.compile(PREFIX + r'PRIVMSG +(?P<nick>[^ ]+) +(:|):(?P<message>[^\n\r]+)')
-IRC_USER_RX = re.compile(PREFIX + r'USER +(?P<username>[^ ]+) +[^ ]+ +[^ ]+ +(:|)(?P<realname>[^\n\r]+)')
-IRC_JOIN_RX = re.compile(PREFIX + r'JOIN +(?P<channel>[^ ]+)')
+PREFIX = r'(:[^ ]+ +|)'
+IRC_NICK_RX = re.compile(PREFIX + r'NICK( +:| +|\n)(?P<nick>[^\n]+|)')
+IRC_PASS_RX = re.compile(PREFIX + r'PASS( +:| +|\n)(?P<password>[^\n]+|)')
+IRC_PING_RX = re.compile(PREFIX + r'PING( +:| +|\n)(?P<payload>[^\n]+|)')
+IRC_PRIVMSG_RX = re.compile(PREFIX + r'PRIVMSG( +|\n)(?P<nick>[^ ]+)( +:| +|\n)(?P<message>[^\n]+|)')
+IRC_USER_RX = re.compile(PREFIX + r'USER( +|\n)(?P<username>[^ ]+) +[^ ]+ +[^ ]+( +:| +|\n)(?P<realname>[^\n]+|)')
+IRC_JOIN_RX = re.compile(PREFIX + r'JOIN( +|\n)(?P<channel>[^ ]+)')
hunk ./irc.py 47
- message = message.decode().rstrip()
+ message = message.decode()
hunk ./irc.py 49
-
- for pattern, handler in self.irc_handlers:
+ matched = False
+
+ for pattern, handler, register_required in self.irc_handlers:
hunk ./irc.py 54
- await handler(user, **matches.groupdict())
+ matched = True
+ if user.registered or not register_required:
+ params = matches.groupdict()
+ num_params = len([x for x in params.values() if x])
+ num_params_expected = len(params.keys())
+ if num_params >= num_params_expected:
+ await handler(user, **params)
+ else:
+ await self.reply(user, 'ERR_NEEDMOREPARAMS')
+
+ if not matched and user.registered:
+ await self.reply(user, 'ERR_UNKNOWNCOMMAND')
hunk ./irc.py 74
- (IRC_NICK_RX , self.handle_irc_nick),
- (IRC_PASS_RX , self.handle_irc_pass),
- (IRC_PING_RX , self.handle_irc_ping),
- (IRC_PRIVMSG_RX, self.handle_irc_privmsg),
- (IRC_USER_RX , self.handle_irc_user),
- (IRC_JOIN_RX , self.handle_irc_join),
+ # pattern handle register_required
+ (IRC_NICK_RX, self.handle_irc_nick, False),
+ (IRC_PASS_RX, self.handle_irc_pass, False),
+ (IRC_PING_RX, self.handle_irc_ping, False),
+ (IRC_PRIVMSG_RX, self.handle_irc_privmsg, True),
+ (IRC_USER_RX, self.handle_irc_user, False),
+ (IRC_JOIN_RX, self.handle_irc_join, True),
hunk ./irc.py 139
+ # IRC functions
+
+ async def reply(self, user, code):
+ num, tail = irc_codes[code]
+ await self.send_irc_command(user, ':{} {} {} :{}'.format(
+ self.hostname, num, user.irc_nick, tail
+ ))
+
hunk ./irc.py 187
+ self.registered = True
+ self.recv_pass = ''
addfile ./irc_replies.py
hunk ./irc_replies.py 1
+
+irc_codes = \
+{
+ 'RPL_WELCOME': ('001', 'Welcome to the irgramd gateway, {}'),
+ 'RPL_YOURHOST': ('002', 'Host {} is running irgramd version {}'),
+ 'RPL_CREATED': ('003', 'This server was created {}'),
+ 'RPL_MYINFO': ('004', '{} ircgramd {} o nt'),
+ 'RPL_ISUPPORT': ('005', 'CASEMAPPING=ascii CHANLIMIT=#&+: CHANTYPES=&#+ CHANMODES=,,,nt CHANNELLEN={} NICKLEN={} SAFELIST'),
+ 'RPL_MOTDSTART': ('375', '- {} Message of the day - '),
+ 'RPL_MOTD': ('372', '{}'),
+ 'RPL_ENDOFMOTD': (376, 'End of MOTD command'),
+ 'ERR_NOSUCHNICK': ('401', 'No such nick'),
+ 'ERR_NOSUCHSERVER': ('402', 'No such server'),
+ 'ERR_NOSUCHCHANNEL': ('403', 'No such channel'),
+ 'ERR_CANNOTSENDTOCHAN': ('404', 'Cannot send to channel'),
+ 'ERR_TOOMANYCHANNELS': ('405', 'Too many channels'),
+ 'ERR_WASNOSUCHNICK': ('406', 'There was no such nick'),
+ 'ERR_TOOMANYTARGETS': ('407', 'Too many targets'),
+ 'ERR_NOORIGIN': ('409', 'No origin present'),
+ 'ERR_NORECIPIENT': ('411', 'No recipient'),
+ 'ERR_NOTEXTTOSEND': ('412', 'No text to send'),
+ 'ERR_NOTOPLEVEL': ('413', 'No top level domain'),
+ 'ERR_WILDTOPLEVEL': ('414', 'Wild top level domain'),
+ 'ERR_UNKNOWNCOMMAND': ('421', 'Unknown command'),
+ 'ERR_NOMOTD': ('422', 'No MOTD'),
+ 'ERR_NOADMININFO': ('423', 'No admin info'),
+ 'ERR_FILEERROR': ('424', 'File error'),
+ 'ERR_NONICKNAMEGIVEN': ('431', 'No nickname given'),
+ 'ERR_ERRONEUSNICKNAME': ('432', 'Erroneus nickname'),
+ 'ERR_NICKNAMEINUSE': ('433', 'Nickname in use'),
+ 'ERR_NICKCOLLISION': ('436', 'Nickname collision'),
+ 'ERR_USERNOTINCHANNEL': ('441', 'User not in channel'),
+ 'ERR_NOTONCHANNEL': ('442', 'Not on channel'),
+ 'ERR_USERONCHANNEL': ('443', 'User on channel'),
+ 'ERR_NOLOGIN': ('444', 'No login'),
+ 'ERR_SUMMONDISABLED': ('445', 'Summon disabled'),
+ 'ERR_USERSDISABLED': ('446', 'Users disabled'),
+ 'ERR_NOTREGISTERED': ('451', 'Not registered'),
+ 'ERR_NEEDMOREPARAMS': ('461', 'Need more params'),
+ 'ERR_ALREADYREGISTRED': ('462', 'Already registered'),
+ 'ERR_NOPERMFORHOST': ('463', 'Insufficient permissions for host'),
+ 'ERR_PASSWDMISMATCH': ('464', 'Password mismatch'),
+ 'ERR_YOUREBANNEDCREEP': ('465', 'You\'re banned, creep'),
+ 'ERR_KEYSET': ('467', 'Key set'),
+ 'ERR_CHANNELISFULL': ('471', 'Channel is full'),
+ 'ERR_UNKNOWNMODE': ('472', 'Unknown mode'),
+ 'ERR_INVITEONLYCHAN': ('473', 'Invite only channel'),
+ 'ERR_BANNEDFROMCHAN': ('474', 'Banned from channel'),
+ 'ERR_BADCHANNELKEY': ('475', 'Bad channel key'),
+ 'ERR_NOPRIVILEGES': ('481', 'No privileges'),
+ 'ERR_CHANOPRIVSNEEDED': ('482', 'Channel +o privileges needed'),
+ 'ERR_CANTKILLSERVER': ('483', 'Cannot kill server'),
+ 'ERR_NOOPERHOST': ('491', 'No operator host'),
+ 'ERR_UMODEUNKNOWNFLAG': ('501', 'User mode unknown flag'),
+ 'ERR_USERSDONTMATCH': ('502', 'Users don\'t match'),
+}
patch 615f063fdfeb239ec0ce302102ba69c73ebfe4c4
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Jan 20 01:13:37 CET 2021
* irc: Add support for multiple irc connections (users) with new IRCUser class
hunk ./irc.py 32
+ self.users = []
hunk ./irc.py 39
- self.stream = stream
- self.address = '{}:{}'.format(address[0], address[1])
-
- self.logger.debug('Running client connection from %s', self.address)
+ user = IRCUser(stream, address)
+ self.users.append(user)
+
+ self.logger.debug('Running client connection from %s', user.address)
hunk ./irc.py 45
- message = await self.stream.read_until(b'\n')
+ message = await user.stream.read_until(b'\n')
hunk ./irc.py 52
- await handler(**matches.groupdict())
+ await handler(user, **matches.groupdict())
hunk ./irc.py 70
- self.irc_nick = None
hunk ./irc.py 74
- async def send_irc_command(self, command):
+ async def send_irc_command(self, user, command):
hunk ./irc.py 77
- self.stream.write(command.encode())
-
- async def handle_irc_nick(self, nick):
+ user.stream.write(command.encode())
+
+ async def handle_irc_nick(self, user, nick):
hunk ./irc.py 82
- if self.irc_nick in self.iid_to_tid:
- tid = self.iid_to_tid[self.irc_nick]
- self.tg.tid_to_iid[tid] = nick
+ if user.irc_nick in self.iid_to_tid:
+ tid = self.iid_to_tid[user.irc_nick]
+ self.tg.tid_to_iid[tid] = nick
hunk ./irc.py 87
- self.irc_nick = nick
-
- async def handle_irc_user(self, username, realname):
+ user.irc_nick = nick
+
+ async def handle_irc_user(self, user, username, realname):
hunk ./irc.py 92
- self.irc_nick = username
-
- await self.send_irc_command(':{} 001 {} :{}'.format(
- self.hostname, self.irc_nick, 'Welcome to irgramd'
+ user.irc_username = username
+ user.irc_realname = realname
+
+ await self.send_irc_command(user, ':{} 001 {} :{}'.format(
+ self.hostname, user.irc_nick, 'Welcome to irgramd'
hunk ./irc.py 98
- await self.send_irc_command(':{} 376 {} :{}'.format(
- self.hostname, self.irc_nick, 'End of MOTD command'
+ await self.send_irc_command(user, ':{} 376 {} :{}'.format(
+ self.hostname, user.irc_nick, 'End of MOTD command'
hunk ./irc.py 102
- async def handle_irc_join(self, channel):
+ async def handle_irc_join(self, user, channel):
hunk ./irc.py 105
- await self.join_irc_channel(self.irc_nick, channel, True)
-
- async def handle_irc_pass(self, app_id, app_hash):
+ await self.join_irc_channel(user, channel, True)
+
+ async def handle_irc_pass(self, user, app_id, app_hash):
hunk ./irc.py 110
- async def handle_irc_ping(self, payload):
+ async def handle_irc_ping(self, user, payload):
hunk ./irc.py 112
- await self.send_irc_command(':{} PONG {} :{}'.format(
+ await self.send_irc_command(user, ':{} PONG {} :{}'.format(
hunk ./irc.py 116
- async def handle_irc_privmsg(self, nick, message):
+ async def handle_irc_privmsg(self, user, nick, message):
hunk ./irc.py 125
- async def join_irc_channel(self, nick, channel, full_join=False):
- self.irc_channels[channel].add(nick)
+ async def join_irc_channel(self, user, channel, full_join=False):
+ self.irc_channels[channel].add(user.irc_nick)
hunk ./irc.py 129
- await self.send_irc_command(':{} JOIN :{}'.format(
- self.get_irc_user_mask(nick), channel
+ await self.send_irc_command(user, ':{} JOIN :{}'.format(
+ self.get_irc_user_mask(user.irc_nick), channel
hunk ./irc.py 142
- await self.send_irc_command(':{} TOPIC {} :{}'.format(
- self.get_irc_user_mask(nick), channel, topic
+ await self.send_irc_command(user, ':{} TOPIC {} :{}'.format(
+ self.get_irc_user_mask(user.irc_nick), channel, topic
hunk ./irc.py 148
- await self.send_irc_command(':{} 353 {} = {} :{}'.format(
- self.hostname, self.irc_nick, channel, ' '.join(chunk)
+ await self.send_irc_command(user, ':{} 353 {} = {} :{}'.format(
+ self.hostname, user.irc_nick, channel, ' '.join(chunk)
hunk ./irc.py 152
- async def part_irc_channel(self, nick, channel):
- self.irc_channels[channel].remove(nick)
- await self.send_irc_command(':{} PART {} :'.format(
- self.get_irc_user_mask(nick), channel
+ async def part_irc_channel(self, user, channel):
+ self.irc_channels[channel].remove(user.irc_nick)
+ await self.send_irc_command(user, ':{} PART {} :'.format(
+ self.get_irc_user_mask(user.irc_nick), channel
hunk ./irc.py 157
+
+class IRCUser(object):
+ def __init__(self, stream, address):
+ self.stream = stream
+ self.address = '{}:{}'.format(address[0], address[1])
+ self.irc_nick = None
+ self.irc_username = None
+ self.irc_realname = None
hunk ./telegram.py 120
- await self.irc.send_irc_command(':{} PRIVMSG {} :{}'.format(
- self.irc.get_irc_user_mask(nick), self.irc.irc_nick, message
- ))
+ for user in self.irc.users:
+ await self.irc.send_irc_command(user, ':{} PRIVMSG {} :{}'.format(
+ self.irc.get_irc_user_mask(nick), user.irc_nick, message
+ ))
hunk ./telegram.py 149
- await self.irc.send_irc_command(':{} PRIVMSG {} :{}'.format(
- self.irc.get_irc_user_mask(nick), channel, message
- ))
+ for user in self.irc.users:
+ await self.irc.send_irc_command(user, ':{} PRIVMSG {} :{}'.format(
+ self.irc.get_irc_user_mask(nick), channel, message
+ ))
patch 2f62e5c9ccde4b855ae31ef552f6dbcc552ea9ba
Author: E. Bosch <presidev@AT@gmail.com>
Date: Wed Jan 20 01:09:41 CET 2021
* irc: Fix PONG response
hunk ./irc.py 111
- await self.send_irc_command(':{} PONG :{}'.format(
- self.hostname, payload
+ await self.send_irc_command(':{} PONG {} :{}'.format(
+ self.hostname, self.hostname, payload
patch ba3e7dc07a05acfd34ca1f04ce176d0e1e48a03f
Author: E. Bosch <presidev@AT@gmail.com>
Date: Tue Nov 24 23:25:57 CET 2020
* irc: Improve the regular expresions for command parsing
hunk ./irc.py 16
-IRC_NICK_RX = re.compile(r'NICK :(?P<nick>[^\n\r]+)')
-IRC_PASS_RX = re.compile(r'PASS :(?P<app_id>[^\s]+) (?P<app_hash>[^\n\r]+)')
-IRC_PING_RX = re.compile(r'PING (?P<payload>[^\n\r]+)')
-IRC_PRIVMSG_RX = re.compile(r'PRIVMSG (?P<nick>[^\s]+) :(?P<message>[^\n\r]+)')
-IRC_USER_RX = re.compile(r'USER (?P<username>[^\s]+) [^\s]+ [^\s]+ :(?P<realname>[^\n\r]+)')
-IRC_JOIN_RX = re.compile(r'JOIN (?P<channel>[^\s]+)')
+PREFIX = r'(:[^ ]+ +|)'
+IRC_NICK_RX = re.compile(PREFIX + r'NICK +(:|)(?P<nick>[^\n\r]+)')
+IRC_PASS_RX = re.compile(PREFIX + r'PASS +(:|)(?P<password>[^\n\r]+)')
+IRC_PING_RX = re.compile(PREFIX + r'PING +(:|)(?P<payload>[^\n\r]+)')
+IRC_PRIVMSG_RX = re.compile(PREFIX + r'PRIVMSG +(?P<nick>[^ ]+) +(:|):(?P<message>[^\n\r]+)')
+IRC_USER_RX = re.compile(PREFIX + r'USER +(?P<username>[^ ]+) +[^ ]+ +[^ ]+ +(:|)(?P<realname>[^\n\r]+)')
+IRC_JOIN_RX = re.compile(PREFIX + r'JOIN +(?P<channel>[^ ]+)')
patch 4e31d63e717768dbe39a3808f54b9e12627a687b
Author: E. Bosch <presidev@AT@gmail.com>
Date: Tue Nov 24 21:11:50 CET 2020
* Remove voices functionality
hunk ./irc.py 14
-# Configuration
-
-UPDATE_CHANNEL_VOICES_DELAY = 300
-
hunk ./irc.py 69
- self.irc_voices = collections.defaultdict(set)
hunk ./irc.py 136
- nicks, voices = await self.tg.get_telegram_channel_participants(tid)
+ nicks = await self.tg.get_telegram_channel_participants(tid)
hunk ./irc.py 150
- # Update voices
- await self.update_channel_voices(channel, voices)
-
hunk ./irc.py 155
-
- async def update_channel_voices(self, channel, voices=None):
- # Get voices for channel if not provided
- if not voices:
- tid = self.iid_to_tid[channel]
- _, voices = await self.tg.get_telegram_channel_participants(tid)
-
- # Add new voices
- for nick in voices:
- if nick not in self.irc_voices[channel]:
- self.irc_voices[channel].add(nick)
- await self.send_irc_command(':{} MODE {} +v {}'.format(
- self.hostname, channel, nick,
- ))
-
- # Remove old voices
- for nick in self.irc_voices[channel].difference(voices):
- self.irc_voices[channel].remove(nick)
- await self.send_irc_command(':{} MODE {} -v {}'.format(
- self.hostname, channel, nick,
- ))
-
- self.ioloop.call_later(UPDATE_CHANNEL_VOICES_DELAY, self.update_channel_voices, channel)
-
hunk ./telegram.py 99
- voices = set()
hunk ./telegram.py 102
- if isinstance(user.status, telethon.types.UserStatusOnline):
- voices.add(user_nick)
-
hunk ./telegram.py 105
- return nicks, voices
+ return nicks
patch c154ded080ea797bb2188ea8318b54f7127eeb91
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sun Nov 22 23:57:38 CET 2020
* Change the point where irgramd connects to Telegram
Before: it connected to Telegram on connection from IRC
Now: it connects to Telegram on irgramd startup and keeps
connected even if there is no user connected to IRC so
works more like a bouncer
The autojoin function is disabled and join command
implemented
hunk ./irc.py 13
-from telegram import TelegramHandler
hunk ./irc.py 25
+IRC_JOIN_RX = re.compile(r'JOIN (?P<channel>[^\s]+)')
hunk ./irc.py 30
- def __init__(self, stream, address, config_dir):
+ def __init__(self, config_dir):
hunk ./irc.py 32
- self.address = '{}:{}'.format(address[0], address[1])
- self.stream = stream
hunk ./irc.py 35
- self.tg = TelegramHandler(self, config_dir)
hunk ./irc.py 39
- self.logger.debug('Established client connection from %s', self.address)
-
- async def run(self):
+
+ async def run(self, stream, address):
+ self.stream = stream
+ self.address = '{}:{}'.format(address[0], address[1])
+
hunk ./irc.py 56
+ def set_telegram(self, tg):
+ self.tg = tg
+
hunk ./irc.py 68
+ (IRC_JOIN_RX , self.handle_irc_join),
hunk ./irc.py 105
- await self.tg.initialize_telegram()
+ async def handle_irc_join(self, channel):
+ self.logger.debug('Handling JOIN: %s', channel)
+
+ await self.join_irc_channel(self.irc_nick, channel, True)
hunk ./irc.py 112
- self.tg.telegram_app_id = int(app_id)
- self.tg.telegram_app_hash = app_hash
hunk ./irgramd 10
-import telethon
-
hunk ./irgramd 12
-from irc import *
-
+from irc import IRCHandler
+from telegram import TelegramHandler
hunk ./irgramd 25
+ self.irc_handler = None
+ self.tg_handler = None
hunk ./irgramd 32
- handler = IRCHandler(stream, address, self.config_dir)
- await handler.run()
+ await self.irc_handler.run(stream, address)
hunk ./irgramd 39
+ self.irc_handler = IRCHandler(self.config_dir)
+ self.tg_handler = TelegramHandler(self.irc_handler, self.config_dir)
+ self.irc_handler.set_telegram(self.tg_handler)
+ self.tg_handler.initialize_telegram()
+
hunk ./telegram.py 8
+# GET API_ID and API_HASH from https://my.telegram.org/apps
+# AND PUT HERE BEFORE RUNNING irgramd
+
+TELEGRAM_API_ID =
+TELEGRAM_API_HASH = ''
+
hunk ./telegram.py 23
- self.telegram_app_id = ''
- self.telegram_app_hash = ''
-
- async def initialize_telegram(self):
+
+ def initialize_telegram(self):
hunk ./telegram.py 36
- telegram_session = os.path.join(self.telegram_session_dir, self.irc.irc_nick)
+ telegram_session = os.path.join(self.telegram_session_dir, 'telegram')
hunk ./telegram.py 38
- self.telegram_app_id, # TODO: handle error
- self.telegram_app_hash,
+ TELEGRAM_API_ID, TELEGRAM_API_HASH
hunk ./telegram.py 53
- await self.telegram_client.start()
-
+ self.telegram_client.start()
hunk ./telegram.py 55
- telegram_me = await self.telegram_client.get_me()
- iid = self.irc.irc_nick
- tid = telegram_me.id
- self.tid_to_iid[tid] = iid
- self.irc.iid_to_tid[iid] = tid
-
- # Join all Telegram channels
- await self.join_all_telegram_channels()
+ for dialog in self.telegram_client.iter_dialogs():
+ chat = dialog.entity
+ if isinstance(chat, telethon.types.User):
+ user = self.get_telegram_nick(chat)
+ self.tid_to_iid[chat.id] = user
+ self.irc.iid_to_tid[user] = chat.id
+ else:
+ channel = self.get_telegram_channel(chat)
+ self.tid_to_iid[chat.id] = channel
+ self.irc.iid_to_tid[channel] = chat.id
patch 142e3a255d4ab531762366c15a2d4234d0c39be3
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Nov 21 03:02:46 CET 2020
* Fix attributes telegram_app_id and telegram_app_hash
hunk ./telegram.py 17
+ self.telegram_app_id = ''
+ self.telegram_app_hash = ''
patch cc0cef88f81e552b8196cf0a9dc9548ef8ebe4dc
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Nov 21 02:12:30 CET 2020
* Put class TelegramHandler in a separated file
hunk ./irc.py 4
-import os
hunk ./irc.py 10
-import telethon
-
hunk ./irc.py 13
+from telegram import TelegramHandler
hunk ./irc.py 17
-NICK_MAX_LENGTH = 20
hunk ./irc.py 183
- # Telegram
-
-class TelegramHandler(object):
- def __init__(self, irc, config_dir):
- self.logger = logging.getLogger()
- self.config_dir = config_dir
- self.irc = irc
-
- async def initialize_telegram(self):
- # Setup media folder
- self.telegram_media_dir = os.path.join(self.config_dir, 'media')
- if not os.path.exists(self.telegram_media_dir):
- os.makedirs(self.telegram_media_dir)
-
- # Setup session folder
- self.telegram_session_dir = os.path.join(self.config_dir, 'session')
- if not os.path.exists(self.telegram_session_dir):
- os.makedirs(self.telegram_session_dir)
-
- # Construct Telegram client
- telegram_session = os.path.join(self.telegram_session_dir, self.irc.irc_nick)
- self.telegram_client = telethon.TelegramClient(telegram_session,
- self.telegram_app_id, # TODO: handle error
- self.telegram_app_hash,
- )
-
- # Initialize Telegram ID to IRC nick mapping
- self.tid_to_iid = {}
-
- # Register Telegram callbacks
- callbacks = (
- (self.handle_telegram_message , telethon.events.NewMessage),
- (self.handle_telegram_chat_action, telethon.events.ChatAction),
- )
- for handler, event in callbacks:
- self.telegram_client.add_event_handler(handler, event)
-
- # Start Telegram client
- await self.telegram_client.start()
-
- # Update IRC <-> Telegram mapping
- telegram_me = await self.telegram_client.get_me()
- iid = self.irc.irc_nick
- tid = telegram_me.id
- self.tid_to_iid[tid] = iid
- self.irc.iid_to_tid[iid] = tid
-
- # Join all Telegram channels
- await self.join_all_telegram_channels()
-
- def get_telegram_nick(self, user):
- nick = (user.username
- or telethon.utils.get_display_name(user)
- or str(user.id))
- nick = nick.replace(' ', '')[:NICK_MAX_LENGTH]
- while nick in self.irc.iid_to_tid:
- nick += '_'
- return nick
-
- def get_telegram_channel(self, chat):
- return '#' + chat.title.lower().replace(' ', '-')
-
- async def get_irc_nick_from_telegram_id(self, tid, entity=None):
- if tid not in self.tid_to_iid:
- user = entity or await self.telegram_client.get_entity(tid)
- nick = self.get_telegram_nick(user)
- self.tid_to_iid[tid] = nick
- self.irc.iid_to_tid[nick] = tid
-
- return self.tid_to_iid[tid]
-
- async def get_irc_channel_from_telegram_id(self, tid, entity=None):
- if tid not in self.tid_to_iid:
- chat = entity or await self.telegram_client.get_entity(tid)
- channel = self.get_telegram_channel(chat)
- self.tid_to_iid[tid] = channel
- self.irc.iid_to_tid[channel] = tid
-
- return self.tid_to_iid[tid]
-
- async def get_telegram_channel_participants(self, tid):
- channel = self.tid_to_iid[tid]
- nicks = []
- voices = set()
- async for user in self.telegram_client.iter_participants(tid):
- user_nick = await self.get_irc_nick_from_telegram_id(user.id, user)
-
- if isinstance(user.status, telethon.types.UserStatusOnline):
- voices.add(user_nick)
-
- nicks.append(user_nick)
- self.irc.irc_channels[channel].add(user_nick)
-
- return nicks, voices
-
- async def handle_telegram_message(self, event):
- self.logger.debug('Handling Telegram Message: %s', event)
-
- if event.message.is_private:
- await self.handle_telegram_private_message(event)
- else:
- await self.handle_telegram_channel_message(event)
-
- async def handle_telegram_private_message(self, event):
- self.logger.debug('Handling Telegram Private Message: %s', event)
-
- nick = await self.get_irc_nick_from_telegram_id(event.sender_id)
- for message in event.message.message.splitlines():
- await self.irc.send_irc_command(':{} PRIVMSG {} :{}'.format(
- self.irc.get_irc_user_mask(nick), self.irc.irc_nick, message
- ))
-
- async def handle_telegram_channel_message(self, event):
- self.logger.debug('Handling Telegram Channel Message: %s', event)
-
- # Join IRC channel if not already in it
- entity = await event.message.get_chat()
- channel = await self.get_irc_channel_from_telegram_id(event.message.chat_id, entity)
- if channel not in self.irc.irc_channels:
- await self.irc.join_irc_channel(self.irc.irc_nick, channel, True)
-
- nick = await self.get_irc_nick_from_telegram_id(event.sender_id)
- if nick not in self.irc.irc_channels[channel]:
- await self.irc.join_irc_channel(nick, channel, False)
-
- # Format messages with media
- messages = event.message.message.splitlines() if event.message.message else []
- if event.message.media and (event.message.photo or event.message.gif):
- message = await self.download_telegram_media(event.message, 'Image')
- if message:
- messages.insert(0, message)
- elif event.message.media and (event.message.sticker):
- messages.insert(0, 'Sticker: {}'.format(event.message.sticker.id))
-
- # Send all messages to IRC
- for message in messages:
- await self.irc.send_irc_command(':{} PRIVMSG {} :{}'.format(
- self.irc.get_irc_user_mask(nick), channel, message
- ))
-
- async def handle_telegram_chat_action(self, event):
- self.logger.debug('Handling Telegram Chat Action: %s', event)
-
- try:
- tid = event.action_message.to_id.channel_id
- except AttributeError:
- tid = event.action_message.to_id.chat_id
- finally:
- irc_channel = await self.get_irc_channel_from_telegram_id(tid)
- await self.get_telegram_channel_participants(tid)
-
- try: # Join Chats
- irc_nick = await self.get_irc_nick_from_telegram_id(event.action_message.action.users[0])
- except (IndexError, AttributeError):
- try: # Kick
- irc_nick = await self.get_irc_nick_from_telegram_id(event.action_message.action.user_id)
- except (IndexError, AttributeError): # Join Channels
- irc_nick = await self.get_irc_nick_from_telegram_id(event.action_message.sender_id)
-
- if event.user_added or event.user_joined:
- await self.irc.join_irc_channel(irc_nick, irc_channel, False)
- elif event.user_kicked or event.user_left:
- await self.irc.part_irc_channel(irc_nick, irc_channel)
-
- async def join_all_telegram_channels(self):
- async for dialog in self.telegram_client.iter_dialogs():
- chat = dialog.entity
- if not isinstance(chat, telethon.types.User):
- channel = self.get_telegram_channel(chat)
- self.tid_to_iid[chat.id] = channel
- self.irc.iid_to_tid[channel] = chat.id
- await self.irc.join_irc_channel(self.irc.irc_nick, channel, True)
-
- async def download_telegram_media(self, message, tag):
- local_path = await message.download_media(self.telegram_media_dir)
- if not local_path:
- return
-
- request = tornado.httpclient.HTTPRequest(
- url = 'https://yld.me/paste',
- method = 'POST',
- body = open(local_path, 'rb').read(),
- )
- response = await tornado.httpclient.AsyncHTTPClient().fetch(request)
-
- os.unlink(local_path)
- return tag + ': ' + response.body.decode().strip()
addfile ./telegram.py
hunk ./telegram.py 1
+
+import logging
+import os
+import telethon
+
+# Configuration
+
+NICK_MAX_LENGTH = 20
+
+ # Telegram
+
+class TelegramHandler(object):
+ def __init__(self, irc, config_dir):
+ self.logger = logging.getLogger()
+ self.config_dir = config_dir
+ self.irc = irc
+
+ async def initialize_telegram(self):
+ # Setup media folder
+ self.telegram_media_dir = os.path.join(self.config_dir, 'media')
+ if not os.path.exists(self.telegram_media_dir):
+ os.makedirs(self.telegram_media_dir)
+
+ # Setup session folder
+ self.telegram_session_dir = os.path.join(self.config_dir, 'session')
+ if not os.path.exists(self.telegram_session_dir):
+ os.makedirs(self.telegram_session_dir)
+
+ # Construct Telegram client
+ telegram_session = os.path.join(self.telegram_session_dir, self.irc.irc_nick)
+ self.telegram_client = telethon.TelegramClient(telegram_session,
+ self.telegram_app_id, # TODO: handle error
+ self.telegram_app_hash,
+ )
+
+ # Initialize Telegram ID to IRC nick mapping
+ self.tid_to_iid = {}
+
+ # Register Telegram callbacks
+ callbacks = (
+ (self.handle_telegram_message , telethon.events.NewMessage),
+ (self.handle_telegram_chat_action, telethon.events.ChatAction),
+ )
+ for handler, event in callbacks:
+ self.telegram_client.add_event_handler(handler, event)
+
+ # Start Telegram client
+ await self.telegram_client.start()
+
+ # Update IRC <-> Telegram mapping
+ telegram_me = await self.telegram_client.get_me()
+ iid = self.irc.irc_nick
+ tid = telegram_me.id
+ self.tid_to_iid[tid] = iid
+ self.irc.iid_to_tid[iid] = tid
+
+ # Join all Telegram channels
+ await self.join_all_telegram_channels()
+
+ def get_telegram_nick(self, user):
+ nick = (user.username
+ or telethon.utils.get_display_name(user)
+ or str(user.id))
+ nick = nick.replace(' ', '')[:NICK_MAX_LENGTH]
+ while nick in self.irc.iid_to_tid:
+ nick += '_'
+ return nick
+
+ def get_telegram_channel(self, chat):
+ return '#' + chat.title.lower().replace(' ', '-')
+
+ async def get_irc_nick_from_telegram_id(self, tid, entity=None):
+ if tid not in self.tid_to_iid:
+ user = entity or await self.telegram_client.get_entity(tid)
+ nick = self.get_telegram_nick(user)
+ self.tid_to_iid[tid] = nick
+ self.irc.iid_to_tid[nick] = tid
+
+ return self.tid_to_iid[tid]
+
+ async def get_irc_channel_from_telegram_id(self, tid, entity=None):
+ if tid not in self.tid_to_iid:
+ chat = entity or await self.telegram_client.get_entity(tid)
+ channel = self.get_telegram_channel(chat)
+ self.tid_to_iid[tid] = channel
+ self.irc.iid_to_tid[channel] = tid
+
+ return self.tid_to_iid[tid]
+
+ async def get_telegram_channel_participants(self, tid):
+ channel = self.tid_to_iid[tid]
+ nicks = []
+ voices = set()
+ async for user in self.telegram_client.iter_participants(tid):
+ user_nick = await self.get_irc_nick_from_telegram_id(user.id, user)
+
+ if isinstance(user.status, telethon.types.UserStatusOnline):
+ voices.add(user_nick)
+
+ nicks.append(user_nick)
+ self.irc.irc_channels[channel].add(user_nick)
+
+ return nicks, voices
+
+ async def handle_telegram_message(self, event):
+ self.logger.debug('Handling Telegram Message: %s', event)
+
+ if event.message.is_private:
+ await self.handle_telegram_private_message(event)
+ else:
+ await self.handle_telegram_channel_message(event)
+
+ async def handle_telegram_private_message(self, event):
+ self.logger.debug('Handling Telegram Private Message: %s', event)
+
+ nick = await self.get_irc_nick_from_telegram_id(event.sender_id)
+ for message in event.message.message.splitlines():
+ await self.irc.send_irc_command(':{} PRIVMSG {} :{}'.format(
+ self.irc.get_irc_user_mask(nick), self.irc.irc_nick, message
+ ))
+
+ async def handle_telegram_channel_message(self, event):
+ self.logger.debug('Handling Telegram Channel Message: %s', event)
+
+ # Join IRC channel if not already in it
+ entity = await event.message.get_chat()
+ channel = await self.get_irc_channel_from_telegram_id(event.message.chat_id, entity)
+ if channel not in self.irc.irc_channels:
+ await self.irc.join_irc_channel(self.irc.irc_nick, channel, True)
+
+ nick = await self.get_irc_nick_from_telegram_id(event.sender_id)
+ if nick not in self.irc.irc_channels[channel]:
+ await self.irc.join_irc_channel(nick, channel, False)
+
+ # Format messages with media
+ messages = event.message.message.splitlines() if event.message.message else []
+ if event.message.media and (event.message.photo or event.message.gif):
+ message = await self.download_telegram_media(event.message, 'Image')
+ if message:
+ messages.insert(0, message)
+ elif event.message.media and (event.message.sticker):
+ messages.insert(0, 'Sticker: {}'.format(event.message.sticker.id))
+
+ # Send all messages to IRC
+ for message in messages:
+ await self.irc.send_irc_command(':{} PRIVMSG {} :{}'.format(
+ self.irc.get_irc_user_mask(nick), channel, message
+ ))
+
+ async def handle_telegram_chat_action(self, event):
+ self.logger.debug('Handling Telegram Chat Action: %s', event)
+
+ try:
+ tid = event.action_message.to_id.channel_id
+ except AttributeError:
+ tid = event.action_message.to_id.chat_id
+ finally:
+ irc_channel = await self.get_irc_channel_from_telegram_id(tid)
+ await self.get_telegram_channel_participants(tid)
+
+ try: # Join Chats
+ irc_nick = await self.get_irc_nick_from_telegram_id(event.action_message.action.users[0])
+ except (IndexError, AttributeError):
+ try: # Kick
+ irc_nick = await self.get_irc_nick_from_telegram_id(event.action_message.action.user_id)
+ except (IndexError, AttributeError): # Join Channels
+ irc_nick = await self.get_irc_nick_from_telegram_id(event.action_message.sender_id)
+
+ if event.user_added or event.user_joined:
+ await self.irc.join_irc_channel(irc_nick, irc_channel, False)
+ elif event.user_kicked or event.user_left:
+ await self.irc.part_irc_channel(irc_nick, irc_channel)
+
+ async def join_all_telegram_channels(self):
+ async for dialog in self.telegram_client.iter_dialogs():
+ chat = dialog.entity
+ if not isinstance(chat, telethon.types.User):
+ channel = self.get_telegram_channel(chat)
+ self.tid_to_iid[chat.id] = channel
+ self.irc.iid_to_tid[channel] = chat.id
+ await self.irc.join_irc_channel(self.irc.irc_nick, channel, True)
+
+ async def download_telegram_media(self, message, tag):
+ local_path = await message.download_media(self.telegram_media_dir)
+ return
+ if not local_path:
+ return
+
+ request = tornado.httpclient.HTTPRequest(
+ url = 'https://yld.me/paste',
+ method = 'POST',
+ body = open(local_path, 'rb').read(),
+ )
+ response = await tornado.httpclient.AsyncHTTPClient().fetch(request)
+
+ os.unlink(local_path)
+ return tag + ': ' + response.body.decode().strip()
patch ee4d2179cb01dca538617a8da0a5e5220a590f36
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Nov 21 00:31:39 CET 2020
* Extract telegram code from class IRCHandler into class TelegramHandler
hunk ./irc.py 40
+ self.tg = TelegramHandler(self, config_dir)
hunk ./irc.py 88
- self.tid_to_iid[tid] = nick
+ self.tg.tid_to_iid[tid] = nick
hunk ./irc.py 105
- await self.initialize_telegram()
+ await self.tg.initialize_telegram()
hunk ./irc.py 109
- self.telegram_app_id = int(app_id)
- self.telegram_app_hash = app_hash
+ self.tg.telegram_app_id = int(app_id)
+ self.tg.telegram_app_hash = app_hash
hunk ./irc.py 125
- await self.telegram_client.send_message(telegram_id, message)
+ await self.tg.telegram_client.send_message(telegram_id, message)
hunk ./irc.py 140
- nicks, voices = await self.get_telegram_channel_participants(tid)
+ nicks, voices = await self.tg.get_telegram_channel_participants(tid)
hunk ./irc.py 143
- topic = (await self.telegram_client.get_entity(tid)).title
+ topic = (await self.tg.telegram_client.get_entity(tid)).title
hunk ./irc.py 167
- _, voices = await self.get_telegram_channel_participants(tid)
+ _, voices = await self.tg.get_telegram_channel_participants(tid)
hunk ./irc.py 188
+class TelegramHandler(object):
+ def __init__(self, irc, config_dir):
+ self.logger = logging.getLogger()
+ self.config_dir = config_dir
+ self.irc = irc
+
hunk ./irc.py 206
- telegram_session = os.path.join(self.telegram_session_dir, self.irc_nick)
+ telegram_session = os.path.join(self.telegram_session_dir, self.irc.irc_nick)
hunk ./irc.py 228
- iid = self.irc_nick
+ iid = self.irc.irc_nick
hunk ./irc.py 231
- self.iid_to_tid[iid] = tid
+ self.irc.iid_to_tid[iid] = tid
hunk ./irc.py 241
- while nick in self.iid_to_tid:
+ while nick in self.irc.iid_to_tid:
hunk ./irc.py 253
- self.iid_to_tid[nick] = tid
+ self.irc.iid_to_tid[nick] = tid
hunk ./irc.py 262
- self.iid_to_tid[channel] = tid
+ self.irc.iid_to_tid[channel] = tid
hunk ./irc.py 277
- self.irc_channels[channel].add(user_nick)
+ self.irc.irc_channels[channel].add(user_nick)
hunk ./irc.py 294
- await self.send_irc_command(':{} PRIVMSG {} :{}'.format(
- self.get_irc_user_mask(nick), self.irc_nick, message
+ await self.irc.send_irc_command(':{} PRIVMSG {} :{}'.format(
+ self.irc.get_irc_user_mask(nick), self.irc.irc_nick, message
hunk ./irc.py 304
- if channel not in self.irc_channels:
- await self.join_irc_channel(self.irc_nick, channel, True)
+ if channel not in self.irc.irc_channels:
+ await self.irc.join_irc_channel(self.irc.irc_nick, channel, True)
hunk ./irc.py 308
- if nick not in self.irc_channels[channel]:
- await self.join_irc_channel(nick, channel, False)
+ if nick not in self.irc.irc_channels[channel]:
+ await self.irc.join_irc_channel(nick, channel, False)
hunk ./irc.py 322
- await self.send_irc_command(':{} PRIVMSG {} :{}'.format(
- self.get_irc_user_mask(nick), channel, message
+ await self.irc.send_irc_command(':{} PRIVMSG {} :{}'.format(
+ self.irc.get_irc_user_mask(nick), channel, message
hunk ./irc.py 346
- await self.join_irc_channel(irc_nick, irc_channel, False)
+ await self.irc.join_irc_channel(irc_nick, irc_channel, False)
hunk ./irc.py 348
- await self.part_irc_channel(irc_nick, irc_channel)
+ await self.irc.part_irc_channel(irc_nick, irc_channel)
hunk ./irc.py 356
- self.iid_to_tid[channel] = chat.id
- await self.join_irc_channel(self.irc_nick, channel, True)
+ self.irc.iid_to_tid[channel] = chat.id
+ await self.irc.join_irc_channel(self.irc.irc_nick, channel, True)
patch 83c559617ebe7e914b931b4b00a3f7e2c897c99a
Author: E. Bosch <presidev@AT@gmail.com>
Date: Fri Nov 20 00:47:57 CET 2020
* Fix a incompatibility with "from_id" intruduced in Telethon 1.17
This was breaking the reception of messages
See https://docs.telethon.dev/en/latest/misc/changelog.html?highlight=from_id#channel-comments-and-anonymous-admins-v1-17
hunk ./irc.py 285
- nick = await self.get_irc_nick_from_telegram_id(event.from_id)
+ nick = await self.get_irc_nick_from_telegram_id(event.sender_id)
hunk ./irc.py 300
- nick = await self.get_irc_nick_from_telegram_id(event.from_id)
+ nick = await self.get_irc_nick_from_telegram_id(event.sender_id)
hunk ./irc.py 336
- irc_nick = await self.get_irc_nick_from_telegram_id(event.action_message.from_id)
+ irc_nick = await self.get_irc_nick_from_telegram_id(event.action_message.sender_id)
patch d689d9ba72a0680d87eca5ed23c26da5438477b5
Author: E. Bosch <presidev@AT@gmail.com>
Date: Thu Nov 19 20:54:39 CET 2020
* Improve the regular expression for USER command, make it compatible with more clients
hunk ./irc.py 28
-IRC_USER_RX = re.compile(r'USER (?P<username>[^\s]+) 0 \* :(?P<realname>[^\n\r]+)')
+IRC_USER_RX = re.compile(r'USER (?P<username>[^\s]+) [^\s]+ [^\s]+ :(?P<realname>[^\n\r]+)')
patch 1eb21f4c245c362c0c64061bfe454d931d741c52
Author: E. Bosch <presidev@AT@gmail.com>
Date: Thu Nov 19 20:52:38 CET 2020
* Replace IRTelegramd with irgramd in some places that were remaining
hunk ./irc.py 98
- self.hostname, self.irc_nick, 'Welcome to IRTelegramD'
+ self.hostname, self.irc_nick, 'Welcome to irgramd'
hunk ./irgramd 26
- self.config_dir = config_dir or os.path.expanduser('~/.config/irtelegramd')
+ self.config_dir = config_dir or os.path.expanduser('~/.config/irgramd')
hunk ./irgramd 37
- self.logger.info('IRTelegramd listening on %s:%s', self.address, self.port)
+ self.logger.info('irgramd listening on %s:%s', self.address, self.port)
patch 37280138e557593419086e9277a35fccc22785ad
Author: E. Bosch <presidev@AT@gmail.com>
Date: Thu Nov 19 20:48:09 CET 2020
* Rename irgramd.py to irgramd, to reflect it's the main file
move ./irgramd.py ./irgramd
patch 33b6416d5bd90961ab7bd1cc3b73103848f76816
Author: E. Bosch <presidev@AT@gmail.com>
Date: Thu Nov 19 20:41:24 CET 2020
* Put IRCHandler and utils in separated files
addfile ./irc.py
hunk ./irc.py 1
+
+import collections
+import logging
+import os
+import re
+import socket
+
+import tornado.httpclient
+import tornado.ioloop
+
+import telethon
+
+# Local modules
+
+from utils import chunks
+
+# Configuration
+
+NICK_MAX_LENGTH = 20
+UPDATE_CHANNEL_VOICES_DELAY = 300
+
+# IRC Regular Expressions
+
+IRC_NICK_RX = re.compile(r'NICK :(?P<nick>[^\n\r]+)')
+IRC_PASS_RX = re.compile(r'PASS :(?P<app_id>[^\s]+) (?P<app_hash>[^\n\r]+)')
+IRC_PING_RX = re.compile(r'PING (?P<payload>[^\n\r]+)')
+IRC_PRIVMSG_RX = re.compile(r'PRIVMSG (?P<nick>[^\s]+) :(?P<message>[^\n\r]+)')
+IRC_USER_RX = re.compile(r'USER (?P<username>[^\s]+) 0 \* :(?P<realname>[^\n\r]+)')
+
+# IRC Handler
+
+class IRCHandler(object):
+ def __init__(self, stream, address, config_dir):
+ self.logger = logging.getLogger()
+ self.address = '{}:{}'.format(address[0], address[1])
+ self.stream = stream
+ self.ioloop = tornado.ioloop.IOLoop.current()
+ self.hostname = socket.gethostname()
+ self.config_dir = config_dir
+
+ # Initialize IRC
+ self.initialize_irc()
+
+ self.logger.debug('Established client connection from %s', self.address)
+
+ async def run(self):
+ self.logger.debug('Running client connection from %s', self.address)
+
+ while True:
+ message = await self.stream.read_until(b'\n')
+ message = message.decode().rstrip()
+ self.logger.debug(message)
+
+ for pattern, handler in self.irc_handlers:
+ matches = pattern.match(message)
+ if matches:
+ await handler(**matches.groupdict())
+
+ # IRC
+
+ def initialize_irc(self):
+ self.irc_handlers = (
+ (IRC_NICK_RX , self.handle_irc_nick),
+ (IRC_PASS_RX , self.handle_irc_pass),
+ (IRC_PING_RX , self.handle_irc_ping),
+ (IRC_PRIVMSG_RX, self.handle_irc_privmsg),
+ (IRC_USER_RX , self.handle_irc_user),
+ )
+ self.iid_to_tid = {}
+ self.irc_channels = collections.defaultdict(set)
+ self.irc_nick = None
+ self.irc_voices = collections.defaultdict(set)
+
+ def get_irc_user_mask(self, nick):
+ return '{}!{}@{}'.format(nick, nick, self.hostname)
+
+ async def send_irc_command(self, command):
+ self.logger.debug('Send IRC Command: %s', command)
+ command = command + '\r\n'
+ self.stream.write(command.encode())
+
+ async def handle_irc_nick(self, nick):
+ self.logger.debug('Handling NICK: %s', nick)
+
+ if self.irc_nick in self.iid_to_tid:
+ tid = self.iid_to_tid[self.irc_nick]
+ self.tid_to_iid[tid] = nick
+ self.iid_to_tid[nick] = tid
+
+ self.irc_nick = nick
+
+ async def handle_irc_user(self, username, realname):
+ self.logger.debug('Handling USER: %s, %s', username, realname)
+
+ self.irc_nick = username
+
+ await self.send_irc_command(':{} 001 {} :{}'.format(
+ self.hostname, self.irc_nick, 'Welcome to IRTelegramD'
+ ))
+ await self.send_irc_command(':{} 376 {} :{}'.format(
+ self.hostname, self.irc_nick, 'End of MOTD command'
+ ))
+
+ await self.initialize_telegram()
+
+ async def handle_irc_pass(self, app_id, app_hash):
+ self.logger.debug('Handling PASS: %s %s', app_id, app_hash)
+ self.telegram_app_id = int(app_id)
+ self.telegram_app_hash = app_hash
+
+ async def handle_irc_ping(self, payload):
+ self.logger.debug('Handling PING: %s', payload)
+ await self.send_irc_command(':{} PONG :{}'.format(
+ self.hostname, payload
+ ))
+
+ async def handle_irc_privmsg(self, nick, message):
+ self.logger.debug('Handling PRIVMSG: %s, %s', nick, message)
+
+ if nick not in self.iid_to_tid:
+ print('TODO: handle error')
+
+ telegram_id = self.iid_to_tid[nick]
+ await self.telegram_client.send_message(telegram_id, message)
+
+ async def join_irc_channel(self, nick, channel, full_join=False):
+ self.irc_channels[channel].add(nick)
+
+ # Join Channel
+ await self.send_irc_command(':{} JOIN :{}'.format(
+ self.get_irc_user_mask(nick), channel
+ ))
+
+ if not full_join:
+ return
+
+ # Add all users to channel
+ tid = self.iid_to_tid[channel]
+ nicks, voices = await self.get_telegram_channel_participants(tid)
+
+ # Set channel topic
+ topic = (await self.telegram_client.get_entity(tid)).title
+ await self.send_irc_command(':{} TOPIC {} :{}'.format(
+ self.get_irc_user_mask(nick), channel, topic
+ ))
+
+ # Send NAMESLIST
+ for chunk in chunks(nicks, 25, ''):
+ await self.send_irc_command(':{} 353 {} = {} :{}'.format(
+ self.hostname, self.irc_nick, channel, ' '.join(chunk)
+ ))
+
+ # Update voices
+ await self.update_channel_voices(channel, voices)
+
+ async def part_irc_channel(self, nick, channel):
+ self.irc_channels[channel].remove(nick)
+ await self.send_irc_command(':{} PART {} :'.format(
+ self.get_irc_user_mask(nick), channel
+ ))
+
+ async def update_channel_voices(self, channel, voices=None):
+ # Get voices for channel if not provided
+ if not voices:
+ tid = self.iid_to_tid[channel]
+ _, voices = await self.get_telegram_channel_participants(tid)
+
+ # Add new voices
+ for nick in voices:
+ if nick not in self.irc_voices[channel]:
+ self.irc_voices[channel].add(nick)
+ await self.send_irc_command(':{} MODE {} +v {}'.format(
+ self.hostname, channel, nick,
+ ))
+
+ # Remove old voices
+ for nick in self.irc_voices[channel].difference(voices):
+ self.irc_voices[channel].remove(nick)
+ await self.send_irc_command(':{} MODE {} -v {}'.format(
+ self.hostname, channel, nick,
+ ))
+
+ self.ioloop.call_later(UPDATE_CHANNEL_VOICES_DELAY, self.update_channel_voices, channel)
+
+ # Telegram
+
+ async def initialize_telegram(self):
+ # Setup media folder
+ self.telegram_media_dir = os.path.join(self.config_dir, 'media')
+ if not os.path.exists(self.telegram_media_dir):
+ os.makedirs(self.telegram_media_dir)
+
+ # Setup session folder
+ self.telegram_session_dir = os.path.join(self.config_dir, 'session')
+ if not os.path.exists(self.telegram_session_dir):
+ os.makedirs(self.telegram_session_dir)
+
+ # Construct Telegram client
+ telegram_session = os.path.join(self.telegram_session_dir, self.irc_nick)
+ self.telegram_client = telethon.TelegramClient(telegram_session,
+ self.telegram_app_id, # TODO: handle error
+ self.telegram_app_hash,
+ )
+
+ # Initialize Telegram ID to IRC nick mapping
+ self.tid_to_iid = {}
+
+ # Register Telegram callbacks
+ callbacks = (
+ (self.handle_telegram_message , telethon.events.NewMessage),
+ (self.handle_telegram_chat_action, telethon.events.ChatAction),
+ )
+ for handler, event in callbacks:
+ self.telegram_client.add_event_handler(handler, event)
+
+ # Start Telegram client
+ await self.telegram_client.start()
+
+ # Update IRC <-> Telegram mapping
+ telegram_me = await self.telegram_client.get_me()
+ iid = self.irc_nick
+ tid = telegram_me.id
+ self.tid_to_iid[tid] = iid
+ self.iid_to_tid[iid] = tid
+
+ # Join all Telegram channels
+ await self.join_all_telegram_channels()
+
+ def get_telegram_nick(self, user):
+ nick = (user.username
+ or telethon.utils.get_display_name(user)
+ or str(user.id))
+ nick = nick.replace(' ', '')[:NICK_MAX_LENGTH]
+ while nick in self.iid_to_tid:
+ nick += '_'
+ return nick
+
+ def get_telegram_channel(self, chat):
+ return '#' + chat.title.lower().replace(' ', '-')
+
+ async def get_irc_nick_from_telegram_id(self, tid, entity=None):
+ if tid not in self.tid_to_iid:
+ user = entity or await self.telegram_client.get_entity(tid)
+ nick = self.get_telegram_nick(user)
+ self.tid_to_iid[tid] = nick
+ self.iid_to_tid[nick] = tid
+
+ return self.tid_to_iid[tid]
+
+ async def get_irc_channel_from_telegram_id(self, tid, entity=None):
+ if tid not in self.tid_to_iid:
+ chat = entity or await self.telegram_client.get_entity(tid)
+ channel = self.get_telegram_channel(chat)
+ self.tid_to_iid[tid] = channel
+ self.iid_to_tid[channel] = tid
+
+ return self.tid_to_iid[tid]
+
+ async def get_telegram_channel_participants(self, tid):
+ channel = self.tid_to_iid[tid]
+ nicks = []
+ voices = set()
+ async for user in self.telegram_client.iter_participants(tid):
+ user_nick = await self.get_irc_nick_from_telegram_id(user.id, user)
+
+ if isinstance(user.status, telethon.types.UserStatusOnline):
+ voices.add(user_nick)
+
+ nicks.append(user_nick)
+ self.irc_channels[channel].add(user_nick)
+
+ return nicks, voices
+
+ async def handle_telegram_message(self, event):
+ self.logger.debug('Handling Telegram Message: %s', event)
+
+ if event.message.is_private:
+ await self.handle_telegram_private_message(event)
+ else:
+ await self.handle_telegram_channel_message(event)
+
+ async def handle_telegram_private_message(self, event):
+ self.logger.debug('Handling Telegram Private Message: %s', event)
+
+ nick = await self.get_irc_nick_from_telegram_id(event.from_id)
+ for message in event.message.message.splitlines():
+ await self.send_irc_command(':{} PRIVMSG {} :{}'.format(
+ self.get_irc_user_mask(nick), self.irc_nick, message
+ ))
+
+ async def handle_telegram_channel_message(self, event):
+ self.logger.debug('Handling Telegram Channel Message: %s', event)
+
+ # Join IRC channel if not already in it
+ entity = await event.message.get_chat()
+ channel = await self.get_irc_channel_from_telegram_id(event.message.chat_id, entity)
+ if channel not in self.irc_channels:
+ await self.join_irc_channel(self.irc_nick, channel, True)
+
+ nick = await self.get_irc_nick_from_telegram_id(event.from_id)
+ if nick not in self.irc_channels[channel]:
+ await self.join_irc_channel(nick, channel, False)
+
+ # Format messages with media
+ messages = event.message.message.splitlines() if event.message.message else []
+ if event.message.media and (event.message.photo or event.message.gif):
+ message = await self.download_telegram_media(event.message, 'Image')
+ if message:
+ messages.insert(0, message)
+ elif event.message.media and (event.message.sticker):
+ messages.insert(0, 'Sticker: {}'.format(event.message.sticker.id))
+
+ # Send all messages to IRC
+ for message in messages:
+ await self.send_irc_command(':{} PRIVMSG {} :{}'.format(
+ self.get_irc_user_mask(nick), channel, message
+ ))
+
+ async def handle_telegram_chat_action(self, event):
+ self.logger.debug('Handling Telegram Chat Action: %s', event)
+
+ try:
+ tid = event.action_message.to_id.channel_id
+ except AttributeError:
+ tid = event.action_message.to_id.chat_id
+ finally:
+ irc_channel = await self.get_irc_channel_from_telegram_id(tid)
+ await self.get_telegram_channel_participants(tid)
+
+ try: # Join Chats
+ irc_nick = await self.get_irc_nick_from_telegram_id(event.action_message.action.users[0])
+ except (IndexError, AttributeError):
+ try: # Kick
+ irc_nick = await self.get_irc_nick_from_telegram_id(event.action_message.action.user_id)
+ except (IndexError, AttributeError): # Join Channels
+ irc_nick = await self.get_irc_nick_from_telegram_id(event.action_message.from_id)
+
+ if event.user_added or event.user_joined:
+ await self.join_irc_channel(irc_nick, irc_channel, False)
+ elif event.user_kicked or event.user_left:
+ await self.part_irc_channel(irc_nick, irc_channel)
+
+ async def join_all_telegram_channels(self):
+ async for dialog in self.telegram_client.iter_dialogs():
+ chat = dialog.entity
+ if not isinstance(chat, telethon.types.User):
+ channel = self.get_telegram_channel(chat)
+ self.tid_to_iid[chat.id] = channel
+ self.iid_to_tid[channel] = chat.id
+ await self.join_irc_channel(self.irc_nick, channel, True)
+
+ async def download_telegram_media(self, message, tag):
+ local_path = await message.download_media(self.telegram_media_dir)
+ if not local_path:
+ return
+
+ request = tornado.httpclient.HTTPRequest(
+ url = 'https://yld.me/paste',
+ method = 'POST',
+ body = open(local_path, 'rb').read(),
+ )
+ response = await tornado.httpclient.AsyncHTTPClient().fetch(request)
+
+ os.unlink(local_path)
+ return tag + ': ' + response.body.decode().strip()
hunk ./irgramd.py 3
-import collections
-import itertools
hunk ./irgramd.py 5
-import re
-import socket
-
-import tornado.gen
-import tornado.httpclient
+
hunk ./irgramd.py 12
-# Configuration
-
-NICK_MAX_LENGTH = 20
-UPDATE_CHANNEL_VOICES_DELAY = 300
-
-# Utilities
-
-def chunks(iterable, n, fillvalue=None):
- ''' Return iterable consisting of a sequence of n-length chunks '''
- args = [iter(iterable)] * n
- return itertools.zip_longest(*args, fillvalue=fillvalue)
+# Local modules
+
+from irc import *
+
hunk ./irgramd.py 42
-# IRC Regular Expressions
-
-IRC_NICK_RX = re.compile(r'NICK :(?P<nick>[^\n\r]+)')
-IRC_PASS_RX = re.compile(r'PASS :(?P<app_id>[^\s]+) (?P<app_hash>[^\n\r]+)')
-IRC_PING_RX = re.compile(r'PING (?P<payload>[^\n\r]+)')
-IRC_PRIVMSG_RX = re.compile(r'PRIVMSG (?P<nick>[^\s]+) :(?P<message>[^\n\r]+)')
-IRC_USER_RX = re.compile(r'USER (?P<username>[^\s]+) 0 \* :(?P<realname>[^\n\r]+)')
-
-# IRC Handler
-
-class IRCHandler(object):
- def __init__(self, stream, address, config_dir):
- self.logger = logging.getLogger()
- self.address = '{}:{}'.format(address[0], address[1])
- self.stream = stream
- self.ioloop = tornado.ioloop.IOLoop.current()
- self.hostname = socket.gethostname()
- self.config_dir = config_dir
-
- # Initialize IRC
- self.initialize_irc()
-
- self.logger.debug('Established client connection from %s', self.address)
-
- async def run(self):
- self.logger.debug('Running client connection from %s', self.address)
-
- while True:
- message = await self.stream.read_until(b'\n')
- message = message.decode().rstrip()
- self.logger.debug(message)
-
- for pattern, handler in self.irc_handlers:
- matches = pattern.match(message)
- if matches:
- await handler(**matches.groupdict())
-
- # IRC
-
- def initialize_irc(self):
- self.irc_handlers = (
- (IRC_NICK_RX , self.handle_irc_nick),
- (IRC_PASS_RX , self.handle_irc_pass),
- (IRC_PING_RX , self.handle_irc_ping),
- (IRC_PRIVMSG_RX, self.handle_irc_privmsg),
- (IRC_USER_RX , self.handle_irc_user),
- )
- self.iid_to_tid = {}
- self.irc_channels = collections.defaultdict(set)
- self.irc_nick = None
- self.irc_voices = collections.defaultdict(set)
-
- def get_irc_user_mask(self, nick):
- return '{}!{}@{}'.format(nick, nick, self.hostname)
-
- async def send_irc_command(self, command):
- self.logger.debug('Send IRC Command: %s', command)
- command = command + '\r\n'
- self.stream.write(command.encode())
-
- async def handle_irc_nick(self, nick):
- self.logger.debug('Handling NICK: %s', nick)
-
- if self.irc_nick in self.iid_to_tid:
- tid = self.iid_to_tid[self.irc_nick]
- self.tid_to_iid[tid] = nick
- self.iid_to_tid[nick] = tid
-
- self.irc_nick = nick
-
- async def handle_irc_user(self, username, realname):
- self.logger.debug('Handling USER: %s, %s', username, realname)
-
- self.irc_nick = username
-
- await self.send_irc_command(':{} 001 {} :{}'.format(
- self.hostname, self.irc_nick, 'Welcome to IRTelegramD'
- ))
- await self.send_irc_command(':{} 376 {} :{}'.format(
- self.hostname, self.irc_nick, 'End of MOTD command'
- ))
-
- await self.initialize_telegram()
-
- async def handle_irc_pass(self, app_id, app_hash):
- self.logger.debug('Handling PASS: %s %s', app_id, app_hash)
- self.telegram_app_id = int(app_id)
- self.telegram_app_hash = app_hash
-
- async def handle_irc_ping(self, payload):
- self.logger.debug('Handling PING: %s', payload)
- await self.send_irc_command(':{} PONG :{}'.format(
- self.hostname, payload
- ))
-
- async def handle_irc_privmsg(self, nick, message):
- self.logger.debug('Handling PRIVMSG: %s, %s', nick, message)
-
- if nick not in self.iid_to_tid:
- print('TODO: handle error')
-
- telegram_id = self.iid_to_tid[nick]
- await self.telegram_client.send_message(telegram_id, message)
-
- async def join_irc_channel(self, nick, channel, full_join=False):
- self.irc_channels[channel].add(nick)
-
- # Join Channel
- await self.send_irc_command(':{} JOIN :{}'.format(
- self.get_irc_user_mask(nick), channel
- ))
-
- if not full_join:
- return
-
- # Add all users to channel
- tid = self.iid_to_tid[channel]
- nicks, voices = await self.get_telegram_channel_participants(tid)
-
- # Set channel topic
- topic = (await self.telegram_client.get_entity(tid)).title
- await self.send_irc_command(':{} TOPIC {} :{}'.format(
- self.get_irc_user_mask(nick), channel, topic
- ))
-
- # Send NAMESLIST
- for chunk in chunks(nicks, 25, ''):
- await self.send_irc_command(':{} 353 {} = {} :{}'.format(
- self.hostname, self.irc_nick, channel, ' '.join(chunk)
- ))
-
- # Update voices
- await self.update_channel_voices(channel, voices)
-
- async def part_irc_channel(self, nick, channel):
- self.irc_channels[channel].remove(nick)
- await self.send_irc_command(':{} PART {} :'.format(
- self.get_irc_user_mask(nick), channel
- ))
-
- async def update_channel_voices(self, channel, voices=None):
- # Get voices for channel if not provided
- if not voices:
- tid = self.iid_to_tid[channel]
- _, voices = await self.get_telegram_channel_participants(tid)
-
- # Add new voices
- for nick in voices:
- if nick not in self.irc_voices[channel]:
- self.irc_voices[channel].add(nick)
- await self.send_irc_command(':{} MODE {} +v {}'.format(
- self.hostname, channel, nick,
- ))
-
- # Remove old voices
- for nick in self.irc_voices[channel].difference(voices):
- self.irc_voices[channel].remove(nick)
- await self.send_irc_command(':{} MODE {} -v {}'.format(
- self.hostname, channel, nick,
- ))
-
- self.ioloop.call_later(UPDATE_CHANNEL_VOICES_DELAY, self.update_channel_voices, channel)
-
- # Telegram
-
- async def initialize_telegram(self):
- # Setup media folder
- self.telegram_media_dir = os.path.join(self.config_dir, 'media')
- if not os.path.exists(self.telegram_media_dir):
- os.makedirs(self.telegram_media_dir)
-
- # Setup session folder
- self.telegram_session_dir = os.path.join(self.config_dir, 'session')
- if not os.path.exists(self.telegram_session_dir):
- os.makedirs(self.telegram_session_dir)
-
- # Construct Telegram client
- telegram_session = os.path.join(self.telegram_session_dir, self.irc_nick)
- self.telegram_client = telethon.TelegramClient(telegram_session,
- self.telegram_app_id, # TODO: handle error
- self.telegram_app_hash,
- )
-
- # Initialize Telegram ID to IRC nick mapping
- self.tid_to_iid = {}
-
- # Register Telegram callbacks
- callbacks = (
- (self.handle_telegram_message , telethon.events.NewMessage),
- (self.handle_telegram_chat_action, telethon.events.ChatAction),
- )
- for handler, event in callbacks:
- self.telegram_client.add_event_handler(handler, event)
-
- # Start Telegram client
- await self.telegram_client.start()
-
- # Update IRC <-> Telegram mapping
- telegram_me = await self.telegram_client.get_me()
- iid = self.irc_nick
- tid = telegram_me.id
- self.tid_to_iid[tid] = iid
- self.iid_to_tid[iid] = tid
-
- # Join all Telegram channels
- await self.join_all_telegram_channels()
-
- def get_telegram_nick(self, user):
- nick = (user.username
- or telethon.utils.get_display_name(user)
- or str(user.id))
- nick = nick.replace(' ', '')[:NICK_MAX_LENGTH]
- while nick in self.iid_to_tid:
- nick += '_'
- return nick
-
- def get_telegram_channel(self, chat):
- return '#' + chat.title.lower().replace(' ', '-')
-
- async def get_irc_nick_from_telegram_id(self, tid, entity=None):
- if tid not in self.tid_to_iid:
- user = entity or await self.telegram_client.get_entity(tid)
- nick = self.get_telegram_nick(user)
- self.tid_to_iid[tid] = nick
- self.iid_to_tid[nick] = tid
-
- return self.tid_to_iid[tid]
-
- async def get_irc_channel_from_telegram_id(self, tid, entity=None):
- if tid not in self.tid_to_iid:
- chat = entity or await self.telegram_client.get_entity(tid)
- channel = self.get_telegram_channel(chat)
- self.tid_to_iid[tid] = channel
- self.iid_to_tid[channel] = tid
-
- return self.tid_to_iid[tid]
-
- async def get_telegram_channel_participants(self, tid):
- channel = self.tid_to_iid[tid]
- nicks = []
- voices = set()
- async for user in self.telegram_client.iter_participants(tid):
- user_nick = await self.get_irc_nick_from_telegram_id(user.id, user)
-
- if isinstance(user.status, telethon.types.UserStatusOnline):
- voices.add(user_nick)
-
- nicks.append(user_nick)
- self.irc_channels[channel].add(user_nick)
-
- return nicks, voices
-
- async def handle_telegram_message(self, event):
- self.logger.debug('Handling Telegram Message: %s', event)
-
- if event.message.is_private:
- await self.handle_telegram_private_message(event)
- else:
- await self.handle_telegram_channel_message(event)
-
- async def handle_telegram_private_message(self, event):
- self.logger.debug('Handling Telegram Private Message: %s', event)
-
- nick = await self.get_irc_nick_from_telegram_id(event.from_id)
- for message in event.message.message.splitlines():
- await self.send_irc_command(':{} PRIVMSG {} :{}'.format(
- self.get_irc_user_mask(nick), self.irc_nick, message
- ))
-
- async def handle_telegram_channel_message(self, event):
- self.logger.debug('Handling Telegram Channel Message: %s', event)
-
- # Join IRC channel if not already in it
- entity = await event.message.get_chat()
- channel = await self.get_irc_channel_from_telegram_id(event.message.chat_id, entity)
- if channel not in self.irc_channels:
- await self.join_irc_channel(self.irc_nick, channel, True)
-
- nick = await self.get_irc_nick_from_telegram_id(event.from_id)
- if nick not in self.irc_channels[channel]:
- await self.join_irc_channel(nick, channel, False)
-
- # Format messages with media
- messages = event.message.message.splitlines() if event.message.message else []
- if event.message.media and (event.message.photo or event.message.gif):
- message = await self.download_telegram_media(event.message, 'Image')
- if message:
- messages.insert(0, message)
- elif event.message.media and (event.message.sticker):
- messages.insert(0, 'Sticker: {}'.format(event.message.sticker.id))
-
- # Send all messages to IRC
- for message in messages:
- await self.send_irc_command(':{} PRIVMSG {} :{}'.format(
- self.get_irc_user_mask(nick), channel, message
- ))
-
- async def handle_telegram_chat_action(self, event):
- self.logger.debug('Handling Telegram Chat Action: %s', event)
-
- try:
- tid = event.action_message.to_id.channel_id
- except AttributeError:
- tid = event.action_message.to_id.chat_id
- finally:
- irc_channel = await self.get_irc_channel_from_telegram_id(tid)
- await self.get_telegram_channel_participants(tid)
-
- try: # Join Chats
- irc_nick = await self.get_irc_nick_from_telegram_id(event.action_message.action.users[0])
- except (IndexError, AttributeError):
- try: # Kick
- irc_nick = await self.get_irc_nick_from_telegram_id(event.action_message.action.user_id)
- except (IndexError, AttributeError): # Join Channels
- irc_nick = await self.get_irc_nick_from_telegram_id(event.action_message.from_id)
-
- if event.user_added or event.user_joined:
- await self.join_irc_channel(irc_nick, irc_channel, False)
- elif event.user_kicked or event.user_left:
- await self.part_irc_channel(irc_nick, irc_channel)
-
- async def join_all_telegram_channels(self):
- async for dialog in self.telegram_client.iter_dialogs():
- chat = dialog.entity
- if not isinstance(chat, telethon.types.User):
- channel = self.get_telegram_channel(chat)
- self.tid_to_iid[chat.id] = channel
- self.iid_to_tid[channel] = chat.id
- await self.join_irc_channel(self.irc_nick, channel, True)
-
- async def download_telegram_media(self, message, tag):
- local_path = await message.download_media(self.telegram_media_dir)
- if not local_path:
- return
-
- request = tornado.httpclient.HTTPRequest(
- url = 'https://yld.me/paste',
- method = 'POST',
- body = open(local_path, 'rb').read(),
- )
- response = await tornado.httpclient.AsyncHTTPClient().fetch(request)
-
- os.unlink(local_path)
- return tag + ': ' + response.body.decode().strip()
addfile ./utils.py
hunk ./utils.py 1
+
+import itertools
+
+# Utilities
+
+def chunks(iterable, n, fillvalue=None):
+ ''' Return iterable consisting of a sequence of n-length chunks '''
+ args = [iter(iterable)] * n
+ return itertools.zip_longest(*args, fillvalue=fillvalue)
patch 4ce3f4a5c671bc4fbe91a03457cf4d87f562f4c7
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Oct 31 02:11:52 CET 2020
* Update README
hunk ./README.md 3
-I'm a fan of the [Destination Linux] and [Late Night Linux] podcasts... but
-they only provide [Telegram] groups for discussion, which is unfortunate
-because I prefer [IRC]. This is my hack to allow me to use [IRC] to
-participate in [Telegram] groups.
-
-
-[Destination Linux]: https://destinationlinux.org/
-[Late Night Linux]: https://latenightlinux.com/
-[Telegram]: https://telegram.org/
+irgramd is a gateway that allows connecting from an [IRC] client to
+[Telegram] as a regular user (not bot)
+
+irgramd is written in [python] (version 3), it acts as an IRC server
+where an IRC client can connect and on the other side it's a Telegram client
+using the [Telethon] library
+
+**[ This is a fork from [pbui/irtelegramd] to resume the development ]**
+
hunk ./README.md 13
+[Telegram]: https://telegram.org/
+[python]: https://www.python.org/
+[Telethon]: https://github.com/LonamiWebs/Telethon
+[pbui/irtelegramd]: https://github.com/pbui/irtelegramd
patch 3d8d0a1ec11316403026ee80427508f00b3727bf
Author: E. Bosch <presidev@AT@gmail.com>
Date: Sat Oct 31 01:04:19 CET 2020
* Rename project from IRTelegramD to irgramd
move ./irtelegramd.py ./irgramd.py
hunk ./README.md 1
-# IRTelegramD - IRC <-> Telegram Gateway
+# irgramd - IRC <-> Telegram Gateway
patch 8e1e98fcbdb12b4d3dc66a4dff34d1cd2532a44c
Author: GitHub <noreply@github.com>
Date: Fri Oct 4 19:26:47 CEST 2019
* Merge pull request #1 from AndroidKitKat/master
Add Links to README
patch a79dcfb59f16bdf21d33e723894c7d6a76c3ea13
Author: ãÂÂã¤ã±ã«ãÂȋ¢ã¤ã¼ãÂÂã³ <japanese_kitkat@yahoo.co.jp>
Date: Fri Oct 4 19:23:22 CEST 2019
* Add Links to README
hunk ./README.md 8
+
+[Destination Linux]: https://destinationlinux.org/
+[Late Night Linux]: https://latenightlinux.com/
+[Telegram]: https://telegram.org/
+[IRC]: https://en.wikipedia.org/wiki/Internet_Relay_Chat
+
patch f2668e35a4bdc7d647754056e9e7ad0d2260ed72
Author: Peter Bui <pbui@bx612.space>
Date: Mon Sep 16 02:22:38 CEST 2019
* Set irc_nick to username
hunk ./irtelegramd.py 128
+ self.irc_nick = username
+
patch 968b4238c8e11e5e2fd2321b69afb5c3d67e0621
Author: Peter Bui <pbui@bx612.space>
Date: Fri Jul 26 04:15:07 CEST 2019
* Add Dockerfile
addfile ./Dockerfile
hunk ./Dockerfile 1
+FROM alpine:latest
+MAINTAINER Peter Bui <pbui@yld.bx612.space>
+
+RUN apk update && \
+ apk add python3 py3-pip
+
+RUN pip3 install telethon tornado==5.1.1
+
+RUN wget -O - https://gitlab.com/pbui/irtelegramd/-/archive/master/irtelegramd-master.tar.gz | tar xzvf -
+
+COPY irtelegramd.py /irtelegramd-master
+
+EXPOSE 6667
+ENTRYPOINT ["/irtelegramd-master/irtelegramd.py", "--address=0.0.0.0", "--config_dir=/var/lib/irtelegramd"]
patch e5f5064add4337d64b2845281f6997b7b46b2312
Author: Peter Bui <pbui@bx612.space>
Date: Fri Jul 26 03:27:47 CEST 2019
* Update logging
hunk ./irtelegramd.py 41
- self.logger.info('Configuration Directory: %s', self.config_dir)
-
hunk ./irtelegramd.py 50
- self.logger.info('Starting server on %s:%s', self.address, self.port)
+ self.logger.info('IRTelegramd listening on %s:%s', self.address, self.port)
+ self.logger.info('Configuration Directory: %s', self.config_dir)
patch ccd25c01c419316eb00888c552bfbda5387db547
Author: Peter Bui <pbui@bx612.space>
Date: Fri Jul 26 03:11:39 CEST 2019
* Log configuration directory
hunk ./irtelegramd.py 41
+ self.logger.info('Configuration Directory: %s', self.config_dir)
+
patch 275639e05e10d91ef44554c6a8a24efed5145c4e
Author: Peter Bui <pbui@bx612.space>
Date: Fri Jul 26 03:05:21 CEST 2019
* Rename classes, add command line options
hunk ./irtelegramd.py 30
-# IRC Server
-
-class IRCServer(tornado.tcpserver.TCPServer):
- def __init__(self, address=None, port=6667):
+# IRC Telegram Daemon
+
+class IRCTelegramd(tornado.tcpserver.TCPServer):
+ def __init__(self, address=None, port=6667, config_dir=None, **settings):
hunk ./irtelegramd.py 36
- self.logger = logging.getLogger()
- self.address = address or '127.0.0.1'
- self.port = port
-
- self.logger.setLevel(logging.INFO)
+ self.logger = logging.getLogger()
+ self.address = address or '127.0.0.1'
+ self.port = port
+ self.config_dir = config_dir or os.path.expanduser('~/.config/irtelegramd')
+
+ if not os.path.exists(self.config_dir):
+ os.makedirs(self.config_dir)
hunk ./irtelegramd.py 45
- client = IRCClient(stream, address)
- await client.run()
+ handler = IRCHandler(stream, address, self.config_dir)
+ await handler.run()
hunk ./irtelegramd.py 62
-# IRC Client
-
-class IRCClient(object):
- def __init__(self, stream, address):
+# IRC Handler
+
+class IRCHandler(object):
+ def __init__(self, stream, address, config_dir):
hunk ./irtelegramd.py 71
-
- # Initialize configuration directory
- self.config_dir = os.path.expanduser('~/.config/irtelegramd')
- if not os.path.exists(self.config_dir):
- os.makedirs(self.config_dir)
+ self.config_dir = config_dir
hunk ./irtelegramd.py 400
- # TODO: Make configuration directory settable at run-time
- irc_server = IRCServer()
+ tornado.options.define('address', default=None, help='Address to listen on.')
+ tornado.options.define('port', default=6667, help='Port to listen on.')
+ tornado.options.define('config_dir', default=None, help='Configuration directory')
+ tornado.options.parse_command_line()
+
+ options = tornado.options.options.as_dict()
+ irc_server = IRCTelegramd(**options)
patch 415db75c7f249bfb40ec65c154487ee0948ff65f
Author: Peter Bui <pbui@bx612.space>
Date: Thu Jul 25 14:28:27 CEST 2019
* Revert "Make address public for now (docker)"
This reverts commit acd526e21e356afcbd2045157dfc086a57f6f083.
hunk ./irtelegramd.py 403
- irc_server = IRCServer(address="0.0.0.0")
+ irc_server = IRCServer()
patch 3e346b8980de4f4c434b77afa420ef0930e7801b
Author: Peter Bui <pbui@bx612.space>
Date: Thu Jul 25 14:27:04 CEST 2019
* Make address public for now (docker)
hunk ./irtelegramd.py 403
- irc_server = IRCServer()
+ irc_server = IRCServer(address="0.0.0.0")
patch 16c7c72a5c090a66bbc367c1bb197082ee005972
Author: Peter Bui <pbui@bx612.space>
Date: Thu Jul 25 14:21:08 CEST 2019
* Use default port of 6667
hunk ./irtelegramd.py 402
- irc_server = IRCServer(port=6668)
+ # TODO: Make configuration directory settable at run-time
+ irc_server = IRCServer()
patch 09a140b7b20de47995143a94a5f2991ebfaeb0c5
Author: Peter Bui <pbui@bx612.space>
Date: Sun Jul 21 02:45:18 CEST 2019
* Add self.get_irc_channel_from_telegram_id
Refactor get_irc_* functions to take an entity object (fallback to
get_entity when none is provided).
hunk ./irtelegramd.py 26
+ ''' Return iterable consisting of a sequence of n-length chunks '''
hunk ./irtelegramd.py 273
- async def get_irc_nick_from_telegram_id(self, tid):
+ async def get_irc_nick_from_telegram_id(self, tid, entity=None):
hunk ./irtelegramd.py 275
- user = await self.telegram_client.get_entity(tid)
+ user = entity or await self.telegram_client.get_entity(tid)
hunk ./irtelegramd.py 282
+ async def get_irc_channel_from_telegram_id(self, tid, entity=None):
+ if tid not in self.tid_to_iid:
+ chat = entity or await self.telegram_client.get_entity(tid)
+ channel = self.get_telegram_channel(chat)
+ self.tid_to_iid[tid] = channel
+ self.iid_to_tid[channel] = tid
+
+ return self.tid_to_iid[tid]
+
hunk ./irtelegramd.py 296
- if user.id not in self.tid_to_iid:
- user_nick = self.get_telegram_nick(user)
- self.tid_to_iid[user.id] = user_nick
- self.iid_to_tid[user_nick] = user.id
- else:
- user_nick = self.tid_to_iid[user.id]
+ user_nick = await self.get_irc_nick_from_telegram_id(user.id, user)
hunk ./irtelegramd.py 326
- # Retrieve IRC channel name from Telegram ID
- if event.message.chat_id not in self.tid_to_iid:
- chat = await event.message.get_chat()
- channel = self.get_telegram_channel(chat)
- self.tid_to_iid[chat.id] = channel
- self.iid_to_tid[channel] = chat.id
- else:
- channel = self.tid_to_iid[event.message.chat_id]
-
hunk ./irtelegramd.py 327
+ entity = await event.message.get_chat()
+ channel = await self.get_irc_channel_from_telegram_id(event.message.chat_id, entity)
hunk ./irtelegramd.py 352
- self.logger.info('Handling Telegram Chat Action: %s', event)
+ self.logger.debug('Handling Telegram Chat Action: %s', event)
hunk ./irtelegramd.py 359
- irc_channel = self.tid_to_iid[tid]
+ irc_channel = await self.get_irc_channel_from_telegram_id(tid)
patch 24aacd1a117ef092876ee5e0bc9522563dcdfd0e
Author: Peter Bui <pbui@bx612.space>
Date: Fri Jul 19 17:21:26 CEST 2019
* Periodically update channel voices
Rather than checking on individual users, simply check the whole channel
periodically... this is much more reliable than depending on messages
for active users.
hunk ./irtelegramd.py 20
-NICK_MAX_LENGTH = 20
-USER_STATUS_DELAY = 300
+NICK_MAX_LENGTH = 20
+UPDATE_CHANNEL_VOICES_DELAY = 300
hunk ./irtelegramd.py 105
- self.irc_online = set()
+ self.irc_voices = collections.defaultdict(set)
hunk ./irtelegramd.py 110
- def schedule_irc_mode_update(self, nick, channel, force=False):
- if nick not in self.irc_online or force:
- self.ioloop.call_later(USER_STATUS_DELAY, self.update_irc_mode, nick, channel)
- self.irc_online.add(nick)
-
hunk ./irtelegramd.py 185
- for user_nick in voices:
- await self.send_irc_command(':{} MODE {} +v {}'.format(
- self.hostname, channel, user_nick,
- ))
-
- self.schedule_irc_mode_update(user_nick, channel)
+ await self.update_channel_voices(channel, voices)
hunk ./irtelegramd.py 193
- async def update_irc_mode(self, nick, channel):
- tid = self.iid_to_tid[nick]
- user = await self.telegram_client.get_entity(tid)
- if isinstance(user.status, telethon.types.UserStatusOnline):
- self.schedule_irc_mode_update(nick, channel, True)
- else:
- self.irc_online.remove(nick)
+ async def update_channel_voices(self, channel, voices=None):
+ # Get voices for channel if not provided
+ if not voices:
+ tid = self.iid_to_tid[channel]
+ _, voices = await self.get_telegram_channel_participants(tid)
+
+ # Add new voices
+ for nick in voices:
+ if nick not in self.irc_voices[channel]:
+ self.irc_voices[channel].add(nick)
+ await self.send_irc_command(':{} MODE {} +v {}'.format(
+ self.hostname, channel, nick,
+ ))
+
+ # Remove old voices
+ for nick in self.irc_voices[channel].difference(voices):
+ self.irc_voices[channel].remove(nick)
hunk ./irtelegramd.py 214
+ self.ioloop.call_later(UPDATE_CHANNEL_VOICES_DELAY, self.update_channel_voices, channel)
+
hunk ./irtelegramd.py 284
- voices = []
+ voices = set()
hunk ./irtelegramd.py 294
- voices.append(user_nick)
+ voices.add(user_nick)
hunk ./irtelegramd.py 347
- # Give voice and schedule IRC mode update
- if nick not in self.irc_online:
- await self.send_irc_command(':{} MODE {} +v {}'.format(
- self.hostname, channel, nick,
- ))
- self.schedule_irc_mode_update(nick, channel)
-
hunk ./irtelegramd.py 362
- await get_telegram_channel_participants(tid)
+ await self.get_telegram_channel_participants(tid)
patch b2f185685325aa61a06179c5079a49f837f12a2f
Author: Peter Bui <pbui@bx612.space>
Date: Fri Jul 19 16:42:42 CEST 2019
* Update channel participants on Chat Action
get_entity does not work reliable (for new users), but get_participants
does, so simply call get_participants on ChatAction. This must happen
before we perform user resolution to ensure the information is in the
cache.
hunk ./irtelegramd.py 16
-import telethon.utils
-from telethon import TelegramClient, events
-from telethon.tl.types import User, UserStatusOnline
+import telethon
hunk ./irtelegramd.py 162
- async def join_irc_channel(self, nick, channel):
+ async def join_irc_channel(self, nick, channel, full_join=False):
hunk ./irtelegramd.py 170
+ if not full_join:
+ return
+
hunk ./irtelegramd.py 174
- tid = self.iid_to_tid[channel]
- nicks = []
- voices = []
- async for user in self.telegram_client.iter_participants(tid):
- if user.id not in self.tid_to_iid:
- user_nick = self.get_telegram_nick(user)
- self.tid_to_iid[user.id] = user_nick
- self.iid_to_tid[user_nick] = user.id
- else:
- user_nick = self.tid_to_iid[user.id]
-
- if isinstance(user.status, UserStatusOnline):
- voices.append(user_nick)
-
- nicks.append(user_nick)
- self.irc_channels[channel].add(user_nick)
+ tid = self.iid_to_tid[channel]
+ nicks, voices = await self.get_telegram_channel_participants(tid)
hunk ./irtelegramd.py 206
- if isinstance(user.status, UserStatusOnline):
+ if isinstance(user.status, telethon.types.UserStatusOnline):
hunk ./irtelegramd.py 229
- self.telegram_client = TelegramClient(telegram_session,
+ self.telegram_client = telethon.TelegramClient(telegram_session,
hunk ./irtelegramd.py 239
- (self.handle_telegram_message , events.NewMessage),
- (self.handle_telegram_chat_action, events.ChatAction),
+ (self.handle_telegram_message , telethon.events.NewMessage),
+ (self.handle_telegram_chat_action, telethon.events.ChatAction),
hunk ./irtelegramd.py 279
+ async def get_telegram_channel_participants(self, tid):
+ channel = self.tid_to_iid[tid]
+ nicks = []
+ voices = []
+ async for user in self.telegram_client.iter_participants(tid):
+ if user.id not in self.tid_to_iid:
+ user_nick = self.get_telegram_nick(user)
+ self.tid_to_iid[user.id] = user_nick
+ self.iid_to_tid[user_nick] = user.id
+ else:
+ user_nick = self.tid_to_iid[user.id]
+
+ if isinstance(user.status, telethon.types.UserStatusOnline):
+ voices.append(user_nick)
+
+ nicks.append(user_nick)
+ self.irc_channels[channel].add(user_nick)
+
+ return nicks, voices
+
hunk ./irtelegramd.py 330
- await self.join_irc_channel(self.irc_nick, channel)
+ await self.join_irc_channel(self.irc_nick, channel, True)
hunk ./irtelegramd.py 334
- await self.join_irc_channel(nick, channel)
+ await self.join_irc_channel(nick, channel, False)
hunk ./irtelegramd.py 362
- irc_channel = self.tid_to_iid[event.action_message.to_id.channel_id]
+ tid = event.action_message.to_id.channel_id
hunk ./irtelegramd.py 364
- irc_channel = self.tid_to_iid[event.action_message.to_id.chat_id]
+ tid = event.action_message.to_id.chat_id
+ finally:
+ irc_channel = self.tid_to_iid[tid]
+ await get_telegram_channel_participants(tid)
hunk ./irtelegramd.py 378
- await self.join_irc_channel(irc_nick, irc_channel)
+ await self.join_irc_channel(irc_nick, irc_channel, False)
hunk ./irtelegramd.py 385
- if not isinstance(chat, User):
+ if not isinstance(chat, telethon.types.User):
hunk ./irtelegramd.py 389
- await self.join_irc_channel(self.irc_nick, channel)
+ await self.join_irc_channel(self.irc_nick, channel, True)
patch 636b9aa2cb3b22eda734047f3d320dcc7e0303ad
Author: Peter Bui <pbui@bx612.space>
Date: Fri Jul 19 04:59:19 CEST 2019
* Just use get_entity (get_input_entity requires it to be cached)
hunk ./irtelegramd.py 285
- try:
- user = await self.telegram_client.get_input_entity(tid)
- except ValueError:
- user = await self.telegram_client.get_entity(tid)
+ user = await self.telegram_client.get_entity(tid)
patch 81a49436803aba222ef9afd79fda42cf3e4ce1c2
Author: Peter Bui <pbui@bx612.space>
Date: Thu Jul 18 22:39:17 CEST 2019
* Better handle the various chat actions
Remember that Chats != Channels and that different events will require
different attributes to determine the nick and channel.
hunk ./irtelegramd.py 285
- user = await self.telegram_client.get_input_entity(tid)
+ try:
+ user = await self.telegram_client.get_input_entity(tid)
+ except ValueError:
+ user = await self.telegram_client.get_entity(tid)
hunk ./irtelegramd.py 357
- irc_channel = self.tid_to_iid[event.action_message.to_id.channel_id]
- irc_nick = await self.get_irc_nick_from_telegram_id(event.action_message.from_id)
+ try:
+ irc_channel = self.tid_to_iid[event.action_message.to_id.channel_id]
+ except AttributeError:
+ irc_channel = self.tid_to_iid[event.action_message.to_id.chat_id]
+
+ try: # Join Chats
+ irc_nick = await self.get_irc_nick_from_telegram_id(event.action_message.action.users[0])
+ except (IndexError, AttributeError):
+ try: # Kick
+ irc_nick = await self.get_irc_nick_from_telegram_id(event.action_message.action.user_id)
+ except (IndexError, AttributeError): # Join Channels
+ irc_nick = await self.get_irc_nick_from_telegram_id(event.action_message.from_id)
patch 1a0add76192510876c18329da2e85acd0a16d368
Author: Peter Bui <pbui@bx612.space>
Date: Thu Jul 18 19:06:07 CEST 2019
* abstract resolving telegram_id to irc_nick
The get_irc_nick_from_telegram_id will check if the telegram_id is in
the dictionary. If it is not, it will look it up and add the
appropriate entries.
hunk ./irtelegramd.py 283
+ async def get_irc_nick_from_telegram_id(self, tid):
+ if tid not in self.tid_to_iid:
+ user = await self.telegram_client.get_input_entity(tid)
+ nick = self.get_telegram_nick(user)
+ self.tid_to_iid[tid] = nick
+ self.iid_to_tid[nick] = tid
+
+ return self.tid_to_iid[tid]
+
hunk ./irtelegramd.py 295
- if event.from_id not in self.tid_to_iid:
- user = await self.telegram_client.get_input_entity(event.from_id)
- nick = self.get_telegram_nick(user)
- self.tid_to_iid[user.id] = nick
- self.iid_to_tid[nick] = user.id
-
hunk ./irtelegramd.py 303
- nick = self.tid_to_iid[event.from_id]
+ nick = await self.get_irc_nick_from_telegram_id(event.from_id)
hunk ./irtelegramd.py 325
- nick = self.tid_to_iid[event.from_id]
+ nick = await self.get_irc_nick_from_telegram_id(event.from_id)
hunk ./irtelegramd.py 355
- irc_nick = self.tid_to_iid[event.action_message.from_id]
+ irc_nick = await self.get_irc_nick_from_telegram_id(event.action_message.from_id)
patch dbdfba82ee51b634c959557fb44f309f8d6b4377
Author: Peter Bui <pbui@bx612.space>
Date: Thu Jul 18 17:06:50 CEST 2019
* Split messages by newlines
Fixes problems where telegram messages are multiple paragraphs.
hunk ./irtelegramd.py 118
+ self.logger.debug('Send IRC Command: %s', command)
hunk ./irtelegramd.py 301
- await self.send_irc_command(':{} PRIVMSG {} :{}'.format(
- self.get_irc_user_mask(nick), self.irc_nick, event.message.message
- ))
+ for message in event.message.message.splitlines():
+ await self.send_irc_command(':{} PRIVMSG {} :{}'.format(
+ self.get_irc_user_mask(nick), self.irc_nick, message
+ ))
hunk ./irtelegramd.py 327
- messages = [event.message.message] if event.message.message else []
+ messages = event.message.message.splitlines() if event.message.message else []
patch 74af34e380c2469adf7752e1a16b875110f5b9cf
Author: Peter Bui <pbui@bx612.space>
Date: Thu Jul 18 14:05:47 CEST 2019
* irtelegramd: fix retrieving nick and channel from chat action
hunk ./irtelegramd.py 349
- irc_channel = self.tid_to_iid[event.to_id]
- irc_nick = self.tid_to_iid[event.user_id]
+ irc_channel = self.tid_to_iid[event.action_message.to_id.channel_id]
+ irc_nick = self.tid_to_iid[event.action_message.from_id]
patch bcb7f600f0cb9f90ee1d79ca0931ea70371cb85a
Author: Peter Bui <pbui@bx612.space>
Date: Wed Jul 17 03:30:32 CEST 2019
* Add MIT LICENSE
addfile ./LICENSE
hunk ./LICENSE 1
+MIT License
+
+Copyright (c) 2019 Peter Bui <pbui@bx612.space>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.