Merge branch 'refresh' of https://github.com/ClouDev/CloudBot into refresh
This commit is contained in:
commit
68ef55e74d
9 changed files with 70 additions and 134 deletions
|
@ -32,8 +32,8 @@ def exit_gracefully(signum, frame):
|
||||||
original_sigint = signal.getsignal(signal.SIGINT)
|
original_sigint = signal.getsignal(signal.SIGINT)
|
||||||
signal.signal(signal.SIGINT, exit_gracefully)
|
signal.signal(signal.SIGINT, exit_gracefully)
|
||||||
|
|
||||||
# create a bot thread and start it
|
# create a bot master and start it
|
||||||
cloudbot = bot.Bot()
|
cloudbot = bot.CloudBot()
|
||||||
cloudbot.start()
|
cloudbot.start()
|
||||||
|
|
||||||
# watch to see if the bot stops running or needs a restart
|
# watch to see if the bot stops running or needs a restart
|
||||||
|
|
51
core/bot.py
51
core/bot.py
|
@ -46,30 +46,30 @@ def get_logger():
|
||||||
return logger
|
return logger
|
||||||
|
|
||||||
|
|
||||||
class Bot(threading.Thread):
|
class CloudBot(threading.Thread):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
# basic variables
|
# basic variables
|
||||||
self.start_time = time.time()
|
self.start_time = time.time()
|
||||||
self.running = True
|
self.running = True
|
||||||
self.do_restart = False
|
self.do_restart = False
|
||||||
self.connections = []
|
|
||||||
|
# stores each instance of the
|
||||||
|
self.instances = []
|
||||||
|
|
||||||
# set up config and logging
|
# set up config and logging
|
||||||
self.setup()
|
self.setup()
|
||||||
self.logger.debug("Bot setup completed.")
|
self.logger.debug("Bot setup completed.")
|
||||||
|
|
||||||
# start IRC connections
|
# start bot instances
|
||||||
self.connect()
|
self.create()
|
||||||
print(self.connections)
|
|
||||||
|
|
||||||
for conn in self.connections:
|
for instance in self.instances:
|
||||||
conn.permissions = PermissionManager(self, conn)
|
instance.permissions = PermissionManager(self, instance)
|
||||||
print(conn)
|
|
||||||
|
|
||||||
# run plugin loader
|
# run plugin loader
|
||||||
self.plugins = collections.defaultdict(list)
|
self.plugins = collections.defaultdict(list)
|
||||||
|
|
||||||
""" plugins format
|
""" self.plugins format
|
||||||
{'PLUGIN_TYPE': [(<COMPILED_PLUGIN_FUNTION>,
|
{'PLUGIN_TYPE': [(<COMPILED_PLUGIN_FUNTION>,
|
||||||
{PLUGIN_ARGS}),
|
{PLUGIN_ARGS}),
|
||||||
(<COMPILED_PLUGIN_FUNTION>,
|
(<COMPILED_PLUGIN_FUNTION>,
|
||||||
|
@ -89,19 +89,19 @@ class Bot(threading.Thread):
|
||||||
"""recieves input from the IRC engine and processes it"""
|
"""recieves input from the IRC engine and processes it"""
|
||||||
self.logger.info("Starting main thread.")
|
self.logger.info("Starting main thread.")
|
||||||
while self.running:
|
while self.running:
|
||||||
for conn in self.connections:
|
for instance in self.instances:
|
||||||
try:
|
try:
|
||||||
incoming = conn.parsed_queue.get_nowait()
|
incoming = instance.parsed_queue.get_nowait()
|
||||||
if incoming == StopIteration:
|
if incoming == StopIteration:
|
||||||
print("StopIteration")
|
print("StopIteration")
|
||||||
# IRC engine has signalled timeout, so reconnect (ugly)
|
# IRC engine has signalled timeout, so reconnect (ugly)
|
||||||
conn.connection.reconnect()
|
instance.connection.reconnect()
|
||||||
main.main(self, conn, incoming)
|
main.main(self, instance, incoming)
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# if no messages are in the incoming queue, sleep
|
# if no messages are in the incoming queue, sleep
|
||||||
while self.running and all(c.parsed_queue.empty() for c in self.connections):
|
while self.running and all(i.parsed_queue.empty() for i in self.instances):
|
||||||
time.sleep(.1)
|
time.sleep(.1)
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
|
@ -126,26 +126,23 @@ class Bot(threading.Thread):
|
||||||
self.db_session = scoped_session(db_factory)
|
self.db_session = scoped_session(db_factory)
|
||||||
self.logger.debug("Database system initalised.")
|
self.logger.debug("Database system initalised.")
|
||||||
|
|
||||||
def connect(self):
|
def create(self):
|
||||||
"""connect to all the networks defined in the bot config"""
|
""" Create a BotInstance for all the networks defined in the config """
|
||||||
for conf in self.config['connections']:
|
for conf in self.config['instances']:
|
||||||
|
|
||||||
# strip all spaces and capitalization from the connection name
|
# strip all spaces and capitalization from the connection name
|
||||||
name = clean_name(conf['name'])
|
name = clean_name(conf['name'])
|
||||||
nick = conf['nick']
|
nick = conf['nick']
|
||||||
server = conf['connection']['server']
|
server = conf['connection']['server']
|
||||||
port = conf['connection'].get('port', 6667)
|
port = conf['connection'].get('port', 6667)
|
||||||
|
|
||||||
self.logger.debug("({}) Creating connection to {}.".format(name, server))
|
self.logger.debug("Creating BotInstance for {}.".format(name))
|
||||||
|
|
||||||
|
self.instances.append(irc.BotInstance(name, server, nick, config=conf,
|
||||||
|
port=port, logger=self.logger, channels=conf['channels'],
|
||||||
|
ssl=conf['connection'].get('ssl', False)))
|
||||||
|
self.logger.debug("({}) Created connection.".format(name))
|
||||||
|
|
||||||
if conf['connection'].get('ssl'):
|
|
||||||
self.connections.append(irc.SSLIRC(name, server, nick, config=conf,
|
|
||||||
port=port, logger=self.logger, channels=conf['channels'],
|
|
||||||
ignore_certificate_errors=conf['connection'].get('ignore_cert', True)))
|
|
||||||
self.logger.debug("({}) Created SSL connection.".format(name))
|
|
||||||
else:
|
|
||||||
self.connections.append(irc.IRC(name, server, nick, config=conf,
|
|
||||||
port=port, logger=self.logger, channels=conf['channels']))
|
|
||||||
self.logger.debug("({}) Created connection.".format(name))
|
|
||||||
|
|
||||||
def stop(self, reason=None):
|
def stop(self, reason=None):
|
||||||
"""quits all networks and shuts the bot down"""
|
"""quits all networks and shuts the bot down"""
|
||||||
|
|
|
@ -38,9 +38,9 @@ class Config(dict):
|
||||||
self.logger.info("Config loaded from file.")
|
self.logger.info("Config loaded from file.")
|
||||||
|
|
||||||
# reload permissions
|
# reload permissions
|
||||||
if self.bot.connections:
|
if self.bot.instances:
|
||||||
for conn in self.bot.connections:
|
for instance in self.bot.instances:
|
||||||
conn.permissions.reload()
|
instance.permissions.reload()
|
||||||
|
|
||||||
def save_config(self):
|
def save_config(self):
|
||||||
"""saves the contents of the config dict to the config file"""
|
"""saves the contents of the config dict to the config file"""
|
||||||
|
|
28
core/irc.py
28
core/irc.py
|
@ -202,13 +202,14 @@ class SSLIRCConnection(IRCConnection):
|
||||||
CERT_REQUIRED)
|
CERT_REQUIRED)
|
||||||
|
|
||||||
|
|
||||||
class IRC(object):
|
class BotInstance(object):
|
||||||
"""handles the IRC protocol"""
|
""" A BotInstance represents each connection the bot makes to an IRC server """
|
||||||
|
|
||||||
def __init__(self, name, server, nick, port=6667, logger=None, channels=[], config={}):
|
def __init__(self, name, server, nick, port=6667, ssl=False, logger=None, channels=[], config={}):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.channels = channels
|
self.channels = channels
|
||||||
self.config = config
|
self.config = config
|
||||||
|
self.ssl = ssl
|
||||||
self.server = server
|
self.server = server
|
||||||
self.port = port
|
self.port = port
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
|
@ -240,8 +241,12 @@ class IRC(object):
|
||||||
self.parse_thread.start()
|
self.parse_thread.start()
|
||||||
|
|
||||||
def create_connection(self):
|
def create_connection(self):
|
||||||
return IRCConnection(self.name, self.server, self.port,
|
if self.ssl:
|
||||||
self.input_queue, self.output_queue)
|
return SSLIRCConnection(self.name, self.server, self.port, self.input_queue,
|
||||||
|
self.output_queue, True)
|
||||||
|
else:
|
||||||
|
return IRCConnection(self.name, self.server, self.port,
|
||||||
|
self.input_queue, self.output_queue)
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self.connection.stop()
|
self.connection.stop()
|
||||||
|
@ -287,15 +292,4 @@ class IRC(object):
|
||||||
except:
|
except:
|
||||||
# if this doesn't work, no big deal
|
# if this doesn't work, no big deal
|
||||||
pass
|
pass
|
||||||
self.output_queue.put(string)
|
self.output_queue.put(string)
|
||||||
|
|
||||||
|
|
||||||
class SSLIRC(IRC):
|
|
||||||
def __init__(self, name, server, nick, port=6667, logger=None, channels=[], config={},
|
|
||||||
ignore_certificate_errors=True):
|
|
||||||
self.ignore_cert_errors = ignore_certificate_errors
|
|
||||||
IRC.__init__(self, name, server, nick, port, logger, channels, config)
|
|
||||||
|
|
||||||
def create_connection(self):
|
|
||||||
return SSLIRCConnection(self.name, self.server, self.port, self.input_queue,
|
|
||||||
self.output_queue, self.ignore_cert_errors)
|
|
|
@ -39,7 +39,6 @@ class PluginLoader(object):
|
||||||
self.observer.start()
|
self.observer.start()
|
||||||
|
|
||||||
self.load_all()
|
self.load_all()
|
||||||
pprint(bot.plugins)
|
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
"""shuts down the plugin reloader"""
|
"""shuts down the plugin reloader"""
|
||||||
|
@ -88,7 +87,6 @@ class PluginLoader(object):
|
||||||
if hasattr(obj, '_hook'): # check for magic
|
if hasattr(obj, '_hook'): # check for magic
|
||||||
if obj._thread:
|
if obj._thread:
|
||||||
self.bot.threads[obj] = main.Handler(self.bot, obj)
|
self.bot.threads[obj] = main.Handler(self.bot, obj)
|
||||||
|
|
||||||
for plug_type, data in obj._hook:
|
for plug_type, data in obj._hook:
|
||||||
# add plugin to the plugin list
|
# add plugin to the plugin list
|
||||||
self.bot.plugins[plug_type] += [data]
|
self.bot.plugins[plug_type] += [data]
|
||||||
|
|
77
core/main.py
77
core/main.py
|
@ -8,6 +8,7 @@ from sqlalchemy.orm import scoped_session
|
||||||
_thread.stack_size(1024 * 512) # reduce vm size
|
_thread.stack_size(1024 * 512) # reduce vm size
|
||||||
|
|
||||||
|
|
||||||
|
#TODO: redesign this messy thing
|
||||||
class Input(dict):
|
class Input(dict):
|
||||||
def __init__(self, bot, conn, raw, prefix, command, params,
|
def __init__(self, bot, conn, raw, prefix, command, params,
|
||||||
nick, user, host, mask, paraml, msg):
|
nick, user, host, mask, paraml, msg):
|
||||||
|
@ -54,47 +55,27 @@ class Input(dict):
|
||||||
|
|
||||||
|
|
||||||
def run(bot, func, input):
|
def run(bot, func, input):
|
||||||
args = func._args
|
uses_db = True
|
||||||
|
# TODO: change to bot.get_db_session()
|
||||||
|
print(input)
|
||||||
|
if 'text' not in input:
|
||||||
|
input.text = input.paraml
|
||||||
|
|
||||||
uses_db = 'db' in args and 'db' not in input
|
if uses_db:
|
||||||
|
# create SQLAlchemy session
|
||||||
|
bot.logger.debug("Opened DB session for: {}".format(func._filename))
|
||||||
|
input.db = input.bot.db_session()
|
||||||
|
|
||||||
if 'inp' not in input:
|
try:
|
||||||
input.inp = input.paraml
|
out = func(input, input.conn)
|
||||||
|
except:
|
||||||
if args:
|
bot.logger.exception("Error in plugin {}:".format(func._filename))
|
||||||
|
return
|
||||||
|
finally:
|
||||||
if uses_db:
|
if uses_db:
|
||||||
# create SQLAlchemy session
|
bot.logger.debug("Closed DB session for: {}".format(func._filename))
|
||||||
bot.logger.debug("Opened DB session for: {}".format(func._filename))
|
input.db.close()
|
||||||
input.db = input.bot.db_session()
|
|
||||||
if 'input' in args:
|
|
||||||
input.input = input
|
|
||||||
if 0 in args:
|
|
||||||
try:
|
|
||||||
out = func(input.inp, **input)
|
|
||||||
except:
|
|
||||||
bot.logger.exception("Error in plugin {}:".format(func._filename))
|
|
||||||
return
|
|
||||||
finally:
|
|
||||||
if uses_db:
|
|
||||||
print("Close")
|
|
||||||
input.db.close()
|
|
||||||
else:
|
|
||||||
kw = dict((key, input[key]) for key in args if key in input)
|
|
||||||
try:
|
|
||||||
out = func(input.inp, **kw)
|
|
||||||
except:
|
|
||||||
bot.logger.exception("Error in plugin {}:".format(func._filename))
|
|
||||||
return
|
|
||||||
finally:
|
|
||||||
if uses_db:
|
|
||||||
bot.logger.debug("Closed DB session for: {}".format(func._filename))
|
|
||||||
input.db.close()
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
out = func(input.inp)
|
|
||||||
except:
|
|
||||||
bot.logger.exception("Error in plugin {}:".format(func._filename))
|
|
||||||
return
|
|
||||||
if out is not None:
|
if out is not None:
|
||||||
input.reply(str(out))
|
input.reply(str(out))
|
||||||
|
|
||||||
|
@ -117,25 +98,15 @@ class Handler(object):
|
||||||
_thread.start_new_thread(self.start, ())
|
_thread.start_new_thread(self.start, ())
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
uses_db = 'db' in self.func._args
|
uses_db = True
|
||||||
while True:
|
while True:
|
||||||
input = self.input_queue.get()
|
input = self.input_queue.get()
|
||||||
|
|
||||||
if input == StopIteration:
|
if input == StopIteration:
|
||||||
break
|
break
|
||||||
|
|
||||||
if uses_db:
|
run(self.bot, self.func, input)
|
||||||
# self.bot.logger.debug("Opened ST DB session for: {}".format(self.func._filename))
|
|
||||||
input.db = input.bot.db_session()
|
|
||||||
|
|
||||||
try:
|
|
||||||
run(self.bot, self.func, input)
|
|
||||||
except:
|
|
||||||
self.bot.logger.exception("Error in plugin {}:".format(self.func._filename))
|
|
||||||
finally:
|
|
||||||
if uses_db:
|
|
||||||
# self.bot.logger.debug("Closed ST DB session for: {}".format(self.func._filename))
|
|
||||||
input.db.close()
|
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self.input_queue.put(StopIteration)
|
self.input_queue.put(StopIteration)
|
||||||
|
@ -203,8 +174,8 @@ def main(bot, conn, out):
|
||||||
elif command in bot.commands:
|
elif command in bot.commands:
|
||||||
input = Input(bot, conn, *out)
|
input = Input(bot, conn, *out)
|
||||||
input.trigger = trigger
|
input.trigger = trigger
|
||||||
input.inp_unstripped = m.group(2)
|
input.text_unstripped = m.group(2)
|
||||||
input.inp = input.inp_unstripped.strip()
|
input.text = input.text_unstripped.strip()
|
||||||
|
|
||||||
func, args = bot.commands[command]
|
func, args = bot.commands[command]
|
||||||
dispatch(bot, input, "command", func, args, autohelp=True)
|
dispatch(bot, input, "command", func, args, autohelp=True)
|
||||||
|
@ -214,6 +185,6 @@ def main(bot, conn, out):
|
||||||
m = args['re'].search(inp.lastparam)
|
m = args['re'].search(inp.lastparam)
|
||||||
if m:
|
if m:
|
||||||
input = Input(bot, conn, *out)
|
input = Input(bot, conn, *out)
|
||||||
input.inp = m
|
input.text = m
|
||||||
|
|
||||||
dispatch(bot, input, "regex", func, args)
|
dispatch(bot, input, "regex", func, args)
|
||||||
|
|
|
@ -14,10 +14,10 @@ with open("./data/8ball_responses.txt") as f:
|
||||||
f.readlines() if not line.startswith("//")]
|
f.readlines() if not line.startswith("//")]
|
||||||
|
|
||||||
|
|
||||||
@hook.command('8ball')
|
@hook.command()
|
||||||
def eightball(inp, action=None):
|
def eightball(input, conn):
|
||||||
"""8ball <question> -- The all knowing magic eight ball,
|
"""8ball <question> -- The all knowing magic eight ball,
|
||||||
in electronic form. Ask and it shall be answered!"""
|
in electronic form. Ask and it shall be answered!"""
|
||||||
|
|
||||||
magic = text.multiword_replace(random.choice(responses), color_codes)
|
magic = text.multiword_replace(random.choice(responses), color_codes)
|
||||||
action("shakes the magic 8 ball... {}".format(magic))
|
input.action("shakes the magic 8 ball... {}".format(magic))
|
||||||
|
|
|
@ -14,12 +14,12 @@ def get_generator(_json):
|
||||||
|
|
||||||
|
|
||||||
@hook.command(autohelp=False)
|
@hook.command(autohelp=False)
|
||||||
def namegen(inp, notice=None):
|
def namegen(input, instance, bot):
|
||||||
"""namegen [generator] -- Generates some names using the chosen generator.
|
"""namegen [generator] -- Generates some names using the chosen generator.
|
||||||
'namegen list' will display a list of all generators."""
|
'namegen list' will display a list of all generators."""
|
||||||
|
|
||||||
# clean up the input
|
# clean up the input
|
||||||
inp = inp.strip().lower()
|
inp = input.text.strip().lower()
|
||||||
|
|
||||||
# get a list of available name generators
|
# get a list of available name generators
|
||||||
files = os.listdir(GEN_DIR)
|
files = os.listdir(GEN_DIR)
|
||||||
|
@ -33,7 +33,7 @@ def namegen(inp, notice=None):
|
||||||
if inp == "list":
|
if inp == "list":
|
||||||
message = "Available generators: "
|
message = "Available generators: "
|
||||||
message += text.get_text_list(all_modules, 'and')
|
message += text.get_text_list(all_modules, 'and')
|
||||||
notice(message)
|
input.notice(message)
|
||||||
return
|
return
|
||||||
|
|
||||||
if inp:
|
if inp:
|
||||||
|
|
24
util/hook.py
24
util/hook.py
|
@ -10,30 +10,6 @@ def _hook_add(func, add, name=''):
|
||||||
if not hasattr(func, '_filename'):
|
if not hasattr(func, '_filename'):
|
||||||
func._filename = func.__code__.co_filename
|
func._filename = func.__code__.co_filename
|
||||||
|
|
||||||
if not hasattr(func, '_args'):
|
|
||||||
argspec = inspect.getargspec(func)
|
|
||||||
if name:
|
|
||||||
n_args = len(argspec.args)
|
|
||||||
if argspec.defaults:
|
|
||||||
n_args -= len(argspec.defaults)
|
|
||||||
if argspec.keywords:
|
|
||||||
n_args -= 1
|
|
||||||
if argspec.varargs:
|
|
||||||
n_args -= 1
|
|
||||||
if n_args != 1:
|
|
||||||
err = '%ss must take 1 non-keyword argument (%s)' % (name,
|
|
||||||
func.__name__)
|
|
||||||
raise ValueError(err)
|
|
||||||
|
|
||||||
args = []
|
|
||||||
if argspec.defaults:
|
|
||||||
end = bool(argspec.keywords) + bool(argspec.varargs)
|
|
||||||
args.extend(argspec.args[-len(argspec.defaults):
|
|
||||||
end if end else None])
|
|
||||||
if argspec.keywords:
|
|
||||||
args.append(0) # means kwargs present
|
|
||||||
func._args = args
|
|
||||||
|
|
||||||
if not hasattr(func, '_thread'): # does function run in its own thread?
|
if not hasattr(func, '_thread'): # does function run in its own thread?
|
||||||
func._thread = False
|
func._thread = False
|
||||||
|
|
||||||
|
|
Reference in a new issue