2016-06-21 20:25:47 +02:00
|
|
|
"""
|
|
|
|
corecommands.py - Implements core PyLink commands.
|
|
|
|
"""
|
|
|
|
|
|
|
|
import gc
|
|
|
|
import sys
|
2016-07-08 07:48:38 +02:00
|
|
|
import importlib
|
2016-06-21 20:25:47 +02:00
|
|
|
|
2016-12-10 06:39:52 +01:00
|
|
|
from . import control, login, permissions
|
2016-07-28 02:02:04 +02:00
|
|
|
from pylinkirc import utils, world, conf
|
2016-06-21 20:25:47 +02:00
|
|
|
from pylinkirc.log import log
|
|
|
|
|
|
|
|
# Essential, core commands go here so that the "commands" plugin with less-important,
|
|
|
|
# but still generic functions can be reloaded.
|
|
|
|
|
2016-11-08 06:01:28 +01:00
|
|
|
def _login(irc, source, username):
|
|
|
|
"""Internal function to process logins."""
|
|
|
|
irc.users[source].account = username
|
|
|
|
irc.reply('Successfully logged in as %s.' % username)
|
|
|
|
log.info("(%s) Successful login to %r by %s",
|
2017-06-30 08:01:39 +02:00
|
|
|
irc.name, username, irc.get_hostmask(source))
|
2016-11-08 06:01:28 +01:00
|
|
|
|
|
|
|
def _loginfail(irc, source, username):
|
|
|
|
"""Internal function to process login failures."""
|
2016-12-17 04:06:33 +01:00
|
|
|
irc.error('Incorrect credentials.')
|
2017-06-30 08:01:39 +02:00
|
|
|
log.warning("(%s) Failed login to %r from %s", irc.name, username, irc.get_hostmask(source))
|
2016-11-08 06:01:28 +01:00
|
|
|
|
2016-06-21 20:25:47 +02:00
|
|
|
@utils.add_cmd
|
|
|
|
def identify(irc, source, args):
|
|
|
|
"""<username> <password>
|
|
|
|
|
|
|
|
Logs in to PyLink using the configured administrator account."""
|
2016-07-14 04:32:39 +02:00
|
|
|
if utils.isChannel(irc.called_in):
|
2016-06-21 20:25:47 +02:00
|
|
|
irc.reply('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:
|
2016-07-14 04:20:11 +02:00
|
|
|
irc.reply('Error: Not enough arguments.')
|
2016-06-21 20:25:47 +02:00
|
|
|
return
|
2016-11-08 06:01:28 +01:00
|
|
|
|
2016-11-20 02:00:18 +01:00
|
|
|
# Process new-style accounts.
|
|
|
|
if login.checkLogin(username, password):
|
|
|
|
_login(irc, source, username)
|
|
|
|
return
|
|
|
|
|
|
|
|
# Process legacy logins (login:user).
|
|
|
|
if username.lower() == conf.conf['login'].get('user', '').lower() and password == conf.conf['login'].get('password'):
|
|
|
|
realuser = conf.conf['login']['user']
|
|
|
|
_login(irc, source, realuser)
|
2016-06-21 20:25:47 +02:00
|
|
|
else:
|
2016-11-20 02:00:18 +01:00
|
|
|
# Username not found.
|
|
|
|
_loginfail(irc, source, username)
|
2016-11-08 06:01:28 +01:00
|
|
|
|
2016-06-21 20:25:47 +02:00
|
|
|
|
|
|
|
@utils.add_cmd
|
|
|
|
def shutdown(irc, source, args):
|
|
|
|
"""takes no arguments.
|
|
|
|
|
|
|
|
Exits PyLink by disconnecting all networks."""
|
2016-12-10 06:39:52 +01:00
|
|
|
permissions.checkPermissions(irc, source, ['core.shutdown'])
|
2017-07-13 07:12:32 +02:00
|
|
|
log.info('(%s) SHUTDOWN requested by %s, exiting...', irc.name, irc.get_hostmask(source))
|
2017-07-13 06:50:20 +02:00
|
|
|
control.shutdown(irc=irc)
|
2016-06-21 20:25:47 +02:00
|
|
|
|
|
|
|
@utils.add_cmd
|
|
|
|
def load(irc, source, args):
|
|
|
|
"""<plugin name>.
|
|
|
|
|
|
|
|
Loads a plugin from the plugin folder."""
|
2016-12-10 06:39:52 +01:00
|
|
|
# Note: reload capability is acceptable here, because all it actually does is call
|
|
|
|
# load after unload.
|
|
|
|
permissions.checkPermissions(irc, source, ['core.load', 'core.reload'])
|
|
|
|
|
2016-06-21 20:25:47 +02:00
|
|
|
try:
|
|
|
|
name = args[0]
|
|
|
|
except IndexError:
|
|
|
|
irc.reply("Error: Not enough arguments. Needs 1: plugin name.")
|
|
|
|
return
|
|
|
|
if name in world.plugins:
|
|
|
|
irc.reply("Error: %r is already loaded." % name)
|
|
|
|
return
|
2017-06-30 08:01:39 +02:00
|
|
|
log.info('(%s) Loading plugin %r for %s', irc.name, name, irc.get_hostmask(source))
|
2016-06-21 20:25:47 +02:00
|
|
|
try:
|
2016-07-24 06:59:25 +02:00
|
|
|
world.plugins[name] = pl = utils.loadPlugin(name)
|
2016-06-21 20:25:47 +02:00
|
|
|
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)
|
2017-05-13 04:19:52 +02:00
|
|
|
pl.main(irc=irc)
|
2016-06-21 20:25:47 +02:00
|
|
|
irc.reply("Loaded plugin %r." % name)
|
|
|
|
|
|
|
|
@utils.add_cmd
|
|
|
|
def unload(irc, source, args):
|
|
|
|
"""<plugin name>.
|
|
|
|
|
|
|
|
Unloads a currently loaded plugin."""
|
2016-12-10 06:39:52 +01:00
|
|
|
permissions.checkPermissions(irc, source, ['core.unload', 'core.reload'])
|
2017-02-25 02:49:54 +01:00
|
|
|
|
2016-06-21 20:25:47 +02:00
|
|
|
try:
|
|
|
|
name = args[0]
|
|
|
|
except IndexError:
|
|
|
|
irc.reply("Error: Not enough arguments. Needs 1: plugin name.")
|
|
|
|
return
|
2016-07-11 06:41:08 +02:00
|
|
|
|
|
|
|
# Since we're using absolute imports in 0.9.x+, the module name differs from the actual plugin
|
|
|
|
# name.
|
2017-03-25 22:08:23 +01:00
|
|
|
modulename = utils.PLUGIN_PREFIX + name
|
2016-07-11 06:41:08 +02:00
|
|
|
|
2016-06-21 20:25:47 +02:00
|
|
|
if name in world.plugins:
|
2017-06-30 08:01:39 +02:00
|
|
|
log.info('(%s) Unloading plugin %r for %s', irc.name, name, irc.get_hostmask(source))
|
2016-06-21 20:25:47 +02:00
|
|
|
pl = world.plugins[name]
|
|
|
|
log.debug('sys.getrefcount of plugin %s is %s', pl, sys.getrefcount(pl))
|
|
|
|
|
|
|
|
# Remove any command functions defined by the plugin.
|
|
|
|
for cmdname, cmdfuncs in world.services['pylink'].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__)
|
2016-07-11 06:41:08 +02:00
|
|
|
if cmdfunc.__module__ == modulename:
|
2016-06-21 20:25:47 +02:00
|
|
|
log.debug("Removing %s from world.services['pylink'].commands[%s]", cmdfunc, cmdname)
|
|
|
|
world.services['pylink'].commands[cmdname].remove(cmdfunc)
|
|
|
|
|
|
|
|
# If the cmdfunc list is empty, remove it.
|
|
|
|
if not cmdfuncs:
|
|
|
|
log.debug("Removing world.services['pylink'].commands[%s] (it's empty now)", cmdname)
|
|
|
|
del world.services['pylink'].commands[cmdname]
|
|
|
|
|
|
|
|
# Remove any command hooks set by the plugin.
|
|
|
|
for hookname, hookfuncs in world.hooks.copy().items():
|
|
|
|
for hookfunc in hookfuncs:
|
2016-07-11 06:41:08 +02:00
|
|
|
if hookfunc.__module__ == modulename:
|
2016-06-21 20:25:47 +02:00
|
|
|
world.hooks[hookname].remove(hookfunc)
|
|
|
|
# If the hookfuncs list is empty, remove it.
|
|
|
|
if not hookfuncs:
|
|
|
|
del world.hooks[hookname]
|
|
|
|
|
|
|
|
# Call the die() function in the plugin, if present.
|
|
|
|
if hasattr(pl, 'die'):
|
|
|
|
try:
|
2017-05-13 04:19:52 +02:00
|
|
|
pl.die(irc=irc)
|
2016-06-21 20:25:47 +02:00
|
|
|
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]
|
2016-07-24 06:59:25 +02:00
|
|
|
for n in (name, modulename):
|
|
|
|
if n in sys.modules:
|
|
|
|
del sys.modules[n]
|
|
|
|
if n in globals():
|
|
|
|
del globals()[n]
|
2016-06-21 20:25:47 +02:00
|
|
|
|
|
|
|
# Garbage collect.
|
|
|
|
gc.collect()
|
|
|
|
|
|
|
|
irc.reply("Unloaded plugin %r." % name)
|
|
|
|
return True # We succeeded, make it clear (this status is used by reload() below)
|
|
|
|
else:
|
|
|
|
irc.reply("Unknown plugin %r." % name)
|
|
|
|
|
|
|
|
@utils.add_cmd
|
|
|
|
def reload(irc, source, args):
|
|
|
|
"""<plugin name>.
|
|
|
|
|
|
|
|
Loads a plugin from the plugin folder."""
|
|
|
|
try:
|
|
|
|
name = args[0]
|
|
|
|
except IndexError:
|
|
|
|
irc.reply("Error: Not enough arguments. Needs 1: plugin name.")
|
|
|
|
return
|
2016-12-10 06:39:52 +01:00
|
|
|
|
|
|
|
# Note: these functions do permission checks, so there are none needed here.
|
2016-06-21 20:25:47 +02:00
|
|
|
if unload(irc, source, args):
|
|
|
|
load(irc, source, args)
|
|
|
|
|
|
|
|
@utils.add_cmd
|
|
|
|
def rehash(irc, source, args):
|
|
|
|
"""takes no arguments.
|
|
|
|
|
|
|
|
Reloads the configuration file for PyLink, (dis)connecting added/removed networks.
|
2016-12-10 06:39:52 +01:00
|
|
|
|
|
|
|
Note: plugins must be manually reloaded."""
|
|
|
|
permissions.checkPermissions(irc, source, ['core.rehash'])
|
2016-06-21 20:25:47 +02:00
|
|
|
try:
|
2017-07-13 06:50:20 +02:00
|
|
|
control.rehash()
|
2016-06-21 20:25:47 +02:00
|
|
|
except Exception as e: # Something went wrong, abort.
|
|
|
|
irc.reply("Error loading configuration file: %s: %s" % (type(e).__name__, e))
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
irc.reply("Done.")
|
2017-03-29 07:39:11 +02:00
|
|
|
|
|
|
|
@utils.add_cmd
|
|
|
|
def clearqueue(irc, source, args):
|
|
|
|
"""takes no arguments.
|
|
|
|
|
|
|
|
Clears the outgoing text queue for the current connection."""
|
|
|
|
permissions.checkPermissions(irc, source, ['core.clearqueue'])
|
2017-07-13 07:56:30 +02:00
|
|
|
irc._queue.queue.clear()
|