Ver código fonte

store config differently (in five separate global variables instead of one) to make accessing it cleaner; convert core/main.py to new config system as well as irc/frontend.py -- both seem to be working

tags/v0.1^2
Ben Kurtovic 13 anos atrás
pai
commit
430ba061af
3 arquivos alterados com 114 adições e 64 exclusões
  1. +60
    -35
      core/config.py
  2. +6
    -6
      core/main.py
  3. +48
    -23
      irc/frontend.py

+ 60
- 35
core/config.py Ver arquivo

@@ -7,8 +7,14 @@ This handles all tasks involving reading and writing to our config file,
including encrypting and decrypting passwords and making a new config file from
scratch at the inital bot run.

Usually you'll just want to do "from core.config import config" and access
config data from within that object.
Usually you'll just want to do "from core import config" and access config data
from within config's five global variables:

* config.components
* config.wiki
* config.irc
* config.schedule
* config.watcher
"""

from collections import defaultdict
@@ -23,7 +29,9 @@ root_dir = path.split(script_dir)[0]
config_path = path.join(root_dir, "config.xml")

_config = None # holds the parsed DOM object for our config file
config = None # holds an instance of Container() with our config data

# initialize our five global variables to store config data
components, wiki, irc, schedule, watcher = (None, None, None, None, None)

class ConfigParseError(Exception):
"""Base exception for when we could not parse the config file."""
@@ -84,27 +92,14 @@ def make_new_config():
return is_encrypted

def are_passwords_encrypted():
"""Determine if the passwords in our config file are encrypted, returning
either True or False."""
"""Determine if the passwords in our config file are encrypted; return
either True or False, or raise an exception if there was a problem reading
the config file."""
element = _config.getElementsByTagName("config")[0]
return attribute_to_bool(element, "encrypt-passwords", default=False)

def attribute_to_bool(element, attribute, default=None):
"""Return True if the value of element's attribute is 'true', '1', or 'on';
return False if it is 'false', '0', or 'off' (regardless of
capitalization); return default if it is empty; raise TypeMismatchError if
it does match any of those."""
value = element.getAttribute(attribute).lower()
if value in ["true", "1", "on"]:
return True
elif value in ["false", "0", "off"]:
attribute = element.getAttribute("encrypt-passwords")
if not attribute:
return False
elif value == '':
return default
else:
e = ("Expected a bool in attribute '{0}' of element '{1}', but " +
"got '{2}'.").format(attribute, element.tagName, value)
raise TypeMismatchError(e)
return attribute_to_bool(attribute, element, "encrypt-passwords")

def get_first_element(parent, tag_name):
"""Return the first child of the parent element with the given tag name, or
@@ -134,6 +129,32 @@ def get_required_attribute(element, attr_name):
raise MissingAttributeError(e)
return attribute

def attribute_to_bool(value, element, attr_name):
"""Return True if 'value' is 'true', '1', or 'on', return False if it is
'false', '0', or 'off' (regardless of capitalization), or raise
TypeMismatchError() if it does match any of those. 'element' and
'attr_name' are only used to generate the error message."""
lcase = value.lower()
if lcase in ["true", "1", "on"]:
return True
elif lcase in ["false", "0", "off"]:
return False
else:
e = ("Expected a bool in attribute '{0}' of tag '{1}', but got '{2}'."
).format(attr_name, element.tagName, value)
raise TypeMismatchError(e)

def attribute_to_int(value, element, attr_name):
"""Return 'value' after it is converted to an integer. If it could not be
converted, raise TypeMismatchError() using 'element' and 'attr_name' only
to give the user information about what happened."""
try:
return int(value)
except ValueError:
e = ("Expected an integer in attribute '{0}' of tag '{1}', but got " +
"'{2}'.").format(attr_name, element.tagName, value)
raise TypeMismatchError(e)

def parse_config(key):
"""A thin wrapper for the actual config parser in _parse_config(): catch
parsing exceptions and report them to the user cleanly."""
@@ -149,21 +170,20 @@ def parse_config(key):
exit(1)

