diff --git a/lolbot.py b/lolbot.py index 5b106b8..9d22ff2 100644 --- a/lolbot.py +++ b/lolbot.py @@ -9,9 +9,11 @@ LOLBOT 2 from __future__ import print_function, unicode_literals +import sys import sqlite3 import random import time +import getopt import irc.strings from irc.bot import SingleServerIRCBot from sqlalchemy import create_engine @@ -37,8 +39,28 @@ class LolBot(SingleServerIRCBot): qb = list() - def __init__(self, channel, nickname, server, database, port=6667): + def __init__(self, config=None): + """ + Constructor. Instantiates a lolbot with a configuration dictionary, + or from command-line options if none is specified. + """ + + if not config: + config = LolBot.get_options() + + if not self.validate_config(config): + sys.exit(1) + + (server, port, channel, nickname, database) = ( + config['irc.server'], + config['irc.port'], + config['irc.channel'], + config['irc.nickname'], + config['db.file'], + ) + debug("Instantiating SingleServerIRCBot") + irc.client.ServerConnection.buffer_class = irc.buffer.LenientDecodingLineBuffer SingleServerIRCBot.__init__(self, [(server, port)], nickname, nickname) self.channel = channel @@ -73,7 +95,7 @@ class LolBot(SingleServerIRCBot): def on_nicknameinuse(self, connection, event): nick = connection.get_nickname() - debug("Nick '%s' in use, trying '%s_'" % nick) + debug("Nick '%s' in use, trying '%s_'" % (nick, nick)) connection.nick(nick + "_") def on_welcome(self, connection, event): @@ -247,36 +269,163 @@ class LolBot(SingleServerIRCBot): else: c.notice(nick, "Not understood: " + cmd) + def validate_config(self, config): + """ + Basic checks for configuration parameters. Returns a Boolean indicating + success or failure. + """ -def main(): - import sys - if len(sys.argv) != 5: - print("Usage: lolbot2.py ") - sys.exit(1) + # validate IRC host + if 'irc.server' not in config.keys(): + print("Error: the IRC server was not specified. Use --help for more information.") + return False + + # validate IRC port + if 'irc.port' not in config.keys(): + config['irc.port'] = '6667' + try: + config['irc.port'] = int(config['irc.port']) + except ValueError: + print("Error: the IRC port must be an integer. If not specified, lolbot will use the default IRC port value 6667. Use --help for more information.") + return False + + # validate IRC channel + if 'irc.channel' not in config.keys() or not config['irc.channel'].startswith('#'): + print("Error: the IRC channel is not specified or incorrect. It must begin with a # - e.g. #mychatchannel. Use --help for more information.") + return False + + # validate bot nickname + if 'irc.nickname' not in config.keys(): + config['irc.nickname'] = 'lolbot' + + # validate bot nickname + if 'db.file' not in config.keys(): + config['db.file'] = 'lolbot.db' + + return True + + @staticmethod + def get_options(): + """ + Set up configuration from the script arguments. + """ - s = sys.argv[1].split(":", 1) - server = s[0] - if len(s) == 2: try: - port = int(s[1]) - except ValueError: - print("Error: Erroneous port.") + (options, args) = getopt.getopt(sys.argv[1:], 'hc:s:p:j:n:d:', ['help', 'config=', 'server=', 'port=', 'join=', 'nick=', 'database=', ]) + except getopt.GetoptError as err: + print(str(err)) + LolBot.usage() + sys.exit(2) + + config = {} + for option, value in options: + # Display help text. + if option in ('-h', '--help'): + LolBot.usage() + sys.exit(2) + + # Get configuration from a file. + if option in ('-c', '--config'): + config = load_config(value) + break + + # Individually specified settings. + if option in ('-s', '--server'): + config['irc.server'] = value + if option in ('-p', '--port'): + config['irc.port'] = value + if option in ('-j', '--join', '--channel', '--join-channel'): + config['irc.channel'] = value + if option in ('-n', '--nickname'): + config['irc.nickname'] = value + if option in ('-d', '--database'): + config['db.file'] = value + + return config + + @staticmethod + def load_config(config_path=''): + """ + This method loads configuration options from a lolbot.conf file. The file + should look something like this:: + + irc.server = irc.yourdomain.com + irc.port = 6667 + irc.channel = #lolbottest + irc.nickname = lolbot + db.file = lolbot.db + """ + + if config_path == '': + config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'lolbot.conf') + if not os.path.exists(config_path): + print("Error: configuration file not found. By default lolbot will look for a lolbot.conf file in the same directory as the lolbot script, but you can override this by specifying a path on the command line with the --config option.") + LolBot.usage() sys.exit(1) - else: - port = 6667 - channel = sys.argv[2] - nickname = sys.argv[3] - database = sys.argv[4] - debug("Parameters: server=%s port=%s nickname=%s channel=%s database=%s" % (server, port, nickname, channel, database)) + # open the configuration file and grab all param=value declarations. + config = {} + with open(config_path) as f: + for line in f: + # skip comments + if line.strip().startswith('#') or line.strip() == '': + continue - irc.client.ServerConnection.buffer_class = irc.buffer.LenientDecodingLineBuffer - bot = LolBot(channel, nickname, server, database, port) - bot.start() + # collect up param = value + try: + (param, value) = line.strip().split('=', 1) + if param.strip() != '': + config[param.strip()] = value.strip() + except ValueError: + continue + + return config + + @staticmethod + def usage(): + """ + Spits out CLI help. + """ + + print("""Run a lolbot. + + -h, --help + This message. + + -s, --server= + The IRC server, e.g. irc.freenode.net + + -p, --port= + The IRC server port. Default: 6667 + + -j, --join= + The chat channel to join, e.g. #trainspotting + + -n, --nickname= + The nickname for your lolbot. Default: "lolbot" + + -d, --database= + The path to a SQLite lolbot database. If not specified, lolbot will + attempt to use a SQLite database called lolbot.db in the same + directory as the script. + + Configuration file: + + -c, --config= + Specify a configuration file. Ignores any options specified from the + command-line. Defaults to lolbot.conf in the same directory as the + script. File layout: + + irc.server = + irc.port = + irc.channel = + irc.nickname = + db.file = +""".strip()) if __name__ == "__main__": try: - main() + LolBot().start() except KeyboardInterrupt: pass