| @@ -23,10 +23,13 @@ | |||||
| import threading | import threading | ||||
| from time import sleep, time | from time import sleep, time | ||||
| from earwigbot.commands import CommandManager | |||||
| from earwigbot.config import BotConfig | from earwigbot.config import BotConfig | ||||
| from earwigbot.irc import Frontend, Watcher | from earwigbot.irc import Frontend, Watcher | ||||
| from earwigbot.tasks import task_manager | from earwigbot.tasks import task_manager | ||||
| __all__ = ["Bot"] | |||||
| class Bot(object): | class Bot(object): | ||||
| """ | """ | ||||
| The Bot class is the core of EarwigBot, essentially responsible for | The Bot class is the core of EarwigBot, essentially responsible for | ||||
| @@ -46,16 +49,14 @@ class Bot(object): | |||||
| def __init__(self, root_dir): | def __init__(self, root_dir): | ||||
| self.config = BotConfig(root_dir) | self.config = BotConfig(root_dir) | ||||
| self.logger = logging.getLogger("earwigbot") | self.logger = logging.getLogger("earwigbot") | ||||
| self.commands = CommandManager(self) | |||||
| self.tasks = None | |||||
| self.frontend = None | self.frontend = None | ||||
| self.watcher = None | self.watcher = None | ||||
| self._keep_scheduling = True | self._keep_scheduling = True | ||||
| self._lock = threading.Lock() | self._lock = threading.Lock() | ||||
| def _start_thread(self, name, target): | |||||
| thread = threading.Thread(name=name, target=target) | |||||
| thread.start() | |||||
| def _wiki_scheduler(self): | def _wiki_scheduler(self): | ||||
| while self._keep_scheduling: | while self._keep_scheduling: | ||||
| time_start = time() | time_start = time() | ||||
| @@ -68,17 +69,18 @@ class Bot(object): | |||||
| def _start_components(self): | def _start_components(self): | ||||
| if self.config.components.get("irc_frontend"): | if self.config.components.get("irc_frontend"): | ||||
| self.logger.info("Starting IRC frontend") | self.logger.info("Starting IRC frontend") | ||||
| self.frontend = Frontend(self.config) | |||||
| self._start_thread(name, self.frontend.loop) | |||||
| self.frontend = Frontend(self) | |||||
| self.commands.load() | |||||
| threading.Thread(name=name, target=self.frontend.loop).start() | |||||
| if self.config.components.get("irc_watcher"): | if self.config.components.get("irc_watcher"): | ||||
| self.logger.info("Starting IRC watcher") | self.logger.info("Starting IRC watcher") | ||||
| self.watcher = Watcher(self.config, self.frontend) | |||||
| self._start_thread(name, self.watcher.loop) | |||||
| self.watcher = Watcher(self) | |||||
| threading.Thread(name=name, target=self.watcher.loop).start() | |||||
| if self.config.components.get("wiki_scheduler"): | if self.config.components.get("wiki_scheduler"): | ||||
| self.logger.info("Starting wiki scheduler") | self.logger.info("Starting wiki scheduler") | ||||
| self._start_thread(name, self._wiki_scheduler) | |||||
| threading.Thread(name=name, target=self._wiki_scheduler).start() | |||||
| def _loop(self): | def _loop(self): | ||||
| while 1: | while 1: | ||||
| @@ -104,6 +106,7 @@ class Bot(object): | |||||
| with self._lock: | with self._lock: | ||||
| self.config.load() | self.config.load() | ||||
| #if self.config.components.get("irc_frontend"): | #if self.config.components.get("irc_frontend"): | ||||
| # self.commands.load() | |||||
| def stop(self): | def stop(self): | ||||
| if self.frontend: | if self.frontend: | ||||
| @@ -111,3 +114,4 @@ class Bot(object): | |||||
| if self.watcher: | if self.watcher: | ||||
| self.watcher.stop() | self.watcher.stop() | ||||
| self._keep_scheduling = False | self._keep_scheduling = False | ||||
| sleep(3) # Give a few seconds to finish closing IRC connections | |||||
| @@ -25,17 +25,16 @@ EarwigBot's IRC Command Manager | |||||
| This package provides the IRC "commands" used by the bot's front-end component. | This package provides the IRC "commands" used by the bot's front-end component. | ||||
| This module contains the BaseCommand class (import with | This module contains the BaseCommand class (import with | ||||
| `from earwigbot.commands import BaseCommand`) and an internal _CommandManager | |||||
| class. This can be accessed through the `command_manager` singleton. | |||||
| `from earwigbot.commands import BaseCommand`) and an internal CommandManager | |||||
| class. This can be accessed through `bot.commands`. | |||||
| """ | """ | ||||
| import imp | |||||
| import logging | import logging | ||||
| import os | |||||
| import sys | |||||
| from os import listdir, path | |||||
| from re import sub | |||||
| from earwigbot.config import config | |||||
| __all__ = ["BaseCommand", "command_manager"] | |||||
| __all__ = ["BaseCommand", "CommandManager"] | |||||
| class BaseCommand(object): | class BaseCommand(object): | ||||
| """A base class for commands on IRC. | """A base class for commands on IRC. | ||||
| @@ -88,32 +87,33 @@ class BaseCommand(object): | |||||
| pass | pass | ||||
| class _CommandManager(object): | |||||
| def __init__(self): | |||||
| class CommandManager(object): | |||||
| def __init__(self, bot): | |||||
| self.bot = bot | |||||
| self.logger = logging.getLogger("earwigbot.tasks") | self.logger = logging.getLogger("earwigbot.tasks") | ||||
| self._base_dir = os.path.dirname(os.path.abspath(__file__)) | |||||
| self._connection = None | |||||
| self._dirs = [path.dirname(__file__), bot.config.root_dir] | |||||
| self._commands = {} | self._commands = {} | ||||
| def _load_command(self, filename): | |||||
| """Load a specific command from a module, identified by filename. | |||||
| def _load_command(self, name, path): | |||||
| """Load a specific command from a module, identified by name and path. | |||||
| Given a Connection object and a filename, we'll first try to import | |||||
| it, and if that works, make an instance of the 'Command' class inside | |||||
| (assuming it is an instance of BaseCommand), add it to self._commands, | |||||
| and log the addition. Any problems along the way will either be | |||||
| ignored or logged. | |||||
| We'll first try to import it using imp magic, and if that works, make | |||||
| an instance of the 'Command' class inside (assuming it is an instance | |||||
| of BaseCommand), add it to self._commands, and log the addition. Any | |||||
| problems along the way will either be ignored or logged. | |||||
| """ | """ | ||||
| # Strip .py from the filename's end and join with our package name: | |||||
| name = ".".join(("commands", filename[:-3])) | |||||
| f, path, desc = imp.find_module(name, [path]) | |||||
| try: | try: | ||||
| __import__(name) | |||||
| except: | |||||
| self.logger.exception("Couldn't load file {0}".format(filename)) | |||||
| module = imp.load_module(name, f, path, desc) | |||||
| except Exception: | |||||
| e = "Couldn't load module {0} from {1}" | |||||
| self.logger.exception(e.format(name, path)) | |||||
| return | return | ||||
| finally: | |||||
| f.close() | |||||
| try: | try: | ||||
| command = sys.modules[name].Command(self._connection) | |||||
| command = module.Command(self.bot.frontend) | |||||
| except AttributeError: | except AttributeError: | ||||
| return # No command in this module | return # No command in this module | ||||
| if not isinstance(command, BaseCommand): | if not isinstance(command, BaseCommand): | ||||
| @@ -122,20 +122,16 @@ class _CommandManager(object): | |||||
| self._commands[command.name] = command | self._commands[command.name] = command | ||||
| self.logger.debug("Added command {0}".format(command.name)) | self.logger.debug("Added command {0}".format(command.name)) | ||||
| def load(self, connection): | |||||
| """Load all valid commands into self._commands. | |||||
| `connection` is a Connection object that is given to each command's | |||||
| constructor. | |||||
| """ | |||||
| self._connection = connection | |||||
| files = os.listdir(self._base_dir) | |||||
| files.sort() | |||||
| for filename in files: | |||||
| if filename.startswith("_") or not filename.endswith(".py"): | |||||
| continue | |||||
| self._load_command(filename) | |||||
| def load(self): | |||||
| """Load (or reload) all valid commands into self._commands.""" | |||||
| dirs = [path.join(path.dirname(__file__), "commands"), | |||||
| path.join(bot.config.root_dir, "commands")] | |||||
| for dir in dirs: | |||||
| files = listdir(dir) | |||||
| files = [sub("\.pyc?$", "", f) for f in files if f[0] != "_"] | |||||
| files = list(set(files)) # Remove duplicates | |||||
| for filename in sorted(files): | |||||
| self._load_command(filename, dir) | |||||
| msg = "Found {0} commands: {1}" | msg = "Found {0} commands: {1}" | ||||
| commands = ", ".join(self._commands.keys()) | commands = ", ".join(self._commands.keys()) | ||||
| @@ -158,6 +154,3 @@ class _CommandManager(object): | |||||
| e = "Error executing command '{0}'" | e = "Error executing command '{0}'" | ||||
| self.logger.exception(e.format(data.command)) | self.logger.exception(e.format(data.command)) | ||||
| break | break | ||||
| command_manager = _CommandManager() | |||||
| @@ -23,7 +23,6 @@ | |||||
| import logging | import logging | ||||
| import re | import re | ||||
| from earwigbot.commands import command_manager | |||||
| from earwigbot.irc import IRCConnection, Data, BrokenSocketException | from earwigbot.irc import IRCConnection, Data, BrokenSocketException | ||||
| __all__ = ["Frontend"] | __all__ = ["Frontend"] | ||||
| @@ -40,14 +39,15 @@ class Frontend(IRCConnection): | |||||
| """ | """ | ||||
| sender_regex = re.compile(":(.*?)!(.*?)@(.*?)\Z") | sender_regex = re.compile(":(.*?)!(.*?)@(.*?)\Z") | ||||
| def __init__(self, config): | |||||
| self.config = config | |||||
| def __init__(self, bot): | |||||
| self.bot = bot | |||||
| self.config = bot.config | |||||
| self.logger = logging.getLogger("earwigbot.frontend") | self.logger = logging.getLogger("earwigbot.frontend") | ||||
| cf = config.irc["frontend"] | cf = config.irc["frontend"] | ||||
| base = super(Frontend, self) | base = super(Frontend, self) | ||||
| base.__init__(cf["host"], cf["port"], cf["nick"], cf["ident"], | base.__init__(cf["host"], cf["port"], cf["nick"], cf["ident"], | ||||
| cf["realname"], self.logger) | cf["realname"], self.logger) | ||||
| command_manager.load(self) | |||||
| self._connect() | self._connect() | ||||
| def _process_message(self, line): | def _process_message(self, line): | ||||
| @@ -38,14 +38,16 @@ class Watcher(IRCConnection): | |||||
| to channels on the IRC frontend. | to channels on the IRC frontend. | ||||
| """ | """ | ||||
| def __init__(self, config, frontend=None): | |||||
| self.config = config | |||||
| def __init__(self, bot): | |||||
| self.bot = bot | |||||
| self.config = bot.config | |||||
| self.frontend = bot.frontend | |||||
| self.logger = logging.getLogger("earwigbot.watcher") | self.logger = logging.getLogger("earwigbot.watcher") | ||||
| cf = config.irc["watcher"] | cf = config.irc["watcher"] | ||||
| base = super(Watcher, self) | base = super(Watcher, self) | ||||
| base.__init__(cf["host"], cf["port"], cf["nick"], cf["ident"], | base.__init__(cf["host"], cf["port"], cf["nick"], cf["ident"], | ||||
| cf["realname"], self.logger) | cf["realname"], self.logger) | ||||
| self.frontend = frontend | |||||
| self._prepare_process_hook() | self._prepare_process_hook() | ||||
| self._connect() | self._connect() | ||||
| @@ -36,12 +36,22 @@ class BotUtility(object): | |||||
| return __version__ | return __version__ | ||||
| def run(self): | def run(self): | ||||
| print "EarwigBot v{0}\n".format(self.version()) | |||||
| def main(self): | |||||
| root_dir = path.abspath(path.curdir()) | root_dir = path.abspath(path.curdir()) | ||||
| bot = Bot(root_dir) | bot = Bot(root_dir) | ||||
| bot.run() | |||||
| try: | |||||
| bot.run() | |||||
| finally: | |||||
| bot.stop() | |||||
| def main(self): | |||||
| print "EarwigBot v{0}\n".format(self.version()) | |||||
| parser = argparse.ArgumentParser(description=BotUtility.__doc__) | |||||
| parser.add_argument("-V", "--version", action="version", | |||||
| version=self.version()) | |||||
| args = parser.parse_args() | |||||
| # args.func(args) | |||||
| main = BotUtility().main | main = BotUtility().main | ||||
| @@ -30,8 +30,10 @@ from setuptools import setup | |||||
| setup( | setup( | ||||
| name = "earwigbot", | name = "earwigbot", | ||||
| entry_points = {"console_scripts": ["earwigbot = earwigbot.util:main"]}, | entry_points = {"console_scripts": ["earwigbot = earwigbot.util:main"]}, | ||||
| install_requires = ["PyYAML>=3.10", "oursql>=0.9.3", "oauth2>=1.5.211", | |||||
| "numpy>=1.6.1", "matplotlib>=1.1.0"], | |||||
| install_requires = ["PyYAML>=3.10", | |||||
| "oursql>=0.9.3", | |||||
| "oauth2>=1.5.211", | |||||
| "matplotlib>=1.1.0"], | |||||
| version = "0.1.dev", | version = "0.1.dev", | ||||
| author = "Ben Kurtovic", | author = "Ben Kurtovic", | ||||
| author_email = "ben.kurtovic@verizon.net", | author_email = "ben.kurtovic@verizon.net", | ||||