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