Beispiel #1
0
    def __init__(self, loop=asyncio.get_event_loop()):
        # basic variables
        self.loop = loop
        self.start_time = time.time()
        self.running = True
        # future which will be called when the bot stopsIf you
        self.stopped_future = asyncio.Future(loop=self.loop)

        # stores each bot server connection
        self.connections = {}

        # for plugins
        self.logger = logger

        # for plugins to abuse
        self.memory = collections.defaultdict()

        # declare and create data folder
        self.data_dir = os.path.abspath('data')
        if not os.path.exists(self.data_dir):
            logger.debug("Data folder not found, creating.")
            os.mkdir(self.data_dir)

        # set up config
        self.config = Config(self)
        logger.debug("Config system initialised.")

        # set values for reloading
        self.plugin_reloading_enabled = self.config.get("reloading", {}).get("plugin_reloading", False)
        self.config_reloading_enabled = self.config.get("reloading", {}).get("config_reloading", True)

        # this doesn't REALLY need to be here but it's nice
        self.user_agent = self.config.get('user_agent', 'CloudBot/3.0 - CloudBot Refresh '
                                                        '<https://github.com/CloudBotIRC/CloudBot/>')

        # setup db
        db_path = self.config.get('database', 'sqlite:///cloudbot.db')
        self.db_engine = create_engine(db_path)
        self.db_factory = sessionmaker(bind=self.db_engine)
        self.db_session = scoped_session(self.db_factory)
        self.db_metadata = MetaData()

        # set botvars so plugins can access when loading
        botvars.metadata = self.db_metadata
        botvars.user_agent = self.user_agent

        logger.debug("Database system initialised.")

        # Bot initialisation complete
        logger.debug("Bot setup completed.")

        # create bot connections
        self.create_connections()

        if self.plugin_reloading_enabled:
            self.reloader = PluginReloader(self)

        self.plugin_manager = PluginManager(self)
Beispiel #2
0
    def __init__(self, loop=asyncio.get_event_loop()):
        # basic variables
        self.loop = loop
        self.start_time = time.time()
        self.running = True
        # future which will be called when the bot stops
        self.stopped_future = asyncio.Future(loop=self.loop)

        # stores each bot server connection
        self.connections = []

        # for plugins
        self.logger = logger

        # declare and create data folder
        self.data_dir = os.path.abspath('data')
        if not os.path.exists(self.data_dir):
            logger.debug("Data folder not found, creating.")
            os.mkdir(self.data_dir)

        # set up config
        self.config = Config(self)
        logger.debug("Config system initialised.")

        # log developer mode
        if cloudbot.dev_mode.get("plugin_reloading"):
            logger.info("Enabling developer option: plugin reloading.")
        if cloudbot.dev_mode.get("config_reloading"):
            logger.info("Enabling developer option: config reloading.")
        if cloudbot.dev_mode.get("console_debug"):
            logger.info("Enabling developer option: console debug.")
        if cloudbot.dev_mode.get("file_debug"):
            logger.info("Enabling developer option: file debug")

        # setup db
        db_path = self.config.get('database', 'sqlite:///cloudbot.db')
        self.db_engine = create_engine(db_path)
        self.db_factory = sessionmaker(bind=self.db_engine)
        self.db_session = scoped_session(self.db_factory)
        self.db_metadata = MetaData()
        # set botvars.metadata so plugins can access when loading
        botvars.metadata = self.db_metadata
        logger.debug("Database system initialised.")

        # Bot initialisation complete
        logger.debug("Bot setup completed.")

        # create bot connections
        self.create_connections()

        if cloudbot.dev_mode.get("plugin_reloading"):
            self.reloader = PluginReloader(self)

        self.plugin_manager = PluginManager(self)
