Пример #1
0
 def load_plugins(self):
     PluginManager.load_plugins()
     self.non_plugins = []
     self.plugins = {}
     self.plugin_helps = {'help': HELP_DOC}
     for plugin in list_plugins():
         if plugin.__doc__ and plugin.COMMAND:
             self.plugin_helps[
                 plugin.COMMAND.lower()] = plugin.__doc__.strip('\n')
         if plugin.COMMAND:
             self.plugins[plugin.COMMAND.lower()] = plugin
         else:
             self.non_plugins.append(plugin)
Пример #2
0
class Environment(object):
    """An environment in which to run a program"""
    def __init__(self, spec):
        self.spec = spec
        self.basename = spec['basename']
        self.files = {}
        self.plugin_mgr = PluginManager()

        # apply defaults
        self.spec['dir'] = self.merge_dicts(self.spec['dir'],  {
            'create': False,
            'relative': False,
            'cleanup': False,
            'mode': 448 # 0700
        })

        # set up environment directory
        self.dir = os.path.expanduser(self.spec['dir']['path'])
        if self.spec['dir']['create']:
            try:
                os.mkdir(self.dir, self.spec['dir']['mode'])
            except OSError as e:
                if e.errno != errno.EEXIST:
                    raise

        # setup files
        self.init_files()

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        self.clean_up()

    def __getitem__(self, key):
        return self.files[key]

    def init_files(self):
        """Initialise files"""

        # initialise files, loading any config files first to ensure that any basename etc changes have applied
        configs = list(filter(lambda i: 'type' in self.spec['files'][i] and self.spec['files'][i]['type'] == 'config', self.spec['files']))
        others = list(filter(lambda i: i not in configs, self.spec['files']))

        for name in (configs + others):
            fspec = self.spec['files'][name]

            # apply defaults
            d = {
                'type': 'raw',      # raw file
                'read': False,      # don't read
                'create': False,    # don't create
                'cleanup': False,   # don't clean up
                'mode': 448,        # 0700
                'rel_to': 'dir'     # relative to environment directory
            }
            fspec = self.merge_dicts(fspec, d)

            # if we didn't get a name, use the name of the dictionary
            if 'name' not in fspec:
                fspec['name'] = name

            # substitute basename
            if self.basename:
                fspec['name'] = fspec['name'].format(basename=self.basename)

            # if this file doesn't have a path specified, use the name
            if 'path' not in fspec:
                # otherwise we use the 'path' field provided
                fspec['path'] = fspec['name']

            # if environment var exists in this fspec then use it to override the path
            if 'var' in fspec and fspec['var'] in os.environ:
                fspec['path'] = os.environ[fspec['var']]
            else:
                # otherwise update the path based on the 'rel_to' field
                if fspec['rel_to'] == 'pkg' and 'pkg' in fspec:
                    # relative to a given package, and package is specified
                    fspec['path'] = pkg_resources.resource_filename(fspec['pkg'], fspec['path'])
                elif fspec['rel_to'] == 'pwd' or fspec['rel_to'] == 'cwd':
                    # relative to the current working directory
                    fspec['path'] = os.path.join(os.getcwd(), fspec['path'])
                elif fspec['rel_to'] == 'abs':
                    # absolute path has been provided, don't update it
                    pass
                else:
                    # relative to the environment directory (the default)
                    fspec['path'] = os.path.join(self.dir, fspec['path'])

            # store updated spec
            self.spec['files'][name] = fspec

            # create file
            if 'create' in fspec and fspec['create']:
                if fspec['type'] == 'plugin_dir':
                    # create the plugin directory if it doesn't exist
                    if not os.path.exists(fspec['path']):
                        os.mkdir(fspec['path'])
                elif not os.path.exists(fspec['path']):
                    # otherwise if it's a normal file of some kind and doesn't exist, create it
                    fd = os.open(fspec['path'], os.O_WRONLY | os.O_CREAT, fspec['mode'])
                    f = os.fdopen(fd, 'w')
                    f.close()

            # load file
            if 'read' in fspec and fspec['read']:
                if fspec['type'] == 'config':
                    # load as a config file
                    self.files[name] = self.load_config(fspec)

                    # if there was a basename variable specified in the config, grab the contents of it
                    if 'basename_variable' in self.files[name] and self.files[name]['basename_variable'] in os.environ:
                        bn = os.environ[self.files[name]['basename_variable']].replace("/", '')
                        if len(bn) > 0:
                            if len(bn) > MAX_BASENAME:
                                bn = bn[-MAX_BASENAME:]
                            self.basename = bn
                elif fspec['type'] == 'json':
                    # load as a json file
                    self.files[name] = self.load_json(fspec)
                elif fspec['type'] == 'raw':
                    # load as raw file
                    self.files[name] = self.load_raw(fspec)
                elif fspec['type'] == 'plugin_dir':
                    # this is a plugin directory, ignore it here as we'll load it below
                    pass
                else:
                    # no idea, just load it as raw
                    self.files[name] = self.load_raw(fspec)
            else:
                self.files[name] = fspec['path']

            # load plugins
            if fspec['type'] == 'plugin_dir':
                self.load_plugins(fspec)

    def load_config(self, spec):
        """Load a JSON configuration file"""

        # load default config
        config = {}
        if 'default' in spec:
            # find where the default configuration is
            if spec['default']['rel_to'] == 'pkg':
                path = pkg_resources.resource_filename(spec['default']['pkg'], spec['default']['path'])
            elif spec['default']['rel_to'] == 'pwd':
                path = os.path.join(os.getcwd(), spec['default']['path'])
            elif spec['default']['rel_to'] == 'abs':
                path = spec['default']['path']

            # load it
            try:
                config = self.parse_json(open(path).read())
            except ValueError as e:
                raise IOError("Error parsing default configuration" + e.message)

        # load local config
        try:
            local_config = self.parse_json(open(spec['path']).read())
            config = self.merge_dicts(local_config, config)
        except ValueError as e:
            raise ValueError("Error parsing local configuration: " + e.message)
        except IOError:
            pass

        return config

    def load_json(self, spec):
        """Load a JSON file"""

        return self.parse_json(file(spec['path']).read())

    def load_raw(self, spec):
        """Load a raw text file"""

        return file(spec['path']).read()

    def load_plugins(self, spec):
        """Load plugins"""

        self.plugin_mgr.load_plugins(spec['path'])

    def parse_json(self, config):
        """Parse a JSON file"""

        lines = filter(lambda x: len(x) != 0 and x.strip()[0] != '#', config.split('\n'))
        return json.loads('\n'.join(lines))

    def read_file(self, name):
        """Read a file within the environment"""

        # get a file spec
        spec = self.file_spec(name)

        # read file
        return file(spec['path']).read()

    def write_file(self, name, data):
        """Read a file within the environment"""

        # open file
        spec = self.file_spec(name)
        f = open(spec['path'], 'w+')

        # truncate the file if we're not appending
        if not 'append' in spec or 'append' in spec and not spec['append']:
            f.truncate()

        # write file
        f.write(data)
        f.close()

    def path_to(self, filename):
        """Return the path to a file within the environment"""

        return os.path.join(self.dir, filename)

    def file_spec(self, name):
        """Return a file spec for the specified name"""

        if name in self.spec['files']:
            spec = self.spec['files'][name]
        else:
            spec = {'name': name, 'type': 'raw', 'path':self.path_to(name)}

        return spec

    def clean_up(self):
        """Clean up the environment"""

        # remove any files that need to be cleaned up
        for name in self.spec['files']:
            fspec = self.spec['files'][name]
            if 'cleanup' in fspec and fspec['cleanup']:
                os.unlink(self.path_to(name))

        # remove the directory if necessary
        if self.spec['dir']['cleanup']:
            os.rmdir(self.dir)

    def merge_dicts(self, d1, d2):
        """Merge two dictionaries"""

        # recursive merge where items in d2 override those in d1
        for k1,v1 in d1.items():
            if isinstance(v1, dict) and k1 in d2.keys() and isinstance(d2[k1], dict):
                self.merge_dicts(v1, d2[k1])
            else:
                d2[k1] = v1
        return d2

    @property
    def plugins(self):
        return self.plugin_mgr.plugins
