diff --git a/Makefile.in b/Makefile.in index 733e3e613..93de77211 100644 --- a/Makefile.in +++ b/Makefile.in @@ -490,6 +490,7 @@ install-doc: install-start cd doc/ && $(MAKE_INSTALL) install install-scripts: install-start - +@cd scripts/ && $(MAKE_INSTALL) install + +@cd scripts/ && $(MAKE_INSTALL) install; \ + cd ../src/mod && $(MAKE_INSTALL) install-scripts #safety hash diff --git a/doc/sphinx_source/modules/mod/python.rst b/doc/sphinx_source/modules/mod/python.rst index 86c26ec68..1a26a291c 100644 --- a/doc/sphinx_source/modules/mod/python.rst +++ b/doc/sphinx_source/modules/mod/python.rst @@ -30,13 +30,16 @@ python You can run a python command from the partyline with the .python command, such as:: - .python print('Hello world!') + .python 1 + 1 + Python: 2 + .python from eggdrop.tcl import putmsg; putmsg('#chan', 'Hello world!') + Python: None ^^^^^^^^^^^^^ .binds python ^^^^^^^^^^^^^ -The python module extends the core ``.binds`` command by adding a ``python`` mask. This command will list all binds for python scripts. +The python module extends the core ``.binds`` partyline command by adding a ``python`` mask. This command will list all binds for python scripts. ------------ Tcl Commands @@ -54,7 +57,21 @@ Eggdrop Python Commands The Python module is built to use the existing core Tcl commands integrated into Eggdrop via the ``eggdrop.tcl`` module. To call an existing Tcl command from Python, you can either load the entire catalog by running ``import eggdrop.tcl``, or be more specific by ``from eggdrop.tcl import putserv, putlog, chanlist``, etc. -Additionally, a few extra python commands have been created for use: +Arguments to the Tcl functions are automatically converted as follows: + +* ``None`` is converted to an empty Tcl object (the empty string, ``""``) +* ``List`` and ``Tuple`` is converted to a ``Tcl list`` +* ``Dict`` is converted to a ``Tcl dictionary`` +* Everything else is converted to a string using the str() method + +Return values from Tcl functions must be manually converted: + +* ``""`` the empty string is automatically converted to None +* everything else is returned as string +* ``Tcl list`` as string can be converted to a Python ``List`` using ``parse_tcl_list`` +* ``Tcl dictionary`` as string can be converted to a Python ``Dict`` using ``parse_tcl_list`` + +Additionally, a few extra python commands have been created for use without these conversions: ^^^^^^^^^^^^^^^^ bind diff --git a/scripts/eggdroppy/__init__.py b/scripts/eggdroppy/__init__.py deleted file mode 100644 index bbaa72631..000000000 --- a/scripts/eggdroppy/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .flags import * -from .cmds import * diff --git a/scripts/eggdroppy/binds.py b/scripts/eggdroppy/binds.py deleted file mode 100644 index 8c7238903..000000000 --- a/scripts/eggdroppy/binds.py +++ /dev/null @@ -1,248 +0,0 @@ -from dataclasses import dataclass -from typing import Callable -import re -import uuid -import sys -from pprint import pprint, pformat -import eggdrop -from eggdroppy.flags import FlagRecord, FlagMatcher -from eggdroppy.cmds import putmsg, putnotc -from dataclasses import dataclass -from datetime import datetime - -bindtypes = { - "pub": {"args": ("nick", "host", "handle", "channel", "text"), "reply": "chanmsg"}, - "pubm": {"args": ("nick", "host", "handle", "channel", "text"), "reply": "chanmsg"}, - "msg": {"args": ("nick", "host", "handle", "text"), "reply": "privmsg"}, - "msgm": {"args": ("nick", "host", "handle", "text"), "reply": "privmsg"}, - "dcc": {"args": ("handle", "idx", "text"), "reply": "dcc"}, - "join": {"args": ("nick", "host", "handle", "channel"), "reply": "privnotc"} -""" - "fil": {"args": ("handle", "idx", "text"), "reply": "xxx"}, - "notc": {"args": ("nick", "host", "handle", "text", "dest"), "reply": "xxx"}, - "part": {"args": ("nick", "host", "handle", "channel", "msg"), "reply": "xxx"}, - "sign": {"args": ("nick", "host", "handle", "channel", "reason"), "reply": "xxx"}, - "kick": {"args": ("nick", "host", "handle", "channel", "topic"), "reply": "xxx"}, - "nick": {"args": ("nick", "host", "handle", "channel", "newnick"), "reply": "xxx"}, - "mode": {"args": ("nick", "host", "handle", "channel", "mode", "target"), "reply": "xxx"}, - "ctcp": {"args": ("nick", "host", "handle", "dest", "key", "text"), "reply": "xxx"}, - "ctcr": {"args": ("nick", "host", "handle", "dest", "key", "text"), "reply": "xxx"}, - "raw": {"args": ("from", "key", "text"), "reply": "xxx"}, - "bot": {"args": ("bot", "cmd", "text"), "reply": "xxx"}, - "chon": {"args": ("handle", "idx"), "reply": "xxx"}, - "chof": {"args": ("handle", "idx"), "reply": "xxx"}, - "sent": {"args": ("nick", "handle", "file"), "reply": "xxx"}, - "rcvd": {"args": ("nick", "handle", "file"), "reply": "xxx"}, - "chat": {"args": ("handle", "channel", "text"), "reply": "xxx"}, - "link": {"args": ("bot", "via"), "reply": "xxx"}, - "disc": {"args": ("bot"), "reply": "xxx"}, - "splt": {"args": ("nick", "host", "handle", "channel"), "reply": "xxx"}, - "rejn": {"args": ("nick", "host", "handle", "channel"), "reply": "xxx"}, - "filt": {"args": ("idx", "text"), "reply": "xxx"}, - "need": {"args": ("channel", "type"), "reply": "xxx"}, - "flud": {"args": ("nick", "host", "handle", "channel", "type"), "reply": "xxx"}, - "note": {"args": ("from", "to", "text"), "reply": "xxx"}, - "act": {"args": ("handle", "channel", "action"), "reply": "xxx"}, - "wall": {"args": ("from", "msg"), "reply": "xxx"}, - "bcst": {"args": ("bot", "channel", "text"), "reply": "xxx"}, - "chjn": {"args": ("bot", "host", "handle", "channel", "flag", "idx"), "reply": "xxx"}, - "chpt": {"args": ("bot", "handle", "channel", "idx"), "reply": "xxx"}, - "time": {"args": ("min", "hour", "day", "month", "year"), "reply": "xxx"}, - "away": {"args": ("bot", "idx", "text"), "reply": "xxx"}, - "load": {"args": ("module"), "reply": "xxx"}, - "unld": {"args": ("module"), "reply": "xxx"}, - "nkch": {"args": ("old", "new"), "reply": "xxx"}, - "evnt": {"args": ("type"), "reply": "xxx"}, - "lost": {"args": ("nick", "handle", "path", "bytes", "length"), "reply": "xxx"}, - "tout": {"args": ("nick", "handle", "path", "bytes", "length"), "reply": "xxx"}, - "out": {"args": ("queue", "text", "sent"), "reply": "xxx"}, - "cron": {"args": ("min", "hour", "day", "month", "weekday"), "reply": "xxx"}, - "log": {"args": ("channel", "text", "level"), "reply": "xxx"}, - "tls": {"args": ("idx"), "reply": "xxx"}, - "die": {"args": ("reason"), "reply": "xxx"}, - "ircaway": {"args": ("nick", "host", "handle", "channel", "msg"), "reply": "xxx"}, - "invt": {"args": ("nick", "host", "handle", "channel", "invitee"), "reply": "xxx"}, - "rawt": {"args": ("from", "key", "text", "tag"), "reply": "xxx"}, - "account": {"args": ("nick", "host", "handle", "channel", "account"), "reply": "xxx"}, - "isupport": {"args": ("key", "isset", "value"), "reply": "xxx"}, - "monitor": {"args": ("nick", "online"), "reply": "xxx"} -""" -} - -@dataclass -class IRCUser: - nick: str - host: str - account: str - lastseen: datetime = None - joined: datetime = None - -@dataclass -class Bind: - bindtype: str - flags: str - mask: str - callback: Callable - hits: int = 0 - - @property - def id(self): - return f'{self.bindtype}{hex(id(self))[2:]}' - -class BindCallback: - - @staticmethod - def make_replyfunc(replytype, argdict): - replyfunc = None - if replytype == "chanmsg": - def replyfunc(response): - putmsg(argdict["channel"], response) - elif replytype == "privmsg": - def replyfunc(response): - putmsg(argdict["nick"], response) - elif replytype == "privnotc": - def replyfunc(response): - putnotc(argdict["nick"], response) - elif replytype == "dcc": - def replyfunc(response): - # TODO: putdcc - print(f"Python DCC response: {response}") - return replyfunc - - def __init__(self, bindtype, mask, callback : Callable): - self.__callback = callback - self.__bindtype = bindtype - self.__mask = mask - - def __call__(self, *args): - pprint(args) - bindinfo = bindtypes[self.__bindtype] - - kwargs = {"bindtype": self.__bindtype, "mask": self.__mask} - kwargs.update(zip(bindinfo["args"], args)) - if "nick" in kwargs: - ircuser_dict = eggdrop.findircuser(kwargs["nick"], kwargs["channel"]) if "channel" in kwargs else eggdrop.findircuser(kwargs["nick"]) - kwargs["ircuser"] = IRCUser(**ircuser_dict) - if "reply" in bindinfo: - kwargs["reply"] = self.make_replyfunc(bindinfo["reply"], argdict=kwargs) - - pprint(kwargs) - self.__callback(**kwargs) - - def __str__(self): - return self.__callback.__name__ - -class BindType: - """ A BindType is an event that can trigger an Eggdrop response - - Each event that Eggdrop refers to, called a bind, requires a :class:`BindType` to be loaded by the - :class:`Binds` class. - - Args: - bindtype (string): A string representing one of the core Eggdrop bind types - managed (bool): True if bindtype is managed by Eggdrop - """ - def __init__(self, bindtype, managed): - self.__bindtype = bindtype - self.__managed = managed - self.__binds = {} - - @staticmethod - def make_callback_func(bindtype, mask, callback): - return BindCallback(bindtype, mask, callback) - - def add(self, mask : str, callback : Callable, flags : str = "-"): - """ Register a new bind event - - Adds a new :class:`BindType` attribute to a :class:`Bind` object. - - Args: - flags (object): a flag object, we'll figure this out soon - mask (str): mask or command or something, maybe find a better word here - callback (method): The name of the function you wish to call when the event is triggered - """ - cb = self.make_callback_func(bindtype=self.__bindtype, mask=mask, callback=callback) - bind = Bind(flags=flags, mask=mask, callback=cb, bindtype=self.__bindtype) - self.__binds[bind.id] = bind - if self.__managed: - eggdrop.bind(self.__bindtype, flags, mask, cb) - - def delete(self, bindid): - bind = self.__binds[bindid] - if self.__managed: - eggdrop.unbind(self.__bindtype, bind.flags, bind.mask, bind.callback) - if bindid in self.__binds: - del self.__binds[bindid] - - def list(self): - """ List all binds of the ``bindtype`` - - Returns: - list: A list of binds, in the format {A B C D} - """ - return self.__binds - - def all(self): - return self.list() - - def __str__(self): - return f"{self.__bindtype}-binds: {str(self.__binds)}" - -class Binds: - """ A :class:`Binds` object holds a collection of :class:`BindTypes` objects - - All binds that are added to Eggdrop are collected and accessed through a :class:`Binds` object. Each - event type that Eggdrop reacts to is added to the :class:`Binds` object as a bind via a - :class:`BindTypes` object. - - Args: None - """ - def __init__(self): - self.__binds = {x: BindType(x, True) for x in bindtypes.keys()} - - def __getattr__(self, name): - return self.__binds[name] - - def all(self): - """ Lists all binds registered with the object - - Returns: - list: A list, or maybe a dict? of all binds - """ - return self.__binds - - def types(self): - """ Lists all :class:`BindType` attributes added to the :class:`Binds` object - - Returns: - list: maybe a list? of attributes - """ - return self.__binds.keys() - - def __str__(self): - return [{x: [str(b) for b in y]} for x, y in self.__binds.items()] - - -__allbinds = Binds() - -for bindtype in __allbinds.types(): - setattr(sys.modules[__name__], bindtype, getattr(__allbinds, bindtype)) - -def all(): - return __allbinds.all() - -def types(): - return __allbinds.types() - -def print_all(reply, **kwargs): - reply("{0: <8} | {1: <18} | {2: <12} | {3: <24} | {4}".format('ID', 'function', 'flags', 'mask', 'hits')) - for i in types(): - if __allbinds.all()[i].all(): - reply("-"*78) - reply(f'{i:<8}') - reply("-"*78) - for j in __allbinds.all()[i].all().keys(): - reply(f'{j} | {str(__allbinds.all()[i].all()[j].callback):<18} | {str(__allbinds.all()[i].all()[j].flags):<12} | {__allbinds.all()[i].all()[j].mask:<24} | {__allbinds.all()[i].all()[j].hits:<4}') - reply("-"*78) - -__allbinds.dcc.add(mask='pybinds', callback=print_all) diff --git a/scripts/eggdroppy/cmds.py b/scripts/eggdroppy/cmds.py deleted file mode 100644 index d86da1664..000000000 --- a/scripts/eggdroppy/cmds.py +++ /dev/null @@ -1,39 +0,0 @@ -from enum import IntEnum -import eggdrop - -class IRCQueue(IntEnum): - NOQUEUE = 0 - SERVER = eggdrop.QUEUE_SERVER - HELP = eggdrop.QUEUE_HELP - QUICK = MODE = eggdrop.QUEUE_MODE - -def ircsend(text, queue = IRCQueue.SERVER, make_first=False): - if queue == IRCQueue.QUICK and make_first: - queuenum = eggdrop.QUEUE_MODE_NEXT - elif queue == IRCQueue.SERVER and make_first: - queuenum = eggdrop.QUEUE_SERVER_NEXT - elif queue == IRCQueue.HELP and make_first: - queuenum = eggdrop.QUEUE_HELP_NEXT - elif queue == IRCQueue.NOQUEUE: - raise Exception("Not implemented yet") - eggdrop.ircsend(text.rstrip('\r\n'), queue) - -# unnecessary shorthands (?) -def putserv(text, make_first=False): - ircsend(text, queue=IRCQueue.SERVER, make_first=make_first) - -def puthelp(text, make_first=False): - ircsend(text, queue=IRCQueue.HELP, make_first=make_first) - -def putquick(text, make_first=False): - ircsend(text, queue=IRCQueue.MODE, make_first=make_first) - -# useful shorthands -def putmsg(dst, text): - for line in text.split('\n'): - ircsend(f"PRIVMSG {dst} :{line}") - -def putnotc(dst, text): - for line in text.split('\n'): - ircsend(f"NOTICE {dst} :{line}") - diff --git a/scripts/eggdroppy/flags.py b/scripts/eggdroppy/flags.py deleted file mode 100644 index b6ed22a9d..000000000 --- a/scripts/eggdroppy/flags.py +++ /dev/null @@ -1,103 +0,0 @@ -from enum import IntFlag -from pprint import pprint -import eggdrop - -# TODO: sort these properly -class UserFlags(IntFlag): - n = owner = eggdrop.USER_OWNER - m = master = eggdrop.USER_MASTER - a = autoop = eggdrop.USER_AUTOOP - o = op = eggdrop.USER_OP - g = autovoice = eggdrop.USER_GVOICE - v = voice = eggdrop.USER_VOICE - b = bot = eggdrop.USER_BOT - c = common = eggdrop.USER_COMMON - d = deop = eggdrop.USER_DEOP - e = exempt = eggdrop.USER_EXEMPT - f = friend = eggdrop.USER_FRIEND - h = highlight = eggdrop.USER_HIGHLITE - j = janitor = eggdrop.USER_JANITOR - k = autokick = eggdrop.USER_KICK - l = halfop = eggdrop.USER_HALFOP - p = partyline = eggdrop.USER_PARTY - q = devoice = eggdrop.USER_QUIET - r = dehalfop = eggdrop.USER_DEHALFOP - t = botnetmaster = eggdrop.USER_BOTMAST - u = unshared = eggdrop.USER_UNSHARED - w = wasoptest = eggdrop.USER_WASOPTEST - x = xfer = eggdrop.USER_XFER - y = autohalfop = eggdrop.USER_AUTOHALFOP - z = washalfoptest = eggdrop.USER_WASHALFOPTEST - - def __repr__(self): - if self.value == 0: - return "-" - return ''.join(f.name for f in self.__class__ if f.value & self.value) - -class FlagRecord: - def __init__(self, globalflags=None, chanflags=None, botflags=None): - self.globl = UserFlags(globalflags) - self.chan = UserFlags(chanflags) - self.bot = UserFlags(botflags) - - def __str__(self): - return 'globl: {}, chan: {}, bot: {}'.format(str(self.globl), str(self.chan), str(self.bot)) - - def __repr__(self): - return f"{repr(self.globl)}|{repr(self.chan)}|{repr(self.bot)}" - -class FlagMatcher: - def __init__(self, globalflags=None, globalnegflags=None, chanflags=None, channegflags=None, botflags=None, botnegflags=None, requireall=False): - self.globalflags = globalflags - self.globalnegflags = globalnegflags - self.chanflags = chanflags - self.channegflags = channegflags - self.botflags = botflags - self.botnegflags = botnegflags - self.requireall = requireall - - @staticmethod - def reprflags(pls, mns): - s = "" - if pls: - s += "+" + repr(pls) - if mns: - s += "-" + repr(mns) - return s - - def __repr__(self): - s = "" - sep = "&" if self.requireall else "|" - if not self.globalflags and not self.globalnegflags and not self.chanflags and not self.channegflags and not self.botflags and not self.botnegflags: - return "-" - if self.globalflags or self.globalnegflags: - s += self.reprflags(self.globalflags, self.globalnegflags) - s += sep - if self.chanflags or self.channegflags: - s += self.reprflags(self.chanflags, self.channegflags) - if self.botflags or self.botnegflags: - s += sep - s += self.reprflags(self.botflags, self.botnegflags) - return s - - @staticmethod - def flagcheck(posflags, negflags, requireall, flags): - if not requireall: - if posflags and not posflags & flags: - return False - else: - if posflags and not posflags & flags == posflags: - return False - if negflags and negflags & flags: - return False - return True - - def match(self, flags : FlagRecord): - if not FlagMatcher.flagcheck(self.globalflags, self.globalnegflags, self.requireall, flags.globl): - return False - if not FlagMatcher.flagcheck(self.chanflags, self.channegflags, self.requireall, flags.chan): - return False - if not FlagMatcher.flagcheck(self.botflags, self.botnegflags, self.requireall, flags.bot): - return False - return True - diff --git a/src/mod/Makefile.in b/src/mod/Makefile.in index ca742f69a..1d32f9180 100644 --- a/src/mod/Makefile.in +++ b/src/mod/Makefile.in @@ -117,7 +117,7 @@ distclean: fi; \ done -install: install-help install-language +install: install-help install-language install-scripts install-help: @echo "Copying module help files." && \ @@ -169,4 +169,14 @@ install-language: fi; \ done; +install-scripts: + @for i in $(mods); do \ + if test ! "x`echo $(srcdir)/$$i/scripts/*`" = "x$(srcdir)/$$i/scripts/*"; then \ + echo "Installing example $$i scripts"; \ + for s in $(srcdir)/$$i/scripts/*; do \ + $(INSTALL_DATA) $$s $(DEST)/scripts/; \ + done; \ + fi; \ + done; + #safety hash diff --git a/src/mod/python.mod/pycmds.c b/src/mod/python.mod/pycmds.c index 2855ed0a3..6d711ec68 100644 --- a/src/mod/python.mod/pycmds.c +++ b/src/mod/python.mod/pycmds.c @@ -1,3 +1,24 @@ +/* + * pycmds.c -- python.mod python functions + */ + +/* + * Copyright (C) 2020 - 2021 Eggheads Development Team + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ #define PY_SSIZE_T_CLEAN #include #include @@ -25,19 +46,6 @@ static PyObject *EggdropError; //create static Python Exception object static Tcl_Obj *py_to_tcl_obj(PyObject *o); // generic conversion function -static PyObject *py_ircsend(PyObject *self, PyObject *args) { - char *text; - int queuenum; - - if (!PyArg_ParseTuple(args, "si", &text, &queuenum)) { - PyErr_SetString(EggdropError, "wrong number of args"); - return NULL; - } - - dprintf(queuenum, "%s\n", text); - Py_RETURN_NONE; -} - static PyObject *py_displayhook(PyObject *self, PyObject *o) { PyObject *pstr; @@ -357,7 +365,6 @@ static PyObject *py_findtclfunc(PyObject *self, PyObject *args) { } static PyMethodDef MyPyMethods[] = { - {"ircsend", py_ircsend, METH_VARARGS, "Send message to server"}, {"bind", py_bind, METH_VARARGS, "register an eggdrop python bind"}, {"findircuser", py_findircuser, METH_VARARGS, "find an IRC user by nickname and optional channel"}, {"parse_tcl_list", py_parse_tcl_list, METH_VARARGS, "convert a Tcl list string to a Python list"}, diff --git a/src/mod/python.mod/python.c b/src/mod/python.mod/python.c index f5aa2b396..4cb3b70d4 100644 --- a/src/mod/python.mod/python.c +++ b/src/mod/python.mod/python.c @@ -1,5 +1,5 @@ /* - * python.c -- part of python.mod + * python.c -- python interpreter handling for python.mod */ /* @@ -46,46 +46,40 @@ static Function *global = NULL, *irc_funcs = NULL; #include "src/mod/python.mod/pycmds.c" #include "src/mod/python.mod/tclpython.c" -//extern p_tcl_bind_list H_pubm; +EXPORT_SCOPE char *python_start(Function *global_funcs); -int runPythonArgs(const char *script, const char *method, int argc, char **argv); -int runPythonPyArgs(const char *script, const char *method, PyObject *pArgs); - - -/* Calculate the memory we keep allocated. - */ static int python_expmem() { - int size = 0; - - Context; - return size; + return 0; // TODO } +// TODO: Do we really have to exit eggdrop on module load failure? static void init_python() { PyObject *pmodule; PyStatus status; PyConfig config; if (PY_VERSION_HEX < 0x0308) { - fprintf(stderr, "Python: Python version %d is lower than 3.8, not loading Python module", PY_VERSION_HEX); + putlog(LOG_MISC, "*", "Python: Python version %d is lower than 3.8, not loading Python module", PY_VERSION_HEX); return; } - PyConfig_InitPythonConfig(&config); + PyConfig_InitIsolatedConfig(&config); + config.install_signal_handlers = 0; + config.parse_argv = 0; status = PyConfig_SetBytesString(&config, &config.program_name, argv0); if (PyStatus_Exception(status)) { PyConfig_Clear(&config); - fprintf(stderr, "Python: Fatal error: Could not set program base path\n"); + putlog(LOG_MISC, "*", "Python: Fatal error: Could not set program base path"); Py_ExitStatusException(status); } if (PyImport_AppendInittab("eggdrop", &PyInit_eggdrop) == -1) { - fprintf(stderr, "Error: could not extend in-built modules table\n"); + putlog(LOG_MISC, "*", "Python: Error: could not extend in-built modules table"); exit(1); } status = Py_InitializeFromConfig(&config); if (PyStatus_Exception(status)) { PyConfig_Clear(&config); - fprintf(stderr, "Python: Fatal error: Could not initialize config\n"); + putlog(LOG_MISC, "*", "Python: Fatal error: Could not initialize config"); fatal(1); } PyConfig_Clear(&config); @@ -93,13 +87,15 @@ static void init_python() { pmodule = PyImport_ImportModule("eggdrop"); if (!pmodule) { PyErr_Print(); - fprintf(stderr, "Error: could not import module 'eggdrop'\n"); + putlog(LOG_MISC, "*", "Error: could not import module 'eggdrop'"); + fatal(1); } pirp = PyImport_AddModule("__main__"); pglobals = PyModule_GetDict(pirp); PyRun_SimpleString("import sys"); + // TODO: Relies on pwd() staying eggdrop main dir PyRun_SimpleString("sys.path.append(\".\")"); PyRun_SimpleString("import eggdrop"); PyRun_SimpleString("sys.displayhook = eggdrop.__displayhook__"); @@ -114,81 +110,9 @@ static void kill_python() { return; } -int runPythonPyArgs(const char *script, const char *method, PyObject *pArgs) -{ - PyObject *pName, *pModule, *pFunc, *pValue; - - pName = PyUnicode_DecodeFSDefault(script); - /* Error checking of pName left out */ - pModule = PyImport_Import(pName); - Py_DECREF(pName); - - if (pModule != NULL) { - pFunc = PyObject_GetAttrString(pModule, method); - /* pFunc is a new reference */ - if (pFunc && PyCallable_Check(pFunc)) { - pValue = PyObject_CallObject(pFunc, pArgs); - Py_DECREF(pArgs); - if (pValue != NULL) { - printf("Result of call: %ld\n", PyLong_AsLong(pValue)); - Py_DECREF(pValue); - } - else { - Py_DECREF(pFunc); - Py_DECREF(pModule); - PyErr_Print(); - fprintf(stderr,"Call failed\n"); - return 1; - } - } - else { - if (PyErr_Occurred()) - PyErr_Print(); - fprintf(stderr, "Cannot find function \"%s\"\n", method); - } - Py_XDECREF(pFunc); - Py_DECREF(pModule); - } - else { - PyErr_Print(); - fprintf(stderr, "Failed to load \"%s\"\n", script); - return 1; - } - return 0; -} - -int runPythonArgs(const char *script, const char *method, int argc, char **argv) -{ - PyObject *pArgs, *pValue; - - pArgs = PyTuple_New(argc); - for (int i = 0; i < argc; i++) { - pValue = Py_BuildValue("s", argv[i]); - if (!pValue) { - Py_DECREF(pArgs); - fprintf(stderr, "Cannot convert argument at position %d: '%s'\n", i, argv[i]); - return 1; - } - /* pValue reference stolen here: */ - PyTuple_SetItem(pArgs, i, pValue); - } - return runPythonPyArgs(script, method, pArgs); -} - -/* A report on the module status. - * - * details is either 0 or 1: - * 0 - `.status' - * 1 - `.status all' or `.module twitch' - */ static void python_report(int idx, int details) { - if (details) { - int size = python_expmem(); - - dprintf(idx, " Using %d byte%s of memory\n", size, - (size != 1) ? "s" : ""); - } + // TODO } static char *python_close() @@ -201,8 +125,6 @@ static char *python_close() return NULL; } -EXPORT_SCOPE char *python_start(); - static Function python_table[] = { (Function) python_start, (Function) python_close, @@ -225,16 +147,12 @@ char *python_start(Function *global_funcs) module_undepend(MODULE_NAME); return "This module requires Eggdrop 1.9.0 or later."; } + // TODO: Is this dependency necessary? It auto-loads irc.mod, that might be undesired if (!(irc_funcs = module_depend(MODULE_NAME, "irc", 1, 5))) { module_undepend(MODULE_NAME); return "This module requires irc module 1.5 or later."; } -/* - if (!(server_funcs = module_depend(MODULE_NAME, "channels", 1, 1))) { - module_undepend(MODULE_NAME); - return "This module requires channels module 1.1 or later."; - } -*/ + // irc.mod depends on server.mod and channels.mod, so those were implicitely loaded init_python(); @@ -242,4 +160,4 @@ char *python_start(Function *global_funcs) add_builtins(H_dcc, mydcc); add_tcl_commands(my_tcl_cmds); return NULL; -} +} \ No newline at end of file diff --git a/src/mod/python.mod/examples/bestfriend.py b/src/mod/python.mod/scripts/bestfriend.py similarity index 100% rename from src/mod/python.mod/examples/bestfriend.py rename to src/mod/python.mod/scripts/bestfriend.py diff --git a/src/mod/python.mod/examples/greet.py b/src/mod/python.mod/scripts/greet.py similarity index 100% rename from src/mod/python.mod/examples/greet.py rename to src/mod/python.mod/scripts/greet.py diff --git a/src/mod/python.mod/examples/imdb.py b/src/mod/python.mod/scripts/imdb.py similarity index 100% rename from src/mod/python.mod/examples/imdb.py rename to src/mod/python.mod/scripts/imdb.py diff --git a/src/mod/python.mod/examples/listtls.py b/src/mod/python.mod/scripts/listtls.py similarity index 100% rename from src/mod/python.mod/examples/listtls.py rename to src/mod/python.mod/scripts/listtls.py diff --git a/src/mod/python.mod/examples/urlTitle.py b/src/mod/python.mod/scripts/urlTitle.py similarity index 100% rename from src/mod/python.mod/examples/urlTitle.py rename to src/mod/python.mod/scripts/urlTitle.py diff --git a/src/mod/python.mod/tclpython.c b/src/mod/python.mod/tclpython.c index 4aef45cc0..88a89b2d7 100644 --- a/src/mod/python.mod/tclpython.c +++ b/src/mod/python.mod/tclpython.c @@ -1,7 +1,5 @@ /* - * tclpython.c -- part of python.mod - * contains all tcl functions - * + * tclpython.c -- tcl functions for python.mod */ /* * Copyright (C) 2000 - 2023 Eggheads Development Team @@ -23,62 +21,63 @@ static int tcl_pysource STDVAR { - BADARGS(2, 2, " script"); - FILE *fp; - PyObject *pobj, *pstr, *ptype, *pvalue, *ptraceback; - PyObject *pystr, *module_name, *pymodule, *pyfunc, *pyval, *item; + PyObject *pobj, *ptype, *pvalue, *ptraceback; + PyObject *pystr; Py_ssize_t n; const char *res = NULL; - char *res2 = NULL; int i; + BADARGS(2, 2, " script"); + if (!(fp = fopen(argv[1], "r"))) { Tcl_AppendResult(irp, "Error: could not open file ", argv[1],": ", strerror(errno), NULL); return TCL_ERROR; } PyErr_Clear(); + // Always PyNone or NULL on exception pobj = PyRun_FileEx(fp, argv[1], Py_file_input, pglobals, pglobals, 1); - if (pobj) { - pstr = PyObject_Str(pobj); - Tcl_AppendResult(irp, PyUnicode_AsUTF8(pstr), NULL); - Py_DECREF(pstr); - Py_DECREF(pobj); - } else if (PyErr_Occurred()) { + Py_XDECREF(pobj); + + if (PyErr_Occurred()) { PyErr_Fetch(&ptype, &pvalue, &ptraceback); pystr = PyObject_Str(pvalue); Tcl_AppendResult(irp, "Error loading python: ", PyUnicode_AsUTF8(pystr), NULL); - module_name = PyUnicode_FromString("traceback"); - pymodule = PyImport_Import(module_name); - Py_DECREF(module_name); - // format backtrace and print - pyfunc = PyObject_GetAttrString(pymodule, "format_exception"); - if (pyfunc && PyCallable_Check(pyfunc)) { - pyval = PyObject_CallFunctionObjArgs(pyfunc, ptype, pvalue, ptraceback, NULL); - // Check if traceback is a list and handle as such - if (PyList_Check(pyval)) { - n = PyList_Size(pyval); - for (i = 0; i < n; i++) { - item = PyList_GetItem(pyval, i); - pystr = PyObject_Str(item); - //Python returns a const char but we need to remove the \n - res = PyUnicode_AsUTF8(pystr); - if (strlen(res)) { - res2 = (char*) nmalloc(strlen(res)); - // Remove trailing \n - strlcpy(res2, res, strlen(res)-1); - } - putlog(LOG_MISC, "*", "%s", res2); - if (res2 != NULL) { - nfree(res2); + Py_DECREF(pystr); + // top level syntax errors do not have a traceback + if (ptraceback) { + PyObject *module_name, *pymodule, *pyfunc, *pyval, *item; + + module_name = PyUnicode_FromString("traceback"); + pymodule = PyImport_Import(module_name); + Py_DECREF(module_name); + // format backtrace and print + pyfunc = PyObject_GetAttrString(pymodule, "format_exception"); + + if (pyfunc && PyCallable_Check(pyfunc)) { + pyval = PyObject_CallFunctionObjArgs(pyfunc, ptype, pvalue, ptraceback, NULL); + + if (pyval && PyList_Check(pyval)) { + n = PyList_Size(pyval); + for (i = 0; i < n; i++) { + item = PyList_GetItem(pyval, i); + pystr = PyObject_Str(item); + res = PyUnicode_AsUTF8(pystr); + // strip \n + putlog(LOG_MISC, "*", "%.*s", (int)(strlen(res) - 1), res); + Py_DECREF(pystr); } + } else { + putlog(LOG_MISC, "*", "Error fetching python traceback"); } - } else { - pystr = PyObject_Str(pyval); - Tcl_AppendResult(irp, PyUnicode_AsUTF8(pystr), NULL); + Py_XDECREF(pyval); } - Py_DECREF(pyval); + Py_XDECREF(pyfunc); + Py_DECREF(pymodule); } + Py_XDECREF(ptype); + Py_XDECREF(pvalue); + Py_XDECREF(ptraceback); return TCL_ERROR; } return TCL_OK;