Beispiel #3
0
class CloudBot:
    """
    :type start_time: float
    :type running: bool
    :type connections: list[Client | IrcClient]
    :type data_dir: bytes
    :type config: core.config.Config
    :type plugin_manager: PluginManager
    :type reloader: PluginReloader
    :type db_engine: sqlalchemy.engine.Engine
    :type db_factory: sqlalchemy.orm.session.sessionmaker
    :type db_session: sqlalchemy.orm.scoping.scoped_session
    :type db_metadata: sqlalchemy.sql.schema.MetaData
    :type loop: asyncio.events.AbstractEventLoop
    :type stopped_future: asyncio.Future
    :param: stopped_future: Future that will be given a result when the bot has stopped.
    """

    def __init__(self, loop=asyncio.get_event_loop()):
        # basic variables
        self.loop = loop
        self.start_time = time.time()
        self.running = True
        # future which will be called when the bot stopsIf you
        self.stopped_future = asyncio.Future(loop=self.loop)

        # stores each bot server connection
        self.connections = {}

        # for plugins
        self.logger = logger

        # for plugins to abuse
        self.memory = collections.defaultdict()

        # declare and create data folder
        self.data_dir = os.path.abspath('data')
        if not os.path.exists(self.data_dir):
            logger.debug("Data folder not found, creating.")
            os.mkdir(self.data_dir)

        # set up config
        self.config = Config(self)
        logger.debug("Config system initialised.")

        # set values for reloading
        self.plugin_reloading_enabled = self.config.get("reloading", {}).get("plugin_reloading", False)
        self.config_reloading_enabled = self.config.get("reloading", {}).get("config_reloading", True)

        # this doesn't REALLY need to be here but it's nice
        self.user_agent = self.config.get('user_agent', 'CloudBot/3.0 - CloudBot Refresh '
                                                        '<https://github.com/CloudBotIRC/CloudBot/>')

        # setup db
        db_path = self.config.get('database', 'sqlite:///cloudbot.db')
        self.db_engine = create_engine(db_path)
        self.db_factory = sessionmaker(bind=self.db_engine)
        self.db_session = scoped_session(self.db_factory)
        self.db_metadata = MetaData()

        # set botvars so plugins can access when loading
        botvars.metadata = self.db_metadata
        botvars.user_agent = self.user_agent

        logger.debug("Database system initialised.")

        # Bot initialisation complete
        logger.debug("Bot setup completed.")

        # create bot connections
        self.create_connections()

        if self.plugin_reloading_enabled:
            self.reloader = PluginReloader(self)

        self.plugin_manager = PluginManager(self)

    def run(self):
        """
        Starts CloudBot.
        This will load plugins, connect to IRC, and process input.
        :return: True if CloudBot should be restarted, False otherwise
        :rtype: bool
        """
        # Initializes the bot, plugins and connections
        self.loop.run_until_complete(self._init_routine())
        # Wait till the bot stops. The stopped_future will be set to True to restart, False otherwise
        restart = self.loop.run_until_complete(self.stopped_future)
        self.loop.close()
        return restart

    def create_connections(self):
        """ Create a BotConnection for all the networks defined in the config """
        for config in self.config['connections']:
            # strip all spaces and capitalization from the connection name
            name = clean_name(config['name'])
            nick = config['nick']
            server = config['connection']['server']
            port = config['connection'].get('port', 6667)
            local_bind = (config['connection'].get('bind_addr', False), config['connection'].get('bind_port', 0))
            if local_bind[0] is False:
                local_bind = False

            self.connections[name] = IrcClient(self, name, nick, config=config, channels=config['channels'],
                                              server=server, port=port, use_ssl=config['connection'].get('ssl', False),
                                              local_bind=local_bind)
            logger.debug("[{}] Created connection.".format(name))

    @asyncio.coroutine
    def stop(self, reason=None, *, restart=False):
        """quits all networks and shuts the bot down"""
        logger.info("Stopping bot.")

        if self.config_reloading_enabled:
            logger.debug("Stopping config reloader.")
            self.config.stop()

        if self.plugin_reloading_enabled:
            logger.debug("Stopping plugin reloader.")
            self.reloader.stop()

        for connection in self.connections.values():
            if not connection.connected:
                # Don't quit a connection that hasn't connected
                continue
            logger.debug("[{}] Closing connection.".format(connection.name))

            connection.quit(reason)

        yield from asyncio.sleep(1.0)  # wait for 'QUIT' calls to take affect

        for connection in self.connections.values():
            if not connection.connected:
                # Don't close a connection that hasn't connected
                continue
            connection.close()

        self.running = False
        # Give the stopped_future a result, so that run() will exit
        self.stopped_future.set_result(restart)

    @asyncio.coroutine
    def restart(self, reason=None):
        """shuts the bot down and restarts it"""
        yield from self.stop(reason=reason, restart=True)

    @asyncio.coroutine
    def _init_routine(self):
        # Load plugins
        yield from self.plugin_manager.load_all(os.path.abspath("plugins"))

        # If we we're stopped while loading plugins, cancel that and just stop
        if not self.running:
            logger.info("Killed while loading, exiting")
            return

        if self.plugin_reloading_enabled:
            # start plugin reloader
            self.reloader.start(os.path.abspath("plugins"))

        # Connect to servers
        yield from asyncio.gather(*[conn.connect() for conn in self.connections.values()], loop=self.loop)

        # Run a manual garbage collection cycle, to clean up any unused objects created during initialization
        gc.collect()

    @asyncio.coroutine
    def process(self, event):
        """
        :type event: Event
        """
        run_before_tasks = []
        tasks = []
        command_prefix = event.conn.config.get('command_prefix', '.')

        # Raw IRC hook
        for raw_hook in self.plugin_manager.catch_all_triggers:
            # run catch-all coroutine hooks before all others - TODO: Make this a plugin argument
            if not raw_hook.threaded:
                run_before_tasks.append(
                    self.plugin_manager.launch(raw_hook, Event(hook=raw_hook, base_event=event)))
            else:
                tasks.append(self.plugin_manager.launch(raw_hook, Event(hook=raw_hook, base_event=event)))
        if event.irc_command in self.plugin_manager.raw_triggers:
            for raw_hook in self.plugin_manager.raw_triggers[event.irc_command]:
                tasks.append(self.plugin_manager.launch(raw_hook, Event(hook=raw_hook, base_event=event)))

        # Event hooks
        if event.type in self.plugin_manager.event_type_hooks:
            for event_hook in self.plugin_manager.event_type_hooks[event.type]:
                tasks.append(self.plugin_manager.launch(event_hook, Event(hook=event_hook, base_event=event)))

        if event.type is EventType.message:
            # Commands
            if event.chan.lower() == event.nick.lower():  # private message, no command prefix
                command_re = r'(?i)^(?:[{}]?|{}[,;:]+\s+)(\w+)(?:$|\s+)(.*)'.format(command_prefix, event.conn.nick)
            else:
                command_re = r'(?i)^(?:[{}]|{}[,;:]+\s+)(\w+)(?:$|\s+)(.*)'.format(command_prefix, event.conn.nick)

            cmd_match = re.match(command_re, event.content)

            if cmd_match:
                command = cmd_match.group(1).lower()
                if command in self.plugin_manager.commands:
                    command_hook = self.plugin_manager.commands[command]
                    command_event = CommandEvent(hook=command_hook, text=cmd_match.group(2).strip(),
                                             triggered_command=command, base_event=event)
                    tasks.append(self.plugin_manager.launch(command_hook, command_event))
                else:
                    potential_matches = []
                    for potential_match, plugin in self.plugin_manager.commands.items():
                        if potential_match.startswith(command):
                            potential_matches.append((potential_match, plugin))
                    if potential_matches:
                        if len(potential_matches) == 1:
                            command_hook = potential_matches[0][1]
                            command_event = CommandEvent(hook=command_hook, text=cmd_match.group(2).strip(),
                                                     triggered_command=command, base_event=event)
                            tasks.append(self.plugin_manager.launch(command_hook, command_event))
                        else:
                            event.notice("Possible matches: {}".format(
                                formatting.get_text_list([command for command, plugin in potential_matches])))

            # Regex hooks
            for regex, regex_hook in self.plugin_manager.regex_hooks:
                if not regex_hook.run_on_cmd and cmd_match:
                    pass
                else:
                    regex_match = regex.search(event.content)
                    if regex_match:
                        regex_event = RegexEvent(hook=regex_hook, match=regex_match, base_event=event)
                        tasks.append(self.plugin_manager.launch(regex_hook, regex_event))

        # Run the tasks
        yield from asyncio.gather(*run_before_tasks, loop=self.loop)
        yield from asyncio.gather(*tasks, loop=self.loop)
