]> jfr.im git - irc/evilnet/x3.git/blob - src/mod-python.c
5c0253dd58a9d7bd8963fb845a9660b16b16de7f
[irc/evilnet/x3.git] / src / mod-python.c
1 /* mod-python.c - Script module for x3
2 * Copyright 2003-2004 Martijn Smit and srvx Development Team
3 * Copyright 2005-2006 X3 Development Team
4 *
5 * This file is part of x3.
6 *
7 * x3 is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with srvx; if not, write to the Free Software Foundation,
19 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
20 */
21
22 #include "config.h"
23 #ifdef WITH_PYTHON /* just disable this file if python doesnt exist */
24
25
26 #include <Python.h>
27 #include "chanserv.h"
28 #include "conf.h"
29 #include "modcmd.h"
30 #include "nickserv.h"
31 #include "opserv.h"
32 #include "saxdb.h"
33 #include "mail.h"
34 #include "timeq.h"
35 #include "compat.h"
36
37 /* TODO notes
38 *
39 * - Impliment most of proto-p10 irc_* commands for calling from scripts
40 * - Impliment functions to look up whois, channel, account, and reg-channel info for scripts
41 * - Impliment x3.conf settings for python variables like include path, etc.
42 * - modpython.py calls for everything you can reg_ a handler for in x3
43 * - Some kind of system for getting needed binds bound automagicaly to make it easier
44 * to run peoples scripts and mod-python in general.
45 * - An interface to reading/writing data to x3.db. Maybe generic, or attached to account or channel reg records?
46 */
47
48 static const struct message_entry msgtab[] = {
49 { "PYMSG_RELOAD_SUCCESS", "Reloaded Python scripts successfully." },
50 { "PYMSG_RELOAD_FAILED", "Error reloading Python scripts." },
51 { "PYMSG_RUN_UNKNOWN_EXCEPTION", "Error running python: unknown exception." },
52 { "PYMSG_RUN_EXCEPTION", "Error running python: %s: %s." },
53 { NULL, NULL } /* sentenal */
54 };
55
56 #define MODPYTHON_CONF_NAME "modules/python"
57
58 static
59 struct {
60 char const* scripts_dir;
61 char const* main_module;
62 } modpython_conf;
63
64 static struct log_type *PY_LOG;
65 const char *python_module_deps[] = { NULL };
66 static struct module *python_module;
67
68 PyObject *base_module = NULL; /* Base python handling library */
69 PyObject *handler_object = NULL; /* instanciation of handler class */
70
71
72 extern struct userNode *global, *chanserv, *opserv, *nickserv, *spamserv;
73
74 /* ---------------------------------------------------------------------- *
75 Some hooks you can call from modpython.py to interact with the
76 service, and IRC. These emb_* functions are available as _svc.*
77 in python.
78 */
79
80 static PyObject*
81 emb_dump(UNUSED_ARG(PyObject *self), PyObject *args)
82 {
83 /* Dump a raw string into the socket
84 usage: _svc.dump(<P10 string>)
85 */
86 char *buf;
87 int ret = 0;
88 char linedup[MAXLEN];
89
90
91 if(!PyArg_ParseTuple(args, "s:dump", &buf ))
92 return NULL;
93
94 safestrncpy(linedup, buf, sizeof(linedup));
95
96 if(parse_line(linedup, 1)) {
97 irc_raw(buf);
98 ret = 1;
99 } else {
100 PyErr_SetString(PyExc_Exception, "invalid protocol message");
101 return NULL;
102 }
103
104 return Py_BuildValue("i", ret);
105 }
106
107 static PyObject*
108 emb_send_target_privmsg(UNUSED_ARG(PyObject *self), PyObject *args)
109 {
110 /* Send a privmsg
111 usage: _svc.send_target_privmsg(<servicenick_from>, <nick_to>, <message>)
112 */
113 int ret = 0;
114 char *servicenick;
115 char *channel;
116 char *buf;
117
118 struct service *service;
119
120
121 if(!PyArg_ParseTuple(args, "sss:reply", &servicenick, &channel, &buf ))
122 return NULL;
123
124 if (buf == NULL || strlen(buf) == 0) {
125 PyErr_SetString(PyExc_Exception, "invalid empty message");
126 return NULL;
127 }
128
129 if(!(service = service_find(servicenick))) {
130 PyErr_SetString(PyExc_Exception, "no such service nick");
131 return NULL;
132 }
133
134 ret = send_target_message(5, channel, service->bot, "%s", buf);
135 return Py_BuildValue("i", ret);
136 }
137
138 static PyObject*
139 emb_send_target_notice(UNUSED_ARG(PyObject *self), PyObject *args)
140 {
141 /* send a notice
142 usage: _svc.send_target_notice(<servicenick_from>, <nick_to>, <message>)
143 */
144 int ret = 0;
145 char *servicenick;
146 char *target;
147 char *buf;
148
149 struct service *service;
150
151 if(!PyArg_ParseTuple(args, "sss:reply", &servicenick, &target, &buf ))
152 return NULL;
153
154 if (buf == NULL || strlen(buf) == 0) {
155 PyErr_SetString(PyExc_Exception, "invalid empty message");
156 return NULL;
157 }
158
159 if(!(service = service_find(servicenick))) {
160 PyErr_SetString(PyExc_Exception, "no such service nick");
161 return NULL;
162 }
163
164 ret = send_target_message(4, target, service->bot, "%s", buf);
165
166 return Py_BuildValue("i", ret);
167 }
168
169 static PyObject*
170 pyobj_from_usernode(struct userNode* user) {
171 unsigned int n;
172 struct modeNode *mn;
173 PyObject* pChanList = PyTuple_New(user->channels.used);
174
175 for (n=0; n < user->channels.used; n++) {
176 mn = user->channels.list[n];
177 PyTuple_SetItem(pChanList, n, Py_BuildValue("s", mn->channel->name));
178 }
179
180 return Py_BuildValue("{"
181 "s: s, " /* nick */
182 "s: s, " /* ident */
183 "s: s, " /* info */
184 "s: s, " /* hostname */
185 "s: s, " /* ip */
186 "s: s, " /* fakehost */
187 "s: s, " /* sethost */
188 "s: s, " /* crypthost */
189 "s: s, " /* cryptip */
190 #ifdef WITH_PROTOCOL_P10
191 "s: s, " /* numeric */
192 #endif /* WITH_PROTOCOL_P10 */
193 "s: i, " /* loc */
194 "s: i, " /* no_notice */
195 "s: s, " /* mark */
196 "s: s, " /* version_reply */
197 "s: s, " /* account */
198 "s: O}", /* channels */
199 "nick", user->nick,
200 "ident", user->ident,
201 "info", user->info,
202 "hostname", user->hostname,
203 "ip", irc_ntoa(&user->ip),
204 "fakehost", user->fakehost,
205 "sethost", user->sethost,
206 "crypthost", user->crypthost,
207 "cryptip", user->cryptip,
208 #ifdef WITH_PROTOCOL_P10
209 "numeric", user->numeric,
210 #endif /* WITH_PROTOCOL_P10 */
211 "loc", user->loc,
212 "no_notice", user->no_notice,
213 "mark", user->mark,
214 "version_reply", user->version_reply,
215 "account", user->handle_info ? user->handle_info->handle : NULL,
216 "channels", pChanList);
217 }
218
219 static PyObject*
220 emb_get_user(UNUSED_ARG(PyObject *self), PyObject *args)
221 {
222 /* Get a python object containing everything x3 knows about a user, by nick.
223 usage: _svc.get_user(<nick>)
224 */
225 char const* nick;
226 struct userNode *user;
227
228 if(!PyArg_ParseTuple(args, "s", &nick))
229 return NULL;
230
231 if(!(user = GetUserH(nick))) {
232 PyErr_SetString(PyExc_Exception, "no such user");
233 return NULL;
234 }
235
236 return pyobj_from_usernode(user);
237 }
238
239 static PyObject*
240 emb_get_channel(UNUSED_ARG(PyObject *self), PyObject *args)
241 {
242 /* Returns a python dict object with all sorts of info about a channel.
243 usage: _svc.get_channel(<name>)
244 */
245 char *name;
246 struct chanNode *channel;
247 unsigned int n;
248 PyObject *pChannelMembers;
249 PyObject *pChannelBans;
250 PyObject *pChannelExempts;
251
252
253 if(!PyArg_ParseTuple(args, "s", &name))
254 return NULL;
255
256 if(!(channel = GetChannel(name))) {
257 PyErr_SetString(PyExc_Exception, "unknown channel");
258 return NULL;
259 }
260
261 /* build tuple of nicks in channel */
262 pChannelMembers = PyTuple_New(channel->members.used);
263 for(n=0;n < channel->members.used;n++) {
264 struct modeNode *mn = channel->members.list[n];
265 PyTuple_SetItem(pChannelMembers, n, Py_BuildValue("s", mn->user->nick));
266 }
267
268 /* build tuple of bans */
269 pChannelBans = PyTuple_New(channel->banlist.used);
270 for(n=0; n < channel->banlist.used;n++) {
271 struct banNode *bn = channel->banlist.list[n];
272 PyTuple_SetItem(pChannelBans, n,
273 Py_BuildValue("{s:s,s:s,s:i}",
274 "ban", bn->ban,
275 "who", bn->who,
276 "set", bn->set)
277 );
278 }
279
280 /* build tuple of exempts */
281 pChannelExempts = PyTuple_New(channel->exemptlist.used);
282 for(n=0; n < channel->exemptlist.used;n++) {
283 struct exemptNode *en = channel->exemptlist.list[n];
284 PyTuple_SetItem(pChannelExempts, n,
285 Py_BuildValue("{s:s,s:s,s:i}",
286 "ban", en->exempt,
287 "who", en->who,
288 "set", en->set)
289 );
290 }
291
292 return Py_BuildValue("{s:s,s:s,s:s,s:i"
293 ",s:i,s:i,s:O,s:O,s:O}",
294
295 "name", channel->name,
296 "topic", channel->topic,
297 "topic_nick", channel->topic_nick,
298 "topic_time", channel->topic_time,
299
300 "timestamp", channel->timestamp,
301 "modes", channel->modes,
302 "members", pChannelMembers,
303 "bans", pChannelBans,
304 "exempts", pChannelExempts
305 );
306 }
307
308 static PyObject*
309 emb_get_account(UNUSED_ARG(PyObject *self), PyObject *args)
310 {
311 /* Returns a python dict object with all sorts of info about an account.
312 usage: _svc.get_account(<account name>)
313 */
314 char *name;
315 struct handle_info *hi;
316
317
318 if(!PyArg_ParseTuple(args, "s", &name))
319 return NULL;
320
321 hi = get_handle_info(name);
322 if(!hi) {
323 return NULL;
324 }
325 return Py_BuildValue("{s:s,s:i,s:s,s:s,s:s"
326 ",s:s,s:s}",
327
328 "account", hi->handle,
329 "registered", hi->registered,
330 "last_seen", hi->lastseen,
331 "infoline", hi->infoline ? hi->infoline : "",
332 "email", hi->email_addr ? hi->email_addr : "",
333
334 "fakehost", hi->fakehost ? hi->fakehost : "",
335 "last_quit_host", hi->last_quit_host
336
337 /* TODO: */
338 /* users online authed to this account */
339 /* cookies */
340 /* nicks (nickserv nets only?) */
341 /* masks */
342 /* ignores */
343 /* channels */
344 );
345 }
346
347 static PyObject*
348 emb_get_info(UNUSED_ARG(PyObject *self), UNUSED_ARG(PyObject *args))
349 {
350 /* return some info about the general setup
351 * of X3, such as what the chanserv's nickname
352 * is.
353 */
354
355
356 return Py_BuildValue("{s:s,s:s,s:s,s:s,s:s}",
357 "chanserv", chanserv? chanserv->nick : "ChanServ",
358 "nickserv", nickserv?nickserv->nick : "NickServ",
359 "opserv", opserv?opserv->nick : "OpServ",
360 "global", global?global->nick : "Global",
361 "spamserv", spamserv?spamserv->nick : "SpamServ");
362 }
363
364 static PyObject*
365 emb_log_module(UNUSED_ARG(PyObject *self), PyObject *args)
366 {
367 /* a gateway to standard X3 logging subsystem.
368 * level is a value 0 to 9 as defined by the log_severity enum in log.h.
369 * LOG_INFO is 3, LOG_WARNING is 6, LOG_ERROR is 7.
370 *
371 * for now, all logs go to the PY_LOG log. In the future this will change.
372 */
373 char *message;
374 int ret = 0;
375 int level;
376
377
378 if(!PyArg_ParseTuple(args, "is", &level, &message))
379 return NULL;
380
381 log_module(PY_LOG, level, "%s", message);
382
383 return Py_BuildValue("i", ret);
384 }
385
386 static PyMethodDef EmbMethods[] = {
387 /* Communication methods */
388 {"dump", emb_dump, METH_VARARGS, "Dump raw P10 line to server"},
389 {"send_target_privmsg", emb_send_target_privmsg, METH_VARARGS, "Send a message to somewhere"},
390 {"send_target_notice", emb_send_target_notice, METH_VARARGS, "Send a notice to somewhere"},
391 {"log_module", emb_log_module, METH_VARARGS, "Log something using the X3 log subsystem"},
392 //TODO: {"exec_cmd", emb_exec_cmd, METH_VARARGS, "execute x3 command provided"},
393 // This should use environment from "python command" call to pass in, if available
394 //TODO: {"kill"
395 //TODO: {"shun"
396 //TODO: {"unshun"
397 //TODO: {"gline", emb_gline, METH_VARARGS, "gline a mask"},
398 //TODO: {"ungline", emb_ungline, METH_VARARGS, "remove a gline"},
399 //TODO: {"kick", emb_kick, METH_VARARGS, "kick someone from a channel"},
400 //TODO: {"channel_mode", emb_channel_mode, METH_VARARGS, "set modes on a channel"},
401 //TODO: {"user_mode", emb_user_mode, METH_VARARGS, "Have x3 set usermodes on one of its own nicks"},
402 //
403 //TODO: {"get_config", emb_get_config, METH_VARARGS, "get x3.conf settings into a nested dict"},
404 //TODO: {"config_set", emb_config_set, METH_VARARGS, "change a config setting 'on-the-fly'."},
405 //
406 //TODO: {"timeq_add", emb_timeq_new, METH_VARARGS, "some kind of interface to the timed event system."},
407 //TODO: {"timeq_del", emb_timeq_new, METH_VARARGS, "some kind of interface to the timed event system."},
408 /* Information gathering methods */
409 {"get_user", emb_get_user, METH_VARARGS, "Get details about a nickname"},
410 {"get_channel", emb_get_channel, METH_VARARGS, "Get details about a channel"},
411 {"get_account", emb_get_account, METH_VARARGS, "Get details about an account"},
412 {"get_info", emb_get_info, METH_VARARGS, "Get various misc info about x3"},
413 /* null terminator */
414 {NULL, NULL, 0, NULL}
415 };
416
417
418 /* ------------------------------------------------------------------------------------------------ *
419 Thes functions set up the embedded environment for us to call out to modpython.py class
420 methods.
421 */
422
423 void python_log_module() {
424 /* Attempt to convert python errors to x3 log system */
425 PyObject *exc, *tb, *value, *tmp;
426 char *str_exc = "NONE";
427 char *str_value = "NONE";
428 char *str_tb = "NONE";
429
430 PyErr_Fetch(&exc, &value, &tb);
431
432 if(exc) {
433 if((tmp = PyObject_Str(exc)))
434 str_exc = PyString_AsString(tmp);
435 }
436 if(value) {
437 if((tmp = PyObject_Str(value)))
438 str_value = PyString_AsString(tmp);
439 }
440 if(tb) {
441 if((tmp = PyObject_Str(tb)))
442 str_tb = PyString_AsString(tmp);
443 }
444
445 /* Now restore it so we can print it (just in case)
446 * (should we do this only when running in debug mode?) */
447 PyErr_Restore(exc, value, tb);
448 PyErr_Print(); /* which of course, clears it again.. */
449
450 log_module(PY_LOG, LOG_WARNING, "PYTHON error: %s, value: %s", str_exc, str_value);
451
452 /* TODO: get the traceback using the traceback module via C api so we can add it to the X3 logs. See
453 * http://mail.python.org/pipermail/python-list/2003-March/192226.html */
454 // (this doesnt work, str_tb is just an object hashid) log_module(PY_LOG, LOG_INFO, "PYTHON error, traceback: %s", str_tb);
455 }
456
457
458 PyObject *python_build_handler_args(size_t argc, char *args[], PyObject *pIrcObj) {
459 /* Sets up a python tuple with passed in arguments, prefixed by the Irc instance
460 which handlers use to interact with c.
461 argc = number of args
462 args = array of args
463 pIrcObj = instance of the irc class
464 */
465 size_t i = 0, n;
466 PyObject *pArgs = NULL;
467
468 pArgs = PyTuple_New(argc + 1);
469 Py_INCREF(pIrcObj);
470 PyTuple_SetItem(pArgs, i++, pIrcObj);
471
472 if(args && argc) {
473 PyObject *pValue;
474 for(n = 0; n < argc; ++n) {
475 pValue = PyString_FromString(args[n]);
476 if(!pValue) {
477 Py_DECREF(pArgs);
478 log_module(PY_LOG, LOG_INFO, "Unable to convert '%s' to python string", args[n]);
479 return NULL;
480 }
481 PyTuple_SetItem(pArgs, n+i, pValue);
482 }
483 }
484 return pArgs;
485 }
486
487 PyObject *python_build_args(size_t argc, char *args[]) {
488 /* Builds the passed in arguments into a python argument tuple.
489 argc = number of args
490 args = array of args
491 */
492 size_t i;
493 PyObject *pArgs = NULL;
494
495 if(args && argc) {
496 pArgs = PyTuple_New(argc);
497 PyObject *pValue;
498 for(i = 0; i< argc; ++i) {
499 pValue = PyString_FromString(args[i]);
500 if(!pValue) {
501 Py_DECREF(pArgs);
502 log_module(PY_LOG, LOG_INFO, "Unable to convert '%s' to python string", args[i]);
503 return NULL;
504 }
505 PyTuple_SetItem(pArgs, i, pValue);
506 }
507 }
508 return pArgs;
509 }
510
511
512 PyObject *new_irc_object(char *command_service, char *command_caller, char *command_target) {
513 /* Creates a new instance of the irc class (from modpython.py) which is initalized
514 with current environment details like which service were talking to.
515 command_service = which service we are talking to, or empty string if none
516 command_caller = nick of user generating message, or empty string if none
517 command_target = If were reacting to something on a channel, this will
518 be set to the name of the channel. Otherwise empty
519 */
520 PyObject *pIrcArgs = NULL;
521 PyObject *pIrcClass;
522 PyObject *pIrcObj;
523
524 log_module(PY_LOG, LOG_INFO, "Attempting to instanciate irc class; %s %s %s", command_service, command_caller, command_target);
525 pIrcClass = PyObject_GetAttrString(base_module, "irc");
526 /* pIrcClass is a new reference */
527 if(pIrcClass && PyCallable_Check(pIrcClass)) {
528 //size_t i;
529 char *ircargs[] = {command_service, command_caller, command_target};
530 //PyObject *pValue;
531
532 pIrcArgs = python_build_args(3, ircargs);
533 pIrcObj = PyObject_CallObject(pIrcClass, pIrcArgs);
534 if(!pIrcObj) {
535 log_module(PY_LOG, LOG_ERROR, "IRC Class failed to load");
536 python_log_module();
537 //PyErr_Print();
538 }
539 if(pIrcArgs != NULL) {
540 Py_DECREF(pIrcArgs);
541 }
542 Py_DECREF(pIrcClass);
543 return pIrcObj;
544 }
545 else {
546 /* need to free pIrcClass here if it WAS found but was NOT callable? */
547 log_module(PY_LOG, LOG_ERROR, "Unable to find irc class");
548 return NULL;
549 }
550 }
551
552 int python_call_handler(char *handler, char *args[], size_t argc, char *command_service, char *command_caller, char *command_target) {
553 /* This is how we talk to modpython.c. First a new instance of the irc class is created using these
554 arguments to setup the current environment. Then the named method of the handler object is called
555 with the givin arguments.
556 */
557 PyObject *pIrcObj;
558 PyObject *pArgs;
559 PyObject *pMethod;
560 PyObject *pValue;
561
562 log_module(PY_LOG, LOG_INFO, "attempting to call handler %s.", handler);
563 if(base_module != NULL && handler_object != NULL) {
564 pIrcObj = new_irc_object(command_service, command_caller, command_target);
565 if(!pIrcObj) {
566 log_module(PY_LOG, LOG_INFO, "Can't get irc object. Bailing.");
567 return 0;
568 }
569
570 pArgs = python_build_handler_args(argc, args, pIrcObj);
571 pMethod = PyObject_GetAttrString(handler_object, handler);
572 if(pMethod && PyCallable_Check(pMethod)) {
573 /* Call the method, with the arguments */
574 pValue = PyObject_CallObject(pMethod, pArgs);
575 if(pArgs) {
576 Py_DECREF(pArgs);
577 }
578 if(pValue != NULL) {
579 int ret;
580 ret = PyInt_AsLong(pValue);
581 if(ret == -1 && PyErr_Occurred()) {
582 //PyErr_Print();
583 log_module(PY_LOG, LOG_INFO, "error converting return value of handler %s to type long. ", handler);
584 python_log_module();
585 ret = 0;
586 }
587 log_module(PY_LOG, LOG_INFO, "handler %s was run successfully, returned %d.", handler, ret);
588 Py_DECREF(pValue);
589 Py_DECREF(pIrcObj);
590 Py_DECREF(pMethod);
591 return ret;
592 }
593 else {
594 /* TODO: instead of print errors, get them as strings
595 * and deal with them with normal x3 log system. */
596 log_module(PY_LOG, LOG_WARNING, "call to handler %s failed", handler);
597 //PyErr_Print();
598 python_log_module();
599 Py_DECREF(pIrcObj);
600 Py_DECREF(pMethod);
601 return 0;
602 }
603 }
604 else { /* couldn't find handler methed */
605 Py_DECREF(pArgs);
606 /* Free pMethod if it was found but not callable? */
607 log_module(PY_LOG, LOG_ERROR, "Cannot find handler %s.", handler);
608 return 0;
609
610 }
611 }
612 else { /* No base module.. no python? */
613 log_module(PY_LOG, LOG_INFO, "Cannot handle %s, Python is not initialized.", handler);
614 return 0;
615 }
616 }
617
618 PyObject *python_new_handler_object() {
619 /* Create a new instance of the handler class.
620 This is called during python initilization (or reload)
621 and the result is saved and re-used.
622 */
623 PyObject *pHandlerClass, *pHandlerObj;
624
625 log_module(PY_LOG, LOG_INFO, "Attempting to instanciate python class handler");
626 pHandlerClass = PyObject_GetAttrString(base_module, "handler");
627 /* Class is a new reference */
628 if(pHandlerClass && PyCallable_Check(pHandlerClass)) {
629 /*PyObject *pValue; */
630
631 pHandlerObj = PyObject_CallObject(pHandlerClass, NULL);
632 if(pHandlerObj != NULL) {
633 log_module(PY_LOG, LOG_INFO, "Created new python handler object.");
634 return pHandlerObj;
635 }
636 else {
637 log_module(PY_LOG, LOG_ERROR, "Unable to instanciate handler object");
638 //PyErr_Print();
639 python_log_module();
640 return NULL;
641 }
642 }
643 else {
644 log_module(PY_LOG, LOG_ERROR, "Unable to find handler class");
645 //PyErr_Print();
646 python_log_module();
647 if(pHandlerClass) {
648 Py_DECREF(pHandlerClass);
649 }
650 return NULL;
651 }
652 }
653
654 /* ------------------------------------------------------------------------------- *
655 Some gateway functions to convert x3 callbacks into modpython.py callbacks.
656 Mostly we just build relevant args and call the proper handler method
657
658 debate: do we just register these and check them in python
659 for every one (slow?) or actually work out if a plugin needs
660 it first? We will start by doing it every time.
661 */
662 static int
663 python_handle_join(struct modeNode *mNode)
664 {
665 /* callback for handle_join events.
666 */
667 struct userNode *user = mNode->user;
668 struct chanNode *channel = mNode->channel;
669
670
671 log_module(PY_LOG, LOG_INFO, "python module handle_join");
672 if(!channel||!user) {
673 log_module(PY_LOG, LOG_WARNING, "Python code got join without channel or user!");
674 return 0;
675 }
676 else {
677 char *args[] = {channel->name, user->nick};
678 return python_call_handler("join", args, 2, "", "", "");
679 }
680 }
681
682 static int
683 python_handle_server_link(struct server *server)
684 {
685 log_module(PY_LOG, LOG_INFO, "python module handle_server_link");
686 if(!server) {
687 log_module(PY_LOG, LOG_WARNING, "Python code got server link without server!");
688 return 0;
689 }
690 else {
691 char *args[] = {server->name, server->description};
692 return python_call_handler("server_link", args, 2, "", "", "");
693 }
694 }
695
696 static int
697 python_handle_new_user(struct userNode *user)
698 {
699 log_module(PY_LOG, LOG_INFO, "Python module handle_new_user");
700 if(!user) {
701 log_module(PY_LOG, LOG_WARNING, "Python code got new_user without the user");
702 return 0;
703 }
704 else {
705 char *args[] = {user->nick, user->ident, user->hostname, user->info};
706 return python_call_handler("new_user", args, 4, "", "", "");
707 }
708 }
709
710 static void
711 python_handle_nick_change(struct userNode *user, const char *old_nick)
712 {
713 log_module(PY_LOG, LOG_INFO, "Python module handle_nick_change");
714 if(!user) {
715 log_module(PY_LOG, LOG_WARNING, "Python code got nick_change without the user!");
716 }
717 else {
718 char *args[] = {user->nick, (char *)old_nick};
719 python_call_handler("nick_change", args, 2, "", "", "");
720 }
721 }
722
723 /* ----------------------------------------------------------------------------- */
724
725
726 int python_load() {
727 /* Init the python engine and do init work on modpython.py
728 This is called during x3 startup, and on a python reload
729 */
730 PyObject *pName;
731 char* buffer;
732 char* env = getenv("PYTHONPATH");
733
734 if (env)
735 env = strdup(env);
736
737 if (!env)
738 setenv("PYTHONPATH", modpython_conf.scripts_dir, 1);
739 else if (!strstr(env, modpython_conf.scripts_dir)) {
740 buffer = (char*)malloc(strlen(env) + strlen(modpython_conf.scripts_dir) + 2);
741 sprintf(buffer, "%s:%s", modpython_conf.scripts_dir, env);
742 setenv("PYTHONPATH", buffer, 1);
743 free(buffer);
744 free(env);
745 }
746
747 Py_Initialize();
748 Py_InitModule("_svc", EmbMethods);
749 pName = PyString_FromString(modpython_conf.main_module);
750 base_module = PyImport_Import(pName);
751 Py_DECREF(pName);
752
753 Py_XDECREF(handler_object);
754 handler_object = NULL;
755
756 if(base_module != NULL) {
757 handler_object = python_new_handler_object();
758 if(handler_object) {
759 python_call_handler("init", NULL, 0, "", "", "");
760 return 1;
761 }
762 else {
763 /* error handler class not found */
764 log_module(PY_LOG, LOG_WARNING, "Failed to create handler object");
765 return 0;
766 }
767 }
768 else {
769 //PyErr_Print();
770 python_log_module();
771 log_module(PY_LOG, LOG_WARNING, "Failed to load modpython.py");
772 return 0;
773 }
774 return 0;
775 }
776
777 int
778 python_finalize(void) {
779 /* Called after X3 is fully up and running.
780 Code can be put here that needs to run to init things, but
781 which is sensitive to everything else in x3 being up and ready
782 to go.
783 */
784
785 PyRun_SimpleString("print 'Hello, World of Python!'");
786 log_module(PY_LOG, LOG_INFO, "python module finalize");
787
788 return 1;
789 }
790
791 static void
792 python_cleanup(void) {
793 /* Called on shutdown of the python module (or before reloading)
794 */
795
796 log_module(PY_LOG, LOG_INFO, "python module cleanup");
797 if (PyErr_Occurred())
798 PyErr_Clear();
799 Py_Finalize(); /* Shut down python enterpriter */
800 }
801
802 /* ---------------------------------------------------------------------------------- *
803 Python module command handlers.
804 */
805 static MODCMD_FUNC(cmd_reload) {
806 /* reload the python system completely
807 */
808 log_module(PY_LOG, LOG_INFO, "Shutting python down");
809 python_cleanup();
810 log_module(PY_LOG, LOG_INFO, "Loading python stuff");
811 if(python_load()) {
812 reply("PYMSG_RELOAD_SUCCESS");
813 }
814 else {
815 reply("PYMSG_RELOAD_FAILED");
816 }
817 return 1;
818 }
819
820 static char* format_python_error(int space_nls) {
821 PyObject* extype = NULL, *exvalue = NULL, *extraceback = NULL;
822 PyObject* pextypestr = NULL, *pexvaluestr = NULL;
823 char* extypestr = NULL, *exvaluestr = NULL;
824 size_t retvallen = 0;
825 char* retval = NULL, *tmp;
826
827 PyErr_Fetch(&extype, &exvalue, &extraceback);
828 if (!extype)
829 goto cleanup;
830
831 pextypestr = PyObject_Str(extype);
832 if (!pextypestr)
833 goto cleanup;
834 extypestr = PyString_AsString(pextypestr);
835 if (!extypestr)
836 goto cleanup;
837
838 pexvaluestr = PyObject_Str(exvalue);
839 if (pexvaluestr)
840 exvaluestr = PyString_AsString(pexvaluestr);
841
842 retvallen = strlen(extypestr) + (exvaluestr ? strlen(exvaluestr) + 2 : 0) + 1;
843 retval = (char*)malloc(retvallen);
844 if (exvaluestr)
845 snprintf(retval, retvallen, "%s: %s", extypestr, exvaluestr);
846 else
847 strncpy(retval, extypestr, retvallen);
848
849 if (space_nls) {
850 tmp = retval;
851 while (*tmp) {
852 if (*tmp == '\n')
853 *tmp = ' ';
854 ++tmp;
855 }
856 }
857
858 cleanup:
859 if (PyErr_Occurred())
860 PyErr_Clear(); /* ignore errors caused by formatting */
861 Py_XDECREF(extype);
862 Py_XDECREF(exvalue);
863 Py_XDECREF(extraceback);
864 Py_XDECREF(pextypestr);
865 Py_XDECREF(pexvaluestr);
866
867 if (retval)
868 return retval;
869
870 return strdup("unknown exception");
871 }
872
873 static MODCMD_FUNC(cmd_run) {
874 /* this method allows running arbitrary python commands.
875 * use with care.
876 */
877 char* msg;
878 PyObject* py_main_module;
879 PyObject* py_globals;
880 PyObject* py_locals;
881 PyObject* py_retval;
882 PyObject* extype, *exvalue, *extraceback;
883 PyObject* exvaluestr = NULL;
884 char* exmsg = NULL, *exmsgptr;
885
886 py_main_module = PyImport_AddModule("__main__");
887 py_globals = py_locals = PyModule_GetDict(py_main_module);
888
889 msg = unsplit_string(argv + 1, argc - 1, NULL);
890
891 py_retval = PyRun_String(msg, Py_file_input, py_globals, py_locals);
892 if (py_retval == NULL) {
893 PyErr_Fetch(&extype, &exvalue, &extraceback);
894 if (exvalue != NULL) {
895 exvaluestr = PyObject_Str(exvalue);
896 exmsg = strdup(PyString_AS_STRING(exvaluestr));
897 exmsgptr = exmsg;
898 while (exmsgptr && *exmsgptr) {
899 if (*exmsgptr == '\n' || *exmsgptr == '\r' || *exmsgptr == '\t')
900 *exmsgptr = ' ';
901 exmsgptr++;
902 }
903 }
904 if (extype != NULL && exvalue != NULL && PyType_Check(extype)) {
905 reply("PYMSG_RUN_EXCEPTION", ((PyTypeObject*)extype)->tp_name, exmsg);
906 } else
907 reply("PYMSG_RUN_UNKNOWN_EXCEPTION");
908
909 if (extype != NULL)
910 Py_DECREF(extype);
911 if (exvalue != NULL)
912 Py_DECREF(exvalue);
913 if (extraceback != NULL)
914 Py_DECREF(extraceback);
915 if (exvaluestr != NULL)
916 Py_DECREF(exvaluestr);
917 if (exmsg)
918 free(exmsg);
919 } else {
920 Py_DECREF(py_retval);
921 }
922
923 return 1;
924 }
925
926 #define numstrargs(X) sizeof(X) / sizeof(*X)
927 static MODCMD_FUNC(cmd_command) {
928 char *plugin = argv[1];
929 char *command = argv[2];
930 char *msg; /* args */
931 if(argc > 3) {
932 msg = unsplit_string(argv + 3, argc - 3, NULL);
933 }
934 else {
935 msg = "";
936 }
937 char *args[] = {plugin, command, msg};
938 python_call_handler("cmd_command", args, numstrargs(args), cmd->parent->bot->nick, user?user->nick:"", channel?channel->name:"");
939 return 1;
940 }
941
942 static void modpython_conf_read(void) {
943 dict_t conf_node;
944 char const* str;
945
946 if (!(conf_node = conf_get_data(MODPYTHON_CONF_NAME, RECDB_OBJECT))) {
947 log_module(PY_LOG, LOG_ERROR, "config node '%s' is missing or has wrong type", MODPYTHON_CONF_NAME);
948 return;
949 }
950
951 str = database_get_data(conf_node, "scripts_dir", RECDB_QSTRING);
952 modpython_conf.scripts_dir = str ? str : "./";
953
954 str = database_get_data(conf_node, "main_module", RECDB_QSTRING);
955 modpython_conf.main_module = str ? str : "modpython";
956 }
957
958 int python_init(void) {
959 /* X3 calls this function on init of the module during startup. We use it to
960 do all our setup tasks and bindings
961 */
962
963 PY_LOG = log_register_type("Python", "file:python.log");
964 python_module = module_register("python", PY_LOG, "mod-python.help", NULL);
965 conf_register_reload(modpython_conf_read);
966
967 log_module(PY_LOG, LOG_INFO, "python module init");
968 message_register_table(msgtab);
969
970 /*
971 reg_auth_func(python_check_messages);
972 reg_handle_rename_func(python_rename_account);
973 reg_unreg_func(python_unreg_account);
974 conf_register_reload(python_conf_read);
975 saxdb_register("python", python_saxdb_read, python_saxdb_write);
976 modcmd_register(python_module, "send", cmd_send, 3, MODCMD_REQUIRE_AUTHED, NULL);
977 */
978 modcmd_register(python_module, "reload", cmd_reload, 1, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
979 modcmd_register(python_module, "run", cmd_run, 2, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
980 modcmd_register(python_module, "command", cmd_command, 3, MODCMD_REQUIRE_STAFF, NULL);
981
982 // Please help us by implimenting any of the callbacks listed as TODO below. They already exist
983 // in x3, they just need handle_ bridges implimented. (see python_handle_join for an example)
984 reg_server_link_func(python_handle_server_link);
985 reg_new_user_func(python_handle_new_user);
986 reg_nick_change_func(python_handle_nick_change);
987 //TODO: reg_del_user_func(python_handle_del_user);
988 //TODO: reg_account_func(python_handle_account); /* stamping of account name to the ircd */
989 //TODO: reg_handle_rename_func(python_handle_handle_rename); /* handle used to ALSO mean account name */
990 //TODO: reg_failpw_func(python_handle_failpw);
991 //TODO: reg_allowauth_func(python_handle_allowauth);
992 //TODO: reg_handle_merge_func(python_handle_merge);
993 //
994 //TODO: reg_oper_func(python_handle_oper);
995 //TODO: reg_new_channel_func(python_handle_new_channel);
996 reg_join_func(python_handle_join);
997 //TODO: reg_del_channel_func(python_handle_del_channel);
998 //TODO: reg_part_func(python_handle_part);
999 //TODO: reg_kick_func(python_handle_kick);
1000 //TODO: reg_topic_func(python_handle_topic);
1001 //TODO: reg_channel_mode_func(python_handle_channel_mode);
1002
1003 //TODO: reg_privmsg_func(python_handle_privmsg);
1004 //TODO: reg_notice_func
1005 //TODO: reg_svccmd_unbind_func(python_handle_svccmd_unbind);
1006 //TODO: reg_chanmsg_func(python_handle_chanmsg);
1007 //TODO: reg_allchanmsg_func
1008 //TODO: reg_user_mode_func
1009
1010 reg_exit_func(python_cleanup);
1011
1012 python_load();
1013 return 1;
1014 }
1015
1016 #endif /* WITH_PYTHON */