class Undo(): undo_stack = [] redo_stack = [] auth_commands = { 'undo': 30, 'redo': 30, } command_descriptions = { 'undo': """ Undoes the last database action (such as add or remove) Note: Using "undo, redo, undo" has the same final effect as simply doing "undo" once, as "undo" will also undo a "redo" Syntax: undo """, 'redo': """ redoes the last database action that was undone using "undo" (such as add or remove) Note: Using "undo, redo, undo" has the same final effect as simply doing "undo" once, as "undo" will also undo a "redo" Syntax: redo """, } def __init__(self): event_handler.hook('modulehandler:before_init_modules', self.on_before_init_modules) event_handler.hook('modulehandler:after_load_modules', self.on_after_load_modules) event_handler.hook('help:get_command_description', self.get_command_description) event_handler.hook('commands:get_auth_commands', self.get_auth_commands) event_handler.hook('commands:do_auth_command', self.do_auth_command) event_handler.hook('db_commands:on_after_add', self.on_after_add) event_handler.hook('db_commands:on_before_remove', self.on_before_remove) event_handler.hook('db_commands:on_after_remove', self.on_after_remove) event_handler.hook('db_commands:on_before_set', self.on_before_set) event_handler.hook('db_commands:on_after_set', self.on_after_set) self.callback_handler = CallbackHandler('undo') def on_before_init_modules(self, module_handler, bot, event_handler, first_time): # we're about to be replaced! bot.module_parameters['undo:undo_stack'] = self.undo_stack bot.module_parameters['undo:redo_stack'] = self.redo_stack def on_after_load_modules(self, module_handler, bot, event_handler, first_time): self.undo_stack = bot.module_parameters.pop('undo:undo_stack', []) self.redo_stack = bot.module_parameters.pop('undo:redo_stack', []) def get_command_description(self, bot, command): if command in self.command_descriptions: return self.command_descriptions[command] def get_auth_commands(self, bot): return self.auth_commands def do_auth_command(self, bot, connection, event, command, parameters, reply_target, auth_level): if command not in self.auth_commands: return False # not for us if command == 'undo': if not self.undo_stack: return False undo = self.undo_stack.pop() if undo: undo['function'](*undo['parameters']) self.add_to_stack(bot, self.redo_stack, undo['alt_function'], undo['alt_parameters'], undo['function'], undo['parameters']) bot.send(connection, reply_target, bot.db.get_random('yes'), event) event_handler.fire('undo:on_undo', (bot, connection, event, command, parameters, reply_target, auth_level)) return True elif command == 'redo': if not self.redo_stack: return False redo = self.redo_stack.pop() if redo: redo['function'](*redo['parameters']) self.add_to_stack(bot, self.undo_stack, redo['alt_function'], redo['alt_parameters'], redo['function'], redo['parameters']) bot.send(connection, reply_target, bot.db.get_random('yes'), event) event_handler.fire('undo:on_redo', (bot, connection, event, command, parameters, reply_target, auth_level)) return True return False def on_after_add(self, bot, connection, event, reply_target, auth_level, key, value): self.add_to_stack(bot, self.undo_stack, bot.db.delete, (key, value), bot.db.add, (key, value)) def on_before_remove(self, bot, connection, event, reply_target, auth_level, key, value): db_key, old_value = bot.db.get_key_value(key, value) if old_value: self.callback_handler.add( 'remove|%s|%s' % (key, value), self.stack_undo_remove, (bot, db_key, old_value, value) ) def on_after_remove(self, bot, connection, event, reply_target, auth_level, key, value): self.callback_handler.run('remove|%s|%s' % (key, value)) def on_before_set(self, bot, connection, event, reply_target, auth_level, key, values): old_values = bot.db.get_all(key) if old_values: self.callback_handler.add( 'set|%s|%s' % (key, '|'.join(values)), self.stack_undo_set, (bot, key, old_values, values) ) def on_after_set(self, bot, connection, event, reply_target, auth_level, key, values): self.callback_handler.run('set|%s|%s' % (key, '|'.join(values))) def stack_undo_remove(self, bot, key, old_value, new_value): self.add_to_stack(bot, self.undo_stack, bot.db.add, (key, old_value), bot.db.delete, (key, new_value)) def stack_undo_set(self, bot, key, old_values, new_values): self.add_to_stack(bot, self.undo_stack, bot.db.set, (key, old_values), bot.db.set, (key, new_values)) def add_to_stack(self, bot, stack, function, parameters, alt_function, alt_parameters): if not isinstance(parameters, (tuple, list)): parameters = (parameters, ) stack.append({ 'function': function, 'parameters': parameters, 'alt_function': alt_function, 'alt_parameters': alt_parameters, }) stack_difference = len(stack) - int(bot.db.get('undo_stack_size', default_value = 10)) if stack_difference > 0: stack = stack[stack_difference:]
class Channels(): def __init__(self): event_handler.hook('irc:on_invite', self.on_invite) event_handler.hook('irc:on_kick', self.on_kick) event_handler.hook('irc:on_join', self.on_join) event_handler.hook('irc:on_namreply', self.on_namreply) event_handler.hook('irc:on_channelisfull', self.on_needinvite) event_handler.hook('irc:on_inviteonlychan', self.on_needinvite) event_handler.hook('irc:on_badchannelkey', self.on_needinvite) event_handler.hook('send:on_before_send_message', self.on_before_send_message) event_handler.hook('bot:on_quit', self.on_quit) self.callback_handler = CallbackHandler('channels') def on_invite(self, bot, connection, event): # invites can only be sent by channel ops, so we don't need to # worry too much about this being abused channel = event.arguments[0].lower() bot.db.add('channel|' + bot.server_name, channel) connection.join(channel) def on_kick(self, bot, connection, event): # if we get kicked, remove the channel if event.arguments[0] == connection.get_nickname(): bot.db.delete('channel|' + bot.server_name, event.target) def on_join(self, bot, connection, event): if event.source.nick == connection.get_nickname(): # register a callback for when we get the namreply for this channel, and know who's in it # (necessary for use of !someone in greeting messages) self.callback_handler.add('greetchannel-%s' % event.target, self.greet_channel, (bot, event.target, connection, event)) if event.source.nick == connection.get_nickname() and event.target == "#blindsight": bot.send(connection, "IdleSightBot", "LOGIN puppy mikeyy", event) def on_namreply(self, bot, connection, event): channel = event.arguments[1] # we just got the information that populates the channel user list? now we can greet the channel self.callback_handler.run('greetchannel-%s' % channel) # if there's only one nick in here, it's us, so we remove the channel from autojoin if len(bot.channels[channel].users()) == 1: bot.db.delete('channel|' + bot.server_name, channel) def greet_channel(self, bot, channel, connection, event): bot.send(connection, channel, bot.db.get_random('join', channel = channel), event) def on_needinvite(self, bot, connection, event): channel = event.arguments[0] if channel and channel[0] == '#': for command in bot.db.get_all('invite_command|' + bot.server_name): target, message = command.split('|', 1) message = message % {'channel': channel} bot.send(connection, target, message, event, False) def on_before_send_message(self, bot, connection, target, message, event, process_message): if target[0] == '#' and target not in bot.channels: return False return True def on_quit(self, bot, connection, event, message): homes = bot.db.get_all('home|' + bot.server_name) for channel in bot.channels: if channel not in homes: # say goodbye! bot.send(connection, channel, bot.db.get_random('part'), event) connection.part(channel, message)
class Alerts(): def __init__(self): event_handler.hook('undo:on_undo', self.on_undo) event_handler.hook('undo:on_redo', self.on_redo) event_handler.hook('db_commands:on_after_add', self.on_after_add) event_handler.hook('db_commands:on_before_remove', self.on_before_remove) event_handler.hook('db_commands:on_after_remove', self.on_after_remove) event_handler.hook('db_commands:on_before_set', self.on_before_set) event_handler.hook('db_commands:on_after_set', self.on_after_set) self.callback_handler = CallbackHandler('alerts') def on_undo(self, bot, connection, event, command, parameters, reply_target, auth_level): source = hasattr(event.source, 'nick') and event.source.nick or event.source message = 'Undid last for %s in %s' % (source, event.target) logging.info(message) for contact in bot.db.get_all('alert_contact'): bot.send(connection, contact, message, event, False) def on_redo(self, bot, connection, event, command, parameters, reply_target, auth_level): source = hasattr(event.source, 'nick') and event.source.nick or event.source message = 'Redid last for %s in %s' % (source, event.target) logging.info(message) for contact in bot.db.get_all('alert_contact'): bot.send(connection, contact, message, event, False) def on_after_add(self, bot, connection, event, reply_target, auth_level, key, value): source = hasattr(event.source, 'nick') and event.source.nick or event.source message = 'Learned "%s = %s" from %s in %s' % (key, value, source, event.target) logging.info(message) for contact in bot.db.get_all('alert_contact'): bot.send(connection, contact, message, event, False) def on_before_remove(self, bot, connection, event, reply_target, auth_level, key, value): db_key, old_value = bot.db.get_key_value(key, value) if db_key and old_value: self.callback_handler.add( 'remove|%s|%s' % (key, value), self.send_remove_message, (bot, connection, event, db_key, old_value) ) def on_after_remove(self, bot, connection, event, reply_target, auth_level, key, value): self.callback_handler.run('remove|%s|%s' % (key, value)) def on_before_set(self, bot, connection, event, reply_target, auth_level, key, values): old_values = bot.db.get_all(key) if old_values: self.callback_handler.add( 'set|%s|%s' % (key, '|'.join(values)), self.send_set_message, (bot, connection, event, key, old_values, values) ) def on_after_set(self, bot, connection, event, reply_target, auth_level, key, values): self.callback_handler.run('set|%s|%s' % (key, '|'.join(values))) def send_remove_message(self, bot, connection, event, key, old_value): source = hasattr(event.source, 'nick') and event.source.nick or event.source message = 'Forgot "%s = %s" for %s in %s' % (key, old_value, source, event.target) logging.info(message) for contact in bot.db.get_all('alert_contact'): bot.send(connection, contact, message, event, False) def send_set_message(self, bot, connection, event, key, old_values, new_values): source = hasattr(event.source, 'nick') and event.source.nick or event.source message = '%s set from "%s" to "%s" by %s in %s' % (key, ', '.join(old_values), ', '.join(new_values), source, event.target) logging.info(message) for contact in bot.db.get_all('alert_contact'): bot.send(connection, contact, message, event, False)
class Commands(): def __init__(self): # commands get checked first - other modules shouldn't be able to override commands from happening event_handler.hook('messages:on_handle_messages', self.on_handle_message, 0) event_handler.hook('commands:do_command', self.do_command) event_handler.hook('irc:on_whoisaccount', self.on_whoisaccount) event_handler.hook('irc:on_endofwhois', self.on_endofwhois) self.callback_handler = CallbackHandler('commands') def on_handle_message(self, bot, connection, event, message, is_public, is_action, reply_target, auth_level): if not is_action: command = None if not is_public: command = message.strip() else: # also try splitting by comma # also, allow any of the nick aliases to be after the colon / comma message_split = message.split(':', 1) if len(message_split) == 2 and message_split[0].lower().strip() == bot.connection.get_nickname().lower(): command = message_split[1] # if sent in private message or prefixed by our name, try it as a command if command: return self.do_command(bot, connection, event, command, reply_target, auth_level) # if we reach here, we didn't handle this message return False def do_command(self, bot, connection, event, command, reply_target, auth_level): original_command = command try: command, parameters = command.strip().split(' ', 1) except ValueError: command = command parameters = '' command = command.strip().lower() command_aliases = {k.strip().lower(): v.strip().lower() for k, v in (v.split('=', 1) for v in bot.db.get_all('command_alias', '%=%'))} if command in command_aliases: command = command_aliases[command] auth_commands = {} for result in event_handler.fire('commands:get_auth_commands', bot): if result: auth_commands.update(result) if '=' in parameters and command not in auth_commands: parameters = original_command command = 'add' if command in auth_commands: if auth_level is None: auth_levels = [r for r in event_handler.fire('commands:on_get_auth_level', (bot, connection, event, event.source.nick)) if isinstance(r, int)] if auth_levels: auth_level = max(auth_levels) else: self.callback_handler.add( 'whois-' + event.source.nick, self.repeat_message_event, { 'bot': bot, 'connection': connection, 'event': event, 'auth_level': 0, } ) connection.whois(event.source.nick) return True # we'll come back when we have more information logging.info('"%s" command issued by %s (%d) in %s' % ( event.arguments[0], event.source, auth_level, event.target, )) if auth_level >= auth_commands[command]: if not any(result is True for result in event_handler.fire('commands:do_auth_command', (bot, connection, event, command, parameters, reply_target, auth_level))): bot.send(connection, reply_target, bot.db.get_random('no'), event) # we've handled the command, whether it failed or not, # so we return True to stop any further message processing return True else: logging.info('Command not accepted (auth level too low)') # if we reach here, we didn't have any use for this command # return False so the message will be tested against the response database return False def on_whoisaccount(self, bot, connection, event): self.callback_handler.update_parameters( 'whois-' + event.arguments[0], { 'auth_level': int(bot.db.get( 'user|%s|%s' % (bot.server_name, event.arguments[1]), default_value = 0 )) } ) def on_endofwhois(self, bot, connection, event): self.callback_handler.run('whois-' + event.arguments[0]) def repeat_message_event(self, bot, connection, event, auth_level): # start over from scratch, so we can still continue to try to find a trigger match if auth is too low event_handler.fire('commands:on_message', (bot, connection, event, auth_level))