def _parse_config(key):
"""Parse config data from a DOM object into the 'config' global variable.
The key is used to unencrypt passwords stored in the XML config file."""
"""Parse config data from a DOM object into the five global variables that
store our config info. The key is used to unencrypt passwords stored in the
XML config file."""
global components, wiki, irc, schedule, watcher

_load_config() # we might be re-loading unnecessarily here, but no harm in
# that!
data = _config.getElementsByTagName("config")[0]

cfg = Container()
cfg.components = parse_components(data)
cfg.wiki = parse_wiki(data, key)
cfg.irc = parse_irc(data, key)
cfg.schedule = parse_schedule(data)
cfg.watcher = parse_watcher(data)

global config
config = cfg
components = parse_components(data)
wiki = parse_wiki(data, key)
irc = parse_irc(data, key)
schedule = parse_schedule(data)
watcher = parse_watcher(data)

def parse_components(data):
"""Parse everything within the <components> XML tag of our config file.
@@ -187,14 +207,17 @@ def parse_wiki(data, key):
def parse_irc_server(data, key):
"""Parse everything within a <server> tag."""
server = Container()
connection = get_required_element(data, "connection")

server.host = get_required_attribute(connection, "host")
server.port = get_required_attribute(connection, "port")
server.nick = get_required_attribute(connection, "nick")
server.ident = get_required_attribute(connection, "ident")
server.realname = get_required_attribute(connection, "realname")

# convert the port from a string to an int
server.port = attribute_to_int(server.port, connection, "port")

nickserv = get_first_element(data, "nickserv")
if nickserv:
server.nickserv = Container()
@@ -204,10 +227,12 @@ def parse_irc_server(data, key):
server.nickserv.password = blowfish.decrypt(key, password)
else:
server.nickserv.password = password
else:
server.nickserv = None

server.channels = list()
channels = get_first_element(data, "channels")
if channels:
server.channels = list()
for channel in channels.getElementsByTagName("channel"):
name = get_required_attribute(channel, "name")
server.channels.append(name)


+ 6
- 6
core/main.py Ver arquivo

@@ -42,7 +42,7 @@ root_dir = os.path.split(script_dir)[0] # the bot's "root" directory relative
sys.path.append(root_dir) # make sure we look in the root dir for modules

from core import config
#from irc import frontend, watcher
from irc import frontend#, watcher
#from wiki import task_manager

f_conn = None
@@ -87,7 +87,7 @@ def irc_frontend(components):
f_conn = frontend.get_connection()
frontend.startup(f_conn)

if enable_wiki_schedule:
if config.components["wiki_schedule"]:
print "\nStarting wiki scheduler..."
task_manager.load_tasks()
t_scheduler = threading.Thread(target=wiki_scheduler)
@@ -95,7 +95,7 @@ def irc_frontend(components):
t_scheduler.daemon = True
t_scheduler.start()

if enable_irc_watcher:
if config.components["irc_watcher"]:
print "\nStarting IRC watcher..."
t_watcher = threading.Thread(target=irc_watcher, args=(f_conn,))
t_watcher.name = "irc-watcher"
@@ -104,7 +104,7 @@ def irc_frontend(components):

frontend.main()

if enable_irc_watcher:
if config.components["irc_watcher"]:
w_conn.close()
f_conn.close()

@@ -115,7 +115,7 @@ def run():
key = None
config.parse_config(key) # load data from the config file and parse it
# using the unlock key
components = config.config.components
components = config.components

if components["irc_frontend"]: # make the frontend run on our primary
irc_frontend(components) # thread if enabled, and enable additional
@@ -124,7 +124,7 @@ def run():
elif components["wiki_schedule"]: # run the scheduler on the main
print "Starting wiki scheduler..." # thread, but also run the IRC
task_manager.load_tasks() # watcher on another thread iff it
if enable_irc_watcher: # is enabled
if components["irc_watcher"]: # is enabled
print "\nStarting IRC watcher..."
t_watcher = threading.Thread(target=irc_watcher, args=(f_conn,))
t_watcher.name = "irc-watcher"


+ 48
- 23
irc/frontend.py Ver arquivo

@@ -1,11 +1,17 @@
# -*- coding: utf-8 -*-

## Imports
import re, time
"""
EarwigBot's Front-end IRC Component

