Esempio n. 1
0
class Bot(Plugin):
    """The IRC bot.

    Handles plugins, command dispatch, hook dispatch, etc.  Persistent across
    losing and regaining connection.
    """

    #: Default configuration values
    CONFIG_DEFAULTS = {
            'nickname': 'csyorkbot',
            'password': None,
            'username': '******',
            'realname': 'cs-york bot',
            'sourceURL': 'http://github.com/csyork/csbot/',
            'lineRate': '1',
            'irc_host': 'irc.freenode.net',
            'irc_port': '6667',
            'command_prefix': '!',
            'channels': ' '.join([
                '#cs-york-dev',
            ]),
            'plugins': ' '.join([
                'example',
            ]),
            'mongodb_uri': 'mongodb://localhost:27017',
            'mongodb_prefix': 'csbot__',
    }

    #: Environment variable fallbacks
    CONFIG_ENVVARS = {
            'password': ['IRC_PASS'],
            'mongodb_uri': ['MONGOLAB_URI', 'MONGODB_URI'],
    }

    #: The top-level package for all bot plugins
    PLUGIN_PACKAGE = 'csbot.plugins'

    def __init__(self, configpath):
        super(Bot, self).__init__(self)

        # Load the configuration file
        self.config_path = configpath
        self.config_root = configparser.ConfigParser(interpolation=None,
                                                     allow_no_value=True)
        with open(self.config_path, 'r') as cfg:
            self.config_root.read_file(cfg)

        # Make mongodb connection
        self.log.info('connecting to mongodb: ' +
                      self.config_get('mongodb_uri'))
        self.mongodb = pymongo.Connection(self.config_get('mongodb_uri'))

        # Plugin management
        self.plugins = PluginManager(self.PLUGIN_PACKAGE, Plugin,
                                     self.config_get('plugins').split(),
                                     [self], [self])
        self.commands = {}

        # Event runner
        self.events = events.ImmediateEventRunner(
            lambda e: self.plugins.broadcast('fire_hooks', (e,)))

    @classmethod
    def plugin_name(cls):
        """Special plugin name that can't clash with real plugin classes.
        """
        return '@' + super(Bot, cls).plugin_name()

    def setup(self):
        """Load plugins defined in configuration.
        """
        super(Bot, self).setup()
        self.plugins.broadcast('setup', static=False)

    def teardown(self):
        """Unload plugins and save data.
        """
        super(Bot, self).teardown()
        self.plugins.broadcast('teardown', static=False)

    def post_event(self, event):
        self.events.post_event(event)

    def register_command(self, cmd, metadata, f, tag=None):
        # Bail out if the command already exists
        if cmd in self.commands:
            self.log.warn('tried to overwrite command: {}'.format(cmd))
            return False

        self.commands[cmd] = (f, metadata, tag)
        self.log.info('registered command: ({}, {})'.format(cmd, tag))
        return True

    def unregister_command(self, cmd, tag=None):
        if cmd in self.commands:
            f, m, t = self.commands[cmd]
            if t == tag:
                del self.commands[cmd]
                self.log.info('unregistered command: ({}, {})'
                              .format(cmd, tag))
            else:
                self.log.error(('tried to remove command {} ' +
                                'with wrong tag {}').format(cmd, tag))

    def unregister_commands(self, tag):
        delcmds = [c for c, (_, _, t) in self.commands.iteritems() if t == tag]
        for cmd in delcmds:
            f, _, tag = self.commands[cmd]
            del self.commands[cmd]
            self.log.info('unregistered command: ({}, {})'.format(cmd, tag))

    @Plugin.hook('core.self.connected')
    def signedOn(self, event):
        map(event.protocol.join, self.config_get('channels').split())

    @Plugin.hook('core.message.privmsg')
    def privmsg(self, event):
        """Handle commands inside PRIVMSGs."""
        # See if this is a command
        command = CommandEvent.parse_command(
                event, self.config_get('command_prefix'))
        if command is not None:
            self.post_event(command)

    @Plugin.hook('core.command')
    def fire_command(self, event):
        """Dispatch a command event to its callback.
        """
        # Ignore unknown commands
        if event['command'] not in self.commands:
            return

        f, _, _ = self.commands[event['command']]
        f(event)

    @Plugin.command('help', help=('help [command]: show help for command, or '
                                  'show available commands'))
    def show_commands(self, e):
        args = e.arguments()
        if len(args) > 0:
            cmd = args[0]
            if cmd in self.commands:
                f, meta, tag = self.commands[cmd]
                e.protocol.msg(e['reply_to'],
                               meta.get('help', cmd + ': no help string'))
            else:
                e.protocol.msg(e['reply_to'], cmd + ': no such command')
        else:
            e.protocol.msg(e['reply_to'], ', '.join(sorted(self.commands)))

    @Plugin.command('plugins')
    def show_plugins(self, event):
        event.protocol.msg(event['reply_to'],
                           'loaded plugins: ' + ', '.join(self.plugins))