telegram: Add support for pin message action
Annotate for file /irc.py
2022-02-20 E. 1 # irgramd: IRC-Telegram gateway
01:25:27 ' 2 # irc.py: IRC server side implementation
' 3 #
' 4 # Copyright (c) 2019 Peter Bui <pbui@bx612.space>
2023-03-29 E. 5 # Copyright (c) 2020-2023 E. Bosch <presidev@AT@gmail.com>
2022-02-20 E. 6 #
01:25:27 ' 7 # Use of this source code is governed by a MIT style license that
' 8 # can be found in the LICENSE file included in this project.
2020-11-19 E. 9
19:41:24 ' 10 import collections
' 11 import logging
' 12 import re
' 13 import socket
2021-01-27 E. 14 import string
19:51:33 ' 15 import time
2020-11-19 E. 16
19:41:24 ' 17 import tornado.ioloop
' 18
' 19 # Local modules
' 20
2021-01-27 E. 21 from include import VERSION, CHAN_MAX_LENGHT, NICK_MAX_LENGTH
19:51:33 ' 22 from irc_replies import irc_codes
2022-01-24 E. 23 from utils import chunks, set_replace, split_lines
2022-03-02 E. 24 from service import service
2023-06-25 E. 25 from exclam import exclam
2021-01-27 E. 26
19:51:33 ' 27 # Constants
' 28
2021-02-08 E. 29 SRV = None
2021-03-04 E. 30 ALL_PARAMS = 16
2021-01-27 E. 31 VALID_IRC_NICK_FIRST_CHARS = string.ascii_letters + '[]\`_^{|}'
19:51:33 ' 32 VALID_IRC_NICK_CHARS = VALID_IRC_NICK_FIRST_CHARS + string.digits + '-'
2020-11-19 E. 33
19:41:24 ' 34 # IRC Regular Expressions
' 35
2021-02-17 E. 36 PREFIX = r'(?ai)(:[^ ]+ +|)'
2021-02-18 E. 37 IRC_JOIN_RX = re.compile(PREFIX + r'JOIN( +:| +|\n)(?P<channels>[^\n ]+|)')
2021-11-23 E. 38 IRC_LIST_RX = re.compile(PREFIX + r'LIST( +:| +|\n)(?P<channels>[^\n ]+|)')
2021-03-04 E. 39 IRC_MODE_RX = re.compile(PREFIX + r'MODE( +|\n)(?P<target>[^ ]+( +|\n)|)(?P<mode>[^ ]+( +|\n)|)(?P<arguments>[^\n]+|)')
2021-02-18 E. 40 IRC_MOTD_RX = re.compile(PREFIX + r'MOTD( +:| +|\n)(?P<target>[^\n ]+|)')
23:48:08 ' 41 IRC_NAMES_RX = re.compile(PREFIX + r'NAMES( +:| +|\n)(?P<channels>[^\n ]+|)')
' 42 IRC_NICK_RX = re.compile(PREFIX + r'NICK( +:| +|\n)(?P<nick>[^\n ]+|)')
2021-03-31 E. 43 IRC_PART_RX = re.compile(PREFIX + r'PART( +|\n)(?P<channels>[^ ]+|)( +:| +|\n|)(?P<reason>[^\n]+|)')
2021-02-18 E. 44 IRC_PASS_RX = re.compile(PREFIX + r'PASS( +:| +|\n)(?P<password>[^\n ]+|)')
2021-01-24 E. 45 IRC_PING_RX = re.compile(PREFIX + r'PING( +:| +|\n)(?P<payload>[^\n]+|)')
2022-02-03 E. 46 IRC_PRIVMSG_RX = re.compile(PREFIX + r'PRIVMSG( +|\n)(?P<target>[^ ]+)( +:| +|\n)(?P<message>[^\n]+|)')
2021-02-20 E. 47 IRC_QUIT_RX = re.compile(PREFIX + r'QUIT( +:| +|\n)(?P<reason>[^\n]+|)')
2021-02-19 E. 48 IRC_TOPIC_RX = re.compile(PREFIX + r'TOPIC( +:| +|\n)(?P<channel>[^\n ]+|)')
2021-01-24 E. 49 IRC_USER_RX = re.compile(PREFIX + r'USER( +|\n)(?P<username>[^ ]+) +[^ ]+ +[^ ]+( +:| +|\n)(?P<realname>[^\n]+|)')
2021-11-17 E. 50 IRC_USERHOST_RX = re.compile(PREFIX + r'USERHOST( +|\n)(?P<nick1>[^ ]+( +|\n)|)(?P<nick2>[^ ]+( +|\n)|)(?P<nick3>[^ ]+( +|\n)|)(?P<nick4>[^ ]+( +|\n)|)(?P<nick5>[^\n]+|)')
2021-02-21 E. 51 IRC_VERSION_RX = re.compile(PREFIX + r'VERSION( +:| +|\n)(?P<target>[^\n ]+|)')
2021-02-18 E. 52 IRC_WHO_RX = re.compile(PREFIX + r'WHO( +:| +|\n)(?P<target>[^\n ]+|)')
23:48:08 ' 53 IRC_WHOIS_RX = re.compile(PREFIX + r'WHOIS( +:| +|\n)(?P<nicks>[^\n ]+|)')
2020-11-19 E. 54
19:41:24 ' 55 # IRC Handler
' 56
' 57 class IRCHandler(object):
2021-12-12 E. 58 def __init__(self, settings):
2020-11-19 E. 59 self.logger = logging.getLogger()
2022-02-12 E. 60 self.hostname = socket.getfqdn()
2021-12-13 E. 61 self.conf = settings
2021-01-28 E. 62 self.users = {}
2020-11-19 E. 63
19:41:24 ' 64 # Initialize IRC
' 65 self.initialize_irc()
' 66
2020-11-22 E. 67
22:57:38 ' 68 async def run(self, stream, address):
2021-01-20 E. 69 user = IRCUser(stream, address)
00:13:37 ' 70
2022-02-16 E. 71 self.logger.info('Running IRC client connection from %s:%s', address[0], address[1])
2020-11-19 E. 72
19:41:24 ' 73 while True:
2021-01-29 E. 74 try:
00:24:25 ' 75 message = await user.stream.read_until(b'\n')
' 76 except tornado.iostream.StreamClosedError:
2021-02-20 E. 77 user.stream = None
22:52:38 ' 78 reason = user.close_reason if user.close_reason else ':Client disconnect'
' 79 await self.send_users_irc(user, 'QUIT', (reason,))
2022-02-16 E. 80 self.logger.info('Closing IRC client connection from %s:%s', address[0], address[1])
2021-01-29 E. 81 if user in self.users.values():
2021-01-29 E. 82 del self.users[user.irc_nick.lower()]
2021-02-04 E. 83 user.del_from_channels(self)
2021-01-29 E. 84 del user
00:24:25 ' 85 break
2023-06-10 E. 86 message = message.decode(self.conf['char_in_encoding'], errors='replace').replace('\r','\n')
2020-11-19 E. 87 self.logger.debug(message)
2021-01-24 E. 88
2021-03-04 E. 89 for pattern, handler, register_required, num_params_required in self.irc_handlers:
2020-11-19 E. 90 matches = pattern.match(message)
19:41:24 ' 91 if matches:
2021-01-24 E. 92 if user.registered or not register_required:
02:31:52 ' 93 params = matches.groupdict()
2021-02-04 E. 94 # Remove possible extra characters in parameters
14:37:25 ' 95 params = {x:y.strip() for x,y in params.items()}
2021-01-24 E. 96 num_params = len([x for x in params.values() if x])
02:31:52 ' 97 num_params_expected = len(params.keys())
2021-03-04 E. 98 if num_params >= self.num_params_necessary(num_params_required,
00:08:27 ' 99 num_params_expected):
2021-01-24 E. 100 await handler(user, **params)
02:31:52 ' 101 else:
2021-01-31 E. 102 await self.reply_code(user, 'ERR_NEEDMOREPARAMS')
2021-02-13 E. 103 else:
23:38:08 ' 104 await self.reply_code(user, 'ERR_NOTREGISTERED', ('',), '*')
2021-02-14 E. 105 break
00:02:09 ' 106
' 107 if not matches and user.registered:
2021-01-31 E. 108 await self.reply_code(user, 'ERR_UNKNOWNCOMMAND')
2020-11-19 E. 109
2020-11-22 E. 110 def set_telegram(self, tg):
22:57:38 ' 111 self.tg = tg
2022-03-08 E. 112 self.service = service(self.conf, self.tg)
2023-06-25 E. 113 self.exclam = exclam(self.tg)
2020-11-22 E. 114
2020-11-19 E. 115 # IRC
19:41:24 ' 116
' 117 def initialize_irc(self):
2022-03-01 E. 118 self.irc_handlers = \
19:02:36 ' 119 (
2021-03-04 E. 120 # pattern handle register_required num_params_required
2022-03-01 E. 121 (IRC_PRIVMSG_RX, self.handle_irc_privmsg, True, ALL_PARAMS),
19:02:36 ' 122 (IRC_PING_RX, self.handle_irc_ping, True, ALL_PARAMS),
2021-03-04 E. 123 (IRC_JOIN_RX, self.handle_irc_join, True, ALL_PARAMS),
00:08:27 ' 124 (IRC_MODE_RX, self.handle_irc_mode, True, 1),
' 125 (IRC_NAMES_RX, self.handle_irc_names, True, ALL_PARAMS),
' 126 (IRC_TOPIC_RX, self.handle_irc_topic, True, ALL_PARAMS),
2021-11-17 E. 127 (IRC_USERHOST_RX, self.handle_irc_userhost, True, 1),
2022-03-01 E. 128 (IRC_PART_RX, self.handle_irc_part, True, 1),
2021-03-04 E. 129 (IRC_WHO_RX, self.handle_irc_who, True, ALL_PARAMS),
00:08:27 ' 130 (IRC_WHOIS_RX, self.handle_irc_whois, True, ALL_PARAMS),
2022-03-01 E. 131 (IRC_LIST_RX, self.handle_irc_list, True, 0),
19:02:36 ' 132 (IRC_NICK_RX, self.handle_irc_nick, False, ALL_PARAMS),
' 133 (IRC_MOTD_RX, self.handle_irc_motd, True, 0),
' 134 (IRC_USER_RX, self.handle_irc_user, False, ALL_PARAMS),
' 135 (IRC_QUIT_RX, self.handle_irc_quit, False, 0),
' 136 (IRC_VERSION_RX, self.handle_irc_version, True, 0),
' 137 (IRC_PASS_RX, self.handle_irc_pass, False, ALL_PARAMS),
2020-11-19 E. 138 )
19:41:24 ' 139 self.iid_to_tid = {}
' 140 self.irc_channels = collections.defaultdict(set)
2021-02-01 E. 141 self.irc_channels_ops = collections.defaultdict(set)
2021-02-04 E. 142 self.irc_channels_founder = collections.defaultdict(set)
2021-01-27 E. 143 self.start_time = time.strftime('%a %d %b %Y %H:%M:%S %z')
2020-11-19 E. 144
2023-06-14 E. 145 self.service_user = IRCUser(None, ('Services',''), self.conf['service_user'],
2022-02-26 E. 146 'Control', 'Telegram Service', is_service=True)
23:47:48 ' 147 self.users[self.conf['service_user'].lower()] = self.service_user
2020-11-19 E. 148
2021-01-20 E. 149 async def send_irc_command(self, user, command):
2020-11-19 E. 150 self.logger.debug('Send IRC Command: %s', command)
19:41:24 ' 151 command = command + '\r\n'
2023-06-10 E. 152 user.stream.write(command.encode(self.conf['char_out_encoding'], errors='replace'))
2021-01-20 E. 153
2021-01-27 E. 154 # IRC handlers
19:51:33 ' 155
' 156 async def handle_irc_pass(self, user, password):
' 157 self.logger.debug('Handling PASS: %s %s', password)
' 158
' 159 if user.registered:
2021-01-31 E. 160 await self.reply_code(user, 'ERR_ALREADYREGISTRED')
2021-01-27 E. 161 else:
19:51:33 ' 162 user.recv_pass = password
' 163
2021-01-20 E. 164 async def handle_irc_nick(self, user, nick):
2020-11-19 E. 165 self.logger.debug('Handling NICK: %s', nick)
19:41:24 ' 166
2021-02-08 E. 167 ni = nick.lower()
2022-02-04 E. 168 current = user.irc_nick.lower() if user.irc_nick else None
18:18:34 ' 169 if ni == current:
' 170 return
2021-01-28 E. 171 if not user.valid_nick(nick):
2021-02-08 E. 172 await self.reply_code(user, 'ERR_ERRONEUSNICKNAME', (nick,), '*')
23:04:54 ' 173 elif ni in self.users.keys():
2021-01-31 E. 174 await self.reply_code(user, 'ERR_NICKNAMEINUSE', (nick,), '*')
2021-12-13 E. 175 elif user.pam_auth(nick, self.conf['pam'], self.conf['pam_group'], user.recv_pass) \
2022-01-22 E. 176 or user.local_auth(nick, self.conf['irc_nicks'], user.recv_pass, self.conf['irc_password'], self.conf['pam']):
2021-02-08 E. 177 if user.registered:
23:04:54 ' 178 # rename
2021-02-20 E. 179 await self.send_users_irc(user, 'NICK', (nick,))
2021-02-21 E. 180 del self.users[current]
00:12:58 ' 181 for ch in self.irc_channels.keys():
' 182 set_replace(self.irc_channels[ch], current, ni)
' 183 set_replace(self.irc_channels_ops[ch], current, ni)
' 184 set_replace(self.irc_channels_founder[ch], current, ni)
2021-01-27 E. 185 user.irc_nick = nick
2021-02-08 E. 186 self.users[ni] = user
2021-01-27 E. 187 if not user.registered and user.irc_username:
2022-03-08 E. 188 await self.register(user)
2021-01-27 E. 189 else:
2022-02-04 E. 190 if user.registered:
18:31:39 ' 191 await self.reply_code(user, 'ERR_ERRONEUSNICKNAME', (nick,))
' 192 else:
' 193 await self.reply_code(user, 'ERR_PASSWDMISMATCH')
2021-01-20 E. 194
2021-11-17 E. 195 async def handle_irc_userhost(self, user, **nicks):
20:49:05 ' 196 niv = nicks.values()
' 197 self.logger.debug('Handling USERHOST: %s', str(tuple(niv)))
' 198
' 199 reply = ''
' 200 sep = ''
' 201 away = '+'
' 202 for ni in niv:
' 203 n = ni.lower()
' 204 if n in self.users.keys():
' 205 usr = self.users[n]
' 206 oper = '*' if usr.oper else ''
' 207 reply += '{}{}{}={}{}@{}'.format(sep, usr.irc_nick, oper, away, usr.irc_username, usr.address)
' 208 if not sep: sep = ' '
' 209 if reply:
' 210 await self.reply_code(user, 'RPL_USERHOST', (reply,))
' 211
2021-01-20 E. 212 async def handle_irc_user(self, user, username, realname):
2020-11-19 E. 213 self.logger.debug('Handling USER: %s, %s', username, realname)
19:41:24 ' 214
2021-01-20 E. 215 user.irc_username = username
00:13:37 ' 216 user.irc_realname = realname
2021-01-27 E. 217 if user.irc_nick:
2022-03-08 E. 218 await self.register(user)
2020-11-19 E. 219
2021-02-12 E. 220 async def handle_irc_join(self, user, channels):
02:09:58 ' 221 self.logger.debug('Handling JOIN: %s', channels)
' 222
' 223 if channels == '0':
2021-03-31 E. 224 for channel in self.irc_channels.keys():
01:21:58 ' 225 if user.irc_nick in self.irc_channels[channel]:
' 226 await self.part_irc_channel(user, channel, '')
2021-02-12 E. 227 else:
02:09:58 ' 228 for channel in channels.split(','):
' 229 if channel.lower() in self.irc_channels.keys():
2023-03-24 E. 230 await self.join_irc_channel(user, channel, full_join=True)
2021-02-12 E. 231 else:
2021-02-14 E. 232 await self.reply_code(user, 'ERR_NOSUCHCHANNEL', (channel,))
2021-01-20 E. 233
2021-03-31 E. 234 async def handle_irc_part(self, user, channels, reason):
01:21:58 ' 235 self.logger.debug('Handling PART: %s, %s', channels, reason)
' 236
' 237 for channel in channels.split(','):
' 238 chan = channel.lower()
' 239 if chan in self.irc_channels.keys():
' 240 if user.irc_nick in self.irc_channels[chan]:
' 241 await self.part_irc_channel(user, channel, reason)
' 242 else:
' 243 await self.reply_code(user, 'ERR_NOTONCHANNEL', (channel,))
' 244 else:
' 245 await self.reply_code(user, 'ERR_NOSUCHCHANNEL', (channel,))
' 246
2021-11-23 E. 247 async def handle_irc_list(self, user, channels):
23:27:12 ' 248 self.logger.debug('Handling LIST: %s', channels)
' 249
' 250 if channels:
' 251 chans = channels.split(',')
' 252 else:
' 253 chans = self.irc_channels.keys()
' 254
' 255 await self.reply_code(user, 'RPL_LISTSTART')
' 256 for channel in chans:
' 257 chan = channel.lower()
' 258 if chan in self.irc_channels.keys():
' 259 real_chan = self.get_realcaps_name(chan)
' 260 users_count = len(self.irc_channels[chan])
2022-01-30 E. 261 topic = await self.tg.get_channel_topic(chan, [None])
2021-11-23 E. 262 await self.reply_code(user, 'RPL_LIST', (real_chan, users_count, topic))
23:27:12 ' 263 await self.reply_code(user, 'RPL_LISTEND')
' 264
2021-02-14 E. 265 async def handle_irc_names(self, user, channels):
00:55:27 ' 266 self.logger.debug('Handling NAMES: %s', channels)
' 267
' 268 for channel in channels.split(','):
' 269 await self.irc_namelist(user, channel)
' 270
2021-03-04 E. 271 async def handle_irc_mode(self, user, target, mode, arguments):
00:08:27 ' 272 self.logger.debug('Handling MODE: %s, %s, %s', target, mode, arguments)
' 273
2022-02-12 E. 274 is_user = False
00:29:36 ' 275 is_channel = False
' 276
' 277 tgt = target.lower()
' 278 if tgt in self.users.keys():
' 279 if tgt == user.irc_nick:
' 280 is_user = True
' 281 else:
' 282 await self.reply_code(user, 'ERR_USERSDONTMATCH')
' 283 elif tgt[0] == '#':
' 284 if tgt in self.irc_channels.keys():
' 285 is_channel = True
' 286 else:
' 287 await self.reply_code(user, 'ERR_NOSUCHCHANNEL', (target,))
' 288 else:
' 289 await self.reply_code(user, 'ERR_NOSUCHNICK', (target,))
' 290
2021-03-04 E. 291 if not mode:
2022-02-12 E. 292 if is_user:
00:29:36 ' 293 await self.mode_user(user, user, False)
' 294 if is_channel:
' 295 await self.mode_channel(user, target, False)
' 296 elif mode == 'b' and is_channel:
' 297 await self.reply_code(user, 'RPL_ENDOFBANLIST', (target,))
2021-03-04 E. 298
2021-02-17 E. 299 async def handle_irc_motd(self, user, target):
20:24:00 ' 300 self.logger.debug('Handling MOTD: %s', target)
' 301
2022-02-12 E. 302 if not target or target == self.gethostname(user):
2021-02-17 E. 303 await self.send_motd(user)
20:24:00 ' 304 else:
' 305 await self.reply_code(user, 'ERR_NOSUCHSERVER', (target,))
' 306
2021-02-19 E. 307 async def handle_irc_topic(self, user, channel):
00:05:45 ' 308 self.logger.debug('Handling TOPIC: %s', channel)
' 309
' 310 chan = channel.lower()
' 311 real_chan = self.get_realcaps_name(chan)
2022-01-30 E. 312 await self.irc_channel_topic(user, real_chan, [None])
2021-02-19 E. 313
2021-01-20 E. 314 async def handle_irc_ping(self, user, payload):
2020-11-19 E. 315 self.logger.debug('Handling PING: %s', payload)
2021-02-08 E. 316
2022-02-12 E. 317 await self.reply_command(user, SRV, 'PONG', (self.gethostname(user), payload))
2020-11-19 E. 318
2021-02-04 E. 319 async def handle_irc_who(self, user, target):
23:38:22 ' 320 self.logger.debug('Handling WHO: %s', target)
' 321 tgt = target.lower()
' 322 if tgt in self.irc_channels.keys():
' 323 users = self.irc_channels[tgt]
2021-02-14 E. 324 chan = self.get_realcaps_name(tgt)
2021-02-04 E. 325 elif tgt in self.users.keys():
23:38:22 ' 326 users = (self.users[tgt],)
' 327 chan = '*'
' 328 else:
' 329 await self.reply_code(user, 'ERR_NOSUCHSERVER', (target,))
' 330 return
' 331 for usr in users:
' 332 if not isinstance(usr,IRCUser):
' 333 usr = self.users[usr.lower()]
' 334 op = self.get_irc_op(usr.irc_nick, chan)
' 335 await self.reply_code(user, 'RPL_WHOREPLY', (chan, usr.irc_username,
2022-02-12 E. 336 usr.address, self.gethostname(user), usr.irc_nick, op, usr.irc_realname
2021-02-04 E. 337 ))
23:38:22 ' 338 await self.reply_code(user, 'RPL_ENDOFWHO', (chan,))
' 339
2021-02-07 E. 340 async def handle_irc_whois(self, user, nicks):
2021-02-21 E. 341 self.logger.debug('Handling WHOIS: %s', nicks)
2021-02-07 E. 342 for nick in nicks.split(','):
02:14:36 ' 343 ni = nick.lower()
' 344 if ni in self.users.keys():
' 345 usr = self.users[ni]
2022-02-27 E. 346 real_ni = usr.irc_nick
2021-02-07 E. 347 await self.reply_code(user, 'RPL_WHOISUSER', (real_ni, usr.irc_username, usr.address, usr.irc_realname))
2022-02-12 E. 348 await self.reply_code(user, 'RPL_WHOISSERVER', (real_ni, self.gethostname(user)))
2021-02-07 E. 349 chans = usr.get_channels(self)
02:14:36 ' 350 if chans: await self.reply_code(user, 'RPL_WHOISCHANNELS', (real_ni, chans))
' 351 idle = await self.tg.get_telegram_idle(ni)
' 352 if idle != None: await self.reply_code(user, 'RPL_WHOISIDLE', (real_ni, idle))
' 353 if usr.oper: await self.reply_code(user, 'RPL_WHOISOPERATOR', (real_ni,))
' 354 if usr.stream: await self.reply_code(user, 'RPL_WHOISACCOUNT', (real_ni,
2022-02-05 E. 355 '{}|{}!{}@Telegram'.format(self.tg.tg_username,
02:30:31 ' 356 await self.tg.get_telegram_display_name_me(), self.tg.id
2021-02-07 E. 357 )))
02:14:36 ' 358 if await self.tg.is_bot(ni):
' 359 await self.reply_code(user, 'RPL_WHOISBOT', (real_ni,))
2022-02-26 E. 360 elif usr.tls or (not usr.stream and not usr.is_service):
2021-02-07 E. 361 proto = 'TLS' if usr.tls else 'MTProto'
2022-02-12 E. 362 server = self.gethostname(user) if usr.stream else 'Telegram'
2021-02-07 E. 363 await self.reply_code(user, 'RPL_WHOISSECURE', (real_ni, proto, server))
2022-02-26 E. 364 if usr.is_service:
23:47:48 ' 365 await self.reply_code(user, 'RPL_WHOISSERVICE', (real_ni,))
2021-02-07 E. 366 await self.reply_code(user, 'RPL_ENDOFWHOIS', (real_ni,))
02:14:36 ' 367 else:
' 368 await self.reply_code(user, 'ERR_NOSUCHNICK', (nick,))
' 369
2021-02-21 E. 370 async def handle_irc_version(self, user, target):
22:35:32 ' 371 self.logger.debug('Handling VERSION: %s', target)
' 372
' 373 tgt = target.lower()
2022-02-12 E. 374 if not tgt or tgt == self.gethostname(user) or tgt in self.users.keys():
02:02:43 ' 375 await self.reply_code(user, 'RPL_VERSION', (VERSION, self.gethostname(user)))
2021-02-21 E. 376 await self.send_isupport(user)
22:35:32 ' 377 else:
' 378 await self.reply_code(user, 'ERR_NOSUCHSERVER', (target,))
' 379
2022-02-03 E. 380 async def handle_irc_privmsg(self, user, target, message):
20:11:10 ' 381 self.logger.debug('Handling PRIVMSG: %s, %s', target, message)
' 382
' 383 tgl = target.lower()
2022-02-26 E. 384 if self.service_user.irc_nick.lower() == tgl:
2022-03-19 E. 385 reply = await self.service.parse_command(message, user.irc_nick)
2022-03-06 E. 386 for reply_line in reply:
2022-03-18 E. 387 await self.send_msg(self.service_user, None, reply_line, user)
2022-02-26 E. 388 return
2023-04-11 E. 389 defered_send = None
2022-02-03 E. 390 # Echo channel messages from IRC to other IRC connections
20:11:10 ' 391 # because they won't receive event from Telegram
2023-04-11 E. 392 # used defered_send function when id is known
2022-02-03 E. 393 if tgl in self.irc_channels.keys():
2023-04-11 E. 394 chan = tgl
07:45:58 ' 395 defered_send = self.send_msg_others
' 396 defered_target = chan
' 397 else:
' 398 chan = None
2022-02-03 E. 399
20:11:10 ' 400 if tgl == user.irc_nick:
' 401 tgt = self.tg.tg_username.lower()
2022-01-27 E. 402 # Echo message to the user him/herself in IRC
22:40:06 ' 403 # because no event will be received from Telegram
2023-04-11 E. 404 # used defered_send function when id is known
07:45:58 ' 405 defered_send = self.send_msg
' 406 defered_target = None
2022-01-27 E. 407 else:
2022-02-03 E. 408 tgt = tgl
2021-02-02 E. 409
2022-02-04 E. 410 if tgt in self.iid_to_tid:
2023-06-15 E. 411 message = self.tg.replace_mentions(message, me_nick='', received=False)
2022-02-04 E. 412 telegram_id = self.iid_to_tid[tgt]
2023-06-25 E. 413 if message[0] == '!':
22:17:22 ' 414 cont, tg_msg = await self.exclam.command(message, telegram_id, user)
' 415 else:
' 416 tg_msg = await self.tg.telegram_client.send_message(telegram_id, message)
' 417 cont = True
' 418 if cont:
' 419 mid = self.tg.mid.num_to_id_offset(telegram_id, tg_msg.id)
' 420 text = '[{}] {}'.format(mid, message)
' 421 self.tg.to_cache(tg_msg.id, mid, text, message, user, chan, media=None)
' 422
' 423 if defered_send:
' 424 await defered_send(user, defered_target, text)
2022-02-04 E. 425 else:
17:21:49 ' 426 await self.reply_code(user, 'ERR_NOSUCHNICK', (target,))
2020-11-19 E. 427
2021-02-20 E. 428 async def handle_irc_quit(self, user, reason):
22:52:38 ' 429 self.logger.debug('Handling TOPIC: %s', reason)
' 430
' 431 await self.reply_command(user, SRV, 'ERROR', (':Client disconnect',))
' 432 user.close_reason = ':' + reason
' 433 user.stream.close()
' 434
2021-01-24 E. 435 # IRC functions
2022-03-08 E. 436 async def register(self, user):
2023-06-14 E. 437 self.logger.info('Registered IRC user "%s" from %s:%s', user.irc_nick, user.address, user.port)
23:15:43 ' 438
2022-03-08 E. 439 user.registered = True
20:57:51 ' 440 await self.send_greeting(user)
' 441 await self.send_help(user)
2022-03-08 E. 442 await self.check_telegram_auth(user)
2021-01-24 E. 443
2022-03-18 E. 444 async def send_msg(self, source, target, message, selfuser=None):
2022-01-24 E. 445 messages = split_lines(message)
2022-02-03 E. 446 tgt = target.lower() if target else ''
20:11:10 ' 447 is_chan = tgt in self.irc_channels.keys()
' 448 # source None (False): it's self Telegram user, see [1]
2022-01-24 E. 449 source_mask = source.get_irc_mask() if source else ''
21:35:55 ' 450 for msg in messages:
2022-03-18 E. 451 if selfuser:
21:08:29 ' 452 irc_users = (selfuser,)
' 453 elif is_chan:
2022-02-03 E. 454 irc_users = (u for u in self.users.values() if u.stream and u.irc_nick in self.irc_channels[tgt])
20:11:10 ' 455 else:
' 456 irc_users = (u for u in self.users.values() if u.stream)
' 457
' 458 for irc_user in irc_users:
' 459 await self.send_privmsg(irc_user, source_mask, target, msg)
' 460
' 461 async def send_msg_others(self, source, target, message):
' 462 source_mask = source.get_irc_mask()
' 463 is_chan = target in self.irc_channels.keys()
' 464 if is_chan:
' 465 irc_users = (u for u in self.users.values() if u.stream and u.irc_nick != source.irc_nick
' 466 and u.irc_nick in self.irc_channels[target])
' 467 else:
' 468 irc_users = (u for u in self.users.values() if u.stream and u.irc_nick != source.irc_nick)
' 469
' 470 for irc_user in irc_users:
' 471 await self.send_privmsg(irc_user, source_mask, target, message)
' 472
2023-11-26 E. 473 async def send_action(self, source, target, message):
19:07:44 ' 474 action_message = '\x01ACTION {}\x01'.format(message)
' 475 await self.send_msg(source, target, action_message)
' 476
2022-02-03 E. 477 async def send_privmsg(self, user, source_mask, target, msg):
20:11:10 ' 478 # reference [1]
' 479 src_mask = source_mask if source_mask else user.get_irc_mask()
' 480 # target None (False): it's private, not a channel
' 481 tgt = target if target else user.irc_nick
2023-03-28 E. 482 if self.tg.refwd_me:
21:32:36 ' 483 msg = msg.format(user.irc_nick)
2023-06-10 E. 484 # replace self @username and other mentions for self messages sent by this instance of irgramd
20:31:22 ' 485 msg = self.tg.replace_mentions(msg, user.irc_nick)
' 486
2022-02-03 E. 487 await self.send_irc_command(user, ':{} PRIVMSG {} :{}'.format(src_mask, tgt, msg))
2022-01-24 E. 488
2021-02-08 E. 489 async def reply_command(self, user, prfx, comm, params):
2022-02-12 E. 490 prefix = self.gethostname(user) if prfx == SRV else prfx.get_irc_mask()
2021-02-08 E. 491 p = len(params)
23:04:54 ' 492 if p == 1:
' 493 fstri = ':{} {} {}'
' 494 else:
' 495 fstri = ':{} {}' + ((p - 1) * ' {}') + ' :{}'
' 496 await self.send_irc_command(user, fstri.format(prefix, comm, *params))
' 497
2021-01-31 E. 498 async def reply_code(self, user, code, params=None, client=None):
2021-01-24 E. 499 num, tail = irc_codes[code]
2021-01-31 E. 500 if params:
00:39:07 ' 501 nick = client if client else user.irc_nick
' 502 rest = tail.format(*params)
2022-02-12 E. 503 stri = ':{} {} {} {}'.format(self.gethostname(user), num, nick, rest)
2021-01-31 E. 504 else:
2022-02-12 E. 505 stri = ':{} {} {} :{}'.format(self.gethostname(user), num, user.irc_nick, tail)
2021-01-31 E. 506 await self.send_irc_command(user, stri)
2021-01-27 E. 507
19:51:33 ' 508 async def send_greeting(self, user):
2021-01-31 E. 509 await self.reply_code(user, 'RPL_WELCOME', (user.irc_nick,))
2022-02-12 E. 510 await self.reply_code(user, 'RPL_YOURHOST', (self.gethostname(user), VERSION))
2021-01-31 E. 511 await self.reply_code(user, 'RPL_CREATED', (self.start_time,))
2022-02-12 E. 512 await self.reply_code(user, 'RPL_MYINFO', (self.gethostname(user), VERSION))
2021-02-21 E. 513 await self.send_isupport(user)
2021-01-27 E. 514 await self.send_motd(user)
2021-03-04 E. 515 await self.mode_user(user, user, True)
2021-01-27 E. 516
19:51:33 ' 517 async def send_motd(self, user):
2022-02-12 E. 518 await self.reply_code(user, 'RPL_MOTDSTART', (self.gethostname(user),))
2021-01-31 E. 519 await self.reply_code(user, 'RPL_MOTD', ('Welcome to the irgramd server',))
00:39:07 ' 520 await self.reply_code(user, 'RPL_MOTD', ('',))
' 521 await self.reply_code(user, 'RPL_MOTD', ('This is not a normal IRC server, it\'s a gateway that',))
' 522 await self.reply_code(user, 'RPL_MOTD', ('allows connecting from an IRC client (the program that',))
' 523 await self.reply_code(user, 'RPL_MOTD', ('you are [probably] using right now) to the Telegram instant',))
' 524 await self.reply_code(user, 'RPL_MOTD', ('messaging network as a regular user account (not bot)',))
' 525 await self.reply_code(user, 'RPL_MOTD', ('',))
' 526 await self.reply_code(user, 'RPL_MOTD', ('irgramd is an open source project that you can find on',))
2022-02-04 E. 527 await self.reply_code(user, 'RPL_MOTD', ('darcs repository: https://src.presi.org/darcs/irgramd',))
2021-01-31 E. 528 await self.reply_code(user, 'RPL_MOTD', ('git repository: https://github.com/prsai/irgramd',))
00:39:07 ' 529 await self.reply_code(user, 'RPL_ENDOFMOTD')
2021-01-27 E. 530
2021-02-21 E. 531 async def send_isupport(self, user):
22:35:32 ' 532 await self.reply_code(user, 'RPL_ISUPPORT', (CHAN_MAX_LENGHT, NICK_MAX_LENGTH))
' 533
2022-03-08 E. 534 async def send_help(self, user):
20:57:51 ' 535 for line in (
' 536 'Welcome to irgramd service',
' 537 'use /msg {} help'.format(self.service_user.irc_nick),
' 538 'to get help',
' 539 ):
2022-03-18 E. 540 await self.send_msg(self.service_user, None, line, user)
2022-03-08 E. 541
2022-03-08 E. 542 async def check_telegram_auth(self, user):
2022-03-19 E. 543 await self.tg.auth_checked.wait()
2022-03-08 E. 544 if not self.tg.authorized and not self.tg.ask_code:
21:58:55 ' 545 for line in (
' 546 '----',
' 547 'Your Telegram account is not authorized yet,',
' 548 'you must supply the code that Telegram sent to your phone',
' 549 'or another client that is currently connected',
' 550 'use /msg {} code <code>'.format(self.service_user.irc_nick),
' 551 'e.g. /msg {} code 12345'.format(self.service_user.irc_nick),
' 552 ):
' 553 await self.send_msg(self.service_user, user.irc_nick, line)
' 554
2021-02-20 E. 555 async def send_users_irc(self, prfx, command, params):
22:52:38 ' 556 for usr in [x for x in self.users.values() if x.stream]:
' 557 await self.reply_command(usr, prfx, command, params)
' 558
2021-03-04 E. 559 async def mode_user(self, user, usr, com):
00:08:27 ' 560 modes = ''
' 561 if usr.oper: modes += 'o'
' 562 if usr.tls: modes += 'S'
' 563 if modes: modes = '+' + modes
' 564 if com:
' 565 await self.reply_command(user, usr, 'MODE', (usr.irc_nick, modes))
' 566 else:
' 567 await self.reply_code(user, 'RPL_UMODEIS', (modes,))
' 568
' 569 async def mode_channel(self, user, channel, com):
' 570 modes = '+nt'
' 571 if com:
' 572 await self.reply_command(user, user, 'MODE', (channel, modes))
' 573 else:
' 574 await self.reply_code(user, 'RPL_CHANNELMODEIS', (channel, modes,''))
' 575
2023-03-24 E. 576 async def join_irc_channel(self, user, channel, full_join):
2021-02-12 E. 577 entity_cache = [None]
2021-02-01 E. 578 chan = channel.lower()
2021-02-12 E. 579 real_chan = self.get_realcaps_name(chan)
02:09:58 ' 580
' 581 if full_join: self.irc_channels[chan].add(user.irc_nick)
' 582
' 583 # Notify IRC users in this channel
' 584 for usr in [self.users[x.lower()] for x in self.irc_channels[chan] if self.users[x.lower()].stream]:
' 585 await self.reply_command(usr, user, 'JOIN', (real_chan,))
' 586
' 587 if not full_join:
' 588 return
' 589
2021-02-04 E. 590 op = self.get_irc_op(self.tg.tg_username, channel)
23:26:44 ' 591 if op == '@': self.irc_channels_ops[chan].add(user.irc_nick)
' 592 elif op == '~': self.irc_channels_founder[chan].add(user.irc_nick)
2020-11-19 E. 593
2021-02-12 E. 594 date = await self.tg.get_channel_creation(channel, entity_cache)
02:09:58 ' 595 await self.reply_code(user, 'RPL_CREATIONTIME', (real_chan, date))
' 596 await self.irc_channel_topic(user, real_chan, entity_cache)
' 597 await self.irc_namelist(user, real_chan)
' 598
2021-03-31 E. 599 async def part_irc_channel(self, user, channel, reason):
01:21:58 ' 600 chan = channel.lower()
' 601 real_chan = self.get_realcaps_name(chan)
' 602
' 603 # Notify IRC users in this channel
' 604 for usr in [self.users[x.lower()] for x in self.irc_channels[chan] if self.users[x.lower()].stream]:
' 605 await self.reply_command(usr, user, 'PART', (real_chan, reason))
' 606
' 607 self.irc_channels[chan].remove(user.irc_nick)
' 608 self.irc_channels_ops[chan].discard(user.irc_nick)
' 609 self.irc_channels_founder[chan].discard(user.irc_nick)
' 610
2022-01-30 E. 611 async def irc_channel_topic(self, user, channel, entity_cache):
2021-02-19 E. 612 chan = channel.lower()
00:05:45 ' 613 topic = await self.tg.get_channel_topic(chan, entity_cache)
' 614 timestamp = await self.tg.get_channel_creation(chan, entity_cache)
2023-03-16 E. 615 if self.irc_channels_founder[chan]:
00:06:33 ' 616 founder = list(self.irc_channels_founder[chan])[0]
' 617 else:
' 618 founder = self.service_user.irc_nick
2021-02-12 E. 619 await self.reply_code(user, 'RPL_TOPIC', (channel, topic))
02:09:58 ' 620 await self.reply_code(user, 'RPL_TOPICWHOTIME', (channel, founder, timestamp))
' 621
' 622 async def irc_namelist(self, user, channel):
' 623 nicks = [self.get_irc_op(x, channel) + x for x in self.irc_channels[channel.lower()]]
' 624 status = '='
2020-11-19 E. 625 for chunk in chunks(nicks, 25, ''):
2021-02-12 E. 626 await self.reply_code(user, 'RPL_NAMREPLY', (status, channel, ' '.join(chunk)))
02:09:58 ' 627 await self.reply_code(user, 'RPL_ENDOFNAMES', (channel,))
2020-11-19 E. 628
2021-02-04 E. 629 def get_irc_op(self, nick, channel):
23:26:44 ' 630 chan = channel.lower()
' 631 if chan in self.irc_channels.keys():
' 632 if nick in self.irc_channels_ops[chan]:
' 633 return '@'
' 634 if nick in self.irc_channels_founder[chan]:
' 635 return '~'
' 636 return ''
' 637
2021-02-12 E. 638 def get_realcaps_name(self, name):
02:09:58 ' 639 # name must be in lower
' 640 return self.tg.tid_to_iid[self.iid_to_tid[name]]
' 641
2021-03-04 E. 642 def num_params_necessary(self, num_params_required, num_params_expected):
00:08:27 ' 643 if num_params_required == ALL_PARAMS:
' 644 npn = num_params_expected
' 645 else:
' 646 npn = num_params_required
' 647 return npn
' 648
2022-02-12 E. 649 def gethostname(self, user):
02:02:43 ' 650 return 'localhost' if user.from_localhost else self.hostname
' 651
2021-01-20 E. 652 class IRCUser(object):
2022-02-26 E. 653 def __init__(self, stream, address, irc_nick=None, username='', realname=None, is_service=False):
2021-01-20 E. 654 self.stream = stream
2021-01-31 E. 655 self.address = address[0]
2023-06-14 E. 656 self.port = str(address[1])
2022-02-12 E. 657 self.from_localhost = True if address[0].split('.')[0] == '127' else False
2021-01-31 E. 658 self.irc_nick = irc_nick
2021-02-10 E. 659 self.irc_username = str(username)
2021-01-31 E. 660 self.irc_realname = realname
2021-01-29 E. 661 self.registered = False
2021-01-27 E. 662 self.password = ''
2021-01-24 E. 663 self.recv_pass = ''
2021-02-07 E. 664 self.oper = False
02:14:36 ' 665 self.tls = False
' 666 self.bot = None
2022-02-26 E. 667 self.is_service = is_service
2021-02-20 E. 668 self.close_reason = ''
2021-01-28 E. 669
2021-02-01 E. 670 def get_irc_mask(self):
18:48:42 ' 671 return '{}!{}@{}'.format(self.irc_nick, self.irc_username, self.address)
' 672
2021-02-07 E. 673 def get_channels(self, irc):
02:14:36 ' 674 res = ''
' 675 for chan in irc.irc_channels.keys():
' 676 if self.irc_nick in irc.irc_channels[chan]:
' 677 res += irc.get_irc_op(self.irc_nick, chan) + chan + ' '
' 678 return res
' 679
2021-01-28 E. 680 def valid_nick(self, nick):
13:07:44 ' 681 if len(nick) <= NICK_MAX_LENGTH and nick[0] in VALID_IRC_NICK_FIRST_CHARS:
' 682 for x in nick[1:]:
' 683 if x not in VALID_IRC_NICK_CHARS:
2021-02-16 E. 684 return False
18:44:01 ' 685 return True
' 686 else: return False
2021-02-04 E. 687
23:26:44 ' 688 def del_from_channels(self, irc, channels=None):
' 689 for chan in channels if channels else irc.irc_channels.keys():
' 690 irc.irc_channels[chan].discard(self.irc_nick)
' 691 irc.irc_channels_ops[chan].discard(self.irc_nick)
' 692 irc.irc_channels_founder[chan].discard(self.irc_nick)
2021-12-13 E. 693
20:31:17 ' 694 def pam_auth(self, nick, pam, pam_group, recv_pass):
' 695 if not pam: return False
' 696
' 697 # Check if user is in groups (main or others)
' 698 if pam_group:
' 699 import pwd
' 700 import grp
' 701 try:
' 702 user_group_id = pwd.getpwnam(nick).pw_gid
' 703 group_data = grp.getgrnam(pam_group)
' 704 pam_group_id = group_data.gr_gid
' 705 group_members = group_data.gr_mem
' 706 check_group = user_group_id == pam_group_id \
' 707 or nick in group_members
' 708 except:
' 709 check_group = False
' 710 if not check_group: return False
' 711
' 712 # Check user authentication (via PAM)
' 713 import PAM
' 714 def pam_conv(auth, query_list, userData):
' 715 resp = []
' 716 resp.append((recv_pass, 0))
' 717 return resp
' 718 p = PAM.pam()
' 719 p.start('passwd')
' 720 p.set_item(PAM.PAM_USER, nick)
' 721 p.set_item(PAM.PAM_CONV, pam_conv)
' 722 try:
' 723 p.authenticate()
' 724 p.acct_mgmt()
' 725 except:
' 726 return False
' 727 else:
' 728 return True
' 729
2022-01-22 E. 730 def local_auth(self, nick, nicks, recv_pass, irc_pass, pam):
21:08:56 ' 731 return ( not pam
' 732 and nick in nicks
2021-12-13 E. 733 and recv_pass == irc_pass
20:31:17 ' 734 )