diff --git a/coreplugin.py b/coreplugin.py index 7a93138..aa2617c 100644 --- a/coreplugin.py +++ b/coreplugin.py @@ -1,4 +1,7 @@ -## coreplugin.py - Core PyLink plugin +# coreplugin.py - Implements core PyLink functions as a plugin + +import gc +import sys import utils from log import log @@ -107,3 +110,151 @@ def handle_mode(irc, source, command, args): if ('-o', None) in modes and (target == irc.pseudoclient.uid or not utils.isManipulatableClient(irc, target)): irc.proto.modeServer(irc.sid, target, {('+o', None)}) utils.add_hook(handle_mode, 'MODE') + +# Essential, core commands go here so that the "commands" plugin with less-important, +# but still generic functions can be reloaded. + +@utils.add_cmd +def identify(irc, source, args): + """ + + Logs in to PyLink using the configured administrator account.""" + if utils.isChannel(irc.called_by): + irc.msg(irc.called_by, 'Error: This command must be sent in private. ' + '(Would you really type a password inside a channel?)') + return + try: + username, password = args[0], args[1] + except IndexError: + irc.msg(source, 'Error: Not enough arguments.') + return + # Usernames are case-insensitive, passwords are NOT. + if username.lower() == irc.conf['login']['user'].lower() and password == irc.conf['login']['password']: + realuser = irc.conf['login']['user'] + irc.users[source].identified = realuser + irc.msg(source, 'Successfully logged in as %s.' % realuser) + log.info("(%s) Successful login to %r by %s.", + irc.name, username, utils.getHostmask(irc, source)) + else: + irc.msg(source, 'Error: Incorrect credentials.') + u = irc.users[source] + log.warning("(%s) Failed login to %r from %s.", + irc.name, username, utils.getHostmask(irc, source)) + +@utils.add_cmd +def shutdown(irc, source, args): + """takes no arguments. + + Exits PyLink by disconnecting all networks.""" + utils.checkAuthenticated(irc, source, allowOper=False) + u = irc.users[source] + log.info('(%s) SHUTDOWN requested by "%s!%s@%s", exiting...', irc.name, u.nick, + u.ident, u.host) + for ircobj in world.networkobjects.values(): + # Disable auto-connect first by setting the time to negative. + ircobj.serverdata['autoconnect'] = -1 + ircobj.aborted.set() + +def load(irc, source, args): + """. + + Loads a plugin from the plugin folder.""" + utils.checkAuthenticated(irc, source, allowOper=False) + try: + name = args[0] + except IndexError: + irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: plugin name.") + return + if name in world.plugins: + irc.msg(irc.called_by, "Error: %r is already loaded." % name) + return + try: + world.plugins[name] = pl = utils.loadModuleFromFolder(name, world.plugins_folder) + except ImportError as e: + if str(e) == ('No module named %r' % name): + log.exception('Failed to load plugin %r: The plugin could not be found.', name) + else: + log.exception('Failed to load plugin %r: ImportError.', name) + raise + else: + if hasattr(pl, 'main'): + log.debug('Calling main() function of plugin %r', pl) + pl.main(irc) + irc.msg(irc.called_by, "Loaded plugin %r." % name) +utils.add_cmd(load) + +def unload(irc, source, args): + """. + + Unloads a currently loaded plugin.""" + utils.checkAuthenticated(irc, source, allowOper=False) + try: + name = args[0] + except IndexError: + irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: plugin name.") + return + if name in world.plugins: + pl = world.plugins[name] + log.debug('sys.getrefcount of plugin %s is %s', pl, sys.getrefcount(pl)) + # Remove any command functions set by the plugin. + for cmdname, cmdfuncs in world.commands.copy().items(): + log.debug('cmdname=%s, cmdfuncs=%s', cmdname, cmdfuncs) + for cmdfunc in cmdfuncs: + log.debug('__module__ of cmdfunc %s is %s', cmdfunc, cmdfunc.__module__) + if cmdfunc.__module__ == name: + log.debug('Removing %s from world.commands[%s]', cmdfunc, cmdname) + world.commands[cmdname].remove(cmdfunc) + # If the cmdfunc list is empty, remove it. + if not cmdfuncs: + log.debug("Removing world.commands[%s] (it's empty now)", cmdname) + del world.commands[cmdname] + + # Remove any command hooks set by the plugin. + for hookname, hookfuncs in world.hooks.copy().items(): + for hookfunc in hookfuncs: + if hookfunc.__module__ == name: + world.hooks[hookname].remove(hookfunc) + # If the hookfuncs list is empty, remove it. + if not hookfuncs: + del world.hooks[hookname] + + # Remove whois handlers too. + for f in world.whois_handlers: + if f.__module__ == name: + world.whois_handlers.remove(f) + + # Call the die() function in the plugin, if present. + if hasattr(pl, 'die'): + try: + pl.die(irc) + except: # But don't allow it to crash the server. + log.exception('(%s) Error occurred in die() of plugin %s, skipping...', irc.name, pl) + + # Delete it from memory (hopefully). + del world.plugins[name] + if name in sys.modules: + del sys.modules[name] + if name in globals(): + del globals()[name] + + # Garbage collect. + gc.collect() + + irc.msg(irc.called_by, "Unloaded plugin %r." % name) + return True # We succeeded, make it clear (this status is used by reload() below) + else: + irc.msg(irc.called_by, "Unknown plugin %r." % name) +utils.add_cmd(unload) + +@utils.add_cmd +def reload(irc, source, args): + """. + + Loads a plugin from the plugin folder.""" + try: + name = args[0] + except IndexError: + irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: plugin name.") + return + if unload(irc, source, args): + load(irc, source, args) diff --git a/plugins/commands.py b/plugins/commands.py index 34c7218..9b3fab9 100644 --- a/plugins/commands.py +++ b/plugins/commands.py @@ -2,8 +2,6 @@ import sys import os from time import ctime -import itertools -import gc sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import utils @@ -24,33 +22,6 @@ def status(irc, source, args): irc.msg(irc.called_by, 'You are not identified as anyone.') irc.msg(irc.called_by, 'Operator access: \x02%s\x02' % bool(utils.isOper(irc, source))) -@utils.add_cmd -def identify(irc, source, args): - """ - - Logs in to PyLink using the configured administrator account.""" - if utils.isChannel(irc.called_by): - irc.msg(irc.called_by, 'Error: This command must be sent in private. ' - '(Would you really type a password inside a channel?)') - return - try: - username, password = args[0], args[1] - except IndexError: - irc.msg(source, 'Error: Not enough arguments.') - return - # Usernames are case-insensitive, passwords are NOT. - if username.lower() == irc.conf['login']['user'].lower() and password == irc.conf['login']['password']: - realuser = irc.conf['login']['user'] - irc.users[source].identified = realuser - irc.msg(source, 'Successfully logged in as %s.' % realuser) - log.info("(%s) Successful login to %r by %s.", - irc.name, username, utils.getHostmask(irc, source)) - else: - irc.msg(source, 'Error: Incorrect credentials.') - u = irc.users[source] - log.warning("(%s) Failed login to %r from %s.", - irc.name, username, utils.getHostmask(irc, source)) - def listcommands(irc, source, args): """takes no arguments. @@ -179,20 +150,6 @@ def showchan(irc, source, args): f('\x02User list\x02: %s' % ' '.join(nicklist[:20])) nicklist = nicklist[20:] -@utils.add_cmd -def shutdown(irc, source, args): - """takes no arguments. - - Exits PyLink by disconnecting all networks.""" - utils.checkAuthenticated(irc, source, allowOper=False) - u = irc.users[source] - log.info('(%s) SHUTDOWN requested by "%s!%s@%s", exiting...', irc.name, u.nick, - u.ident, u.host) - for ircobj in world.networkobjects.values(): - # Disable auto-connect first by setting the time to negative. - ircobj.serverdata['autoconnect'] = -1 - ircobj.aborted.set() - @utils.add_cmd def version(irc, source, args): """takes no arguments. @@ -208,113 +165,6 @@ def echo(irc, source, args): Echoes the text given.""" irc.msg(irc.called_by, ' '.join(args)) -def load(irc, source, args): - """. - - Loads a plugin from the plugin folder.""" - utils.checkAuthenticated(irc, source, allowOper=False) - try: - name = args[0] - except IndexError: - irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: plugin name.") - return - if name in world.plugins: - irc.msg(irc.called_by, "Error: %r is already loaded." % name) - return - try: - world.plugins[name] = pl = utils.loadModuleFromFolder(name, world.plugins_folder) - except ImportError as e: - if str(e) == ('No module named %r' % name): - log.exception('Failed to load plugin %r: The plugin could not be found.', name) - else: - log.exception('Failed to load plugin %r: ImportError.', name) - raise - else: - if hasattr(pl, 'main'): - log.debug('Calling main() function of plugin %r', pl) - pl.main(irc) - irc.msg(irc.called_by, "Loaded plugin %r." % name) -utils.add_cmd(load) - -def unload(irc, source, args): - """. - - Unloads a currently loaded plugin.""" - utils.checkAuthenticated(irc, source, allowOper=False) - try: - name = args[0] - except IndexError: - irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: plugin name.") - return - if name == 'commands': - irc.msg(irc.called_by, "Error: Cannot unload the commands plugin!") - return - elif name in world.plugins: - pl = world.plugins[name] - log.debug('sys.getrefcount of plugin %s is %s', pl, sys.getrefcount(pl)) - # Remove any command functions set by the plugin. - for cmdname, cmdfuncs in world.commands.copy().items(): - log.debug('cmdname=%s, cmdfuncs=%s', cmdname, cmdfuncs) - for cmdfunc in cmdfuncs: - log.debug('__module__ of cmdfunc %s is %s', cmdfunc, cmdfunc.__module__) - if cmdfunc.__module__ == name: - log.debug('Removing %s from world.commands[%s]', cmdfunc, cmdname) - world.commands[cmdname].remove(cmdfunc) - # If the cmdfunc list is empty, remove it. - if not cmdfuncs: - log.debug("Removing world.commands[%s] (it's empty now)", cmdname) - del world.commands[cmdname] - - # Remove any command hooks set by the plugin. - for hookname, hookfuncs in world.hooks.copy().items(): - for hookfunc in hookfuncs: - if hookfunc.__module__ == name: - world.hooks[hookname].remove(hookfunc) - # If the hookfuncs list is empty, remove it. - if not hookfuncs: - del world.hooks[hookname] - - # Remove whois handlers too. - for f in world.whois_handlers: - if f.__module__ == name: - world.whois_handlers.remove(f) - - # Call the die() function in the plugin, if present. - if hasattr(pl, 'die'): - try: - pl.die(irc) - except: # But don't allow it to crash the server. - log.exception('(%s) Error occurred in die() of plugin %s, skipping...', irc.name, pl) - - # Delete it from memory (hopefully). - del world.plugins[name] - if name in sys.modules: - del sys.modules[name] - if name in globals(): - del globals()[name] - - # Garbage collect. - gc.collect() - - irc.msg(irc.called_by, "Unloaded plugin %r." % name) - return True # We succeeded, make it clear (this status is used by reload() below) - else: - irc.msg(irc.called_by, "Unknown plugin %r." % name) -utils.add_cmd(unload) - -@utils.add_cmd -def reload(irc, source, args): - """. - - Loads a plugin from the plugin folder.""" - try: - name = args[0] - except IndexError: - irc.msg(irc.called_by, "Error: Not enough arguments. Needs 1: plugin name.") - return - if unload(irc, source, args): - load(irc, source, args) - @utils.add_cmd def rehash(irc, source, args): """takes no arguments.