Пример #3
0
class Bot(Daemon):
    def __init__(self, config):
        super().__init__()

        self.config = config

        self.plugin_manager = PluginManager(self, os.path.join(self.config['home_path'], self.config['plugin_path']))
        self.plugin_manager.load_plugins()

        self.builtin_commands = {
            'quit': self._quit,
            'join': self._join,
            'part': self._part,
            'loaded': self._plugin_loaded,
            'unload': self._plugin_unload,
            'load': self._plugin_load,
            'reload': self._plugin_reload,
            'ping': self._ping,
            'help': self._command_help
        }

        self.events = {
            'send_privmsg': self._send_privmsg_event,
            'privmsg': self._privmsg_event,
            'stop': self._stop_event,
            'pong': self._pong_event,
            'users': self._qnull_event,
            'part': self._part_event,
            'recv': self._qnull_event,
            'send': self._qnull_event,
            'server_ping': self._qnull_event,
            'join': self._qnull_event
        }
        self.channels = {}

    def init_loop(self):
        logger.info("Bot started.")

    def cleanup_loop(self):
        self.event_handler(("stop", ()))

    def do(self):
        #TODO: Event driven framework?
        time.sleep(0.01)

    def event_handler(self, event):
        event_type, data = event

        # TODO: Pool?
        try:
            for _, plugin in self.plugin_manager.plugins.items():
                try:
                    attr = getattr(plugin, "{}_hook".format(event_type))
                except AttributeError:
                    attr = None

                if attr is not None:
                    attr_thread = threading.Thread(target=attr, args=event)
                    attr_thread.start()

        except RuntimeError as e:
            logger.debug("Skipped plugin hooks. {}".format(e))

        self.events.get(event_type, self._null_event)(event)

    # Built-in commands
    @staticmethod
    def _null_command(data):
        logger.debug("Unknown command: {}".format(data))

    def _quit(self, data):
        message = data[0]
        sender = data[1]
        destination = data[2]

        if re.search(self.config['admin_pass'], message):
            self.send_event("stop", message)
            self.stop()
        else:
            self.send_event("send_response", "F**k You!", sender)

    def _ping(self, data):
        self.send_event("ping", data)

    def _join(self, data):
        self.send_event("join", data)

    def _part(self, data):
        self.send_event("part", data)

    def _plugin_loaded(self, data):
        if data[2][0] == '#':
            destination = data[2]
        else:
            destination = data[1]

        self.send_event("send_response", "Loaded plugins: {}".format([i for i in self.plugin_manager.plugins.keys()]), destination)

    def _plugin_reload(self, data):
        try:
            plugin_name = data[0].split(" ")[2]
        except IndexError:
            plugin_name = None

        if plugin_name in self.plugin_manager.plugins:
            self._plugin_unload(data)
            self._plugin_load(data)

    def _plugin_unload(self, data):
        try:
            plugin_name = data[0].split(" ")[2]
        except IndexError:
            return

        if data[2][0] == '#':
            destination = data[2]
        else:
            destination = data[1]

        if plugin_name == "all":
            self.plugin_manager.unload_plugins()
            self.send_event("send_response", "All plugins unloaded.", destination)

        elif plugin_name in self.plugin_manager.plugins:
            self.plugin_manager.unload_plugin(plugin_name)
            self.send_event("send_response", "Plugin {} unloaded.".format(plugin_name), destination)

    def _plugin_load(self, data):
        try:
            plugin_name = data[0].split(" ")[2]
        except IndexError:
            return

        if data[2][0] == '#':
            destination = data[2]
        else:
            destination = data[1]
        if plugin_name == "all":
            self.plugin_manager.load_plugins()
            self.send_event("send_response", "All plugins loaded: {}".format([i for i in self.plugin_manager.plugins.keys()]),
                            destination)

        else:
            self.plugin_manager.load_plugin(plugin_name)
            self.send_event("send_response", "Plugin {} loaded.".format(plugin_name), destination)

    def _command_help(self, data):
        if data[2][0] == '#':
            destination = data[2]
        else:
            destination = data[1]

        cmds = [i for i in self.builtin_commands]
        self.send_event("send_response", cmds, destination)

        cmds = [i for i in self.plugin_manager.commands]
        self.send_event("send_response", cmds, destination)

    # Events
    def _privmsg_event(self, event):
        event_type, data = event
        message = data[0]
        sender = data[1]
        destination = data[2]

        tokens = message.split(" ")

        prefix, command, message = None, None, None
        if len(tokens) == 1:
            command = tokens[0]
        elif len(tokens) == 2:
            command, message = tokens
        elif len(tokens) >= 3:
            prefix = tokens[0]
            if not re.search("{}[:,]?".format(self.config['name']), prefix):
                command, *message = tokens
            else:
                command, *message = tokens[1:]

        if destination[0] == '#' and prefix is None:
            command, message = message, ""
            prefix = self.config['name']

        # TODO: Proper authentication
        if command is not None:
            if sender in self.config['authorized_users']:
                if prefix is None and destination != self.config['name']:
                    return

                if command in self.builtin_commands:
                    logger.debug("CMD | {}: {}".format(command, message))
                    self.builtin_commands.get(command, self._null_command)(data)
                elif command in self.plugin_manager.commands:
                    logger.debug("CMD | {}: {}".format(command, message))
                    # TODO: threading
                    plugin_thread = threading.Thread(target=self.plugin_manager.commands.get(command,
                                                                                             self._null_command),
                                                     args=data)
                    logger.log(level=5, msg=plugin_thread)
                    plugin_thread.start()
                else:
                    self._null_command(command)

    def _stop_event(self, event):
        self.stop()

    def _pong_event(self, event):
        event_type, data = event

        try:
            if data[2][0] == '#':
                destination = data[2]
            else:
                destination = data[1]
        except IndexError:
            destination = data[1]

        logger.info("Ping time: {}".format(data[0]))
        self.send_event("send_response", "Ping time: {}".format(data[0]), destination)

    def _part_event(self, event):
        event_type, data = event
        # self.channels.pop(data[0])

    def _send_privmsg_event(self, event):
        pass