#! /usr/bin/env python # -*- coding: utf-8 -*- """ EarwigBot's Core This should not be run directly; the wrapper in "earwigbot.py" is preferred, but it should work fine alone, as long as you enter the password-unlock key at the initial hidden prompt if one is needed. The core is essentially responsible for starting the various bot components (irc, scheduler, etc) and making sure they are all happy. An explanation of the different components follows: EarwigBot has three components that can run independently of each other: an IRC front-end, an IRC watcher, and a wiki scheduler. * The IRC front-end runs on a normal IRC server and expects users to interact with it/give it commands. * The IRC watcher runs on a wiki recent-changes server and listens for edits. Users cannot interact with this part of the bot. * The wiki scheduler runs wiki-editing bot tasks in separate threads at user-defined times through a cron-like interface. There is a "priority" system here: 1. If the IRC frontend is enabled, it will run on the main thread, and the IRC watcher and wiki scheduler (if enabled) will run on separate threads. 2. If the wiki scheduler is enabled, it will run on the main thread, and the IRC watcher (if enabled) will run on a separate thread. 3. If the IRC watcher is enabled, it will run on the main (and only) thread. Else, the bot will stop, as no components are enabled. """ import logging import threading import time import config import frontend import tasks import watcher f_conn = None w_conn = None def irc_watcher(f_conn=None): """Function to handle the IRC watcher as another thread (if frontend and/or scheduler is enabled), otherwise run as the main thread.""" global w_conn while 1: # restart the watcher component if it breaks (and nothing else) w_conn = watcher.get_connection() w_conn.connect() try: watcher.main(w_conn, f_conn) except: logging.exception("Watcher had an error") time.sleep(5) # sleep a bit before restarting watcher logging.warn("Watcher has stopped; restarting component") def wiki_scheduler(): """Function to handle the wiki scheduler as another thread, or as the primary thread if the IRC frontend is not enabled.""" while 1: time_start = time.time() now = time.gmtime(time_start) tasks.schedule(now) time_end = time.time() time_diff = time_start - time_end if time_diff < 60: # sleep until the next minute time.sleep(60 - time_diff) def irc_frontend(): """If the IRC frontend is enabled, make it run on our primary thread, and enable the wiki scheduler and IRC watcher on new threads if they are enabled.""" global f_conn logging.info("Starting IRC frontend") f_conn = frontend.get_connection() frontend.startup(f_conn) if "wiki_schedule" in config.components: logging.info("Starting wiki scheduler") tasks.load() t_scheduler = threading.Thread(target=wiki_scheduler) t_scheduler.name = "wiki-scheduler" t_scheduler.daemon = True t_scheduler.start() if "irc_watcher" in config.components: logging.info("Starting IRC watcher") t_watcher = threading.Thread(target=irc_watcher, args=(f_conn,)) t_watcher.name = "irc-watcher" t_watcher.daemon = True t_watcher.start() frontend.main() if "irc_watcher" in config.components: w_conn.close() f_conn.close() def run(): config.load() try: # Wait for our password decrypt key from the bot's wrapper, then # decrypt passwords: key = raw_input() except EOFError: pass else: config.decrypt(key) enabled = config.components if "irc_frontend" in enabled: # Make the frontend run on our primary thread if enabled, and enable # additional components through that function irc_frontend() elif "wiki_schedule" in enabled: # Run the scheduler on the main thread, but also run the IRC watcher on # another thread iff it is enabled logging.info("Starting wiki scheduler") tasks.load() if "irc_watcher" in enabled: logging.info("Starting IRC watcher") t_watcher = threading.Thread(target=irc_watcher) t_watcher.name = "irc-watcher" t_watcher.daemon = True t_watcher.start() wiki_scheduler() elif "irc_watcher" in enabled: # The IRC watcher is our only enabled component, so run its function # only and don't worry about anything else: logging.info("Starting IRC watcher") irc_watcher() else: # Nothing is enabled! logging.critical("No bot parts are enabled; stopping") exit(1) if __name__ == "__main__": try: run() except KeyboardInterrupt: logging.critical("KeyboardInterrupt: stopping main bot loop") exit(1)