Beispiel #4
0
class CloudBot:
    """
    :type start_time: float
    :type running: bool
    :type connections: dict[str, Client]
    :type data_dir: bytes
    :type config: core.config.Config
    :type plugin_manager: PluginManager
    :type plugin_reloader: PluginReloader
    :type db_engine: sqlalchemy.engine.Engine
    :type db_factory: sqlalchemy.orm.session.sessionmaker
    :type db_session: sqlalchemy.orm.scoping.scoped_session
    :type db_metadata: sqlalchemy.sql.schema.MetaData
    :type loop: asyncio.events.AbstractEventLoop
    :type stopped_future: asyncio.Future
    :param: stopped_future: Future that will be given a result when the bot has stopped.
    """
    def __init__(self, loop=asyncio.get_event_loop()):
        if bot.get():
            raise ValueError("There seems to already be a bot running!")

        bot.set(self)
        # basic variables
        self.base_dir = Path().resolve()
        self.loop = loop
        self.start_time = time.time()
        self.running = True
        self.clients = {}
        # future which will be called when the bot stopsIf you
        self.stopped_future = async_util.create_future(self.loop)

        # stores each bot server connection
        self.connections = KeyFoldDict()

        # for plugins
        self.logger = logger

        # for plugins to abuse
        self.memory = collections.defaultdict()

        # declare and create data folder
        self.data_dir = os.path.abspath("data")
        if not os.path.exists(self.data_dir):
            logger.debug("Data folder not found, creating.")
            os.mkdir(self.data_dir)

        # set up config
        self.config = Config(self)
        logger.debug("Config system initialised.")

        # set values for reloading
        reloading_conf = self.config.get("reloading", {})
        self.plugin_reloading_enabled = reloading_conf.get(
            "plugin_reloading", False)
        self.config_reloading_enabled = reloading_conf.get(
            "config_reloading", True)

        # this doesn't REALLY need to be here but it's nice
        self.repo_link = self.config.get(
            "repo_link", "https://github.com/TotallyNotRobots/CloudBot/")
        self.user_agent = self.config.get(
            "user_agent",
            "CloudBot/3.0 - CloudBot Refresh <{repo_link}>").format(
                repo_link=self.repo_link)

        # setup db
        db_path = self.config.get("database", "sqlite:///cloudbot.db")
        self.db_engine = create_engine(db_path)
        self.db_factory = sessionmaker(bind=self.db_engine)
        self.db_session = scoped_session(self.db_factory)
        self.db_metadata = database.metadata
        self.db_base = declarative_base(metadata=self.db_metadata,
                                        bind=self.db_engine)

        # set botvars so plugins can access when loading
        database.base = self.db_base

        logger.debug("Database system initialised.")

        # Bot initialisation complete
        logger.debug("Bot setup completed.")

        self.load_clients()

        # create bot connections
        self.create_connections()

        self.observer = Observer()

        if self.plugin_reloading_enabled:
            self.plugin_reloader = PluginReloader(self)

        if self.config_reloading_enabled:
            self.config_reloader = ConfigReloader(self)

        self.plugin_manager = PluginManager(self)

    def run(self):
        """
        Starts CloudBot.
        This will load plugins, connect to IRC, and process input.
        :return: True if CloudBot should be restarted, False otherwise
        :rtype: bool
        """
        # Initializes the bot, plugins and connections
        self.loop.run_until_complete(self._init_routine())
        # Wait till the bot stops. The stopped_future will be set to True to restart, False otherwise
        logger.debug("Init done")
        restart = self.loop.run_until_complete(self.stopped_future)
        logger.debug("Waiting for plugin unload")
        self.loop.run_until_complete(self.plugin_manager.unload_all())
        logger.debug("Unload complete")
        self.loop.close()
        return restart

    def get_client(self, name: str) -> Type[Client]:
        return self.clients[name]

    def register_client(self, name, cls):
        self.clients[name] = cls

    def create_connections(self):
        """ Create a BotConnection for all the networks defined in the config """
        for config in self.config["connections"]:
            # strip all spaces and capitalization from the connection name
            name = clean_name(config["name"])
            nick = config["nick"]
            _type = config.get("type", "irc")

            self.connections[name] = self.get_client(_type)(
                self,
                _type,
                name,
                nick,
                config=config,
                channels=config["channels"])
            logger.debug("[%s] Created connection.", name)

    async def stop(self, reason=None, *, restart=False):
        """quits all networks and shuts the bot down"""
        logger.info("Stopping bot.")

        if self.config_reloading_enabled:
            logger.debug("Stopping config reloader.")
            self.config_reloader.stop()

        if self.plugin_reloading_enabled:
            logger.debug("Stopping plugin reloader.")
            self.plugin_reloader.stop()

        self.observer.stop()

        logger.debug("Stopping connect loops and shutting down clients")
        for connection in self.connections.values():
            connection.active = False
            if not connection.cancelled_future.done():
                connection.cancelled_future.set_result(None)

            if not connection.connected:
                # Don't quit a connection that hasn't connected
                continue
            logger.debug("[%s] Closing connection.", connection.name)

            connection.quit(reason)

        logger.debug("Done.")

        logger.debug("Sleeping to let clients quit")
        await asyncio.sleep(1.0)  # wait for 'QUIT' calls to take affect
        logger.debug("Sleep done.")

        logger.debug("Closing clients")
        for connection in self.connections.values():
            connection.close()

        logger.debug("All clients closed")

        self.running = False
        # Give the stopped_future a result, so that run() will exit
        logger.debug("Setting future result for shutdown")
        self.stopped_future.set_result(restart)

    async def restart(self, reason=None):
        """shuts the bot down and restarts it"""
        await self.stop(reason=reason, restart=True)

    async def _init_routine(self):
        # Load plugins
        await self.plugin_manager.load_all(os.path.abspath("plugins"))

        # If we we're stopped while loading plugins, cancel that and just stop
        if not self.running:
            logger.info("Killed while loading, exiting")
            return

        if self.plugin_reloading_enabled:
            # start plugin reloader
            self.plugin_reloader.start(os.path.abspath("plugins"))

        if self.config_reloading_enabled:
            self.config_reloader.start()

        self.observer.start()

        for conn in self.connections.values():
            conn.active = True

        # Connect to servers
        await asyncio.gather(
            *[conn.try_connect() for conn in self.connections.values()],
            loop=self.loop)
        logger.debug("Connections created.")

        # Run a manual garbage collection cycle, to clean up any unused objects created during initialization
        gc.collect()

    def load_clients(self):
        """
        Load all clients from the "clients" directory
        """
        scanner = Scanner(bot=self)
        scanner.scan(clients, categories=["cloudbot.client"])

    async def process(self, event):
        """
        :type event: Event
        """
        run_before_tasks = []
        tasks = []
        halted = False

        def add_hook(hook, _event, _run_before=False):
            nonlocal halted
            if halted:
                return False

            if hook.clients and _event.conn.type not in hook.clients:
                return True

            coro = self.plugin_manager.launch(hook, _event)
            if _run_before:
                run_before_tasks.append(coro)
            else:
                tasks.append(coro)

            if hook.action is Action.HALTALL:
                halted = True
                return False

            if hook.action is Action.HALTTYPE:
                return False

            return True

        # Raw IRC hook
        for raw_hook in self.plugin_manager.catch_all_triggers:
            # run catch-all coroutine hooks before all others - TODO: Make this a plugin argument
            run_before = not raw_hook.threaded
            if not add_hook(raw_hook,
                            Event(hook=raw_hook, base_event=event),
                            _run_before=run_before):
                # The hook has an action of Action.HALT* so stop adding new tasks
                break

        if event.irc_command in self.plugin_manager.raw_triggers:
            for raw_hook in self.plugin_manager.raw_triggers[
                    event.irc_command]:
                if not add_hook(raw_hook, Event(hook=raw_hook,
                                                base_event=event)):
                    # The hook has an action of Action.HALT* so stop adding new tasks
                    break

        # Event hooks
        if event.type in self.plugin_manager.event_type_hooks:
            for event_hook in self.plugin_manager.event_type_hooks[event.type]:
                if not add_hook(event_hook,
                                Event(hook=event_hook, base_event=event)):
                    # The hook has an action of Action.HALT* so stop adding new tasks
                    break

        matched_command = False

        if event.type is EventType.message:
            # Commands
            cmd_match = get_cmd_regex(event).match(event.content)

            if cmd_match:
                command_prefix = event.conn.config.get("command_prefix", ".")
                prefix = cmd_match.group("prefix") or command_prefix[0]
                command = cmd_match.group("command").lower()
                text = cmd_match.group("text").strip()
                cmd_event = partial(
                    CommandEvent,
                    text=text,
                    triggered_command=command,
                    base_event=event,
                    cmd_prefix=prefix,
                )
                if command in self.plugin_manager.commands:
                    command_hook = self.plugin_manager.commands[command]
                    command_event = cmd_event(hook=command_hook)
                    add_hook(command_hook, command_event)
                    matched_command = True
                else:
                    potential_matches = []
                    for potential_match, plugin in self.plugin_manager.commands.items(
                    ):
                        if potential_match.startswith(command):
                            potential_matches.append((potential_match, plugin))

                    if potential_matches:
                        matched_command = True
                        if len(potential_matches) == 1:
                            command_hook = potential_matches[0][1]
                            command_event = cmd_event(hook=command_hook)
                            add_hook(command_hook, command_event)
                        else:
                            commands = sorted(
                                command
                                for command, plugin in potential_matches)
                            txt_list = formatting.get_text_list(commands)
                            event.notice(
                                "Possible matches: {}".format(txt_list))

        if event.type in (EventType.message, EventType.action):
            # Regex hooks
            regex_matched = False
            for regex, regex_hook in self.plugin_manager.regex_hooks:
                if not regex_hook.run_on_cmd and matched_command:
                    continue

                if regex_hook.only_no_match and regex_matched:
                    continue

                regex_match = regex.search(event.content)
                if regex_match:
                    regex_matched = True
                    regex_event = RegexEvent(hook=regex_hook,
                                             match=regex_match,
                                             base_event=event)
                    if not add_hook(regex_hook, regex_event):
                        # The hook has an action of Action.HALT* so stop adding new tasks
                        break

        # Run the tasks
        await asyncio.gather(*run_before_tasks, loop=self.loop)
        await asyncio.gather(*tasks, loop=self.loop)
