#!/usr/bin/env python #Copyright 2004,2005,2006 Sebastian Hagen # This file is part of eucharis. # eucharis is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 # as published by the Free Software Foundation # eucharis is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with eucharis; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import socket import re import time import logging import irc_base import socket_management import input_handlers class irc_client: def __init__(self, address, nick, username, realname, hostname=socket.gethostname(), dynamic_nick=True, umodes=[], channels=[]): self.network_channels = {} self.goal = 'continue' self.loggername = 'irc_client' self.logger = logging.getLogger(self.loggername) self.client_settings = { 'reconnect_delay':20, 'uplink_address':address } self.network_nick = {} self.network_nicks = {} self.local_nick = { 'nick':nick, 'suffix':'', 'username':username, 'hostname':hostname, 'realname':realname, 'umodes':umodes, 'channels':channels, } self.regexps = { 'client_source':re.compile('.+!.+@.+') } self.dynamic_nick = dynamic_nick self.connection_reset_variables() self.handlers = handlers_irc_client(parent=self, output=self.send_output_heuristic, parent_loggername=self.loggername) def send_command_null(self, source=None, command=None, arguments=None): pass def send_output_heuristic(self, source=None, command=None, arguments=[]): command = command.upper() self.send_command(source=source, command=command, arguments=arguments) def connection_reset_variables(self): """Set connection-instance-specific variable to default (empty) values.""" self.irc_connection = None self.send_data = None self.send_command = self.send_command_null for dictionary in (self.network_channels, self.network_nick): for element_name in dictionary.keys(): del dictionary[element_name] self.uplink_data = { 'name':None, 'protocol_version':None, 'versionflags':None, 'versionstring':None, 'chantypes':['#','&'], #Modes for 0:nickmask-list manipulation; 1:parameter (un)setting, 2.parameter setting, but not unsetting, 3.binary modes 'chanmodes':[[],[],[],[]], 'chanflags':['o', 'v'], 'prefixes':['@','+'], 'NICKLEN':None, 'KICKLEN':None, 'MODES':None, 'TOPICLEN':None, } self.local_nick['suffix'] = '' def connection_init_delayed(self): socket_management.Timer(self.client_settings['reconnect_delay'], self.connection_init, self) def connection_init(self): if (self.irc_connection != None): self.irc_connection.clean_up() self.connection_reset_variables() try: self.irc_connection = irc_base.irc_base_connection(self.client_settings['uplink_address'], input_handler=self.process_input, close_handler=self.connection_close_handle, parent_loggername=self.loggername) except socket.error: self.logger.log(40, 'Connection attempt to %s failed:' % (self.client_settings['uplink_address'],), exc_info=True) if (self.goal == 'continue'): self.connection_init_delayed() else: self.send_data = self.irc_connection.send_data self.send_command = self.irc_connection.send_command self.send_command(command='USER', arguments=[self.local_nick['username'], self.local_nick['hostname'], self.client_settings['uplink_address'][0], self.local_nick['realname']]) self.send_command(command='NICK', arguments=[self.local_nick['nick']]) def connection_close_handle(self, irc_base): self.connection_reset_variables() self.handlers.event_connection(statechange='down', data=self.client_settings['uplink_address']) if (self.goal == 'continue'): self.connection_init_delayed() def process_input(self, irc_base, input_dictionary): """Parse and process full input lines.""" source_raw = input_dictionary['source'] command = input_dictionary['command'] arguments = raw_arguments = input_dictionary['arguments'][:] source = None if (type(source_raw) == str): source_raw = source_raw.lstrip(':') if (source_raw != self.uplink_data['name']): source = source_raw if ((type(source) == str) and (self.regexps['client_source'].match(source))): (source_nick, source_userhost) = source.split('!',1) (source_user, source_host) = source_userhost.split('@',1) del source_userhost else: source_nick = source_user = source_host = None target_nick = None if (command.isdigit()): if (len(arguments) > 0): target_nick = arguments.pop(0) if (command == 'PING'): self.send_command(command='PONG', arguments=arguments) elif (command == 'PONG'): pass elif (command == 'NICK'): if (len(arguments) < 1): self.logger.log(30, 'Got NICK commandline with too few parameters (exepcted at least one). Dump: ' + str(input_dictionaty)) else: if (source_nick == self.network_nick['nick']): self.change_nick_local(arguments[0]) self.change_nick_network() elif (command == 'JOIN'): if (len(arguments) < 1): self.logger.log(30, 'Got JOIN commandline with too few parameters (expected at least one). Dump: ' + str(input_dictionary)) else: channel = arguments[0] if (source_nick == self.network_nick['nick']): self.nick_local_modify_list(parameter='channels', modification='add', input_data=channel) self.send_command(command='MODE', arguments=[channel]) self.send_command(command='MODE', arguments=[channel, 'b']) self.send_command(command='MODE', arguments=[channel, 'e']) elif (command == 'PART'): if (len(arguments) < 1): self.logger.log(30, 'Got PART commandline with too few parameters (expected at least one). Dump: ' + str(input_dictionary)) else: channel = arguments[0] if (source_nick == self.network_nick['nick']): self.nick_local_modify_list(parameter='channels', modification='remove', input_data=channel) elif (command == 'KICK'): if (len(arguments) < 2): self.logger.log(30, 'Got KICK commandline with too few parameters (expected at least two). Dump: ' + str(input_dictionary)) else: channel = arguments[0] victim = arguments[1] if (victim == self.network_nick['nick']): self.nick_local_modify_list(parameter='channels', modification='remove', input_data=channel) elif (command == 'MODE'): if (len(arguments) < 2): self.logger.log(30, 'Got MODE commandline with too few parameters (expected at least two). Dump: %s' % (input_dictionary,)) else: target = arguments[0] mode_string = arguments[1] if (len(arguments) >= 3): mode_arguments = arguments[2:] else: mode_arguments = [] if (len(target) < 1): self.logger.log(30, 'Got MODE commandline with too few characters in target arguments (expected at least one). Dump: %s' % (input_dictionary,)) elif (target[0] in self.uplink_data['chantypes']): #This is a channel mode - line. self.channeltracking_modify_modes(channel=target, mode_string=mode_string, mode_arguments=mode_arguments) elif ((source != None) and command.isdigit() and (int(command) >= 100)): #Some serveradmins reportedly like to fake these from other network servers on occasion. #We will not parse them further if they don't appear to have originated from our server, #but instead log the occurance. self.logger.log(30, 'Got %s command from %s which does not appear to be our peer. Ignoring. Dump: %s' % (command, source, input_dictionary)) elif (command == '001'): #RPL_WELCOME "Welcome to the Internet Relay Network !@" self.logger.log(20, 'Got 001 line. Trying to use data.') if (source_raw != None): self.uplink_data['name'] = source_raw text_split = arguments[-1].split() userstring_parse_failure = True if (len(text_split) < 1): self.logger.log(40, "Can't extract own nickuserhost-mask from 001 commandline (insufficient text). Dump: " + str(input_dictionary)) else: userinfo_string = text_split[-1] userinfo_string_split_1 = userinfo_string.split('!') if (len(userinfo_string_split_1) != 2): self.logger.log(40, "Can't parse userinfo_string from 001 commandline (invalid number of '!'). Dump: " + userinfo_string) else: n001_nick = userinfo_string_split_1[0] userinfo_string_split_2 = userinfo_string_split_1[1].split('@') if (len(userinfo_string_split_2) != 2): self.logger.log(40, "Can't parse userinfo_string from 001 commandline (invalid number of '@' after first '!'). Dump: " + userinfo_string) else: n001_username, n001_host = userinfo_string_split_2 self.add_nick_base(n001_nick) self.network_nick['username'] = n001_username self.network_nick['host'] = n001_host userstring_parse_failure = False if (userstring_parse_failure): if (target_nick != None): self.logger.log(20, 'Using backup method to determine own nick from 001 numeric.') self.add_nick_base(target_nick) else: self.logger.log(40, 'Failure to use backup method to determine own nick from 001 numeric.') self.try_goals() elif (command == '004'): #RPL_MYINFO " " arguments_004 = arguments[:] if (len(arguments_004) >= 4): self.logger.log(20, 'Trying to get servername and -version from 004 line.') self.uplink_data['name'] = arguments_004[0] if (self.uplink_data['versionstring'] == None): self.uplink_data['versionstring'] = arguments_004[1] else: self.logger.log(30, 'Got 004 line in unknown format. Discarding. Dump: ' + str(input_dictionary)) self.handlers.event_connection(statechange='up', data=self.client_settings['uplink_address']) elif (command == '005'): #This is intended to implement http://www.ietf.org/internet-drafts/draft-brocklesby-irc-isupport-03.txt #where possible and useful, but might also support unreal-specific syntax for the moment. self.logger.log(20, 'Got 005 line. Trying to interpret as RPL_ISUPPORT. This does not conform with rfc2812, but afaik no one really uses their definition of numeric 005 anyway.') for argument in arguments[:]: argument_split = argument.split('=',1) if (len(argument_split) == 2): argument_name = argument_split[0] argument_data = argument_split[1] if (argument_name == 'CHANMODES'): chanmode_list = argument_data.split(',') if (len(chanmode_list) < 4): self.logger.log(30, 'Got 005 line with argument CHANMODES and insufficient data elements (expected at least 4). Discarding element. Dump:' + str(input_dictionary)) else: for index in range(4): self.uplink_data['chanmodes'][index] = list(chanmode_list[index]) elif (argument_name == 'CHANTYPES'): self.uplink_data['chantypes'] = list(argument_data) elif (argument_name in ('NICKLEN', 'KICKLEN', 'MODES', 'TOPICLEN')): try: argument_data_integer = int(argument_data) except ValueError: self.logger.log(30, 'Got 005 line with argument %s and invalid string argument %s (expected integer).' % (argument_name, argument_data,)) self.uplink_data[argument_name] = argument_data_integer elif (argument_name == 'PREFIX'): argument_data_split = argument_data.split(')',1) if (len(argument_data_split) != 2): self.logger.log(30, 'Got 005 line with argument PREFIX and invalid (does not contain at least one ")") string argument %s.' % (argument_data,)) else: if (argument_data_split[0][0] != '('): self.logger.log(30, 'Got 005 line with argument PREFIX and invalid (does not begin with "(") string argument %s.' % (argument_data,)) else: argument_data_split[0] = argument_data_split[0][1:] if (len(argument_data_split[0]) != len(argument_data_split[1])): self.logger.log(30, 'Got 005 line with argument PREFIX and invalid (prefix and mode strings have different lengths) string argument %s.' % (argument_data,)) else: self.uplink_data['chanflags'] = list(argument_data_split[0]) self.uplink_data['prefixes'] = list(argument_data_split[1]) elif (command == '324'): #RPL_CHANNELMODEIS " " if (len(arguments) < 2): self.logger.log(30, 'Got 324 numeric with insufficient arguments (expected at least two). Dump: %s' % (input_dictionary,)) else: channel = arguments[0] modestring = arguments[1] if (len(arguments) >= 3): mode_arguments = arguments[2:] else: mode_arguments = [] self.channeltracking_modify_modes(channel=channel, mode_string=modestring, mode_arguments=mode_arguments) elif (command == '329'): #nonstandard " " if (len(arguments) < 2): self.logger.log(30, 'Got 329 numeric with insufficient arguments (expected at least two). Dump: %s' % (input_dictionary,)) else: channel = arguments[0] timestamp = arguments[1] self.channeltracking_set_parameter(channel=channel, parameter='timestamp', data=timestamp) elif (command == '331'): #RPL_NOTOPIC " :No topic is set" if (len(arguments) < 1): self.logger.log(30, 'Got 331 numeric with insufficient arguments (expected at least one). Dump: %s' % (input_dictionary,)) else: channel = arguments[0] self.channeltracking_set_parameter(channel=channel, parameter='topic', data=None) elif (command == '332'): #RPL_TOPIC " :" if (len(arguments) < 2): self.logger.log(30, 'Got 332 numeric with insufficient arguments (expected at least two). Dump: %s' % (input_dictionary,)) else: channel = arguments[0] topic = arguments[1] self.channeltracking_set_parameter(channel=channel, parameter='topic', data=topic) elif (command == '333'): #nonstandard " " if (len(arguments) < 3): self.logger.log(30, 'Got 333 numeric with insufficient arguments (expected at least three). Dump: %s' % (input_dictionary,)) else: channel, topic_source, timestamp = arguments[0:3] self.channeltracking_set_parameter(channel=channel, parameter='topic_source', data=topic_source) self.channeltracking_set_parameter(channel=channel, parameter='topic_timestamp', data=timestamp) elif (command in ('348', '367')): #348: RPL_EXCEPTLIST " " #367: RPL_BANLIST " " if (len(arguments) < 2): self.logger.log(30, 'Got %s numeric with insufficient arguments (expected at least two). Dump: %s' % (command, input_dictionary)) else: channel, datamask = arguments[:2] if (command == '348'): listname = 'e' elif (command == '367'): listname = 'b' self.channeltracking_modify_list(channel=channel, listname=listname, action='add', entry=datamask) elif (command == '353'): #RPL_NAMREPLY "( "=" / "*" / "@" ) :[ "@" / "+" ] *( " " [ "@" / "+" ] )" #"@" is used for secret channels, "*" for private channels, and "=" for others (public channels) if (len(arguments) < 3): self.logger.log(30, 'Got 353 numeric with insufficient arguments (expected at least three). Dump: %s' % (command, input_dictionary)) else: channel = arguments[1] nickstring = arguments[-1] self.channeltracking_process_names(channel=channel, nickstring=nickstring) elif (command == '433'): #ERR_NICKNAMEINUSE " :Nickname is already in use" if (len(arguments) == 0): self.logger.log(35, 'Got numeric 344 (ERR_NICKNAMEINUSE) without any arguments. Ignoring. Dump: ' + str(input_dictionary)) else: if (target_nick == '*'): old_nick = None elif (target_nick == None): if ('nick' in self.network_nick): old_nick = self.network_nick['nick'] else: old_nick = None else: old_nick = target_nick blocked_nick = arguments[0] if ((old_nick == None) or (re.compile(re.escape(self.local_nick['nick']) + '.*').match(blocked_nick) == None)): if (self.dynamic_nick): self.choose_nick_alternative() self.change_nick_network() else: self.logger.log(30, "Preferred nick is blocked, and dynamic_nick is False. Terminating connection.") self.goal = 'stop' self.irc_connection.clean_up() self.irc_connection = None self.handlers.process_input(source, command, arguments) def add_nick_base(self, nick): self.network_nick = { 'nick':nick, 'username':None, 'host':None, 'umodes':[], 'away':None, } self.network_nicks = {nick:self.network_nick} def return_nick(self): return self.local_nick['nick'] + self.local_nick['suffix'] def choose_nick_alternative(self, force=False): if (self.dynamic_nick or force): old_suffix = self.local_nick['suffix'] if (old_suffix == ''): old_integer = 0 else: old_integer = int(old_suffix) self.local_nick['suffix'] = str(old_integer + 1) def change_nick_local(self, nick): self.network_nick['nick'] = nick self.network_nicks = {'nick':self.network_nick} def change_nick_network(self, nick=None): if (nick == None): nick = self.return_nick() self.send_command(command='NICK', arguments=[nick]) def nick_local_modify_list(self, parameter, modification, input_data): if (parameter == 'channels'): channel = input_data if (modification == 'add'): if not (channel in self.network_channels): self.channeltracking_add(channel) else: self.logger.log(30, 'Trying to join channel ' + str(channel) + " I'm already on.") elif (modification == 'remove'): if (channel in self.network_channels): del(self.network_channels[channel]) else: self.logger.log(30, 'Trying to leave channel ' + str(channel) + " but don't remember having been there.") else: raise ValueError('Invalid value %s for modification for parameter "channels".' % (str(modification),)) self.try_goal_channels() def channeltracking_add(self, channel): self.network_channels[channel] = { 'nicks':[], 'topic':None, 'topic_source':None, 'topic_timestamp':None, 'timestamp':None, 'chanmodes': {}, 'nicks':[], } for mode_list in (self.uplink_data['chanmodes'][:3] + [self.uplink_data['chanflags']]): for list_mode in mode_list: self.network_channels[channel]['chanmodes'][list_mode] = [] def channeltracking_set_parameter(self, channel, parameter, data): if not (channel in self.network_channels): self.logger.log(30, 'Trying to set parameter for nontracked channel. Ignoring. Channel: %s parameter: %s data: %s' % (channel, parameter, data)) return False if (parameter in ('topic', 'topic_source', 'topic_timestamp', 'timestamp')): if (parameter in ('topic_timestamp', 'timestamp')): try: data = int(data) except ValueError: self.logger.log(30, "Trying to set integer parameter %s of channel %s to %s, which couldn't be converted to integer. Ignoring." % (parameter, channel, data)) return False self.network_channels[channel][parameter] = data else: raise ValueError('Invalid parameter %s.' % (parameter,)) def channeltracking_modify_list(self, channel, listname, action, entry): if not (channel in self.network_channels): self.logger.log(30, 'Trying to modify list for nontracked channel. Ignoring. Channel: %s listname: %s action: %s entry: %s' % (channel, listname, action, entry)) return False if (listname in ('b', 'e')): if not (listname in self.network_channels[channel]['chanmodes']): self.logger.log(30, 'Trying to modify nonexisting mode-list with name "%s" of channel %s. Adding entry to chanmodes-dictionary.' % (listname, channel)) self.network_channels[channel]['chanmodes'][listname] = [] list_data = self.network_channels[channel]['chanmodes'][listname] elif (listname == 'nicks'): list_data = self.network_channels[channel][listname] else: raise ValueError('Invalid listname %s.' % (listname,)) if (action == 'add'): if (entry in list_data): self.logger.log(30, 'Trying to add entry "%s" to list %s of channel %s which is already in that list. Ignoring.' % (entry, listname, channel)) else: list_data.append(entry) elif (action == 'remove'): if (entry in list_data): list_data.remove(entry) else: self.logger.log(30, 'Trying to remove entry "%s" from list %s of channel %s which is not in that list. Ignoring.' % (entry, listname, channel)) def channeltracking_modify_modes(self, channel, mode_string, mode_arguments): (modes_A, modes_B, modes_C, modes_D) = self.uplink_data['chanmodes'] flags = self.uplink_data['chanflags'] if not (channel in self.network_channels): self.logger.log(30, 'Trying to modify modes for nontracked channel. Ignoring. Channel: %s mode_string: %s mode_arguments: %s' % (channel, mode_string, mode_arguments)) return False for mode in (modes_A + modes_B + modes_C + flags): if not mode in (self.network_channels[channel]['chanmodes']): self.logger.log(30, 'Channel %s does not have list_entry for mode %s of type A, B or C or flag. Adding it.' % (channel, mode)) self.network_channels[channel]['chanmodes'][mode] = [] current_modifier = None while (len(mode_string) > 0): current_character = mode_string[0] mode_string = mode_string[1:] if ((current_character == '+') or (current_character == '-')): current_modifier = current_character elif (current_character in modes_D): if (current_modifier == '+'): self.network_channels[channel]['chanmodes'][current_character] = None if (current_modifier == '-'): if (current_character in self.network_channels[channel]['chanmodes']): del(self.network_channels[channel]['chanmodes'][current_character]) elif (current_character in (modes_A + modes_B + modes_C + flags)): if ((current_character in modes_C) and (current_modifier != '+')): current_argument = None else: try: current_argument = mode_arguments.pop(0) except IndexError: self.logger.log(30, 'Missing data argument for mode %s. Ignoring.' % (current_character,)) continue if (current_modifier == '+'): if (current_character in (modes_A + flags)): if not (current_argument in self.network_channels[channel]['chanmodes'][current_character]): self.network_channels[channel]['chanmodes'][current_character].append(current_argument) else: self.network_channels[channel]['chanmodes'][current_character] = current_argument elif (current_modifier == '-'): if (current_character in (modes_A + flags)): if (current_argument in self.network_channels[channel]['chanmodes'][current_character]): self.network_channels[channel]['chanmodes'][current_character].remove(current_argument) else: self.network_channels[channel]['chanmodes'][current_character] = None def channeltracking_process_names(self, channel, nickstring): if not (channel in self.network_channels): self.logger.log(30, 'Trying to process names reply for nontracked channel. Ignoring. Channel: %s nickstring: %s' % (channel, nickstring)) return False nicklist = nickstring.split() for nick_entry in nicklist: flags = [] while (len(nick_entry) > 0): if (nick_entry[0] in self.uplink_data['prefixes']): index = self.uplink_data['prefixes'].index(nick_entry[0]) flags.append(self.uplink_data['chanflags'][index]) nick_entry = nick_entry[1:] else: break if (len(nick_entry) < 1): self.logger.log(30, 'Got supposed nick_entry "%s", consisting entirely of prefix characters, in nickstring "%s". Ignoring it.' % (nick_entry, nickstring)) else: self.channeltracking_modify_list(channel=channel, listname='nicks', action='add', entry=nick_entry) for flag in flags: if not (flag in self.network_channels[channel]['chanmodes']): self.logger.log(30, 'Channel %s does not have list_entry for flag %s. Adding it.' % (channel, flag)) self.network_channels[channel]['chanmodes'][flag] = [] if (nick_entry in self.network_channels[channel]['chanmodes'][flag]): self.logger.log(30, 'Trying to set flag %s for nick %s on channel %s which is already set. Ignoring.' % (flag, nick_entry, channel)) else: self.network_channels[channel]['chanmodes'][flag].append(nick_entry) def try_goals(self): self.try_goal_nick() self.try_goal_channels() def try_goal_nick(self): if not (self.local_nick['nick'] == self.network_nick['nick']): self.send_command(command='ISON', arguments=[self.local_nick['nick']]) def try_goal_channels(self): for channel in self.local_nick['channels']: if not (channel in self.network_channels): self.send_command(command='JOIN', arguments=[channel]) for channel in self.network_channels: if not (channel in self.local_nick['channels']): self.send_command(command='PART', arguments=[channel]) def nick_our(self, nick): if (('nick' in self.network_nick) and (self.network_nick['nick'] == nick)): return True else: return False class handlers_irc_client(irc_base.handlers_irc): def __init__(self, parent, output, parent_loggername=''): irc_base.handlers_irc.__init__(self, parent=parent, output=output, event_types=['statechange_nicks', 'statechange_connection', 'command', 'CTCP', 'snotice'], parent_loggername=parent_loggername) self.strings = { 'ip':r'\d{1,3}(?:\.\d{1,3}){3}', } self.strings['peer_block'] = r'\[(?P.+)?@(?P%s)(?:\.(?P\d{,5}))??\]' % (self.strings['ip'],) self.regexps = { 1:{ 'unreal_snotice':re.compile(r'\*\*\* (Notice|Global|LocOps) -- (.+)'), }, 2:{ 'error':re.compile(r'ERROR :?from (?P\S+?)(?:\[(?P\S+)\])? -- (?P.+)'), }, 3:{ 'connect_out_try': re.compile(r'Connection to (?P.*\..*)\[(?P.+)\] activated.'), 'connect_out_timeout': re.compile(r'No response from (?P.*\..*), closing link'), 'connect_out_rejected_ip': re.compile(r'(?:Closing Link: (?:\S*\.\S*)\[%s\] \(No C/N conf lines\))|(?:Link denied \(No matching link configuration\) %s)' % (self.strings['ip'], self.strings['peer_block'])), 'connect_out_rejected_duplicate_warn': re.compile('rServer (?P.*\..*) already exists from (?P.*\..*)'), 'connect_out_rejected_duplicate': re.compile(r'Closing Link: \[(?P' + self.strings['ip'] + r')\] \((?P.*)\)'), 'connect_out_fail': re.compile(r'Closing link: Write error: (?P.*) - (?P.*\..*)'), 'connect_success': re.compile('\\(\x02link\x02\\) ' + r'(ZIP)?Link (?P\S*\.\S*) -> (?P.*\..*)' + self.strings['peer_block'] + ' established'), 'connect_in_denied_ip': re.compile(r"(?:Link denied for (?P.*\..*)\((?P.+)?@(?P" + self.strings['ip'] + r")\) \(Server is in link block but IP/host didn't match\) " + self.strings['peer_block'] + r')|(?:Access denied \(No matching N\:line\) \[' + self.strings['ip'] +r'\])'), 'connection_loss':re.compile(r'Server (?P.*\..*) closed the connection'), } } self.settings_network['chantypes'] = parent.uplink_data['chantypes'] self.command_prefixes = () def process_input(self, source, command, arguments): irc_base.handlers_irc.process_input(self, source, command, arguments) if ((command=='NOTICE') and ((source == None) or ('.' in source)) and (len(arguments) >= 2)): #This is a SNOTICE. self.process_snotice_stage1(source=source, command=command, arguments=arguments) def process_snotice_stage1(self, source, command, arguments): text = arguments[-1] unreal_snotice_match = self.regexps[1]['unreal_snotice'].match(text) if (unreal_snotice_match != None): (notice_name, event_text) = unreal_snotice_match.groups() if (notice_name in ('Notice', 'LocOps')): scope = 'local' elif (notice_name == 'Global'): scope = 'global' else: scope = None if (scope == None): self.logger.log(30, 'Unable to determine scope of unreal-like snotice. Dump: %s' % (data,)) else: notice_data = { 'notice_scope':scope, 'notice_source':source, 'notice_text':text, 'event_text':event_text } notice_data.update(self.process_snotice_stage2(event_text=notice_data['event_text'])) notice_data.update(self.process_snotice_stage3(notice_data)) self.handle_event(event_type='snotice', event=notice_data['event2_type'], data=notice_data, permissions=[]) def process_snotice_stage2(self, event_text): for regexp_name in self.regexps[2].keys(): match = self.regexps[2][regexp_name].match(event_text) if (match != None): match_data = match.groupdict() match_data['event_type'] = regexp_name return match_data return {'event_type':'generic'} def process_snotice_stage3(self, notice_data): event_type = notice_data['event_type'] if (event_type == 'error'): text = notice_data['error_text'] else: text = notice_data['event_text'] scope = notice_data['notice_scope'] for regexp_name in self.regexps[3].keys(): match = self.regexps[3][regexp_name].match(text) if (match != None): match_data = match.groupdict() match_data['event2_type'] = regexp_name return match_data break return {'event2_type':None}