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