Beispiel #5
0
    def __init__(self, loop=asyncio.get_event_loop()):
        if bot.get():
            raise ValueError("There seems to already be a bot running!")

        bot.set(self)
        # basic variables
        self.base_dir = Path().resolve()
        self.loop = loop
        self.start_time = time.time()
        self.running = True
        self.clients = {}
        # future which will be called when the bot stopsIf you
        self.stopped_future = async_util.create_future(self.loop)

        # stores each bot server connection
        self.connections = KeyFoldDict()

        # for plugins
        self.logger = logger

        # for plugins to abuse
        self.memory = collections.defaultdict()

        # declare and create data folder
        self.data_dir = os.path.abspath("data")
        if not os.path.exists(self.data_dir):
            logger.debug("Data folder not found, creating.")
            os.mkdir(self.data_dir)

        # set up config
        self.config = Config(self)
        logger.debug("Config system initialised.")

        # set values for reloading
        reloading_conf = self.config.get("reloading", {})
        self.plugin_reloading_enabled = reloading_conf.get(
            "plugin_reloading", False)
        self.config_reloading_enabled = reloading_conf.get(
            "config_reloading", True)

        # this doesn't REALLY need to be here but it's nice
        self.repo_link = self.config.get(
            "repo_link", "https://github.com/TotallyNotRobots/CloudBot/")
        self.user_agent = self.config.get(
            "user_agent",
            "CloudBot/3.0 - CloudBot Refresh <{repo_link}>").format(
                repo_link=self.repo_link)

        # setup db
        db_path = self.config.get("database", "sqlite:///cloudbot.db")
        self.db_engine = create_engine(db_path)
        self.db_factory = sessionmaker(bind=self.db_engine)
        self.db_session = scoped_session(self.db_factory)
        self.db_metadata = database.metadata
        self.db_base = declarative_base(metadata=self.db_metadata,
                                        bind=self.db_engine)

        # set botvars so plugins can access when loading
        database.base = self.db_base

        logger.debug("Database system initialised.")

        # Bot initialisation complete
        logger.debug("Bot setup completed.")

        self.load_clients()

        # create bot connections
        self.create_connections()

        self.observer = Observer()

        if self.plugin_reloading_enabled:
            self.plugin_reloader = PluginReloader(self)

        if self.config_reloading_enabled:
            self.config_reloader = ConfigReloader(self)

        self.plugin_manager = PluginManager(self)