from config.irc import *
from config.secure import *
The IRC frontend runs on a normal IRC server and expects users to interact with
it and give it commands. Commands are stored as "command classes", subclasses
of BaseCommand in irc/base_command.py. All command classes are automatically
imported by irc/command_handler.py if they are in irc/commands.
"""

from re import findall

from core import config
from irc import command_handler
from irc.connection import *
from irc.data import Data
@@ -13,16 +19,24 @@ from irc.data import Data
connection = None

def get_connection():
connection = Connection(HOST, PORT, NICK, IDENT, REALNAME)
"""Return a new Connection() instance with information about our server
connection, but don't actually connect yet."""
cf = config.irc.frontend
connection = Connection(cf.host, cf.port, cf.nick, cf.nick, cf.realname)
return connection

def startup(conn):
"""Accept a single arg, a Connection() object, and set our global variable
'connection' to it. Load all command classes in irc/commands with
command_handler, and then establish a connection with the IRC server."""
global connection
connection = conn
command_handler.load_commands(connection)
connection.connect()

def main():
"""Main loop for the Frontend IRC Bot component. get_connection() and
startup() should have already been called."""
read_buffer = str()

while 1:
@@ -35,41 +49,52 @@ def main():
lines = read_buffer.split("\n")
read_buffer = lines.pop()

for line in lines:
for line in lines: # handle a single message from IRC
line = line.strip().split()
data = Data()
data = Data() # new Data() instance to store info about this line
data.line = line

if line[1] == "JOIN":
data.nick, data.ident, data.host = re.findall(":(.*?)!(.*?)@(.*?)\Z", line[0])[0]
data.nick, data.ident, data.host = findall(
":(.*?)!(.*?)@(.*?)\Z", line[0])[0]
data.chan = line[2][1:]
command_handler.check("join", data) # check if there's anything we can respond to, and if so, respond
command_handler.check("join", data) # check for 'join' hooks in
# our commands

if line[1] == "PRIVMSG":
data.nick, data.ident, data.host = re.findall(":(.*?)!(.*?)@(.*?)\Z", line[0])[0]
data.nick, data.ident, data.host = findall(
":(.*?)!(.*?)@(.*?)\Z", line[0])[0]
data.msg = ' '.join(line[3:])[1:]
data.chan = line[2]

if data.chan == NICK: # this is a privmsg to us, so set 'chan' as the nick of the sender
if data.chan == config.irc.frontend.nick:
# this is a privmsg to us, so set 'chan' as the nick of the
# sender, then check for private-only command hooks
data.chan = data.nick
command_handler.check("msg_private", data) # only respond if it's a private message
command_handler.check("msg_private", data)
else:
command_handler.check("msg_public", data) # only respond if it's a public (channel) message
# check for public-only command hooks
command_handler.check("msg_public", data)

command_handler.check("msg", data) # check for general messages
# check for command hooks that apply to all messages
command_handler.check("msg", data)

if data.msg.startswith("!restart"): # hardcode the !restart command (we can't restart from within an ordinary command)
if data.host in OWNERS:
# hardcode the !restart command (we can't restart from within
# an ordinary command)
if data.msg in ["!restart", ".restart"]:
if data.host in config.irc.permissions["owners"]:
print "Restarting bot per owner request..."
return

if line[0] == "PING": # If we are pinged, pong back to the server
if line[0] == "PING": # if we are pinged, pong back to the server
connection.send("PONG %s" % line[1])

if line[1] == "376":
if NS_AUTH: # if we're supposed to auth to nickserv, do that
connection.say("NickServ", "IDENTIFY %s %s" % (NS_USER, NS_PASS))
time.sleep(3) # sleep for a bit so we don't join channels un-authed
for chan in CHANS: # join all of our startup channels
if line[1] == "376": # we've successfully connected to the network
ns = config.irc.frontend.nickserv
if ns: # if we're supposed to auth to nickserv, do that
connection.say("NickServ", "IDENTIFY %s %s" % (ns.username,
ns.password))
# join all of our startup channels
for chan in config.irc.frontend.channels:
connection.join(chan)

Carregando…
Cancelar
Salvar