/***********************************************************************
X3 ChangeLog
+2009-01-24 Alex Schumann <rubin@afternet.org>
+
+ * src/mod-python.c: added plugin hook to log to x3 logs, and worked
+ out the basics of callback registration.
+
+ * src/modpython.py: x3 logging hook and new callback registration
+
+ * Makefile.am: install modpython.py and plugins/ during make install
+
+ * install-r.sh: shell script to do recursive copy with exclusions
+
2009-01-23 Alex Schumann <rubin@afternet.org>
* src/mod-python.c: refactor to use class-based hook manager. Seems to
$(INSTALL) -m 644 $(srcdir)/src/*.help $(prefix)
$(INSTALL) -m 600 $(srcdir)/x3.conf.example $(prefix)
$(INSTALL) -m 644 $(srcdir)/sockcheck.conf.example $(prefix)
+ ./install-r.sh $(srcdir)/src/plugins $(prefix)
@echo
@echo X3-$(VERSION) has been installed to $(prefix)
@echo Remember to edit x3.conf.example and sockcheck.conf.example
CC = @CC@
CCDEPMODE = @CCDEPMODE@
CFLAGS = @CFLAGS@
+CP = @CP@
CPP = @CPP@
CPPFLAGS = @CPPFLAGS@
CYGPATH_W = @CYGPATH_W@
$(INSTALL) -m 644 $(srcdir)/src/*.help $(prefix)
$(INSTALL) -m 600 $(srcdir)/x3.conf.example $(prefix)
$(INSTALL) -m 644 $(srcdir)/sockcheck.conf.example $(prefix)
+ ./install-r.sh $(srcdir)/src/plugins $(prefix)
@echo
@echo X3-$(VERSION) has been installed to $(prefix)
@echo Remember to edit x3.conf.example and sockcheck.conf.example
#! /bin/sh
-# From configure.in Id: configure.in 2384 2008-12-25 06:49:44Z sirvulcan .
+# From configure.in Id: configure.in 2427 2009-01-23 23:27:32Z rubin .
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.61 for X3 1.6.
#
CCDEPMODE
am__fastdepCC_TRUE
am__fastdepCC_FALSE
+CP
RANLIB
LN_S
CPP
test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644'
+# Extract the first word of "cp", so it can be a program name with args.
+set dummy cp; ac_word=$2
+{ echo "$as_me:$LINENO: checking for $ac_word" >&5
+echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6; }
+if test "${ac_cv_path_CP+set}" = set; then
+ echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+ case $CP in
+ [\\/]* | ?:[\\/]*)
+ ac_cv_path_CP="$CP" # Let the user override the test with a path.
+ ;;
+ *)
+ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then
+ ac_cv_path_CP="$as_dir/$ac_word$ac_exec_ext"
+ echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+done
+IFS=$as_save_IFS
+
+ ;;
+esac
+fi
+CP=$ac_cv_path_CP
+if test -n "$CP"; then
+ { echo "$as_me:$LINENO: result: $CP" >&5
+echo "${ECHO_T}$CP" >&6; }
+else
+ { echo "$as_me:$LINENO: result: no" >&5
+echo "${ECHO_T}no" >&6; }
+fi
+
+
+
if test -n "$ac_tool_prefix"; then
# Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args.
set dummy ${ac_tool_prefix}ranlib; ac_word=$2
CCDEPMODE!$CCDEPMODE$ac_delim
am__fastdepCC_TRUE!$am__fastdepCC_TRUE$ac_delim
am__fastdepCC_FALSE!$am__fastdepCC_FALSE$ac_delim
+CP!$CP$ac_delim
RANLIB!$RANLIB$ac_delim
LN_S!$LN_S$ac_delim
CPP!$CPP$ac_delim
GREP!$GREP$ac_delim
EGREP!$EGREP$ac_delim
MAKER!$MAKER$ac_delim
-ALLOCA!$ALLOCA$ac_delim
_ACEOF
if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 97; then
ac_delim='%!_!# '
for ac_last_try in false false false false false :; do
cat >conf$$subs.sed <<_ACEOF
+ALLOCA!$ALLOCA$ac_delim
pythonpath!$pythonpath$ac_delim
DO_PYTHON_TRUE!$DO_PYTHON_TRUE$ac_delim
DO_PYTHON_FALSE!$DO_PYTHON_FALSE$ac_delim
LTLIBOBJS!$LTLIBOBJS$ac_delim
_ACEOF
- if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 12; then
+ if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 13; then
break
elif $ac_last_try; then
{ { echo "$as_me:$LINENO: error: could not make $CONFIG_STATUS" >&5
AC_PROG_CC
AC_PROG_INSTALL
+AC_PATH_PROG(CP, cp)
+
AC_PROG_RANLIB
dnl AC_PROG_LIBTOOL
--- /dev/null
+#! /bin/sh
+
+# This is a hacky solution to the problem of INSTALL not knowing how to recursively copy files
+# and our need to recursively copy with exceptions the plugin tree. It works on linux,
+# but needs serious work to be more portable. Please Help. -Rubin
+SRC=$1
+DST=$2
+
+# TODO: find banaries like 'find' and 'cp' in common locations and/or path, and
+# use them there instead of assuming they are in path.
+
+if [ "_$DST" = _ ]; then
+ exit
+fi
+
+cd `dirname "$SRC"`
+SRCDIR=`basename "$SRC"`
+find "$SRCDIR" \! -path '*/.*' | \
+ while read f; do \
+ d="$DST/${f#foo/}"; \
+ mkdir -p "$(dirname "$d")"; \
+ if [ -f $f ]; then cp -v $f $d; fi; \
+ done
+# this will break if SRCDIR has hidden directories in it :/
+#find "$SRCDIR" \! -path '*/.*'
+
CC = @CC@
CCDEPMODE = @CCDEPMODE@
CFLAGS = @CFLAGS@
+CP = @CP@
CPP = @CPP@
CPPFLAGS = @CPPFLAGS@
CYGPATH_W = @CYGPATH_W@
CC = @CC@
CCDEPMODE = @CCDEPMODE@
CFLAGS = @CFLAGS@
+CP = @CP@
CPP = @CPP@
CPPFLAGS = @CPPFLAGS@
CYGPATH_W = @CYGPATH_W@
);
}
+static PyObject*
+emb_log_module(PyObject *self, PyObject *args)
+{
+ /* a gateway to standard X3 logging subsystem.
+ * level is a value 0 to 9 as defined by the log_severity enum in log.h.
+ * LOG_INFO is 3, LOG_WARNING is 6, LOG_ERROR is 7.
+ *
+ * for now, all logs go to the PY_LOG log. In the future this will change.
+ */
+ char *message;
+ int ret = 0;
+ int level;
+
+ if(!PyArg_ParseTuple(args, "is", &level, &message))
+ return NULL;
+
+ log_module(PY_LOG, level, "%s", message);
+
+ return Py_BuildValue("i", ret);
+}
static PyMethodDef EmbMethods[] = {
+ /* Communication methods */
{"dump", emb_dump, METH_VARARGS, "Dump raw P10 line to server"},
{"send_target_privmsg", emb_send_target_privmsg, METH_VARARGS, "Send a message to somewhere"},
{"send_target_notice", emb_send_target_notice, METH_VARARGS, "Send a notice to somewhere"},
+ {"log_module", emb_log_module, METH_VARARGS, "Log something using the X3 log subsystem"},
+ /* Information gathering methods */
{"get_user", emb_get_user, METH_VARARGS, "Get details about a nickname"},
{"get_channel", emb_get_channel, METH_VARARGS, "Get details about a channel"},
{"get_account", emb_get_account, METH_VARARGS, "Get details about an account"},
+ /* null terminator */
{NULL, NULL, 0, NULL}
};
methods.
*/
+void python_log_module() {
+ /* Attempt to convert python errors to x3 log system */
+ PyObject *exc, *tb, *value, *tmp;
+ char *str_exc = "NONE";
+ char *str_value = "NONE";
+ char *str_tb = "NONE";
+
+ PyErr_Fetch(&exc, &value, &tb);
+
+ if(exc) {
+ if((tmp = PyObject_Str(exc)))
+ str_exc = PyString_AsString(tmp);
+ }
+ if(value) {
+ if((tmp = PyObject_Str(value)))
+ str_value = PyString_AsString(tmp);
+ }
+ if(tb) {
+ if((tmp = PyObject_Str(tb)))
+ str_tb = PyString_AsString(tmp);
+ }
+
+ /* Now restore it so we can print it (just in case)
+ * (should we do this only when running in debug mode?) */
+ PyErr_Restore(exc, value, tb);
+ PyErr_Print(); /* which of course, clears it again.. */
+
+ log_module(PY_LOG, LOG_WARNING, "PYTHON error: %s, value: %s", str_exc, str_value);
+
+ /* TODO: get the traceback using the traceback module via C api so we can add it to the X3 logs. See
+ * http://mail.python.org/pipermail/python-list/2003-March/192226.html */
+ // (this doesnt work, str_tb is just an object hashid) log_module(PY_LOG, LOG_INFO, "PYTHON error, traceback: %s", str_tb);
+}
+
+
PyObject *python_build_handler_args(size_t argc, char *args[], PyObject *pIrcObj) {
/* Sets up a python tuple with passed in arguments, prefixed by the Irc instance
which handlers use to interact with c.
pIrcObj = PyObject_CallObject(pIrcClass, pIrcArgs);
if(!pIrcObj) {
log_module(PY_LOG, LOG_ERROR, "IRC Class failed to load");
- PyErr_Print();
+ python_log_module();
+ //PyErr_Print();
}
if(pIrcArgs != NULL) {
Py_DECREF(pIrcArgs);
}
}
-
int python_call_handler(char *handler, char *args[], size_t argc, char *command_service, char *command_caller, char *command_target) {
/* This is how we talk to modpython.c. First a new instance of the irc class is created using these
arguments to setup the current environment. Then the named method of the handler object is called
PyObject *pValue;
log_module(PY_LOG, LOG_INFO, "attempting to call handler %s.", handler);
- if(base_module != NULL) {
+ if(base_module != NULL && handler_object != NULL) {
pIrcObj = new_irc_object(command_service, command_caller, command_target);
if(!pIrcObj) {
log_module(PY_LOG, LOG_INFO, "Can't get irc object. Bailing.");
pArgs = python_build_handler_args(argc, args, pIrcObj);
pMethod = PyObject_GetAttrString(handler_object, handler);
if(pMethod && PyCallable_Check(pMethod)) {
+ /* Call the method, with the arguments */
pValue = PyObject_CallObject(pMethod, pArgs);
if(pArgs) {
Py_DECREF(pArgs);
int ret;
ret = PyInt_AsLong(pValue);
if(ret == -1 && PyErr_Occurred()) {
- PyErr_Print();
+ //PyErr_Print();
log_module(PY_LOG, LOG_INFO, "error converting return value of handler %s to type long. ", handler);
+ python_log_module();
ret = 0;
}
log_module(PY_LOG, LOG_INFO, "handler %s was run successfully, returned %d.", handler, ret);
else {
/* TODO: instead of print errors, get them as strings
* and deal with them with normal x3 log system. */
- PyErr_Print();
log_module(PY_LOG, LOG_WARNING, "call to handler %s failed", handler);
+ //PyErr_Print();
+ python_log_module();
Py_DECREF(pIrcObj);
Py_DECREF(pMethod);
return 0;
/*PyObject *pValue; */
pHandlerObj = PyObject_CallObject(pHandlerClass, NULL);
- return pHandlerObj;
+ if(pHandlerObj != NULL) {
+ log_module(PY_LOG, LOG_INFO, "Created new python handler object.");
+ return pHandlerObj;
+ }
+ else {
+ log_module(PY_LOG, LOG_ERROR, "Unable to instanciate handler object");
+ //PyErr_Print();
+ python_log_module();
+ return NULL;
+ }
}
else {
log_module(PY_LOG, LOG_ERROR, "Unable to find handler class");
+ //PyErr_Print();
+ python_log_module();
+ if(pHandlerClass) {
+ Py_DECREF(pHandlerClass);
+ }
return NULL;
}
}
}
}
else {
- PyErr_Print();
+ //PyErr_Print();
+ python_log_module();
log_module(PY_LOG, LOG_WARNING, "Failed to load modpython.py");
return 0;
}
return 1;
}
+#define numstrargs(X) sizeof(X) / sizeof(*X)
+static MODCMD_FUNC(cmd_command) {
+ char *plugin = argv[1];
+ char *command = argv[2];
+ char *msg; /* args */
+ if(argc > 3) {
+ msg = unsplit_string(argv + 3, argc - 3, NULL);
+ }
+ else {
+ msg = "";
+ }
+ char *args[] = {plugin, command, msg};
+ python_call_handler("cmd_command", args, numstrargs(args), cmd->parent->bot->nick, user?user->nick:"", channel?channel->name:"");
+ return 1;
+}
+
int python_init(void) {
/* X3 calls this function on init of the module during startup. We use it to
do all our setup tasks and bindings
*/
modcmd_register(python_module, "reload", cmd_reload, 1, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
modcmd_register(python_module, "run", cmd_run, 2, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
+ modcmd_register(python_module, "command", cmd_command, 3, MODCMD_REQUIRE_STAFF, NULL);
reg_join_func(python_handle_join);
reg_exit_func(python_cleanup);
def reply(self, message):
""" Send a private reply to the user using convenience values"""
print "DEBUG: sending a message from %s to %s: %s"%(self.service, self.caller, message)
- if(self.target):
+ if(len(self.target)):
self.send_target_privmsg(self.service, self.target, "%s: %s"%(self.caller, message))
else:
self.send_target_privmsg(self.service, self.caller, message)
class handler:
""" Main hub of python system. Handle callbacks from c. """
- modules = None #module object to deal with
+
+ def __init__(self):
+ print "DEBUG: constructor for handler initing"
+ self.plugins = plugins(self)
+ if(not self.plugins):
+ print "DEBUG: unable to make self.plugins!?!"
def init(self, irc): # not to be confused with __init__!
- print "DEBUG: This is x3init in python"
- self.modules = modules()
+ """ This gets called once all the objects are up and running. Otherwise,
+ were not done initing this own instance to be able to start calling it """
+ print "DEBUG: in handler.init()"
+ self.plugins.init()
return 0
def join(self, irc, channel, nick):
user = svc.get_user(nick)
print "DEBUG: handler.join()"
- irc.send_target_privmsg("x3", channel, "%s joined %s: %s "%(nick, channel, user))
+ self.plugins.callhandler("join", irc, [channel, nick], [channel, nick])
return 0
def cmd_run(self, irc, cmd):
print "DEBUG: handler.cmd_run: %s"%cmd
- eval(cmd);
- return 0;
+ eval(cmd)
+ return 0
-class modules:
- """Class to handle loading/unloading of modules"""
- loaded_modules = {}
+ def addhook(self, event, method, filter=[None], data=None):
+ self.plugins.addhook(event, method, filter, data)
+ return 0
- def __init__(self):
+ def addcommand(self, plugin, command, method):
+ self.addhook("command", method, [plugin, command])
+
+ def cmd_command(self, irc, plugin, cmd, args):
+ print "DEBUG: handlec.cmd_command; %s %s; args= %s"%(plugin, cmd, args)
+ self.plugins.callhandler("command", irc, [plugin, cmd], [args])
+ return 0
+
+class plugins:
+ """Class to handle loading/unloading of plugins"""
+ loaded_plugins = {}
+ hooks = []
+
+ class hook:
+ """ This is a request from a plugin to be called on an event """
+ event = "" # Event to be called on (eg "join")
+ method = None # Method to call
+ filter = None # Arguments to filter
+ data = "" # plugin-supplied data for plugin use
+
+ def __init__(self, event, method, filter, data):
+ self.event = event
+ self.method = method
+ self.filter = filter
+ self.data = data
+
+ def event_is(self, event, evdata):
+ if(self.event == event):
+ for i in range(len(self.filter)):
+ if( self.filter[i] != None
+ and self.filter[i] != evdata[i]): # should be case insensitive? or how to compare?
+ print "DEBUG: rejecting event, %s is not %s"%(self.filter[i], evdata[i])
+ return False
+ return True
+ else:
+ return False
+
+ def trigger(self, irc, args):
+ print "DEBUG: Triggering %s event. with '%s' arguments."%(self.event, args)
+ self.method(irc, *args)
+
+ def __init__(self, handler):
+ """ Constructor """
+ print "DEBUG: constructor for plugins initing"
+ self.handler = handler
+
+ def init(self):
+ print "DEBUG: in plugins.init()"
self.load("annoy")
+ def addhook(self, event, method, filter=[None], data=None):
+ print "DEBUG: Adding hook for %s."%event
+ self.hooks.append(self.hook(event, method, filter, data))
+
+ def findhooksforevent(self, event, data):
+ ret = []
+ print "DEBUG: findhooksforevent() looking..."
+ for hook in self.hooks:
+ print "DEBUG: looking at a %s hook..."%hook.event
+ if(hook.event_is(event, data)):
+ ret.append(hook)
+ return ret
+
+ def callhandler(self, event, irc, filter, args):
+ for hook in self.findhooksforevent(event, filter):
+ hook.trigger(irc, args)
+
def load(self, name):
+ """ Loads a plugin by name """
mod_name = "plugins.%s"%name
need_reload = False
if(sys.modules.has_key(mod_name)):
if(need_reload == True):
reload(module) # to ensure its read fresh
Class = module.Class
- pluginObj = Class(irc())
- self.loaded_modules[mod_name] = pluginObj
+ pluginObj = Class(self.handler, irc())
+ self.loaded_plugins[mod_name] = pluginObj
-
--- /dev/null
+These are mod-python plugins distributed with x3. If you wish to make customizations, you should do so in the
+plugins/ directory of your x3 runtime location, rather than the x3 source tree.
+
+Make a copy of the plugin directory you wish to change to your own plugin name, so your changes won't be overwritten on the next make install.
+
+
--- /dev/null
+__all__ = [ "annoy" ]
--- /dev/null
+
+import plugin
+reload(plugin)
+
+Class = plugin.Class
+
--- /dev/null
+# anoy module
+
+import svc
+
+class Annoy:
+
+ def __init__(self, handler, irc):
+ self.handler = handler
+ self.name = "annoy"
+
+ irc.send_target_privmsg("O3", "#TheOPS", "%s is loaded"%self.name)
+ handler.addhook("join", self.on_join, "foobar")
+ handler.addcommand(self.name, "dance", self.dance)
+ self.test = "footest"
+
+ def on_join(self, irc, channel, nick):
+ irc.send_target_privmsg("x3", channel, "%s joined %s:%s "%(nick, channel, self.test))
+
+ def dance(self, irc, args):
+ irc.reply("Ok, %s, we can dance %s."%(irc.caller, args))
+
+Class = Annoy