Beispiel #6
0
def mock_manager(mock_bot):
    mock_bot.plugin_manager = mgr = PluginManager(mock_bot)
    yield mgr
Beispiel #7
0
    def __init__(self, loop=asyncio.get_event_loop()):
        # basic variables
        self.loop = loop
        self.start_time = time.time()
        self.running = True
        # future which will be called when the bot stopsIf you
        self.stopped_future = asyncio.Future(loop=self.loop)

        # stores each bot server connection
        self.connections = {}

        # for plugins
        self.logger = logger

        # for plugins to abuse
        self.memory = collections.defaultdict()

        # declare and create data folder
        self.data_dir = os.path.abspath('data')
        if not os.path.exists(self.data_dir):
            logger.debug("Data folder not found, creating.")
            os.mkdir(self.data_dir)

        # set up config
        self.config = Config(self)
        logger.debug("Config system initialised.")

        # set values for reloading
        self.plugin_reloading_enabled = self.config.get("reloading", {}).get("plugin_reloading", False)
        self.config_reloading_enabled = self.config.get("reloading", {}).get("config_reloading", True)

        # this doesn't REALLY need to be here but it's nice
        self.user_agent = self.config.get('user_agent', 'CloudBot/3.0 - CloudBot Refresh '
                                                        '<https://github.com/CloudBotIRC/CloudBot/>')

        # setup db
        db_path = self.config.get('database', 'sqlite:///cloudbot.db')
        self.db_engine = create_engine(db_path)
        self.db_factory = sessionmaker(bind=self.db_engine)
        self.db_session = scoped_session(self.db_factory)
        self.db_metadata = MetaData()
        self.db_base = declarative_base(metadata=self.db_metadata, bind=self.db_engine)

        # create web interface
        if self.config.get("web", {}).get("enabled", False) and web_installed:
            self.web = WebInterface(self)

        # set botvars so plugins can access when loading
        database.metadata = self.db_metadata
        database.base = self.db_base

        logger.debug("Database system initialised.")

        # Bot initialisation complete
        logger.debug("Bot setup completed.")

        # create bot connections
        self.create_connections()

        if self.plugin_reloading_enabled:
            self.reloader = PluginReloader(self)

        self.plugin_manager = PluginManager(self)
