@@ -4,12 +4,12 @@ | |||
# Ignore bot-specific config file: | |||
config.json | |||
# Ignore logs directory: | |||
logs/ | |||
# Ignore cookies file: | |||
.cookies | |||
# Ignore statistics file: | |||
statistics.txt | |||
# Ignore OS X's crud: | |||
.DS_Store | |||
@@ -12,12 +12,13 @@ class Connection(object): | |||
"""A class to interface with IRC.""" | |||
def __init__(self, host=None, port=None, nick=None, ident=None, | |||
realname=None): | |||
realname=None, logger=None): | |||
self.host = host | |||
self.port = port | |||
self.nick = nick | |||
self.ident = ident | |||
self.realname = realname | |||
self.logger = logger | |||
# A lock to prevent us from sending two messages at once: | |||
self.lock = threading.Lock() | |||
@@ -50,7 +51,7 @@ class Connection(object): | |||
# Ensure that we only send one message at a time with a blocking lock: | |||
with self.lock: | |||
self.sock.sendall(msg + "\r\n") | |||
print " %s" % msg | |||
self.logger.debug(msg) | |||
def say(self, target, msg): | |||
"""Send a private message to a target on the server.""" | |||
@@ -7,9 +7,9 @@ This package provides the IRC "commands" used by the bot's front-end component. | |||
In __init__, you can find some functions used to load and run these commands. | |||
""" | |||
import logging | |||
import os | |||
import sys | |||
import traceback | |||
from classes import BaseCommand | |||
import config | |||
@@ -23,6 +23,9 @@ base_dir = os.path.join(config.root_dir, "bot", "commands") | |||
# is an instance of the command's class: | |||
_commands = {} | |||
# Logger for this module: | |||
logger = logging.getLogger("tasks") | |||
def _load_command(connection, filename): | |||
"""Try to load a specific command from a module, identified by file name. | |||
@@ -39,8 +42,7 @@ def _load_command(connection, filename): | |||
try: | |||
__import__(name) | |||
except: | |||
print "Couldn't load file {0}:".format(filename) | |||
traceback.print_exc() | |||
logger.exception("Couldn't load file {0}".format(filename)) | |||
return | |||
command = sys.modules[name].Command(connection) | |||
@@ -48,7 +50,7 @@ def _load_command(connection, filename): | |||
return | |||
_commands[command.name] = command | |||
print "Added command {0}...".format(command.name) | |||
logger.debug("Added command {0}".format(command.name)) | |||
def load(connection): | |||
"""Load all valid commands into the _commands global variable. | |||
@@ -67,8 +69,8 @@ def load(connection): | |||
except AttributeError: | |||
pass # The file is doesn't contain a command, so just move on | |||
msg = "Found {0} commands: {1}." | |||
print msg.format(len(_commands), ", ".join(_commands.keys())) | |||
msg = "Found {0} commands: {1}" | |||
logger.info(msg.format(len(_commands), ", ".join(_commands.keys()))) | |||
def get_all(): | |||
"""Return our dict of all loaded commands.""" | |||
@@ -85,6 +87,5 @@ def check(hook, data): | |||
try: | |||
command.process(data) | |||
except: | |||
print "Error executing command '{0}':".format(data.command) | |||
traceback.print_exc() | |||
logger.exception("Error executing command '{0}'".format(data.command)) | |||
break |
@@ -70,7 +70,7 @@ def _setup_logging(): | |||
stream_handler = logging.StreamHandler() | |||
main_handler.setLevel(logging.INFO) | |||
error_handler.setLevel(logging.ERROR) | |||
error_handler.setLevel(logging.WARNING) | |||
debug_handler.setLevel(logging.DEBUG) | |||
stream_handler.setLevel(logging.DEBUG) | |||
@@ -9,6 +9,7 @@ of BaseCommand in irc/base_command.py. All command classes are automatically | |||
imported by irc/command_handler.py if they are in irc/commands. | |||
""" | |||
import logging | |||
import re | |||
import config | |||
@@ -18,6 +19,7 @@ from classes import Connection, Data, BrokenSocketException | |||
__all__ = ["get_connection", "startup", "main"] | |||
connection = None | |||
logger = logging.getLogger("frontend") | |||
sender_regex = re.compile(":(.*?)!(.*?)@(.*?)\Z") | |||
def get_connection(): | |||
@@ -25,7 +27,7 @@ def get_connection(): | |||
connection, but don't actually connect yet.""" | |||
cf = config.irc["frontend"] | |||
connection = Connection(cf["host"], cf["port"], cf["nick"], cf["ident"], | |||
cf["realname"]) | |||
cf["realname"], logger) | |||
return connection | |||
def startup(conn): | |||
@@ -48,7 +50,7 @@ def main(): | |||
try: | |||
read_buffer = read_buffer + connection.get() | |||
except BrokenSocketException: | |||
print "Socket has broken on front-end; restarting bot..." | |||
logger.warn("Socket has broken on front-end; restarting bot") | |||
return | |||
lines = read_buffer.split("\n") | |||
@@ -90,7 +92,7 @@ def _process_message(line): | |||
# ordinary command): | |||
if data.msg in ["!restart", ".restart"]: | |||
if data.host in config.irc["permissions"]["owners"]: | |||
print "Restarting bot per owner request..." | |||
logger.info("Restarting bot per owner request") | |||
return True | |||
# If we are pinged, pong back: | |||
@@ -30,9 +30,9 @@ There is a "priority" system here: | |||
Else, the bot will stop, as no components are enabled. | |||
""" | |||
import logging | |||
import threading | |||
import time | |||
import traceback | |||
import config | |||
import frontend | |||
@@ -49,13 +49,12 @@ def irc_watcher(f_conn=None): | |||
while 1: # restart the watcher component if it breaks (and nothing else) | |||
w_conn = watcher.get_connection() | |||
w_conn.connect() | |||
print # blank line to signify that the bot has finished starting up | |||
try: | |||
watcher.main(w_conn, f_conn) | |||
except: | |||
traceback.print_exc() | |||
logging.exception("Watcher had an error") | |||
time.sleep(5) # sleep a bit before restarting watcher | |||
print "\nWatcher has stopped; restarting component..." | |||
logging.warn("Watcher has stopped; restarting component") | |||
def wiki_scheduler(): | |||
"""Function to handle the wiki scheduler as another thread, or as the | |||
@@ -77,12 +76,12 @@ def irc_frontend(): | |||
enabled.""" | |||
global f_conn | |||
print "Starting IRC frontend..." | |||
logging.info("Starting IRC frontend") | |||
f_conn = frontend.get_connection() | |||
frontend.startup(f_conn) | |||
if "wiki_schedule" in config.components: | |||
print "\nStarting wiki scheduler..." | |||
logging.info("Starting wiki scheduler") | |||
tasks.load() | |||
t_scheduler = threading.Thread(target=wiki_scheduler) | |||
t_scheduler.name = "wiki-scheduler" | |||
@@ -90,7 +89,7 @@ def irc_frontend(): | |||
t_scheduler.start() | |||
if "irc_watcher" in config.components: | |||
print "\nStarting IRC watcher..." | |||
logging.info("Starting IRC watcher") | |||
t_watcher = threading.Thread(target=irc_watcher, args=(f_conn,)) | |||
t_watcher.name = "irc-watcher" | |||
t_watcher.daemon = True | |||
@@ -105,40 +104,47 @@ def irc_frontend(): | |||
def run(): | |||
config.load() | |||
try: | |||
key = raw_input() # wait for our password decrypt key from the bot's | |||
except EOFError: # wrapper, then decrypt passwords | |||
# 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 | |||
irc_frontend() # thread if enabled, and enable additional | |||
# components through that function | |||
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 | |||
print "Starting wiki scheduler..." # thread, but also run the IRC | |||
tasks.load() # watcher on another thread iff it | |||
if "irc_watcher" in enabled: # is enabled | |||
print "\nStarting IRC watcher..." | |||
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 | |||
print "Starting IRC watcher..." # component, so run its function only | |||
irc_watcher() # and don't worry about anything else | |||
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! | |||
print "No bot parts are enabled; stopping..." | |||
else: # Nothing is enabled! | |||
logging.critical("No bot parts are enabled; stopping") | |||
exit(1) | |||
if __name__ == "__main__": | |||
try: | |||
run() | |||
except KeyboardInterrupt: | |||
print "\nKeyboardInterrupt: stopping main bot loop." | |||
logging.critical("KeyboardInterrupt: stopping main bot loop") | |||
exit(1) |
@@ -7,11 +7,11 @@ This package provides the wiki bot "tasks" EarwigBot runs. Here in __init__, | |||
you can find some functions used to load and run these tasks. | |||
""" | |||
import logging | |||
import os | |||
import sys | |||
import threading | |||
import time | |||
import traceback | |||
from classes import BaseTask | |||
import config | |||
@@ -25,6 +25,9 @@ base_dir = os.path.join(config.root_dir, "bot", "tasks") | |||
# an instance of the task class: | |||
_tasks = {} | |||
# Logger for this module: | |||
logger = logging.getLogger("commands") | |||
def _load_task(filename): | |||
"""Try to load a specific task from a module, identified by file name.""" | |||
global _tasks | |||
@@ -34,8 +37,7 @@ def _load_task(filename): | |||
try: | |||
__import__(name) | |||
except: | |||
print "Couldn't load file {0}:".format(filename) | |||
traceback.print_exc() | |||
logger.exception("Couldn't load file {0}:".format(filename)) | |||
return | |||
task = sys.modules[name].Task() | |||
@@ -44,18 +46,17 @@ def _load_task(filename): | |||
return | |||
_tasks[task.name] = task | |||
print "Added task {0}...".format(task.name) | |||
logger.debug("Added task {0}".format(task.name)) | |||
def _wrapper(task, **kwargs): | |||
"""Wrapper for task classes: run the task and catch any errors.""" | |||
try: | |||
task.run(**kwargs) | |||
except: | |||
error = "Task '{0}' raised an exception and had to stop:" | |||
print error.format(task.name) | |||
traceback.print_exc() | |||
error = "Task '{0}' raised an exception and had to stop" | |||
logger.exception(error.format(task.name)) | |||
else: | |||
print "Task '{0}' finished without error.".format(task.name) | |||
logger.info("Task '{0}' finished without error".format(task.name)) | |||
def load(): | |||
"""Load all valid tasks from bot/tasks/, into the _tasks variable.""" | |||
@@ -70,7 +71,7 @@ def load(): | |||
except AttributeError: | |||
pass # The file is doesn't contain a task, so just move on | |||
print "Found {0} tasks: {1}.".format(len(_tasks), ', '.join(_tasks.keys())) | |||
logger.info("Found {0} tasks: {1}".format(len(_tasks), ', '.join(_tasks.keys()))) | |||
def schedule(now=time.gmtime()): | |||
"""Start all tasks that are supposed to be run at a given time.""" | |||
@@ -87,13 +88,13 @@ def schedule(now=time.gmtime()): | |||
def start(task_name, **kwargs): | |||
"""Start a given task in a new thread. Pass args to the task's run() | |||
function.""" | |||
print "Starting task '{0}' in a new thread...".format(task_name) | |||
logger.info("Starting task '{0}' in a new thread".format(task_name)) | |||
try: | |||
task = _tasks[task_name] | |||
except KeyError: | |||
error = "Couldn't find task '{0}': bot/tasks/{0}.py does not exist." | |||
print error.format(task_name) | |||
error = "Couldn't find task '{0}': bot/tasks/{0}.py does not exist" | |||
logger.error(error.format(task_name)) | |||
return | |||
task_thread = threading.Thread(target=lambda: _wrapper(task, **kwargs)) | |||
@@ -10,11 +10,14 @@ being started (located in tasks/) or messages being sent to channels on the IRC | |||
frontend. | |||
""" | |||
import logging | |||
import config | |||
from classes import Connection, RC, BrokenSocketException | |||
import rules | |||
frontend_conn = None | |||
logger = logging.getLogger("watcher") | |||
def get_connection(): | |||
"""Return a new Connection() instance with connection information. | |||
@@ -23,7 +26,7 @@ def get_connection(): | |||
""" | |||
cf = config.irc["watcher"] | |||
connection = Connection(cf["host"], cf["port"], cf["nick"], cf["ident"], | |||
cf["realname"]) | |||
cf["realname"], logger) | |||
return connection | |||
def main(connection, f_conn=None): | |||
@@ -10,6 +10,10 @@ written by Mr.Z-man, other than a similar purpose. We share no code. | |||
Import the toolset with `import wiki`. | |||
""" | |||
import logging | |||
logger = logging.getLogger("wiki") | |||
logger.addHandler(logging.NullHandler()) | |||
from wiki.constants import * | |||
from wiki.exceptions import * | |||
from wiki.functions import * | |||
@@ -16,6 +16,7 @@ try: | |||
except ImportError: | |||
oursql = None | |||
from wiki import logger | |||
from wiki.category import Category | |||
from wiki.constants import * | |||
from wiki.exceptions import * | |||
@@ -169,7 +170,7 @@ class Site(object): | |||
data = urlencode(params) | |||
print url, data # debug code | |||
logger.debug("{0} -> {1}".format(url, data)) | |||
try: | |||
response = self._opener.open(url, data) | |||
@@ -207,7 +208,7 @@ class Site(object): | |||
raise SiteAPIError(e.format(self._max_retries)) | |||
tries += 1 | |||
msg = 'Server says: "{0}". Retrying in {1} seconds ({2}/{3}).' | |||
print msg.format(info, wait, tries, self._max_retries) | |||
logger.info(msg.format(info, wait, tries, self._max_retries)) | |||
sleep(wait) | |||
return self._api_query(params, tries=tries, wait=wait*3) | |||
else: | |||