2012-08-14 03:51:55 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
# Nemubot is a modulable IRC bot, built around XML configuration files.
|
|
|
|
# Copyright (C) 2012 Mercier Pierre-Olivier
|
|
|
|
#
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU Affero General Public License as published by
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU Affero General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
2014-08-29 09:53:32 +00:00
|
|
|
from distutils.version import LooseVersion
|
2012-08-14 03:51:55 +00:00
|
|
|
from importlib.abc import Finder
|
2014-12-29 06:23:11 +00:00
|
|
|
from importlib.machinery import SourceFileLoader
|
2012-08-14 03:51:55 +00:00
|
|
|
import imp
|
2014-08-14 10:49:38 +00:00
|
|
|
import logging
|
2012-08-14 03:51:55 +00:00
|
|
|
import os
|
|
|
|
import sys
|
2012-08-22 19:05:33 +00:00
|
|
|
|
2014-08-29 09:53:32 +00:00
|
|
|
from bot import __version__
|
2012-09-01 09:23:41 +00:00
|
|
|
import event
|
2014-04-30 20:19:32 +00:00
|
|
|
import exception
|
2014-08-12 18:08:55 +00:00
|
|
|
import hooks
|
2014-10-05 16:19:20 +00:00
|
|
|
from message import TextMessage
|
2014-12-06 08:00:53 +00:00
|
|
|
from tools.xmlparser import parse_file, module_state
|
2012-08-14 03:51:55 +00:00
|
|
|
|
2014-08-27 05:57:00 +00:00
|
|
|
logger = logging.getLogger("nemubot.importer")
|
2014-08-14 10:49:38 +00:00
|
|
|
|
2012-08-14 03:51:55 +00:00
|
|
|
class ModuleFinder(Finder):
|
|
|
|
def __init__(self, context, prompt):
|
|
|
|
self.context = context
|
|
|
|
self.prompt = prompt
|
|
|
|
|
|
|
|
def find_module(self, fullname, path=None):
|
2014-12-15 20:36:59 +00:00
|
|
|
# print ("looking for", fullname, "in", path)
|
2012-08-14 03:51:55 +00:00
|
|
|
# Search only for new nemubot modules (packages init)
|
|
|
|
if path is None:
|
2014-08-29 09:53:32 +00:00
|
|
|
for mpath in self.context.modules_paths:
|
2014-12-15 20:36:59 +00:00
|
|
|
# print ("looking for", fullname, "in", mpath)
|
2014-12-29 06:23:11 +00:00
|
|
|
if os.path.isfile(os.path.join(mpath, fullname + ".py")):
|
|
|
|
return ModuleLoader(self.context, self.prompt, fullname,
|
|
|
|
os.path.join(mpath, fullname + ".py"))
|
|
|
|
elif os.path.isfile(os.path.join(os.path.join(mpath, fullname), "__init__.py")):
|
|
|
|
return ModuleLoader(self.context, self.prompt, fullname,
|
|
|
|
os.path.join(
|
|
|
|
os.path.join(mpath, fullname),
|
|
|
|
"__init__.py"))
|
2014-12-15 20:36:59 +00:00
|
|
|
# print ("not found")
|
2012-08-14 03:51:55 +00:00
|
|
|
return None
|
|
|
|
|
|
|
|
|
2014-12-29 06:23:11 +00:00
|
|
|
class ModuleLoader(SourceFileLoader):
|
|
|
|
|
2014-08-28 10:26:02 +00:00
|
|
|
def __init__(self, context, prompt, fullname, path):
|
2012-08-14 03:51:55 +00:00
|
|
|
self.context = context
|
|
|
|
self.prompt = prompt
|
|
|
|
|
2014-12-29 06:23:11 +00:00
|
|
|
if fullname in self.context.modules_configuration:
|
|
|
|
self.config = self.context.modules_configuration[fullname]
|
2012-08-16 03:23:45 +00:00
|
|
|
else:
|
|
|
|
self.config = None
|
2012-08-14 03:51:55 +00:00
|
|
|
|
2014-12-29 06:23:11 +00:00
|
|
|
SourceFileLoader.__init__(self, fullname, path)
|
|
|
|
|
2012-08-14 03:51:55 +00:00
|
|
|
|
|
|
|
def load_module(self, fullname):
|
2014-12-29 06:23:11 +00:00
|
|
|
module = SourceFileLoader.load_module(self, fullname)
|
2012-08-14 03:51:55 +00:00
|
|
|
|
|
|
|
# Check that is a valid nemubot module
|
|
|
|
if not hasattr(module, "nemubotversion"):
|
2014-12-29 06:23:11 +00:00
|
|
|
raise ImportError("Module `%s' is not a nemubot module." %
|
|
|
|
fullname)
|
2012-08-14 03:51:55 +00:00
|
|
|
# Check module version
|
2014-08-29 09:53:32 +00:00
|
|
|
if LooseVersion(__version__) < LooseVersion(str(module.nemubotversion)):
|
2012-08-14 03:51:55 +00:00
|
|
|
raise ImportError("Module `%s' is not compatible with this "
|
2014-12-29 06:23:11 +00:00
|
|
|
"version." % fullname)
|
2012-08-14 03:51:55 +00:00
|
|
|
|
2014-08-29 09:53:32 +00:00
|
|
|
# Set module common functions and data
|
2012-08-14 03:51:55 +00:00
|
|
|
module.__LOADED__ = True
|
2014-08-27 05:57:00 +00:00
|
|
|
module.logger = logging.getLogger("nemubot.module." + fullname)
|
2012-08-14 03:51:55 +00:00
|
|
|
|
2014-08-12 15:45:38 +00:00
|
|
|
def prnt(*args):
|
2014-10-05 16:19:20 +00:00
|
|
|
print("[%s]" % module.__name__, *args)
|
2014-08-14 10:49:38 +00:00
|
|
|
module.logger.info(*args)
|
2014-08-12 15:45:38 +00:00
|
|
|
def prnt_dbg(*args):
|
|
|
|
if module.DEBUG:
|
2014-10-05 16:19:20 +00:00
|
|
|
print("{%s}" % module.__name__, *args)
|
2014-08-14 10:49:38 +00:00
|
|
|
module.logger.debug(*args)
|
2014-08-12 15:45:38 +00:00
|
|
|
|
|
|
|
def mod_save():
|
2014-12-15 20:36:59 +00:00
|
|
|
fpath = os.path.join(self.context.data_path, module.__name__ + ".xml")
|
2014-08-14 10:49:38 +00:00
|
|
|
module.print_debug("Saving DATAS to " + fpath)
|
|
|
|
module.DATAS.save(fpath)
|
2014-08-12 15:45:38 +00:00
|
|
|
|
|
|
|
def send_response(server, res):
|
|
|
|
if server in self.context.servers:
|
2014-10-05 16:19:20 +00:00
|
|
|
r = res.next_response()
|
|
|
|
if r.server is not None:
|
|
|
|
return self.context.servers[r.server].send_response(r)
|
|
|
|
else:
|
|
|
|
return self.context.servers[server].send_response(r)
|
2014-08-12 15:45:38 +00:00
|
|
|
else:
|
2014-08-27 05:57:00 +00:00
|
|
|
module.logger.error("Try to send a message to the unknown server: %s", server)
|
2014-08-12 15:45:38 +00:00
|
|
|
return False
|
|
|
|
|
|
|
|
def add_hook(store, hook):
|
2014-09-01 17:21:54 +00:00
|
|
|
store = convert_legacy_store(store)
|
|
|
|
module.REGISTERED_HOOKS.append((store, hook))
|
|
|
|
return self.context.hooks.add_hook(hook, store)
|
2014-08-12 15:45:38 +00:00
|
|
|
def del_hook(store, hook):
|
2014-09-01 17:21:54 +00:00
|
|
|
store = convert_legacy_store(store)
|
|
|
|
module.REGISTERED_HOOKS.remove((store, hook))
|
|
|
|
return self.context.hooks.del_hook(hook, store)
|
2014-08-12 15:45:38 +00:00
|
|
|
def add_event(evt, eid=None):
|
|
|
|
return self.context.add_event(evt, eid, module_src=module)
|
|
|
|
def add_event_eid(evt, eid):
|
|
|
|
return add_event(evt, eid)
|
|
|
|
def del_event(evt):
|
|
|
|
return self.context.del_event(evt, module_src=module)
|
|
|
|
|
2012-08-14 03:51:55 +00:00
|
|
|
# Set module common functions and datas
|
2012-09-01 09:23:41 +00:00
|
|
|
module.REGISTERED_HOOKS = list()
|
2012-11-04 03:28:24 +00:00
|
|
|
module.REGISTERED_EVENTS = list()
|
2014-12-15 20:43:40 +00:00
|
|
|
module.DEBUG = self.context.verbosity > 0
|
2014-08-12 15:45:38 +00:00
|
|
|
module.print = prnt
|
|
|
|
module.print_debug = prnt_dbg
|
|
|
|
module.send_response = send_response
|
|
|
|
module.add_hook = add_hook
|
|
|
|
module.del_hook = del_hook
|
|
|
|
module.add_event = add_event
|
|
|
|
module.add_event_eid = add_event_eid
|
|
|
|
module.del_event = del_event
|
2012-08-14 03:51:55 +00:00
|
|
|
|
|
|
|
if not hasattr(module, "NODATA"):
|
2014-12-15 23:56:47 +00:00
|
|
|
data_file = os.path.join(self.context.data_path,
|
|
|
|
module.__name__ + ".xml")
|
|
|
|
if os.path.isfile(data_file):
|
|
|
|
module.DATAS = parse_file(data_file)
|
|
|
|
else:
|
|
|
|
module.DATAS = module_state.ModuleState("nemubotstate")
|
2014-08-12 15:45:38 +00:00
|
|
|
module.save = mod_save
|
2012-08-14 03:51:55 +00:00
|
|
|
else:
|
|
|
|
module.DATAS = None
|
|
|
|
module.save = lambda: False
|
|
|
|
module.CONF = self.config
|
|
|
|
|
2012-09-01 09:23:41 +00:00
|
|
|
module.ModuleEvent = event.ModuleEvent
|
2014-12-06 08:00:53 +00:00
|
|
|
module.ModuleState = module_state.ModuleState
|
2014-04-30 20:19:32 +00:00
|
|
|
module.IRCException = exception.IRCException
|
2012-08-30 15:43:24 +00:00
|
|
|
|
2012-08-14 03:51:55 +00:00
|
|
|
# Load dependancies
|
|
|
|
if module.CONF is not None and module.CONF.hasNode("dependson"):
|
|
|
|
module.MODS = dict()
|
|
|
|
for depend in module.CONF.getNodes("dependson"):
|
|
|
|
for md in MODS:
|
|
|
|
if md.name == depend["name"]:
|
|
|
|
mod.MODS[md.name] = md
|
|
|
|
break
|
|
|
|
if depend["name"] not in module.MODS:
|
2014-08-14 10:49:38 +00:00
|
|
|
logger.error("In module `%s', module `%s' require by this "
|
2014-10-05 16:19:20 +00:00
|
|
|
"module but is not loaded.", module.__name__,
|
2014-08-27 05:57:00 +00:00
|
|
|
depend["name"])
|
2012-08-14 03:51:55 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
# Add the module to the global modules list
|
|
|
|
if self.context.add_module(module):
|
|
|
|
|
|
|
|
# Launch the module
|
|
|
|
if hasattr(module, "load"):
|
2012-08-14 04:53:52 +00:00
|
|
|
module.load(self.context)
|
2012-08-14 03:51:55 +00:00
|
|
|
|
|
|
|
# Register hooks
|
|
|
|
register_hooks(module, self.context, self.prompt)
|
|
|
|
|
2014-10-05 16:19:20 +00:00
|
|
|
logger.info("Module '%s' successfully loaded.", module.__name__)
|
2012-08-14 03:51:55 +00:00
|
|
|
else:
|
2014-10-05 16:19:20 +00:00
|
|
|
logger.error("An error occurs while importing `%s'.", module.__name__)
|
2012-08-14 03:51:55 +00:00
|
|
|
raise ImportError("An error occurs while importing `%s'."
|
2014-10-05 16:19:20 +00:00
|
|
|
% module.__name__)
|
2012-08-14 03:51:55 +00:00
|
|
|
return module
|
|
|
|
|
|
|
|
|
2014-09-01 17:21:54 +00:00
|
|
|
def convert_legacy_store(old):
|
|
|
|
if old == "cmd_hook" or old == "cmd_rgxp" or old == "cmd_default":
|
2014-10-05 16:19:20 +00:00
|
|
|
return "in_Command"
|
2014-09-01 17:21:54 +00:00
|
|
|
elif old == "ask_hook" or old == "ask_rgxp" or old == "ask_default":
|
2014-10-05 16:19:20 +00:00
|
|
|
return "in_DirectAsk"
|
2014-09-01 17:21:54 +00:00
|
|
|
elif old == "msg_hook" or old == "msg_rgxp" or old == "msg_default":
|
2014-10-05 16:19:20 +00:00
|
|
|
return "in_TextMessage"
|
2014-09-01 17:21:54 +00:00
|
|
|
elif old == "all_post":
|
|
|
|
return "post"
|
|
|
|
elif old == "all_pre":
|
|
|
|
return "pre"
|
|
|
|
else:
|
|
|
|
print("UNKNOWN store:", old)
|
|
|
|
return old
|
|
|
|
|
2012-08-14 03:51:55 +00:00
|
|
|
|
|
|
|
def register_hooks(module, context, prompt):
|
2014-11-13 20:46:35 +00:00
|
|
|
"""Register all available hooks
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
module -- the loaded Python module
|
|
|
|
context -- bot context
|
|
|
|
prompt -- the current Prompt instance
|
|
|
|
"""
|
|
|
|
|
2014-08-12 18:08:55 +00:00
|
|
|
# Register decorated functions
|
|
|
|
for s, h in hooks.last_registered:
|
2014-08-28 10:26:02 +00:00
|
|
|
if s == "prompt_cmd":
|
|
|
|
prompt.add_cap_hook(h.name, h.call)
|
2014-11-17 21:48:17 +00:00
|
|
|
elif s == "prompt_list":
|
|
|
|
prompt.add_list_hook(h.name, h.call)
|
2014-08-28 10:26:02 +00:00
|
|
|
else:
|
2014-09-01 17:21:54 +00:00
|
|
|
s = convert_legacy_store(s)
|
|
|
|
module.REGISTERED_HOOKS.append((s, h))
|
|
|
|
context.hooks.add_hook(h, s)
|
2014-08-12 18:08:55 +00:00
|
|
|
hooks.last_registered = []
|