Beispiel #8
0
class CloudBot:
    """
    :type start_time: float
    :type running: bool
    :type connections: list[Client | IrcClient]
    :type data_dir: bytes
    :type config: core.config.Config
    :type plugin_manager: PluginManager
    :type reloader: PluginReloader
    :type db_engine: sqlalchemy.engine.Engine
    :type db_factory: sqlalchemy.orm.session.sessionmaker
    :type db_session: sqlalchemy.orm.scoping.scoped_session
    :type db_metadata: sqlalchemy.sql.schema.MetaData
    :type loop: asyncio.events.AbstractEventLoop
    :type stopped_future: asyncio.Future
    :param: stopped_future: Future that will be given a result when the bot has stopped.
    """

    def __init__(self, loop=asyncio.get_event_loop()):
        # basic variables
        self.loop = loop
        self.start_time = time.time()
        self.running = True
        # future which will be called when the bot stopsIf you
        self.stopped_future = asyncio.Future(loop=self.loop)

        # stores each bot server connection
        self.connections = {}

        # for plugins
        self.logger = logger

        # for plugins to abuse
        self.memory = collections.defaultdict()

        # declare and create data folder
        self.data_dir = os.path.abspath('data')
        if not os.path.exists(self.data_dir):
            logger.debug("Data folder not found, creating.")
            os.mkdir(self.data_dir)

        # set up config
        self.config = Config(self)
        logger.debug("Config system initialised.")

        # set values for reloading
        self.plugin_reloading_enabled = self.config.get("reloading", {}).get("plugin_reloading", False)
        self.config_reloading_enabled = self.config.get("reloading", {}).get("config_reloading", True)

        # this doesn't REALLY need to be here but it's nice
        self.user_agent = self.config.get('user_agent', 'CloudBot/3.0 - CloudBot Refresh '
                                                        '<https://github.com/CloudBotIRC/CloudBot/>')

        # setup db
        db_path = self.config.get('database', 'sqlite:///cloudbot.db')
        self.db_engine = create_engine(db_path)
        self.db_factory = sessionmaker(bind=self.db_engine)
        self.db_session = scoped_session(self.db_factory)
        self.db_metadata = MetaData()
        self.db_base = declarative_base(metadata=self.db_metadata, bind=self.db_engine)

        # create web interface
        if self.config.get("web", {}).get("enabled", False) and web_installed:
            self.web = WebInterface(self)

        # set botvars so plugins can access when loading
        database.metadata = self.db_metadata
        database.base = self.db_base

        logger.debug("Database system initialised.")

        # Bot initialisation complete
        logger.debug("Bot setup completed.")

        # create bot connections
        self.create_connections()

        if self.plugin_reloading_enabled:
            self.reloader = PluginReloader(self)

        self.plugin_manager = PluginManager(self)

    def run(self):
        """
        Starts CloudBot.
        This will load plugins, connect to IRC, and process input.
        :return: True if CloudBot should be restarted, False otherwise
        :rtype: bool
        """
        # Initializes the bot, plugins and connections
        self.loop.run_until_complete(self._init_routine())
        # Wait till the bot stops. The stopped_future will be set to True to restart, False otherwise
        restart = self.loop.run_until_complete(self.stopped_future)
        self.loop.close()
        return restart

    def create_connections(self):
        """ Create a BotConnection for all the networks defined in the config """
        for config in self.config['connections']:
            # strip all spaces and capitalization from the connection name
            name = clean_name(config['name'])
            nick = config['nick']
            server = config['connection']['server']
            port = config['connection'].get('port', 6667)
            local_bind = (config['connection'].get('bind_addr', False), config['connection'].get('bind_port', 0))
            if local_bind[0] is False:
                local_bind = False

            self.connections[name] = IrcClient(self, name, nick, config=config, channels=config['channels'],
                                              server=server, port=port, use_ssl=config['connection'].get('ssl', False),
                                              local_bind=local_bind)
            logger.debug("[{}] Created connection.".format(name))

    @asyncio.coroutine
    def stop(self, reason=None, *, restart=False):
        """quits all networks and shuts the bot down"""
        logger.info("Stopping bot.")

        if self.config_reloading_enabled:
            logger.debug("Stopping config reloader.")
            self.config.stop()

        if self.plugin_reloading_enabled:
            logger.debug("Stopping plugin reloader.")
            self.reloader.stop()

        for connection in self.connections.values():
            if not connection.connected:
                # Don't quit a connection that hasn't connected
                continue
            logger.debug("[{}] Closing connection.".format(connection.name))

            connection.quit(reason)

        yield from asyncio.sleep(1.0)  # wait for 'QUIT' calls to take affect

        for connection in self.connections.values():
            if not connection.connected:
                # Don't close a connection that hasn't connected
                continue
            connection.close()

        self.running = False
        # Give the stopped_future a result, so that run() will exit
        self.stopped_future.set_result(restart)

    @asyncio.coroutine
    def restart(self, reason=None):
        """shuts the bot down and restarts it"""
        yield from self.stop(reason=reason, restart=True)

    @asyncio.coroutine
    def _init_routine(self):
        # Load plugins
        yield from self.plugin_manager.load_all(os.path.abspath("plugins"))

        # If we we're stopped while loading plugins, cancel that and just stop
        if not self.running:
            logger.info("Killed while loading, exiting")
            return

        if self.plugin_reloading_enabled:
            # start plugin reloader
            self.reloader.start(os.path.abspath("plugins"))

        # Connect to servers
        yield from asyncio.gather(*[conn.connect() for conn in self.connections.values()], loop=self.loop)

        # Activate web interface.
        if self.config.get("web", {}).get("enabled", False) and web_installed:
            self.web.start()

        # Run a manual garbage collection cycle, to clean up any unused objects created during initialization
        gc.collect()

    @asyncio.coroutine
    def process(self, event):
        """
        :type event: Event
        """
        run_before_tasks = []
        tasks = []
        command_prefix = event.conn.config.get('command_prefix', '.')

        # Raw IRC hook
        for raw_hook in self.plugin_manager.catch_all_triggers:
            # run catch-all coroutine hooks before all others - TODO: Make this a plugin argument
            if not raw_hook.threaded:
                run_before_tasks.append(
                    self.plugin_manager.launch(raw_hook, Event(hook=raw_hook, base_event=event)))
            else:
                tasks.append(self.plugin_manager.launch(raw_hook, Event(hook=raw_hook, base_event=event)))
        if event.irc_command in self.plugin_manager.raw_triggers:
            for raw_hook in self.plugin_manager.raw_triggers[event.irc_command]:
                tasks.append(self.plugin_manager.launch(raw_hook, Event(hook=raw_hook, base_event=event)))

        # Event hooks
        if event.type in self.plugin_manager.event_type_hooks:
            for event_hook in self.plugin_manager.event_type_hooks[event.type]:
                tasks.append(self.plugin_manager.launch(event_hook, Event(hook=event_hook, base_event=event)))

        if event.type is EventType.message:
            # Commands
            if event.chan.lower() == event.nick.lower():  # private message, no command prefix
                command_re = r'(?i)^(?:[{}]?|{}[,;:]+\s+)(\w+)(?:$|\s+)(.*)'.format(command_prefix, event.conn.nick)
            else:
                command_re = r'(?i)^(?:[{}]|{}[,;:]+\s+)(\w+)(?:$|\s+)(.*)'.format(command_prefix, event.conn.nick)

            cmd_match = re.match(command_re, event.content)

            if cmd_match:
                command = cmd_match.group(1).lower()
                if command in self.plugin_manager.commands:
                    command_hook = self.plugin_manager.commands[command]
                    command_event = CommandEvent(hook=command_hook, text=cmd_match.group(2).strip(),
                                             triggered_command=command, base_event=event)
                    tasks.append(self.plugin_manager.launch(command_hook, command_event))
                else:
                    potential_matches = []
                    for potential_match, plugin in self.plugin_manager.commands.items():
                        if potential_match.startswith(command):
                            potential_matches.append((potential_match, plugin))
                    if potential_matches:
                        if len(potential_matches) == 1:
                            command_hook = potential_matches[0][1]
                            command_event = CommandEvent(hook=command_hook, text=cmd_match.group(2).strip(),
                                                     triggered_command=command, base_event=event)
                            tasks.append(self.plugin_manager.launch(command_hook, command_event))
                        else:
                            event.notice("Possible matches: {}".format(
                                formatting.get_text_list([command for command, plugin in potential_matches])))

            # Regex hooks
            for regex, regex_hook in self.plugin_manager.regex_hooks:
                if not regex_hook.run_on_cmd and cmd_match:
                    pass
                else:
                    regex_match = regex.search(event.content)
                    if regex_match:
                        regex_event = RegexEvent(hook=regex_hook, match=regex_match, base_event=event)
                        tasks.append(self.plugin_manager.launch(regex_hook, regex_event))

        # Run the tasks
        yield from asyncio.gather(*run_before_tasks, loop=self.loop)
        yield from asyncio.gather(*tasks, loop=self.loop)
Beispiel #9
0
class CloudBot:
    """
    :type start_time: float
    :type running: bool
    :type connections: dict[str, Client]
    :type data_dir: bytes
    :type config: core.config.Config
    :type plugin_manager: PluginManager
    :type plugin_reloader: PluginReloader
    :type db_engine: sqlalchemy.engine.Engine
    :type db_factory: sqlalchemy.orm.session.sessionmaker
    :type db_session: sqlalchemy.orm.scoping.scoped_session
    :type db_metadata: sqlalchemy.sql.schema.MetaData
    :type loop: asyncio.events.AbstractEventLoop
    :type stopped_future: asyncio.Future
    :param: stopped_future: Future that will be given a result when the bot has stopped.
    """
    def __init__(self, loop=asyncio.get_event_loop()):
        # basic variables
        self.base_dir = Path().resolve()
        self.loop = loop
        self.start_time = time.time()
        self.running = True
        # future which will be called when the bot stopsIf you
        self.stopped_future = async_util.create_future(self.loop)

        # stores each bot server connection
        self.connections = {}

        # for plugins
        self.logger = logger

        # for plugins to abuse
        self.memory = collections.defaultdict()

        # declare and create data folder
        self.data_dir = os.path.abspath('data')
        if not os.path.exists(self.data_dir):
            logger.debug("Data folder not found, creating.")
            os.mkdir(self.data_dir)

        # set up config
        self.config = Config(self)
        logger.debug("Config system initialised.")

        # set values for reloading
        reloading_conf = self.config.get("reloading", {})
        self.plugin_reloading_enabled = reloading_conf.get(
            "plugin_reloading", False)
        self.config_reloading_enabled = reloading_conf.get(
            "config_reloading", True)

        # this doesn't REALLY need to be here but it's nice
        self.user_agent = self.config.get(
            'user_agent', 'CloudBot/3.0 - CloudBot Refresh '
            '<https://github.com/CloudBotIRC/CloudBot/>')

        # setup db
        db_path = self.config.get('database', 'sqlite:///cloudbot.db')
        self.db_engine = create_engine(db_path)
        self.db_factory = sessionmaker(bind=self.db_engine)
        self.db_session = scoped_session(self.db_factory)
        self.db_metadata = MetaData()
        self.db_base = declarative_base(metadata=self.db_metadata,
                                        bind=self.db_engine)

        # create web interface
        if self.config.get("web", {}).get("enabled", False) and web_installed:
            self.web = WebInterface(self)

        # set botvars so plugins can access when loading
        database.metadata = self.db_metadata
        database.base = self.db_base

        logger.debug("Database system initialised.")

        # Bot initialisation complete
        logger.debug("Bot setup completed.")

        self.load_clients()

        # create bot connections
        self.create_connections()

        self.observer = Observer()

        if self.plugin_reloading_enabled:
            self.plugin_reloader = PluginReloader(self)

        if self.config_reloading_enabled:
            self.config_reloader = ConfigReloader(self)

        self.plugin_manager = PluginManager(self)

    def run(self):
        """
        Starts CloudBot.
        This will load plugins, connect to IRC, and process input.
        :return: True if CloudBot should be restarted, False otherwise
        :rtype: bool
        """
        # Initializes the bot, plugins and connections
        self.loop.run_until_complete(self._init_routine())
        # Wait till the bot stops. The stopped_future will be set to True to restart, False otherwise
        restart = self.loop.run_until_complete(self.stopped_future)
        self.loop.run_until_complete(self.plugin_manager.unload_all())
        self.loop.close()
        return restart

    def create_connections(self):
        """ Create a BotConnection for all the networks defined in the config """
        for config in self.config['connections']:
            # strip all spaces and capitalization from the connection name
            name = clean_name(config['name'])
            nick = config['nick']
            _type = config.get("type", "irc")

            self.connections[name] = CLIENTS[_type](
                self, name, nick, config=config, channels=config['channels'])
            logger.debug("[{}] Created connection.".format(name))

    @asyncio.coroutine
    def stop(self, reason=None, *, restart=False):
        """quits all networks and shuts the bot down"""
        logger.info("Stopping bot.")

        if self.config_reloading_enabled:
            logger.debug("Stopping config reloader.")
            self.config_reloader.stop()

        if self.plugin_reloading_enabled:
            logger.debug("Stopping plugin reloader.")
            self.plugin_reloader.stop()

        self.observer.stop()

        for connection in self.connections.values():
            if not connection.connected:
                # Don't quit a connection that hasn't connected
                continue
            logger.debug("[{}] Closing connection.".format(connection.name))

            connection.quit(reason)

        yield from asyncio.sleep(1.0)  # wait for 'QUIT' calls to take affect

        for connection in self.connections.values():
            connection.close()

        self.running = False
        # Give the stopped_future a result, so that run() will exit
        self.stopped_future.set_result(restart)

    @asyncio.coroutine
    def restart(self, reason=None):
        """shuts the bot down and restarts it"""
        yield from self.stop(reason=reason, restart=True)

    @asyncio.coroutine
    def _init_routine(self):
        # Load plugins
        yield from self.plugin_manager.load_all(os.path.abspath("plugins"))

        # If we we're stopped while loading plugins, cancel that and just stop
        if not self.running:
            logger.info("Killed while loading, exiting")
            return

        if self.plugin_reloading_enabled:
            # start plugin reloader
            self.plugin_reloader.start(os.path.abspath("plugins"))

        if self.config_reloading_enabled:
            self.config_reloader.start()

        self.observer.start()

        # Connect to servers
        yield from asyncio.gather(
            *[conn.try_connect() for conn in self.connections.values()],
            loop=self.loop)

        # Activate web interface.
        if self.config.get("web", {}).get("enabled", False) and web_installed:
            self.web.start()

        # Run a manual garbage collection cycle, to clean up any unused objects created during initialization
        gc.collect()

    def load_clients(self):
        """
        Load all clients from the "clients" directory
        """
        client_dir = self.base_dir / "cloudbot" / "clients"
        for path in client_dir.rglob('*.py'):
            rel_path = path.relative_to(self.base_dir)
            mod_path = '.'.join(rel_path.parts).rsplit('.', 1)[0]
            importlib.import_module(mod_path)

    @asyncio.coroutine
    def process(self, event):
        """
        :type event: Event
        """
        run_before_tasks = []
        tasks = []
        halted = False

        def add_hook(hook, _event, _run_before=False):
            nonlocal halted
            if halted:
                return False

            if hook.clients and _event.conn.type not in hook.clients:
                return True

            coro = self.plugin_manager.launch(hook, _event)
            if _run_before:
                run_before_tasks.append(coro)
            else:
                tasks.append(coro)

            if hook.action is Action.HALTALL:
                halted = True
                return False
            elif hook.action is Action.HALTTYPE:
                return False
            return True

        # Raw IRC hook
        for raw_hook in self.plugin_manager.catch_all_triggers:
            # run catch-all coroutine hooks before all others - TODO: Make this a plugin argument
            run_before = not raw_hook.threaded
            if not add_hook(raw_hook,
                            Event(hook=raw_hook, base_event=event),
                            _run_before=run_before):
                # The hook has an action of Action.HALT* so stop adding new tasks
                break

        if event.irc_command in self.plugin_manager.raw_triggers:
            for raw_hook in self.plugin_manager.raw_triggers[
                    event.irc_command]:
                if not add_hook(raw_hook, Event(hook=raw_hook,
                                                base_event=event)):
                    # The hook has an action of Action.HALT* so stop adding new tasks
                    break

        # Event hooks
        if event.type in self.plugin_manager.event_type_hooks:
            for event_hook in self.plugin_manager.event_type_hooks[event.type]:
                if not add_hook(event_hook,
                                Event(hook=event_hook, base_event=event)):
                    # The hook has an action of Action.HALT* so stop adding new tasks
                    break

        matched_command = False

        if event.type is EventType.message:
            # Commands
            cmd_match = get_cmd_regex(event).match(event.content)

            if cmd_match:
                command_prefix = event.conn.config.get('command_prefix', '.')
                prefix = cmd_match.group('prefix') or command_prefix
                command = cmd_match.group('command').lower()
                text = cmd_match.group('text').strip()
                cmd_event = partial(CommandEvent,
                                    text=text,
                                    triggered_command=command,
                                    base_event=event,
                                    cmd_prefix=prefix)
                if command in self.plugin_manager.commands:
                    command_hook = self.plugin_manager.commands[command]
                    command_event = cmd_event(hook=command_hook)
                    add_hook(command_hook, command_event)
                    matched_command = True
                else:
                    potential_matches = []
                    for potential_match, plugin in self.plugin_manager.commands.items(
                    ):
                        if potential_match.startswith(command):
                            potential_matches.append((potential_match, plugin))

                    if potential_matches:
                        matched_command = True
                        if len(potential_matches) == 1:
                            command_hook = potential_matches[0][1]
                            command_event = cmd_event(hook=command_hook)
                            add_hook(command_hook, command_event)
                        else:
                            commands = sorted(
                                command
                                for command, plugin in potential_matches)
                            txt_list = formatting.get_text_list(commands)
                            event.notice(
                                "Possible matches: {}".format(txt_list))

        if event.type in (EventType.message, EventType.action):
            # Regex hooks
            regex_matched = False
            for regex, regex_hook in self.plugin_manager.regex_hooks:
                if not regex_hook.run_on_cmd and matched_command:
                    continue

                if regex_hook.only_no_match and regex_matched:
                    continue

                regex_match = regex.search(event.content)
                if regex_match:
                    regex_matched = True
                    regex_event = RegexEvent(hook=regex_hook,
                                             match=regex_match,
                                             base_event=event)
                    if not add_hook(regex_hook, regex_event):
                        # The hook has an action of Action.HALT* so stop adding new tasks
                        break

        # Run the tasks
        yield from asyncio.gather(*run_before_tasks, loop=self.loop)
        yield from asyncio.gather(*tasks, loop=self.loop)